Name the frame's category for the decision, not the maths
`Attribute::Geometry` becomes `Attribute::Compose`, and `film_sim` moves from `[tone, colour]` to `[effect]`. Two categories were doing the wrong job. "Geometry" describes what crop, straighten and the quarter turns do to coordinates — but it describes lens distortion correction exactly as well, and that is not a compositional choice at all. Naming the attribute for the photographer's decision is what separates it from `Optics`: one is what the lens did, the other is what they chose. The maths the two have in common is not the thing worth filing them under. A film stock declared both `tone` and `colour`, so "Kodachrome" appeared in the Light group beside exposure and again in Colour beside white balance — two places, neither of which is where anyone looks for it. It is neither: `Effect` is defined in this same file as "applied rather than corrected — a look, not a fix", which is what a stock is. That it moves tone and colour is true of every look, and is not what the attribute is for. `from_name` still accepts "geometry" on the way in. That string is persisted in `develop.copy_attributes`, and an entry it fails to parse is not an error — `presets::scope_for` logs it and drops it — so without the alias an existing settings file would have quietly narrowed what a paste carries. `name` writes the current spelling, so the file migrates itself the first time it is saved. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,12 @@
|
||||
id: film_sim
|
||||
order: 25
|
||||
attributes: [tone, colour]
|
||||
# Effect, not tone-and-colour. It declared both, which put "Kodachrome" in
|
||||
# the Light group beside exposure and again in Colour beside white balance —
|
||||
# two places, neither of which is where anyone looks for it, and both of which
|
||||
# it crowded. A stock is `Effect`'s own definition: applied rather than
|
||||
# corrected, a look and not a fix. That it moves tone and colour is true of
|
||||
# every look and is not what the attribute is for.
|
||||
attributes: [effect]
|
||||
rust: FilmSim
|
||||
|
||||
why_rust: |
|
||||
|
||||
@@ -120,7 +120,7 @@ pub enum Attr {
|
||||
Colour,
|
||||
Detail,
|
||||
Optics,
|
||||
Geometry,
|
||||
Compose,
|
||||
Effect,
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ impl Attr {
|
||||
Attr::Colour => "colour",
|
||||
Attr::Detail => "detail",
|
||||
Attr::Optics => "optics",
|
||||
Attr::Geometry => "geometry",
|
||||
Attr::Compose => "compose",
|
||||
Attr::Effect => "effect",
|
||||
}
|
||||
}
|
||||
@@ -144,7 +144,7 @@ impl Attr {
|
||||
Attr::Colour,
|
||||
Attr::Detail,
|
||||
Attr::Optics,
|
||||
Attr::Geometry,
|
||||
Attr::Compose,
|
||||
Attr::Effect,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -350,7 +350,7 @@ fn attribute(a: decl::Attr) -> Attribute {
|
||||
decl::Attr::Colour => Attribute::Colour,
|
||||
decl::Attr::Detail => Attribute::Detail,
|
||||
decl::Attr::Optics => Attribute::Optics,
|
||||
decl::Attr::Geometry => Attribute::Geometry,
|
||||
decl::Attr::Compose => Attribute::Compose,
|
||||
decl::Attr::Effect => Attribute::Effect,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -580,8 +580,15 @@ pub enum Attribute {
|
||||
/// Corrections for the lens that took the photograph: distortion,
|
||||
/// chromatic aberration, vignetting.
|
||||
Optics,
|
||||
/// The shape of the frame: crop, straighten, rotation, flips.
|
||||
Geometry,
|
||||
/// How the frame is composed: crop, straighten, rotation, flips.
|
||||
///
|
||||
/// Named for the decision rather than for the maths. The lens corrections
|
||||
/// are geometry too — distortion moves pixels exactly as a straighten
|
||||
/// does — and lumping the two together would file a correction the
|
||||
/// photographer never asked for beside a choice that is the whole reason
|
||||
/// they opened the photograph. [`Self::Optics`] is what the lens did;
|
||||
/// this is what they decided.
|
||||
Compose,
|
||||
/// Applied rather than corrected — a look, not a fix.
|
||||
Effect,
|
||||
}
|
||||
@@ -598,7 +605,7 @@ impl Attribute {
|
||||
Attribute::Colour,
|
||||
Attribute::Detail,
|
||||
Attribute::Optics,
|
||||
Attribute::Geometry,
|
||||
Attribute::Compose,
|
||||
Attribute::Effect,
|
||||
];
|
||||
|
||||
@@ -613,7 +620,7 @@ impl Attribute {
|
||||
Self::Colour => "attr.colour",
|
||||
Self::Detail => "attr.detail",
|
||||
Self::Optics => "attr.optics",
|
||||
Self::Geometry => "attr.geometry",
|
||||
Self::Compose => "attr.compose",
|
||||
Self::Effect => "attr.effect",
|
||||
})
|
||||
}
|
||||
@@ -630,7 +637,7 @@ impl Attribute {
|
||||
Self::Colour => "colour",
|
||||
Self::Detail => "detail",
|
||||
Self::Optics => "optics",
|
||||
Self::Geometry => "geometry",
|
||||
Self::Compose => "compose",
|
||||
Self::Effect => "effect",
|
||||
}
|
||||
}
|
||||
@@ -642,7 +649,15 @@ impl Attribute {
|
||||
"colour" => Self::Colour,
|
||||
"detail" => Self::Detail,
|
||||
"optics" => Self::Optics,
|
||||
"geometry" => Self::Geometry,
|
||||
"compose" => Self::Compose,
|
||||
// The name this attribute was persisted under before it was
|
||||
// called Compose. Settings written by an older build carry it in
|
||||
// `develop.copy_attributes`, and `from_name` returning `None`
|
||||
// there does not fail loudly — `presets::scope_for` logs and drops
|
||||
// the entry, silently narrowing what a paste carries. Accepted on
|
||||
// the way in only; `name` writes the current spelling, so a
|
||||
// settings file rewrites itself the first time it is saved.
|
||||
"geometry" => Self::Compose,
|
||||
"effect" => Self::Effect,
|
||||
_ => return None,
|
||||
})
|
||||
|
||||
@@ -78,7 +78,7 @@ static DESCRIPTOR: LazyLock<Arc<OpDescriptor>> = LazyLock::new(|| {
|
||||
Arc::new(OpDescriptor {
|
||||
// The shape of the frame, and the only operation that changes the
|
||||
// output's dimensions.
|
||||
attributes: vec![Attribute::Geometry],
|
||||
attributes: vec![Attribute::Compose],
|
||||
id: ID,
|
||||
label: LocalizedKey("op.framing"),
|
||||
params: vec![
|
||||
|
||||
@@ -59,7 +59,7 @@ use crate::graph::EditGraph;
|
||||
///
|
||||
/// It also dissolves a special case. Framing used to be excluded by an
|
||||
/// explicit test against one operation's id; it is now excluded because
|
||||
/// [`Attribute::Geometry`] is not in the default set, and the argument below
|
||||
/// [`Attribute::Compose`] is not in the default set, and the argument below
|
||||
/// is a statement about a kind of edit rather than about a particular node.
|
||||
///
|
||||
/// # Why geometry is out by default
|
||||
@@ -116,7 +116,7 @@ impl Scope {
|
||||
/// its shape. The default.
|
||||
pub fn adjustments() -> Self {
|
||||
Self {
|
||||
bits: Self::all_bits() & !Self::bit(Attribute::Geometry),
|
||||
bits: Self::all_bits() & !Self::bit(Attribute::Compose),
|
||||
carries_unclassified: true,
|
||||
}
|
||||
}
|
||||
@@ -278,7 +278,7 @@ impl Preset {
|
||||
pub fn touches_framing(&self) -> bool {
|
||||
self.params
|
||||
.keys()
|
||||
.any(|(op, _)| attributes_of(op).is_some_and(|a| a.contains(&Attribute::Geometry)))
|
||||
.any(|(op, _)| attributes_of(op).is_some_and(|a| a.contains(&Attribute::Compose)))
|
||||
}
|
||||
|
||||
/// How many operations this preset touches, in scope.
|
||||
@@ -1254,8 +1254,8 @@ mod tests {
|
||||
.into_iter()
|
||||
.filter(|a| !default_scope.has(*a))
|
||||
.collect();
|
||||
assert_eq!(excluded, vec![Attribute::Geometry]);
|
||||
assert!(Scope::everything().has(Attribute::Geometry));
|
||||
assert_eq!(excluded, vec![Attribute::Compose]);
|
||||
assert!(Scope::everything().has(Attribute::Compose));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -46,7 +46,7 @@ fn the_groups_are_derivable_from_the_chain() {
|
||||
);
|
||||
assert!(present.contains(&Attribute::Colour));
|
||||
assert!(
|
||||
present.contains(&Attribute::Geometry),
|
||||
present.contains(&Attribute::Compose),
|
||||
"framing is in the capability list and is geometry"
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user