lightweight image tools

feat: add optimization

dunkirk.sh 0cff59f0 528a1f3a

verified
+93
+93
public/index.html
··· 135 135 cursor: pointer; 136 136 } 137 137 label { font-size: 0.9rem; color: #666; display: flex; align-items: center; gap: 0.25rem; } 138 + input[type="range"] { 139 + -webkit-appearance: none; 140 + appearance: none; 141 + vertical-align: middle; 142 + margin: 0; 143 + height: 20px; 144 + background: transparent; 145 + } 146 + input[type="range"]::-webkit-slider-runnable-track { 147 + height: 6px; 148 + background: #ccc; 149 + border-radius: 3px; 150 + } 151 + input[type="range"]::-webkit-slider-thumb { 152 + -webkit-appearance: none; 153 + width: 16px; 154 + height: 16px; 155 + background: #666; 156 + border-radius: 50%; 157 + margin-top: -5px; 158 + cursor: pointer; 159 + } 160 + input[type="range"]::-moz-range-track { 161 + height: 6px; 162 + background: #ccc; 163 + border-radius: 3px; 164 + } 165 + input[type="range"]::-moz-range-thumb { 166 + width: 16px; 167 + height: 16px; 168 + background: #666; 169 + border-radius: 50%; 170 + border: none; 171 + cursor: pointer; 172 + } 173 + input[type="number"] { width: 70px; } 138 174 139 175 @media (max-width: 500px) { 140 176 body { margin: 1rem auto; padding: 0.5rem; } ··· 173 209 <input type="checkbox" id="showTransparent" checked onchange="updatePreview()"> 174 210 Transparent 175 211 </label> 212 + </div> 213 + <div class="toolbar-group"> 214 + <label> 215 + Quality: 216 + <input type="range" id="optimizeQuality" min="10" max="100" value="80" style="width: 100px;"> 217 + <span id="qualityValue">80%</span> 218 + </label> 219 + <label> 220 + Max: 221 + <input type="number" id="maxDimension" value="2048" min="100" max="8192" style="width: 70px;"> 222 + px 223 + </label> 224 + <button onclick="optimizeImage()">Optimize</button> 176 225 </div> 177 226 <div class="toolbar-group"> 178 227 <button onclick="downloadImage()">Download</button> ··· 641 690 hideError(); 642 691 hideStatus(); 643 692 fileInput.value = ''; 693 + } 694 + 695 + // Optimize 696 + document.getElementById('optimizeQuality').oninput = function() { 697 + document.getElementById('qualityValue').textContent = this.value + '%'; 698 + }; 699 + 700 + function optimizeImage() { 701 + if (!currentImage) return; 702 + 703 + const quality = document.getElementById('optimizeQuality').value / 100; 704 + const maxDim = parseInt(document.getElementById('maxDimension').value) || 2048; 705 + 706 + // Calculate new dimensions (maintain aspect ratio) 707 + let newW = currentImage.width; 708 + let newH = currentImage.height; 709 + 710 + if (newW > maxDim || newH > maxDim) { 711 + if (newW > newH) { 712 + newH = Math.round(newH * (maxDim / newW)); 713 + newW = maxDim; 714 + } else { 715 + newW = Math.round(newW * (maxDim / newH)); 716 + newH = maxDim; 717 + } 718 + } 719 + 720 + // Create optimized canvas 721 + const tempCanvas = document.createElement('canvas'); 722 + tempCanvas.width = newW; 723 + tempCanvas.height = newH; 724 + const ctx = tempCanvas.getContext('2d'); 725 + ctx.drawImage(currentImage, 0, 0, newW, newH); 726 + 727 + // Convert to JPEG with quality setting 728 + const optimized = tempCanvas.toDataURL('image/jpeg', quality); 729 + 730 + // Load back as current image 731 + const img = new Image(); 732 + img.onload = () => { 733 + currentImage = img; 734 + updatePreview(); 735 + }; 736 + img.src = optimized; 644 737 } 645 738 </script> 646 739 </body>