From Millimeters to Pixels: How a Passport Photo Sheet Is Laid Out
Passport rules are written in millimeters. Photo printers think in inches. Online application forms count pixels. When I built ToolOnTap’s passport photo maker, most of the work was converting between those three units without losing a millimeter along the way, and then fitting as many copies as possible onto a single 4 × 6 inch photo print.
Millimeters to pixels
A pixel has no physical size on its own. It only gets one when you print it at a given resolution, measured in dots per inch (DPI). The conversion is one line: divide millimeters by 25.4 to get inches, then multiply by the DPI.
The tool prints at 300 DPI, which is the usual resolution for photo prints. Each official size is stored once in millimeters, and the print size in pixels is worked out from it:
const at300 = (mm: number) => Math.round(mm / 25.4 * 300)
const spec = (s: Omit<PhotoSpec, 'px'> & { px?: PhotoSpec['px'] }): PhotoSpec =>
({ ...s, px: s.px ?? { width: at300(s.widthMm), height: at300(s.heightMm) } })
For the three countries with their own pages, that gives:
| Document | Official size | Print size at 300 DPI | Digital file |
|---|---|---|---|
| US passport | 2 × 2 in (51 × 51 mm) | 600 × 600 px | 600 × 600 px |
| UK passport | 35 × 45 mm | 413 × 531 px | 600 × 772 px |
| Canadian passport | 50 × 70 mm | 591 × 827 px | 591 × 827 px |
Two rows need explaining.
The US entry overrides the formula. The US rule is defined in inches. 51 mm is that size rounded to a whole millimeter, and running it through at300 gives 602 pixels, which is 2 pixels too wide. So the US spec sets 600 × 600 directly, which is exactly 2 inches at 300 DPI.
The UK digital file is bigger than the print. The UK’s online applications ask for a digital photo of at least 600 × 750 pixels, and 413 × 531 is too small for that. The tool scales the print size up until both sides meet the minimum, keeping the 35 × 45 shape. Width needs the bigger factor (600 / 413), and that same factor applied to 531 gives 772. That’s why the UK page offers a 600 × 772 photo instead of 600 × 750: a 600 × 750 photo would have a slightly different shape from the printed one. The scale factor never goes below 1, so a country without an online minimum keeps its print size.
Tagging the file with 300 DPI
Getting the pixels right is half the job. A printer also needs to know how big to make them. If a JPG doesn’t say, printing software has to guess, and a 600-pixel photo can come out at the wrong size.
A JPG can carry its resolution in its JFIF header, a few bytes near the start of the file. The browser’s canvas doesn’t let you set it, so the tool writes it by hand after encoding:
const out = bytes.slice()
out[13] = 1 // density units: dots per inch
out[14] = dpi >> 8; out[15] = dpi & 0xFF
out[16] = dpi >> 8; out[17] = dpi & 0xFF
return out
Byte 13 says the unit is dots per inch, and the next four bytes hold the horizontal and vertical density as 16-bit numbers. The function first checks that the file really starts with a JFIF header, and returns it unchanged if it doesn’t. Both downloads, the single photo and the sheet, are JPGs at 95% quality tagged at 300 DPI.
Fitting copies on a 4 × 6 sheet
A 4 × 6 inch print is 1200 × 1800 pixels at 300 DPI. The tool tries to fit as many copies of the photo as it can, with a 30-pixel gap between them (about 2.5 mm, room for scissors) and a 30-pixel margin around the edge.
The layout code tries four arrangements and keeps the one with the most photos:
- Portrait sheet, with the gap and margin.
- Landscape sheet, with the gap and margin.
- Portrait sheet, photos edge to edge.
- Landscape sheet, photos edge to edge.
// First candidate with the most photos wins, so spacing is kept on ties
return candidates.reduce((best, c) => (c.positions.length > best.positions.length ? c : best))
The comparison is a strict “more than”, so on a tie the earlier candidate, the one with spacing, stays. Gaps are only dropped when they would cost a whole photo. Each arrangement also centers its grid on the sheet, so leftover space is split evenly around the edges.
Here’s what that produces for the three verified sizes:
- US, 2 × 2 in: six photos. Three rows of two use exactly 1200 × 1800 pixels, so there’s no room for gaps. The copies touch, with a thin gray line between them to cut along.
- UK, 35 × 45 mm: eight photos, on a sheet turned to landscape, edge to edge. With gaps, only six would fit.
- Canada, 50 × 70 mm: four photos. Two 591-pixel photos side by side take 1182 of the 1200 pixels, so a gap wouldn’t fit and the pair sits in the middle of the sheet.
Every photo has a 1-pixel light gray outline (#c8c8c8) around it, so you can still see where to cut when there’s no gap. The sheet is drawn from the same cropped photo as the digital file, scaled to the print size, so the UK’s 600 × 772 photo is drawn at 413 × 531 on the sheet.
To print it, tell the shop or your printer to use 100% or “actual size”. “Fit to page” would scale the sheet and undo all the careful math. A ruler check on one photo takes ten seconds and saves a trip back.
Head size as a percentage
The rules also say how big your head must be, from chin to crown, in millimeters. While you’re framing the photo, a millimeter on a phone screen means nothing, so the tool turns it into a share of the photo’s height. For the UK, 29–34 mm in a 45 mm photo becomes “about 64–76% of the photo height”, which you can judge by eye against the crop box. The crop box itself is locked to the document’s shape, so you can’t produce a photo with the wrong proportions.
Only checked sizes get a page
This is the rule I care about most. Official photo sizes look like an easy thing to copy from other websites, and plenty of websites copy each other. A wrong size means a rejected application, a wasted print, and sometimes a missed appointment.
So every size in data/photo-specs.ts has a verified flag. It’s set to true only after I’ve checked the size against the government’s own page, and the link to that page is stored next to it. A unit test enforces the pairing: any spec marked verified must have an https:// source, or the test suite fails.
Right now three are verified: the US, the UK and Canada. They’re the only ones with their own pages, and their pages link to the official requirements. A few more countries are in the code with a // TODO: verify comment, because the sources I found disagree or aren’t official. Those stay in the passport photo maker’s list so people can still use them, but the tool shows “Check the official requirements before printing.” instead of an official link, and they get no dedicated page until I’ve confirmed them. I’m not going to repeat their sizes here either.
What the tool doesn’t do
It doesn’t whiten or replace the background, and it doesn’t check your expression, glasses or lighting. Size and shape are math, and the tool gets them exact. Whether a photo is accepted also depends on those other things, and they’re decided by the photo you take.
Try it
The passport photo maker does all of the above in your browser, so your photo isn’t uploaded. If you know which document you need, start from its own page: US passport photo, UK passport photo or Canadian passport photo.