diff --git a/docker/frontend/src/App.tsx b/docker/frontend/src/App.tsx index 946c0f5..3166d60 100644 --- a/docker/frontend/src/App.tsx +++ b/docker/frontend/src/App.tsx @@ -2472,7 +2472,8 @@ export function Workspace() { cropWH={cropWH} onCropRectChange={setCropRect} onStageZoom={onStageZoom} - canCompare={crop === 'none'} + canCompare={!!source} + compareCrop={cropLive ? cropRect : null} onSaveOriginal={() => void saveOriginal()} onClear={() => setClearing(true)} picking={picking} diff --git a/docker/frontend/src/styles/app.css b/docker/frontend/src/styles/app.css index 1661be2..cc1559b 100644 --- a/docker/frontend/src/styles/app.css +++ b/docker/frontend/src/styles/app.css @@ -284,6 +284,9 @@ input[type="range"] { width: 100%; accent-color: var(--accent); } cursor: ew-resize; touch-action: none; } +/* The window the original is looked at through: the render's own shape, so a + crop can slide and scale the photo behind it and still line up. */ +.compare-window { position: absolute; inset: 0; overflow: hidden; } .compare-orig { position: absolute; inset: 0; width: 100%; height: 100%; box-shadow: none; } .compare-bar { position: absolute; diff --git a/docker/frontend/src/ui/ImageStage.tsx b/docker/frontend/src/ui/ImageStage.tsx index 1cc0f3b..0b97535 100644 --- a/docker/frontend/src/ui/ImageStage.tsx +++ b/docker/frontend/src/ui/ImageStage.tsx @@ -65,6 +65,9 @@ export function ImageStage({ onCropRectChange, onStageZoom, canCompare = false, + // The crop the render is already carrying: the original is shown through the + // same window, so the two halves line up whatever the shape. + compareCrop = null, onSaveOriginal, // Takes the photo off the stage. What is on it goes first — the confirmation // offers to file the frame or export it, so nothing is lost by accident. @@ -93,9 +96,11 @@ export function ImageStage({ // at — the app re-cuts its preview copy to match. Scaled to contain the // photo and multiplied by the zoom and the screen's pixel ratio. onStageZoom?: (longDevicePx: number) => void; - // Compare needs the un-cropped photo on one side and the render on the other, - // so it is only offered while the crop is untouched. + // Compare puts the photo that was loaded beside the render, and the crop is + // part of that render: when one is applied the original is shown through the + // same window, so the split stays a straight line-up. canCompare?: boolean; + compareCrop?: CropRect | null; // Puts the untouched photo file itself in the owner's hands — on a phone that // is the share sheet, whose "Save Image" is what reaches the photo library. onSaveOriginal?: () => void; @@ -757,7 +762,7 @@ export function ImageStage({ {straightenRuler} ) : null} - {compare && canCompare && originalUrl && box ? ( + {compare && canCompare && !cropEditing && originalUrl && box ? (