mirror of
https://github.com/storytold/lightcraft.git
synced 2026-11-13 09:20:07 -06:00
security(raster, segment): resample tall narrow images columns first (#3678)
This commit is contained in:
@@ -115,12 +115,22 @@ impl Pixel for [f32; 4] {
|
||||
}
|
||||
}
|
||||
|
||||
/// Resize to `w × h` (separable; horizontal pass then vertical pass).
|
||||
/// Resize to `w × h` (separable; horizontal pass then vertical pass, or the other way round when
|
||||
/// that intermediate would be far larger: widening a tall, narrow strip kept every source row at
|
||||
/// the new width, gigabytes from a small source).
|
||||
pub fn resize<T: Pixel>(img: &Image<T>, w: usize, h: usize, filter: Filter) -> Image<T> {
|
||||
let (w, h) = (w.max(1), h.max(1));
|
||||
if img.width == w && img.height == h {
|
||||
return img.clone();
|
||||
}
|
||||
if w.saturating_mul(img.height) > img.width.saturating_mul(h).saturating_mul(4) {
|
||||
return horizontal(&vertical(img, h, filter), w, filter);
|
||||
}
|
||||
vertical(&horizontal(img, w, filter), h, filter)
|
||||
}
|
||||
|
||||
/// Each row resampled to `w` wide.
|
||||
fn horizontal<T: Pixel>(img: &Image<T>, w: usize, filter: Filter) -> Image<T> {
|
||||
let wx = weights(img.width, w, filter);
|
||||
let mut tmp = Image::<T>::new(w, img.height);
|
||||
par_rows(&mut tmp.data, w, |y, row| {
|
||||
@@ -134,12 +144,18 @@ pub fn resize<T: Pixel>(img: &Image<T>, w: usize, h: usize, filter: Filter) -> I
|
||||
*o = acc;
|
||||
}
|
||||
});
|
||||
tmp
|
||||
}
|
||||
|
||||
/// Each column resampled to `h` tall.
|
||||
fn vertical<T: Pixel>(img: &Image<T>, h: usize, filter: Filter) -> Image<T> {
|
||||
let w = img.width;
|
||||
let wy = weights(img.height, h, filter);
|
||||
let mut out = Image::<T>::new(w, h);
|
||||
par_rows(&mut out.data, w, |y, row| {
|
||||
let (lo, ws) = &wy[y];
|
||||
for (k, wt) in ws.iter().enumerate() {
|
||||
let src = tmp.row(lo + k);
|
||||
let src = img.row(lo + k);
|
||||
for (o, s) in row.iter_mut().zip(src) {
|
||||
*o = o.madd(*s, *wt);
|
||||
}
|
||||
@@ -178,6 +194,16 @@ pub fn half<T: Pixel>(img: &Image<T>) -> Image<T> {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn widening_a_tall_strip_resamples_its_columns_first() {
|
||||
// 1 × 400 000 to 4000 × 8: horizontal-first kept 400 000 rows of 4000 (25 GB of RGBA f32)
|
||||
let mut strip = Image::<[f32; 4]>::new(1, 400_000);
|
||||
strip.data.iter_mut().for_each(|p| *p = [0.25, 0.5, 0.75, 1.0]);
|
||||
let out = resize(&strip, 4000, 8, Filter::Mitchell);
|
||||
assert_eq!((out.width, out.height), (4000, 8));
|
||||
assert!(out.data.iter().all(|p| (p[1] - 0.5).abs() < 1e-3), "a flat strip stays flat");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn constant_image_stays_constant() {
|
||||
let img = Image::<f32>::filled(37, 21, 0.42);
|
||||
|
||||
@@ -34,6 +34,31 @@ pub fn preprocess(rgb: &[u8], w: usize, h: usize, side: usize) -> Result<Vec<f32
|
||||
if w == 0 || h == 0 || rgb.len() < n {
|
||||
return Err(Error::Model(format!("image buffer of {} bytes for {w} × {h}", rgb.len())));
|
||||
}
|
||||
// The horizontal pass keeps every source row (h × side samples): for a strip far taller than
|
||||
// it is wide that is many times the image itself, so such an image is resampled transposed
|
||||
// (its rows become columns) and the result transposed back.
|
||||
if h > w.saturating_mul(4) {
|
||||
let mut t = vec![0u8; n];
|
||||
for y in 0..h {
|
||||
for x in 0..w {
|
||||
if let (Some(d), Some(s)) = (t.get_mut((x * h + y) * 3..(x * h + y) * 3 + 3), rgb.get((y * w + x) * 3..(y * w + x) * 3 + 3)) {
|
||||
d.copy_from_slice(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
let o = preprocess(&t, h, w, side)?;
|
||||
let mut out = vec![0f32; o.len()];
|
||||
for c in 0..3 {
|
||||
for oy in 0..side {
|
||||
for ox in 0..side {
|
||||
if let (Some(d), Some(v)) = (out.get_mut((c * side + oy) * side + ox), o.get((c * side + ox) * side + oy)) {
|
||||
*d = *v;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Ok(out);
|
||||
}
|
||||
let (wx, wy) = (weights(w, side), weights(h, side));
|
||||
// horizontal pass: h rows × side columns × 3
|
||||
let mut tmp = vec![0f32; h * side * 3];
|
||||
@@ -117,6 +142,19 @@ mod tests {
|
||||
#[test]
|
||||
fn short_buffers_are_errors() {
|
||||
assert!(preprocess(&[0; 5], 2, 2, 4).is_err());
|
||||
// a strip 1 pixel wide and 200 000 tall: resampled transposed, so the intermediate is
|
||||
// side × 1 rather than 200 000 × side; it equals the resample of its transposed twin
|
||||
let h = 200_000usize;
|
||||
let strip: Vec<u8> = (0..h * 3).map(|i| (i / 3 % 251) as u8).collect();
|
||||
let a = preprocess(&strip, 1, h, 8).unwrap();
|
||||
let b = preprocess(&strip, h, 1, 8).unwrap();
|
||||
for c in 0..3 {
|
||||
for y in 0..8 {
|
||||
for x in 0..8 {
|
||||
assert_eq!(a[(c * 8 + y) * 8 + x], b[(c * 8 + x) * 8 + y]);
|
||||
}
|
||||
}
|
||||
}
|
||||
assert!(preprocess(&[], 0, 0, 4).is_err());
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user