diff --git a/src/geometric_transformations.rs b/src/geometric_transformations.rs index c6b12d7a..9ede1be8 100644 --- a/src/geometric_transformations.rs +++ b/src/geometric_transformations.rs @@ -1,8 +1,11 @@ //! Geometric transformations of images. This includes rotations, translation, and general //! projective transformations. -use crate::definitions::{Clamp, Image}; -use image::{GenericImageView, Pixel}; +use crate::{ + compose::crop, + definitions::{Clamp, Image}, +}; +use image::{math::Rect, GenericImageView, Pixel}; #[cfg(feature = "rayon")] use rayon::prelude::*; use std::{cmp, ops::Mul}; @@ -99,7 +102,6 @@ impl Projection { /// An anisotropic scaling (sx, sy). /// /// Note that the `warp` function does not change the size of the input image. - /// If you want to resize an image then use the `imageops` module in the `image` crate. #[rustfmt::skip] pub fn scale(sx: f32, sy: f32) -> Projection { Projection { @@ -317,6 +319,199 @@ where warp(image, &projection, interpolation, default) } +/// Resizes an image to the exact given `new_width` and `new_height`, +/// ignoring aspect ratio, using the given `interpolation` method. +/// +/// # Examples +/// ``` +/// use imageproc::geometric_transformations::{resize_exact, Interpolation}; +/// use imageproc::gray_image; +/// +/// let image = gray_image!( +/// 0, 0, 0, 0; +/// 0, 0, 0, 0; +/// 0, 0, 0, 0; +/// 0, 0, 0, 0); +/// +/// let resized = resize_exact(&image, 2, 3, Interpolation::Bilinear); +/// +/// assert_eq!(resized, gray_image!( +/// 0, 0; +/// 0, 0; +/// 0, 0)); +/// ``` +pub fn resize_exact

( + image: &Image

, + new_width: u32, + new_height: u32, + interpolation: Interpolation, +) -> Image

+where + P: Pixel, +

