motiongraphics / core /vectorizer.py
karthikeya1212's picture
Upload 26 files
7782338 verified
raw
history blame
35.3 kB
# # app/core/vectorizer.py
# from pathlib import Path
# from typing import Dict
# def vectorize_image(input_raster_path: Path, output_svg_path: Path, options: Dict = None) -> Dict:
# """
# Simple, safe vectorizer stub.
# - Writes a minimal SVG to output_svg_path.
# - You should replace with your real vectorizer (potrace, autotrace, model, etc.)
# Returns: {"success": True, "svg_path": str(output_svg_path)} or {"success": False, "error": "..."}
# """
# options = options or {}
# try:
# input_raster_path = Path(input_raster_path)
# output_svg_path = Path(output_svg_path)
# output_svg_path.parent.mkdir(parents=True, exist_ok=True)
# if not input_raster_path.exists():
# return {"success": False, "error": "input-missing"}
# # Placeholder SVG (safe). Replace with actual vector output.
# sample_svg = f"""<svg xmlns="http://www.w3.org/2000/svg" width="600" height="200">
# <rect width="100%" height="100%" fill="transparent"/>
# <text x="10" y="40" font-family="Arial" font-size="28">Vector placeholder for {input_raster_path.name}</text>
# </svg>"""
# output_svg_path.write_text(sample_svg, encoding="utf-8")
# return {"success": True, "svg_path": str(output_svg_path)}
# except Exception as e:
# return {"success": False, "error": str(e)}
# from pathlib import Path
# from typing import Dict, Optional
# import cv2
# import numpy as np
# def vectorize_image(input_raster_path: Path, output_svg_path: Path = None, options: Optional[Dict] = None) -> Dict:
# """
# Production-ready in-memory vectorizer.
# Converts raster (PNG/JPG) β†’ SVG (string, not saved).
# Args:
# input_raster_path (Path): Path to input raster image.
# output_svg_path (Path, optional): Ignored here, kept for compatibility.
# options (dict, optional): {
# "threshold": int (default=127),
# "simplify_tolerance": float (default=1.5),
# "quality": str (e.g., "low", "medium", "high") - optional
# }
# Returns:
# dict: {"success": True, "svg": "<svg>...</svg>", "width": w, "height": h}
# or {"success": False, "error": "..."}
# """
# options = options or {}
# threshold_value = options.get("threshold", 127)
# simplify_tolerance = options.get("simplify_tolerance", 1.5)
# try:
# input_raster_path = Path(input_raster_path)
# if not input_raster_path.exists():
# return {"success": False, "error": "input file not found"}
# # ---- Load image ----
# img = cv2.imread(str(input_raster_path))
# if img is None:
# return {"success": False, "error": "invalid or unreadable image"}
# gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# _, thresh = cv2.threshold(gray, threshold_value, 255, cv2.THRESH_BINARY_INV)
# # ---- Find contours ----
# contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# h, w = gray.shape[:2]
# # ---- Build SVG in-memory ----
# svg_header = f'<svg xmlns="http://www.w3.org/2000/svg" width="{w}" height="{h}" viewBox="0 0 {w} {h}">'
# svg_paths = []
# for contour in contours:
# contour = cv2.approxPolyDP(contour, simplify_tolerance, True)
# if contour.shape[0] < 2:
# continue
# path_data = "M " + " L ".join(f"{int(x)} {int(y)}" for x, y in contour[:, 0, :]) + " Z"
# svg_paths.append(f'<path d="{path_data}" fill="none" stroke="black" stroke-width="1"/>')
# svg_content = svg_header + "".join(svg_paths) + "</svg>"
# # ---- Return result ----
# return {
# "success": True,
# "svg": svg_content,
# "width": w,
# "height": h
# }
# except Exception as e:
# return {"success": False, "error": str(e)}
# from pathlib import Path
# from typing import Dict, Optional
# import cv2
# import numpy as np
# def vectorize_image(input_raster_path: Path, output_svg_path: Path = None, options: Optional[Dict] = None) -> Dict:
# """
# Production-ready in-memory vectorizer.
# Converts raster (PNG/JPG) β†’ SVG (string, not saved).
# Args:
# input_raster_path (Path): Path to input raster image.
# output_svg_path (Path, optional): Ignored here, kept for compatibility.
# options (dict, optional): {
# "threshold": int (default=127),
# "simplify_tolerance": float (default=1.5),
# "quality": str (e.g., "low", "medium", "high") - optional
# }
# Returns:
# dict: {"success": True, "svg": "<svg>...</svg>", "width": w, "height": h}
# or {"success": False, "error": "..."}
# """
# print("\n[DEBUG] 🧩 Starting vectorize_image()...")
# print(f"[DEBUG] Input raster path: {input_raster_path}")
# print(f"[DEBUG] Output SVG path (if any): {output_svg_path}")
# print(f"[DEBUG] Options: {options}")
# options = options or {}
# threshold_value = options.get("threshold", 127)
# simplify_tolerance = options.get("simplify_tolerance", 1.5)
# print(f"[DEBUG] Using threshold={threshold_value}, simplify_tolerance={simplify_tolerance}")
# try:
# input_raster_path = Path(input_raster_path)
# if not input_raster_path.exists():
# print("[ERROR] ❌ Input file not found.")
# return {"success": False, "error": "input file not found"}
# # ---- Load image ----
# img = cv2.imread(str(input_raster_path))
# if img is None:
# print("[ERROR] ❌ Invalid or unreadable image.")
# return {"success": False, "error": "invalid or unreadable image"}
# print(f"[DEBUG] Image loaded successfully. Shape: {img.shape}")
# gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# print("[DEBUG] Converted image to grayscale.")
# _, thresh = cv2.threshold(gray, threshold_value, 255, cv2.THRESH_BINARY_INV)
# print("[DEBUG] Applied thresholding to generate binary image.")
# # ---- Find contours ----
# contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# h, w = gray.shape[:2]
# print(f"[DEBUG] Found {len(contours)} contours. Image size: {w}x{h}")
# # ---- Build SVG in-memory ----
# svg_header = f'<svg xmlns="http://www.w3.org/2000/svg" width="{w}" height="{h}" viewBox="0 0 {w} {h}">'
# svg_paths = []
# for i, contour in enumerate(contours):
# contour = cv2.approxPolyDP(contour, simplify_tolerance, True)
# if contour.shape[0] < 2:
# print(f"[DEBUG] Skipping small contour #{i} (too few points).")
# continue
# path_data = "M " + " L ".join(f"{int(x)} {int(y)}" for x, y in contour[:, 0, :]) + " Z"
# svg_paths.append(f'<path d="{path_data}" fill="none" stroke="black" stroke-width="1"/>')
# print(f"[DEBUG] Added contour #{i} with {contour.shape[0]} points to SVG.")
# svg_content = svg_header + "".join(svg_paths) + "</svg>"
# print("[DEBUG] SVG generation completed successfully.")
# print(f"[DEBUG] Total paths in SVG: {len(svg_paths)}")
# # ---- Return result ----
# print("[SUCCESS] βœ… Vectorization completed.")
# return {
# "success": True,
# "svg": svg_content,
# "width": w,
# "height": h
# }
# except Exception as e:
# print(f"[ERROR] ❌ Exception in vectorize_image(): {e}")
# return {"success": False, "error": str(e)}
from pathlib import Path
from typing import Dict, Optional
import cv2
import numpy as np
# def vectorize_image(input_raster_path: Path, output_svg_path: Path = None, options: Optional[Dict] = None) -> Dict:
# """
# Converts a raster (PNG/JPG) into a backgroundless SVG that represents visible shapes.
# Supports transparency, soft edges, and grayscale drawings.
# """
# print("\n[DEBUG] 🧩 Starting vectorize_image()...")
# input_raster_path = Path(input_raster_path)
# if not input_raster_path.exists():
# return {"success": False, "error": "input file not found"}
# options = options or {}
# threshold_value = options.get("threshold", 180)
# simplify_tolerance = options.get("simplify_tolerance", 2.0)
# stroke_color = options.get("stroke_color", "black")
# print(f"[DEBUG] Reading image: {input_raster_path}")
# img = cv2.imread(str(input_raster_path), cv2.IMREAD_UNCHANGED)
# if img is None:
# return {"success": False, "error": "cannot read image"}
# h, w = img.shape[:2]
# print(f"[DEBUG] Image shape: {w}x{h}")
# # ---- Handle alpha channel for transparency ----
# if img.shape[2] == 4:
# b, g, r, a = cv2.split(img)
# alpha_mask = a
# print("[DEBUG] Image has alpha channel (transparency detected).")
# else:
# b, g, r = cv2.split(img)
# alpha_mask = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# print("[DEBUG] No alpha channel, using grayscale as mask.")
# # Invert alpha if background is white
# mean_val = np.mean(alpha_mask)
# if mean_val > 200:
# alpha_mask = 255 - alpha_mask
# print("[DEBUG] Inverted mask since background seemed white.")
# # ---- Threshold to get visible content ----
# _, binary = cv2.threshold(alpha_mask, threshold_value, 255, cv2.THRESH_BINARY)
# print("[DEBUG] Applied adaptive threshold.")
# # ---- Find contours ----
# contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# print(f"[DEBUG] Found {len(contours)} contours in drawing.")
# svg_header = f'<svg xmlns="http://www.w3.org/2000/svg" width="{w}" height="{h}" viewBox="0 0 {w} {h}">'
# svg_paths = []
# for i, contour in enumerate(contours):
# contour = cv2.approxPolyDP(contour, simplify_tolerance, True)
# if contour.shape[0] < 3:
# continue
# path_data = "M " + " L ".join(f"{int(x)} {int(y)}" for x, y in contour[:, 0, :]) + " Z"
# svg_paths.append(f'<path d="{path_data}" fill="none" stroke="{stroke_color}" stroke-width="1"/>')
# svg_content = svg_header + "".join(svg_paths) + "</svg>"
# print(f"[DEBUG] Generated SVG with {len(svg_paths)} paths.")
# if len(svg_paths) == 0:
# print("[WARN] No visible content detected in the image.")
# return {
# "success": True,
# "svg": svg_content,
# "width": w,
# "height": h
# }
# def vectorize_image(input_raster_path: Path, output_svg_path: Path = None, options: Optional[Dict] = None) -> Dict:
# """
# Converts a raster (PNG/JPG) into a backgroundless SVG that represents visible shapes.
# Supports transparency, soft edges, grayscale drawings, and colored logos.
# """
# print("\n[DEBUG] 🧩 Starting vectorize_image()...")
# input_raster_path = Path(input_raster_path)
# if not input_raster_path.exists():
# return {"success": False, "error": "input file not found"}
# options = options or {}
# threshold_value = options.get("threshold", 180)
# simplify_tolerance = options.get("simplify_tolerance", 2.0)
# stroke_color = options.get("stroke_color", "black")
# print(f"[DEBUG] Reading image: {input_raster_path}")
# img = cv2.imread(str(input_raster_path), cv2.IMREAD_UNCHANGED)
# if img is None:
# return {"success": False, "error": "cannot read image"}
# h, w = img.shape[:2]
# print(f"[DEBUG] Image shape: {w}x{h}")
# # ---- Handle alpha channel for transparency ----
# if img.shape[2] == 4:
# b, g, r, a = cv2.split(img)
# alpha_mask = a
# print("[DEBUG] Image has alpha channel (transparency detected).")
# else:
# b, g, r = cv2.split(img)
# alpha_mask = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# print("[DEBUG] No alpha channel, using grayscale as mask.")
# # Invert alpha if background is white
# mean_val = np.mean(alpha_mask)
# if mean_val > 200:
# alpha_mask = 255 - alpha_mask
# print("[DEBUG] Inverted mask since background seemed white.")
# # ---- Threshold to get visible content ----
# _, binary = cv2.threshold(alpha_mask, threshold_value, 255, cv2.THRESH_BINARY)
# print("[DEBUG] Applied adaptive threshold.")
# # ---- Find contours ----
# contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# print(f"[DEBUG] Found {len(contours)} contours in drawing.")
# svg_header = f'<svg xmlns="http://www.w3.org/2000/svg" width="{w}" height="{h}" viewBox="0 0 {w} {h}">'
# svg_paths = []
# for i, contour in enumerate(contours):
# contour = cv2.approxPolyDP(contour, simplify_tolerance, True)
# if contour.shape[0] < 3:
# continue
# # ---- Sample mean color inside contour for fill ----
# mask = np.zeros((h, w), dtype=np.uint8)
# cv2.drawContours(mask, [contour], -1, 255, -1)
# mean_color = cv2.mean(img[:, :, :3], mask=mask)
# fill_color = f"rgb({int(mean_color[2])},{int(mean_color[1])},{int(mean_color[0])})" # RGB
# path_data = "M " + " L ".join(f"{int(x)} {int(y)}" for x, y in contour[:, 0, :]) + " Z"
# svg_paths.append(f'<path d="{path_data}" fill="{fill_color}" stroke="{stroke_color}" stroke-width="1"/>')
# svg_content = svg_header + "".join(svg_paths) + "</svg>"
# print(f"[DEBUG] Generated SVG with {len(svg_paths)} paths.")
# if len(svg_paths) == 0:
# print("[WARN] No visible content detected in the image.")
# # Save SVG if path provided
# if output_svg_path:
# Path(output_svg_path).write_text(svg_content)
# print(f"[DEBUG] SVG saved to: {output_svg_path}")
# return {
# "success": True,
# "svg": svg_content,
# "width": w,
# "height": h
# }
# def vectorize_image(input_raster_path: Path, output_svg_path: Path = None, options: Optional[Dict] = None) -> Dict:
# """
# Converts a raster (PNG/JPG) into an SVG that visually matches the original.
# Preserves colors, gradients, and transparency perfectly by embedding the image.
# """
# print("\n[DEBUG] 🧩 Starting vectorize_image() with full color preservation...")
# input_raster_path = Path(input_raster_path)
# if not input_raster_path.exists():
# return {"success": False, "error": "input file not found"}
# # Read image and encode as base64
# import base64
# import mimetypes
# mime_type, _ = mimetypes.guess_type(input_raster_path)
# if mime_type is None:
# mime_type = "image/png"
# with open(input_raster_path, "rb") as f:
# encoded = base64.b64encode(f.read()).decode("utf-8")
# # Read image size
# import cv2
# img = cv2.imread(str(input_raster_path), cv2.IMREAD_UNCHANGED)
# if img is None:
# return {"success": False, "error": "cannot read image"}
# h, w = img.shape[:2]
# print(f"[DEBUG] Image shape: {w}x{h}")
# # Construct SVG that embeds image as <image> tag
# svg_content = f'''<svg xmlns="http://www.w3.org/2000/svg"
# width="{w}" height="{h}" viewBox="0 0 {w} {h}" version="1.1">
# <image href="data:{mime_type};base64,{encoded}" width="{w}" height="{h}" />
# </svg>'''
# if output_svg_path:
# Path(output_svg_path).write_text(svg_content)
# print(f"[DEBUG] Saved SVG with embedded image to: {output_svg_path}")
# print("[DEBUG] βœ… Vectorization complete β€” colors and gradients preserved perfectly.")
# return {
# "success": True,
# "svg": svg_content,
# "width": w,
# "height": h
# }
# def vectorize_image(input_raster_path: Path, output_svg_path: Optional[Path] = None, options: Optional[Dict] = None) -> Dict:
# """
# Converts a raster image (PNG/JPG) into a visually perfect SVG.
# βœ… Preserves all colors, gradients, and transparency by embedding the raster as a base64 image.
# ⚑ Output SVG is fully scalable and compatible with Manim or web rendering.
# """
# print("\n[DEBUG] 🎨 Starting vectorize_image() β€” full visual fidelity mode")
# input_raster_path = Path(input_raster_path)
# if not input_raster_path.exists():
# return {"success": False, "error": "Input file not found"}
# # Detect MIME type
# mime_type, _ = mimetypes.guess_type(input_raster_path)
# if mime_type is None:
# mime_type = "image/png"
# # Read and encode image
# with open(input_raster_path, "rb") as f:
# encoded = base64.b64encode(f.read()).decode("utf-8")
# # Get image size
# img = cv2.imread(str(input_raster_path), cv2.IMREAD_UNCHANGED)
# if img is None:
# return {"success": False, "error": "Cannot read image"}
# h, w = img.shape[:2]
# print(f"[DEBUG] πŸ–ΌοΈ Image dimensions: {w}x{h}")
# # High-quality embedded SVG
# svg_content = f"""<?xml version="1.0" encoding="UTF-8" standalone="no"?>
# <svg xmlns="http://www.w3.org/2000/svg"
# width="{w}px" height="{h}px"
# viewBox="0 0 {w} {h}"
# version="1.1"
# preserveAspectRatio="xMidYMid meet">
# <image width="{w}" height="{h}"
# href="data:{mime_type};base64,{encoded}"
# style="image-rendering: optimizeQuality;"
# preserveAspectRatio="xMidYMid meet"/>
# </svg>
# """
# if output_svg_path:
# Path(output_svg_path).write_text(svg_content, encoding="utf-8")
# print(f"[DEBUG] πŸ’Ύ Saved high-fidelity SVG β†’ {output_svg_path}")
# print("[DEBUG] βœ… Vectorization complete β€” gradients, alpha, and sharp edges preserved perfectly.")
# return {
# "success": True,
# "svg": svg_content,
# "width": w,
# "height": h
# }
# def vectorize_image(input_raster_path: Path, output_svg_path: Optional[Path] = None, options: Optional[Dict] = None) -> Dict:
# """
# Converts a raster image (PNG/JPG/PNG with alpha) into a near visually perfect vector-based SVG.
# βœ… Preserves colors, gradients, and transparency as much as possible.
# ⚑ Output SVG is fully scalable and compatible with Manim or web rendering.
# """
# import cv2, numpy as np, mimetypes
# from pathlib import Path
# from sklearn.cluster import KMeans
# print("\n[DEBUG] 🎨 Starting vectorize_image() β€” high-fidelity gradient-aware mode")
# input_raster_path = Path(input_raster_path)
# if not input_raster_path.exists():
# return {"success": False, "error": "Input file not found"}
# mime_type, _ = mimetypes.guess_type(input_raster_path)
# if mime_type is None:
# mime_type = "image/png"
# # Read with alpha preserved
# img = cv2.imread(str(input_raster_path), cv2.IMREAD_UNCHANGED)
# if img is None:
# return {"success": False, "error": "Cannot read image"}
# if img.shape[2] == 4:
# bgr, alpha = img[:, :, :3], img[:, :, 3]
# else:
# bgr, alpha = img, np.full(img.shape[:2], 255, dtype=np.uint8)
# h, w = bgr.shape[:2]
# print(f"[DEBUG] πŸ–ΌοΈ Image dimensions: {w}x{h}")
# # ---------------------------
# # Step 1: Smart color clustering (using KMeans)
# # ---------------------------
# n_colors = (options or {}).get("colors", 24)
# print(f"[DEBUG] 🎨 Using {n_colors} colors for smoother accuracy")
# data = bgr.reshape((-1, 3))
# kmeans = KMeans(n_clusters=n_colors, n_init=4, random_state=0).fit(data)
# labels = kmeans.labels_.reshape(h, w)
# centers = np.uint8(kmeans.cluster_centers_)
# # ---------------------------
# # Step 2: Build SVG Paths
# # ---------------------------
# print("[DEBUG] ✏️ Extracting color regions...")
# paths = []
# for idx, color in enumerate(centers):
# mask = (labels == idx).astype(np.uint8) * 255
# contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# hex_color = "#{:02x}{:02x}{:02x}".format(*color)
# for cnt in contours:
# if len(cnt) > 3:
# d = "M " + " L ".join(f"{p[0][0]},{p[0][1]}" for p in cnt) + " Z"
# # Apply average alpha for this region
# region_alpha = np.mean(alpha[mask == 255]) / 255.0
# if region_alpha < 0.05: # fully transparent, skip
# continue
# fill_opacity = round(region_alpha, 3)
# paths.append((hex_color, fill_opacity, d))
# if not paths:
# return {"success": False, "error": "No visible regions found (maybe full transparency)"}
# print(f"[DEBUG] βœ… {len(paths)} color regions traced")
# # ---------------------------
# # Step 3: Gradient approximation
# # ---------------------------
# print("[DEBUG] 🌈 Estimating global color gradients...")
# dominant1, dominant2 = centers[0], centers[-1]
# grad_id = "grad_main"
# grad_def = f"""
# <defs>
# <linearGradient id="{grad_id}" x1="0%" y1="0%" x2="100%" y2="100%">
# <stop offset="0%" stop-color="#{dominant1[0]:02x}{dominant1[1]:02x}{dominant1[2]:02x}" />
# <stop offset="100%" stop-color="#{dominant2[0]:02x}{dominant2[1]:02x}{dominant2[2]:02x}" />
# </linearGradient>
# </defs>
# """
# # ---------------------------
# # Step 4: Assemble SVG
# # ---------------------------
# svg_content = [
# '<?xml version="1.0" encoding="UTF-8" standalone="no"?>',
# f'<svg xmlns="http://www.w3.org/2000/svg" width="{w}px" height="{h}px" viewBox="0 0 {w} {h}" version="1.1">'
# ]
# svg_content.append(grad_def)
# for color, opacity, d in paths:
# svg_content.append(f' <path d="{d}" fill="{color}" fill-opacity="{opacity}" stroke="none"/>')
# svg_content.append('</svg>')
# svg_content = "\n".join(svg_content)
# # Save SVG
# if output_svg_path:
# Path(output_svg_path).write_text(svg_content, encoding="utf-8")
# print(f"[DEBUG] πŸ’Ύ Saved vectorized SVG β†’ {output_svg_path}")
# print("[DEBUG] βœ… Vectorization complete β€” transparency, color, and gradient preserved.")
# return {
# "success": True,
# "svg": svg_content,
# "width": w,
# "height": h
# }
# def vectorize_image(input_raster_path: Path, options: Optional[Dict] = None) -> Dict:
# """
# Converts a raster image (PNG/JPG/WEBP) into a high-quality vector SVG using PyVTracer.
# βœ… Uses only PyVTracer
# βœ… Returns SVG directly in memory
# βœ… Fixes all int/float/bool/string conversion issues
# """
# print("\n[DEBUG] 🎨 Starting vectorize_image() β€” final type-safe configuration")
# input_raster_path = Path(input_raster_path)
# if not input_raster_path.exists():
# return {"success": False, "error": "Input file not found"}
# opts = options or {}
# try:
# with Image.open(input_raster_path) as img:
# w, h = img.size
# tracer = pyvtracer.Vtracer()
# # βœ… Correct, final parameter typing
# tracer.input_path = str(input_raster_path)
# tracer.color_mode = str(opts.get("color_mode", "color"))
# tracer.filter_speckle = int(opts.get("filter_speckle", 2))
# tracer.corner_threshold = int(opts.get("corner_threshold", 60))
# tracer.color_precision = int(opts.get("color_precision", 10))
# tracer.layer_difference = int(opts.get("layer_difference", 16))
# tracer.path_precision = int(opts.get("path_precision", 2))
# tracer.length_threshold = int(opts.get("length_threshold", 4))
# tracer.splice_threshold = int(opts.get("splice_threshold", 45))
# tracer.hierarchical = "true" if opts.get("hierarchical", True) else "false"
# tracer.max_iterations = int(opts.get("max_iterations", 10))
# tracer.path_simplify_mode = int(opts.get("path_simplify_mode", 0))
# print("[DEBUG] βœ… All parameters properly typed (int/str).")
# print(f"[DEBUG] πŸš€ Vectorizing: {input_raster_path.name}")
# # πŸŒ€ Get SVG string directly
# if hasattr(tracer, "to_svg_string"):
# svg_content = tracer.to_svg_string()
# else:
# tmp_svg = Path(tempfile.gettempdir()) / f"{input_raster_path.stem}_vtrace.svg"
# tracer.output_path = str(tmp_svg)
# tracer.to_svg()
# svg_content = tmp_svg.read_text(encoding="utf-8")
# tmp_svg.unlink(missing_ok=True)
# print("[DEBUG] βœ… Vectorization complete β€” SVG generated in memory.")
# return {
# "success": True,
# "svg": svg_content,
# "width": w,
# "height": h
# }
# except Exception as e:
# print(f"❌ [ERROR] PyVTracer failed: {e}")
# return {"success": False, "error": f"PyVTracer failed: {e}"}
# from pathlib import Path
# from typing import Optional, Dict, Any
# from PIL import Image
# import tempfile
# # prefer the official vtracer binding from PyPI
# import vtracer
# # mapping and sane defaults (names & types follow vtracer docs)
# DEFAULTS: Dict[str, Any] = {
# "colormode": "color", # "color" or "binary"
# "hierarchical": "stacked", # "stacked" or "cutout"
# "mode": "spline", # "spline", "polygon", or "none"
# "filter_speckle": 2, # int
# "color_precision": 14, # int
# "layer_difference": 6, # int (gradient step)
# "corner_threshold": 50, # int
# "length_threshold": 3.5, # float (in [3.5, 10])
# "max_iterations": 10, # int
# "splice_threshold": 40, # int
# "path_precision": 10 # int (path digits/precision)
# }
# def _normalize_options(opts: Optional[Dict]) -> Dict:
# """
# Keep only acceptable keys with correct types according to official docs.
# """
# opts = opts or {}
# out: Dict[str, Any] = {}
# # strings (colormode, hierarchical, mode)
# out["colormode"] = str(opts.get("colormode", DEFAULTS["colormode"]))
# out["hierarchical"] = str(opts.get("hierarchical", DEFAULTS["hierarchical"]))
# out["mode"] = str(opts.get("mode", DEFAULTS["mode"]))
# # integer parameters
# for k in ("filter_speckle", "color_precision", "layer_difference",
# "corner_threshold", "max_iterations", "splice_threshold",
# "path_precision"):
# out[k] = int(opts.get(k, DEFAULTS[k]))
# # float parameter
# out["length_threshold"] = float(opts.get("length_threshold", DEFAULTS["length_threshold"]))
# return out
# def vectorize_image(input_raster_path: Path, options: Optional[Dict] = None) -> Dict:
# """
# Vectorize using the official vtracer binding.
# Returns:
# {
# "success": True,
# "svg": "<svg ...>",
# "width": w,
# "height": h
# }
# On error:
# {"success": False, "error": "message"}
# """
# input_raster_path = Path(input_raster_path)
# if not input_raster_path.exists():
# return {"success": False, "error": "Input file not found"}
# opts = _normalize_options(options)
# try:
# # read width/height
# with Image.open(input_raster_path) as img:
# w, h = img.size
# # convert to bytes for the raw API if needed
# img_bytes_io = None
# try:
# # ensure a common in-memory format, keep original mode if possible
# img_format = img.format or "PNG"
# img_bytes_io = tempfile.SpooledTemporaryFile() # small-memory friendly
# img.save(img_bytes_io, format=img_format)
# img_bytes_io.seek(0)
# raw_bytes = img_bytes_io.read()
# finally:
# if img_bytes_io is not None:
# img_bytes_io.close()
# # Prefer in-memory API: convert_raw_image_to_svg(bytes, img_format=...)
# if hasattr(vtracer, "convert_raw_image_to_svg"):
# # vtracer expects bytes and an image format string like 'png' or 'jpg'
# img_format_lower = (Image.open(input_raster_path).format or "PNG").lower()
# svg_str = vtracer.convert_raw_image_to_svg(raw_bytes, img_format=img_format_lower, **opts)
# elif hasattr(vtracer, "convert_pixels_to_svg"):
# # alternative: convert pixels to svg β€” slower for large images
# img = Image.open(input_raster_path).convert("RGBA")
# pixels = list(img.getdata())
# svg_str = vtracer.convert_pixels_to_svg(pixels, img.width, img.height, **opts)
# else:
# # last resort: call convert_image_to_svg_py which writes to file; read & delete the file
# tmp_svg = Path(tempfile.gettempdir()) / f"{input_raster_path.stem}_vtrace_temp.svg"
# # convert_image_to_svg_py(inp, out, **kwargs) - writes file
# vtracer.convert_image_to_svg_py(str(input_raster_path), str(tmp_svg), **opts)
# svg_str = tmp_svg.read_text(encoding="utf-8")
# try:
# tmp_svg.unlink()
# except Exception:
# pass
# return {"success": True, "svg": svg_str, "width": w, "height": h}
# except Exception as e:
# # return full error message so you can debug inside logs
# return {"success": False, "error": f"VTracer failed: {e}"}
from pathlib import Path
from typing import Optional, Dict, Any
from PIL import Image, ImageFilter
import tempfile
import vtracer
# DEFAULTS: Dict[str, Any] = {
# "colormode": "color",
# "hierarchical": "stacked",
# "mode": "spline",
# "filter_speckle": 2,
# "color_precision": 14,
# "layer_difference": 2,
# "corner_threshold": 50,
# "length_threshold": 3.5,
# "max_iterations": 10,
# "splice_threshold": 40,
# "path_precision": 10
# }
# pro
# DEFAULTS_PAID: Dict[str, Any] = {
# "colormode": "color",
# "hierarchical": "stacked",
# "mode": "spline", # sharpest edges and curves
# "filter_speckle": 0, # keep all tiny speckles
# "color_precision": 256, # extremely high color accuracy
# "layer_difference": 8, # very fine gradient layers
# "corner_threshold": 100, # preserve almost all corners
# "length_threshold": 0.1, # keep even the tiniest paths
# "max_iterations": 500, # thorough path optimization
# "splice_threshold": 100, # merge paths very carefully
# "path_precision": 128 # ultra-smooth curves and clean edges
# }
DEFAULTS: Dict[str, Any] = {
"colormode": "color",
"hierarchical": "stacked",
"mode": "curve", # sharper edges
"filter_speckle": 1, # minimal removal
"color_precision": 20, # high color accuracy
"layer_difference": 10, # finer gradient layers
"corner_threshold": 35, # preserve corners
"length_threshold": 1.0, # more detail
"max_iterations": 10,
"splice_threshold": 40,
"path_precision": 16 # smoother curves and clean edges
}
def _normalize_options(opts: Optional[Dict]) -> Dict[str, Any]:
opts = opts or {}
out: Dict[str, Any] = {}
out["colormode"] = str(opts.get("colormode", DEFAULTS["colormode"]))
out["hierarchical"] = str(opts.get("hierarchical", DEFAULTS["hierarchical"]))
out["mode"] = str(opts.get("mode", DEFAULTS["mode"]))
for k in ("filter_speckle", "color_precision", "layer_difference",
"corner_threshold", "max_iterations", "splice_threshold",
"path_precision"):
out[k] = int(opts.get(k, DEFAULTS[k]))
out["length_threshold"] = float(opts.get("length_threshold", DEFAULTS["length_threshold"]))
return out
def vectorize_image(input_raster_path: Path, options: Optional[Dict] = None,
preprocess: bool = False) -> Dict[str, Any]:
input_raster_path = Path(input_raster_path)
if not input_raster_path.exists():
return {"success": False, "error": "Input file not found"}
opts = _normalize_options(options)
try:
with Image.open(input_raster_path) as img:
# preserve original mode
orig_mode = img.mode
w, h = img.size
if preprocess:
# optional: add slight blur or noise to reduce banding in gradients
img = img.convert("RGB")
img = img.filter(ImageFilter.GaussianBlur(radius=0.8))
# you could add noise here if needed
img_format = img.format or "PNG"
bytes_io = tempfile.SpooledTemporaryFile()
img.save(bytes_io, format=img_format)
bytes_io.seek(0)
raw_bytes = bytes_io.read()
bytes_io.close()
# Use in-memory API if available
if hasattr(vtracer, "convert_raw_image_to_svg"):
format_lower = img_format.lower()
svg_str = vtracer.convert_raw_image_to_svg(raw_bytes, img_format=format_lower, **opts)
elif hasattr(vtracer, "convert_pixels_to_svg"):
with Image.open(input_raster_path) as img2:
img2 = img2.convert("RGBA")
pixels = list(img2.getdata())
svg_str = vtracer.convert_pixels_to_svg(pixels, img2.width, img2.height, **opts)
else:
tmp_svg = Path(tempfile.gettempdir()) / f"{input_raster_path.stem}_vtrace_temp.svg"
vtracer.convert_image_to_svg_py(str(input_raster_path), str(tmp_svg), **opts)
svg_str = tmp_svg.read_text(encoding="utf-8")
try:
tmp_svg.unlink()
except Exception:
pass
return {
"success": True,
"svg": svg_str,
"width": w,
"height": h,
"mode": orig_mode
}
except Exception as e:
return {"success": False, "error": f"VTracer failed: {e}"}