pg0 commited on
Commit
1f66928
·
verified ·
1 Parent(s): d9cb1f6

Add 1 files

Browse files
Files changed (1) hide show
  1. index.html +279 -60
index.html CHANGED
@@ -3,9 +3,10 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Image to SVG Vectorizer</title>
7
  <script src="https://cdn.tailwindcss.com"></script>
8
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
 
9
  <style>
10
  .dropzone {
11
  border: 2px dashed #cbd5e0;
@@ -58,16 +59,28 @@
58
  .file-info {
59
  display: none;
60
  }
 
 
 
 
 
 
 
 
 
 
 
 
61
  </style>
62
  </head>
63
  <body class="bg-gray-50 min-h-screen">
64
  <div class="container mx-auto px-4 py-8">
65
  <header class="text-center mb-12">
66
  <h1 class="text-4xl font-bold text-indigo-600 mb-2">
67
- <i class="fas fa-vector-square mr-2"></i>Image to SVG Vectorizer
68
  </h1>
69
  <p class="text-gray-600 max-w-2xl mx-auto">
70
- Convert your raster images (JPG, PNG, etc.) into scalable vector graphics (SVG) with customizable settings
71
  </p>
72
  </header>
73
 
@@ -107,44 +120,75 @@
107
  </div>
108
 
109
  <div class="space-y-4">
110
- <h3 class="text-lg font-medium text-gray-700">Vectorization Settings</h3>
111
-
112
- <div>
113
- <label for="colorPrecision" class="block text-sm font-medium text-gray-700 mb-1">Color Precision</label>
114
- <select id="colorPrecision" class="w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 text-sm">
115
- <option value="1">Low (fewer colors)</option>
116
- <option value="2" selected>Medium</option>
117
- <option value="3">High (more colors)</option>
118
- </select>
119
  </div>
 
 
 
 
 
 
 
 
 
 
120
 
121
- <div>
122
- <label for="smoothness" class="block text-sm font-medium text-gray-700 mb-1">Smoothness</label>
123
- <input type="range" id="smoothness" min="0" max="100" value="50" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer">
124
- <div class="flex justify-between text-xs text-gray-500 mt-1">
125
- <span>Rough</span>
126
- <span>Balanced</span>
127
- <span>Smooth</span>
 
 
128
  </div>
129
- </div>
130
 
131
- <div>
132
- <label for="detailLevel" class="block text-sm font-medium text-gray-700 mb-1">Detail Level</label>
133
- <input type="range" id="detailLevel" min="1" max="10" value="5" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer">
134
- <div class="flex justify-between text-xs text-gray-500 mt-1">
135
- <span>Low</span>
136
- <span>Medium</span>
137
- <span>High</span>
 
138
  </div>
139
- </div>
140
 
141
- <div class="flex items-center">
142
- <input type="checkbox" id="transparentBg" class="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded">
143
- <label for="transparentBg" class="ml-2 block text-sm text-gray-700">Transparent background</label>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144
  </div>
145
 
146
  <button id="convertBtn" class="w-full bg-indigo-600 hover:bg-indigo-700 text-white py-2 px-4 rounded-md font-medium transition disabled:opacity-50 disabled:cursor-not-allowed" disabled>
147
- Convert to SVG
148
  </button>
149
  </div>
150
  </div>
@@ -166,7 +210,7 @@
166
  <div id="processingUI" class="hidden text-center py-12">
167
  <div class="inline-block animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-indigo-500 mb-4"></div>
168
  <h3 class="text-lg font-medium text-gray-700">Processing Image</h3>
169
- <p class="text-gray-500 text-sm mt-1">This may take a few moments...</p>
170
  <div class="w-full bg-gray-200 rounded-full h-2.5 mt-4">
171
  <div id="progressBar" class="progress-bar bg-indigo-600 h-2.5 rounded-full" style="width: 0%"></div>
172
  </div>
@@ -177,6 +221,26 @@
177
  <div id="svgPreview" class="mx-auto"></div>
178
  </div>
179
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
180
  <div>
181
  <div class="flex items-center justify-between mb-2">
182
  <h4 class="text-sm font-medium text-gray-700">SVG Code</h4>
@@ -200,7 +264,7 @@
200
  </div>
201
 
202
  <footer class="text-center mt-12 text-gray-500 text-sm">
203
- <p>Image to SVG Vectorizer &copy; 2023 | All rights reserved</p>
204
  </footer>
205
  </div>
206
 
@@ -227,6 +291,41 @@
227
  const downloadBtn = document.getElementById('downloadBtn');
228
  const copyBtn = document.getElementById('copyBtn');
229
  const copyCodeBtn = document.getElementById('copyCodeBtn');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
230
 
231
  // Prevent default drag behaviors
232
  ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
@@ -292,6 +391,7 @@
292
  // Handle selected files
293
  function handleFiles(files) {
294
  const file = files[0];
 
295
 
296
  // Check if file is an image
297
  if (!file.type.match('image.*')) {
@@ -299,9 +399,9 @@
299
  return;
300
  }
301
 
302
- // Check file size (max 5MB)
303
- if (file.size > 5 * 1024 * 1024) {
304
- showError('File size exceeds 5MB limit');
305
  return;
306
  }
307
 
@@ -317,6 +417,15 @@
317
  imagePreview.src = e.target.result;
318
  previewContainer.classList.remove('hidden');
319
  convertBtn.disabled = false;
 
 
 
 
 
 
 
 
 
320
  };
321
  reader.readAsDataURL(file);
322
  }
