Methodology & Accuracy

Every tool on FileConverters is a thin interface over a set of small, pure, automatically tested functions. This page documents how the pipeline actually works — detection, conversion, verification — and, just as importantly, what it deliberately does not do, so you can judge exactly how far to trust each output.

This page is the summary. Each stage has a longer guide that runs the same engine on illustrative inputs and shows every figure it returns. How image formats are detected covers the signatures, the ftyp brand rules, the extension check and animation detection. The conversion pipeline follows one file from detection through planning, decoding, encoding and verification. Transparency and flattening shows which targets cannot hold alpha and when the background prompt appears. Quality parameters and compress-to-target explains the quality scales, the MozJPEG lane and the binary search. HEIC and AVIF: decoding and licensing restates the vendored-codec facts and why conversions run from HEIC and to AVIF, never the reverse. What re-encoding does to metadata lists what a canvas re-encode discards and what survives. Resizing math works the fit, exact and percent arithmetic, the rounding rule and the caps. Image to PDF: page math works the points-per-inch arithmetic and the A4 and Letter fit. All eight are listed at /guides/.

Files are identified by content, not name

The first thing any tool does with a file is read its leading bytes and match them against the format signatures: \x89PNG, FF D8 FF for JPEG, RIFF…WEBP, GIF87a/GIF89a, and BM for BMP. The filename's extension is compared against that detection: a file named photo.png whose bytes are JPEG gets a visible notice and is converted from what it actually is. Animated WebP is recognized from the VP8X animation flag and ANIM chunks; animated GIF by counting image descriptors — so the "first frame only" caveat appears exactly when it applies. HEIC is identified by parsing the ISO-BMFF ftyp box — major and compatible brands (heic, heix, hevc, hevx, mif1, msf1) read from the box structure, bounded by the declared box size, never by substring-searching the file. AVIF — the same container, AV1-coded — is detected from its own brands (avif, avis) by the same box parser, with explicitly HEVC-coded brands taking precedence, and the avis image-sequence brand drives the same "first frame only" caveat that animated GIF and WebP get.

The conversion pipeline

Decoding uses the browser's own codecs via createImageBitmap with imageOrientation: 'from-image', which applies EXIF rotation so phone photos come out upright. The pixels are drawn to a canvas — composited over a background color first whenever the target format cannot represent transparency (JPEG; the page asks for the color before converting) — and re-encoded by the browser's PNG, JPEG, or WebP encoder, or by one of the two self-hosted encoders described below (MozJPEG on the Compress JPEG lane, libavif+aom for AVIF output). Which conversions need flattening, which take a quality parameter, and what the output filename becomes are decided by a pure planning module with its own tests, not ad-hoc in the UI.

HEIC decoding — the one third-party decoder on the site

Every other tool here decodes with the browser's own codecs. HEIC is the exception, because no browser ships a HEIC decoder: the two HEIC pages load libheif — © struktur AG and contributors, licensed LGPL-3.0 — compiled to WebAssembly from pinned upstream source, together with its libde265 HEVC decoder from the same upstream family, also LGPL. The upstream codec source is compiled without source modifications, self-hosted from this site's own origin, and loaded at runtime as a separately replaceable module in a dedicated worker (a script plus a .wasm file under /vendor/libheif/) — never compiled into the site's own JavaScript — so anyone can inspect it, verify its checksums, or substitute their own build. Source code is at github.com/strukturag/libheif; exact versions, commits, checksums, and the reproducible build recipe are in the project's provenance record. The full license text ships alongside the module at /vendor/libheif/LICENSE.txt. Decoding still happens entirely in your browser: the module is fetched from this site, and your photo never leaves the tab. Why a third-party decoder at all? Because "convert my iPhone photo" is the single most common conversion request there is, and the alternative — uploading your camera roll to someone's server — is exactly what this site exists to avoid.

Vendored encoders — MozJPEG and libavif + aom

Two tools go beyond the browser's built-in encoders, using the same self-hosting arrangement as libheif: open-source codecs compiled from pinned upstream source, served from this site's own origin as separately replaceable files, fetched lazily only when a file is actually dropped, and run in bounded workers entirely in your browser.

Every vendored file's exact size and SHA-256 is pinned by automated tests, and every codec build uses immutable upstream commits, so served bytes cannot drift silently. AVIF decoding (AVIF to JPG, AVIF to PNG) deliberately 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.

Why the site encodes AVIF but never HEIC

