Online photo studios often require users to upload their images to remote servers for processing. This introduces latency, server storage expenses, and privacy concerns for sensitive personal identification documents.
By leveraging the HTML5 Canvas API, modern web applications can perform precise image cropping, contrast adjustments, border generation, and sheet composition directly in the browser at print-grade 300 Dots Per Inch (DPI) resolution.
Here is how the underlying rendering pipeline is engineered.
1. The Physics of Print: Millimeters to Pixels at 300 DPI
Screen displays operate in CSS pixels (typically 72 or 96 pixels per inch). However, professional photo printers require 300 DPI to avoid pixelation on physical paper.
To convert real-world millimetric passport standards into Canvas pixel dimensions, use this universal formula:
Pixels = (Dimension in Millimeters / 25.4) 300
Standard Dimensions Breakdown:
- Indian Passport Photo: 35mm x 45mm = 413px x 531px at 300 DPI.
- US Visa / Passport Photo: 51mm x 51mm (2" x 2") = 600px x 600px at 300 DPI.
- Standard 4R Print Paper: 102mm x 152mm (4" x 6") = 1200px x 1800px at 300 DPI.
- Standard A4 Print Paper: 210mm x 297mm = 2480px x 3508px at 300 DPI.
2. High-Performance Canvas Sheet Tiling Math
When arranging multiple photos onto an A4 or 4R sheet, you must calculate:
- Margins (Top, Bottom, Left, Right)
- Spacing gaps between individual photos
- Maximum columns and rows that fit cleanly
- Cut guides (dotted lines for manual scissors trimming)
export function renderPassportSheet(
croppedImg: HTMLImageElement,
paperWidthMm: number,
paperHeightMm: number,
photoWidthMm: number,
photoHeightMm: number,
marginMm: number,
gapMm: number
): HTMLCanvasElement {
const DPI = 300;
const mmToPx = (mm: number) => Math.round((mm / 25.4) DPI);
const canvas = document.createElement('canvas');
canvas.width = mmToPx(paperWidthMm);
canvas.height = mmToPx(paperHeightMm);
const ctx = canvas.getContext('2d');
if (!ctx) throw new Error('Could not obtain canvas context');
// Fill white print background
ctx.fillStyle = '#FFFFFF';
ctx.fillRect(0, 0, canvas.width, canvas.height);
const photoWidthPx = mmToPx(photoWidthMm);
const photoHeightPx = mmToPx(photoHeightMm);
const marginPx = mmToPx(marginMm);
const gapPx = mmToPx(gapMm);
const availableWidth = canvas.width - (2 marginPx);
const availableHeight = canvas.height - (2 marginPx);
const cols = Math.floor((availableWidth + gapPx) / (photoWidthPx + gapPx));
const rows = Math.floor((availableHeight + gapPx) / (photoHeightPx + gapPx));
for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
const x = marginPx + c (photoWidthPx + gapPx);
const y = marginPx + r (photoHeightPx + gapPx);
// Draw photo
ctx.drawImage(croppedImg, x, y, photoWidthPx, photoHeightPx);
// Draw subtle cut-line border
ctx.strokeStyle = '#CCCCCC';
ctx.lineWidth = 1;
ctx.strokeRect(x, y, photoWidthPx, photoHeightPx);
}
}
return canvas;
}
3. Preserving Image Sharpness During Export
Browsers often downsample canvas data if exported naively. To ensure full fidelity:- Use
canvas.toBlob(callback, 'image/jpeg', 1.0)with quality parameter set to1.0. - Wrap the download in a synthetic anchor element with the
downloadattribute. - For PDF generation, pass the uncompressed canvas data URL to jsPDF with explicit millimeters dimensions so print shops get exact physical scale.