Skip to content

Commit 3b10606

Browse files
author
Andreas Molzer
authored
Merge pull request #2342 from image-rs/issue-2340
Optimize sampling for empty images
2 parents 02a4928 + e62349b commit 3b10606

File tree

1 file changed

+57
-0
lines changed

1 file changed

+57
-0
lines changed

src/imageops/sample.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,8 @@ pub(crate) fn box_kernel(_x: f32) -> f32 {
221221
// ```new_width``` is the desired width of the new image
222222
// ```filter``` is the filter to use for sampling.
223223
// ```image``` is not necessarily Rgba and the order of channels is passed through.
224+
//
225+
// Note: if an empty image is passed in, panics unless the image is truly empty.
224226
fn horizontal_sample<P, S>(
225227
image: &Rgba32FImage,
226228
new_width: u32,
@@ -231,6 +233,13 @@ where
231233
S: Primitive + 'static,
232234
{
233235
let (width, height) = image.dimensions();
236+
// This is protection against a memory usage similar to #2340. See `vertical_sample`.
237+
assert!(
238+
// Checks the implication: (width == 0) -> (height == 0)
239+
width != 0 || height == 0,
240+
"Unexpected prior allocation size. This case should have been handled by the caller"
241+
);
242+
234243
let mut out = ImageBuffer::new(new_width, height);
235244
let mut ws = Vec::new();
236245

@@ -463,13 +472,24 @@ pub fn interpolate_bilinear<P: Pixel>(
463472
// ```filter``` is the filter to use for sampling.
464473
// The return value is not necessarily Rgba, the underlying order of channels in ```image``` is
465474
// preserved.
475+
//
476+
// Note: if an empty image is passed in, panics unless the image is truly empty.
466477
fn vertical_sample<I, P, S>(image: &I, new_height: u32, filter: &mut Filter) -> Rgba32FImage
467478
where
468479
I: GenericImageView<Pixel = P>,
469480
P: Pixel<Subpixel = S> + 'static,
470481
S: Primitive + 'static,
471482
{
472483
let (width, height) = image.dimensions();
484+
485+
// This is protection against a regression in memory usage such as #2340. Since the strategy to
486+
// deal with it depends on the caller it is a precondition of this function.
487+
assert!(
488+
// Checks the implication: (height == 0) -> (width == 0)
489+
height != 0 || width == 0,
490+
"Unexpected prior allocation size. This case should have been handled by the caller"
491+
);
492+
473493
let mut out = ImageBuffer::new(width, new_height);
474494
let mut ws = Vec::new();
475495

@@ -916,6 +936,16 @@ where
916936
I::Pixel: 'static,
917937
<I::Pixel as Pixel>::Subpixel: 'static,
918938
{
939+
// Check if there is nothing to sample from.
940+
let is_empty = {
941+
let (width, height) = image.dimensions();
942+
width == 0 || height == 0
943+
};
944+
945+
if is_empty {
946+
return ImageBuffer::new(nwidth, nheight);
947+
}
948+
919949
// check if the new dimensions are the same as the old. if they are, make a copy instead of resampling
920950
if (nwidth, nheight) == image.dimensions() {
921951
let mut tmp = ImageBuffer::new(image.width(), image.height());
@@ -970,6 +1000,11 @@ where
9701000
};
9711001

9721002
let (width, height) = image.dimensions();
1003+
let is_empty = width == 0 || height == 0;
1004+
1005+
if is_empty {
1006+
return ImageBuffer::new(width, height);
1007+
}
9731008

9741009
// Keep width and height the same for horizontal and
9751010
// vertical sampling.
@@ -1259,4 +1294,26 @@ mod tests {
12591294
let result = resize(&image, 22, 22, FilterType::Lanczos3);
12601295
assert!(result.into_raw().into_iter().any(|c| c != 0));
12611296
}
1297+
1298+
#[test]
1299+
fn issue_2340() {
1300+
let empty = crate::GrayImage::from_raw(1 << 31, 0, vec![]).unwrap();
1301+
// Really we're checking that no overflow / outsized allocation happens here.
1302+
let result = resize(&empty, 1, 1, FilterType::Lanczos3);
1303+
assert!(result.into_raw().into_iter().all(|c| c == 0));
1304+
// With the previous strategy before the regression this would allocate 1TB of memory for a
1305+
// temporary during the sampling evaluation.
1306+
let result = resize(&empty, 256, 256, FilterType::Lanczos3);
1307+
assert!(result.into_raw().into_iter().all(|c| c == 0));
1308+
}
1309+
1310+
#[test]
1311+
fn issue_2340_refl() {
1312+
// Tests the swapped coordinate version of `issue_2340`.
1313+
let empty = crate::GrayImage::from_raw(0, 1 << 31, vec![]).unwrap();
1314+
let result = resize(&empty, 1, 1, FilterType::Lanczos3);
1315+
assert!(result.into_raw().into_iter().all(|c| c == 0));
1316+
let result = resize(&empty, 256, 256, FilterType::Lanczos3);
1317+
assert!(result.into_raw().into_iter().all(|c| c == 0));
1318+
}
12621319
}

0 commit comments

Comments
 (0)