The conversion pipeline

Every converter on this site runs the same five stages in the same order: identify the file from its bytes, plan the conversion, decode the pixels, draw and encode them, and verify what came out. This guide shows what each stage decides. The planning stage is a pure module with no access to the page, and every verdict, flag, quality value, filename and percentage quoted below was produced by the engine's own functions at build time on illustrative inputs. If the engine changes, this page changes with it — or the build fails.

Stage 1 — identify the file from its bytes

Nothing downstream trusts a filename. The detector reads the first bytes of the file and matches them against the format signatures the methodology page lists — \x89PNG, FF D8 FF, RIFF…WEBP, GIF87a/GIF89a, BM — and, for the ISO-BMFF family, parses the ftyp box to read its brands. The inputs here are illustrative: constructed byte arrays that begin the way a real file begins, not real files. Given a 12-byte array that opens the way a PNG file opens, the detector returns png, the engine's internal name for what you see as PNG. Given a 6-byte array beginning FF D8 FF it returns jpeg.

The extension check then compares that reading with the name. A file called holiday.png whose bytes are JPEG yields actual JPEG, claimed PNG, mismatch yes: the page shows a notice and converts from what the file actually is. The same bytes named holiday.jpg yield mismatch no and no notice. Brand lists, animation detection and truncated files are covered in How image formats are detected.

Stage 2 — plan before touching a pixel

With the source known, the page asks the planner for a plan to its target. The planner takes the source, the target and two optional settings — a background colour and a quality override — and returns a small record: the MIME type to encode with, whether a background flatten is needed and with which colour, the quality to use (or none), and whether animation would drop to its first frame. It consults a capability table, one row per producible format:

Target Carries transparency Takes a quality parameter Default quality (engine scale, zero to one)
PNG yes no none (lossless)
JPEG no yes 0.92
WebP yes yes 0.85
AVIF yes yes 0.5

Two rules fall out of that table, and a third from the engine's list of animatable sources. A flatten is planned exactly when the target cannot carry transparency and the source might: PNG to JPEG plans one (yes, background #ffffff); JPEG to PNG does not (no). HEIC and AVIF sources count as possibly transparent because the HEIF container can carry an alpha plane, so HEIC to JPEG plans one too (yes) — a no-op for an opaque photo. The quality field is filled only for lossy targets, from the table's default unless the page passes an override; a lossless target gets none (lossless). The first-frame flag is not in the table at all: it comes from the engine's list of animatable source formats — GIF, WebP and AVIF, whose containers can hold more than one frame — and depends on the source alone, so GIF to PNG carries animationDropsToFirstFrame: yes.

The planner also knows what it will never produce. The engine's source-only list — GIF, BMP, HEIC — names formats the site decodes but does not encode, and its target type admits only PNG, JPEG, WebP, AVIF — which is why there is no "to GIF" or "to HEIC" page.

Worked plans

Each row is the planner's answer for an illustrative filename on one of the tool pages, with the output filename from the same module. None of these files exist; the values are what the engine decides for the pair.

Illustrative input Tool Encode as Flatten? Quality First frame only? Output filename
logo.png PNG to JPG image/jpeg yes 0.92 no logo.jpg
scan.jpg JPG to PNG image/png no none (lossless) no scan.png
icon.png PNG to WebP image/webp no 0.85 no icon.webp
banner.webp WebP to PNG image/png no none (lossless) yes banner.png
banner.webp WebP to JPG image/jpeg yes 0.92 yes banner.jpg
IMG_0412.HEIC HEIC to JPG image/jpeg yes 0.92 no IMG_0412.jpg
IMG_0412.HEIC HEIC to PNG image/png no none (lossless) no IMG_0412.png
poster.avif AVIF to PNG image/png no none (lossless) yes poster.png
poster.avif AVIF to JPG image/jpeg yes 0.92 yes poster.jpg
photo.jpg JPG to AVIF image/avif no 0.5 no photo.avif

The filename rule: the input's extension is swapped for the target's canonical one and the base name kept, so IMG_0412.HEIC becomes IMG_0412.jpg. When the swap would leave the name unchanged — the target's canonical extension equals the input's, as for a file named photo.jpg whose bytes are AVIF on AVIF to JPG — the planner's default suffix keeps the download from shadowing your original: photo-converted.jpg. The comparison ignores case: Photo.JPG becomes Photo-converted.jpg. The pages that re-encode a file to its own format pass their own suffix to the same function: Compress JPEG and the Image Compressor turn photo.jpg into photo-compressed.jpg, and the Image Resizer into photo-resized.jpg.

