fix(studio): a dust spot is a place on the photo, so a quarter turn carries it with the frame

This commit is contained in:
2026-10-13 06:38:43 +07:00
parent 05a527557e
commit 1d7510366a
3 changed files with 278 additions and 2 deletions
+24 -2
View File
@@ -63,6 +63,7 @@ import {
import { grainPerInch, grainStockFor } from '../shared/utils/grainShader';
import { curveIsActive } from '../shared/utils/toneCurve';
import { HEAL_DEFAULT_R } from '../shared/utils/heal';
import { layMarks, type Orientation } from '../shared/utils/orientation';
import { MOSAIC_DEFAULT_R } from '../shared/utils/mosaic';
import { MASK_DEFAULT_FEATHER, MASK_EXPOSURE_MAX } from '../shared/utils/gradientMask';
import { readHistogram, autoExposureStops, autoTone, autoWhiteBalance } from './ui/Histogram';
@@ -1498,6 +1499,22 @@ export function Workspace() {
[remember, setAdjustment]
);
// The ROTATE strip and the two mirrors are the only things that move the photo
// under the marks already on it, and a mark is a place on the photo, not a
// place on the screen: turn the frame and every spot the dust brush laid down
// has to turn with it, or the repair slides off its mote and the mote is
// uncovered again (shared/utils/orientation.ts). One undo step, like the turn
// itself — UNDO hands back the frame AND the marks of the frame before it.
const carryMarks = useCallback(
(next: Orientation) => {
setRecipe((prev) => ({
...prev,
adjustments: layMarks(prev.adjustments, { rot: rotation, flipH, flipV }, next, photoDims),
}));
},
[rotation, flipH, flipV, photoDims]
);
// FX's HEAL brush writes the spots the same way a knob writes its value: one
// gesture is one undo step whatever it laid down, so UNDO takes the dust back
// out a stroke at a time, and CLEAR — one step — hands the whole photo back.
@@ -2851,9 +2868,12 @@ export function Workspace() {
],
onPick: (v) => {
remember();
setRotation(v === 'reset' ? 0 : (Number(v) as 0 | 90 | 180 | 270));
const next = v === 'reset' ? 0 : (Number(v) as 0 | 90 | 180 | 270);
// RESET levels the whole frame, angle and mirrors included.
if (v === 'reset') {
const level = v === 'reset';
carryMarks({ rot: next, flipH: level ? false : flipH, flipV: level ? false : flipV });
setRotation(next);
if (level) {
setStraighten(0);
setFlipH(false);
setFlipV(false);
@@ -3759,6 +3779,7 @@ export function Workspace() {
active: flipH,
onClick: () => {
remember();
carryMarks({ rot: rotation, flipH: !flipH, flipV });
setFlipH((v) => !v);
},
},
@@ -3768,6 +3789,7 @@ export function Workspace() {
active: flipV,
onClick: () => {
remember();
carryMarks({ rot: rotation, flipH, flipV: !flipV });
setFlipV((v) => !v);
},
},