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

Cropping API doesn't check bounds, easy to misuse #2296

Open
Shnatsel opened this issue Jul 28, 2024 · 7 comments
Open

Cropping API doesn't check bounds, easy to misuse #2296

Shnatsel opened this issue Jul 28, 2024 · 7 comments
Labels
kind: API missing or awkward public APIs next: breaking Information tag for PRs and ideas that require an interface break

Comments

@Shnatsel
Copy link
Contributor

In image v0.25.2, the crop() and crop_imm() functions return an image view or an image unconditionally, even if the area to be cropped is completely out of bounds. This makes the API very easy to misuse:

use image; // 0.25.1
use image::{DynamicImage, GenericImageView};

pub fn main() {
    let image = DynamicImage::new_rgba8(50, 50);
    let cropped = image.crop_imm(100, 100, 100, 100); //  Succeeds despite wrong bounds!
    // and when we try to actually use it later...
    cropped.get_pixel(0,0); // PANIC!
}

Demo in playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=0898257582886870218d41c94506e3cb

@Shnatsel
Copy link
Contributor Author

SubImages seem to be kinda broken in general: #1412

@Shnatsel Shnatsel mentioned this issue Jul 28, 2024
6 tasks
@Shnatsel Shnatsel added next: breaking Information tag for PRs and ideas that require an interface break kind: API missing or awkward public APIs labels Sep 13, 2024
@soumyasen1809
Copy link

soumyasen1809 commented Sep 18, 2024

@Shnatsel
Hi, I am new to Rust and would like to take a shot at this issue. Is it possible to work on it?

I was thinking of a simple solution, like

/// Return an immutable view into an image
/// The coordinates set the position of the top left corner of the crop.
pub fn crop_imm<I: GenericImageView>(
image: &I,
x: u32,
y: u32,
width: u32,
height: u32,
) -> SubImage<&I> {
let (x, y, width, height) = crop_dimms(image, x, y, width, height);
SubImage::new(image, x, y, width, height)
}

We can only return a SubImage if the width and height is > 0, else we return None

let (x, y, width, height) = crop_dimms(image, x, y, width, height);
if width > 0 && height > 0 { return Some(SubImage::new(image, x, y, width, height);) }
return None;

@Shnatsel
Copy link
Contributor Author

This will require changing the public API, so it's not a change that will be easy to get merged. I suggest looking at issues that do not have the API tag for your first contribution.

@kornelski
Copy link
Contributor

Since the result is useless anyway, I don't think it would be a breaking change to return a slightly less broken result?

Crop could try to stay within bounds of the image: clamp left & top coordinates to width-1 & height-1, and then clamp size to whatever is left.

@soumyasen1809
Copy link

If we assign the (x, y) to (width-1, height-1), and the size left is > 0, then we will always return a single pixel as a Subimage. Isn't that wrong? Because the result is a single pixel, while there should not be any pixels in the output Subimago (i.e. the Subimage should not exist/ be defined).

@kornelski
Copy link
Contributor

It is wrong, but the wrongness started with invalid input. A 1x1 image may be better than a crashy empty one. Alternatively, the function could itself panic on out-of-bounds coordinates. The docs currently don't say anything about error handling.

@soumyasen1809
Copy link

True...

fn crop_dimms<I: GenericImageView>(
image: &I,
x: u32,
y: u32,
width: u32,
height: u32,
) -> (u32, u32, u32, u32) {
let (iwidth, iheight) = image.dimensions();
let x = cmp::min(x, iwidth);
let y = cmp::min(y, iheight);
let height = cmp::min(height, iheight - y);
let width = cmp::min(width, iwidth - x);
(x, y, width, height)
}

Looking at the code, we can then replace iwidth and iheight like:

let x = cmp::min(x, iwidth-1); 
let y = cmp::min(y, iheight-1); 

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind: API missing or awkward public APIs next: breaking Information tag for PRs and ideas that require an interface break
Projects
None yet
Development

No branches or pull requests

3 participants