Correct the lens first and settle the grain last

`Attribute::ALL` has claimed since it was written to be roughly the order a
photographer works in, and 474dcf0 moved `Compose` to the front on exactly that
argument. Both ends went on contradicting it.

`Optics` sat fifth, so the column offered the lens corrections after the tones
they change. Removing a vignette brightens the frame; an exposure judged before
that correction has to be judged again after it, which is the definition of the
wrong order. `Detail` sat fourth, so sharpening and noise reduction — the only
work here that depends on everything above it, and the only work that cannot be
judged at fit view at all — were offered before the lens had even been put
right.

So `Optics, Compose, Tone, Colour, Effect, Detail`. `Optics` leads even
`Compose` because it is not a decision about the photograph at all: it is
undoing what the equipment did, a property of the capture rather than a choice.
`Effect` after `Colour` is a look laid over a settled picture, and is the one
slot that is genuinely arguable — a spectral film simulation declares `renders`
and replaces the base curve, which is a case for treating it as foundational
instead, and e235e99 filed film under `Effect` only days ago. The doc comment
records that tension rather than pretending to settle it; an array of six
cannot say "last, except when it is first".

`decl::Attr::ALL` moves with it. It is the second spelling of one vocabulary,
compiled by `build.rs` where `descriptor` is not visible, and the agreement
test in `declared/mod.rs` zips the two positionally — that test is what caught
the last reorder, and it would have caught this one.

Nothing persists a position in this list, which is what makes the reorder safe
rather than merely tidy. `Scope` packs one bit per attribute indexed by
`Attribute::ALL`, but `bits` is private, has no accessor and no `serde`; what
reaches a settings file is `develop.copy_attributes`, a list of names read back
through `Attribute::from_name`. A photographer's copy scope survives untouched
— only the order the names happen to be written in changes.

6a97fdf is why this is worth a commit now rather than a shrug: the
contradiction was harmless while the list only fed a row of chips nobody reads
in order, and stopped being harmless when the same list began driving a column
read top to bottom.

The matrix follows the two files' shifted line numbers.
This commit is contained in:
2026-09-05 21:17:56 +02:00
parent 59917c5183
commit efa9d84aad
3 changed files with 41 additions and 18 deletions
+6 -4
View File
@@ -137,15 +137,17 @@ impl Attr {
}
}
/// Every attribute, in the order `crate::descriptor::Attribute` declares
/// them.
/// Every attribute, in the order `crate::descriptor::Attribute::ALL`
/// lists them — the reasoning for the sequence lives there, and the
/// agreement test in `declared/mod.rs` zips the two, so a reorder there
/// that is not mirrored here is a failure rather than a drift.
pub const ALL: [Attr; 6] = [
Attr::Optics,
Attr::Compose,
Attr::Tone,
Attr::Colour,
Attr::Detail,
Attr::Optics,
Attr::Effect,
Attr::Detail,
];
}
+33 -12
View File
@@ -594,26 +594,47 @@ pub enum Attribute {
}
impl Attribute {
/// Every attribute, in declaration order.
/// Every attribute, in roughly the order a photographer works through
/// them.
///
/// Declaration order is roughly the order a photographer works in, which
/// makes it a reasonable *default* for a frontend that wants one. It is
/// not a screen order: nothing here obliges a frontend to show them all,
/// show them in this sequence, or show them at all.
/// That makes it a reasonable *default* for a frontend that wants one. It
/// is not a screen order: nothing here obliges a frontend to show them
/// all, show them in this sequence, or show them at all.
///
/// `Compose` leads because framing is the first decision made about a
/// photograph and the one every later judgement is made inside — there is
/// `Optics` leads because correcting the lens is not a decision about the
/// photograph — it is undoing what the equipment did, a property of the
/// capture rather than a choice — and it moves the ground every later
/// judgement stands on. Removing a vignette *brightens the frame*, so an
/// exposure set before the correction has to be set again after it.
///
/// `Compose` follows as the first decision actually made about the
/// photograph, and the one every later judgement is made inside: there is
/// no sense in balancing tones across a frame that is about to lose a
/// third of its width. It sat fifth while it was called `Geometry`, which
/// contradicted the claim above and was noticed only when this list
/// started driving a control read top to bottom.
/// third of its width.
///
/// `Detail` trails because sharpening and noise reduction depend on
/// everything above them, and are the only ones here that cannot be judged
/// at fit view at all. Offering them fourth, before the lens has even been
/// corrected, invites the photographer to settle grain against an image
/// that is still going to move.
///
/// `Effect` after `Colour` is a look laid over a settled picture — and is
/// the one arguable slot. A spectral film simulation declares
/// [`crate::Operation::renders`] and replaces the base curve, which is an
/// argument for treating it as foundational rather than final; an array of
/// six cannot say "last, except when it is first". The tension is recorded
/// here rather than settled.
///
/// Both ends were wrong for as long as this list only fed a row of chips
/// nobody reads in order. It stopped being harmless when the same list
/// began driving a column read top to bottom.
pub const ALL: [Attribute; 6] = [
Attribute::Optics,
Attribute::Compose,
Attribute::Tone,
Attribute::Colour,
Attribute::Detail,
Attribute::Optics,
Attribute::Effect,
Attribute::Detail,
];
/// The localisation key naming this concept.