File size: 8,748 Bytes
dfa364e
6278861
dfa364e
6278861
 
5ce36d4
 
dfa364e
6278861
 
 
 
 
5ce36d4
 
 
 
6278861
 
5ce36d4
6278861
 
 
5ce36d4
 
 
 
 
 
 
 
 
 
6278861
 
 
 
 
 
 
 
 
 
5ce36d4
6278861
 
5ce36d4
 
 
6278861
 
 
 
 
5ce36d4
 
 
 
 
 
 
 
 
 
 
6278861
 
5ce36d4
 
6278861
5ce36d4
6278861
 
 
 
 
 
 
 
5ce36d4
 
6278861
 
 
 
 
 
 
 
 
5ce36d4
6278861
 
5ce36d4
 
6278861
5ce36d4
 
dfa364e
6278861
 
 
 
 
5ce36d4
6278861
5ce36d4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
dfa364e
6278861
5ce36d4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6278861
 
5ce36d4
 
 
 
 
6278861
5ce36d4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6278861
5ce36d4
 
 
 
 
 
 
dfa364e
5ce36d4
dfa364e
5ce36d4
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
import gradio as gr
import cv2
import numpy as np
from PIL import Image
import io
import os
import tempfile

def image_to_line_art_svg(image, line_thickness=2, blur_value=7, threshold_value=7):
    """
    Convert an image to line art and return as SVG
    """
    try:
        # Ensure blur_value and threshold_value are odd
        blur_value = blur_value if blur_value % 2 == 1 else blur_value + 1
        threshold_value = threshold_value if threshold_value % 2 == 1 else threshold_value + 1
        
        # Convert PIL image to numpy array
        if isinstance(image, Image.Image):
            image_array = np.array(image.convert('RGB'))
        else:
            image_array = image
        
        # Resize image if too large (to prevent memory issues)
        height, width = image_array.shape[:2]
        max_dimension = 1024
        if max(height, width) > max_dimension:
            scale = max_dimension / max(height, width)
            new_width = int(width * scale)
            new_height = int(height * scale)
            image_array = cv2.resize(image_array, (new_width, new_height))
            height, width = new_height, new_width
        
        # Convert to grayscale
        gray = cv2.cvtColor(image_array, cv2.COLOR_RGB2GRAY)
        
        # Apply median filter to reduce noise
        gray_blur = cv2.medianBlur(gray, blur_value)
        
        # Create an edge mask using adaptive threshold
        edges = cv2.adaptiveThreshold(gray_blur, 255, cv2.ADAPTIVE_THRESH_MEAN_C, 
                                     cv2.THRESH_BINARY, threshold_value, blur_value)
        
        # Convert edges back to 3-channel for display
        edges_colored = cv2.cvtColor(edges, cv2.COLOR_GRAY2RGB)
        
        # Invert edges so lines are black on white background
        edges = cv2.bitwise_not(edges)
        
        # Find contours
        contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        
        # Create SVG content
        svg_content = f'''<?xml version="1.0" encoding="UTF-8"?>
<svg width="{width}" height="{height}" viewBox="0 0 {width} {height}" xmlns="http://www.w3.org/2000/svg">
<style>
    .line-art {{ 
        fill: none; 
        stroke: #000000; 
        stroke-width: {line_thickness}; 
        stroke-linecap: round; 
        stroke-linejoin: round; 
    }}
</style>
<rect width="100%" height="100%" fill="white"/>'''
        
        # Add paths for each contour
        for contour in contours:
            if len(contour) > 5:  # Only include contours with sufficient points
                # Simplify contour to reduce SVG size
                epsilon = 0.01 * cv2.arcLength(contour, True)
                simplified_contour = cv2.approxPolyDP(contour, epsilon, True)
                
                if len(simplified_contour) > 2:
                    path_data = f"M {simplified_contour[0][0][0]} {simplified_contour[0][0][1]}"
                    
                    for point in simplified_contour[1:]:
                        path_data += f" L {point[0][0]} {point[0][1]}"
                    
                    # Close the path if it forms a closed shape
                    if len(simplified_contour) > 3:
                        path_data += " Z"
                    
                    svg_content += f'\n<path d="{path_data}" class="line-art"/>'
        
        svg_content += '\n</svg>'
        
        return svg_content, edges_colored
        
    except Exception as e:
        # Return error SVG
        error_svg = f'''<?xml version="1.0" encoding="UTF-8"?>
<svg width="400" height="200" xmlns="http://www.w3.org/2000/svg">
<rect width="100%" height="100%" fill="white"/>
<text x="200" y="100" text-anchor="middle" fill="red" font-family="Arial" font-size="16">Error: {str(e)}</text>
</svg>'''
        error_image = np.ones((200, 400, 3), dtype=np.uint8) * 255
        return error_svg, error_image

