pg0 commited on
Commit
ff33fc3
·
verified ·
1 Parent(s): 9c6ac90

Add 2 files

Browse files
Files changed (2) hide show
  1. README.md +7 -5
  2. index.html +486 -19
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Bitmap To Vector2
3
- emoji: 👀
4
- colorFrom: red
5
- colorTo: red
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: bitmap-to-vector2
3
+ emoji: 🐳
4
+ colorFrom: gray
5
+ colorTo: blue
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite
10
  ---
11
 
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
index.html CHANGED
@@ -1,19 +1,486 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Bitmap to Vector Converter</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script src="https://cdn.jsdelivr.net/npm/potrace@2.1.8/dist/potrace.min.js"></script>
9
+ <script src="https://cdn.jsdelivr.net/npm/svg.js@3.0.17/dist/svg.min.js"></script>
10
+ <style>
11
+ .dropzone {
12
+ border: 2px dashed #ccc;
13
+ transition: all 0.3s;
14
+ }
15
+ .dropzone.active {
16
+ border-color: #4f46e5;
17
+ background-color: #eef2ff;
18
+ }
19
+ #canvas-container {
20
+ position: relative;
21
+ }
22
+ #preview-canvas, #vector-canvas {
23
+ position: absolute;
24
+ top: 0;
25
+ left: 0;
26
+ }
27
+ .slider-thumb::-webkit-slider-thumb {
28
+ -webkit-appearance: none;
29
+ appearance: none;
30
+ width: 20px;
31
+ height: 20px;
32
+ border-radius: 50%;
33
+ background: #4f46e5;
34
+ cursor: pointer;
35
+ }
36
+ .slider-thumb::-moz-range-thumb {
37
+ width: 20px;
38
+ height: 20px;
39
+ border-radius: 50%;
40
+ background: #4f46e5;
41
+ cursor: pointer;
42
+ }
43
+ </style>
44
+ </head>
45
+ <body class="bg-gray-50 min-h-screen">
46
+ <div class="container mx-auto px-4 py-8">
47
+ <header class="mb-8 text-center">
48
+ <h1 class="text-4xl font-bold text-indigo-700 mb-2">Bitmap to Vector Converter</h1>
49
+ <p class="text-gray-600">Upload an image to trace it into vector paths and export as SVG</p>
50
+ </header>
51
+
52
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
53
+ <!-- Left Panel - Upload and Controls -->
54
+ <div class="bg-white rounded-xl shadow-md p-6">
55
+ <div id="dropzone" class="dropzone rounded-lg p-8 mb-6 text-center cursor-pointer">
56
+ <div class="flex flex-col items-center justify-center">
57
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-12 w-12 text-indigo-500 mb-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
58
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
59
+ </svg>
60
+ <p class="text-lg font-medium text-gray-700">Drag & drop your image here</p>
61
+ <p class="text-sm text-gray-500 mt-1">or click to browse files</p>
62
+ <input type="file" id="file-input" class="hidden" accept="image/*">
63
+ </div>
64
+ </div>
65
+
66
+ <div class="space-y-6">
67
+ <!-- Tracing Options -->
68
+ <div>
69
+ <h3 class="text-lg font-medium text-gray-900 mb-3">Tracing Options</h3>
70
+ <div class="space-y-4">
71
+ <!-- Threshold Slider -->
72
+ <div>
73
+ <label for="threshold" class="block text-sm font-medium text-gray-700 mb-1">Threshold: <span id="threshold-value">128</span></label>
74
+ <input type="range" id="threshold" min="0" max="255" value="128" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer slider-thumb">
75
+ </div>
76
+
77
+ <!-- Turd Size -->
78
+ <div>
79
+ <label for="turdSize" class="block text-sm font-medium text-gray-700 mb-1">Noise Reduction: <span id="turdSize-value">2</span></label>
80
+ <input type="range" id="turdSize" min="0" max="20" value="2" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer slider-thumb">
81
+ </div>
82
+
83
+ <!-- Curve Optimization -->
84
+ <div>
85
+ <label for="curveOptimization" class="block text-sm font-medium text-gray-700 mb-1">Curve Optimization: <span id="curveOptimization-value">0.2</span></label>
86
+ <input type="range" id="curveOptimization" min="0" max="1" step="0.01" value="0.2" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer slider-thumb">
87
+ </div>
88
+
89
+ <!-- Color Mode -->
90
+ <div>
91
+ <label class="block text-sm font-medium text-gray-700 mb-2">Color Mode:</label>
92
+ <div class="flex space-x-4">
93
+ <label class="inline-flex items-center">
94
+ <input type="radio" name="colorMode" value="black" checked class="h-4 w-4 text-indigo-600 focus:ring-indigo-500">
95
+ <span class="ml-2 text-gray-700">Black & White</span>
96
+ </label>
97
+ <label class="inline-flex items-center">
98
+ <input type="radio" name="colorMode" value="color" class="h-4 w-4 text-indigo-600 focus:ring-indigo-500">
99
+ <span class="ml-2 text-gray-700">Color</span>
100
+ </label>
101
+ </div>
102
+ </div>
103
+ </div>
104
+ </div>
105
+
106
+ <!-- Actions -->
107
+ <div class="pt-4">
108
+ <button id="trace-btn" class="w-full bg-indigo-600 hover:bg-indigo-700 text-white font-medium py-2 px-4 rounded-lg transition duration-200 flex items-center justify-center">
109
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" viewBox="0 0 20 20" fill="currentColor">
110
+ <path fill-rule="evenodd" d="M4 5a2 2 0 00-2 2v8a2 2 0 002 2h12a2 2 0 002-2V7a2 2 0 00-2-2h-1.586a1 1 0 01-.707-.293l-1.121-1.121A2 2 0 0011.172 3H8.828a2 2 0 00-1.414.586L6.293 4.707A1 1 0 015.586 5H4zm6 9a3 3 0 100-6 3 3 0 000 6z" clip-rule="evenodd" />
111
+ </svg>
112
+ Trace Image
113
+ </button>
114
+ </div>
115
+ </div>
116
+ </div>
117
+
118
+ <!-- Right Panel - Preview and Results -->
119
+ <div class="bg-white rounded-xl shadow-md p-6">
120
+ <div class="flex justify-between items-center mb-4">
121
+ <h3 class="text-lg font-medium text-gray-900">Preview</h3>
122
+ <div class="flex space-x-2">
123
+ <button id="zoom-in" class="p-2 rounded-md hover:bg-gray-100">
124
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-gray-600" viewBox="0 0 20 20" fill="currentColor">
125
+ <path fill-rule="evenodd" d="M10 5a1 1 0 011 1v3h3a1 1 0 110 2h-3v3a1 1 0 11-2 0v-3H6a1 1 0 110-2h3V6a1 1 0 011-1z" clip-rule="evenodd" />
126
+ </svg>
127
+ </button>
128
+ <button id="zoom-out" class="p-2 rounded-md hover:bg-gray-100">
129
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-gray-600" viewBox="0 0 20 20" fill="currentColor">
130
+ <path fill-rule="evenodd" d="M5 10a1 1 0 011-1h8a1 1 0 110 2H6a1 1 0 01-1-1z" clip-rule="evenodd" />
131
+ </svg>
132
+ </button>
133
+ <button id="reset-zoom" class="p-2 rounded-md hover:bg-gray-100">
134
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-gray-600" viewBox="0 0 20 20" fill="currentColor">
135
+ <path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd" />
136
+ </svg>
137
+ </button>
138
+ </div>
139
+ </div>
140
+
141
+ <div id="canvas-container" class="relative border border-gray-200 rounded-lg overflow-hidden bg-gray-100" style="height: 400px;">
142
+ <canvas id="preview-canvas" class="absolute top-0 left-0"></canvas>
143
+ <svg id="vector-canvas" class="absolute top-0 left-0" width="100%" height="100%"></svg>
144
+ <div id="empty-state" class="flex flex-col items-center justify-center h-full text-gray-400">
145
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-12 w-12 mb-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
146
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
147
+ </svg>
148
+ <p class="text-lg">No image loaded</p>
149
+ </div>
150
+ </div>
151
+
152
+ <div class="mt-6">
153
+ <div class="flex justify-between items-center mb-3">
154
+ <h3 class="text-lg font-medium text-gray-900">Vector Output</h3>
155
+ <div class="flex space-x-2">
156
+ <button id="copy-svg" class="text-sm bg-gray-100 hover:bg-gray-200 text-gray-700 py-1 px-3 rounded-md transition duration-200 flex items-center">
157
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" viewBox="0 0 20 20" fill="currentColor">
158
+ <path d="M8 3a1 1 0 011-1h2a1 1 0 110 2H9a1 1 0 01-1-1z" />
159
+ <path d="M6 3a2 2 0 00-2 2v11a2 2 0 002 2h8a2 2 0 002-2V5a2 2 0 00-2-2 3 3 0 01-3 3H9a3 3 0 01-3-3z" />
160
+ </svg>
161
+ Copy SVG
162
+ </button>
163
+ <button id="download-svg" class="text-sm bg-indigo-600 hover:bg-indigo-700 text-white py-1 px-3 rounded-md transition duration-200 flex items-center">
164
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" viewBox="0 0 20 20" fill="currentColor">
165
+ <path fill-rule="evenodd" d="M3 17a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm3.293-7.707a1 1 0 011.414 0L9 10.586V3a1 1 0 112 0v7.586l1.293-1.293a1 1 0 111.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z" clip-rule="evenodd" />
166
+ </svg>
167
+ Download SVG
168
+ </button>
169
+ </div>
170
+ </div>
171
+ <div id="svg-code-container" class="bg-gray-50 rounded-lg p-3 h-40 overflow-auto font-mono text-sm text-gray-700">
172
+ <div class="text-gray-400 italic">SVG output will appear here after tracing</div>
173
+ </div>
174
+ </div>
175
+ </div>
176
+ </div>
177
+ </div>
178
+
179
+ <script>
180
+ document.addEventListener('DOMContentLoaded', function() {
181
+ // DOM Elements
182
+ const dropzone = document.getElementById('dropzone');
183
+ const fileInput = document.getElementById('file-input');
184
+ const previewCanvas = document.getElementById('preview-canvas');
185
+ const vectorCanvas = document.getElementById('vector-canvas');
186
+ const emptyState = document.getElementById('empty-state');
187
+ const traceBtn = document.getElementById('trace-btn');
188
+ const zoomInBtn = document.getElementById('zoom-in');
189
+ const zoomOutBtn = document.getElementById('zoom-out');
190
+ const resetZoomBtn = document.getElementById('reset-zoom');
191
+ const copySvgBtn = document.getElementById('copy-svg');
192
+ const downloadSvgBtn = document.getElementById('download-svg');
193
+ const svgCodeContainer = document.getElementById('svg-code-container');
194
+
195
+ // Sliders and their value displays
196
+ const thresholdSlider = document.getElementById('threshold');
197
+ const turdSizeSlider = document.getElementById('turdSize');
198
+ const curveOptimizationSlider = document.getElementById('curveOptimization');
199
+ const thresholdValue = document.getElementById('threshold-value');
200
+ const turdSizeValue = document.getElementById('turdSize-value');
201
+ const curveOptimizationValue = document.getElementById('curveOptimization-value');
202
+
203
+ // Canvas context
204
+ const ctx = previewCanvas.getContext('2d');
205
+
206
+ // State variables
207
+ let currentImage = null;
208
+ let scale = 1;
209
+ let offsetX = 0;
210
+ let offsetY = 0;
211
+
212
+ // Initialize sliders
213
+ thresholdSlider.addEventListener('input', () => {
214
+ thresholdValue.textContent = thresholdSlider.value;
215
+ });
216
+
217
+ turdSizeSlider.addEventListener('input', () => {
218
+ turdSizeValue.textContent = turdSizeSlider.value;
219
+ });
220
+
221
+ curveOptimizationSlider.addEventListener('input', () => {
222
+ curveOptimizationValue.textContent = curveOptimizationSlider.value;
223
+ });
224
+
225
+ // Drag and drop handling
226
+ dropzone.addEventListener('click', () => fileInput.click());
227
+
228
+ fileInput.addEventListener('change', handleFileSelect);
229
+
230
+ ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
231
+ dropzone.addEventListener(eventName, preventDefaults, false);
232
+ });
233
+
234
+ function preventDefaults(e) {
235
+ e.preventDefault();
236
+ e.stopPropagation();
237
+ }
238
+
239
+ ['dragenter', 'dragover'].forEach(eventName => {
240
+ dropzone.addEventListener(eventName, highlight, false);
241
+ });
242
+
243
+ ['dragleave', 'drop'].forEach(eventName => {
244
+ dropzone.addEventListener(eventName, unhighlight, false);
245
+ });
246
+
247
+ function highlight() {
248
+ dropzone.classList.add('active');
249
+ }
250
+
251
+ function unhighlight() {
252
+ dropzone.classList.remove('active');
253
+ }
254
+
255
+ dropzone.addEventListener('drop', handleDrop, false);
256
+
257
+ function handleDrop(e) {
258
+ const dt = e.dataTransfer;
259
+ const files = dt.files;
260
+ if (files.length) {
261
+ handleFileSelect({ target: { files } });
262
+ }
263
+ }
264
+
265
+ function handleFileSelect(e) {
266
+ const file = e.target.files[0];
267
+ if (!file.type.match('image.*')) {
268
+ alert('Please select an image file');
269
+ return;
270
+ }
271
+
272
+ const reader = new FileReader();
273
+ reader.onload = function(event) {
274
+ const img = new Image();
275
+ img.onload = function() {
276
+ currentImage = img;
277
+ displayImage(img);
278
+ emptyState.style.display = 'none';
279
+ };
280
+ img.src = event.target.result;
281
+ };
282
+ reader.readAsDataURL(file);
283
+ }
284
+
285
+ function displayImage(img) {
286
+ const container = document.getElementById('canvas-container');
287
+ const containerWidth = container.clientWidth;
288
+ const containerHeight = container.clientHeight;
289
+
290
+ // Calculate dimensions to fit the container while maintaining aspect ratio
291
+ let width = img.width;
292
+ let height = img.height;
293
+
294
+ if (width > containerWidth || height > containerHeight) {
295
+ const ratio = Math.min(containerWidth / width, containerHeight / height);
296
+ width = width * ratio;
297
+ height = height * ratio;
298
+ }
299
+
300
+ // Set canvas dimensions
301
+ previewCanvas.width = width;
302
+ previewCanvas.height = height;
303
+ vectorCanvas.setAttribute('width', width);
304
+ vectorCanvas.setAttribute('height', height);
305
+
306
+ // Draw image
307
+ ctx.clearRect(0, 0, previewCanvas.width, previewCanvas.height);
308
+ ctx.drawImage(img, 0, 0, width, height);
309
+
310
+ // Reset zoom and offset
311
+ scale = 1;
312
+ offsetX = 0;
313
+ offsetY = 0;
314
+ updateCanvasTransform();
315
+ }
316
+
317
+ // Zoom controls
318
+ zoomInBtn.addEventListener('click', () => {
319
+ scale *= 1.2;
320
+ updateCanvasTransform();
321
+ });
322
+
323
+ zoomOutBtn.addEventListener('click', () => {
324
+ scale /= 1.2;
325
+ updateCanvasTransform();
326
+ });
327
+
328
+ resetZoomBtn.addEventListener('click', () => {
329
+ scale = 1;
330
+ offsetX = 0;
331
+ offsetY = 0;
332
+ updateCanvasTransform();
333
+ });
334
+
335
+ function updateCanvasTransform() {
336
+ previewCanvas.style.transform = `scale(${scale}) translate(${offsetX}px, ${offsetY}px)`;
337
+ vectorCanvas.style.transform = `scale(${scale}) translate(${offsetX}px, ${offsetY}px)`;
338
+ }
339
+
340
+ // Trace image button
341
+ traceBtn.addEventListener('click', traceImage);
342
+
343
+ function traceImage() {
344
+ if (!currentImage) {
345
+ alert('Please upload an image first');
346
+ return;
347
+ }
348
+
349
+ traceBtn.disabled = true;
350
+ traceBtn.innerHTML = `
351
+ <svg class="animate-spin -ml-1 mr-2 h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
352
+ <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
353
+ <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
354
+ </svg>
355
+ Tracing...
356
+ `;
357
+
358
+ // Get parameters
359
+ const threshold = parseInt(thresholdSlider.value);
360
+ const turdSize = parseInt(turdSizeSlider.value);
361
+ const curveOptimization = parseFloat(curveOptimizationSlider.value);
362
+ const colorMode = document.querySelector('input[name="colorMode"]:checked').value;
363
+
364
+ // Create a temporary canvas for processing
365
+ const tempCanvas = document.createElement('canvas');
366
+ const tempCtx = tempCanvas.getContext('2d');
367
+ tempCanvas.width = currentImage.width;
368
+ tempCanvas.height = currentImage.height;
369
+ tempCtx.drawImage(currentImage, 0, 0);
370
+
371
+ // Get image data
372
+ const imageData = tempCtx.getImageData(0, 0, tempCanvas.width, tempCanvas.height);
373
+
374
+ // Trace using Potrace
375
+ const potrace = new Potrace();
376
+
377
+ if (colorMode === 'black') {
378
+ potrace.setParameters({
379
+ threshold: threshold,
380
+ turdSize: turdSize,
381
+ curveOptimization: curveOptimization
382
+ });
383
+
384
+ potrace.loadImageData(imageData, function() {
385
+ const svg = potrace.getSVG();
386
+ displayVectorResult(svg);
387
+ });
388
+ } else {
389
+ // For color tracing, we'll need to process each color channel separately
390
+ // This is a simplified approach - a production tool would need more sophisticated color quantization
391
+ alert('Color tracing is more complex and would require additional libraries for best results. Using black and white for now.');
392
+
393
+ potrace.setParameters({
394
+ threshold: threshold,
395
+ turdSize: turdSize,
396
+ curveOptimization: curveOptimization
397
+ });
398
+
399
+ potrace.loadImageData(imageData, function() {
400
+ const svg = potrace.getSVG();
401
+ displayVectorResult(svg);
402
+ });
403
+ }
404
+ }
405
+
406
+ function displayVectorResult(svg) {
407
+ // Clear previous vector
408
+ vectorCanvas.innerHTML = '';
409
+
410
+ // Parse the SVG string and add it to our vector canvas
411
+ vectorCanvas.innerHTML = svg;
412
+
413
+ // Display the SVG code
414
+ svgCodeContainer.innerHTML = `<pre class="text-xs">${escapeHtml(svg)}</pre>`;
415
+
416
+ // Reset trace button
417
+ traceBtn.disabled = false;
418
+ traceBtn.innerHTML = `
419
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" viewBox="0 0 20 20" fill="currentColor">
420
+ <path fill-rule="evenodd" d="M4 5a2 2 0 00-2 2v8a2 2 0 002 2h12a2 2 0 002-2V7a2 2 0 00-2-2h-1.586a1 1 0 01-.707-.293l-1.121-1.121A2 2 0 0011.172 3H8.828a2 2 0 00-1.414.586L6.293 4.707A1 1 0 015.586 5H4zm6 9a3 3 0 100-6 3 3 0 000 6z" clip-rule="evenodd" />
421
+ </svg>
422
+ Trace Image
423
+ `;
424
+ }
425
+
426
+ // Copy SVG button
427
+ copySvgBtn.addEventListener('click', () => {
428
+ if (vectorCanvas.innerHTML.includes('path')) {
429
+ const svgText = vectorCanvas.innerHTML;
430
+ navigator.clipboard.writeText(svgText).then(() => {
431
+ const originalText = copySvgBtn.textContent;
432
+ copySvgBtn.innerHTML = `
433
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" viewBox="0 0 20 20" fill="currentColor">
434
+ <path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd" />
435
+ </svg>
436
+ Copied!
437
+ `;
438
+ setTimeout(() => {
439
+ copySvgBtn.innerHTML = `
440
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" viewBox="0 0 20 20" fill="currentColor">
441
+ <path d="M8 3a1 1 0 011-1h2a1 1 0 110 2H9a1 1 0 01-1-1z" />
442
+ <path d="M6 3a2 2 0 00-2 2v11a2 2 0 002 2h8a2 2 0 002-2V5a2 2 0 00-2-2 3 3 0 01-3 3H9a3 3 0 01-3-3z" />
443
+ </svg>
444
+ ${originalText}
445
+ `;
446
+ }, 2000);
447
+ }).catch(err => {
448
+ console.error('Failed to copy: ', err);
449
+ });
450
+ } else {
451
+ alert('No vector data to copy. Please trace an image first.');
452
+ }
453
+ });
454
+
455
+ // Download SVG button
456
+ downloadSvgBtn.addEventListener('click', () => {
457
+ if (vectorCanvas.innerHTML.includes('path')) {
458
+ const svgText = vectorCanvas.innerHTML;
459
+ const blob = new Blob([svgText], { type: 'image/svg+xml' });
460
+ const url = URL.createObjectURL(blob);
461
+
462
+ const a = document.createElement('a');
463
+ a.href = url;
464
+ a.download = 'vector-image.svg';
465
+ document.body.appendChild(a);
466
+ a.click();
467
+ document.body.removeChild(a);
468
+ URL.revokeObjectURL(url);
469
+ } else {
470
+ alert('No vector data to download. Please trace an image first.');
471
+ }
472
+ });
473
+
474
+ // Helper function to escape HTML
475
+ function escapeHtml(unsafe) {
476
+ return unsafe
477
+ .replace(/&/g, "&amp;")
478
+ .replace(/</g, "&lt;")
479
+ .replace(/>/g, "&gt;")
480
+ .replace(/"/g, "&quot;")
481
+ .replace(/'/g, "&#039;");
482
+ }
483
+ });
484
+ </script>
485
+ <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - <a href="https://enzostvs-deepsite.hf.space?remix=pg0/bitmap-to-vector2" style="color: #fff;text-decoration: underline;" target="_blank" >🧬 Remix</a></p></body>
486
+ </html>