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

Add rotation module and functions #669

Merged
merged 3 commits into from
Jun 13, 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
140 changes: 140 additions & 0 deletions src/geometric_transformations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,146 @@ where
warp(image, &projection, interpolation, default)
}

/// Rotates an image 90 degrees clockwise.
///
/// # Examples
/// ```
/// use imageproc::geometric_transformations::rotate90;
/// use imageproc::gray_image;
///
/// let image = gray_image!(
/// 1, 2, 0, 0;
/// 3, 4, 0, 0;
/// 0, 0, 0, 0;
/// 0, 0, 0, 0);
///
/// let rotated = rotate90(&image);
///
/// assert_eq!(rotated, gray_image!(
/// 0, 0, 3, 1;
/// 0, 0, 4, 2;
/// 0, 0, 0, 0;
/// 0, 0, 0, 0));
/// ```
pub fn rotate90<P>(image: &Image<P>) -> Image<P>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don’t feel strongly either way but it’s a bit unfortunate to use angles in degrees in the names of these functions when angles everywhere else in this file (/crate) are in radians. Can’t think of better names off the top of my head though.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As some prior art, opencv just has a single rotate() function which takes an enum that looks like ROTATE_90_CLOCKWISE

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should do the same and use a single function with an enum, but I don't feel particularly strongly either way.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wondered the same thing. I’ll just merge as is for now. Thanks.

where
P: Pixel,
{
let (width, height) = image.dimensions();

let mut rotated = Image::new(height, width);

for y in 0..height {
for x in 0..width {
rotated.put_pixel(height - y - 1, x, *image.get_pixel(x, y));
}
}

rotated
}

/// Rotates an image 270 degrees clockwise.
///
/// # Examples
/// ```
/// use imageproc::geometric_transformations::rotate270;
/// use imageproc::gray_image;
///
/// let image = gray_image!(
/// 1, 2, 0, 0;
/// 3, 4, 0, 0;
/// 0, 0, 0, 0;
/// 0, 0, 0, 0);
///
/// let rotated = rotate270(&image);
///
/// assert_eq!(rotated, gray_image!(
/// 0, 0, 0, 0;
/// 0, 0, 0, 0;
/// 2, 4, 0, 0;
/// 1, 3, 0, 0));
/// ```
pub fn rotate270<P>(image: &Image<P>) -> Image<P>
where
P: Pixel,
{
let (width, height) = image.dimensions();

let mut rotated = Image::new(height, width);

for y in 0..height {
for x in 0..width {
rotated.put_pixel(y, width - x - 1, *image.get_pixel(x, y));
}
}

rotated
}

/// Rotates an image 180 degrees clockwise.
///
/// # Examples
/// ```
/// use imageproc::geometric_transformations::rotate180;
/// use imageproc::gray_image;
///
/// let image = gray_image!(
/// 1, 2, 0, 0;
/// 3, 4, 0, 0;
/// 0, 0, 0, 0;
/// 0, 0, 0, 0);
///
/// let rotated = rotate180(&image);
///
/// assert_eq!(rotated, gray_image!(
/// 0, 0, 0, 0;
/// 0, 0, 0, 0;
/// 0, 0, 4, 3;
/// 0, 0, 2, 1));
/// ```
pub fn rotate180<P>(image: &Image<P>) -> Image<P>
where
P: Pixel,
{
let mut out = image.clone();
rotate180_mut(&mut out);
out
}
#[doc=generate_mut_doc_comment!("rotate180")]
pub fn rotate180_mut<P>(image: &mut Image<P>)
where
P: Pixel,
{
let (width, height) = image.dimensions();

for y in 0..height / 2 {
for x in 0..width {
let x180 = width - x - 1;
let y180 = height - y - 1;

let p = *image.get_pixel(x, y);
let p180 = *image.get_pixel(x180, y180);

image.put_pixel(x, y, p180);
image.put_pixel(x180, y180, p);
}
}

if height % 2 != 0 {
let y_middle = height / 2;

for x in 0..width / 2 {
let x180 = width - x - 1;

let p = *image.get_pixel(x, y_middle);
let p180 = *image.get_pixel(x180, y_middle);

image.put_pixel(x, y_middle, p180);
image.put_pixel(x180, y_middle, p);
}
}
}

/// Translates the input image by t. Note that image coordinates increase from
/// top left to bottom right. Output pixels whose pre-image are not in the input
/// image are set to the boundary pixel in the input image nearest to their pre-image.
Expand Down
Loading