umuth commited on
Commit
0af58b0
·
verified ·
1 Parent(s): 7e01a4f

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +206 -0
app.py ADDED
@@ -0,0 +1,206 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import os
3
+ import tempfile
4
+ from PIL import Image
5
+ import subprocess
6
+ import logging
7
+ import base64
8
+ import xml.etree.ElementTree as ET
9
+ from concurrent.futures import ThreadPoolExecutor, as_completed
10
+ import re
11
+ from iconsheet import icon_sheet_interface
12
+ import mimetypes
13
+
14
+ # Set up logging
15
+ logging.basicConfig(filename='svgmaker.log', level=logging.INFO,
16
+ format='%(asctime)s - %(levelname)s - %(message)s')
17
+
18
+ def process_single_image(image_file, stroke_width, fill, stroke, opacity, output_size, output_dir):
19
+ try:
20
+ logging.info(f"Processing image: {image_file.name}")
21
+ with Image.open(image_file.name) as image:
22
+ # Calculate new dimensions maintaining aspect ratio
23
+ aspect_ratio = image.width / image.height
24
+ if aspect_ratio > 1:
25
+ new_width = output_size
26
+ new_height = int(output_size / aspect_ratio)
27
+ else:
28
+ new_height = output_size
29
+ new_width = int(output_size * aspect_ratio)
30
+
31
+ logging.info(f"Resizing image to {new_width}x{new_height}")
32
+ # Resize image
33
+ image = image.resize((new_width, new_height), Image.LANCZOS)
34
+
35
+ # Convert to grayscale
36
+ image = image.convert('L')
37
+
38
+ # Save as temporary PGM file
39
+ with tempfile.NamedTemporaryFile(suffix='.pgm', delete=False) as temp_pgm:
40
+ image.save(temp_pgm.name)
41
+ temp_pgm_path = temp_pgm.name
42
+
43
+ # Run potrace with improved settings
44
+ output_svg = os.path.join(output_dir, f"{os.path.splitext(os.path.basename(image_file.name))[0]}.svg")
45
+ subprocess.run([
46
+ "potrace",
47
+ "--svg",
48
+ "--output", output_svg,
49
+ "--turdsize", "2",
50
+ "--alphamax", "1",
51
+ "--opttolerance", "0.2",
52
+ "--unit", "10",
53
+ temp_pgm_path
54
+ ], check=True)
55
+
56
+ # Clean up temporary PGM
57
+ os.unlink(temp_pgm_path)
58
+
59
+ # Modify SVG with user-specified styles and remove unnecessary elements
60
+ with open(output_svg, 'r') as f:
61
+ svg_content = f.read()
62
+
63
+ # Remove namespace prefixes and metadata
64
+ svg_content = re.sub(r'<ns0:', '<', svg_content)
65
+ svg_content = re.sub(r'</ns0:', '</', svg_content)
66
+ svg_content = re.sub(r'xmlns:ns0="[^"]+"', '', svg_content)
67
+ svg_content = re.sub(r'<metadata>.*?</metadata>', '', svg_content, flags=re.DOTALL)
68
+
69
+ # Remove unnecessary group transformation
70
+ svg_content = re.sub(r'<g transform="[^"]+">', '<g>', svg_content)
71
+
72
+ # Update SVG attributes
73
+ svg_tree = ET.fromstring(svg_content)
74
+ svg_tree.set('width', str(new_width))
75
+ svg_tree.set('height', str(new_height))
76
+ svg_tree.set('viewBox', f"0 0 {new_width} {new_height}")
77
+ for path in svg_tree.findall('.//{http://www.w3.org/2000/svg}path'):
78
+ path.set('fill', fill)
79
+ path.set('fill-opacity', str(opacity))
80
+ path.set('stroke', stroke)
81
+ path.set('stroke-width', str(stroke_width))
82
+ path.set('stroke-opacity', str(opacity))
83
+
84
+ svg_content = ET.tostring(svg_tree, encoding='unicode')
85
+
86
+ # Final cleanup
87
+ svg_content = re.sub(r'\s+', ' ', svg_content) # Remove excess whitespace
88
+ svg_content = re.sub(r'> <', '><', svg_content) # Remove space between tags
89
+
90
+ with open(output_svg, 'w') as f:
91
+ f.write(svg_content)
92
+
93
+ # Create a base64 encoded version of the SVG for preview
94
+ svg_base64 = base64.b64encode(svg_content.encode('utf-8')).decode('utf-8')
95
+ preview = f"data:image/svg+xml;base64,{svg_base64}"
96
+
97
+ return output_svg, preview
98
+ except Exception as e:
99
+ logging.error(f"Error processing image {image_file.name}: {str(e)}")
100
+ return None, None
101
+
102
+ def vectorize_icons(images, stroke_width, fill, stroke, opacity, output_size, output_dir, progress=gr.Progress()):
103
+ vectorized_icons = []
104
+ svg_previews = []
105
+ total_images = len(images)
106
+ logging.info(f"Vectorizing {total_images} images")
107
+
108
+ os.makedirs(output_dir, exist_ok=True)
109
+
110
+ with ThreadPoolExecutor() as executor:
111
+ future_to_image = {executor.submit(process_single_image, image, stroke_width, fill, stroke, opacity, output_size, output_dir): image for image in images}
112
+ for i, future in enumerate(as_completed(future_to_image)):
113
+ image = future_to_image[future]
114
+ progress((i + 1) / total_images, f"Vectorizing image {i+1}/{total_images}")
115
+ try:
116
+ result, preview = future.result()
117
+ vectorized_icons.append(result)
118
+ svg_previews.append(preview)
119
+ logging.info(f"Successfully vectorized image {i+1}")
120
+ progress((i + 1) / total_images, f"Vectorized image {i+1}/{total_images}")
121
+ except Exception as e:
122
+ logging.error(f"Error vectorizing icon {image.name}: {str(e)}")
123
+ vectorized_icons.append(None)
124
+ svg_previews.append(None)
125
+ progress((i + 1) / total_images, f"Failed to vectorize image {i+1}/{total_images}: {str(e)}")
126
+
127
+ progress(1.0, "Vectorization complete")
128
+ 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])}")
129
+ return vectorized_icons, svg_previews
130
+
131
+ def create_preview_grid(svg_previews):
132
+ # Dynamically create grid based on number of SVG previews
133
+ num_images = len([preview for preview in svg_previews if preview is not None])
134
+
135
+ # Define the number of columns for the grid
136
+ columns = 4
137
+ grid_html = f'<div style="display: grid; grid-template-columns: repeat({columns}, 1fr); gap: 10px; background-color: #f0f0f0; padding: 20px; border-radius: 10px;">'
138
+
139
+ for preview in svg_previews:
140
+ if preview:
141
+ grid_html += f'''
142
+ <div style="background-color: white; padding: 10px; border-radius: 5px;">
143
+ <img src="{preview}" style="width: 100%; height: auto;" onclick="this.classList.toggle('zoomed')" class="zoomable">
144
+ </div>
145
+ '''
146
+ else:
147
+ grid_html += '<div style="background-color: white; padding: 10px; border-radius: 5px;"><p>Error</p></div>'
148
+
149
+ grid_html += '</div>'
150
+
151
+ # Add CSS for zoom functionality
152
+ grid_html += '''
153
+ <style>
154
+ .zoomable { transition: transform 0.3s ease; }
155
+ .zoomable.zoomed { transform: scale(2.5); z-index: 1000; position: relative; }
156
+ </style>
157
+ '''
158
+
159
+ return grid_html
160
+
161
+
162
+ with gr.Blocks() as app:
163
+ gr.Markdown("# SVG Maker and Icon Sheet Generator")
164
+
165
+ with gr.Tabs():
166
+ with gr.TabItem("SVG Maker"):
167
+ gr.Markdown("# SVG Maker")
168
+
169
+ with gr.Row():
170
+ image_input = gr.File(label="Upload Images", file_count="multiple")
171
+
172
+ with gr.Row():
173
+ stroke_width = gr.Slider(0, 10, label="Stroke Width", value=1)
174
+ fill = gr.ColorPicker(label="Fill Color", value="#000000")
175
+ stroke = gr.ColorPicker(label="Stroke Color", value="#000000")
176
+ opacity = gr.Slider(0, 1, label="Opacity", value=1, step=0.1)
177
+
178
+ with gr.Row():
179
+ output_size = gr.Slider(32, 1024, label="Output Size (px)", value=512, step=32)
180
+ output_dir = gr.Textbox(label="Output Directory", value="output", placeholder="Enter output directory path")
181
+
182
+ vectorize_btn = gr.Button("Vectorize")
183
+ svg_output = gr.File(label="Vectorized Icons", file_count="multiple")
184
+ svg_preview = gr.HTML(label="SVG Previews")
185
+
186
+ def process_and_display(images, stroke_width, fill, stroke, opacity, output_size, output_dir):
187
+ vectorized_icons, svg_previews = vectorize_icons(images, stroke_width, fill, stroke, opacity, output_size, output_dir)
188
+ preview_html = create_preview_grid(svg_previews)
189
+ # Filter out None values from vectorized_icons
190
+ valid_icons = [icon for icon in vectorized_icons if icon is not None]
191
+ return valid_icons, preview_html
192
+
193
+ vectorize_btn.click(process_and_display,
194
+ inputs=[image_input, stroke_width, fill, stroke, opacity, output_size, output_dir],
195
+ outputs=[svg_output, svg_preview])
196
+
197
+ with gr.TabItem("Icon Sheet"):
198
+ mimetypes.add_type('image/svg+xml', '.svg')
199
+ icon_sheet_interface()
200
+
201
+ if __name__ == "__main__":
202
+ try:
203
+ app.launch()
204
+ except Exception as e:
205
+ logging.error(f"Failed to launch app: {str(e)}")
206
+ raise