Download script.js from DanielKruzo/pixel-perfect-vectorizer: direct link, hf CLI and curl.
- Browser
- Download file 11.9 kB
-
https://huggingface.co/spaces/DanielKruzo/pixel-perfect-vectorizer/resolve/main/script.js
- Command line
-
hf download hf://spaces/DanielKruzo/pixel-perfect-vectorizer/script.js
-
curl -L -o script.js https://huggingface.co/spaces/DanielKruzo/pixel-perfect-vectorizer/resolve/main/script.js
11.9 kB
| document.addEventListener('DOMContentLoaded', function() { | |
| // Initialize UI elements | |
| const dropArea = document.getElementById('drop-area'); | |
| const fileInput = document.getElementById('file-input'); | |
| const convertBtn = document.getElementById('convert-btn'); | |
| const convertText = document.getElementById('convert-text'); | |
| const convertSpinner = document.getElementById('convert-spinner'); | |
| const resultsSection = document.getElementById('results'); | |
| const originalContainer = document.getElementById('original-container'); | |
| const resultContainer = document.getElementById('result-container'); | |
| const svgCode = document.getElementById('svg-code'); | |
| const downloadBtn = document.getElementById('download-btn'); | |
| const copyBtn = document.getElementById('copy-btn'); | |
| const fileInfo = document.getElementById('file-info'); | |
| const zoomControls = document.getElementById('zoom-controls'); | |
| const zoomInBtn = document.getElementById('zoom-in'); | |
| const zoomOutBtn = document.getElementById('zoom-out'); | |
| const zoomResetBtn = document.getElementById('zoom-reset'); | |
| const colorModeToggle = document.getElementById('color-mode'); | |
| const colorModeLabel = document.getElementById('color-mode-label'); | |
| const colorSettings = document.getElementById('color-settings'); | |
| const colorCount = document.getElementById('color-count'); | |
| const colorCountValue = document.getElementById('color-count-value'); | |
| const detailLevel = document.getElementById('detail-level'); | |
| const smoothness = document.getElementById('smoothness'); | |
| const smoothnessValue = document.getElementById('smoothness-value'); | |
| const outputFormat = document.getElementById('output-format'); | |
| let uploadedFile = null; | |
| let scale = 1; | |
| let canvas = null; | |
| let svgPreview = null; | |
| let isDragging = false; | |
| let startPos = { x: 0, y: 0 }; | |
| let translate = { x: 0, y: 0 }; | |
| // Initialize event listeners | |
| initEventListeners(); | |
| function initEventListeners() { | |
| // Drag and drop handlers | |
| ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { | |
| dropArea.addEventListener(eventName, preventDefaults, false); | |
| document.body.addEventListener(eventName, preventDefaults, false); | |
| }); | |
| ['dragenter', 'dragover'].forEach(eventName => { | |
| dropArea.addEventListener(eventName, highlight, false); | |
| }); | |
| ['dragleave', 'drop'].forEach(eventName => { | |
| dropArea.addEventListener(eventName, unhighlight, false); | |
| }); | |
| dropArea.addEventListener('drop', handleDrop, false); | |
| fileInput.addEventListener('change', handleFiles, false); | |
| convertBtn.addEventListener('click', convertToSVG); | |
| downloadBtn.addEventListener('click', downloadSVG); | |
| copyBtn.addEventListener('click', copySVGCode); | |
| // Color mode toggle | |
| colorModeToggle.addEventListener('change', toggleColorMode); | |
| colorCount.addEventListener('input', updateColorCount); | |
| smoothness.addEventListener('input', updateSmoothness); | |
| // Zoom controls | |
| zoomInBtn.addEventListener('click', () => zoomImage(1.2)); | |
| zoomOutBtn.addEventListener('click', () => zoomImage(0.8)); | |
| zoomResetBtn.addEventListener('click', resetZoom); | |
| // SVG preview interactions | |
| resultContainer.addEventListener('mousedown', startDrag); | |
| document.addEventListener('mousemove', dragImage); | |
| document.addEventListener('mouseup', endDrag); | |
| resultContainer.addEventListener('touchstart', startDrag); | |
| document.addEventListener('touchmove', dragImage); | |
| document.addEventListener('touchend', endDrag); | |
| } | |
| function preventDefaults(e) { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| } | |
| function highlight() { | |
| dropArea.classList.add('highlight'); | |
| } | |
| function unhighlight() { | |
| dropArea.classList.remove('highlight'); | |
| } | |
| function handleDrop(e) { | |
| const dt = e.dataTransfer; | |
| const files = dt.files; | |
| handleFiles({ target: { files } }); | |
| } | |
| function handleFiles(e) { | |
| const files = e.target.files; | |
| if (files.length) { | |
| const file = files[0]; | |
| if (!file.type.match('image.*')) { | |
| alert('Please upload an image file (JPG, PNG etc.)'); | |
| return; | |
| } | |
| uploadedFile = file; | |
| displayOriginalImage(uploadedFile); | |
| } | |
| } | |
| function displayOriginalImage(file) { | |
| const reader = new FileReader(); | |
| reader.onload = function(e) { | |
| const img = document.createElement('img'); | |
| img.onload = function() { | |
| URL.revokeObjectURL(this.src); | |
| updateFileInfo(file, this.width, this.height); | |
| }; | |
| img.src = e.target.result; | |
| img.className = 'max-w-full max-h-full'; | |
| originalContainer.innerHTML = ''; | |
| originalContainer.appendChild(img); | |
| }; | |
| reader.readAsDataURL(file); | |
| } | |
| function updateFileInfo(file, width, height) { | |
| const sizeMB = (file.size / (1024 * 1024)).toFixed(2); | |
| fileInfo.innerHTML = ` | |
| ${file.name} | ${width}×${height}px | ${sizeMB}MB | |
| `; | |
| } | |
| function convertToSVG() { | |
| if (!uploadedFile) { | |
| showAlert('Please upload an image first'); | |
| return; | |
| } | |
| // Show loading state | |
| convertBtn.disabled = true; | |
| convertText.textContent = 'Processing...'; | |
| convertSpinner.classList.remove('hidden'); | |
| // Process image (simplified example - in production you would use a proper vectorization algorithm) | |
| setTimeout(() => { | |
| const img = new Image(); | |
| img.onload = function() { | |
| const canvas = document.createElement('canvas'); | |
| const ctx = canvas.getContext('2d'); | |
| canvas.width = img.width; | |
| canvas.height = img.height; | |
| ctx.drawImage(img, 0, 0); | |
| // Get conversion settings | |
| const isColor = colorModeToggle.checked; | |
| const colorCountVal = parseInt(colorCount.value); | |
| const smoothnessVal = parseInt(smoothness.value); | |
| const detailLevelVal = detailLevel.value; | |
| // Process image based on settings (simplified) | |
| const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); | |
| const data = imageData.data; | |
| if (!isColor) { | |
| // Monochrome conversion | |
| const threshold = 128; // Default threshold | |
| for (let i = 0; i < data.length; i += 4) { | |
| const avg = (data[i] + data[i + 1] + data[i + 2]) / 3; | |
| const val = avg > threshold ? 255 : 0; | |
| data[i] = data[i + 1] = data[i + 2] = val; | |
| } | |
| } else { | |
| // Color quantization (simplified) | |
| // In a real app, you would use a proper quantization algorithm | |
| const step = Math.floor(256 / colorCountVal); | |
| for (let i = 0; i < data.length; i += 4) { | |
| data[i] = Math.floor(data[i] / step) * step; // R | |
| data[i+1] = Math.floor(data[i+1] / step) * step; // G | |
| data[i+2] = Math.floor(data[i+2] / step) * step; // B | |
| } | |
| } | |
| ctx.putImageData(imageData, 0, 0); | |
| // Generate SVG (simplified - real implementation would use vector paths) | |
| const canvasDataURL = canvas.toDataURL('image/png'); | |
| const svg = `<svg viewBox="0 0 ${img.width} ${img.height}" xmlns="http://www.w3.org/2000/svg"> | |
| <image href="${canvasDataURL}" width="${img.width}" height="${img.height}"/> | |
| </svg>`; | |
| // Display result | |
| displaySVGResult(svg, img.width, img.height); | |
| // Reset button state | |
| convertBtn.disabled = false; | |
| convertText.textContent = 'Convert to Vector'; | |
| convertSpinner.classList.add('hidden'); | |
| }; | |
| img.src = URL.createObjectURL(uploadedFile); | |
| }, 500); // Simulate processing time | |
| } | |
| function displaySVGResult(svg, width, height) { | |
| resultContainer.innerHTML = ''; | |
| const svgBlob = new Blob([svg], {type: 'image/svg+xml'}); | |
| const svgUrl = URL.createObjectURL(svgBlob); | |
| svgPreview = document.createElement('img'); | |
| svgPreview.src = svgUrl; | |
| svgPreview.className = 'max-w-full max-h-full transform origin-center'; | |
| svgPreview.style.transform = `scale(${scale}) translate(${translate.x}px, ${translate.y}px)`; | |
| resultContainer.appendChild(svgPreview); | |
| svgCode.textContent = svg; | |
| resultsSection.classList.remove('hidden'); | |
| zoomControls.classList.remove('hidden'); | |
| // Scroll to results | |
| resultsSection.scrollIntoView({ behavior: 'smooth' }); | |
| } | |
| function toggleColorMode() { | |
| const isColor = colorModeToggle.checked; | |
| colorModeLabel.textContent = isColor ? 'Color' : 'Monochrome'; | |
| colorSettings.style.display = isColor ? 'block' : 'none'; | |
| } | |
| function updateColorCount() { | |
| colorCountValue.textContent = colorCount.value; | |
| } | |
| function updateSmoothness() { | |
| smoothnessValue.textContent = smoothness.value; | |
| } | |
| function zoomImage(factor) { | |
| scale *= factor; | |
| updateImageTransform(); | |
| } | |
| function resetZoom() { | |
| scale = 1; | |
| translate = { x: 0, y: 0 }; | |
| updateImageTransform(); | |
| } | |
| function startDrag(e) { | |
| if (!svgPreview) return; | |
| isDragging = true; | |
| startPos = { | |
| x: e.clientX || e.touches[0].clientX, | |
| y: e.clientY || e.touches[0].clientY | |
| }; | |
| resultContainer.classList.add('dragging'); | |
| e.preventDefault(); | |
| } | |
| function dragImage(e) { | |
| if (!isDragging || !svgPreview) return; | |
| const currentPos = { | |
| x: e.clientX || e.touches[0].clientX, | |
| y: e.clientY || e.touches[0].clientY | |
| }; | |
| translate.x += (currentPos.x - startPos.x) / scale; | |
| translate.y += (currentPos.y - startPos.y) / scale; | |
| startPos = currentPos; | |
| updateImageTransform(); | |
| e.preventDefault(); | |
| } | |
| function endDrag() { | |
| isDragging = false; | |
| resultContainer.classList.remove('dragging'); | |
| } | |
| function updateImageTransform() { | |
| if (svgPreview) { | |
| svgPreview.style.transform = `scale(${scale}) translate(${translate.x}px, ${translate.y}px)`; | |
| } | |
| } | |
| function showAlert(message, type = 'error') { | |
| const bgColor = type === 'error' ? 'bg-red-100 border-red-500 text-red-700' : 'bg-green-100 border-green-500 text-green-700'; | |
| const alert = document.createElement('div'); | |
| alert.className = `fixed top-4 right-4 ${bgColor} border-l-4 p-4 rounded shadow-lg z-50`; | |
| alert.innerHTML = ` | |
| <div class="flex items-center gap-2"> | |
| <i data-feather="alert-triangle"></i> | |
| <span>${message}</span> | |
| </div> | |
| `; | |
| document.body.appendChild(alert); | |
| feather.replace(); | |
| setTimeout(() => { | |
| alert.classList.add('opacity-0', 'transition-opacity', 'duration-300'); | |
| setTimeout(() => alert.remove(), 300); | |
| }, 3000); | |
| } | |
| }); |