import gradio as gr import vtracer import os import tempfile def image_to_svg(image, colormode, hierarchical, filter_speckle, color_precision, layer_difference, mode, corner_threshold, length_threshold, splice_threshold, path_precision): """ Converts an input image to an SVG string using vtracer. This function is triggered automatically when any of the input controls change. """ if image is None: return None, "Upload an image to begin.", None # Use a temporary file to handle the image path input_path = image.name # Use a temporary directory for the output to ensure a clean file system with tempfile.TemporaryDirectory() as temp_dir: output_filename = f"{os.path.splitext(os.path.basename(input_path))[0]}.svg" output_path = os.path.join(temp_dir, output_filename) try: # vtracer conversion call with all parameters from the UI vtracer.convert_image_to_svg_py( input_path, output_path, colormode=colormode.lower(), hierarchical=hierarchical.lower(), mode=mode.lower(), filter_speckle=int(filter_speckle), color_precision=int(color_precision), layer_difference=int(layer_difference), corner_threshold=int(corner_threshold), length_threshold=float(length_threshold), splice_threshold=int(splice_threshold), path_precision=int(path_precision), max_iterations=10 # Default from vtracer docs ) # Read the generated SVG content to display in the code block with open(output_path, "r") as f: svg_content = f.read() # Return the path for the file download, the SVG code, and an updated image for display return output_path, svg_content, output_path except Exception as e: # Handle potential errors during conversion error_message = f"An error occurred during conversion: {str(e)}" return None, error_message, None # --- Gradio User Interface --- with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue")) as demo: gr.Markdown("# IMG2SVG: Real-time Image to SVG Converter") gr.Markdown("Upload an image, then adjust the settings below. The SVG will update automatically.") with gr.Row(variant="panel"): # ----- Input Column ----- with gr.Column(scale=1): image_input = gr.Image(type="filepath", label="Upload Image", sources=["upload", "clipboard"]) with gr.Group(): gr.Markdown("### Clustering") colormode = gr.Radio(["Color", "B/W"], value="Color", label="Color Mode") hierarchical = gr.Radio(["Stacked", "Cutout"], value="Stacked", label="Hierarchical Mode") filter_speckle = gr.Slider(0, 128, value=4, step=1, label="Filter Speckle (Cleaner)") color_precision = gr.Slider(0, 8, value=6, step=1, label="Color Precision (More accurate)") layer_difference = gr.Slider(0, 128, value=16, step=1, label="Gradient Step (Less layers)") with gr.Group(): gr.Markdown("### Curve Fitting") mode = gr.Radio(["Spline", "Polygon", "Pixel"], value="Spline", label="Mode") corner_threshold = gr.Slider(0, 180, value=60, step=1, label="Corner Threshold (Smoother)") length_threshold = gr.Slider(0, 10, value=4.0, step=0.5, label="Segment Length (More coarse)") splice_threshold = gr.Slider(0, 180, value=45, step=1, label="Splice Threshold (Less accurate)") path_precision = gr.Slider(1, 8, value=3, step=1, label="Path Precision") # ----- Output Column ----- with gr.Column(scale=2): with gr.Tabs(): with gr.TabItem("SVG Preview"): svg_image_output = gr.Image(label="SVG Preview", interactive=False) with gr.TabItem("SVG Code"): # The traceback error is fixed here by using 'html' which supports XML syntax highlighting. svg_text_output = gr.Code(label="SVG Code", language="html", interactive=False) svg_file_output = gr.File(label="Download SVG") # ----- Documentation ----- with gr.Accordion("How VTracer Works (Technical Details)", open=False): gr.Markdown( """ This tool uses VTracer to convert raster images (like JPG, PNG) into vector graphics (SVG). The process involves several key stages: 1. **Clustering**: The input image is first analyzed and grouped into clusters of similar colors using Hierarchical Clustering. Each cluster will become a separate shape in the final vector image. 2. **Path Walking**: A "walker" algorithm traces the outline of each pixel cluster to create a raw, pixelated path. 3. **Path Simplification**: This raw path is then simplified to remove "jaggies" (pixel staircases) and reduce the number of points by replacing sections of the path with straight lines, as long as it doesn't deviate too much from the original shape. 4. **Path Smoothing**: To create smooth curves, the simplified polygon is refined using a corner-preserving subdivision algorithm. This adds more points to the path where needed, allowing for better curve generation. 5. **Curve Fitting**: Finally, the smoothed path is analyzed to find the best places to "cut" it into segments. Each segment is then approximated with a Bézier curve, resulting in the final, clean SVG output. """ ) # List of all input controls that should trigger a re-render inputs = [ image_input, colormode, hierarchical, filter_speckle, color_precision, layer_difference, mode, corner_threshold, length_threshold, splice_threshold, path_precision ] outputs = [svg_file_output, svg_text_output, svg_image_output] # Connect all input controls to the conversion function. # The 'debounce' parameter adds a 1-second delay after the user stops changing a value # before the function is called, improving performance and user experience. for component in inputs: component.change(fn=image_to_svg, inputs=inputs, outputs=outputs, every=1) # To launch the application demo.launch(debug=True)