mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
fix(passport-photo): make preview WYSIWYG - remove zoom, what you see is what you download
- Removed zoom controls entirely from preview pane - Canvas always shows exact passport photo output at 1:1 - Drag to adjust position still works for fine-tuning - Overlay lines use crop coordinates directly instead of zoom-adjusted ones - Added "what you see is what you download" label with output dimensions
This commit is contained in:
@@ -936,7 +936,7 @@ export function PassportPhotoPreview() {
|
|||||||
canvasDisplayHeight = Math.round(canvasDisplayWidth / aspectRatio);
|
canvasDisplayHeight = Math.round(canvasDisplayWidth / aspectRatio);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Canvas stays the same size; zoom crops into a sub-region of the passport photo
|
// Canvas always shows exactly what will be downloaded - no zoom distortion
|
||||||
canvas.width = canvasDisplayWidth;
|
canvas.width = canvasDisplayWidth;
|
||||||
canvas.height = canvasDisplayHeight;
|
canvas.height = canvasDisplayHeight;
|
||||||
|
|
||||||
@@ -951,18 +951,12 @@ export function PassportPhotoPreview() {
|
|||||||
const scaleX = img.naturalWidth / imageWidth;
|
const scaleX = img.naturalWidth / imageWidth;
|
||||||
const scaleY = img.naturalHeight / imageHeight;
|
const scaleY = img.naturalHeight / imageHeight;
|
||||||
|
|
||||||
// When zoom > 1, show a sub-region centered on the face
|
const srcX = crop.leftX * scaleX;
|
||||||
const zoomedW = crop.photoWidthPx / zoom;
|
const srcY = crop.topY * scaleY;
|
||||||
const zoomedH = crop.photoHeightPx / zoom;
|
const srcW = crop.photoWidthPx * scaleX;
|
||||||
const zoomedLeft = crop.leftX + (crop.photoWidthPx - zoomedW) / 2;
|
const srcH = crop.photoHeightPx * scaleY;
|
||||||
const zoomedTop = crop.topY + (crop.photoHeightPx - zoomedH) / 2;
|
|
||||||
|
|
||||||
const srcX = zoomedLeft * scaleX;
|
// Draw preview image onto full canvas - this is exactly what gets downloaded
|
||||||
const srcY = zoomedTop * scaleY;
|
|
||||||
const srcW = zoomedW * scaleX;
|
|
||||||
const srcH = zoomedH * scaleY;
|
|
||||||
|
|
||||||
// Draw preview image onto full canvas
|
|
||||||
ctx.drawImage(img, srcX, srcY, srcW, srcH, 0, 0, canvasDisplayWidth, canvasDisplayHeight);
|
ctx.drawImage(img, srcX, srcY, srcW, srcH, 0, 0, canvasDisplayWidth, canvasDisplayHeight);
|
||||||
|
|
||||||
// Compliance overlay
|
// Compliance overlay
|
||||||
@@ -973,9 +967,11 @@ export function PassportPhotoPreview() {
|
|||||||
ctx.setLineDash([6, 4]);
|
ctx.setLineDash([6, 4]);
|
||||||
ctx.lineWidth = 1.5;
|
ctx.lineWidth = 1.5;
|
||||||
|
|
||||||
// Helper: convert original-image coords to canvas coords (zoom-aware)
|
// Helper: convert original-image coords to canvas coords
|
||||||
const toCanvasY = (origY: number) => ((origY - zoomedTop) / zoomedH) * canvasDisplayHeight;
|
const toCanvasY = (origY: number) =>
|
||||||
const toCanvasX = (origX: number) => ((origX - zoomedLeft) / zoomedW) * canvasDisplayWidth;
|
((origY - crop.topY) / crop.photoHeightPx) * canvasDisplayHeight;
|
||||||
|
const toCanvasX = (origX: number) =>
|
||||||
|
((origX - crop.leftX) / crop.photoWidthPx) * canvasDisplayWidth;
|
||||||
|
|
||||||
// Center line (vertical) - face centered check
|
// Center line (vertical) - face centered check
|
||||||
const centerXCanvas = toCanvasX((landmarks.faceCenterX + adjustX) * imageWidth);
|
const centerXCanvas = toCanvasX((landmarks.faceCenterX + adjustX) * imageWidth);
|
||||||
@@ -1059,15 +1055,6 @@ export function PassportPhotoPreview() {
|
|||||||
};
|
};
|
||||||
}, [dragging, setAdjustX, setAdjustY]);
|
}, [dragging, setAdjustX, setAdjustY]);
|
||||||
|
|
||||||
// Wheel zoom
|
|
||||||
const handleWheel = useCallback(
|
|
||||||
(e: React.WheelEvent<HTMLCanvasElement>) => {
|
|
||||||
e.preventDefault();
|
|
||||||
setZoom(Math.max(1, Math.min(3, zoom + (e.deltaY > 0 ? -0.1 : 0.1))));
|
|
||||||
},
|
|
||||||
[zoom, setZoom],
|
|
||||||
);
|
|
||||||
|
|
||||||
// No file / no analysis state
|
// No file / no analysis state
|
||||||
if (!analyzeResult && !analyzing) {
|
if (!analyzeResult && !analyzing) {
|
||||||
return (
|
return (
|
||||||
@@ -1102,59 +1089,22 @@ export function PassportPhotoPreview() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={containerRef} className="flex flex-col items-center h-full w-full p-4 gap-3">
|
<div ref={containerRef} className="flex flex-col items-center h-full w-full p-4 gap-3">
|
||||||
{/* Zoom controls */}
|
{/* Output dimensions */}
|
||||||
<div className="flex items-center gap-2 shrink-0">
|
<div className="text-xs text-muted-foreground shrink-0">
|
||||||
<button
|
Output: {pxDims.w}x{pxDims.h}px - what you see is what you download
|
||||||
type="button"
|
|
||||||
onClick={() => setZoom(Math.max(1, zoom - 0.25))}
|
|
||||||
className="p-1.5 rounded-lg border border-border text-muted-foreground hover:text-foreground hover:bg-muted transition-colors"
|
|
||||||
title="Zoom out"
|
|
||||||
>
|
|
||||||
<ZoomOut className="h-4 w-4" />
|
|
||||||
</button>
|
|
||||||
<span className="text-xs text-muted-foreground w-12 text-center tabular-nums">
|
|
||||||
{Math.round(zoom * 100)}%
|
|
||||||
</span>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setZoom(Math.min(3, zoom + 0.25))}
|
|
||||||
className="p-1.5 rounded-lg border border-border text-muted-foreground hover:text-foreground hover:bg-muted transition-colors"
|
|
||||||
title="Zoom in"
|
|
||||||
>
|
|
||||||
<ZoomIn className="h-4 w-4" />
|
|
||||||
</button>
|
|
||||||
{zoom !== 1 && (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setZoom(1)}
|
|
||||||
className="p-1.5 rounded-lg border border-border text-muted-foreground hover:text-foreground hover:bg-muted transition-colors"
|
|
||||||
title="Reset zoom"
|
|
||||||
>
|
|
||||||
<RotateCcw className="h-3.5 w-3.5" />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
<span className="text-[10px] text-muted-foreground ml-2">
|
|
||||||
{pxDims.w}x{pxDims.h}px
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Canvas */}
|
{/* Canvas - WYSIWYG preview */}
|
||||||
<div className="flex-1 flex items-center justify-center overflow-auto min-h-0 w-full">
|
<div className="flex-1 flex items-center justify-center overflow-auto min-h-0 w-full">
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<canvas
|
<canvas
|
||||||
ref={canvasRef}
|
ref={canvasRef}
|
||||||
className={`rounded-lg border border-border shadow-md ${dragging ? "cursor-grabbing" : "cursor-grab"}`}
|
className={`rounded-lg border border-border shadow-md ${dragging ? "cursor-grabbing" : "cursor-grab"}`}
|
||||||
onMouseDown={handleMouseDown}
|
onMouseDown={handleMouseDown}
|
||||||
onWheel={handleWheel}
|
|
||||||
/>
|
/>
|
||||||
<div className="absolute bottom-2 left-2 right-2 flex items-center justify-between">
|
<div className="absolute bottom-2 left-2 flex items-center gap-1 bg-black/50 text-white text-[10px] px-2 py-1 rounded-md">
|
||||||
<div className="flex items-center gap-1 bg-black/50 text-white text-[10px] px-2 py-1 rounded-md">
|
<Move className="h-3 w-3" />
|
||||||
<Move className="h-3 w-3" />
|
Drag to adjust position
|
||||||
Drag to adjust
|
|
||||||
</div>
|
|
||||||
<div className="bg-black/50 text-white text-[10px] px-2 py-1 rounded-md">
|
|
||||||
Scroll to zoom
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user