AVIF and HEIC are structurally siblings — the same HEIF container around a modern video codec — but they live under opposite legal regimes. HEIC's HEVC codec is covered by commercial patent pools, which is why browsers do not ship HEVC encoders and why this site's libheif build is decode-only: producing HEIC files would require licensing terms that a free, in-browser tool cannot honestly satisfy. AVIF's AV1 codec was developed by the Alliance for Open Media to be royalty-free under an explicit patent grant, which is why every current browser reads it and why this site is comfortable shipping an encoder for it. Practically: HEIC-class compression, or better, in a format the web actually reads — so conversions here go from HEIC and to AVIF, never the reverse.

What re-encoding does to metadata

Canvas re-encoding strips all embedded metadata: EXIF (including GPS location, camera model, and timestamps), color-profile tags, and text chunks. We present this as both a feature and a caveat, because it is both — publishing a converted photo cannot leak where it was taken, and equally, photographers lose copyright fields and ICC profiles, so originals should be kept. Orientation is applied during decoding, before metadata is discarded, so uprightness survives even though the orientation tag does not.

Every output is verified before download

A download button on this site means the output has already been checked: the produced bytes are sniffed with the same magic-byte detector applied to inputs (which catches a browser silently substituting PNG when it cannot encode WebP — reported as an honest error, never a mislabeled file), and the blob is re-decoded to confirm its pixel dimensions match the plan. File sizes shown are the output blob's true byte count. Where a result row shows a percent change, it is computed from those two measured byte counts for your file — the site publishes no blanket compression percentages anywhere, because no percentage is true for every image, and a result that comes out larger says "larger — keep your original" in the same breath.

The compress-to-target search

"Make this fit under N kilobytes" is solved by measurement, not prediction: the compressor encodes at maximum quality, then binary-searches the quality range — bracketing between the highest quality known to fit and the lowest known to overshoot — converging within eight encodes. The planner is a pure function over the list of attempts so far, and its tests pin the convergence bound and the failure mode: if even minimum quality overshoots the target, the tool reports the smallest achievable size instead of claiming success. PNG inputs are never "PNG-compressed" — a canvas cannot honestly do that — they are converted to a lossy format, labeled as such.

Resize arithmetic

Fit mode scales by min(boxWidth/w, boxHeight/h) preserving aspect ratio; exact mode stretches (with an optional lock that derives height from width); percent mode scales uniformly. Dimensions round to whole pixels with a 1-pixel floor so extreme aspect ratios cannot collapse to zero. Outputs are capped at 16,384 pixels per side and 80 megapixels overall, with a warning above 50 megapixels. Inputs are limited to 50 megapixels, 100 MB per file, and 40 files or 300 MB per selection. Enlargement is permitted but flagged: interpolation adds no detail, and the tool says so rather than advertising "enhancement".

The PDF writer

Image to PDF uses no library. A ~200-line writer assembles PDF 1.4 bytes directly: a catalog, a page tree, and per image a page object, a content stream, and the JPEG data embedded verbatim as a DCTDecode XObject — grayscale and CMYK JPEGs get the matching color space read from their own frame headers. Page geometry ("match image" at a chosen DPI, or A4/Letter with the image fit inside half-inch margins and centered) is computed by a pure function. Tests assert the header, object count, cross-reference offsets, EOF marker, and that embedded JPEG bytes round-trip byte-for-byte; the client re-checks the assembled structure before offering the download.

Verify the privacy claim yourself

"Your files never leave your browser" is enforced by architecture, and you do not have to take our word for it:

  • Open your browser's DevTools (F12) → Network tab, then convert a file. You will see no request carrying your image — no POST, no fetch, nothing but the page's own static assets.
  • The site's Content-Security-Policy header sets connect-src 'self': the browser itself refuses to let scripts on this page send data to any other origin. Check it in DevTools → Network → the document request → Headers.
  • The site is static files — there is no server application and no upload endpoint to receive files in the first place. Downloads are object URLs pointing at memory in your own tab.

Worked examples can't drift

Numbers in page copy — the resizer's "4032×3024 fit into 1200×1200 becomes 1200×900", the PDF page's point sizes — are computed at build time by the same engine code that processes your files. If the logic changed, the copy would change with it (or the build would fail), so the documentation cannot silently disagree with the tools.

Worked figures, computed at build time

The sections above state the rules. This section shows the engine applying them. Every figure below was produced when this page was built, by calling the functions the tools call — detectFormat, checkExtension, planConversion, outputFilename, planResize, pageGeometry, buildPdf and describeSizeChange — on inputs that are deliberately invented: byte arrays constructed to begin the way a file begins, fictional filenames, made-up dimensions and byte counts. The figures illustrate what the engine decides for those inputs. They are not measurements of any real file and imply nothing about what a real file will do. If a rule changed, these figures would change on the next build, or the build would fail.

