mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
refactor(passport-photo): rebuild UI for online applications with file size control
This commit is contained in:
@@ -33,6 +33,7 @@ const generateSettingsSchema = z.object({
|
|||||||
documentType: z.string().default("passport"),
|
documentType: z.string().default("passport"),
|
||||||
bgColor: z.string().default("#FFFFFF"),
|
bgColor: z.string().default("#FFFFFF"),
|
||||||
printLayout: z.string().default("none"),
|
printLayout: z.string().default("none"),
|
||||||
|
maxFileSizeKb: z.number().default(0),
|
||||||
adjustX: z.number().default(0),
|
adjustX: z.number().default(0),
|
||||||
adjustY: z.number().default(0),
|
adjustY: z.number().default(0),
|
||||||
landmarks: landmarksSchema,
|
landmarks: landmarksSchema,
|
||||||
@@ -287,6 +288,7 @@ export function registerPassportPhoto(app: FastifyInstance) {
|
|||||||
documentType,
|
documentType,
|
||||||
bgColor,
|
bgColor,
|
||||||
printLayout,
|
printLayout,
|
||||||
|
maxFileSizeKb,
|
||||||
adjustX,
|
adjustX,
|
||||||
adjustY,
|
adjustY,
|
||||||
landmarks: rawLandmarks,
|
landmarks: rawLandmarks,
|
||||||
@@ -369,7 +371,7 @@ export function registerPassportPhoto(app: FastifyInstance) {
|
|||||||
const targetHeightPx = Math.round((docSpec.height / MM_PER_INCH) * docSpec.dpi);
|
const targetHeightPx = Math.round((docSpec.height / MM_PER_INCH) * docSpec.dpi);
|
||||||
|
|
||||||
// Extract crop region and resize to target dimensions
|
// Extract crop region and resize to target dimensions
|
||||||
const cropped = await sharp(bgLayer)
|
let cropped = await sharp(bgLayer)
|
||||||
.extract({
|
.extract({
|
||||||
left: cropLeft,
|
left: cropLeft,
|
||||||
top: cropTop,
|
top: cropTop,
|
||||||
@@ -380,6 +382,25 @@ export function registerPassportPhoto(app: FastifyInstance) {
|
|||||||
.jpeg({ quality: 95 })
|
.jpeg({ quality: 95 })
|
||||||
.toBuffer();
|
.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
|
// Save output
|
||||||
const outputFilename = `${filename.replace(/\.[^.]+$/, "")}_passport.jpg`;
|
const outputFilename = `${filename.replace(/\.[^.]+$/, "")}_passport.jpg`;
|
||||||
const outputPath = join(workspacePath, "output", outputFilename);
|
const outputPath = join(workspacePath, "output", outputFilename);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -274,6 +274,11 @@ const PassportPhotoSettings = lazy(() =>
|
|||||||
default: m.PassportPhotoSettings,
|
default: m.PassportPhotoSettings,
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
|
const PassportPhotoPreview = lazy(() =>
|
||||||
|
import("@/components/tools/passport-photo-settings").then((m) => ({
|
||||||
|
default: m.PassportPhotoPreview,
|
||||||
|
})),
|
||||||
|
);
|
||||||
const RedEyeRemovalSettings = lazy(() =>
|
const RedEyeRemovalSettings = lazy(() =>
|
||||||
import("@/components/tools/red-eye-removal-settings").then((m) => ({
|
import("@/components/tools/red-eye-removal-settings").then((m) => ({
|
||||||
default: m.RedEyeRemovalSettings,
|
default: m.RedEyeRemovalSettings,
|
||||||
@@ -423,7 +428,14 @@ export const toolRegistry = new Map<string, ToolRegistryEntry>([
|
|||||||
],
|
],
|
||||||
["colorize", { displayMode: "before-after", Settings: ColorizeSettings }],
|
["colorize", { displayMode: "before-after", Settings: ColorizeSettings }],
|
||||||
["noise-removal", { displayMode: "before-after", Settings: NoiseRemovalSettings }],
|
["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 }],
|
["red-eye-removal", { displayMode: "before-after", Settings: RedEyeRemovalSettings }],
|
||||||
["restore-photo", { displayMode: "before-after", Settings: RestorePhotoSettings }],
|
["restore-photo", { displayMode: "before-after", Settings: RestorePhotoSettings }],
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -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
|
// Asia
|
||||||
{
|
{
|
||||||
code: "IN",
|
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
|
// Oceania
|
||||||
{
|
{
|
||||||
code: "AU",
|
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
|
// Middle East
|
||||||
{
|
{
|
||||||
code: "SA",
|
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
|
// Africa
|
||||||
{
|
{
|
||||||
code: "NG",
|
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 {
|
export interface PrintLayout {
|
||||||
|
|||||||
Reference in New Issue
Block a user