| import gradio as gr |
| import os |
| import tempfile |
| from PIL import Image |
| import subprocess |
| import logging |
| import base64 |
| import xml.etree.ElementTree as ET |
| from concurrent.futures import ThreadPoolExecutor, as_completed |
| import re |
| from iconsheet import icon_sheet_interface |
| import mimetypes |
|
|
| |
| logging.basicConfig(filename='svgmaker.log', level=logging.INFO, |
| format='%(asctime)s - %(levelname)s - %(message)s') |
|
|
| def process_single_image(image_file, stroke_width, fill, stroke, opacity, output_size, output_dir): |
| try: |
| if image_file is None: |
| raise ValueError("No image file provided.") |
| |
| logging.info(f"Processing image: {image_file.name}") |
| with Image.open(image_file.name) as image: |
| |
| aspect_ratio = image.width / image.height |
| if aspect_ratio > 1: |
| new_width = output_size |
| new_height = int(output_size / aspect_ratio) |
| else: |
| new_height = output_size |
| new_width = int(output_size * aspect_ratio) |
| |
| logging.info(f"Resizing image to {new_width}x{new_height}") |
| |
| image = image.resize((new_width, new_height), Image.LANCZOS) |
| |
| |
| image = image.convert('L') |
| |
| |
| with tempfile.NamedTemporaryFile(suffix='.pgm', delete=False) as temp_pgm: |
| image.save(temp_pgm.name) |
| temp_pgm_path = temp_pgm.name |
|
|
| |
| output_svg = os.path.join(output_dir, f"{os.path.splitext(os.path.basename(image_file.name))[0]}.svg") |
| subprocess.run([ |
| "potrace", |
| temp_pgm_path, |
| "--svg", |
| "-o", output_svg |
| ], check=True) |
|
|
| |
| os.unlink(temp_pgm_path) |
|
|
| |
| with open(output_svg, 'r') as f: |
| svg_content = f.read() |
| svg_base64 = base64.b64encode(svg_content.encode('utf-8')).decode('utf-8') |
| preview = f"data:image/svg+xml;base64,{svg_base64}" |
|
|
| return output_svg, preview |
| except Exception as e: |
| logging.error(f"Error processing image {image_file.name}: {str(e)}") |
| return None, None |
|
|
| def vectorize_icons(images, stroke_width, fill, stroke, opacity, output_size, output_dir, progress=gr.Progress()): |
| if images is None or len(images) == 0: |
| raise ValueError("No images were uploaded. Please upload at least one image.") |
|
|
| vectorized_icons = [] |
| svg_previews = [] |
| total_images = len(images) |
| logging.info(f"Vectorizing {total_images} images") |
|
|
| os.makedirs(output_dir, exist_ok=True) |
|
|
| with ThreadPoolExecutor() as executor: |
| future_to_image = {executor.submit(process_single_image, image, stroke_width, fill, stroke, opacity, output_size, output_dir): image for image in images} |
| for i, future in enumerate(as_completed(future_to_image)): |
| image = future_to_image[future] |
| progress((i + 1) / total_images, f"Vectorizing image {i+1}/{total_images}") |
| try: |
| result, preview = future.result() |
| vectorized_icons.append(result) |
| svg_previews.append(preview) |
| logging.info(f"Successfully vectorized image {i+1}") |
| progress((i + 1) / total_images, f"Vectorized image {i+1}/{total_images}") |
| except Exception as e: |
| logging.error(f"Error vectorizing icon {image.name}: {str(e)}") |
| vectorized_icons.append(None) |
| svg_previews.append(None) |
| progress((i + 1) / total_images, f"Failed to vectorize image {i+1}/{total_images}: {str(e)}") |
|
|
| progress(1.0, "Vectorization complete") |
| logging.info(f"Vectorization complete. Successful: {len([i for i in vectorized_icons if i is not None])}, Failed: {len([i for i in vectorized_icons if i is None])}") |
| return vectorized_icons, svg_previews |
|
|
| def create_preview_grid(svg_previews): |
| |
| num_images = len([preview for preview in svg_previews if preview is not None]) |
| |
| |
| columns = 4 |
| grid_html = f'<div style="display: grid; grid-template-columns: repeat({columns}, 1fr); gap: 10px; background-color: #f0f0f0; padding: 20px; border-radius: 10px;">' |
| |
| for preview in svg_previews: |
| if preview: |
| grid_html += f''' |
| <div style="background-color: white; padding: 10px; border-radius: 5px;"> |
| <img src="{preview}" style="width: 100%; height: auto;" onclick="this.classList.toggle('zoomed')" class="zoomable"> |
| </div> |
| ''' |
| else: |
| grid_html += '<div style="background-color: white; padding: 10px; border-radius: 5px;"><p>Error</p></div>' |
| |
| grid_html += '</div>' |
| |
| |
| grid_html += ''' |
| <style> |
| .zoomable { transition: transform 0.3s ease; } |
| .zoomable.zoomed { transform: scale(2.5); z-index: 1000; position: relative; } |
| </style> |
| ''' |
| |
| return grid_html |
|
|
|
|
| with gr.Blocks() as app: |
| gr.Markdown("# SVG Maker and Icon Sheet Generator") |
| |
| with gr.Tabs(): |
| with gr.TabItem("SVG Maker"): |
| gr.Markdown("# SVG Maker") |
| |
| with gr.Row(): |
| image_input = gr.File(label="Upload Images", file_count="multiple") |
| |
| with gr.Row(): |
| stroke_width = gr.Slider(0, 10, label="Stroke Width", value=1) |
| fill = gr.ColorPicker(label="Fill Color", value="#000000") |
| stroke = gr.ColorPicker(label="Stroke Color", value="#000000") |
| opacity = gr.Slider(0, 1, label="Opacity", value=1, step=0.1) |
| |
| with gr.Row(): |
| output_size = gr.Slider(32, 1024, label="Output Size (px)", value=512, step=32) |
| output_dir = gr.Textbox(label="Output Directory", value="output", placeholder="Enter output directory path") |
| |
| vectorize_btn = gr.Button("Vectorize") |
| svg_output = gr.File(label="Vectorized Icons", file_count="multiple") |
| svg_preview = gr.HTML(label="SVG Previews") |
| |
| def process_and_display(images, stroke_width, fill, stroke, opacity, output_size, output_dir): |
| if images is None or len(images) == 0: |
| raise ValueError("No images were uploaded. Please upload at least one image.") |
| |
| vectorized_icons, svg_previews = vectorize_icons(images, stroke_width, fill, stroke, opacity, output_size, output_dir) |
| preview_html = create_preview_grid(svg_previews) |
| |
| valid_icons = [icon for icon in vectorized_icons if icon is not None] |
| return valid_icons, preview_html |
|
|
| vectorize_btn.click(process_and_display, |
| inputs=[image_input, stroke_width, fill, stroke, opacity, output_size, output_dir], |
| outputs=[svg_output, svg_preview]) |
| |
| with gr.TabItem("Icon Sheet"): |
| mimetypes.add_type('image/svg+xml', '.svg') |
| icon_sheet_interface() |
|
|
| if __name__ == "__main__": |
| try: |
| app.launch() |
| except Exception as e: |
| logging.error(f"Failed to launch app: {str(e)}") |
| raise |
|
|