mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
feat(passport-photo): SOTA passport photo maker with compliance validation (#64)
* feat(passport-photo): add passport specs database and tool constants * feat(passport-photo): add MediaPipe FaceMesh landmark detection script * feat(passport-photo): add TypeScript bridge for face landmark detection * feat(passport-photo): add API routes with analyze and generate endpoints * fix(passport-photo): accept landmarks from request body and fix pixel coordinate conversion - Generate endpoint now accepts landmarks + imageWidth/imageHeight in request body instead of re-running AI face detection (makes generate phase instant) - Fixed bug where normalized landmark coordinates (0-1) were used directly as pixel values in crop computation - now properly multiplied by imgW/imgH - Fixed same bug in pipeline process function * feat(passport-photo): add UI component with live preview and compliance overlay --------- Co-authored-by: stirling-image <stirling-image@users.noreply.github.com>
This commit is contained in:
co-authored by
stirling-image
parent
43821a955c
commit
2f11b9e101
@@ -217,6 +217,14 @@ export const TOOLS: Tool[] = [
|
||||
icon: "Undo2",
|
||||
route: "/restore-photo",
|
||||
},
|
||||
{
|
||||
id: "passport-photo",
|
||||
name: "Passport Photo",
|
||||
description: "AI-powered passport and ID photo generator",
|
||||
category: "ai",
|
||||
icon: "UserCheck",
|
||||
route: "/passport-photo",
|
||||
},
|
||||
// Watermark & Overlay
|
||||
{
|
||||
id: "watermark-text",
|
||||
@@ -424,6 +432,455 @@ export const SMART_CROP_FACE_PRESETS: SmartCropFacePreset[] = [
|
||||
{ id: "half-body", label: "Half Body", multiplier: 7.0 },
|
||||
];
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Passport / ID Photo Specs
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export type PassportRegion = "americas" | "europe" | "asia" | "africa" | "oceania" | "middle-east";
|
||||
|
||||
export interface PassportDocumentSpec {
|
||||
type: string;
|
||||
label: string;
|
||||
width: number; // mm
|
||||
height: number; // mm
|
||||
dpi: number;
|
||||
headHeightMin: number; // fraction of photo height
|
||||
headHeightMax: number; // fraction of photo height
|
||||
eyeLineFromBottom: number; // fraction of photo height
|
||||
bgColor: string; // default background hex
|
||||
bgColors: string[]; // allowed background colors
|
||||
}
|
||||
|
||||
export interface PassportSpec {
|
||||
code: string;
|
||||
name: string;
|
||||
flag: string;
|
||||
region: PassportRegion;
|
||||
documents: PassportDocumentSpec[];
|
||||
}
|
||||
|
||||
export const PASSPORT_SPECS: PassportSpec[] = [
|
||||
// Americas
|
||||
{
|
||||
code: "US",
|
||||
name: "United States",
|
||||
flag: "🇺🇸",
|
||||
region: "americas",
|
||||
documents: [
|
||||
{
|
||||
type: "passport",
|
||||
label: "US Passport",
|
||||
width: 51,
|
||||
height: 51,
|
||||
dpi: 300,
|
||||
headHeightMin: 0.5,
|
||||
headHeightMax: 0.69,
|
||||
eyeLineFromBottom: 0.56,
|
||||
bgColor: "#FFFFFF",
|
||||
bgColors: ["#FFFFFF"],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
code: "CA",
|
||||
name: "Canada",
|
||||
flag: "🇨🇦",
|
||||
region: "americas",
|
||||
documents: [
|
||||
{
|
||||
type: "passport",
|
||||
label: "Canadian Passport",
|
||||
width: 50,
|
||||
height: 70,
|
||||
dpi: 300,
|
||||
headHeightMin: 0.44,
|
||||
headHeightMax: 0.51,
|
||||
eyeLineFromBottom: 0.55,
|
||||
bgColor: "#FFFFFF",
|
||||
bgColors: ["#FFFFFF"],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
code: "BR",
|
||||
name: "Brazil",
|
||||
flag: "🇧🇷",
|
||||
region: "americas",
|
||||
documents: [
|
||||
{
|
||||
type: "passport",
|
||||
label: "Brazilian Passport",
|
||||
width: 50,
|
||||
height: 70,
|
||||
dpi: 300,
|
||||
headHeightMin: 0.5,
|
||||
headHeightMax: 0.69,
|
||||
eyeLineFromBottom: 0.56,
|
||||
bgColor: "#FFFFFF",
|
||||
bgColors: ["#FFFFFF"],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
code: "MX",
|
||||
name: "Mexico",
|
||||
flag: "🇲🇽",
|
||||
region: "americas",
|
||||
documents: [
|
||||
{
|
||||
type: "passport",
|
||||
label: "Mexican Passport",
|
||||
width: 35,
|
||||
height: 45,
|
||||
dpi: 300,
|
||||
headHeightMin: 0.7,
|
||||
headHeightMax: 0.8,
|
||||
eyeLineFromBottom: 0.63,
|
||||
bgColor: "#FFFFFF",
|
||||
bgColors: ["#FFFFFF"],
|
||||
},
|
||||
],
|
||||
},
|
||||
// Europe
|
||||
{
|
||||
code: "GB",
|
||||
name: "United Kingdom",
|
||||
flag: "🇬🇧",
|
||||
region: "europe",
|
||||
documents: [
|
||||
{
|
||||
type: "passport",
|
||||
label: "UK Passport",
|
||||
width: 35,
|
||||
height: 45,
|
||||
dpi: 300,
|
||||
headHeightMin: 0.7,
|
||||
headHeightMax: 0.8,
|
||||
eyeLineFromBottom: 0.63,
|
||||
bgColor: "#D4D4D4",
|
||||
bgColors: ["#D4D4D4"],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
code: "EU",
|
||||
name: "European Union",
|
||||
flag: "🇪🇺",
|
||||
region: "europe",
|
||||
documents: [
|
||||
{
|
||||
type: "passport",
|
||||
label: "EU Passport (ICAO)",
|
||||
width: 35,
|
||||
height: 45,
|
||||
dpi: 300,
|
||||
headHeightMin: 0.7,
|
||||
headHeightMax: 0.8,
|
||||
eyeLineFromBottom: 0.63,
|
||||
bgColor: "#FFFFFF",
|
||||
bgColors: ["#FFFFFF", "#D4D4D4"],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
code: "DE",
|
||||
name: "Germany",
|
||||
flag: "🇩🇪",
|
||||
region: "europe",
|
||||
documents: [
|
||||
{
|
||||
type: "passport",
|
||||
label: "German Passport",
|
||||
width: 35,
|
||||
height: 45,
|
||||
dpi: 300,
|
||||
headHeightMin: 0.7,
|
||||
headHeightMax: 0.8,
|
||||
eyeLineFromBottom: 0.63,
|
||||
bgColor: "#D4D4D4",
|
||||
bgColors: ["#D4D4D4"],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
code: "FR",
|
||||
name: "France",
|
||||
flag: "🇫🇷",
|
||||
region: "europe",
|
||||
documents: [
|
||||
{
|
||||
type: "passport",
|
||||
label: "French Passport",
|
||||
width: 35,
|
||||
height: 45,
|
||||
dpi: 300,
|
||||
headHeightMin: 0.7,
|
||||
headHeightMax: 0.8,
|
||||
eyeLineFromBottom: 0.63,
|
||||
bgColor: "#D4D4D4",
|
||||
bgColors: ["#D4D4D4", "#BFDBFE"],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
code: "RU",
|
||||
name: "Russia",
|
||||
flag: "🇷🇺",
|
||||
region: "europe",
|
||||
documents: [
|
||||
{
|
||||
type: "passport",
|
||||
label: "Russian Passport",
|
||||
width: 35,
|
||||
height: 45,
|
||||
dpi: 300,
|
||||
headHeightMin: 0.7,
|
||||
headHeightMax: 0.8,
|
||||
eyeLineFromBottom: 0.63,
|
||||
bgColor: "#FFFFFF",
|
||||
bgColors: ["#FFFFFF"],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
code: "TR",
|
||||
name: "Turkey",
|
||||
flag: "🇹🇷",
|
||||
region: "europe",
|
||||
documents: [
|
||||
{
|
||||
type: "passport",
|
||||
label: "Turkish Passport",
|
||||
width: 50,
|
||||
height: 60,
|
||||
dpi: 300,
|
||||
headHeightMin: 0.7,
|
||||
headHeightMax: 0.8,
|
||||
eyeLineFromBottom: 0.63,
|
||||
bgColor: "#FFFFFF",
|
||||
bgColors: ["#FFFFFF"],
|
||||
},
|
||||
],
|
||||
},
|
||||
// Asia
|
||||
{
|
||||
code: "IN",
|
||||
name: "India",
|
||||
flag: "🇮🇳",
|
||||
region: "asia",
|
||||
documents: [
|
||||
{
|
||||
type: "passport",
|
||||
label: "Indian Passport",
|
||||
width: 51,
|
||||
height: 51,
|
||||
dpi: 300,
|
||||
headHeightMin: 0.5,
|
||||
headHeightMax: 0.69,
|
||||
eyeLineFromBottom: 0.56,
|
||||
bgColor: "#FFFFFF",
|
||||
bgColors: ["#FFFFFF"],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
code: "CN",
|
||||
name: "China",
|
||||
flag: "🇨🇳",
|
||||
region: "asia",
|
||||
documents: [
|
||||
{
|
||||
type: "passport",
|
||||
label: "Chinese Passport",
|
||||
width: 33,
|
||||
height: 48,
|
||||
dpi: 300,
|
||||
headHeightMin: 0.7,
|
||||
headHeightMax: 0.8,
|
||||
eyeLineFromBottom: 0.63,
|
||||
bgColor: "#FFFFFF",
|
||||
bgColors: ["#FFFFFF"],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
code: "JP",
|
||||
name: "Japan",
|
||||
flag: "🇯🇵",
|
||||
region: "asia",
|
||||
documents: [
|
||||
{
|
||||
type: "passport",
|
||||
label: "Japanese Passport",
|
||||
width: 35,
|
||||
height: 45,
|
||||
dpi: 300,
|
||||
headHeightMin: 0.7,
|
||||
headHeightMax: 0.8,
|
||||
eyeLineFromBottom: 0.63,
|
||||
bgColor: "#FFFFFF",
|
||||
bgColors: ["#FFFFFF"],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
code: "KR",
|
||||
name: "South Korea",
|
||||
flag: "🇰🇷",
|
||||
region: "asia",
|
||||
documents: [
|
||||
{
|
||||
type: "passport",
|
||||
label: "Korean Passport",
|
||||
width: 35,
|
||||
height: 45,
|
||||
dpi: 300,
|
||||
headHeightMin: 0.7,
|
||||
headHeightMax: 0.8,
|
||||
eyeLineFromBottom: 0.63,
|
||||
bgColor: "#FFFFFF",
|
||||
bgColors: ["#FFFFFF"],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
code: "TH",
|
||||
name: "Thailand",
|
||||
flag: "🇹🇭",
|
||||
region: "asia",
|
||||
documents: [
|
||||
{
|
||||
type: "passport",
|
||||
label: "Thai Passport",
|
||||
width: 35,
|
||||
height: 45,
|
||||
dpi: 300,
|
||||
headHeightMin: 0.7,
|
||||
headHeightMax: 0.8,
|
||||
eyeLineFromBottom: 0.63,
|
||||
bgColor: "#FFFFFF",
|
||||
bgColors: ["#FFFFFF"],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
code: "PH",
|
||||
name: "Philippines",
|
||||
flag: "🇵🇭",
|
||||
region: "asia",
|
||||
documents: [
|
||||
{
|
||||
type: "passport",
|
||||
label: "Philippine Passport",
|
||||
width: 35,
|
||||
height: 45,
|
||||
dpi: 300,
|
||||
headHeightMin: 0.7,
|
||||
headHeightMax: 0.8,
|
||||
eyeLineFromBottom: 0.63,
|
||||
bgColor: "#FFFFFF",
|
||||
bgColors: ["#FFFFFF"],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
code: "ID",
|
||||
name: "Indonesia",
|
||||
flag: "🇮🇩",
|
||||
region: "asia",
|
||||
documents: [
|
||||
{
|
||||
type: "passport",
|
||||
label: "Indonesian Passport",
|
||||
width: 30,
|
||||
height: 40,
|
||||
dpi: 300,
|
||||
headHeightMin: 0.7,
|
||||
headHeightMax: 0.8,
|
||||
eyeLineFromBottom: 0.63,
|
||||
bgColor: "#EF4444",
|
||||
bgColors: ["#EF4444"],
|
||||
},
|
||||
],
|
||||
},
|
||||
// Oceania
|
||||
{
|
||||
code: "AU",
|
||||
name: "Australia",
|
||||
flag: "🇦🇺",
|
||||
region: "oceania",
|
||||
documents: [
|
||||
{
|
||||
type: "passport",
|
||||
label: "Australian Passport",
|
||||
width: 35,
|
||||
height: 45,
|
||||
dpi: 300,
|
||||
headHeightMin: 0.7,
|
||||
headHeightMax: 0.8,
|
||||
eyeLineFromBottom: 0.63,
|
||||
bgColor: "#FFFFFF",
|
||||
bgColors: ["#FFFFFF"],
|
||||
},
|
||||
],
|
||||
},
|
||||
// Middle East
|
||||
{
|
||||
code: "SA",
|
||||
name: "Saudi Arabia",
|
||||
flag: "🇸🇦",
|
||||
region: "middle-east",
|
||||
documents: [
|
||||
{
|
||||
type: "passport",
|
||||
label: "Saudi Passport",
|
||||
width: 40,
|
||||
height: 60,
|
||||
dpi: 300,
|
||||
headHeightMin: 0.7,
|
||||
headHeightMax: 0.8,
|
||||
eyeLineFromBottom: 0.63,
|
||||
bgColor: "#FFFFFF",
|
||||
bgColors: ["#FFFFFF"],
|
||||
},
|
||||
],
|
||||
},
|
||||
// Africa
|
||||
{
|
||||
code: "NG",
|
||||
name: "Nigeria",
|
||||
flag: "🇳🇬",
|
||||
region: "africa",
|
||||
documents: [
|
||||
{
|
||||
type: "passport",
|
||||
label: "Nigerian Passport",
|
||||
width: 35,
|
||||
height: 45,
|
||||
dpi: 300,
|
||||
headHeightMin: 0.7,
|
||||
headHeightMax: 0.8,
|
||||
eyeLineFromBottom: 0.63,
|
||||
bgColor: "#FFFFFF",
|
||||
bgColors: ["#FFFFFF"],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export interface PrintLayout {
|
||||
id: string;
|
||||
label: string;
|
||||
width: number; // mm
|
||||
height: number; // mm
|
||||
}
|
||||
|
||||
export const PRINT_LAYOUTS: PrintLayout[] = [
|
||||
{ id: "4x6", label: "4x6 inch", width: 102, height: 152 },
|
||||
{ id: "a4", label: "A4", width: 210, height: 297 },
|
||||
{ id: "none", label: "None", width: 0, height: 0 },
|
||||
];
|
||||
|
||||
export const APP_VERSION = "1.14.0";
|
||||
|
||||
/**
|
||||
@@ -441,4 +898,5 @@ export const PYTHON_SIDECAR_TOOLS = [
|
||||
"noise-removal",
|
||||
"red-eye-removal",
|
||||
"restore-photo",
|
||||
"passport-photo",
|
||||
] as const;
|
||||
|
||||
@@ -104,6 +104,11 @@ export const en = {
|
||||
name: "AI Colorization",
|
||||
description: "Convert black & white photos to full color using AI deep learning models",
|
||||
},
|
||||
"passport-photo": {
|
||||
name: "Passport Photo",
|
||||
description:
|
||||
"Create government-compliant passport, visa, and ID photos with auto face detection",
|
||||
},
|
||||
"watermark-text": { name: "Text Watermark", description: "Add text watermark overlay" },
|
||||
"watermark-image": { name: "Image Watermark", description: "Overlay a logo as watermark" },
|
||||
"text-overlay": { name: "Text Overlay", description: "Add styled text to images" },
|
||||
|
||||
Reference in New Issue
Block a user