Options change the plan predictably; both inputs here are illustrative. An illustrative black background passed to the PNG-to-JPEG plan yields background #000000 with nothing else changed; an illustrative quality of 0.6 passed to PNG-to-WebP replaces the default exactly. The two self-hosted encoder lanes take the same number on the 0–100 scale — 75 for the MozJPEG lane's default, 50 for the AVIF lane's, which is what the photo.jpg to AVIF plan above resolves to (50). Quality parameters and compress to target covers what the number controls and how the compress-to-target search drives it.

What the planner refuses

The planner refuses rather than guesses, in these words: a background given as a colour name — "Enter the background as a hex color like #ffffff."; a quality passed for a lossless target — "This output format is lossless and has no quality setting."; a quality outside the accepted range — "Quality must be between 0.05 and 1." Each refusal is a guard inside the planning module with its own test, and it holds whatever calls the module. The tool pages' own controls are built not to trigger the first of them: their background control is a colour picker that can only produce a six-digit hex value, so the colour-name refusal is a guard the pages never reach. When a plan is refused, the client stops that file and the planner's message is shown in the file's result row.

Stage 3 — decode with orientation applied

Decoding uses the browser's own codecs via createImageBitmap with imageOrientation: 'from-image', which applies EXIF rotation so phone photos come out upright. Two sources take a different route. No browser ships a HEIC decoder, so the two HEIC pages load libheif — compiled from pinned upstream source without modification, self-hosted under /vendor/libheif/, running in a dedicated worker, decode only. AVIF decoding vendors nothing: current Chrome and Edge, Firefox 93+, and Safari 16.4+ decode AVIF natively, so those pages use the browser's own codec with a runtime feature probe and a plain-language notice when a browser cannot. The licensing behind both choices is set out in HEIC and AVIF decoding and licensing.

Whatever the route, one bitmap comes out — for a GIF, WebP or AVIF file holding several frames, the first frame, exactly as the plan's flag warned. Whether the notice appears for a given file is decided by the detector's animation checks — the VP8X animation flag or an ANIM chunk for WebP, more than one image descriptor for GIF, the avis image-sequence brand for AVIF — which the page runs for GIF, WebP and AVIF sources.

Stage 4 — draw, flatten, encode

The bitmap is drawn to a canvas. If the plan set needsBackgroundFlatten, the canvas is first filled with the plan's background colour — #ffffff unless the page asked you for another — and the image is composited over it, because the target has nowhere to store per-pixel transparency. Transparency and flattening covers what that does to semi-transparent edges.

The canvas is then handed to an encoder with the plan's MIME type and quality: the browser's PNG, JPEG or WebP encoder for most pages, MozJPEG on the Compress JPEG lane, and libavif driving the aom AV1 encoder on the two AVIF output pages — the last two compiled from pinned upstream source and self-hosted under /vendor/mozjpeg/ and /vendor/avif/, with every vendored file's size and SHA-256 pinned by tests.

One consequence applies to every conversion and so appears in no plan: canvas re-encoding strips all embedded metadata — EXIF including GPS location, camera model and timestamps, colour-profile tags and text chunks. That is both a feature and a caveat, and What re-encoding does to metadata goes through it in full. Orientation survives because it was applied in stage 3, before the metadata was discarded.

Stage 5 — verify before a download button appears

An encoder's output is not trusted either. The produced bytes are sniffed with the same detector from stage 1 — which catches a browser silently substituting PNG when it cannot encode WebP, reported as an honest error rather than a mislabelled file — and the blob is re-decoded to confirm its pixel dimensions match the plan. Only then does the row fill in, with the output's true byte count.

Where a row shows a percent change, it is computed from the two measured byte counts for your file and nothing else. The function that formats it has rounding rules pinned by tests, and the examples below let the function state them. They run it on invented byte counts, chosen only to show each rule — they say nothing about what any real file will do, and the site publishes no blanket compression percentages anywhere. "3.6% smaller" and "9.5% smaller" keep one decimal place, and an exact figure such as "5% smaller" drops its trailing zero; from "10% smaller" upward the figure is a whole number, so "13% smaller" is shown without a decimal. A change too small to round to a tenth of a percent reads "less than 0.1% smaller", identical counts read "same size", and growth is stated as plainly as shrinkage with the advice attached: "8% larger — keep your original".

  • 2,400,000 bytes → 2,100,000 bytes: 13% smaller
  • 1,000,000 bytes → 964,000 bytes: 3.6% smaller
  • 1,000,000 bytes → 950,000 bytes: 5% smaller
  • 1,000,000 bytes → 905,000 bytes: 9.5% smaller
  • 1,000,000 bytes → 900,000 bytes: 10% smaller
  • 850,000 bytes → 918,000 bytes: 8% larger — keep your original
  • 1,000,000 bytes → 999,900 bytes: less than 0.1% smaller
  • 640,000 bytes → 640,000 bytes: same size
  • a "before" of zero bytes → 512,000 bytes: null — no honest percent exists