Detection on constructed signatures

Each row is a short byte array that begins the way a file of that kind begins — the signature and little else. The detector reads bytes, not names, and for the PNG, JPEG, RIFF and GIF rows the verdict depends only on the leading bytes, so it is the same whether the array is a dozen bytes or a photograph. Two signatures also carry a minimum length, found here by probing the detector rather than copied from it. A BM header is accepted only from 14 bytes — the same header one byte shorter is unrecognized — which is why the BM row is padded to 14 bytes. An ISO-BMFF ftyp box must be at least 12 bytes long and declare a box size of 16 or more; a box declaring 15 is unrecognized, because the brand parser returns nothing for it. For the ISO-BMFF rows the table also shows the brand list the ftyp parser read before deciding.

Constructed input Leading bytes ftyp brands Verdict
PNG signature, then an IHDR chunk 89 50 4E 47 0D 0A 1A 0A — PNG
JPEG SOI marker, then an APP1 segment FF D8 FF E1 — JPEG
RIFF header, WEBP form, one VP8 chunk 52 49 46 46 10 00 00 00 57 45 42 50 — WebP
GIF89a header and screen descriptor 47 49 46 38 39 61 — GIF
BM header padded to 14 bytes 42 4D — BMP
ftyp box, major brand heic 00 00 00 18 66 74 79 70 68 65 69 63 heic, mif1, heic HEIC
ftyp box, major brand mif1, compatibles heic and hevc 00 00 00 1C 66 74 79 70 6D 69 66 31 mif1, mif1, heic, hevc HEIC
ftyp box, major brand avif 00 00 00 18 66 74 79 70 61 76 69 66 avif, mif1, miaf AVIF
ftyp box, major brand avis 00 00 00 18 66 74 79 70 61 76 69 73 avis, avif, mif1 AVIF
ftyp box, major brand avif, compatible heic 00 00 00 18 66 74 79 70 61 76 69 66 avif, mif1, heic HEIC
plain text 68 65 6C 6C 6F 2C — unrecognized

Three rows carry the rules worth noticing. A box whose major brand is the structural mif1 is HEIC because an HEVC brand appears among its compatibles. A box declaring both avif and heic is HEIC, because an explicitly HEVC-coded brand takes precedence, as the detection section states. Plain text is unrecognized: the detector returns no format rather than a guess, and the tool reports the file as unrecognized instead of converting it.

The extension check compares that verdict with the name. For a file named holiday.png whose bytes are the JPEG row above, the bytes are JPEG, the name claims PNG, and the mismatch flag is yes — the tool shows the notice and converts from JPEG. The same bytes named holiday.jpg give a mismatch flag of no. Named holiday.xyz, the claimed format is unrecognized and the flag is no: the check fires only when both sides are known and disagree.

Animation is a separate question from format, answered by three separate checks on the same kind of constructed bytes:

  • isAnimatedWebP on a WebP with a VP8 chunk only: no
  • isAnimatedWebP on a WebP with the VP8X animation flag and an ANIM chunk: yes
  • isAnimatedGif on a GIF with one image descriptor: no
  • isAnimatedGif on a GIF with three image descriptors: yes
  • isAnimatedAvif on an ftyp box, major brand avif: no
  • isAnimatedAvif on an ftyp box, major brand avis: yes

Three conversion plans

The planning module takes a detected source format, a target format and optional settings, and returns the decisions the client must carry out. It never sees pixels. These three plans were computed for invented filenames; the table shows every field a plan returns.

Input Source → target Flatten Background Quality First frame only Output name
logo.png PNG → JPEG yes #ffffff 0.92 no logo.jpg
sticker.gif GIF → PNG no #ffffff none (lossless) yes sticker.png
photo.jpg JPEG → AVIF no #ffffff 0.5 no photo.avif

PNG to JPEG is the flattening case. The target's capability entry says it cannot carry alpha and PNG is among the formats that can, so the flatten flag is yes and the background is #ffffff unless the page is given another hex colour. The plan does not know whether this particular PNG has any transparent pixels; on an opaque image the composite changes nothing. The quality of 0.92 is on the canvas encoder's scale, taken from the capability table because no override was passed. GIF to PNG is the first-frame case: the flag is yes because the GIF container can hold more than one frame, and it is set before the file has been examined — whether this GIF actually has several frames is isAnimatedGif's question, answered above. PNG is lossless, so the quality is none (lossless), and it carries alpha, so flattening is no. JPEG to AVIF runs on the vendored encoder lane: the plan's quality of 0.5 maps to 50 on the encoder's own scale, flattening is no and the first-frame flag is no. The two flags are independent: a WebP to JPEG plan sets both — flatten yes, first frame only yes.

