Hold the repairs a photographer makes, and say which may share a pass
A spot is a disc, a source offset and four numbers, and it lives beside `ops` for the reason `masks` and `film` do: the operation trait is ParamId -> f32, and a list of repairs is neither scalar nor fixed. Two decisions here are not obvious. The id is derived from the position rather than counted, because two devices editing offline would each mint `spot3` for different marks and the sidecar merge would then treat two repairs as one — from the position, two devices that removed the same piece of dust agree, and two that removed different ones do not. And every length is in the frame's isotropic units, not a mixture of those and shorter-edge fractions: one unit for the radius, the feather and the offset agrees on a landscape frame and on a portrait one, where a mixture only agrees on the first. `rounds` is the arithmetic that keeps a source from reading a destination. Every spot in one pass reads the photograph as it stood before that pass, so a spot sourcing from an earlier spot's destination would copy the mark that spot was removing. Grouping is not a pass per spot — that is sixty-four dispatches for a case that almost never arises — it is a new round only when the sources actually collide. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+17
-6
@@ -73,7 +73,7 @@ pub struct Spot {
|
||||
/// What covers it, as an offset from `centre` in **frame units**
|
||||
/// (y spans 0..1, x spans 0..aspect — the mask convention).
|
||||
pub offset: (f32, f32),
|
||||
/// Fraction of the source frame's shorter edge.
|
||||
/// The radius of the disc, in frame units.
|
||||
pub radius: f32,
|
||||
/// Fraction of `radius` over which the edge falls away. 0 is hard.
|
||||
pub feather: f32,
|
||||
@@ -88,10 +88,20 @@ pub struct Spot {
|
||||
is a length that means something different in the preview and in the export
|
||||
([`RenderScale`]'s whole documentation is this argument). `centre` is normalised
|
||||
source, so a crop, a zoom, a pan and a rotation move the spot with the
|
||||
photograph and no arithmetic is needed to keep it there. `radius` is a fraction
|
||||
of the **source** frame's shorter edge rather than of the rendered region, so
|
||||
cropping does not resize a spot that was already placed — a dust mark is a fact
|
||||
about the sensor, not about the composition.
|
||||
photograph and no arithmetic is needed to keep it there.
|
||||
|
||||
Every *length* — radius, feather, offset — is in the frame's **isotropic
|
||||
units**, `MaskSource::Radial`'s convention, where y spans `0..1` and x spans
|
||||
`0..aspect`. Only in those units is a disc a disc: normalised coordinates would
|
||||
make a spot on a 3:2 frame an ellipse half again wider than it is tall. They are
|
||||
lengths against the *source* frame rather than the rendered region, so cropping
|
||||
does not resize a spot already placed — a dust mark is a fact about the sensor,
|
||||
not about the composition.
|
||||
|
||||
One unit for all three, deliberately. A radius in shorter-edge fractions beside
|
||||
an offset in frame units agrees on a landscape frame and silently disagrees on a
|
||||
portrait one, which is a bug that stays invisible until somebody rotates a
|
||||
photograph.
|
||||
|
||||
**`offset` is a vector, not a second point.** Dragging the destination moves the
|
||||
source with it, which is what a photographer expects when they nudge a spot half
|
||||
@@ -130,7 +140,8 @@ Bounds, following `mask.rs`'s example of bounding what a sidecar can grow to:
|
||||
|---|---|---|
|
||||
| `MAX_SPOTS` | 64 | Beyond a few dozen the answer is to clean the sensor. Refuses rather than dropping, as `MaskStack::push` does. |
|
||||
| `MAX_SOURCE_DISTANCE` | 0.5 | Frame units. Bounds the halo in §5.3, which is otherwise unbounded. |
|
||||
| `DEFAULT_RADIUS` | 0.012 | About 25 px on a 24 MP frame's short edge — a dust mark. |
|
||||
| `DEFAULT_RADIUS` | 0.012 | Frame units — about 25 px on a 24 MP frame's short edge, which is a dust mark. |
|
||||
| `MIN_RADIUS` / `MAX_RADIUS` | 0.001 / 0.5 | Not zero, because a spot that repairs nothing reads as a broken tool; not larger, because the halo bound has to mean something. |
|
||||
| `DEFAULT_FEATHER` | 0.35 | Fraction of the radius. Soft enough that a heal on a gradient sky has no visible boundary. |
|
||||
|
||||
## 5. Where it runs
|
||||
|
||||
Reference in New Issue
Block a user