Skip to content

Commit cfa3d27

Browse files
committed
- implement specialized handling for image/png conversion using canvas
- add format validation to ensure correct mime types in converted files - fix png conversion issue by using direct canvas rendering instead of `imageConversion` library - add warning logging for blob type mismatches during conversion - improve rotation handling for different image formats
1 parent fb14f94 commit cfa3d27

File tree

1 file changed

+82
-53
lines changed

1 file changed

+82
-53
lines changed

tools/image_converter.html

Lines changed: 82 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -8,27 +8,11 @@
88
<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>">
99
<script src="https://cdn.tailwindcss.com"></script>
1010
<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;}
3216
</style>
3317
</head>
3418
<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>
235219

236220
<!-- Footer -->
237221
<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>
239223
</footer>
240224
</div>
241225
</div>
@@ -615,50 +599,95 @@ <h2 class="text-xl font-semibold text-gray-800">Conversion Results</h2>
615599
const buffer = await image.getBufferAsync(Jimp.MIME_BMP);
616600
convertedBlob = new Blob([buffer], { type: 'image/bmp' });
617601
} 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+
});
628610

629-
convertedBlob = await imageConversion.compress(file, options);
611+
await loadPromise;
630612

631-
if (state.rotate.enabled && state.rotate.angle !== 0) {
632613
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+
633617
const ctx = canvas.getContext('2d');
634-
const img = new Image();
635618

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+
}
640625

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);
644629
} 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);
647632
}
648633

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');
658637
});
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+
}
659681
}
660682
}
661683

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
662691
zip.file(fileName, convertedBlob);
663692
const blobUrl = URL.createObjectURL(convertedBlob);
664693
const newSize = convertedBlob.size;

0 commit comments

Comments
 (0)