diff --git a/src/geometric_transformations.rs b/src/geometric_transformations.rs
index a6394b52..28badcc1 100644
--- a/src/geometric_transformations.rs
+++ b/src/geometric_transformations.rs
@@ -317,6 +317,85 @@ where
warp(image, &projection, interpolation, default)
}
+/// Rotates an image clockwise about its center by theta radians without cropping.
+/// The output image has dimensions calculated to fit the entire rotated image.
+/// Output pixels whose pre-image lies outside the input image are set to `default`.
+pub fn rotate_about_center_no_crop
(
+ image: &Image
,
+ theta: f32,
+ interpolation: Interpolation,
+ default: P,
+) -> Image
+where
+ P: Pixel + Send + Sync,
+
::Subpixel: Send + Sync,
+
::Subpixel: Into + Clamp,
+{
+ let (width, height) = image.dimensions();
+
+ let cos = theta.cos();
+ let sin = theta.sin();
+
+ let new_width = (height as f32 * sin.abs() + width as f32 * cos.abs()).ceil() as u32;
+ let new_height = (height as f32 * cos.abs() + width as f32 * sin.abs()).ceil() as u32;
+
+ let mut out_img = Image::new(new_width, new_height);
+
+ rotate_about_center_into(image, theta, interpolation, default, &mut out_img);
+
+ out_img
+}
+
+/// Rotates an image clockwise about its center by theta radians, writing to a provided output.
+/// Output pixels whose pre-image lies outside the input image are set to `default`.
+fn rotate_about_center_into(
+ image: &Image
,
+ theta: f32,
+ interpolation: Interpolation,
+ default: P,
+ out: &mut Image
,
+) where
+ P: Pixel + Send + Sync,
+
::Subpixel: Send + Sync,
+
::Subpixel: Into + Clamp,
+{
+ let (w, h) = image.dimensions();
+ let (ow, oh) = out.dimensions();
+ rotate_into(
+ image,
+ (w as f32 / 2.0, h as f32 / 2.0),
+ (ow as f32 / 2.0, oh as f32 / 2.0),
+ theta,
+ interpolation,
+ default,
+ out,
+ )
+}
+
+/// Rotates an image clockwise about the provided center by theta radians, writing to a provided output.
+/// Output pixels whose pre-image lies outside the input image are set to `default`.
+fn rotate_into(
+ image: &Image
,
+ center: (f32, f32),
+ out_center: (f32, f32),
+ theta: f32,
+ interpolation: Interpolation,
+ default: P,
+ out: &mut Image
,
+) where
+ P: Pixel + Send + Sync,
+
::Subpixel: Send + Sync,
+
::Subpixel: Into + Clamp,
+{
+ let (cx, cy) = center;
+ let (ocx, ocy) = out_center;
+ let projection = Projection::translate(ocx, ocy)
+ * Projection::rotate(theta)
+ * Projection::translate(-cx, -cy);
+
+ warp_into(image, &projection, interpolation, default, out);
+}
+
/// Rotates an image 90 degrees clockwise.
///
/// # Examples
@@ -882,7 +961,8 @@ pub enum Interpolation {
#[cfg(test)]
mod tests {
use super::*;
- use image::Luma;
+ use image::{GrayImage, Luma};
+ use std::f32::consts::PI;
#[test]
fn test_rotate_nearest_zero_radians() {
@@ -901,7 +981,7 @@ mod tests {
}
#[test]
- fn text_rotate_nearest_quarter_turn_clockwise() {
+ fn test_rotate_nearest_quarter_turn_clockwise() {
let image = gray_image!(
00, 01, 02;
10, 11, 12);
@@ -917,7 +997,7 @@ mod tests {
}
#[test]
- fn text_rotate_nearest_half_turn_anticlockwise() {
+ fn test_rotate_nearest_half_turn_anticlockwise() {
let image = gray_image!(
00, 01, 02;
10, 11, 12);
diff --git a/tests/data/truth/elephant_rotate_no_crop_bicubic.png b/tests/data/truth/elephant_rotate_no_crop_bicubic.png
new file mode 100644
index 00000000..c7c89919
Binary files /dev/null and b/tests/data/truth/elephant_rotate_no_crop_bicubic.png differ
diff --git a/tests/data/truth/elephant_rotate_no_crop_bicubic_rgba.png b/tests/data/truth/elephant_rotate_no_crop_bicubic_rgba.png
new file mode 100644
index 00000000..317a133f
Binary files /dev/null and b/tests/data/truth/elephant_rotate_no_crop_bicubic_rgba.png differ
diff --git a/tests/data/truth/elephant_rotate_no_crop_bilinear.png b/tests/data/truth/elephant_rotate_no_crop_bilinear.png
new file mode 100644
index 00000000..d601a11f
Binary files /dev/null and b/tests/data/truth/elephant_rotate_no_crop_bilinear.png differ
diff --git a/tests/data/truth/elephant_rotate_no_crop_bilinear_rgba.png b/tests/data/truth/elephant_rotate_no_crop_bilinear_rgba.png
new file mode 100644
index 00000000..cac81b3e
Binary files /dev/null and b/tests/data/truth/elephant_rotate_no_crop_bilinear_rgba.png differ
diff --git a/tests/data/truth/elephant_rotate_no_crop_nearest.png b/tests/data/truth/elephant_rotate_no_crop_nearest.png
new file mode 100644
index 00000000..b7e8b83e
Binary files /dev/null and b/tests/data/truth/elephant_rotate_no_crop_nearest.png differ
diff --git a/tests/data/truth/elephant_rotate_no_crop_nearest_default_red_rgba.png b/tests/data/truth/elephant_rotate_no_crop_nearest_default_red_rgba.png
new file mode 100644
index 00000000..c1cc1027
Binary files /dev/null and b/tests/data/truth/elephant_rotate_no_crop_nearest_default_red_rgba.png differ
diff --git a/tests/data/truth/elephant_rotate_no_crop_nearest_rgba.png b/tests/data/truth/elephant_rotate_no_crop_nearest_rgba.png
new file mode 100644
index 00000000..817f6c9b
Binary files /dev/null and b/tests/data/truth/elephant_rotate_no_crop_nearest_rgba.png differ
diff --git a/tests/regression.rs b/tests/regression.rs
index a8e478df..af0c6667 100644
--- a/tests/regression.rs
+++ b/tests/regression.rs
@@ -25,6 +25,7 @@ use imageproc::contrast::ThresholdType;
use imageproc::definitions::Image;
use imageproc::filter::bilateral::GaussianEuclideanColorDistance;
use imageproc::filter::bilateral_filter;
+use imageproc::geometric_transformations::rotate_about_center_no_crop;
use imageproc::kernel::{self};
use imageproc::{
definitions::{Clamp, HasBlack, HasWhite},
@@ -142,6 +143,23 @@ fn test_rotate_nearest_rgb() {
);
}
+#[test]
+fn test_rotate_no_crop_nearest_rgb() {
+ fn rotate_nearest_about_center_no_crop(image: &RgbImage) -> RgbImage {
+ rotate_about_center_no_crop(
+ image,
+ std::f32::consts::PI / 4f32,
+ Interpolation::Nearest,
+ Rgb::black(),
+ )
+ }
+ compare_to_truth(
+ "elephant.png",
+ "elephant_rotate_no_crop_nearest.png",
+ rotate_nearest_about_center_no_crop,
+ );
+}
+
#[test]
fn test_rotate_nearest_rgba() {
fn rotate_nearest_about_center(image: &RgbaImage) -> RgbaImage {
@@ -159,6 +177,23 @@ fn test_rotate_nearest_rgba() {
);
}
+#[test]
+fn test_rotate_no_crop_nearest_rgba() {
+ fn rotate_nearest_about_center_no_crop(image: &RgbaImage) -> RgbaImage {
+ rotate_about_center_no_crop(
+ image,
+ std::f32::consts::PI / 4f32,
+ Interpolation::Nearest,
+ Rgba::black(),
+ )
+ }
+ compare_to_truth(
+ "elephant_rgba.png",
+ "elephant_rotate_no_crop_nearest_rgba.png",
+ rotate_nearest_about_center_no_crop,
+ );
+}
+
#[test]
fn test_equalize_histogram_grayscale() {
use imageproc::contrast::equalize_histogram;
@@ -183,6 +218,24 @@ fn test_rotate_bilinear_rgb() {
);
}
+#[test]
+fn test_rotate_bilinear_no_crop_rgb() {
+ fn rotate_bilinear_about_center_no_crop(image: &RgbImage) -> RgbImage {
+ rotate_about_center_no_crop(
+ image,
+ std::f32::consts::PI / 4f32,
+ Interpolation::Bilinear,
+ Rgb::black(),
+ )
+ }
+ compare_to_truth_with_tolerance(
+ "elephant.png",
+ "elephant_rotate_no_crop_bilinear.png",
+ rotate_bilinear_about_center_no_crop,
+ 2,
+ );
+}
+
#[test]
fn test_rotate_bilinear_rgba() {
fn rotate_bilinear_about_center(image: &RgbaImage) -> RgbaImage {
@@ -201,6 +254,24 @@ fn test_rotate_bilinear_rgba() {
);
}
+#[test]
+fn test_rotate_no_crop_bilinear_rgba() {
+ fn rotate_bilinear_about_center_no_crop(image: &RgbaImage) -> RgbaImage {
+ rotate_about_center_no_crop(
+ image,
+ std::f32::consts::PI / 4f32,
+ Interpolation::Bilinear,
+ Rgba::black(),
+ )
+ }
+ compare_to_truth_with_tolerance(
+ "elephant_rgba.png",
+ "elephant_rotate_no_crop_bilinear_rgba.png",
+ rotate_bilinear_about_center_no_crop,
+ 2,
+ );
+}
+
#[test]
fn test_rotate_bicubic_rgb() {
fn rotate_bicubic_about_center(image: &RgbImage) -> RgbImage {
@@ -219,6 +290,24 @@ fn test_rotate_bicubic_rgb() {
);
}
+#[test]
+fn test_rotate_no_crop_bicubic_rgb() {
+ fn rotate_bicubic_about_center_no_crop(image: &RgbImage) -> RgbImage {
+ rotate_about_center_no_crop(
+ image,
+ std::f32::consts::PI / 4f32,
+ Interpolation::Bicubic,
+ Rgb::black(),
+ )
+ }
+ compare_to_truth_with_tolerance(
+ "elephant.png",
+ "elephant_rotate_no_crop_bicubic.png",
+ rotate_bicubic_about_center_no_crop,
+ 2,
+ );
+}
+
#[test]
fn test_rotate_bicubic_rgba() {
fn rotate_bicubic_about_center(image: &RgbaImage) -> RgbaImage {
@@ -237,6 +326,42 @@ fn test_rotate_bicubic_rgba() {
);
}
+#[test]
+fn test_rotate_no_crop_bicubic_rgba() {
+ fn rotate_bicubic_about_center_no_crop(image: &RgbaImage) -> RgbaImage {
+ rotate_about_center_no_crop(
+ image,
+ std::f32::consts::PI / 4f32,
+ Interpolation::Bicubic,
+ Rgba::black(),
+ )
+ }
+ compare_to_truth_with_tolerance(
+ "elephant_rgba.png",
+ "elephant_rotate_no_crop_bicubic_rgba.png",
+ rotate_bicubic_about_center_no_crop,
+ 2,
+ );
+}
+
+#[test]
+fn test_rotate_no_crop_default_color() {
+ fn rotate_nearest_about_center_no_crop_default_red(image: &RgbaImage) -> RgbaImage {
+ rotate_about_center_no_crop(
+ image,
+ std::f32::consts::PI / 4f32,
+ Interpolation::Nearest,
+ Rgba([255, 0, 0, 255]),
+ )
+ }
+ compare_to_truth_with_tolerance(
+ "elephant_rgba.png",
+ "elephant_rotate_no_crop_nearest_default_red_rgba.png",
+ rotate_nearest_about_center_no_crop_default_red,
+ 2,
+ )
+}
+
#[test]
fn test_affine_nearest_rgb() {
fn affine_nearest(image: &RgbImage) -> RgbImage {