Avi3738 commited on
Commit
5ce36d4
·
verified ·
1 Parent(s): b08cae0

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +175 -93
app.py CHANGED
@@ -3,19 +3,34 @@ import cv2
3
  import numpy as np
4
  from PIL import Image
5
  import io
6
- import base64
 
7
 
8
  def image_to_line_art_svg(image, line_thickness=2, blur_value=7, threshold_value=7):
9
  """
10
  Convert an image to line art and return as SVG
11
  """
12
  try:
 
 
 
 
13
  # Convert PIL image to numpy array
14
  if isinstance(image, Image.Image):
15
- image_array = np.array(image)
16
  else:
17
  image_array = image
18
 
 
 
 
 
 
 
 
 
 
 
19
  # Convert to grayscale
20
  gray = cv2.cvtColor(image_array, cv2.COLOR_RGB2GRAY)
21
 
@@ -26,35 +41,34 @@ def image_to_line_art_svg(image, line_thickness=2, blur_value=7, threshold_value
26
  edges = cv2.adaptiveThreshold(gray_blur, 255, cv2.ADAPTIVE_THRESH_MEAN_C,
27
  cv2.THRESH_BINARY, threshold_value, blur_value)
28
 
29
- # Convert edges back to 3-channel for consistency
30
  edges_colored = cv2.cvtColor(edges, cv2.COLOR_GRAY2RGB)
31
 
 
 
 
32
  # Find contours
33
  contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
34
 
35
- # Get image dimensions
36
- height, width = gray.shape
37
-
38
  # Create SVG content
39
  svg_content = f'''<?xml version="1.0" encoding="UTF-8"?>
40
- <svg width="{width}" height="{height}" xmlns="http://www.w3.org/2000/svg">
41
- <defs>
42
- <style>
43
- .line-art {{
44
- fill: none;
45
- stroke: black;
46
- stroke-width: {line_thickness};
47
- stroke-linecap: round;
48
- stroke-linejoin: round;
49
- }}
50
- </style>
51
- </defs>'''
52
 
53
  # Add paths for each contour
54
- for i, contour in enumerate(contours):
55
- if len(contour) > 10: # Only include contours with sufficient points
56
  # Simplify contour to reduce SVG size
57
- epsilon = 0.005 * cv2.arcLength(contour, True)
58
  simplified_contour = cv2.approxPolyDP(contour, epsilon, True)
59
 
60
  if len(simplified_contour) > 2:
@@ -63,102 +77,170 @@ def image_to_line_art_svg(image, line_thickness=2, blur_value=7, threshold_value
63
  for point in simplified_contour[1:]:
64
  path_data += f" L {point[0][0]} {point[0][1]}"
65
 
66
- # Close the path if it's a closed contour
67
- if cv2.contourArea(simplified_contour) > 100:
68
  path_data += " Z"
69
 
70
  svg_content += f'\n<path d="{path_data}" class="line-art"/>'
71
 
72
  svg_content += '\n</svg>'
73
 
74
- # Also return the processed image for preview
75
  return svg_content, edges_colored
76
 
77
  except Exception as e:
 
78
  error_svg = f'''<?xml version="1.0" encoding="UTF-8"?>
79
  <svg width="400" height="200" xmlns="http://www.w3.org/2000/svg">
80
- <text x="200" y="100" text-anchor="middle" fill="red">Error: {str(e)}</text>
 
81
  </svg>'''
82
- return error_svg, np.zeros((200, 400, 3), dtype=np.uint8)
 
83
 
84
  def process_image(image, line_thickness, blur_value, threshold_value):
85
  """
86
  Process the uploaded image and return both SVG content and preview image
87
  """
88
  if image is None:
89
- return None, None, "Please upload an image first."
90
 
91
- svg_content, preview_image = image_to_line_art_svg(
92
- image, line_thickness, blur_value, threshold_value
93
- )
94
-
95
- # Save SVG to a temporary file for download
96
- svg_filename = "line_art.svg"
97
- with open(svg_filename, "w") as f:
98
- f.write(svg_content)
99
-
100
- return preview_image, svg_filename, "Conversion completed successfully!"
 
 
 
 
 
 
 
101
 
102
  # Create Gradio interface
103
- def create_interface():
104
- with gr.Blocks(title="Image to Line Art SVG Converter", theme=gr.themes.Soft()) as demo:
105
- gr.Markdown("# 🎨 Image to Line Art SVG Converter")
106
- gr.Markdown("Upload an image and convert it to clean line art in SVG format!")
107
-
108
- with gr.Row():
109
- with gr.Column():
110
- image_input = gr.Image(
111
- label="Upload Image",
112
- type="pil",
113
- sources=["upload", "webcam"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
114
  )
115
-
116
- with gr.Accordion("Advanced Settings", open=False):
117
- line_thickness = gr.Slider(
118
- minimum=1,
119
- maximum=5,
120
- value=2,
121
- step=0.5,
122
- label="Line Thickness"
123
- )
124
- blur_value = gr.Slider(
125
- minimum=3,
126
- maximum=15,
127
- value=7,
128
- step=2,
129
- label="Blur Value (must be odd)"
130
- )
131
- threshold_value = gr.Slider(
132
- minimum=3,
133
- maximum=15,
134
- value=7,
135
- step=2,
136
- label="Threshold Value (must be odd)"
137
- )
138
-
139
- convert_btn = gr.Button("Convert to Line Art", variant="primary")
140
 
141
- with gr.Column():
142
- preview_output = gr.Image(label="Line Art Preview")
143
- status_output = gr.Textbox(label="Status", interactive=False)
144
- download_output = gr.File(label="Download SVG")
145
-
146
- # Event handler
147
- convert_btn.click(
148
- fn=process_image,
149
- inputs=[image_input, line_thickness, blur_value, threshold_value],
150
- outputs=[preview_output, download_output, status_output]
151
- )
152
 
153
- # Example images
154
- gr.Markdown("## Example Usage")
155
- gr.Markdown("1. Upload an image using the upload button or webcam")
156
- gr.Markdown("2. Adjust the settings if needed (optional)")
157
- gr.Markdown("3. Click 'Convert to Line Art' to process")
158
- gr.Markdown("4. Download the generated SVG file")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
 
160
- return demo
 
 
 
 
 
 
161
 
 
162
  if __name__ == "__main__":
163
- demo = create_interface()
164
- demo.launch(share=True)
 
 
 
 
3
  import numpy as np
4
  from PIL import Image
5
  import io
6
+ import os
7
+ import tempfile
8
 
9
  def image_to_line_art_svg(image, line_thickness=2, blur_value=7, threshold_value=7):
10
  """
11
  Convert an image to line art and return as SVG
12
  """
13
  try:
14
+ # Ensure blur_value and threshold_value are odd
15
+ blur_value = blur_value if blur_value % 2 == 1 else blur_value + 1
16
+ threshold_value = threshold_value if threshold_value % 2 == 1 else threshold_value + 1
17
+
18
  # Convert PIL image to numpy array
19
  if isinstance(image, Image.Image):
20
+ image_array = np.array(image.convert('RGB'))
21
  else:
22
  image_array = image
23
 
24
+ # Resize image if too large (to prevent memory issues)
25
+ height, width = image_array.shape[:2]
26
+ max_dimension = 1024
27
+ if max(height, width) > max_dimension:
28
+ scale = max_dimension / max(height, width)
29
+ new_width = int(width * scale)
30
+ new_height = int(height * scale)
31
+ image_array = cv2.resize(image_array, (new_width, new_height))
32
+ height, width = new_height, new_width
33
+
34
  # Convert to grayscale
35
  gray = cv2.cvtColor(image_array, cv2.COLOR_RGB2GRAY)
36
 
 
41
  edges = cv2.adaptiveThreshold(gray_blur, 255, cv2.ADAPTIVE_THRESH_MEAN_C,
42
  cv2.THRESH_BINARY, threshold_value, blur_value)
43
 
44
+ # Convert edges back to 3-channel for display
45
  edges_colored = cv2.cvtColor(edges, cv2.COLOR_GRAY2RGB)
46
 
47
+ # Invert edges so lines are black on white background
48
+ edges = cv2.bitwise_not(edges)
49
+
50
  # Find contours
51
  contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
52
 
 
 
 
53
  # Create SVG content
54
  svg_content = f'''<?xml version="1.0" encoding="UTF-8"?>
55
+ <svg width="{width}" height="{height}" viewBox="0 0 {width} {height}" xmlns="http://www.w3.org/2000/svg">
56
+ <style>
57
+ .line-art {{
58
+ fill: none;
59
+ stroke: #000000;
60
+ stroke-width: {line_thickness};
61
+ stroke-linecap: round;
62
+ stroke-linejoin: round;
63
+ }}
64
+ </style>
65
+ <rect width="100%" height="100%" fill="white"/>'''
 
66
 
67
  # Add paths for each contour
68
+ for contour in contours:
69
+ if len(contour) > 5: # Only include contours with sufficient points
70
  # Simplify contour to reduce SVG size
71
+ epsilon = 0.01 * cv2.arcLength(contour, True)
72
  simplified_contour = cv2.approxPolyDP(contour, epsilon, True)
73
 
74
  if len(simplified_contour) > 2:
 
77
  for point in simplified_contour[1:]:
78
  path_data += f" L {point[0][0]} {point[0][1]}"
79
 
80
+ # Close the path if it forms a closed shape
81
+ if len(simplified_contour) > 3:
82
  path_data += " Z"
83
 
84
  svg_content += f'\n<path d="{path_data}" class="line-art"/>'
85
 
86
  svg_content += '\n</svg>'
87
 
 
88
  return svg_content, edges_colored
89
 
90
  except Exception as e:
91
+ # Return error SVG
92
  error_svg = f'''<?xml version="1.0" encoding="UTF-8"?>
93
  <svg width="400" height="200" xmlns="http://www.w3.org/2000/svg">
94
+ <rect width="100%" height="100%" fill="white"/>
95
+ <text x="200" y="100" text-anchor="middle" fill="red" font-family="Arial" font-size="16">Error: {str(e)}</text>
96
  </svg>'''
97
+ error_image = np.ones((200, 400, 3), dtype=np.uint8) * 255
98
+ return error_svg, error_image
99
 
100
  def process_image(image, line_thickness, blur_value, threshold_value):
101
  """
102
  Process the uploaded image and return both SVG content and preview image
103
  """
104
  if image is None:
105
+ return None, None, "Please upload an image first."
106
 
107
+ try:
108
+ svg_content, preview_image = image_to_line_art_svg(
109
+ image, line_thickness, blur_value, threshold_value
110
+ )
111
+
112
+ # Create temporary file for SVG download
113
+ temp_dir = tempfile.gettempdir()
114
+ svg_path = os.path.join(temp_dir, "line_art.svg")
115
+
116
+ with open(svg_path, "w", encoding="utf-8") as f:
117
+ f.write(svg_content)
118
+
119
+ return preview_image, svg_path, "✅ Conversion completed successfully!"
120
+
121
+ except Exception as e:
122
+ error_image = np.ones((200, 400, 3), dtype=np.uint8) * 255
123
+ return error_image, None, f"❌ Error processing image: {str(e)}"
124
 
125
  # Create Gradio interface
126
+ with gr.Blocks(
127
+ title="Image to Line Art SVG Converter",
128
+ theme=gr.themes.Soft(),
129
+ css="""
130
+ .gradio-container {
131
+ max-width: 1200px !important;
132
+ }
133
+ """
134
+ ) as demo:
135
+
136
+ gr.HTML("""
137
+ <div style="text-align: center; padding: 20px;">
138
+ <h1>🎨 Image to Line Art SVG Converter</h1>
139
+ <p style="font-size: 18px; color: #666;">
140
+ Convert your images into beautiful line art and download as scalable SVG files!
141
+ </p>
142
+ </div>
143
+ """)
144
+
145
+ with gr.Row():
146
+ with gr.Column(scale=1):
147
+ gr.Markdown("### 📤 Upload & Settings")
148
+
149
+ image_input = gr.Image(
150
+ label="Upload Image",
151
+ type="pil",
152
+ sources=["upload"],
153
+ height=300
154
+ )
155
+
156
+ with gr.Group():
157
+ gr.Markdown("**Adjustment Controls**")
158
+ line_thickness = gr.Slider(
159
+ minimum=0.5,
160
+ maximum=5,
161
+ value=2,
162
+ step=0.1,
163
+ label="📏 Line Thickness",
164
+ info="Controls the width of the drawn lines"
165
+ )
166
+ blur_value = gr.Slider(
167
+ minimum=3,
168
+ maximum=15,
169
+ value=7,
170
+ step=2,
171
+ label="🌀 Blur Amount",
172
+ info="Higher values = smoother lines (must be odd)"
173
+ )
174
+ threshold_value = gr.Slider(
175
+ minimum=3,
176
+ maximum=15,
177
+ value=7,
178
+ step=2,
179
+ label="🎯 Edge Sensitivity",
180
+ info="Lower values = more details (must be odd)"
181
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
182
 
183
+ convert_btn = gr.Button(
184
+ "🚀 Convert to Line Art",
185
+ variant="primary",
186
+ size="lg"
187
+ )
 
 
 
 
 
 
188
 
189
+ with gr.Column(scale=1):
190
+ gr.Markdown("### 👁️ Preview & Download")
191
+
192
+ preview_output = gr.Image(
193
+ label="Line Art Preview",
194
+ height=300
195
+ )
196
+
197
+ status_output = gr.Textbox(
198
+ label="Status",
199
+ interactive=False,
200
+ show_label=False
201
+ )
202
+
203
+ download_output = gr.File(
204
+ label="📥 Download SVG File",
205
+ file_count="single"
206
+ )
207
+
208
+ # Add usage instructions
209
+ with gr.Row():
210
+ gr.Markdown("""
211
+ ### 📝 How to Use:
212
+ 1. **Upload** an image using the upload area
213
+ 2. **Adjust** the settings to fine-tune the line art (optional)
214
+ 3. **Click** "Convert to Line Art" to process
215
+ 4. **Download** your SVG file to use anywhere!
216
+
217
+ ### 💡 Tips:
218
+ - **Line Thickness**: Start with 2, increase for bolder lines
219
+ - **Blur Amount**: Higher values create cleaner, simpler lines
220
+ - **Edge Sensitivity**: Lower values capture more details
221
+ - SVG files are scalable and perfect for print or web use!
222
+ """)
223
+
224
+ # Event handlers
225
+ convert_btn.click(
226
+ fn=process_image,
227
+ inputs=[image_input, line_thickness, blur_value, threshold_value],
228
+ outputs=[preview_output, download_output, status_output],
229
+ show_progress=True
230
+ )
231
 
232
+ # Auto-process when image is uploaded (optional)
233
+ image_input.change(
234
+ fn=lambda img: process_image(img, 2, 7, 7) if img is not None else (None, None, ""),
235
+ inputs=[image_input],
236
+ outputs=[preview_output, download_output, status_output],
237
+ show_progress=False
238
+ )
239
 
240
+ # Launch settings
241
  if __name__ == "__main__":
242
+ demo.launch(
243
+ server_name="0.0.0.0",
244
+ server_port=7860,
245
+ share=False
246
+ )