Output names swap the extension for the target's canonical one. When the swap would leave the name unchanged, a suffix keeps the download from shadowing the original: photo.jpg re-encoded as JPEG is named photo-converted.jpg. 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."

Resize plans in each mode

The resize planner takes source dimensions and a request and returns target dimensions, the source rectangle to draw from, an enlargement flag and a list of warnings. Besides the fit, exact and percent modes described above, the module defines a fill mode that covers the box exactly and records a centred crop of the source; the Image Resizer page offers fit, exact and percent. Every row below is the planner's output for an invented source.

Mode Request Source Plan Drawn from Enlargement Warnings
fit 1200×1200 box 4032×3024 1200×900 whole source no none
fill 1200×1200 box 4032×3024 1200×1200 3024×3024 at (504, 0) no none
exact, lock on width 1600 4032×3024 1600×1200 whole source no none
exact, lock off 1200×1200 4032×3024 1200×1200 whole source no none
percent 25% 4032×3024 1008×756 whole source no none
fit 100×100 box 10000×10 100×1 whole source no none
fit 1600×1600 box 800×600 1600×1200 whole source yes 1
percent 150% 6000×4000 9000×6000 whole source yes 2

Fit chose the smaller of the two ratios, so the 4032×3024 source landed at 1200×900 inside the box rather than being padded to fill it. Fill chose the larger ratio and cropped the overflow: 1200×1200 drawn from a 3024×3024 region of the source starting at (504, 0). The aspect lock ran first for the exact-mode row: a width of 1600 derives a height of 1200 from the source ratio. Without the lock, 1200×1200 is handed through as 1200×1200 and the plan records no distortion — its only flag is enlargement, which is no here. Percent is linear on each side: 25% leaves 762,048 of the original 12,192,768 pixels, 6.25% of them. The 1-pixel floor shows in the 10000×10 row, which becomes 100×1 instead of collapsing to zero height.

The enlargement row's warning reads, verbatim: "Enlarging beyond the original size cannot add detail — the result will be softer than the source." The 6000×4000 source at 150% is permitted — 9000×6000, 54,000,000 pixels — and arrives with 2 warnings:

  • "Enlarging beyond the original size cannot add detail — the result will be softer than the source."
  • "Very large output (over 50 megapixels) — encoding may be slow or fail on low-memory devices."

Three requests were refused before any pixel work, in the engine's words. The 4032×3024 source at 500% — "Output dimensions are limited to 16384 pixels per side (browser canvas limit)." The 6000×4000 source at 200%, within the side limit but over the pixel cap — "Outputs are limited to 80 megapixels to keep the browser responsive." A request of 1001% — "Scaling beyond 1000% is not supported."

Page geometry for A4 and Letter

The PDF writer works in points. Rather than type its units and defaults here, the build reads them back from the writer: an image declared at one pixel and one DPI measures 72 points across, so there are 72 points to the inch; a PDF built with no options declares a page 720 points wide for a 960-pixel image, which makes the default 96 DPI; and a very wide image fitted on A4 sits 36 points in from the left edge, which makes the default margin 36 points — 0.5 inch, the half inch the PDF section states. The geometry below uses those defaults plus one illustrative chosen DPI of 300.

Image (px) Page Page size (pt) Image drawn (pt) Offset (pt) Bound by
4032×3024 match image, 96 DPI 3024×2268 3024×2268 (0, 0) —
4032×3024 match image, 300 DPI 967.68×725.76 967.68×725.76 (0, 0) —
4032×3024 A4 595.28×841.89 523.28×392.46 (36, 224.72) width
4032×3024 Letter 612×792 540×405 (36, 193.5) width
1000×4000 A4 595.28×841.89 192.47×769.89 (201.4, 36) height
1000×4000 Letter 612×792 180×720 (216, 36) height

In "match image" mode the page is the image: 4032×3024 pixels at 96 DPI is a 3024×2268-point page, and at 300 DPI a 967.68×725.76-point page; the embedded JPEG is the same bytes in both. On A4 the image is scaled by the smaller of the two available-to-image ratios and centred: 523.28×392.46 points at (36, 224.72), bound by its width. On Letter the same image is 540×405 points at (36, 193.5). The tall 1000×4000 image is bound by its height on both pages, so its vertical offset is exactly the margin and its horizontal offset is larger; the build checks that relationship before publishing the table.