::Subpixel: Into + Clamp, +{ + if new_width == image.width() && new_height == image.height() { + return image.clone(); + } + + let x_ratio: f32 = image.width() as f32 / new_width as f32; + let y_ratio: f32 = image.height() as f32 / new_height as f32; + + Image::from_fn(new_width, new_height, |x, y| { + interpolate( + image, + x as f32 * x_ratio, + y as f32 * y_ratio, + //TODO make this P::default() once the common `Pixel` + //types implement default or when `rgb` v0.9 is + //implemented https://github.com/image-rs/image/issues/2259 + *P::from_slice(&vec![ + ::DEFAULT_MIN_VALUE; + usize::from(P::CHANNEL_COUNT) + ]), + interpolation, + ) + }) +} +/// Resizes an image to the exact given `new_width` and `new_height` +/// while maintaining the images aspect ratio by cropping +/// ignoring aspect ratio, using the given `interpolation` method. +/// +/// # Examples +/// ``` +/// use imageproc::geometric_transformations::{resize_crop_to_fill, Interpolation}; +/// use imageproc::gray_image; +/// +/// let image = gray_image!( +/// 0, 0, 0, 0; +/// 0, 0, 0, 0; +/// 0, 0, 0, 0; +/// 0, 0, 0, 0); +/// +/// let resized = resize_crop_to_fill(&image, 2, 3, Interpolation::Bilinear); +/// +/// assert_eq!(resized, gray_image!( +/// 0, 0; +/// 0, 0; +/// 0, 0)); +/// ``` +pub fn resize_crop_to_fill

( + image: &Image

, + new_width: u32, + new_height: u32, + interpolation: Interpolation, +) -> Image

+where + P: Pixel, +

::Subpixel: Into + Clamp, +{ + let (intermediate_width, intermediate_height) = + resize_dimensions(image.width(), image.height(), new_width, new_height, true); + + let intermediate = resize_exact( + image, + intermediate_width, + intermediate_height, + interpolation, + ); + + let horizontal_ratio = u64::from(intermediate_width) * u64::from(new_height); + let vertical_ratio = u64::from(new_width) * u64::from(intermediate_height); + + if vertical_ratio > horizontal_ratio { + crop( + &intermediate, + Rect { + x: 0, + y: (intermediate_height - new_height) / 2, + width: new_width, + height: new_height, + }, + ) + } else { + crop( + &intermediate, + Rect { + x: (intermediate_width - new_width) / 2, + y: 0, + width: new_width, + height: new_height, + }, + ) + } +} +/// Resizes an image to the largest dimensions smaller or equal to the +/// given `max_width` and `max_height`, while maintaining the images +/// aspect ratio without cropping the image, using the given +/// interopolation method.` +/// +/// Importantly the returned image will not necessarily have the same +/// dimensions as the given `max_width` and `max_height`. +/// +/// # Examples +/// ``` +/// use imageproc::geometric_transformations::{resize_max_no_crop, Interpolation}; +/// use imageproc::gray_image; +/// +/// let image = gray_image!( +/// 0, 0, 0, 0; +/// 0, 0, 0, 0; +/// 0, 0, 0, 0; +/// 0, 0, 0, 0); +/// +/// let resized = resize_max_no_crop(&image, 2, 3, Interpolation::Bilinear); +/// +/// assert_eq!(resized, gray_image!( +/// 0, 0; +/// 0, 0)); +/// ``` +pub fn resize_max_no_crop

( + image: &Image

, + max_width: u32, + max_height: u32, + interpolation: Interpolation, +) -> Image

+where + P: Pixel, +

::Subpixel: Into + Clamp, +{ + let (new_width, new_height) = + resize_dimensions(image.width(), image.height(), max_width, max_height, false); + + resize_exact(image, new_width, new_height, interpolation) +} +/// used by [`resize_max_no_crop()`] and [`resize_crop_to_fill()`], +/// see them for details. +fn resize_dimensions(width: u32, height: u32, nwidth: u32, nheight: u32, fill: bool) -> (u32, u32) { + let wratio = nwidth as f64 / width as f64; + let hratio = nheight as f64 / height as f64; + + let ratio = if fill { + f64::max(wratio, hratio) + } else { + f64::min(wratio, hratio) + }; + + let nw = core::cmp::max((width as f64 * ratio).round() as u64, 1); + let nh = core::cmp::max((height as f64 * ratio).round() as u64, 1); + + if nw > u64::from(u32::MAX) { + let ratio = u32::MAX as f64 / width as f64; + ( + u32::MAX, + core::cmp::max((height as f64 * ratio).round() as u32, 1), + ) + } else if nh > u64::from(u32::MAX) { + let ratio = u32::MAX as f64 / height as f64; + ( + core::cmp::max((width as f64 * ratio).round() as u32, 1), + u32::MAX, + ) + } else { + (nw as u32, nh as u32) + } +} + /// Rotates an image 90 degrees clockwise. /// /// # Examples @@ -878,6 +1073,28 @@ pub enum Interpolation { /// closest to the pre-image of the output pixel. Bicubic, } +/// Returns an interpolated pixel from an image at the given pixel +/// coordinates using the given `interpolation`. +/// +/// If the pixel is out of bounds of `image` then the `default` pixel +/// value is used. +pub fn interpolate

( + image: &Image

, + x: f32, + y: f32, + default: P, + interpolation: Interpolation, +) -> P +where + P: Pixel, +

::Subpixel: Into + Clamp, +{ + match interpolation { + Interpolation::Nearest => interpolate_nearest(image, x, y, default), + Interpolation::Bilinear => interpolate_bilinear(image, x, y, default), + Interpolation::Bicubic => interpolate_bicubic(image, x, y, default), + } +} #[cfg(test)] mod tests {