DEV Community

whitetirocket
whitetirocket

Posted on

Validating Passport Photos for 3 of the Strictest Government Portals (India, China, US)

Validating Passport Photos for 3 of the Strictest Government Portals (India, China, US)

Passport photo validation looks like a solved problem. Crop to the country spec, slap on a white background, save as JPG. Done.

Then you start submitting to actual government portals and discover that each one has its own undocumented edge cases that reject otherwise correct photos. The crop is right. The background is right. The portal still rejects.

I run a free browser-based passport photo tool covering 85+ countries. Building country-specific validation has surfaced some genuinely strange constraints. Here are the three that taught me the most: India's Sarathi/Parivahan, China's COVA, and the US DS-160.

India: Sarathi and Parivahan portals

The Indian Ministry of Road Transport and Highways runs two portals for driving licenses and vehicle registration: Sarathi (for citizens) and Parivahan (the umbrella service). Both accept passport-style photos for license applications.

Documented spec:

  • 35 x 45 mm
  • Plain white background
  • JPG only

Undocumented constraint that catches every tool I have tested:

  • File size must be 20 to 50 KB

Not 50 KB max. A 20 KB minimum too. A clean photo from a modern phone, properly cropped to 35 x 45 mm at 300 DPI, lands at 80-150 KB. The portal silently rejects anything above 50 KB with a generic error like "image format not accepted".

The actual fix is JPEG quality compression, but you cannot just lower the quality slider blindly. At quality 60, the file size hits the range but Sarathi's automated face detection rejects "low image quality". At quality 75, the file is 60-70 KB and still rejected. The narrow window is quality 70 with subsampling 4:2:0 and progressive encoding off.

// Browser-side compression for Sarathi/Parivahan
async function compressForSarathi(canvas) {
  let quality = 0.70
  for (let attempt = 0; attempt < 5; attempt++) {
    const blob = await new Promise(r => canvas.toBlob(r, 'image/jpeg', quality))
    if (blob.size >= 20_000 && blob.size <= 50_000) return blob
    quality += blob.size > 50_000 ? -0.05 : 0.03
  }
  throw new Error('Could not hit Sarathi 20-50 KB window')
}
Enter fullscreen mode Exit fullscreen mode

The PAN card portal is similar: 25 x 35 mm size, but with a different range (10 to 300 KB) and no minimum on dimensions, only a 200 x 230 px maximum.

For the validated DIY workflow see the Indian driving license photo tool which auto-fits the 20-50 KB window before output.

China: COVA online visa portal

The China Online Visa Application portal (cova.cs.mfa.gov.cn) is the modern alternative to the 2 printed photos at a CVASC visa center. Foreigners applying for a Chinese visa from anywhere in the world upload through the same system.

Documented spec:

  • 33 x 48 mm (note: not 35 x 45)
  • Pure white background
  • JPG only
  • 354 x 472 px minimum
  • 40 KB to 1 MB file size

Undocumented:

  • The portal validates background uniformity by sampling pixels in 4 corners. If any corner pixel is not in the range R[245-255] G[245-255] B[245-255], it returns error E002.
  • Eyes must be detected as fully open. Even slightly squinted photos return E003.
  • The face must occupy 60-70% of frame height. Anything below 55% or above 75% returns E001 even though the file dimensions are correct.

The corner pixel check matters because most "white" backgrounds in real photos are slightly off-white from ambient light. A photo taken against a true-white wall in your living room often has corners reading R[238-242] - looks white to a human, fails the COVA validator.

The workaround is post-processing the background to pure #FFFFFF after segmentation:

function flattenBackgroundToPureWhite(imageData, mask) {
  for (let i = 0; i < imageData.data.length; i += 4) {
    const idx = i / 4
    if (mask[idx] < 0.5) {  // pixel is background
      imageData.data[i]     = 255  // R
      imageData.data[i + 1] = 255  // G
      imageData.data[i + 2] = 255  // B
    }
  }
  return imageData
}
Enter fullscreen mode Exit fullscreen mode