The writer's structure is read back rather than described. A one-image PDF built from a fixture begins %PDF-1.4, ends %%EOF and contains 5 objects — a catalog, a page tree, and a page, a content stream and an image object for the image. Three images produce 11 objects, a page tree with /Count 3 and a cross-reference table of 12 entries. The image object's filter is /DCTDecode; its colour space is /DeviceRGB for a three-component frame header, /DeviceGray for one component and /DeviceCMYK for four. The build confirmed that each fixture's bytes appear unchanged inside the output and that startxref points at the table. The frame-header parser read the 1920×1080 fixture as 1920×1080 with 3 components; handed PNG bytes it refused: "Not a JPEG file (missing FF D8 FF signature)." A DPI outside the writer's range is refused as "DPI must be between 24 and 600."

Size-change wording on invented byte counts

The result row's percent is produced by one function from two measured byte counts. The byte counts below are invented to exercise each rounding rule the function applies; they are arithmetic, not measurements, and they say nothing about how much any file will shrink.

Before (bytes) After (bytes) Shown as
2,400,000 1,800,000 25% smaller
1,000,000 964,000 3.6% smaller
1,000,000 905,000 9.5% smaller
1,000,000 900,000 10% smaller
850,000 918,000 8% larger — keep your original
1,000,000 999,900 less than 0.1% smaller
640,000 640,000 same size

The rows show the rules. "3.6% smaller" and "9.5% smaller" keep one decimal place; from "10% smaller" upward the figure is a whole number. A change too small to round to a tenth of a percent is shown as "less than 0.1% smaller", identical counts as "same size", and growth is stated as plainly as shrinkage with the advice attached: "8% larger — keep your original". A before-count of zero returns no value (null): no percent exists, so none is shown.

Constants, read from the engine

The tables below are not typed. Each value is read from the engine module that enforces it when this page is built, so a changed constant changes this page.

Formats

Format MIME type Output extension Decoded Produced Note
PNG image/png .png yes yes —
JPEG image/jpeg .jpg yes yes —
WebP image/webp .webp yes yes —
GIF image/gif .gif yes no decoded, never encoded
BMP image/bmp .bmp yes no decoded, never encoded
HEIC image/heic .heic yes no decoded, never encoded
AVIF image/avif .avif yes yes decoded by the browser only

The engine decodes PNG, JPEG, WebP, GIF, BMP, HEIC, AVIF and produces PNG, JPEG, WebP, AVIF. GIF, BMP, HEIC are decoded and never encoded — the target type admits only the 4 produced formats, and HEIC's absence from it is the licensing matter explained above. AVIF is decoded by the browser's own codec with no vendored fallback, as the encoder section states.

Target capabilities

Target Carries alpha Lossy Default quality (engine scale) Encoder-scale equivalent
PNG yes no none (lossless) —
JPEG no yes 0.92 92
WebP yes yes 0.85 85
AVIF yes yes 0.5 50

The engine's quality scale runs from zero to one; encoderQualityPercent maps it to the whole numbers from 0 to 100 that the self-hosted encoders take. A target whose quality is none is lossless and accepts no quality setting — the planner's refusal for that case is quoted above. The two vendored lanes carry their own defaults. The Compress JPEG lane encodes at 0.75 on the engine scale — 75 on the encoder's — with progressive scan order on by default, and the compress-to-target search is available on it. The AVIF lane encodes at 0.5 — 50 on the encoder's scale — at speed preset 8, and the compress-to-target search is not available on it, so those pages offer the quality control only.

Limits and page sizes

  • Output dimensions: at most 16,384 pixels per side (MAX_DIMENSION).
  • Output pixels: at most 80 megapixels (MAX_OUTPUT_PIXELS), with a warning above 50 megapixels (MEGAPIXEL_WARN_THRESHOLD).
  • Percent scaling: refused above the bound the planner states as "Scaling beyond 1000% is not supported."
  • A4: 595.28×841.89 points (8.27×11.69 inches).
  • Letter: 612×792 points (8.50×11.00 inches).
  • Points per inch 72; default DPI 96; default margin 36 points — each read back from the writer as described above.

The input limits are those stated under "Resize arithmetic" above; they bound a selection before any plan is made.

Tested against fixtures, fixed with tests

The engines are covered by an automated suite: byte-array fixtures for every format signature (including mismatched-extension and truncated cases), convergence and unreachable-target cases for the compression search, aspect-ratio edge cases for the resizer, and structural assertions for the PDF writer. If you believe an output is wrong, that is exactly the report we want — see the contact page. Confirmed issues are fixed in the engine and locked in with a new test so they cannot recur silently.

Last reviewed: 2026-10-01, against the engine modules named on this page. The worked figures and constants above are recomputed on every build.