Client-Side Image Format Conversion
Converting an image from one format to another usually means opening Photoshop, GIMP, or uploading to a service. But the browser can do it natively — and it's surprisingly simple.
The API
The same canvas.toBlob() that handles compression also handles format conversion:
js
async function convertImage(file, targetFormat, quality = 0.92) {
const mimeMap = {
jpeg: 'image/jpeg',
png: 'image/png',
webp: 'image/webp',
avif: 'image/avif',
};
const img = await loadImage(URL.createObjectURL(file));
const canvas = document.createElement('canvas');
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;
canvas.getContext('2d').drawImage(img, 0, 0);
return new Promise((resolve) => {
canvas.toBlob((blob) => resolve(blob), mimeMap[targetFormat], quality);
});
}
function loadImage(src) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = reject;
img.src = src;
});
}
That's the entire conversion logic. The browser handles all the format-specific encoding internally.
What Happens to Transparency?
When converting PNG (with transparency) to JPEG (no transparency), the transparent areas become white or black depending on the browser:
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#FFFFFF';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0);
Detecting Format Support
Not all browsers support all output formats. AVIF is still rolling out:
async function getSupportedFormats() {
const canvas = document.createElement('canvas');
canvas.width = 1;
canvas.height = 1;
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'red';
ctx.fillRect(0, 0, 1, 1);
const formats = ['image/jpeg', 'image/png', 'image/webp', 'image/avif'];
const results = {};
for (const mime of formats) {
results[mime] = await new Promise((resolve) => {
canvas.toBlob((blob) => resolve(blob?.type === mime), mime, 0.5);
});
}
return results;
}
Batch Conversion Flow
async function batchConvert(files, targetFormat, quality) {
const results = [];
for (const file of files) {
const blob = await convertImage(file, targetFormat, quality);
const name = file.name.replace(/\.[^.]+$/, '') + '.' + targetFormat;
results.push({ blob, name });
}
return results;
}
Supported Conversion Matrix
Source
JPEG
PNG
WebP
AVIF
GIF
SVG
HEIC/HEIF Input
iPhone photos in HEIC format are trickier — browsers can't decode them natively through <img>. The workaround is to use the image/heic MIME support detection (growing in Chrome) or decode via a WASM library like libheif-js.
The Tool
If you'd rather use a ready-made solution, ToolBox Image Converter (https://toolboximage.com/tools/converter/) handles all of this with a drag-drop UI, batch ZIP export, HEIC support, and adjustable quality — all client-side, zero uploads.
Try it at toolboximage.com/tools/converter/ (https://toolboximage.com/tools/converter/)
Top comments (2)
Nice walkthrough. Two production details I’d add: revoke the object URL after
img.onload/img.onerrorso batch conversions don’t retain every source file, and treatcanvas.toBlob()as nullable because an unsupported encoder can yieldnull. For AVIF/WebP, verify the returnedblob.typerather than assuming the requested MIME succeeded, then offer a fallback. Also, if camera photos are involved, normalize EXIF orientation before drawing—canvas output won’t preserve the original EXIF metadata.How does the Canvas API handle color profiles during format conversion, especially for WebP and AVIF? I'd love to swap ideas on optimizing this process.