Background segmentation runs on BRIA RMBG-1.4 in WebAssembly. Once the mask is computed, flattening to pure white is cheap. The visa-specific validator is at the China visa photo tool.

US DS-160: the 240 KB cap

The US State Department's DS-160 form for nonimmigrant visa applications has the strictest documented file size cap of any major portal:

  • 240 KB maximum for the photo upload

That is below the natural file size of a properly cropped 600 x 600 px JPG at any reasonable quality setting. The portal also enforces:

  • Square aspect ratio (1:1)
  • 600 x 600 px minimum, 1200 x 1200 px maximum
  • JPEG only
  • White background, head 50-69% of frame

The 240 KB cap means JPEG quality must be in the 60-70 range AND dimensions cannot exceed about 800 x 800 px. Smaller dimensions actually help here - a 600 x 600 px photo at quality 80 fits comfortably.

The same cap does not apply to the paper passport photo (which is 51 x 51 mm at 300 DPI, no file size limit). DS-160 is digital-only, separate constraint.

// DS-160-compliant output (square + 240 KB cap)
async function exportForDS160(canvas) {
  // Force square 600x600
  const out = document.createElement('canvas')
  out.width = 600
  out.height = 600
  out.getContext('2d').drawImage(canvas, 0, 0, 600, 600)
  // Tune quality to fit 240 KB
  for (const q of [0.85, 0.80, 0.75, 0.70, 0.65, 0.60]) {
    const blob = await new Promise(r => out.toBlob(r, 'image/jpeg', q))
    if (blob.size <= 240_000) return blob
  }
  throw new Error('Could not fit 240 KB cap')
}
Enter fullscreen mode Exit fullscreen mode

The validated US flow is at the US passport photo tool which produces the DS-160-compatible square JPG plus a print-ready 51 x 51 mm PDF in the same export.

Architecture summary

Three portals, three different constraints, all of which the browser can solve without uploading anything.

The architecture I settled on:

  1. Capture or upload in the browser. No server, no upload.
  2. Face detection with face-api.js (WebAssembly + tiny TF model). Returns landmarks for cropping.
  3. Background segmentation with BRIA RMBG-1.4 (WebAssembly inference). Returns a per-pixel mask.
  4. Country-specific cropping based on each spec (head height, dimensions, ratio).
  5. Background replacement to pure white or country-specific (light grey for German, white for most).
  6. JPEG export with country-specific quality tuning to hit file-size constraints.

Privacy-wise the win is that biometric data (face, segmentation mask) never leaves the device. Practically, the win is that you can run validation against the actual portal constraints before the user even tries to upload, saving them the rejected-application loop.

The full tool is at idphotosnap.com - free, no signup, no watermark, 85+ countries.

FAQ

Q: Why not just use a server for image processing?

For passport photos specifically, the photo contains biometric data (face, expression, distinguishing marks). Many users of these tools are visa applicants from one country applying to another - exactly the scenario where regulators (and users) prefer the photo never reach a third-party server. WebAssembly inference makes this practical.

Q: How accurate is browser-side background segmentation?

BRIA RMBG-1.4 is good enough for hairline accuracy in most lighting conditions. The failure mode is high-contrast hair against a light background - here the segmentation occasionally clips fine strands. We mitigate by running a guided filter post-processing pass on the mask.

Q: What about countries with light grey backgrounds (UK, Germany)?

Same architecture, different output color. The mask defines the background region; replacement is just fillStyle = '#eeeeee' instead of #ffffff for those cases.

Q: Why do countries diverge so much on file size limits?

Most portals were built when JPEG was the only practical format and bandwidth was tight. The 240 KB DS-160 cap predates HTTPS being the default - it is a 2010-era constraint that no one updated as cameras got better. The 20 KB minimum on Sarathi is anti-malware (rejects very small files that might be exploit payloads). Combined, these heuristics are the legacy you have to code around.

Q: Open source?

The validators are not, but the architecture pattern is in the article. BRIA RMBG-1.4 and face-api.js are both open weights. WebAssembly is just WebAssembly.

Top comments (0)