def process_image(image, line_thickness, blur_value, threshold_value):
    """
    Process the uploaded image and return both SVG content and preview image
    """
    if image is None:
        return None, None, "❌ Please upload an image first."
    
    try:
        svg_content, preview_image = image_to_line_art_svg(
            image, line_thickness, blur_value, threshold_value
        )
        
        # Create temporary file for SVG download
        temp_dir = tempfile.gettempdir()
        svg_path = os.path.join(temp_dir, "line_art.svg")
        
        with open(svg_path, "w", encoding="utf-8") as f:
            f.write(svg_content)
        
        return preview_image, svg_path, "βœ… Conversion completed successfully!"
        
    except Exception as e:
        error_image = np.ones((200, 400, 3), dtype=np.uint8) * 255
        return error_image, None, f"❌ Error processing image: {str(e)}"

# Create Gradio interface
with gr.Blocks(
    title="Image to Line Art SVG Converter",
    theme=gr.themes.Soft(),
    css="""
    .gradio-container {
        max-width: 1200px !important;
    }
    """
) as demo:
    
    gr.HTML("""
    <div style="text-align: center; padding: 20px;">
        <h1>🎨 Image to Line Art SVG Converter</h1>
        <p style="font-size: 18px; color: #666;">
            Convert your images into beautiful line art and download as scalable SVG files!
        </p>
    </div>
    """)
    
    with gr.Row():
        with gr.Column(scale=1):
            gr.Markdown("### πŸ“€ Upload & Settings")
            
            image_input = gr.Image(
                label="Upload Image", 
                type="pil",
                sources=["upload"],
                height=300
            )
            
            with gr.Group():
                gr.Markdown("**Adjustment Controls**")
                line_thickness = gr.Slider(
                    minimum=0.5, 
                    maximum=5, 
                    value=2, 
                    step=0.1,
                    label="πŸ“ Line Thickness",
                    info="Controls the width of the drawn lines"
                )
                blur_value = gr.Slider(
                    minimum=3, 
                    maximum=15, 
                    value=7, 
                    step=2,
                    label="πŸŒ€ Blur Amount",
                    info="Higher values = smoother lines (must be odd)"
                )
                threshold_value = gr.Slider(
                    minimum=3, 
                    maximum=15, 
                    value=7, 
                    step=2,
                    label="🎯 Edge Sensitivity",
                    info="Lower values = more details (must be odd)"
                )
            
            convert_btn = gr.Button(
                "πŸš€ Convert to Line Art", 
                variant="primary",
                size="lg"
            )
        
        with gr.Column(scale=1):
            gr.Markdown("### πŸ‘οΈ Preview & Download")
            
            preview_output = gr.Image(
                label="Line Art Preview",
                height=300
            )
            
            status_output = gr.Textbox(
                label="Status", 
                interactive=False,
                show_label=False
            )
            
            download_output = gr.File(
                label="πŸ“₯ Download SVG File",
                file_count="single"
            )
    
    # Add usage instructions
    with gr.Row():
        gr.Markdown("""
        ### πŸ“ How to Use:
        1. **Upload** an image using the upload area
        2. **Adjust** the settings to fine-tune the line art (optional)
        3. **Click** "Convert to Line Art" to process
        4. **Download** your SVG file to use anywhere!
        
        ### πŸ’‘ Tips:
        - **Line Thickness**: Start with 2, increase for bolder lines
        - **Blur Amount**: Higher values create cleaner, simpler lines  
        - **Edge Sensitivity**: Lower values capture more details
        - SVG files are scalable and perfect for print or web use!
        """)
    
    # Event handlers
    convert_btn.click(
        fn=process_image,
        inputs=[image_input, line_thickness, blur_value, threshold_value],
        outputs=[preview_output, download_output, status_output],
        show_progress=True
    )
    
    # Auto-process when image is uploaded (optional)
    image_input.change(
        fn=lambda img: process_image(img, 2, 7, 7) if img is not None else (None, None, ""),
        inputs=[image_input],
        outputs=[preview_output, download_output, status_output],
        show_progress=False
    )

# Launch settings
if __name__ == "__main__":
    demo.launch(
        server_name="0.0.0.0",
        server_port=7860,
        share=False
    )