@@ -345,6 +454,7 @@
345
  // Reset upload
346
  function resetUpload() {
347
  fileInput.value = '';
 
348
  uploadUI.style.display = 'flex';
349
  fileInfo.style.display = 'none';
350
  previewContainer.classList.add('hidden');
@@ -353,49 +463,158 @@
353
  resultContainer.classList.add('hidden');
354
  }
355
 
356
- // Process image (simulated)
357
  function processImage() {
358
  // Show processing UI
359
  processingUI.classList.remove('hidden');
360
  emptyState.classList.add('hidden');
361
  resultContainer.classList.add('hidden');
362
 
363
- // Simulate processing with progress
364
  let progress = 0;
365
- const interval = setInterval(() => {
366
  progress += Math.random() * 10;
367
- if (progress > 100) progress = 100;
368
  progressBar.style.width = progress + '%';
 
369
 
370
- if (progress === 100) {
371
- clearInterval(interval);
372
- setTimeout(showResult, 500);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
373
  }
374
- }, 300);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
375
  }
376
 
377
  // Show result
378
- function showResult() {
379
  processingUI.classList.add('hidden');
380
  resultContainer.classList.remove('hidden');
381
  emptyState.classList.add('hidden');
382
  downloadBtn.classList.remove('hidden');
383
  copyBtn.classList.remove('hidden');
384
 
385
- // Generate sample SVG (in a real app, this would come from your vectorization process)
386
- const sampleSVG = generateSampleSVG(imagePreview.src);
387
- svgPreview.innerHTML = sampleSVG;
388
- svgCode.textContent = sampleSVG;
389
- }
390
 
