|
8 | 8 | <link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🖼️</text></svg>"> |
9 | 9 | <script src="https://cdn.tailwindcss.com"></script> |
10 | 10 | <style> |
11 | | - .drop-zone { |
12 | | - border: 2px dashed #d1d5db; |
13 | | - transition: all 0.2s ease; |
14 | | - } |
15 | | - .drop-zone.active { |
16 | | - border-color: #3b82f6; |
17 | | - background-color: rgba(59, 130, 246, 0.05); |
18 | | - } |
19 | | - .thumbnail { |
20 | | - width: 60px; |
21 | | - height: 60px; |
22 | | - object-fit: cover; |
23 | | - border-radius: 4px; |
24 | | - } |
25 | | - input[type="range"]::-webkit-slider-thumb { |
26 | | - appearance: none; |
27 | | - width: 16px; |
28 | | - height: 16px; |
29 | | - background: #3b82f6; |
30 | | - border-radius: 50%; |
31 | | - } |
| 11 | + .drop-zone {border: 2px dashed #d1d5db; transition: all 0.2s ease;} |
| 12 | + .drop-zone.active {border-color: #3b82f6; background-color: rgba(59, 130, 246, 0.05);} |
| 13 | + .thumbnail {width: 60px; height: 60px; object-fit: cover; border-radius: 4px;} |
| 14 | + input[type="range"]::-webkit-slider-thumb {appearance: none; width: 16px; height: 16px; background: #3b82f6; border-radius: 50%;} |
| 15 | + input[type="range"]::-moz-range-thumb {appearance: none; width: 16px; height: 16px; background: #3b82f6; border-radius: 50%; border: none;} |
32 | 16 | </style> |
33 | 17 | </head> |
34 | 18 | <body class="bg-gray-100 min-h-screen py-8"> |
@@ -235,7 +219,7 @@ <h2 class="text-xl font-semibold text-gray-800">Conversion Results</h2> |
235 | 219 |
|
236 | 220 | <!-- Footer --> |
237 | 221 | <footer class="mt-8 text-center text-sm text-gray-500"> |
238 | | - <p>© 2025 Image Converter Tool. All processing happens in your browser — no files are uploaded to any server.</p> |
| 222 | + <p>© 2025 Image Converter Tool. All image processing happens in your browser — no files are uploaded to any server.</p> |
239 | 223 | </footer> |
240 | 224 | </div> |
241 | 225 | </div> |
@@ -615,50 +599,95 @@ <h2 class="text-xl font-semibold text-gray-800">Conversion Results</h2> |
615 | 599 | const buffer = await image.getBufferAsync(Jimp.MIME_BMP); |
616 | 600 | convertedBlob = new Blob([buffer], { type: 'image/bmp' }); |
617 | 601 | } else { |
618 | | - // Use image-conversion for other formats |
619 | | - let options = { |
620 | | - quality: state.compressionLevel / 100, |
621 | | - type: state.format |
622 | | - }; |
623 | | - |
624 | | - if (state.resize.enabled) { |
625 | | - options.width = parseInt(state.resize.width); |
626 | | - options.height = parseInt(state.resize.height); |
627 | | - } |
| 602 | + // For PNG, force proper conversion using Canvas |
| 603 | + if (state.format === 'image/png') { |
| 604 | + // Create a canvas to ensure PNG format |
| 605 | + const img = new Image(); |
| 606 | + const loadPromise = new Promise((resolve) => { |
| 607 | + img.onload = resolve; |
| 608 | + img.src = URL.createObjectURL(file); |
| 609 | + }); |
628 | 610 |
|
629 | | - convertedBlob = await imageConversion.compress(file, options); |
| 611 | + await loadPromise; |
630 | 612 |
|
631 | | - if (state.rotate.enabled && state.rotate.angle !== 0) { |
632 | 613 | const canvas = document.createElement('canvas'); |
| 614 | + canvas.width = state.resize.enabled ? state.resize.width : img.width; |
| 615 | + canvas.height = state.resize.enabled ? state.resize.height : img.height; |
| 616 | + |
633 | 617 | const ctx = canvas.getContext('2d'); |
634 | | - const img = new Image(); |
635 | 618 |
|
636 | | - await new Promise((resolve) => { |
637 | | - img.onload = resolve; |
638 | | - img.src = URL.createObjectURL(convertedBlob); |
639 | | - }); |
| 619 | + if (state.rotate.enabled && state.rotate.angle !== 0) { |
| 620 | + // Handle rotation for PNG |
| 621 | + if (state.rotate.angle === 90 || state.rotate.angle === 270) { |
| 622 | + canvas.width = img.height; |
| 623 | + canvas.height = img.width; |
| 624 | + } |
640 | 625 |
|
641 | | - if (state.rotate.angle === 90 || state.rotate.angle === 270) { |
642 | | - canvas.width = img.height; |
643 | | - canvas.height = img.width; |
| 626 | + ctx.translate(canvas.width / 2, canvas.height / 2); |
| 627 | + ctx.rotate(state.rotate.angle * Math.PI / 180); |
| 628 | + ctx.drawImage(img, -img.width / 2, -img.height / 2); |
644 | 629 | } else { |
645 | | - canvas.width = img.width; |
646 | | - canvas.height = img.height; |
| 630 | + // No rotation, just draw the image |
| 631 | + ctx.drawImage(img, 0, 0, canvas.width, canvas.height); |
647 | 632 | } |
648 | 633 |
|
649 | | - ctx.translate(canvas.width / 2, canvas.height / 2); |
650 | | - ctx.rotate(state.rotate.angle * Math.PI / 180); |
651 | | - ctx.drawImage(img, -img.width / 2, -img.height / 2); |
652 | | - |
653 | | - await new Promise(resolve => { |
654 | | - canvas.toBlob((blob) => { |
655 | | - convertedBlob = blob; |
656 | | - resolve(); |
657 | | - }, state.format, state.compressionLevel / 100); |
| 634 | + // Get PNG data |
| 635 | + convertedBlob = await new Promise(resolve => { |
| 636 | + canvas.toBlob(blob => resolve(blob), 'image/png'); |
658 | 637 | }); |
| 638 | + } else { |
| 639 | + // Use image-conversion for JPEG and GIF |
| 640 | + let options = { |
| 641 | + quality: state.compressionLevel / 100, |
| 642 | + type: state.format |
| 643 | + }; |
| 644 | + |
| 645 | + if (state.resize.enabled) { |
| 646 | + options.width = parseInt(state.resize.width); |
| 647 | + options.height = parseInt(state.resize.height); |
| 648 | + } |
| 649 | + |
| 650 | + convertedBlob = await imageConversion.compress(file, options); |
| 651 | + |
| 652 | + if (state.rotate.enabled && state.rotate.angle !== 0) { |
| 653 | + const canvas = document.createElement('canvas'); |
| 654 | + const ctx = canvas.getContext('2d'); |
| 655 | + const img = new Image(); |
| 656 | + |
| 657 | + await new Promise((resolve) => { |
| 658 | + img.onload = resolve; |
| 659 | + img.src = URL.createObjectURL(convertedBlob); |
| 660 | + }); |
| 661 | + |
| 662 | + if (state.rotate.angle === 90 || state.rotate.angle === 270) { |
| 663 | + canvas.width = img.height; |
| 664 | + canvas.height = img.width; |
| 665 | + } else { |
| 666 | + canvas.width = img.width; |
| 667 | + canvas.height = img.height; |
| 668 | + } |
| 669 | + |
| 670 | + ctx.translate(canvas.width / 2, canvas.height / 2); |
| 671 | + ctx.rotate(state.rotate.angle * Math.PI / 180); |
| 672 | + ctx.drawImage(img, -img.width / 2, -img.height / 2); |
| 673 | + |
| 674 | + await new Promise(resolve => { |
| 675 | + canvas.toBlob((blob) => { |
| 676 | + convertedBlob = blob; |
| 677 | + resolve(); |
| 678 | + }, state.format, state.compressionLevel / 100); |
| 679 | + }); |
| 680 | + } |
659 | 681 | } |
660 | 682 | } |
661 | 683 |
|
| 684 | + // Double-check that we're using the correct MIME type |
| 685 | + if (convertedBlob.type !== state.format) { |
| 686 | + console.warn(`Blob type mismatch: ${convertedBlob.type} vs expected ${state.format}. Forcing correct type.`); |
| 687 | + convertedBlob = new Blob([await convertedBlob.arrayBuffer()], { type: state.format }); |
| 688 | + } |
| 689 | + |
| 690 | + // Add to zip with correct extension |
662 | 691 | zip.file(fileName, convertedBlob); |
663 | 692 | const blobUrl = URL.createObjectURL(convertedBlob); |
664 | 693 | const newSize = convertedBlob.size; |
|
0 commit comments