DEV Community

Arhan Ahmad
Arhan Ahmad

Posted on • Originally published at toolboximage.com

Building a Browser-Based Passport Photo Maker with Country Presets

Building a Browser-Based Passport Photo Maker

Passport photos have strict government specifications — exact dimensions, specific background colors, and precise head positioning. Here's how to build a tool that handles all of this entirely in the browser.

The Challenge

Each country has different requirements:

Country Size (mm) Size (px at 300 DPI) Background
US 51×51 600×600 White
UK/EU 35×45 413×531 Light gray
India 35×45 413×531 White
Canada 50×70 591×827 White
China 33×48 390×567 White
Japan 35×45 413×531 White
Australia 35×45 413×531 White
France 35×45 413×531 Light gray
Germany 35×45 413×531 Light gray
UAE 43×55 508×650 White

Loading and Displaying the Image

First, load the user's image and display it with an interactive crop overlay:


js
function loadImage(file) {
  return new Promise((resolve) => {
    const img = new Image();
    img.onload = () => resolve(img);
    img.src = URL.createObjectURL(file);
  });
}
Interactive Crop Window
The crop window is an absolutely positioned div overlaid on the image. Users can drag it to reposition and resize using corner handles:
class CropWindow {
  constructor(container, image, preset) {
    this.container = container;
    this.image = image;
    this.preset = preset;
    this.x = 0;
    this.y = 0;
    this.w = 200;
    this.h = 260;
    this.initHandles();
    this.initKeyboard();
  }

  initHandles() {
    // 4 corner handles for resize
    const handles = ['nw', 'ne', 'sw', 'se'];
    handles.forEach(pos => {
      const handle = document.createElement('div');
      handle.className = `crop-handle ${pos}`;
      handle.addEventListener('pointerdown', (e) => this.startResize(e, pos));
      this.container.appendChild(handle);
    });
  }

  initKeyboard() {
    document.addEventListener('keydown', (e) => {
      const step = e.shiftKey ? 10 : 2;
      switch(e.key) {
        case 'ArrowUp': this.y = Math.max(0, this.y - step); break;
        case 'ArrowDown': this.y = Math.min(this.maxY, this.y + step); break;
        case 'ArrowLeft': this.x = Math.max(0, this.x - step); break;
        case 'ArrowRight': this.x = Math.min(this.maxX, this.x + step); break;
      }
      this.render();
    });
  }
}
Background Replacement
To change the background color, we scan edge pixels and replace similar colors:
function replaceBackground(canvas, targetColor, tolerance = 30) {
  const ctx = canvas.getContext('2d');
  const data = ctx.getImageData(0, 0, canvas.width, canvas.height);
  const pixels = data.data;

  // Sample background color from corners
  const bg = sampleCornerPixels(pixels, canvas.width, canvas.height);

  for (let i = 0; i < pixels.length; i += 4) {
    const diff = colorDistance(pixels, bg, i);
    if (diff < tolerance) {
      pixels[i] = targetColor.r;     // R
      pixels[i + 1] = targetColor.g; // G
      pixels[i + 2] = targetColor.b; // B
    }
  }

  ctx.putImageData(data, 0, 0);
}
Print Layout Generation
For users who want to print at a photo lab, we need to arrange multiple copies on a 4×6 inch (1024×1536 px at 300 DPI) print sheet:
function generatePrintLayout(singlePhoto, preset, sheetWidth = 1500, sheetHeight = 1000) {
  const canvas = document.createElement('canvas');
  canvas.width = sheetWidth;
  canvas.height = sheetHeight;
  const ctx = canvas.getContext('2d');

  // Fill with white background
  ctx.fillStyle = 'white';
  ctx.fillRect(0, 0, sheetWidth, sheetHeight);

  // Calculate how many photos fit
  const cols = Math.floor(sheetWidth / (preset.outW + 20));
  const rows = Math.floor(sheetHeight / (preset.outH + 20));

  for (let row = 0; row < rows; row++) {
    for (let col = 0; col < cols; col++) {
      const x = col * (preset.outW + 20) + 10;
      const y = row * (preset.outH + 20) + 10;
      ctx.drawImage(singlePhoto, x, y, preset.outW, preset.outH);
    }
  }

  return canvas;
}
Full Tool
The ToolBox Image Passport Photo Maker (https://toolboximage.com/tools/passport/) implements all of this with 10 country presets, 4 background colors, arrow key fine positioning, and both single photo + print layout downloads. All client-side, zero uploads.
Try it at toolboximage.com/tools/passport/ (https://toolboximage.com/tools/passport/)
Enter fullscreen mode Exit fullscreen mode

Top comments (2)

Collapse
 
nazar-boyko profile image
Nazar Boyko

The country preset table is genuinely useful, half the pain of passport photos is nobody knowing the exact spec, so baking those in is the real feature. Where I'd expect the background swap to struggle is any hair or edge that's close to the background color, since a flat corner-sample plus a distance threshold will either leave a halo or start eating into the person. If you ever want to push it further, running the color check in a space like LAB instead of raw RGB matches how people actually see "close enough" colors and tends to cut those halos a lot. For a simple white-wall photo though, the corner-sampling trick is exactly the right amount of clever.

Collapse
 
frank_signorini profile image
Frank

This is really cool! I'm curious how you