391
- // Generate sample SVG (for demo purposes)
392
- function generateSampleSVG(imageSrc) {
393
- return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200">
394
- <rect width="200" height="200" fill="#f0f0f0"/>
395
- <circle cx="100" cy="100" r="80" fill="#4f46e5" opacity="0.8"/>
396
- <path d="M100 50 L120 150 L80 150 Z" fill="#ffffff" stroke="#333" stroke-width="2"/>
397
- <text x="100" y="120" font-family="Arial" font-size="24" fill="#ffffff" text-anchor="middle">SVG</text>
398
- </svg>`;
 
 
 
 
 
 
 
 
 
 
 
399
  }
400
 
401
  // Copy SVG to clipboard
@@ -429,7 +648,7 @@
429
  const url = URL.createObjectURL(blob);
430
  const a = document.createElement('a');
431
  a.href = url;
432
- a.download = fileName.textContent.replace(/\.[^/.]+$/, "") + '.svg';
433
  document.body.appendChild(a);
434
  a.click();
435
  document.body.removeChild(a);
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Advanced Image to SVG Vectorizer</title>
7
  <script src="https://cdn.tailwindcss.com"></script>
8
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
+ <script src="https://cdn.jsdelivr.net/npm/potrace@2.1.8/dist/potrace.min.js"></script>
10
  <style>
11
  .dropzone {
12
  border: 2px dashed #cbd5e0;
 
59
  .file-info {
60
  display: none;
61
  }
62
+ .settings-panel {
63
+ transition: all 0.3s ease;
64
+ }
65
+ .settings-panel.collapsed {
66
+ max-height: 0;
67
+ overflow: hidden;
68
+ opacity: 0;
69
+ }
70
+ .settings-panel.expanded {
71
+ max-height: 500px;
72
+ opacity: 1;
73
+ }
74
  </style>
75
  </head>
76
  <body class="bg-gray-50 min-h-screen">
77
  <div class="container mx-auto px-4 py-8">
78
  <header class="text-center mb-12">
79
  <h1 class="text-4xl font-bold text-indigo-600 mb-2">
80
+ <i class="fas fa-vector-square mr-2"></i>Advanced Image to SVG Vectorizer
81
  </h1>
82
  <p class="text-gray-600 max-w-2xl mx-auto">
83
+ Convert raster images to high-quality scalable vector graphics with advanced settings
84
  </p>
85
  </header>
86
 
 
120
  </div>
121
 
122
  <div class="space-y-4">
123
+ <div class="flex justify-between items-center cursor-pointer" id="settingsToggle">
124
+ <h3 class="text-lg font-medium text-gray-700">Vectorization Settings</h3>
125
+ <i class="fas fa-chevron-down text-gray-500 transition-transform duration-300" id="toggleIcon"></i>
 
 
 
 
 
 
126
  </div>
127
+
128
+ <div id="settingsPanel" class="settings-panel expanded space-y-4">
129
+ <div>
130
+ <label for="vectorMode" class="block text-sm font-medium text-gray-700 mb-1">Vectorization Mode</label>
131
+ <select id="vectorMode" class="w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 text-sm">
132
+ <option value="posterize">Posterize (color blocks)</option>
133
+ <option value="grayscale">Grayscale</option>
134
+ <option value="blackwhite" selected>Black & White</option>
135
+ </select>
136
+ </div>
137
 
138
+ <div id="colorSettings">
139
+ <label for="colorCount" class="block text-sm font-medium text-gray-700 mb-1">Color Count</label>
140
+ <input type="range" id="colorCount" min="2" max="16" value="4" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer">
141
+ <div class="flex justify-between text-xs text-gray-500 mt-1">
142
+ <span>2</span>
143
+ <span>4</span>
144
+ <span>8</span>
145
+ <span>16</span>
146
+ </div>
147
  </div>
 
148
 
149
+ <div>
150
+ <label for="threshold" class="block text-sm font-medium text-gray-700 mb-1">Threshold</label>
151
+ <input type="range" id="threshold" min="0" max="100" value="50" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer">
152
+ <div class="flex justify-between text-xs text-gray-500 mt-1">
153
+ <span>Low</span>
154
+ <span>Medium</span>
155
+ <span>High</span>
156
+ </div>
157
  </div>
 
158
 
159
+ <div>
160
+ <label for="smoothness" class="block text-sm font-medium text-gray-700 mb-1">Smoothness</label>
161
+ <input type="range" id="smoothness" min="0" max="10" value="4" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer">
162
+ <div class="flex justify-between text-xs text-gray-500 mt-1">
163
+ <span>Sharp</span>
164
+ <span>Balanced</span>
165
+ <span>Smooth</span>
166
+ </div>
167
+ </div>
168
+
169
+ <div>
170
+ <label for="detailLevel" class="block text-sm font-medium text-gray-700 mb-1">Detail Level</label>
171
+ <input type="range" id="detailLevel" min="1" max="10" value="5" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer">
172
+ <div class="flex justify-between text-xs text-gray-500 mt-1">
173
+ <span>Low</span>
174
+ <span>Medium</span>
175
+ <span>High</span>
176
+ </div>
177
+ </div>
178
+
179
+ <div class="flex items-center">
180
+ <input type="checkbox" id="transparentBg" class="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded">
181
+ <label for="transparentBg" class="ml-2 block text-sm text-gray-700">Transparent background</label>
182
+ </div>
183
+
184
+ <div class="flex items-center">
185
+ <input type="checkbox" id="optimizePaths" class="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded" checked>
186
+ <label for="optimizePaths" class="ml-2 block text-sm text-gray-700">Optimize paths</label>
187
+ </div>
188
  </div>
189
 
190
  <button id="convertBtn" class="w-full bg-indigo-600 hover:bg-indigo-700 text-white py-2 px-4 rounded-md font-medium transition disabled:opacity-50 disabled:cursor-not-allowed" disabled>
191
+ <i class="fas fa-magic mr-2"></i> Convert to SVG
192
  </button>
193
  </div>
194
  </div>
 
210
  <div id="processingUI" class="hidden text-center py-12">
211
  <div class="inline-block animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-indigo-500 mb-4"></div>
212
  <h3 class="text-lg font-medium text-gray-700">Processing Image</h3>
213
+ <p class="text-gray-500 text-sm mt-1">Vectorizing your image...</p>
214
  <div class="w-full bg-gray-200 rounded-full h-2.5 mt-4">
215
  <div id="progressBar" class="progress-bar bg-indigo-600 h-2.5 rounded-full" style="width: 0%"></div>
216
  </div>
 
221
  <div id="svgPreview" class="mx-auto"></div>
222
  </div>
223
 
224
+ <div class="mb-4">
225
+ <div class="flex items-center justify-between mb-2">
226
+ <h4 class="text-sm font-medium text-gray-700">SVG Statistics</h4>
227
+ </div>
228
+ <div class="grid grid-cols-3 gap-2 text-xs">
229
+ <div class="bg-gray-100 p-2 rounded">
230
+ <div class="text-gray-500">File Size</div>
231
+ <div id="svgSize" class="font-medium">-</div>
232
+ </div>
233
+ <div class="bg-gray-100 p-2 rounded">
234
+ <div class="text-gray-500">Path Count</div>
235
+ <div id="pathCount" class="font-medium">-</div>
236
+ </div>
237
+ <div class="bg-gray-100 p-2 rounded">
238
+ <div class="text-gray-500">Colors</div>
239
+ <div id="colorCountDisplay" class="font-medium">-</div>
240
+ </div>
241
+ </div>
242
+ </div>
243
+
244
  <div>
245
  <div class="flex items-center justify-between mb-2">
246
  <h4 class="text-sm font-medium text-gray-700">SVG Code</h4>
 
264
  </div>
265
 
266
  <footer class="text-center mt-12 text-gray-500 text-sm">
267
+ <p>Advanced Image to SVG Vectorizer &copy; 2023 | Powered by Potrace algorithm</p>
268
  </footer>
269
  </div>
270
 
 
291
  const downloadBtn = document.getElementById('downloadBtn');
292
  const copyBtn = document.getElementById('copyBtn');
293
  const copyCodeBtn = document.getElementById('copyCodeBtn');
294
+ const settingsToggle = document.getElementById('settingsToggle');
295
+ const settingsPanel = document.getElementById('settingsPanel');
296
+ const toggleIcon = document.getElementById('toggleIcon');
297
+ const vectorMode = document.getElementById('vectorMode');
298
+ const colorCount = document.getElementById('colorCount');
299
+ const threshold = document.getElementById('threshold');
300
+ const smoothness = document.getElementById('smoothness');
301
+ const detailLevel = document.getElementById('detailLevel');
302
+ const transparentBg = document.getElementById('transparentBg');
303
+ const optimizePaths = document.getElementById('optimizePaths');
304
+ const colorSettings = document.getElementById('colorSettings');
305
+ const svgSize = document.getElementById('svgSize');
306
+ const pathCount = document.getElementById('pathCount');
307
+ const colorCountDisplay = document.getElementById('colorCountDisplay');
308
+
309
+ // Current file data
310
+ let currentFile = null;
311
+ let canvas = document.createElement('canvas');
312
+ let ctx = canvas.getContext('2d');
313
+
314
+ // Toggle settings panel
315
+ settingsToggle.addEventListener('click', () => {
316
+ settingsPanel.classList.toggle('expanded');
317
+ settingsPanel.classList.toggle('collapsed');
318
+ toggleIcon.classList.toggle('rotate-180');
319
+ });
320
+
321
+ // Toggle color settings based on vector mode
322
+ vectorMode.addEventListener('change', () => {
323
+ if (vectorMode.value === 'posterize') {
324
+ colorSettings.style.display = 'block';
325
+ } else {
326
+ colorSettings.style.display = 'none';
327
+ }
328
+ });
329
 
330
  // Prevent default drag behaviors
331
  ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
 
391
  // Handle selected files
392
  function handleFiles(files) {
393
  const file = files[0];
394
+ currentFile = file;
395
 
396
  // Check if file is an image
397
  if (!file.type.match('image.*')) {
 
399
  return;
400
  }
401
 
402
+ // Check file size (max 10MB)
403
+ if (file.size > 10 * 1024 * 1024) {
404
+ showError('File size exceeds 10MB limit');
405
  return;
406
  }
407
 
 
417
  imagePreview.src = e.target.result;
418
  previewContainer.classList.remove('hidden');
419
  convertBtn.disabled = false;
420
+
421
+ // Load image to canvas for processing
422
+ const img = new Image();
423
+ img.onload = function() {
424
+ canvas.width = img.width;
425
+ canvas.height = img.height;
426
+ ctx.drawImage(img, 0, 0);
427
+ };
428
+ img.src = e.target.result;
429
  };
430
  reader.readAsDataURL(file);
431
  }
 
454
  // Reset upload
455
  function resetUpload() {
456
  fileInput.value = '';
457
+ currentFile = null;
458
  uploadUI.style.display = 'flex';
459
  fileInfo.style.display = 'none';
460
  previewContainer.classList.add('hidden');
 
463
  resultContainer.classList.add('hidden');
464
  }
465
 
466
+ // Process image with vectorization
467
  function processImage() {
468
  // Show processing UI
469
  processingUI.classList.remove('hidden');
470
  emptyState.classList.add('hidden');
471
  resultContainer.classList.add('hidden');
472
 
473
+ // Simulate progress
474
  let progress = 0;
475
+ const progressInterval = setInterval(() => {
476
  progress += Math.random() * 10;
477
+ if (progress > 90) progress = 90;
478
  progressBar.style.width = progress + '%';
479
+ }, 200);
480
 
481
+ // Process image after a short delay to allow UI to update
482
+ setTimeout(() => {
483
+ // Get settings from UI
484
+ const settings = {
485
+ mode: vectorMode.value,
486
+ colorCount: parseInt(colorCount.value),
487
+ threshold: parseInt(threshold.value) / 100,
488
+ smoothness: parseInt(smoothness.value),
489
+ detail: parseInt(detailLevel.value),
490
+ transparent: transparentBg.checked,
491
+ optimize: optimizePaths.checked
492
+ };
493
+
494
+ // Process the image based on selected mode
495
+ let processedImageData;
496
+ if (settings.mode === 'posterize') {
497
+ processedImageData = posterizeImage(canvas, settings.colorCount);
498
+ } else if (settings.mode === 'grayscale') {
499
+ processedImageData = convertToGrayscale(canvas);
500
+ } else { // blackwhite
501
+ processedImageData = applyThreshold(canvas, settings.threshold);
502
  }
503
+
504
+ // Create temporary canvas with processed image
505
+ const tempCanvas = document.createElement('canvas');
506
+ tempCanvas.width = canvas.width;
507
+ tempCanvas.height = canvas.height;
508
+ const tempCtx = tempCanvas.getContext('2d');
509
+ tempCtx.putImageData(processedImageData, 0, 0);
510
+
511
+ // Use Potrace to vectorize the image
512
+ const imageDataURL = tempCanvas.toDataURL('image/png');
513
+
514
+ // Update progress
515
+ progressBar.style.width = '95%';
516
+
517
+ // Vectorize using Potrace
518
+ potrace.loadImageFromUrl(imageDataURL);
519
+ potrace.setParameters({
520
+ turdsize: Math.max(1, 10 - settings.detail), // Fewer details with higher values
521
+ optcurve: settings.optimize,
522
+ alphamax: settings.smoothness * 1.4, // Controls corner threshold
523
+ opttolerance: settings.smoothness * 0.5 // Optimization tolerance
524
+ });
525
+
526
+ potrace.process(() => {
527
+ clearInterval(progressInterval);
528
+ progressBar.style.width = '100%';
529
+
530
+ // Get SVG data
531
+ const svg = potrace.getSVG(1, settings.transparent ? 'none' : '#ffffff');
532
+
533
+ // Show result after a short delay
534
+ setTimeout(() => {
535
+ showResult(svg);
536
+ }, 300);
537
+ });
538
+ }, 500);
539
+ }
540
+
541
+ // Image processing functions
542
+ function posterizeImage(canvas, levels) {
543
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
544
+ const data = imageData.data;
545
+
546
+ // Calculate the step for each channel
547
+ const step = 255 / (levels - 1);
548
+
549
+ for (let i = 0; i < data.length; i += 4) {
550
+ // Posterize each channel
551
+ data[i] = Math.round(data[i] / step) * step; // R
552
+ data[i + 1] = Math.round(data[i + 1] / step) * step; // G
553
+ data[i + 2] = Math.round(data[i + 2] / step) * step; // B
554
+ }
555
+
556
+ return imageData;
557
+ }
558
+
559
+ function convertToGrayscale(canvas) {
560
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
561
+ const data = imageData.data;
562
+
563
+ for (let i = 0; i < data.length; i += 4) {
564
+ // Convert to grayscale using luminance
565
+ const gray = 0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2];
566
+ data[i] = data[i + 1] = data[i + 2] = gray;
567
+ }
568
+
569
+ return imageData;
570
+ }
571
+
572
+ function applyThreshold(canvas, thresholdValue) {
573
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
574
+ const data = imageData.data;
575
+
576
+ for (let i = 0; i < data.length; i += 4) {
577
+ // Convert to grayscale first
578
+ const gray = 0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2];
579
+ // Apply threshold
580
+ const value = gray > (thresholdValue * 255) ? 255 : 0;
581
+ data[i] = data[i + 1] = data[i + 2] = value;
582
+ }
583
+
584
+ return imageData;
585
  }
586
 
587
  // Show result
588
+ function showResult(svgData) {
589
  processingUI.classList.add('hidden');
590
  resultContainer.classList.remove('hidden');
591
  emptyState.classList.add('hidden');
592
  downloadBtn.classList.remove('hidden');
593
  copyBtn.classList.remove('hidden');
594
 
595
+ // Display SVG
596
+ svgPreview.innerHTML = svgData;
597
+ svgCode.textContent = svgData;
 
 
598
 
599
+ // Calculate and display statistics
600
+ const svgSizeBytes = new Blob([svgData]).size;
601
+ svgSize.textContent = formatFileSize(svgSizeBytes);
602
+
603
+ // Count paths in SVG
604
+ const pathMatches = svgData.match(/<path/g);
605
+ const pathCountValue = pathMatches ? pathMatches.length : 0;
606
+ pathCount.textContent = pathCountValue;
607
+
608
+ // Count colors in SVG
609
+ const colorMatches = svgData.match(/fill="([^"]+)"/g);
610
+ let uniqueColors = new Set();
611
+ if (colorMatches) {
612
+ colorMatches.forEach(match => {
613
+ const color = match.match(/fill="([^"]+)"/)[1];
614
+ uniqueColors.add(color.toLowerCase());
615
+ });
616
+ }
617
+ colorCountDisplay.textContent = uniqueColors.size;
618
  }
619
 
620
  // Copy SVG to clipboard
 
648
  const url = URL.createObjectURL(blob);
649
  const a = document.createElement('a');
650
  a.href = url;
651
+ a.download = (fileName.textContent.replace(/\.[^/.]+$/, "") || 'vectorized') + '.svg';
652
  document.body.appendChild(a);
653
  a.click();
654
  document.body.removeChild(a);