Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize sampling for empty images #2342

Merged
merged 2 commits into from
Oct 12, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions src/imageops/sample.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,8 @@ pub(crate) fn box_kernel(_x: f32) -> f32 {
// ```new_width``` is the desired width of the new image
// ```filter``` is the filter to use for sampling.
// ```image``` is not necessarily Rgba and the order of channels is passed through.
//
// Note: if an empty image is passed in, panics unless the image is truly empty.
fn horizontal_sample<P, S>(
image: &Rgba32FImage,
new_width: u32,
Expand All @@ -231,6 +233,13 @@ where
S: Primitive + 'static,
{
let (width, height) = image.dimensions();
// This is protection against a memory usage similar to #2340. See `vertical_sample`.
assert!(
// Checks the implication: (width == 0) -> (height == 0)
width != 0 || height == 0,
"Unexpected prior allocation size. This case should have been handled by the caller"
);

let mut out = ImageBuffer::new(new_width, height);
let mut ws = Vec::new();

Expand Down Expand Up @@ -463,13 +472,24 @@ pub fn interpolate_bilinear<P: Pixel>(
// ```filter``` is the filter to use for sampling.
// The return value is not necessarily Rgba, the underlying order of channels in ```image``` is
// preserved.
//
// Note: if an empty image is passed in, panics unless the image is truly empty.
fn vertical_sample<I, P, S>(image: &I, new_height: u32, filter: &mut Filter) -> Rgba32FImage
where
I: GenericImageView<Pixel = P>,
P: Pixel<Subpixel = S> + 'static,
S: Primitive + 'static,
{
let (width, height) = image.dimensions();

// This is protection against a regression in memory usage such as #2340. Since the strategy to
// deal with it depends on the caller it is a precondition of this function.
assert!(
// Checks the implication: (height == 0) -> (width == 0)
height != 0 || width == 0,
"Unexpected prior allocation size. This case should have been handled by the caller"
);

let mut out = ImageBuffer::new(width, new_height);
let mut ws = Vec::new();

Expand Down Expand Up @@ -916,6 +936,16 @@ where
I::Pixel: 'static,
<I::Pixel as Pixel>::Subpixel: 'static,
{
// Check if there is nothing to sample from.
let is_empty = {
let (width, height) = image.dimensions();
width == 0 || height == 0
};

if is_empty {
return ImageBuffer::new(nwidth, nheight);
}

// check if the new dimensions are the same as the old. if they are, make a copy instead of resampling
if (nwidth, nheight) == image.dimensions() {
let mut tmp = ImageBuffer::new(image.width(), image.height());
Expand Down Expand Up @@ -968,6 +998,11 @@ where
};

let (width, height) = image.dimensions();
let is_empty = width == 0 || height == 0;

if is_empty {
return ImageBuffer::new(width, height);
}

// Keep width and height the same for horizontal and
// vertical sampling.
Expand Down Expand Up @@ -1257,4 +1292,26 @@ mod tests {
let result = resize(&image, 22, 22, FilterType::Lanczos3);
assert!(result.into_raw().into_iter().any(|c| c != 0));
}

#[test]
fn issue_2340() {
let empty = crate::GrayImage::from_raw(1 << 31, 0, vec![]).unwrap();
HeroicKatora marked this conversation as resolved.
Show resolved Hide resolved
// Really we're checking that no overflow / outsized allocation happens here.
let result = resize(&empty, 1, 1, FilterType::Lanczos3);
assert!(result.into_raw().into_iter().all(|c| c == 0));
// With the previous strategy before the regression this would allocate 1TB of memory for a
// temporary during the sampling evaluation.
let result = resize(&empty, 256, 256, FilterType::Lanczos3);
assert!(result.into_raw().into_iter().all(|c| c == 0));
}

#[test]
fn issue_2340_refl() {
// Tests the swapped coordinate version of `issue_2340`.
let empty = crate::GrayImage::from_raw(0, 1 << 31, vec![]).unwrap();
let result = resize(&empty, 1, 1, FilterType::Lanczos3);
assert!(result.into_raw().into_iter().all(|c| c == 0));
let result = resize(&empty, 256, 256, FilterType::Lanczos3);
assert!(result.into_raw().into_iter().all(|c| c == 0));
}
}
Loading