# # 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""""""
# 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'"
# # ---- 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'"
# 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'"
# 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'"
# 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 = "\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": "