How it works
No part of this is secret, and knowing what the tools do — and do not do — is the difference between a photo that gets accepted and one that gets rejected for looking edited.
Nothing is uploaded, and here is how to check
This site is static files served from a CDN. There is no application server, no database, and
no upload endpoint — there is nowhere for a file to be sent even if the code tried. The image
work uses browser APIs that have been standard for years: createImageBitmap to
decode, a canvas to draw and scale, and toBlob to encode.
Two ways to verify it rather than take our word for it. Open your browser's developer tools, switch to the network tab, and use a tool — you will see the page's own files load and nothing else. Or load the page, disconnect from the internet entirely, and keep using it. It works.
Hitting an exact kilobyte target
The JPEG quality parameter is not a size dial. Quality 0.8 might produce 90 KB from one photo and 400 KB from another, because the encoder's output depends on how much detail is actually in the image. There is no formula that maps a target size to a quality value.
What is true is that the relationship is monotonic: raising quality never makes the file smaller. That is enough for a binary search. The tool encodes at high quality, and if that overshoots, it halves the quality range and tries again, eight times over. Each pass either fits — in which case it becomes the new best answer and the search moves upward looking for something better — or it does not, and the search moves down.
The goal is the largest file that fits, not the smallest possible file. If your limit is 200 KB, a 40 KB result is a worse answer than a 197 KB one: same acceptance, visibly worse photo. Portals that specify a floor as well as a ceiling make this explicit, but it is the right target either way.
Every pass re-encodes from the original decoded pixels. Re-compressing a previous output would stack generations of loss, so the file you download has been through exactly one encode at the final quality.
When quality is the wrong lever
Below a certain size, no quality setting is small enough. A 4000-pixel-wide photograph has too much detail to fit in 20 KB at any quality — you get mush, not a small photo. When the search exhausts its range, the tool reduces the dimensions by 25% and searches again, repeating until it fits. It reports that it had to, along with the final dimensions, because a portal that checks dimensions as well as file size will care.
Decoding HEIC without a server
HEIC files are HEVC-compressed images in a HEIF container. HEVC is patent-encumbered, and only Safari — from version 17 — decodes it natively. Chrome and Firefox do not.
So the page carries its own decoder: libheif, the open-source reference implementation,
compiled to WebAssembly. It is loaded on demand, only when you actually give it a HEIC file, so
the JPEG tools stay lightweight. This is also why the tool can read a HEIC that has been
renamed to .jpg — a routine result of sending photos through messaging apps. Files
are identified by their first bytes, not their extension.
Calculating a passport crop
Every official specification measures head height from the crown of the head — hair included — to the bottom of the chin, either in millimetres or as a percentage of the image height. That single measurement determines everything else, because the crop's aspect ratio is fixed by the spec.
Given the head height in your photo's pixels and the required head fraction, the crop height follows: divide one by the other. The width follows from the aspect ratio. The horizontal centre is taken from the midpoint of your two markers, which tracks a slightly tilted head better than either marker alone.
Why you place the markers instead of a model finding your face
Face detection finds the face. Specs measure the head. Those differ by the height of the hair, and the gap is larger than the tolerance some countries allow — the Dutch specification permits a 4 mm window on a 45 mm photo, which is under 9%. An inferred bounding box does not reliably land inside that.
Two markers you place yourself are more accurate, need no model download, work offline, and — most importantly — are checkable. The tool shows you the millimetre figure your crop produced and whether it falls inside the official range. You can verify the arithmetic against the published specification yourself, which you cannot do with a black box.
The one number that is a convention, not a rule
Most authorities specify head height and leave vertical placement implied. So the tool has to decide how much of the leftover space goes above the crown versus below the chin. It defaults to putting slightly more below, which is how compliant photos are framed in practice, and it exposes that as a slider labelled as a convention rather than a requirement. We would rather show you an assumption than hide it.
What the tools deliberately do not do
- Replace or remove backgrounds. It requires guessing where hair ends and wall begins, and the artefacts — halos, softened edges, chewed fringes — are precisely what gets a photo flagged as digitally altered. Multiple specifications on this site name that as a rejection reason. The fill colour setting only paints area outside your photo's edges.
- Retouch faces. Japan's guidance explicitly rejects eye enlargement, face slimming and blemish removal — the things phone cameras now apply by default. A tool that smoothed skin would be actively harmful here.
- Promise acceptance. Geometry and file size are the parts that can be computed exactly, and this does them exactly. Lighting, expression, sharpness, background and recency are properties of how the photo was taken.
A note on kilobytes
1 KB means 1024 bytes here, because that is what upload validators check against. Some file managers use 1000 bytes, making the same file look about 2.4% larger. On a tight limit, aim a few KB under.
Built with
Astro generating static HTML, no UI framework, one stylesheet. libheif via WebAssembly for HEIC decoding, loaded on demand. Hosted on Cloudflare Pages. The image code is a few hundred lines of TypeScript, which is roughly the point: this was never a hard problem, just an unglamorous one.