# # 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""" # # Vector placeholder for {input_raster_path.name} # """ # 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": "...", "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_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'') # svg_content = svg_header + "".join(svg_paths) + "" # # ---- 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": "...", "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_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'') # print(f"[DEBUG] Added contour #{i} with {contour.shape[0]} points to SVG.") # svg_content = svg_header + "".join(svg_paths) + "" # 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_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'') # svg_content = svg_header + "".join(svg_paths) + "" # 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_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'') # svg_content = svg_header + "".join(svg_paths) + "" # 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 tag # svg_content = f''' # # ''' # 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""" # # # # """ # 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""" # # # # # # # """ # # --------------------------- # # Step 4: Assemble SVG # # --------------------------- # svg_content = [ # '', # f'' # ] # svg_content.append(grad_def) # for color, opacity, d in paths: # svg_content.append(f' ') # svg_content.append('') # 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": "", # "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}"}