Show the photograph the way it was taken
Nothing read EXIF orientation, so every frame from a body held sideways lay on its side — in the grid, in develop, and in the read-only preview. The tag is honoured as part of *reading the file*, at the same standing as a RAW's masked-photosite crop, never as an edit. It lives as a baseline on Framing rather than as a starting value for quarter_turns, which is what keeps four things true: a sideways file opens unmodified, reset returns it to upright rather than to the sensor's scan order, its sidecar stays empty, and the rotate button still moves the image 90° whatever the file underneath it says. Framing::effective composes the baseline with the user's own turns through the group law rather than by adding turns and OR-ing flags. The naive version gets one case wrong — an odd baseline turn plus a user mirror — and gets it wrong quietly, because the result is still a plausible orientation. The composition collapses to a single permutation, so obeying the tag costs nothing per pixel. dr_decode::orientation is a header-only IFD walk, separate from metadata() for the reason the entry points are separate at all: the grid asks once per cell and must not build a rawler decoder to get one tag. CR3 and RAF fall back to the full read, being neither TIFF nor JPEG. Written down as FR-DEV-3h. Known gap: thumbnails cached before this stay sideways. The store is keyed by file and size, and its shards sync — invalidating them would have every client re-download 25 MB a shard, which is not this commit's call to make. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -683,6 +683,48 @@ mod tests {
|
||||
assert_eq!(restored.param(framing::ID, framing::ROTATION), Some(1.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_sidecar_neither_records_nor_erases_the_files_orientation() {
|
||||
// How a file stored its pixels is a fact about the file, so it must
|
||||
// not travel in the sidecar — a shared edit would then carry one
|
||||
// camera's sensor scan onto another's. Two failures are checked
|
||||
// together because they are the same mistake seen from each end.
|
||||
let mut sideways = EditGraph::default_chain();
|
||||
sideways.set_orientation(dr_types::Orientation::from_exif(6));
|
||||
|
||||
// Nothing was edited, so there is nothing to write. If the baseline
|
||||
// leaked into `param`, a rotation would appear here.
|
||||
let v = version_of(&sideways);
|
||||
let text = {
|
||||
let mut s = Sidecar::new();
|
||||
s.put(v);
|
||||
s.to_text()
|
||||
};
|
||||
assert!(
|
||||
!text.contains("framing.rotation"),
|
||||
"an untouched sideways file wrote a rotation:\n{text}"
|
||||
);
|
||||
|
||||
// And applying an edit — which resets the graph first — must leave the
|
||||
// orientation where it was, or reopening an edited portrait frame
|
||||
// shows it on its side.
|
||||
let mut edited = EditGraph::default_chain();
|
||||
edited.set_param(framing::ID, framing::ANGLE, -1.5);
|
||||
let mut sidecar = Sidecar::new();
|
||||
sidecar.put(version_of(&edited));
|
||||
Sidecar::parse(&sidecar.to_text())
|
||||
.expect("valid")
|
||||
.default_version()
|
||||
.expect("a version")
|
||||
.apply(&mut sideways);
|
||||
|
||||
assert_eq!(
|
||||
sideways.framing().baseline(),
|
||||
dr_types::Orientation::from_exif(6)
|
||||
);
|
||||
assert_eq!(sideways.output_size(6000, 4000), (4000, 6000));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn applying_a_version_replaces_rather_than_overlays() {
|
||||
// Loading an edit onto a graph that already holds one must not leave
|
||||
|
||||
Reference in New Issue
Block a user