Skip to content

Methodology

Image format methodology

Canvas API + browser-native encoders. The image never leaves your device.

By Published Updated

The Image clusterconverts between PNG, JPG, and WebP using the browser’s native Canvas API. There’s no upload step, no server-side processing, and no third-party imaging service in the path — which has implications for both privacy and quality.

The conversion pipeline

  1. The user picks a file via drag-and-drop or file picker.
  2. The file is read as an ImageBitmap via createImageBitmap(file).
  3. An offscreen canvas is created at the bitmap’s native dimensions.
  4. The bitmap is drawn to the canvas.
  5. The canvas is exported via canvas.toBlob(callback, mimeType, quality) — quality only applies to lossy targets.
  6. The resulting Blob is offered as a download.

Every step runs in the browser. The file isn’t sent anywhere — there’s no fetch, no FormData, no XHR upload. The same applies to the Image to Base64 tool, which uses the FileReader API instead of canvas but follows the same browser-only principle.

Quality handling

Lossless → lossy (PNG/WebP → JPG)

Going from a lossless format to JPG, we expose a quality slider (default 0.85) that controls the JPG encoder’s quantisation aggressiveness. At 0.85 the file is typically 80-95% smaller than the source PNG with no perceptible visual difference for photographs. Below 0.7 artefacts become visible; we cap the lower bound at 0.5 to prevent accidentally producing visibly-degraded output.

Lossy → lossless (JPG → PNG)

Going from JPG to PNG doesn’t restore quality the JPG already discarded. The output is bit-perfect of what the JPG decoded into, but the JPG’s rounding losses are baked in. This is rarely useful in practice; the better path is to find a higher-quality source.

Lossy → lossy (JPG → WebP, etc.)

Each lossy save introduces new quantisation errors on top of the source’s existing ones. For files that have been through several saves already, this can cause visible compounding degradation. Always re-encode from the highest- quality source you have.

What we don’t handle

  • HEIC / HEIF. Apple’s default since iOS 11. Browser support is essentially zero outside Apple’s ecosystem, so we’d need to bundle a 1+ MB decoder. Not worth it.
  • RAW formats (DNG, CR2, NEF, ARW). Same problem — heavyweight decoders for low audience. Use Lightroom / darktable / RawTherapee.
  • SVG ↔ raster. Possible via canvas, but vector → raster loses scalability and the reverse loses fidelity. Different conversation.
  • Bulk batch conversion. Our UI handles one file at a time. For batch use the REST endpoint or a desktop tool.

The encoder pipeline, in code

The conversion is short enough to write out. Given a File from a file input, the full pipeline runs about six lines:

const bitmap = await createImageBitmap(file);
const canvas = new OffscreenCanvas(bitmap.width, bitmap.height);
const ctx = canvas.getContext("2d");
ctx.drawImage(bitmap, 0, 0);
const blob = await canvas.convertToBlob({ type: "image/webp", quality: 0.85 });
// blob is the converted output, ready to download

Every step is browser-native: createImageBitmap decodes the source format (defined in the HTML Living Standard), OffscreenCanvas provides a non-rendering drawing surface, and convertToBlob invokes the browser’s built-in WebP / JPEG / PNG encoder with the spec-defined quality parameter (0-1 for lossy formats, ignored for PNG). The same encoder is what Chrome ships for saving canvases via toDataURL; we just route the result to a download instead of a string. There is no WASM, no third-party library, and no network call — the entire pipeline runs in roughly 40-200 ms for a 4K input on a 2023-class laptop, with the bottleneck shifting between decode (PNG sources) and encode (WebP outputs at quality 0.85+).

Sources & references

Encoding behaviour is defined in the HTML Living Standard for canvas serialisation. JPEG baseline DCT comes from ITU-T T.81; PNG from IETF RFC 2083; WebP from RFC 9649. Quality semantics — the quality parameter between 0 and 1 for lossy encoders — are normative in the HTML spec. Full citations in the Sources block below.

Assumptions & limitations

  • One file at a time. The UI takes a single file per session. Bulk re-encoding needs a script against the REST API or a desktop tool.
  • Native browser encoders.Output quality depends on the user’s browser. Chromium’s WebP encoder is libwebp; Firefox uses its own. Subtle per-engine artefact differences exist at quality < 0.7.
  • No HEIC/HEIF/RAW decoding.Inputs in those formats can’t be opened viacreateImageBitmapin most browsers without a WASM shim we don’t ship.
  • No AVIF encoding. Reading is supported in modern browsers; writing requires WASM (libaom), which would push the bundle past acceptable size.
  • Lossless ↔ lossy is one-way.Re-encoding a JPG to PNG doesn’t restore information; the re-encoded PNG is bit-perfect of the decoded JPG, with its quantisation losses baked in.
  • ICC profile preserved only for same-format rewrites. Cross-format conversion (PNG → JPG) may drop the embedded ICC profile depending on the browser implementation.
  • Memory bound. Very large inputs (40+ megapixel) can OOM on mobile Safari.

Browser support floor

WebP encoding via canvas.toBlobrequires Chromium 23+, Firefox 65+, Safari 14+, Edge 79+. As of 2026 that’s >97% of global traffic. AVIF encoding is not supported in the browser canvas API; we’d need a WASM encoder to ship that target, which is roadmap material.

Frequently asked questions

How does Convertitive convert between image formats?
The pipeline: (1) load the source file into a browser Image element; (2) draw it onto an HTML Canvas at the target dimensions; (3) call canvas.toBlob(callback, mimeType, quality) with the desired format and quality parameter. The browser's built-in codec handles the actual encoding. This means format support is device-dependent: WebP decoding requires Chrome/Firefox/Edge (Safari 14+); AVIF requires Chrome 85+/Firefox 93+.
What compression algorithm does the JPEG encoder use?
JPEG uses Discrete Cosine Transform (DCT) lossy compression as specified in ISO/IEC 10918-1:1994 (JPEG baseline). The browser encoder's quality parameter maps to the DCT quantisation table scaling — quality=1.0 uses minimal quantisation (near-lossless), quality=0 uses maximal quantisation (heavy lossy). The exact quantisation tables are implementation-specific per the HTML canvas.toBlob() spec (WHATWG HTML §15.4.13).
Is the image conversion lossless for PNG-to-PNG?
PNG uses DEFLATE lossless compression (RFC 2083, §2.1). Re-encoding a PNG through the Canvas API re-runs DEFLATE, which may produce a larger or smaller file than the original depending on the browser's DEFLATE implementation and compression level settings. The pixel values are identical; only the compressed file size changes. Alpha channel data is preserved exactly.
Does the image ever leave my device?
No. All image processing uses the browser's Canvas API and FileReader API — purely client-side JavaScript with no server requests. The Network tab in browser DevTools shows zero outbound requests when converting images. This is verifiable independently; the source is public at github.com/convertitive.
What are the accuracy and quality limitations of the image converter?
Three limitations: (1) color profile: Canvas draws images in the browser's internal color space (sRGB on most devices), so embedded ICC profiles in source images may be discarded; (2) EXIF/metadata: canvas.toBlob() strips all EXIF, IPTC, and XMP metadata — orientation, GPS, camera model are lost; (3) quality is subjective — the quality parameter maps differently across browsers' JPEG and WebP encoder implementations, so a quality=0.8 JPEG from Chrome may differ in size and appearance from one generated by Firefox at the same setting.

Sources & references

Authoritative references cited by this piece. Verified by Buğra Sözeri on the dates shown and re-checked at every deploy.

Related

Published May 14, 2026 · Last reviewed May 31, 2026