web: compare the same frame rendered twice, whatever its geometry
The split used to paint the photo file beside the render, so it only lined up while nothing had moved: a turn, a straighten, a printed frame and the halves were two different pictures. The app now renders the same frame twice — once through the look, once through the neutral stock — and the left of the bar is that second copy. Rotation, straighten, crop and frame land on both halves by construction, so the CSS that tried to map the crop onto the file goes away. The toggle lives in the app now, which is what knows how to ask for the extra render; it is only asked for while the split is up. The layer waits for that copy rather than flashing the raw file, whose geometry is already wrong.
This commit is contained in:
@@ -65,9 +65,10 @@ 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,
|
||||
// Compare is the app's: turning it on is what asks for the neutral render the
|
||||
// left of the split shows, so the flag lives up there with the renderer.
|
||||
compare = false,
|
||||
onCompareChange,
|
||||
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.
|
||||
@@ -96,11 +97,12 @@ 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 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.
|
||||
// Compare puts the photo the way it arrived beside the render — the app
|
||||
// renders both from the same geometry, so the split is a straight line-up
|
||||
// whatever the rotation, the straighten, the crop or the frame.
|
||||
canCompare?: boolean;
|
||||
compareCrop?: CropRect | null;
|
||||
compare?: boolean;
|
||||
onCompareChange?: (on: boolean) => void;
|
||||
// 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;
|
||||
@@ -144,7 +146,6 @@ export function ImageStage({
|
||||
const panRef = useRef<{ px: number; py: number } | null>(null);
|
||||
// Compare: the original on the left of the split, the render on the right.
|
||||
// 0.5 is the middle of the photo, which is where the bar starts.
|
||||
const [compare, setCompare] = useState(false);
|
||||
const [split, setSplit] = useState(0.5);
|
||||
const draggingSplit = useRef(false);
|
||||
// The histogram overlay is shown by default and dismissed from its own frame
|
||||
@@ -205,15 +206,6 @@ export function ImageStage({
|
||||
return () => window.removeEventListener('resize', measure);
|
||||
}, [cropEditing, compare, picking, pickPanelAt, wmCount, showStraighten, measure, previewUrl]);
|
||||
|
||||
// Compare is a property of the un-cropped photo, so a crop taking over (or a
|
||||
// new photo arriving) puts it away.
|
||||
useEffect(() => {
|
||||
if (!canCompare) setCompare(false);
|
||||
}, [canCompare]);
|
||||
useEffect(() => {
|
||||
setCompare(false);
|
||||
}, [originalUrl]);
|
||||
|
||||
// Eyedropper. The decode is done on the click, not when PICK is armed: a
|
||||
// bitmap made up front would be stale after the first band move, and re-making
|
||||
// it on every render would decode a fresh JPEG per slider step. One bitmap is
|
||||
@@ -780,39 +772,17 @@ export function ImageStage({
|
||||
onPointerCancel={endSplit}
|
||||
>
|
||||
{/* The original is clipped to everything left of the bar; the
|
||||
graded render stays the un-clipped base underneath it. The
|
||||
window is the render's own shape, so when a crop is applied the
|
||||
original is scaled and shifted until that same rectangle fills
|
||||
it — otherwise the split would compare a cropped render with a
|
||||
whole photo. */}
|
||||
<div
|
||||
className="compare-window"
|
||||
graded render stays the un-clipped base underneath it. Both
|
||||
copies come out of the same pass through the engine, so the
|
||||
clip is the whole geometry — the two line up on their own. */}
|
||||
<img
|
||||
className="compare-orig"
|
||||
data-key="compare-orig"
|
||||
src={originalUrl}
|
||||
alt=""
|
||||
draggable={false}
|
||||
style={{ clipPath: `inset(0 ${(1 - split) * 100}% 0 0)` }}
|
||||
>
|
||||
<img
|
||||
className="compare-orig"
|
||||
data-key="compare-orig"
|
||||
src={originalUrl}
|
||||
alt=""
|
||||
draggable={false}
|
||||
style={
|
||||
compareCrop
|
||||
? {
|
||||
left: `${(-compareCrop.x / compareCrop.w) * 100}%`,
|
||||
top: `${(-compareCrop.y / compareCrop.h) * 100}%`,
|
||||
width: `${100 / compareCrop.w}%`,
|
||||
height: `${100 / compareCrop.h}%`,
|
||||
right: 'auto',
|
||||
bottom: 'auto',
|
||||
// .canvas-wrap img clamps to the box; the original has
|
||||
// to be free to overflow it so the crop can slide.
|
||||
maxWidth: 'none',
|
||||
maxHeight: 'none',
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
/>
|
||||
<span
|
||||
className="compare-bar"
|
||||
data-key="compare-bar"
|
||||
@@ -891,7 +861,7 @@ export function ImageStage({
|
||||
disabled={!canCompare}
|
||||
onClick={() => {
|
||||
setSplit(0.5);
|
||||
setCompare((v) => !v);
|
||||
onCompareChange?.(!compare);
|
||||
}}
|
||||
>
|
||||
{t('drop.compare')}
|
||||
|
||||
Reference in New Issue
Block a user