What the pipeline cannot know

The pipeline measures bytes and pixel dimensions; it does not look at the picture. It cannot tell you whether a lossy result looks acceptable for your purpose, whether the first frame was the frame you wanted, or whether the flattening colour suits wherever the image is going. It cannot restore transparency a plan flattened or metadata the re-encode discarded — which is why every tool page says to keep your original.

Common misreadings

  • "I converted to PNG, so the image is lossless now." The PNG encode is lossless from the canvas onward; whatever the decoded source had already lost is not recovered.
  • "The file ends in .png, so it is a PNG." Stage 1 reads bytes. A .png that is JPEG inside is detected, flagged (mismatch yes) and converted from JPEG.
  • "A quality of 0.92 keeps that fraction of the original." It is an encoder parameter on the engine's scale, which runs from zero to one, not a percentage of anything measurable.

Each stage has its own guide — detection, flattening, quality and the target-size search, HEIC and AVIF decoding, metadata, resizing math and PDF page math — all listed at /guides/. The methodology page is the vetted summary of the whole engine.

Frequently asked questions

Does the pipeline change the pixels before encoding them?

For the converters, only in two planned ways. Orientation is applied while decoding, so a rotated photo is drawn upright. And when the plan sets needsBackgroundFlatten — as the PNG to JPEG plan does — the canvas is filled with the plan's background colour (#ffffff unless you pick another) before the image is drawn over it. No other planned change is applied before encoding; the encoder's own lossy step, where the target has one, is covered under stage 4. The Image Resizer adds a third planned change: it draws the bitmap at the planned target size before encoding (see the Resizing math guide).

Why does the download have a different extension from the one I expected?

The planner's outputFilename function swaps the input's extension for the target's canonical one, so a JPEG target always ends in .jpg — the illustrative IMG_0412.HEIC becomes IMG_0412.jpg. When the swap would leave the name unchanged — an illustrative file named photo.jpg whose bytes are not JPEG, converted to JPEG — the planner's default suffix keeps the download from shadowing your original: photo-converted.jpg. The compressor and resizer pages pass their own suffix to the same function, so the same name comes back as photo-compressed.jpg from Compress JPEG or the Image Compressor and photo-resized.jpg from the Image Resizer.

When does the "first frame only" notice appear, and what does it mean?

The plan's animationDropsToFirstFrame flag is true whenever the source container can hold more than one frame — GIF, WebP and AVIF sources. The notice itself appears when the detector's animation check fires for your file — the VP8X animation flag or an ANIM chunk for WebP, more than one image descriptor for GIF, the avis image-sequence brand for AVIF. The page runs that check for GIF, WebP and AVIF sources, the same list the plan flag reflects. Decoding keeps only the first frame, so a multi-frame file converts to a single still. For the illustrative banner.webp to PNG plan the flag is yes; for scan.jpg to PNG it is no.

Why was I asked for a background colour?

Because the plan set needsBackgroundFlatten: the target cannot carry transparency — the capability table marks JPEG's supportsAlpha as no — and the source might. The default is #ffffff. The planner accepts only hex colours.

How do I know the output really is what the result row says?

Because the row is filled in after verification, not before. The produced bytes are sniffed with the same detector used on inputs, the blob is re-decoded to confirm its pixel dimensions match the plan, and the size shown is the output blob’s true byte count. Any percent change is computed from those two measured counts for your file only; a result that comes out larger says "larger — keep your original".

Can the pipeline produce HEIC, GIF or BMP files?

No. The engine's SOURCE_ONLY_FORMATS list — GIF, BMP, HEIC — names the formats it decodes but never encodes, and its target type admits only PNG, JPEG, WebP, AVIF. The HEIC and AVIF guide explains the licensing reason behind the HEIC half of that rule.

Local processing: every stage described here (detection, planning, decoding, encoding and verification) runs in your own browser tab — files are decoded, converted, and verified entirely in your browser and never transmitted. See the methodology page for how to verify the no-upload claim yourself.