Spaces:
Sleeping
Sleeping
| # # 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}"} | |