The State Department's passport photo page has a line most photo apps quietly ignore: "Do not change your photo using computer software, phone apps or filters, or artificial intelligence." Many passport photo tools auto-fix the photo, replacing the background, smoothing the light and recentering the face. I wanted the opposite: a tool that only checks the photo, and takes it only when it already passes.

That became PicPassed. Here is how it works and what I learned measuring the official examples.

Everything runs in the browser

The photo never leaves the phone. The checks run on the device with MediaPipe's Face Landmarker (@mediapipe/tasks-vision), which gives face landmarks plus "blendshapes" such as eye blink and jaw open. From those and the raw pixels, the checker measures:

  • Head tilt and turn: the angle of the line between the eyes, and how far the nose sits off the face's centre.
  • Eyes and mouth: blink and jaw-open blendshapes, plus the gap between the inner lips for "avoid showing teeth".
  • Even light: the darker cheek's brightness compared with the brighter one.
  • The wall: brightness, colour and unevenness of the background around the head.
  • Sharpness: the variance of the Laplacian over the face.

There is no shutter button. The camera loop runs the full check on each frame and takes the photo only when a frame passes everything, so nobody takes a photo and is then told it failed. Each failed check has a short hint on screen ("Turn to face the light", "Find a plain white wall").

Calibrating against the official examples

The State Department doesn't publish numbers for "not tilted" or "even lighting". It publishes example photos, each marked acceptable or unacceptable. So I ran them through the checker and set the limits in the gap between the two groups. Re-running 21 of them with the current limits:

Check Accepted examples Unacceptable example Our limit
Head tilt (adults and children) 0.3° to 4.7° "head tilted to the side": 10.2° under 5°
Even light (darker side vs brighter side) 86% to 100% tilted example with shadows: 55% at least 62%
Wall brightness (0 black, 255 white) 188 to 253 dark photo: 157 at least 170
Wall colour (0 white or grey) up to 11 blue wall: 65 34 or less

Every unacceptable example was turned down: blue background, dark photo, eyes closed, head tilted, mouth open, and a baby with a pacifier.

What it gets wrong, honestly

  • The baby with the pacifier fails only because the head is turned. The checker doesn't see pacifiers.
  • It also can't see glasses, hats, scarves, red eye, colour tints, AI changes or textured walls such as white brick. The site says so, and tells people to check those themselves.
  • Two accepted examples fail. One is a man in a wheelchair whose head support shows behind him; the State Department accepts that with a note. The other is a baby lying down with the head tilted 9.2°.
  • The examples are tiny (200 to 300 pixels), so sharpness and size can't be calibrated from them. Those limits still need readouts from real phones.

The full write-up is on the site: acceptable vs unacceptable passport photo examples, measured.

The rest of the stack

  • Hosting: a static site generated by one Node script and deployed on Cloudflare Pages, with a few Pages Functions for checkout.
  • Payment: Stripe Checkout. Checking is free, and you pay only after the photo passes.
  • Output: the digital file is cropped and resized only, never retouched.

If you've built something with MediaPipe in the browser, I'd like to hear how you handled sharpness on phone cameras. That's the part I'm still calibrating.