refactor(passport-photo): rebuild UI for online applications with file size control

This commit is contained in:
stirling-image
2026-04-14 12:55:37 +08:00
parent 3b8cec36e4
commit 831ef3f2de
4 changed files with 925 additions and 390 deletions
+22 -1
View File
@@ -33,6 +33,7 @@ const generateSettingsSchema = z.object({
documentType: z.string().default("passport"),
bgColor: z.string().default("#FFFFFF"),
printLayout: z.string().default("none"),
maxFileSizeKb: z.number().default(0),
adjustX: z.number().default(0),
adjustY: z.number().default(0),
landmarks: landmarksSchema,
@@ -287,6 +288,7 @@ export function registerPassportPhoto(app: FastifyInstance) {
documentType,
bgColor,
printLayout,
maxFileSizeKb,
adjustX,
adjustY,
landmarks: rawLandmarks,
@@ -369,7 +371,7 @@ export function registerPassportPhoto(app: FastifyInstance) {
const targetHeightPx = Math.round((docSpec.height / MM_PER_INCH) * docSpec.dpi);
// Extract crop region and resize to target dimensions
const cropped = await sharp(bgLayer)
let cropped = await sharp(bgLayer)
.extract({
left: cropLeft,
top: cropTop,
@@ -380,6 +382,25 @@ export function registerPassportPhoto(app: FastifyInstance) {
.jpeg({ quality: 95 })
.toBuffer();
// Compress to fit within max file size if specified
if (maxFileSizeKb > 0) {
const targetBytes = maxFileSizeKb * 1024;
let quality = 90;
while (cropped.length > targetBytes && quality > 10) {
quality -= 5;
cropped = await sharp(bgLayer)
.extract({
left: cropLeft,
top: cropTop,
width: cropW,
height: cropH,
})
.resize(targetWidthPx, targetHeightPx, { fit: "fill" })
.jpeg({ quality })
.toBuffer();
}
}
// Save output
const outputFilename = `${filename.replace(/\.[^.]+$/, "")}_passport.jpg`;
const outputPath = join(workspacePath, "output", outputFilename);
File diff suppressed because it is too large Load Diff
+13 -1
View File
@@ -274,6 +274,11 @@ const PassportPhotoSettings = lazy(() =>
default: m.PassportPhotoSettings,
})),
);
const PassportPhotoPreview = lazy(() =>
import("@/components/tools/passport-photo-settings").then((m) => ({
default: m.PassportPhotoPreview,
})),
);
const RedEyeRemovalSettings = lazy(() =>
import("@/components/tools/red-eye-removal-settings").then((m) => ({
default: m.RedEyeRemovalSettings,
@@ -423,7 +428,14 @@ export const toolRegistry = new Map<string, ToolRegistryEntry>([
],
["colorize", { displayMode: "before-after", Settings: ColorizeSettings }],
["noise-removal", { displayMode: "before-after", Settings: NoiseRemovalSettings }],
["passport-photo", { displayMode: "no-comparison", Settings: PassportPhotoSettings }],
[
"passport-photo",
{
displayMode: "custom-results",
Settings: PassportPhotoSettings,
ResultsPanel: PassportPhotoPreview,
},
],
["red-eye-removal", { displayMode: "before-after", Settings: RedEyeRemovalSettings }],
["restore-photo", { displayMode: "before-after", Settings: RestorePhotoSettings }],
]);
+300
View File
@@ -670,6 +670,126 @@ export const PASSPORT_SPECS: PassportSpec[] = [
},
],
},
{
code: "IE",
name: "Ireland",
flag: "\u{1F1EE}\u{1F1EA}",
region: "europe",
documents: [
{
type: "passport",
label: "Irish Passport",
width: 35,
height: 45,
dpi: 300,
headHeightMin: 0.7,
headHeightMax: 0.8,
eyeLineFromBottom: 0.63,
bgColor: "#FFFFFF",
bgColors: ["#FFFFFF"],
},
],
},
{
code: "IT",
name: "Italy",
flag: "\u{1F1EE}\u{1F1F9}",
region: "europe",
documents: [
{
type: "passport",
label: "Italian Passport",
width: 35,
height: 45,
dpi: 300,
headHeightMin: 0.7,
headHeightMax: 0.8,
eyeLineFromBottom: 0.63,
bgColor: "#FFFFFF",
bgColors: ["#FFFFFF"],
},
],
},
{
code: "ES",
name: "Spain",
flag: "\u{1F1EA}\u{1F1F8}",
region: "europe",
documents: [
{
type: "passport",
label: "Spanish Passport",
width: 26,
height: 32,
dpi: 300,
headHeightMin: 0.7,
headHeightMax: 0.8,
eyeLineFromBottom: 0.63,
bgColor: "#FFFFFF",
bgColors: ["#FFFFFF"],
},
],
},
{
code: "NL",
name: "Netherlands",
flag: "\u{1F1F3}\u{1F1F1}",
region: "europe",
documents: [
{
type: "passport",
label: "Dutch Passport",
width: 35,
height: 45,
dpi: 300,
headHeightMin: 0.7,
headHeightMax: 0.8,
eyeLineFromBottom: 0.63,
bgColor: "#FFFFFF",
bgColors: ["#FFFFFF"],
},
],
},
{
code: "PL",
name: "Poland",
flag: "\u{1F1F5}\u{1F1F1}",
region: "europe",
documents: [
{
type: "passport",
label: "Polish Passport",
width: 35,
height: 45,
dpi: 300,
headHeightMin: 0.7,
headHeightMax: 0.8,
eyeLineFromBottom: 0.63,
bgColor: "#FFFFFF",
bgColors: ["#FFFFFF"],
},
],
},
{
code: "SE",
name: "Sweden",
flag: "\u{1F1F8}\u{1F1EA}",
region: "europe",
documents: [
{
type: "passport",
label: "Swedish Passport",
width: 35,
height: 45,
dpi: 300,
headHeightMin: 0.7,
headHeightMax: 0.8,
eyeLineFromBottom: 0.63,
bgColor: "#FFFFFF",
bgColors: ["#FFFFFF"],
},
],
},
// Asia
{
code: "IN",
@@ -811,6 +931,106 @@ export const PASSPORT_SPECS: PassportSpec[] = [
},
],
},
{
code: "SG",
name: "Singapore",
flag: "\u{1F1F8}\u{1F1EC}",
region: "asia",
documents: [
{
type: "passport",
label: "Singapore Passport",
width: 35,
height: 45,
dpi: 300,
headHeightMin: 0.7,
headHeightMax: 0.8,
eyeLineFromBottom: 0.63,
bgColor: "#FFFFFF",
bgColors: ["#FFFFFF"],
},
],
},
{
code: "MY",
name: "Malaysia",
flag: "\u{1F1F2}\u{1F1FE}",
region: "asia",
documents: [
{
type: "passport",
label: "Malaysian Passport",
width: 35,
height: 50,
dpi: 300,
headHeightMin: 0.7,
headHeightMax: 0.8,
eyeLineFromBottom: 0.63,
bgColor: "#FFFFFF",
bgColors: ["#FFFFFF"],
},
],
},
{
code: "VN",
name: "Vietnam",
flag: "\u{1F1FB}\u{1F1F3}",
region: "asia",
documents: [
{
type: "passport",
label: "Vietnamese Passport",
width: 40,
height: 60,
dpi: 300,
headHeightMin: 0.7,
headHeightMax: 0.8,
eyeLineFromBottom: 0.63,
bgColor: "#FFFFFF",
bgColors: ["#FFFFFF"],
},
],
},
{
code: "PK",
name: "Pakistan",
flag: "\u{1F1F5}\u{1F1F0}",
region: "asia",
documents: [
{
type: "passport",
label: "Pakistani Passport",
width: 35,
height: 45,
dpi: 300,
headHeightMin: 0.7,
headHeightMax: 0.8,
eyeLineFromBottom: 0.63,
bgColor: "#FFFFFF",
bgColors: ["#FFFFFF", "#BFDBFE"],
},
],
},
{
code: "BD",
name: "Bangladesh",
flag: "\u{1F1E7}\u{1F1E9}",
region: "asia",
documents: [
{
type: "passport",
label: "Bangladeshi Passport",
width: 35,
height: 45,
dpi: 300,
headHeightMin: 0.7,
headHeightMax: 0.8,
eyeLineFromBottom: 0.63,
bgColor: "#FFFFFF",
bgColors: ["#FFFFFF"],
},
],
},
// Oceania
{
code: "AU",
@@ -832,6 +1052,26 @@ export const PASSPORT_SPECS: PassportSpec[] = [
},
],
},
{
code: "NZ",
name: "New Zealand",
flag: "\u{1F1F3}\u{1F1FF}",
region: "oceania",
documents: [
{
type: "passport",
label: "New Zealand Passport",
width: 35,
height: 45,
dpi: 300,
headHeightMin: 0.7,
headHeightMax: 0.8,
eyeLineFromBottom: 0.63,
bgColor: "#FFFFFF",
bgColors: ["#FFFFFF"],
},
],
},
// Middle East
{
code: "SA",
@@ -853,6 +1093,26 @@ export const PASSPORT_SPECS: PassportSpec[] = [
},
],
},
{
code: "AE",
name: "United Arab Emirates",
flag: "\u{1F1E6}\u{1F1EA}",
region: "middle-east",
documents: [
{
type: "passport",
label: "UAE Passport",
width: 43,
height: 55,
dpi: 300,
headHeightMin: 0.7,
headHeightMax: 0.8,
eyeLineFromBottom: 0.63,
bgColor: "#FFFFFF",
bgColors: ["#FFFFFF"],
},
],
},
// Africa
{
code: "NG",
@@ -874,6 +1134,46 @@ export const PASSPORT_SPECS: PassportSpec[] = [
},
],
},
{
code: "EG",
name: "Egypt",
flag: "\u{1F1EA}\u{1F1EC}",
region: "africa",
documents: [
{
type: "passport",
label: "Egyptian Passport",
width: 40,
height: 60,
dpi: 300,
headHeightMin: 0.7,
headHeightMax: 0.8,
eyeLineFromBottom: 0.63,
bgColor: "#FFFFFF",
bgColors: ["#FFFFFF"],
},
],
},
{
code: "ZA",
name: "South Africa",
flag: "\u{1F1FF}\u{1F1E6}",
region: "africa",
documents: [
{
type: "passport",
label: "South African Passport",
width: 35,
height: 45,
dpi: 300,
headHeightMin: 0.7,
headHeightMax: 0.8,
eyeLineFromBottom: 0.63,
bgColor: "#FFFFFF",
bgColors: ["#FFFFFF"],
},
],
},
];
export interface PrintLayout {