Images: pick a reduced JPEG's scale by its long side

The JPEG decoder takes the smallest scale that meets either requested
side. A short side rounds up to the same size at several scales, so a
100 x 2 JPEG asked for at a half decoded at an eighth (13 x 1). The
long side alone now picks the scale, and a decode whose size differs
from the requested reduction is refused, which falls back to the full
decode.
This commit is contained in:
dotCooCoo committed 2026-11-12 12:57:16 -08:00
1 parent 113319e3e1
commit 55963c4918
2 files changed
+21 -1

No files matched your search

+8 -1
View File
@@ -100,7 +100,14 @@ pub(crate) fn decode_reduced(bytes: &[u8], shrink: u8) -> Option<(image::RgbaIma
}
}
let reduced = |n: u16| u16::try_from(u32::from(n).div_ceil(1 << shrink)).ok();
let (w, h) = d.scale(reduced(info.width)?, reduced(info.height)?).ok()?;
let expected = (reduced(info.width)?, reduced(info.height)?);
// The decoder takes the smallest scale that meets either requested side, and a short side
// rounds up to the same size at several scales, so only the long side is requested.
let request = if info.width >= info.height { (expected.0, u16::MAX) } else { (u16::MAX, expected.1) };
let (w, h) = d.scale(request.0, request.1).ok()?;
if (w, h) != expected {
return None;
}
let px = d.decode().ok()?;
let pixels = usize::from(w).checked_mul(usize::from(h))?;
let rgba: Vec<u8> = if colour {
+13
View File
@@ -596,6 +596,19 @@ fn reduced_jpegs_decode_at_a_half_quarter_or_eighth_rounded_up() {
assert!(decode_rgba_reduced(&jpeg, 4).is_none());
}
#[test]
fn a_thin_reduced_jpeg_keeps_its_long_side_at_the_requested_reduction() {
// The short side rounds up to 1 at every reduction, so it cannot pick the scale.
for (w, h) in [(100, 2), (2, 100)] {
let jpeg = gradient_jpeg(w, h, false);
for (shrink, long) in [(1, 50), (2, 25), (3, 13)] {
let img = decode_rgba_reduced(&jpeg, shrink).unwrap();
let size = if w > h { (long, 1) } else { (1, long) };
assert_eq!(img.dimensions(), size, "{w} × {h}, shrink {shrink}");
}
}
}
#[test]
fn reduced_decoding_is_for_jpegs_and_survives_damaged_ones() {
let gray = decode_rgba_reduced(&gradient_jpeg(64, 64, true), 2).unwrap();