LR-CROP-RECT: the crop's source size is counted in the turned photo (#2555)

`source_edge_needed` bounds the crop in the photo as shown (turned by its orientation), but counted
the rect's pixels with the unturned width and height. On a photo turned a quarter, a crop whose
aspect differs from the photo's then asked for too small a source: a square crop of a 3:2 photo
shown 2000 px long needs the full-size source, and got the 2560 px preview. It now counts them with
the turned size. The two tests that pinned the old count use the turned size too, and one checks
that square crop. Found by CodeRabbit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CvoftLURuf4uBmSatewB6V
This commit is contained in:
insideTheFlux
2026-10-27 21:00:50 -07:00
co-authored by Claude Opus 5.5
parent f860577c51
commit c0f4f54e7f
2 changed files with 13 additions and 4 deletions
+3 -2
View File
@@ -60,12 +60,13 @@ impl SourceLevel {
/// only part of the photo, so its pixels come from a source that many times larger (the source
/// level is chosen from this, or a tight crop shown at its own pixels would be a stretched preview).
/// The crop is bounded as the render bounds it (issue #2555), so one reaching far past the photo
/// doesn't pick a source too small for the frame it renders.
/// doesn't pick a source too small for the frame it renders. Its rect is in the photo as shown
/// (turned by its orientation), so it is measured with the turned size.
pub(super) fn source_edge_needed(p: &Photo, settings: &DevelopSettings, apply_crop: bool, out_long: usize) -> usize {
let (w, h) = (f64::from(p.width.max(1)), f64::from(p.height.max(1)));
let (ow, oh) = if settings.orientation.swaps_axes() { (h, w) } else { (w, h) };
let r = settings.crop.geometry.bounded(ow, oh).rect;
let shown = if apply_crop { (r.width() * w).max(r.height() * h) } else { w.max(h) };
let shown = if apply_crop { (r.width() * ow).max(r.height() * oh) } else { w.max(h) };
if !shown.is_finite() || shown < 1.0 {
return out_long;
}
+10 -2
View File
@@ -389,7 +389,7 @@ fn a_crop_far_past_the_photo_reads_the_source_its_bounded_frame_needs() {
let mut d = lightcraft_develop::DevelopSettings { orientation, ..Default::default() };
d.crop.geometry = wild;
let r = wild.bounded(ow, oh).rect;
let expected = (1600.0 * w.max(h) / (r.width() * w).max(r.height() * h)) as usize;
let expected = (1600.0 * w.max(h) / (r.width() * ow).max(r.height() * oh)) as usize;
assert_eq!(super::source::source_edge_needed(&p, &d, true, 1600), expected, "{orientation:?}");
}
}
@@ -408,12 +408,20 @@ fn crops_on_the_photo_keep_the_source_size_they_need() {
for aspect in [None, Some(1.0), Some(3.0), Some(0.25)] {
let mut d = lightcraft_develop::DevelopSettings { orientation, ..Default::default() };
d.crop.geometry = crop_fit_angle(ow, oh, angle, aspect);
// the rect is in the photo as shown, so its pixels are counted in the turned size
let r = d.crop.geometry.rect;
let expected = (1600.0 * w.max(h) / (r.width() * w).max(r.height() * h)) as usize;
let expected = (1600.0 * w.max(h) / (r.width() * ow).max(r.height() * oh)) as usize;
assert_eq!(super::source::source_edge_needed(&p, &d, true, 1600), expected, "{orientation:?} {angle} {aspect:?}");
}
}
}
// a square crop of the photo turned a quarter is the photo's short side across: shown 2000 px
// long it needs more than the 2560 px preview (counted in the unturned size, it asked for 2000)
let mut d = lightcraft_develop::DevelopSettings { orientation: Orientation::Rotate90, ..Default::default() };
d.crop.geometry = crop_fit_angle(h, w, 0.0, Some(1.0));
let needed = super::source::source_edge_needed(&p, &d, true, 2000);
assert_eq!(needed, (2000.0 * w.max(h) / w.min(h)) as usize);
assert_eq!(SourceLevel::for_size(needed), SourceLevel::Full, "{needed}");
}
// a window whose spot reads from further away than a render can hold is refused