Schengen requires a 35×45 mm paper photo, with a 32–36 mm chin-to-crown span. At pixtidy's chosen 600 dpi, the export is 827×1063 pixels. The head-height calculation below uses annotated chin and crown coordinates. There is no common Schengen digital-upload specification.
You can check your own photo against these rules for free in your browser on this page; the photo is never uploaded (disclosure: I built it).
What measurements should you start with?
- Keep the paper size and head size as separate inputs.
I built pixtidy, whose free check uses MediaPipe face detection in your browser. Your photo is never uploaded. The useful engineering question is how to connect physical photo rules to measurements you can actually test.
Start with the European Commission ICAO photo guidelines and German Foreign Office visa photo instructions.
For the geometry, keep these values explicit:
const photoWidthMm = 35;
const photoHeightMm = 45;
const minHeadHeightMm = 32;
const maxHeadHeightMm = 36;
The first two describe the finished paper photo. The last two describe the chin-to-crown span within it.
How do you convert 35×45 mm to pixels at 600 dpi?
- Apply the export resolution to each paper dimension.
Use millimetres divided by 25.4, then multiply by the resolution in dots per inch:
const dpi = 600;
const mmToPx = mm => Math.round(mm * dpi / 25.4);
const widthPx = mmToPx(photoWidthMm); // 827
const heightPx = mmToPx(photoHeightMm); // 1063
The calculations are:
Width: 35 × 600 / 25.4 ≈ 827 pixels
Height: 45 × 600 / 25.4 ≈ 1063 pixels
That is pixtidy's 827×1063-pixel export at 600 dpi. The rounding gives you whole-pixel image dimensions.
I would label this in an interface as "600 dpi export dimensions." There is no common Schengen digital-upload specification.
How do you obtain a chin-to-crown measurement?
- Make the measurement endpoints explicit before checking the range.
MediaPipe face detection is the detection step in pixtidy. For this walkthrough, I would use manual annotation for the chin-to-crown measurement: display the photo and ask you to mark the crown and chin. This is a proposed measurement workflow, not a claim that pixtidy provides an annotation feature.
The annotations would look like this:
Top of photo
┌─────────────────────────┐
│ │
│ Crown ───────── yCrown│
│ ↕ │
│ Measured span │
│ ↕ │
│ Chin ────────── yChin │
│ │
└─────────────────────────┘
Bottom of photo
With vertical coordinates increasing down the image, subtract the crown coordinate from the chin coordinate:
const headHeightPx = yChin - yCrown;
The important work happens before that subtraction. In this workflow, you identify the endpoints on the photo.
If annotations are made on a scaled browser preview, convert the span to export coordinates:
const exportHeadHeightPx =
(previewChinY - previewCrownY) *
heightPx / previewHeightPx;
This gives you a concrete way to convert the annotated span into export pixels.
What does a worked head-height check look like?
- Calculate proportional bounds, then test the annotated span.
The head-height rule is 32–36 mm within a 45 mm-high photo. As a fraction of image height, that is approximately 71–80%.
Keep the original fractions in code:
const minHeadPx = heightPx * minHeadHeightMm / photoHeightMm;
const maxHeadPx = heightPx * maxHeadHeightMm / photoHeightMm;
// minHeadPx ≈ 755.91
// maxHeadPx = 850.4
For a worked boundary example, take an annotated span of 850 pixels, the whole-pixel value just below the calculated upper bound. This is an arithmetic example, not a measurement from a real applicant's photo.
const measuredHeadPx = Math.floor(maxHeadPx); // 850
const withinHeadHeightRange =
measuredHeadPx >= minHeadPx &&
measuredHeadPx <= maxHeadPx;
const equivalentHeadMm =
measuredHeadPx / heightPx * photoHeightMm;
// withinHeadHeightRange: true
// equivalentHeadMm: approximately 35.98
The annotated distance passes because 850 is above approximately 755.91 and below 850.4. Translated back into the paper-photo proportions, it represents approximately 35.98 mm.
The appropriate result label is "Measured head height is within range." It describes exactly what the comparison established.
Which measurement edge cases need special handling?
- Handle these two cases before presenting a result.
A resized preview. The approximately 756–850-pixel range belongs to the 1063-pixel-high export.
A rounded boundary. "71–80%" and "756–850 pixels" are useful display labels, but they are approximations.
Both cases can produce a misleading answer even when your annotation endpoints are correct.
What must you inspect beyond the pixel checks?
- Review appearance separately from geometry.
The background must be plain, light and even, ideally neutral grey, with no shadows. Your face must be centred, sharp and evenly lit.
Check your expression: neutral, mouth closed, looking straight at the camera.
If you wear glasses, there must be no glare or tinted lenses, and the frames must not cover your eyes. Head coverings are allowed only for religious reasons.
Review those requirements separately. Neither detection nor an in-range measurement guarantees acceptance.
Where can you try the browser check?
You can use my free Schengen photo tool or the French checker.
When implementing the annotation workflow above, make the endpoints visible alongside the numerical result. That lets you inspect the measurement the code actually tested.
Related: Checking passport photos against government specs in the browser with MediaPipe · Why a DS-160 photo can be too small, and how I test it in the browser
Disclosure: I built pixtidy.
Top comments (0)