diff --git a/crates/pipeline/src/tests_geometry.rs b/crates/pipeline/src/tests_geometry.rs index 3be2dc1806..12f9ca88ad 100644 --- a/crates/pipeline/src/tests_geometry.rs +++ b/crates/pipeline/src/tests_geometry.rs @@ -597,3 +597,19 @@ fn auto_straighten_takes_the_roll_from_converging_verticals() { let deg = auto_level(&line_drawing(&lines)).expect("the vanishing point"); assert!((deg + 3.0).abs() < 0.3, "{deg}"); } + +/// The cached analysis tells lenses apart: the analysis image is corrected for `info.lens`, and +/// Auto straighten reads the horizon from a fresh image and the lines from the cache, so a hit +/// for another lens would mix two frames (review of #2809). +#[test] +fn the_analysis_cache_tells_lenses_apart() { + let src = tilted_scene(2.0, 30.0); + let s = DevelopSettings::default(); + let plain = SourceInfo::default(); + let vignette = lightcraft_develop::EmbeddedVignette::default(); + let lens = + SourceInfo { lens: Some(lightcraft_develop::EmbeddedLens { vignette: Some(vignette), ..Default::default() }), ..SourceInfo::default() }; + let key = crate::upright::analysis_key; + assert_eq!(key(&src, &plain, &s), key(&src, &plain.clone(), &s), "the same source, settings and lens"); + assert_ne!(key(&src, &plain, &s), key(&src, &lens, &s), "another lens"); +} diff --git a/crates/pipeline/src/upright.rs b/crates/pipeline/src/upright.rs index f90bd2c4ad..e9ce00695b 100644 --- a/crates/pipeline/src/upright.rs +++ b/crates/pipeline/src/upright.rs @@ -553,8 +553,7 @@ pub fn analyze_source(src: &Rgb32f, info: &SourceInfo, s: &DevelopSettings) -> A /// cache miss instead of building it again. fn analyze_cached(src: &Rgb32f, info: &SourceInfo, s: &DevelopSettings, plane: Option<&Plane>) -> Analysis { static CACHE: Mutex> = Mutex::new(Vec::new()); - let key = crate::optics::fingerprint(src) - ^ lightcraft_develop::DevelopSettings { optics: s.optics, orientation: s.orientation, ..Default::default() }.hash64().rotate_left(17); + let key = analysis_key(src, info, s); if let Ok(c) = CACHE.lock() && let Some((_, a)) = c.iter().find(|(k, _)| *k == key) { @@ -573,6 +572,17 @@ fn analyze_cached(src: &Rgb32f, info: &SourceInfo, s: &DevelopSettings, plane: O a } +/// What an analysis depends on: the source's pixels, the optics and orientation settings, and the +/// lens the frame is corrected for (`info.lens`, which [`analysis_plane`] reads through the frame). +pub(crate) fn analysis_key(src: &Rgb32f, info: &SourceInfo, s: &DevelopSettings) -> u64 { + use std::hash::{Hash, Hasher}; + let mut lens = std::collections::hash_map::DefaultHasher::new(); + format!("{:?}", info.lens).hash(&mut lens); + crate::optics::fingerprint(src) + ^ lightcraft_develop::DevelopSettings { optics: s.optics, orientation: s.orientation, ..Default::default() }.hash64().rotate_left(17) + ^ lens.finish().rotate_left(29) +} + /// The image an analysis reads: the lens-corrected source (optics applied, no perspective or crop) /// at [`ANALYSIS_LONG`] px, as perceptual lightness normalized to its bright end. pub(crate) fn analysis_plane(src: &Rgb32f, info: &SourceInfo, s: &DevelopSettings) -> Plane {