use crate::core::{Pix, PixMut, PixelDepth, pixel};
use crate::transform::shear::{ShearFill, h_shear_ip, v_shear_ip};
use crate::transform::{TransformError, TransformResult};
const MIN_ANGLE_TO_ROTATE: f32 = 0.001; const MAX_TWO_SHEAR_ANGLE: f32 = 0.06; #[allow(dead_code)]
const MAX_THREE_SHEAR_ANGLE: f32 = 0.35; const MAX_SHEAR_ANGLE: f32 = 0.50; const MAX_1BPP_SHEAR_ANGLE: f32 = 0.06;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum RotateMethod {
Sampling,
AreaMap,
Shear,
Bilinear,
#[default]
Auto,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum RotateFill {
#[default]
White,
Black,
Color(u32),
}
impl RotateFill {
pub fn to_value(self, depth: PixelDepth) -> u32 {
match self {
RotateFill::White => match depth {
PixelDepth::Bit1 => 0, PixelDepth::Bit2 => 3,
PixelDepth::Bit4 => 15,
PixelDepth::Bit8 => 255,
PixelDepth::Bit16 => 65535,
PixelDepth::Bit32 => 0xFFFFFF00,
},
RotateFill::Black => match depth {
PixelDepth::Bit1 => 1, PixelDepth::Bit32 => 0x00000000,
_ => 0,
},
RotateFill::Color(val) => val,
}
}
}
#[derive(Debug, Clone)]
pub struct RotateOptions {
pub method: RotateMethod,
pub fill: RotateFill,
pub center_x: Option<f32>,
pub center_y: Option<f32>,
pub expand: bool,
}
impl Default for RotateOptions {
fn default() -> Self {
Self {
method: RotateMethod::Auto,
fill: RotateFill::White,
center_x: None,
center_y: None,
expand: true,
}
}
}
impl RotateOptions {
pub fn with_method(method: RotateMethod) -> Self {
Self {
method,
..Default::default()
}
}
pub fn with_fill(fill: RotateFill) -> Self {
Self {
fill,
..Default::default()
}
}
pub fn center(mut self, x: f32, y: f32) -> Self {
self.center_x = Some(x);
self.center_y = Some(y);
self
}
pub fn expand(mut self, expand: bool) -> Self {
self.expand = expand;
self
}
}
pub fn rotate_orth(pix: &Pix, quads: u32) -> TransformResult<Pix> {
match quads % 4 {
0 => Ok(pix.deep_clone()),
1 => rotate_90(pix, true),
2 => rotate_180(pix),
3 => rotate_90(pix, false),
_ => unreachable!(),
}
}
pub fn rotate_90(pix: &Pix, clockwise: bool) -> TransformResult<Pix> {
let w = pix.width();
let h = pix.height();
let depth = pix.depth();
let out_pix = Pix::new(h, w, depth)?;
let mut out_mut = out_pix.try_into_mut().unwrap();
if let Some(cmap) = pix.colormap() {
let _ = out_mut.set_colormap(Some(cmap.clone()));
}
rotate_90_impl(pix, &mut out_mut, clockwise, w, h);
Ok(out_mut.into())
}
fn rotate_90_impl(src: &Pix, dst: &mut PixMut, clockwise: bool, w: u32, h: u32) {
for y in 0..h {
for x in 0..w {
let val = src.get_pixel_unchecked(x, y);
let (nx, ny) = if clockwise {
(h - 1 - y, x)
} else {
(y, w - 1 - x)
};
dst.set_pixel_unchecked(nx, ny, val);
}
}
}
pub fn rotate_180(pix: &Pix) -> TransformResult<Pix> {
let flipped_h = flip_lr(pix)?;
flip_tb(&flipped_h)
}
pub fn flip_lr(pix: &Pix) -> TransformResult<Pix> {
let w = pix.width();
let h = pix.height();
let depth = pix.depth();
let out_pix = Pix::new(w, h, depth)?;
let mut out_mut = out_pix.try_into_mut().unwrap();
if let Some(cmap) = pix.colormap() {
let _ = out_mut.set_colormap(Some(cmap.clone()));
}
for y in 0..h {
for x in 0..w {
let val = pix.get_pixel_unchecked(x, y);
let nx = w - 1 - x;
out_mut.set_pixel_unchecked(nx, y, val);
}
}
Ok(out_mut.into())
}
pub fn flip_tb(pix: &Pix) -> TransformResult<Pix> {
let w = pix.width();
let h = pix.height();
let depth = pix.depth();
let out_pix = Pix::new(w, h, depth)?;
let mut out_mut = out_pix.try_into_mut().unwrap();
if let Some(cmap) = pix.colormap() {
let _ = out_mut.set_colormap(Some(cmap.clone()));
}
for y in 0..h {
for x in 0..w {
let val = pix.get_pixel_unchecked(x, y);
let ny = h - 1 - y;
out_mut.set_pixel_unchecked(x, ny, val);
}
}
Ok(out_mut.into())
}
pub fn rotate_180_in_place(pix: &mut PixMut) -> TransformResult<()> {
let w = pix.width();
let h = pix.height();
let total_pixels = (w as u64) * (h as u64);
let half = total_pixels / 2;
for i in 0..half {
let x1 = (i % (w as u64)) as u32;
let y1 = (i / (w as u64)) as u32;
let x2 = w - 1 - x1;
let y2 = h - 1 - y1;
let val1 = pix.get_pixel_unchecked(x1, y1);
let val2 = pix.get_pixel_unchecked(x2, y2);
pix.set_pixel_unchecked(x1, y1, val2);
pix.set_pixel_unchecked(x2, y2, val1);
}
Ok(())
}
pub fn rotate_by_angle(pix: &Pix, angle: f32) -> TransformResult<Pix> {
let fill_value = get_default_fill_value(pix.depth());
rotate_by_angle_with_options(pix, angle, fill_value)
}
pub fn rotate_by_radians(pix: &Pix, radians: f32) -> TransformResult<Pix> {
let angle = radians.to_degrees();
rotate_by_angle(pix, angle)
}
pub fn rotate_by_angle_with_options(
pix: &Pix,
angle: f32,
fill_value: u32,
) -> TransformResult<Pix> {
let angle = angle % 360.0;
let angle = if angle < 0.0 { angle + 360.0 } else { angle };
if (angle - 0.0).abs() < 0.001 || (angle - 360.0).abs() < 0.001 {
return Ok(pix.deep_clone());
}
if (angle - 90.0).abs() < 0.001 {
return rotate_90(pix, false); }
if (angle - 180.0).abs() < 0.001 {
return rotate_180(pix);
}
if (angle - 270.0).abs() < 0.001 {
return rotate_90(pix, true); }
let radians = angle.to_radians();
let cos_a = radians.cos();
let sin_a = radians.sin();
let w = pix.width() as f32;
let h = pix.height() as f32;
let (new_w, new_h) = calculate_rotated_bounds(w, h, cos_a, sin_a);
let new_w = new_w as u32;
let new_h = new_h as u32;
let out_pix = Pix::new(new_w, new_h, pix.depth())?;
let mut out_mut = out_pix.try_into_mut().unwrap();
if let Some(cmap) = pix.colormap() {
let _ = out_mut.set_colormap(Some(cmap.clone()));
}
fill_image(&mut out_mut, fill_value);
let cx_src = w / 2.0;
let cy_src = h / 2.0;
let cx_dst = new_w as f32 / 2.0;
let cy_dst = new_h as f32 / 2.0;
match pix.depth() {
PixelDepth::Bit1 => {
rotate_nearest_neighbor(
pix,
&mut out_mut,
cos_a,
sin_a,
cx_src,
cy_src,
cx_dst,
cy_dst,
);
}
PixelDepth::Bit8 | PixelDepth::Bit16 | PixelDepth::Bit32 => {
rotate_bilinear(
pix,
&mut out_mut,
cos_a,
sin_a,
cx_src,
cy_src,
cx_dst,
cy_dst,
);
}
_ => {
rotate_nearest_neighbor(
pix,
&mut out_mut,
cos_a,
sin_a,
cx_src,
cy_src,
cx_dst,
cy_dst,
);
}
}
Ok(out_mut.into())
}
fn get_default_fill_value(depth: PixelDepth) -> u32 {
match depth {
PixelDepth::Bit1 => 0, PixelDepth::Bit2 => 3,
PixelDepth::Bit4 => 15,
PixelDepth::Bit8 => 255,
PixelDepth::Bit16 => 65535,
PixelDepth::Bit32 => 0xFFFFFFFF,
}
}
fn calculate_rotated_bounds(w: f32, h: f32, cos_a: f32, sin_a: f32) -> (f32, f32) {
let corners = [
(-w / 2.0, -h / 2.0),
(w / 2.0, -h / 2.0),
(w / 2.0, h / 2.0),
(-w / 2.0, h / 2.0),
];
let mut min_x = f32::MAX;
let mut max_x = f32::MIN;
let mut min_y = f32::MAX;
let mut max_y = f32::MIN;
for (x, y) in corners {
let rx = x * cos_a - y * sin_a;
let ry = x * sin_a + y * cos_a;
min_x = min_x.min(rx);
max_x = max_x.max(rx);
min_y = min_y.min(ry);
max_y = max_y.max(ry);
}
((max_x - min_x).ceil(), (max_y - min_y).ceil())
}
fn fill_image(pix: &mut PixMut, value: u32) {
let w = pix.width();
let h = pix.height();
for y in 0..h {
for x in 0..w {
pix.set_pixel_unchecked(x, y, value);
}
}
}
#[allow(clippy::too_many_arguments)]
fn rotate_nearest_neighbor(
src: &Pix,
dst: &mut PixMut,
cos_a: f32,
sin_a: f32,
cx_src: f32,
cy_src: f32,
cx_dst: f32,
cy_dst: f32,
) {
let src_w = src.width() as i32;
let src_h = src.height() as i32;
let dst_w = dst.width();
let dst_h = dst.height();
for dy in 0..dst_h {
for dx in 0..dst_w {
let x_rel = dx as f32 - cx_dst;
let y_rel = dy as f32 - cy_dst;
let sx = x_rel * cos_a + y_rel * sin_a + cx_src;
let sy = -x_rel * sin_a + y_rel * cos_a + cy_src;
let sx_i = sx.round() as i32;
let sy_i = sy.round() as i32;
if sx_i >= 0 && sx_i < src_w && sy_i >= 0 && sy_i < src_h {
let val = src.get_pixel_unchecked(sx_i as u32, sy_i as u32);
dst.set_pixel_unchecked(dx, dy, val);
}
}
}
}
#[allow(clippy::too_many_arguments)]
fn rotate_bilinear(
src: &Pix,
dst: &mut PixMut,
cos_a: f32,
sin_a: f32,
cx_src: f32,
cy_src: f32,
cx_dst: f32,
cy_dst: f32,
) {
let src_w = src.width() as i32;
let src_h = src.height() as i32;
let dst_w = dst.width();
let dst_h = dst.height();
let depth = src.depth();
for dy in 0..dst_h {
for dx in 0..dst_w {
let x_rel = dx as f32 - cx_dst;
let y_rel = dy as f32 - cy_dst;
let sx = x_rel * cos_a + y_rel * sin_a + cx_src;
let sy = -x_rel * sin_a + y_rel * cos_a + cy_src;
let x0 = sx.floor() as i32;
let y0 = sy.floor() as i32;
let x1 = x0 + 1;
let y1 = y0 + 1;
if x0 >= 0 && x1 < src_w && y0 >= 0 && y1 < src_h {
let fx = sx - x0 as f32;
let fy = sy - y0 as f32;
let val = interpolate_pixel(
src, depth, x0 as u32, y0 as u32, x1 as u32, y1 as u32, fx, fy,
);
dst.set_pixel_unchecked(dx, dy, val);
} else if x0 >= -1 && x1 <= src_w && y0 >= -1 && y1 <= src_h {
let val = interpolate_edge_pixel(src, depth, x0, y0, x1, y1, sx, sy, src_w, src_h);
dst.set_pixel_unchecked(dx, dy, val);
}
}
}
}
#[allow(clippy::too_many_arguments)]
fn interpolate_pixel(
src: &Pix,
depth: PixelDepth,
x0: u32,
y0: u32,
x1: u32,
y1: u32,
fx: f32,
fy: f32,
) -> u32 {
let p00 = src.get_pixel_unchecked(x0, y0);
let p10 = src.get_pixel_unchecked(x1, y0);
let p01 = src.get_pixel_unchecked(x0, y1);
let p11 = src.get_pixel_unchecked(x1, y1);
match depth {
PixelDepth::Bit32 => {
let r = interpolate_channel(
(p00 >> 24) & 0xFF,
(p10 >> 24) & 0xFF,
(p01 >> 24) & 0xFF,
(p11 >> 24) & 0xFF,
fx,
fy,
);
let g = interpolate_channel(
(p00 >> 16) & 0xFF,
(p10 >> 16) & 0xFF,
(p01 >> 16) & 0xFF,
(p11 >> 16) & 0xFF,
fx,
fy,
);
let b = interpolate_channel(
(p00 >> 8) & 0xFF,
(p10 >> 8) & 0xFF,
(p01 >> 8) & 0xFF,
(p11 >> 8) & 0xFF,
fx,
fy,
);
let a = interpolate_channel(p00 & 0xFF, p10 & 0xFF, p01 & 0xFF, p11 & 0xFF, fx, fy);
(r << 24) | (g << 16) | (b << 8) | a
}
_ => {
interpolate_channel(p00, p10, p01, p11, fx, fy)
}
}
}
fn interpolate_channel(p00: u32, p10: u32, p01: u32, p11: u32, fx: f32, fy: f32) -> u32 {
let top = p00 as f32 * (1.0 - fx) + p10 as f32 * fx;
let bottom = p01 as f32 * (1.0 - fx) + p11 as f32 * fx;
let result = top * (1.0 - fy) + bottom * fy;
result.round() as u32
}
#[allow(clippy::too_many_arguments)]
fn interpolate_edge_pixel(
src: &Pix,
depth: PixelDepth,
x0: i32,
y0: i32,
x1: i32,
y1: i32,
sx: f32,
sy: f32,
src_w: i32,
src_h: i32,
) -> u32 {
let clamp_x = |x: i32| x.clamp(0, src_w - 1) as u32;
let clamp_y = |y: i32| y.clamp(0, src_h - 1) as u32;
let p00 = src.get_pixel_unchecked(clamp_x(x0), clamp_y(y0));
let p10 = src.get_pixel_unchecked(clamp_x(x1), clamp_y(y0));
let p01 = src.get_pixel_unchecked(clamp_x(x0), clamp_y(y1));
let p11 = src.get_pixel_unchecked(clamp_x(x1), clamp_y(y1));
let fx = sx - x0 as f32;
let fy = sy - y0 as f32;
match depth {
PixelDepth::Bit32 => {
let r = interpolate_channel(
(p00 >> 24) & 0xFF,
(p10 >> 24) & 0xFF,
(p01 >> 24) & 0xFF,
(p11 >> 24) & 0xFF,
fx,
fy,
);
let g = interpolate_channel(
(p00 >> 16) & 0xFF,
(p10 >> 16) & 0xFF,
(p01 >> 16) & 0xFF,
(p11 >> 16) & 0xFF,
fx,
fy,
);
let b = interpolate_channel(
(p00 >> 8) & 0xFF,
(p10 >> 8) & 0xFF,
(p01 >> 8) & 0xFF,
(p11 >> 8) & 0xFF,
fx,
fy,
);
let a = interpolate_channel(p00 & 0xFF, p10 & 0xFF, p01 & 0xFF, p11 & 0xFF, fx, fy);
(r << 24) | (g << 16) | (b << 8) | a
}
_ => interpolate_channel(p00, p10, p01, p11, fx, fy),
}
}
pub fn rotate(pix: &Pix, angle: f32, options: &RotateOptions) -> TransformResult<Pix> {
if angle.abs() < MIN_ANGLE_TO_ROTATE {
return Ok(pix.deep_clone());
}
let depth = pix.depth();
let fill_value = options.fill.to_value(depth);
let method = select_rotate_method(options.method, depth, angle);
let w = pix.width() as f32;
let h = pix.height() as f32;
let cos_a = angle.cos();
let sin_a = angle.sin();
let (out_w, out_h) = if options.expand {
let (nw, nh) = calculate_rotated_bounds(w, h, cos_a, sin_a);
(nw as u32, nh as u32)
} else {
(pix.width(), pix.height())
};
let cx_src = options.center_x.unwrap_or(w / 2.0);
let cy_src = options.center_y.unwrap_or(h / 2.0);
let cx_dst = if options.expand {
out_w as f32 / 2.0
} else {
cx_src
};
let cy_dst = if options.expand {
out_h as f32 / 2.0
} else {
cy_src
};
let out_pix = Pix::new(out_w, out_h, depth)?;
let mut out_mut = out_pix.try_into_mut().unwrap();
if let Some(cmap) = pix.colormap() {
let _ = out_mut.set_colormap(Some(cmap.clone()));
}
fill_image(&mut out_mut, fill_value);
match method {
RotateMethod::Sampling => {
rotate_by_sampling_impl(
pix,
&mut out_mut,
cos_a,
sin_a,
cx_src,
cy_src,
cx_dst,
cy_dst,
fill_value,
);
}
RotateMethod::AreaMap => {
rotate_area_map_impl(
pix,
&mut out_mut,
cos_a,
sin_a,
cx_src,
cy_src,
cx_dst,
cy_dst,
fill_value,
);
}
RotateMethod::Shear => {
rotate_shear_impl(
pix,
&mut out_mut,
angle,
cx_src as i32,
cy_src as i32,
fill_value,
);
}
RotateMethod::Bilinear => {
rotate_bilinear(
pix,
&mut out_mut,
cos_a,
sin_a,
cx_src,
cy_src,
cx_dst,
cy_dst,
);
}
RotateMethod::Auto => unreachable!(),
}
Ok(out_mut.into())
}
pub fn rotate_with_method(pix: &Pix, angle: f32, method: RotateMethod) -> TransformResult<Pix> {
let options = RotateOptions {
method,
..Default::default()
};
rotate(pix, angle, &options)
}
pub fn rotate_about_center(
pix: &Pix,
angle: f32,
center_x: f32,
center_y: f32,
fill: RotateFill,
) -> TransformResult<Pix> {
let options = RotateOptions {
fill,
center_x: Some(center_x),
center_y: Some(center_y),
expand: false, ..Default::default()
};
rotate(pix, angle, &options)
}
fn select_rotate_method(method: RotateMethod, depth: PixelDepth, angle: f32) -> RotateMethod {
match method {
RotateMethod::Auto => {
let abs_angle = angle.abs();
if depth == PixelDepth::Bit1 {
if abs_angle > MAX_1BPP_SHEAR_ANGLE {
RotateMethod::Sampling
} else {
RotateMethod::Shear
}
}
else if abs_angle > MAX_SHEAR_ANGLE {
RotateMethod::Sampling
} else {
match depth {
PixelDepth::Bit8 | PixelDepth::Bit32 => RotateMethod::AreaMap,
_ => RotateMethod::Sampling,
}
}
}
RotateMethod::AreaMap => {
if depth == PixelDepth::Bit1
|| depth == PixelDepth::Bit2
|| depth == PixelDepth::Bit4
|| depth == PixelDepth::Bit16
{
RotateMethod::Sampling
} else {
RotateMethod::AreaMap
}
}
RotateMethod::Shear => {
if angle.abs() > MAX_SHEAR_ANGLE {
RotateMethod::Sampling
} else {
RotateMethod::Shear
}
}
other => other,
}
}
#[allow(clippy::too_many_arguments)]
fn rotate_by_sampling_impl(
src: &Pix,
dst: &mut PixMut,
cos_a: f32,
sin_a: f32,
cx_src: f32,
cy_src: f32,
cx_dst: f32,
cy_dst: f32,
_fill_value: u32,
) {
let src_w = src.width() as i32;
let src_h = src.height() as i32;
let dst_w = dst.width();
let dst_h = dst.height();
let wm1 = src_w - 1;
let hm1 = src_h - 1;
for i in 0..dst_h {
let ydif = cy_dst - i as f32;
for j in 0..dst_w {
let xdif = cx_dst - j as f32;
let x = (cx_src + (-xdif * cos_a - ydif * sin_a)).round() as i32;
let y = (cy_src + (-ydif * cos_a + xdif * sin_a)).round() as i32;
if x >= 0 && x <= wm1 && y >= 0 && y <= hm1 {
let val = src.get_pixel_unchecked(x as u32, y as u32);
dst.set_pixel_unchecked(j, i, val);
}
}
}
}
#[allow(clippy::too_many_arguments)]
fn rotate_area_map_impl(
src: &Pix,
dst: &mut PixMut,
cos_a: f32,
sin_a: f32,
cx_src: f32,
cy_src: f32,
cx_dst: f32,
cy_dst: f32,
fill_value: u32,
) {
let depth = src.depth();
match depth {
PixelDepth::Bit8 => {
rotate_area_map_gray(
src,
dst,
cos_a,
sin_a,
cx_src,
cy_src,
cx_dst,
cy_dst,
fill_value as u8,
);
}
PixelDepth::Bit32 => {
rotate_area_map_color(
src, dst, cos_a, sin_a, cx_src, cy_src, cx_dst, cy_dst, fill_value,
);
}
_ => {
rotate_by_sampling_impl(
src, dst, cos_a, sin_a, cx_src, cy_src, cx_dst, cy_dst, fill_value,
);
}
}
}
#[allow(clippy::too_many_arguments)]
fn rotate_area_map_gray(
src: &Pix,
dst: &mut PixMut,
cos_a: f32,
sin_a: f32,
cx_src: f32,
cy_src: f32,
cx_dst: f32,
cy_dst: f32,
grayval: u8,
) {
let src_w = src.width() as i32;
let src_h = src.height() as i32;
let dst_w = dst.width();
let dst_h = dst.height();
let wm2 = src_w - 2;
let hm2 = src_h - 2;
let sina = 16.0 * sin_a;
let cosa = 16.0 * cos_a;
for i in 0..dst_h {
let ydif = cy_dst - i as f32;
for j in 0..dst_w {
let xdif = cx_dst - j as f32;
let xpm = (-xdif * cosa - ydif * sina) as i32;
let ypm = (-ydif * cosa + xdif * sina) as i32;
let xp = (cx_src as i32) + (xpm >> 4);
let yp = (cy_src as i32) + (ypm >> 4);
let xf = xpm & 0x0f;
let yf = ypm & 0x0f;
if xp < 0 || yp < 0 || xp > wm2 || yp > hm2 {
dst.set_pixel_unchecked(j, i, grayval as u32);
continue;
}
let v00 = src.get_pixel_unchecked(xp as u32, yp as u32);
let v10 = src.get_pixel_unchecked((xp + 1) as u32, yp as u32);
let v01 = src.get_pixel_unchecked(xp as u32, (yp + 1) as u32);
let v11 = src.get_pixel_unchecked((xp + 1) as u32, (yp + 1) as u32);
let val = ((16 - xf) * (16 - yf) * v00 as i32
+ xf * (16 - yf) * v10 as i32
+ (16 - xf) * yf * v01 as i32
+ xf * yf * v11 as i32
+ 128)
/ 256;
dst.set_pixel_unchecked(j, i, val as u32);
}
}
}
#[allow(clippy::too_many_arguments)]
fn rotate_area_map_color(
src: &Pix,
dst: &mut PixMut,
cos_a: f32,
sin_a: f32,
cx_src: f32,
cy_src: f32,
cx_dst: f32,
cy_dst: f32,
colorval: u32,
) {
let src_w = src.width() as i32;
let src_h = src.height() as i32;
let dst_w = dst.width();
let dst_h = dst.height();
let wm2 = src_w - 2;
let hm2 = src_h - 2;
let sina = 16.0 * sin_a;
let cosa = 16.0 * cos_a;
for i in 0..dst_h {
let ydif = cy_dst - i as f32;
for j in 0..dst_w {
let xdif = cx_dst - j as f32;
let xpm = (-xdif * cosa - ydif * sina) as i32;
let ypm = (-ydif * cosa + xdif * sina) as i32;
let xp = (cx_src as i32) + (xpm >> 4);
let yp = (cy_src as i32) + (ypm >> 4);
let xf = xpm & 0x0f;
let yf = ypm & 0x0f;
if xp < 0 || yp < 0 || xp > wm2 || yp > hm2 {
dst.set_pixel_unchecked(j, i, colorval);
continue;
}
let word00 = src.get_pixel_unchecked(xp as u32, yp as u32);
let word10 = src.get_pixel_unchecked((xp + 1) as u32, yp as u32);
let word01 = src.get_pixel_unchecked(xp as u32, (yp + 1) as u32);
let word11 = src.get_pixel_unchecked((xp + 1) as u32, (yp + 1) as u32);
let (r00, g00, b00, a00) = pixel::extract_rgba(word00);
let (r10, g10, b10, a10) = pixel::extract_rgba(word10);
let (r01, g01, b01, a01) = pixel::extract_rgba(word01);
let (r11, g11, b11, a11) = pixel::extract_rgba(word11);
let rval = area_interp(r00, r10, r01, r11, xf, yf);
let gval = area_interp(g00, g10, g01, g11, xf, yf);
let bval = area_interp(b00, b10, b01, b11, xf, yf);
let aval = area_interp(a00, a10, a01, a11, xf, yf);
let pixel = pixel::compose_rgba(rval, gval, bval, aval);
dst.set_pixel_unchecked(j, i, pixel);
}
}
}
#[inline]
fn area_interp(v00: u8, v10: u8, v01: u8, v11: u8, xf: i32, yf: i32) -> u8 {
let val = ((16 - xf) * (16 - yf) * v00 as i32
+ xf * (16 - yf) * v10 as i32
+ (16 - xf) * yf * v01 as i32
+ xf * yf * v11 as i32
+ 128)
/ 256;
val.clamp(0, 255) as u8
}
#[allow(clippy::too_many_arguments)]
fn rotate_shear_impl(
src: &Pix,
dst: &mut PixMut,
angle: f32,
xcen: i32,
ycen: i32,
fill_value: u32,
) {
let abs_angle = angle.abs();
if abs_angle <= MAX_TWO_SHEAR_ANGLE {
rotate_2_shear(src, dst, angle, xcen, ycen, fill_value);
} else {
rotate_3_shear(src, dst, angle, xcen, ycen, fill_value);
}
}
fn rotate_2_shear(src: &Pix, dst: &mut PixMut, angle: f32, xcen: i32, ycen: i32, fill_value: u32) {
let w = src.width() as i32;
let h = src.height() as i32;
let tan_a = angle.tan();
let temp_pix = Pix::new(src.width(), src.height(), src.depth()).unwrap();
let mut temp = temp_pix.try_into_mut().unwrap();
fill_image(&mut temp, fill_value);
for y in 0..h {
let shift = ((y - ycen) as f32 * tan_a).round() as i32;
for x in 0..w {
let new_x = x + shift;
if new_x >= 0 && new_x < w {
let val = src.get_pixel_unchecked(x as u32, y as u32);
temp.set_pixel_unchecked(new_x as u32, y as u32, val);
}
}
}
let temp_ref: Pix = temp.into();
for x in 0..w {
let shift = ((x - xcen) as f32 * tan_a).round() as i32;
for y in 0..h {
let new_y = y + shift;
if new_y >= 0 && new_y < h {
let val = temp_ref.get_pixel_unchecked(x as u32, y as u32);
dst.set_pixel_unchecked(x as u32, new_y as u32, val);
}
}
}
}
pub fn rotate_am_corner(pix: &Pix, angle: f32, fill: RotateFill) -> TransformResult<Pix> {
match pix.depth() {
PixelDepth::Bit32 => rotate_am_color_corner(pix, angle, fill),
PixelDepth::Bit8 => rotate_am_gray_corner(pix, angle, fill),
_ => Ok(pix.deep_clone()),
}
}
pub fn rotate_am_color_corner(pix: &Pix, angle: f32, fill: RotateFill) -> TransformResult<Pix> {
if pix.depth() != PixelDepth::Bit32 {
return Err(TransformError::UnsupportedDepth(format!(
"{:?}",
pix.depth()
)));
}
if angle.abs() < MIN_ANGLE_TO_ROTATE {
return Ok(pix.deep_clone());
}
let w = pix.width();
let h = pix.height();
let colorval = fill.to_value(PixelDepth::Bit32);
let out_pix = Pix::new(w, h, PixelDepth::Bit32)?;
let mut out_mut = out_pix.try_into_mut().unwrap();
let cos_a = angle.cos();
let sin_a = angle.sin();
rotate_area_map_color(
pix,
&mut out_mut,
cos_a,
sin_a,
0.0,
0.0,
0.0,
0.0,
colorval,
);
Ok(out_mut.into())
}
pub fn rotate_am_gray_corner(pix: &Pix, angle: f32, fill: RotateFill) -> TransformResult<Pix> {
if pix.depth() != PixelDepth::Bit8 {
return Err(TransformError::UnsupportedDepth(format!(
"{:?}",
pix.depth()
)));
}
if angle.abs() < MIN_ANGLE_TO_ROTATE {
return Ok(pix.deep_clone());
}
let w = pix.width();
let h = pix.height();
let grayval = fill.to_value(PixelDepth::Bit8) as u8;
let out_pix = Pix::new(w, h, PixelDepth::Bit8)?;
let mut out_mut = out_pix.try_into_mut().unwrap();
let cos_a = angle.cos();
let sin_a = angle.sin();
rotate_area_map_gray(pix, &mut out_mut, cos_a, sin_a, 0.0, 0.0, 0.0, 0.0, grayval);
Ok(out_mut.into())
}
pub fn rotate_shear(
pix: &Pix,
cx: i32,
cy: i32,
angle: f32,
fill: ShearFill,
) -> TransformResult<Pix> {
if angle.abs() < MIN_ANGLE_TO_ROTATE {
return Ok(pix.deep_clone());
}
let w = pix.width();
let h = pix.height();
let depth = pix.depth();
let fill_value = fill.to_value(depth);
let out_pix = Pix::new(w, h, depth)?;
let mut out_mut = out_pix.try_into_mut().unwrap();
if let Some(cmap) = pix.colormap() {
let _ = out_mut.set_colormap(Some(cmap.clone()));
}
fill_image(&mut out_mut, fill_value);
rotate_shear_impl(pix, &mut out_mut, angle, cx, cy, fill_value);
Ok(out_mut.into())
}
pub fn rotate_shear_ip(
pix: &mut PixMut,
cx: i32,
cy: i32,
angle: f32,
fill: ShearFill,
) -> TransformResult<()> {
if angle.abs() < MIN_ANGLE_TO_ROTATE {
return Ok(());
}
let hangle = angle.sin().atan();
let half_angle = angle / 2.0;
h_shear_ip(pix, cy, half_angle, fill)?;
v_shear_ip(pix, cx, hangle, fill)?;
h_shear_ip(pix, cy, half_angle, fill)?;
Ok(())
}
pub fn rotate_shear_center(pix: &Pix, angle: f32, fill: ShearFill) -> TransformResult<Pix> {
let cx = (pix.width() / 2) as i32;
let cy = (pix.height() / 2) as i32;
rotate_shear(pix, cx, cy, angle, fill)
}
pub fn rotate_shear_center_ip(
pix: &mut PixMut,
angle: f32,
fill: ShearFill,
) -> TransformResult<()> {
let cx = (pix.width() / 2) as i32;
let cy = (pix.height() / 2) as i32;
rotate_shear_ip(pix, cx, cy, angle, fill)
}
pub fn rotate_with_alpha(
pix: &Pix,
angle: f32,
alpha_pix: Option<&Pix>,
fract: f32,
) -> TransformResult<Pix> {
if pix.depth() != PixelDepth::Bit32 {
return Err(TransformError::UnsupportedDepth(format!(
"{:?}",
pix.depth()
)));
}
let w = pix.width();
let h = pix.height();
let alpha_owned;
let alpha = if let Some(a) = alpha_pix {
if a.depth() != PixelDepth::Bit8 {
return Err(TransformError::UnsupportedDepth(format!("{:?}", a.depth())));
}
if a.width() != w || a.height() != h {
return Err(TransformError::InvalidParameters(format!(
"alpha image dimensions ({}x{}) must match source image dimensions ({}x{})",
a.width(),
a.height(),
w,
h
)));
}
a
} else {
let a_pix = Pix::new(w, h, PixelDepth::Bit8)?;
let mut a_mut = a_pix.try_into_mut().unwrap();
let alpha_val = (255.0 * fract.clamp(0.0, 1.0) + 0.5) as u32;
for y in 0..h {
for x in 0..w {
a_mut.set_pixel_unchecked(x, y, alpha_val);
}
}
alpha_owned = Pix::from(a_mut);
&alpha_owned
};
let options = RotateOptions {
method: RotateMethod::AreaMap,
fill: RotateFill::White,
expand: false,
..Default::default()
};
let rotated = rotate(pix, angle, &options)?;
if angle.abs() < MIN_ANGLE_TO_ROTATE {
let mut out_mut = rotated.try_into_mut().unwrap();
for y in 0..h {
for x in 0..w {
let pixel = out_mut.get_pixel_unchecked(x, y);
let a = alpha.get_pixel_unchecked(x, y) & 0xFF;
out_mut.set_pixel_unchecked(x, y, (pixel & 0xFFFFFF00) | a);
}
}
return Ok(out_mut.into());
}
let cos_a = angle.cos();
let sin_a = angle.sin();
let cx = w as f32 / 2.0;
let cy = h as f32 / 2.0;
let alpha_out_pix = Pix::new(w, h, PixelDepth::Bit8)?;
let mut alpha_out = alpha_out_pix.try_into_mut().unwrap();
rotate_area_map_gray(alpha, &mut alpha_out, cos_a, sin_a, cx, cy, cx, cy, 255);
let rotated_alpha: Pix = alpha_out.into();
let mut out_mut = rotated.try_into_mut().unwrap();
for y in 0..h {
for x in 0..w {
let pixel = out_mut.get_pixel_unchecked(x, y);
let a = rotated_alpha.get_pixel_unchecked(x, y) & 0xFF;
out_mut.set_pixel_unchecked(x, y, (pixel & 0xFFFFFF00) | a);
}
}
Ok(out_mut.into())
}
fn rotate_3_shear(src: &Pix, dst: &mut PixMut, angle: f32, xcen: i32, ycen: i32, fill_value: u32) {
let w = src.width() as i32;
let h = src.height() as i32;
let half_tan = (angle / 2.0).tan();
let hangle = (angle.sin()).atan();
let temp1_pix = Pix::new(src.width(), src.height(), src.depth()).unwrap();
let mut temp1 = temp1_pix.try_into_mut().unwrap();
fill_image(&mut temp1, fill_value);
let temp2_pix = Pix::new(src.width(), src.height(), src.depth()).unwrap();
let mut temp2 = temp2_pix.try_into_mut().unwrap();
fill_image(&mut temp2, fill_value);
for x in 0..w {
let shift = ((x - xcen) as f32 * half_tan).round() as i32;
for y in 0..h {
let new_y = y + shift;
if new_y >= 0 && new_y < h {
let val = src.get_pixel_unchecked(x as u32, y as u32);
temp1.set_pixel_unchecked(x as u32, new_y as u32, val);
}
}
}
let temp1_ref: Pix = temp1.into();
for y in 0..h {
let shift = ((y - ycen) as f32 * hangle).round() as i32;
for x in 0..w {
let new_x = x + shift;
if new_x >= 0 && new_x < w {
let val = temp1_ref.get_pixel_unchecked(x as u32, y as u32);
temp2.set_pixel_unchecked(new_x as u32, y as u32, val);
}
}
}
let temp2_ref: Pix = temp2.into();
for x in 0..w {
let shift = ((x - xcen) as f32 * half_tan).round() as i32;
for y in 0..h {
let new_y = y + shift;
if new_y >= 0 && new_y < h {
let val = temp2_ref.get_pixel_unchecked(x as u32, y as u32);
dst.set_pixel_unchecked(x as u32, new_y as u32, val);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::{PixelDepth, pixel};
#[test]
fn test_rotate_90_clockwise() {
let pix = Pix::new(2, 3, PixelDepth::Bit8).unwrap();
let mut pix_mut = pix.try_into_mut().unwrap();
pix_mut.set_pixel_unchecked(0, 0, 1);
pix_mut.set_pixel_unchecked(1, 0, 2);
pix_mut.set_pixel_unchecked(0, 1, 3);
pix_mut.set_pixel_unchecked(1, 1, 4);
pix_mut.set_pixel_unchecked(0, 2, 5);
pix_mut.set_pixel_unchecked(1, 2, 6);
let pix: Pix = pix_mut.into();
let rotated = rotate_90(&pix, true).unwrap();
assert_eq!((rotated.width(), rotated.height()), (3, 2));
assert_eq!(rotated.get_pixel_unchecked(0, 0), 5);
assert_eq!(rotated.get_pixel_unchecked(1, 0), 3);
assert_eq!(rotated.get_pixel_unchecked(2, 0), 1);
assert_eq!(rotated.get_pixel_unchecked(0, 1), 6);
assert_eq!(rotated.get_pixel_unchecked(1, 1), 4);
assert_eq!(rotated.get_pixel_unchecked(2, 1), 2);
}
#[test]
fn test_rotate_90_counterclockwise() {
let pix = Pix::new(2, 3, PixelDepth::Bit8).unwrap();
let mut pix_mut = pix.try_into_mut().unwrap();
pix_mut.set_pixel_unchecked(0, 0, 1);
pix_mut.set_pixel_unchecked(1, 0, 2);
pix_mut.set_pixel_unchecked(0, 1, 3);
pix_mut.set_pixel_unchecked(1, 1, 4);
pix_mut.set_pixel_unchecked(0, 2, 5);
pix_mut.set_pixel_unchecked(1, 2, 6);
let pix: Pix = pix_mut.into();
let rotated = rotate_90(&pix, false).unwrap();
assert_eq!((rotated.width(), rotated.height()), (3, 2));
assert_eq!(rotated.get_pixel_unchecked(0, 0), 2);
assert_eq!(rotated.get_pixel_unchecked(1, 0), 4);
assert_eq!(rotated.get_pixel_unchecked(2, 0), 6);
assert_eq!(rotated.get_pixel_unchecked(0, 1), 1);
assert_eq!(rotated.get_pixel_unchecked(1, 1), 3);
assert_eq!(rotated.get_pixel_unchecked(2, 1), 5);
}
#[test]
fn test_rotate_180() {
let pix = Pix::new(2, 2, PixelDepth::Bit8).unwrap();
let mut pix_mut = pix.try_into_mut().unwrap();
pix_mut.set_pixel_unchecked(0, 0, 1);
pix_mut.set_pixel_unchecked(1, 0, 2);
pix_mut.set_pixel_unchecked(0, 1, 3);
pix_mut.set_pixel_unchecked(1, 1, 4);
let pix: Pix = pix_mut.into();
let rotated = rotate_180(&pix).unwrap();
assert_eq!(rotated.get_pixel_unchecked(0, 0), 4);
assert_eq!(rotated.get_pixel_unchecked(1, 0), 3);
assert_eq!(rotated.get_pixel_unchecked(0, 1), 2);
assert_eq!(rotated.get_pixel_unchecked(1, 1), 1);
}
#[test]
fn test_flip_lr() {
let pix = Pix::new(3, 2, PixelDepth::Bit8).unwrap();
let mut pix_mut = pix.try_into_mut().unwrap();
pix_mut.set_pixel_unchecked(0, 0, 1);
pix_mut.set_pixel_unchecked(1, 0, 2);
pix_mut.set_pixel_unchecked(2, 0, 3);
pix_mut.set_pixel_unchecked(0, 1, 4);
pix_mut.set_pixel_unchecked(1, 1, 5);
pix_mut.set_pixel_unchecked(2, 1, 6);
let pix: Pix = pix_mut.into();
let flipped = flip_lr(&pix).unwrap();
assert_eq!(flipped.get_pixel_unchecked(0, 0), 3);
assert_eq!(flipped.get_pixel_unchecked(1, 0), 2);
assert_eq!(flipped.get_pixel_unchecked(2, 0), 1);
assert_eq!(flipped.get_pixel_unchecked(0, 1), 6);
assert_eq!(flipped.get_pixel_unchecked(1, 1), 5);
assert_eq!(flipped.get_pixel_unchecked(2, 1), 4);
}
#[test]
fn test_flip_tb() {
let pix = Pix::new(2, 3, PixelDepth::Bit8).unwrap();
let mut pix_mut = pix.try_into_mut().unwrap();
pix_mut.set_pixel_unchecked(0, 0, 1);
pix_mut.set_pixel_unchecked(1, 0, 2);
pix_mut.set_pixel_unchecked(0, 1, 3);
pix_mut.set_pixel_unchecked(1, 1, 4);
pix_mut.set_pixel_unchecked(0, 2, 5);
pix_mut.set_pixel_unchecked(1, 2, 6);
let pix: Pix = pix_mut.into();
let flipped = flip_tb(&pix).unwrap();
assert_eq!(flipped.get_pixel_unchecked(0, 0), 5);
assert_eq!(flipped.get_pixel_unchecked(1, 0), 6);
assert_eq!(flipped.get_pixel_unchecked(0, 1), 3);
assert_eq!(flipped.get_pixel_unchecked(1, 1), 4);
assert_eq!(flipped.get_pixel_unchecked(0, 2), 1);
assert_eq!(flipped.get_pixel_unchecked(1, 2), 2);
}
#[test]
fn test_rotate_orth_all_quadrants() {
let pix = Pix::new(2, 2, PixelDepth::Bit8).unwrap();
let mut pix_mut = pix.try_into_mut().unwrap();
pix_mut.set_pixel_unchecked(0, 0, 1);
pix_mut.set_pixel_unchecked(1, 0, 2);
pix_mut.set_pixel_unchecked(0, 1, 3);
pix_mut.set_pixel_unchecked(1, 1, 4);
let pix: Pix = pix_mut.into();
let r0 = rotate_orth(&pix, 0).unwrap();
assert_eq!(r0.get_pixel_unchecked(0, 0), 1);
let r4 = rotate_orth(&pix, 4).unwrap();
assert_eq!(r4.get_pixel_unchecked(0, 0), 1);
}
#[test]
fn test_rotate_32bpp() {
let pix = Pix::new(2, 2, PixelDepth::Bit32).unwrap();
let mut pix_mut = pix.try_into_mut().unwrap();
let red = pixel::compose_rgb(255, 0, 0);
let green = pixel::compose_rgb(0, 255, 0);
let blue = pixel::compose_rgb(0, 0, 255);
let white = pixel::compose_rgb(255, 255, 255);
pix_mut.set_pixel_unchecked(0, 0, red);
pix_mut.set_pixel_unchecked(1, 0, green);
pix_mut.set_pixel_unchecked(0, 1, blue);
pix_mut.set_pixel_unchecked(1, 1, white);
let pix: Pix = pix_mut.into();
let rotated = rotate_90(&pix, true).unwrap();
assert_eq!(rotated.get_pixel_unchecked(0, 0), blue);
assert_eq!(rotated.get_pixel_unchecked(1, 0), red);
assert_eq!(rotated.get_pixel_unchecked(0, 1), white);
assert_eq!(rotated.get_pixel_unchecked(1, 1), green);
}
#[test]
fn test_rotate_by_angle_zero() {
let pix = Pix::new(10, 10, PixelDepth::Bit8).unwrap();
let rotated = rotate_by_angle(&pix, 0.0).unwrap();
assert_eq!(rotated.width(), 10);
assert_eq!(rotated.height(), 10);
}
#[test]
fn test_rotate_by_angle_90() {
let pix = Pix::new(20, 10, PixelDepth::Bit8).unwrap();
let rotated = rotate_by_angle(&pix, 90.0).unwrap();
assert_eq!(rotated.width(), 10);
assert_eq!(rotated.height(), 20);
}
#[test]
fn test_rotate_by_angle_180() {
let pix = Pix::new(20, 10, PixelDepth::Bit8).unwrap();
let mut pix_mut = pix.try_into_mut().unwrap();
pix_mut.set_pixel_unchecked(0, 0, 100);
let pix: Pix = pix_mut.into();
let rotated = rotate_by_angle(&pix, 180.0).unwrap();
assert_eq!(rotated.width(), 20);
assert_eq!(rotated.height(), 10);
assert_eq!(rotated.get_pixel_unchecked(19, 9), 100);
}
#[test]
fn test_rotate_by_angle_45() {
let pix = Pix::new(100, 100, PixelDepth::Bit8).unwrap();
let rotated = rotate_by_angle(&pix, 45.0).unwrap();
let expected_size = (100.0 * 2.0_f32.sqrt()).ceil() as u32;
assert!(rotated.width() >= expected_size - 2 && rotated.width() <= expected_size + 2);
assert!(rotated.height() >= expected_size - 2 && rotated.height() <= expected_size + 2);
}
#[test]
fn test_rotate_by_radians() {
let pix = Pix::new(50, 50, PixelDepth::Bit8).unwrap();
let rotated_deg = rotate_by_angle(&pix, 45.0).unwrap();
let rotated_rad = rotate_by_radians(&pix, std::f32::consts::FRAC_PI_4).unwrap();
assert_eq!(rotated_deg.width(), rotated_rad.width());
assert_eq!(rotated_deg.height(), rotated_rad.height());
}
#[test]
fn test_rotate_negative_angle() {
let pix = Pix::new(100, 50, PixelDepth::Bit8).unwrap();
let rotated_pos = rotate_by_angle(&pix, 30.0).unwrap();
let rotated_neg = rotate_by_angle(&pix, -330.0).unwrap();
assert_eq!(rotated_pos.width(), rotated_neg.width());
assert_eq!(rotated_pos.height(), rotated_neg.height());
}
#[test]
fn test_rotate_1bpp() {
let pix = Pix::new(50, 50, PixelDepth::Bit1).unwrap();
let mut pix_mut = pix.try_into_mut().unwrap();
for i in 10..40 {
pix_mut.set_pixel_unchecked(i, 25, 1);
}
let pix: Pix = pix_mut.into();
let rotated = rotate_by_angle(&pix, 15.0).unwrap();
assert!(rotated.width() > 50);
assert!(rotated.height() > 50);
}
#[test]
fn test_calculate_rotated_bounds() {
let cos_45 = std::f32::consts::FRAC_PI_4.cos();
let sin_45 = std::f32::consts::FRAC_PI_4.sin();
let (w, h) = calculate_rotated_bounds(100.0, 100.0, cos_45, sin_45);
let expected = 100.0 * 2.0_f32.sqrt();
assert!((w - expected).abs() < 2.0);
assert!((h - expected).abs() < 2.0);
}
#[test]
fn test_rotate_with_options_sampling() {
let pix = Pix::new(50, 50, PixelDepth::Bit8).unwrap();
let options = RotateOptions::with_method(RotateMethod::Sampling);
let rotated = rotate(&pix, 0.5, &options).unwrap();
assert!(rotated.width() > 0);
assert!(rotated.height() > 0);
}
#[test]
fn test_rotate_with_options_area_map() {
let pix = Pix::new(50, 50, PixelDepth::Bit8).unwrap();
let options = RotateOptions::with_method(RotateMethod::AreaMap);
let rotated = rotate(&pix, 0.2, &options).unwrap();
assert!(rotated.width() > 0);
assert!(rotated.height() > 0);
}
#[test]
fn test_rotate_with_options_shear() {
let pix = Pix::new(50, 50, PixelDepth::Bit8).unwrap();
let options = RotateOptions::with_method(RotateMethod::Shear);
let rotated = rotate(&pix, 0.05, &options).unwrap();
assert!(rotated.width() > 0);
assert!(rotated.height() > 0);
}
#[test]
fn test_rotate_with_fill_black() {
let pix = Pix::new(50, 50, PixelDepth::Bit8).unwrap();
let options = RotateOptions::with_fill(RotateFill::Black);
let rotated = rotate(&pix, 0.3, &options).unwrap();
assert!(rotated.width() > 0);
}
#[test]
fn test_rotate_with_custom_center() {
let pix = Pix::new(100, 100, PixelDepth::Bit8).unwrap();
let rotated = rotate_about_center(&pix, 0.2, 25.0, 25.0, RotateFill::White).unwrap();
assert_eq!(rotated.width(), 100);
assert_eq!(rotated.height(), 100);
}
#[test]
fn test_rotate_method_auto_selection() {
let method_1bpp = select_rotate_method(RotateMethod::Auto, PixelDepth::Bit1, 0.02);
assert_eq!(method_1bpp, RotateMethod::Shear);
let method_1bpp_large = select_rotate_method(RotateMethod::Auto, PixelDepth::Bit1, 0.1);
assert_eq!(method_1bpp_large, RotateMethod::Sampling);
let method_8bpp = select_rotate_method(RotateMethod::Auto, PixelDepth::Bit8, 0.2);
assert_eq!(method_8bpp, RotateMethod::AreaMap);
let method_32bpp = select_rotate_method(RotateMethod::Auto, PixelDepth::Bit32, 0.2);
assert_eq!(method_32bpp, RotateMethod::AreaMap);
}
#[test]
fn test_rotate_32bpp_area_map() {
let pix = Pix::new(30, 30, PixelDepth::Bit32).unwrap();
let mut pix_mut = pix.try_into_mut().unwrap();
for y in 0..30 {
for x in 0..30 {
let val = pixel::compose_rgb(x as u8 * 8, y as u8 * 8, 128);
pix_mut.set_pixel_unchecked(x, y, val);
}
}
let pix: Pix = pix_mut.into();
let options = RotateOptions::with_method(RotateMethod::AreaMap);
let rotated = rotate(&pix, 0.3, &options).unwrap();
assert!(rotated.width() > 30);
assert!(rotated.height() > 30);
}
#[test]
fn test_rotate_very_small_angle() {
let pix = Pix::new(50, 50, PixelDepth::Bit8).unwrap();
let options = RotateOptions::default();
let rotated = rotate(&pix, 0.0001, &options).unwrap();
assert_eq!(rotated.width(), 50);
assert_eq!(rotated.height(), 50);
}
#[test]
fn test_rotate_fill_to_value() {
assert_eq!(RotateFill::White.to_value(PixelDepth::Bit1), 0);
assert_eq!(RotateFill::Black.to_value(PixelDepth::Bit1), 1);
assert_eq!(RotateFill::White.to_value(PixelDepth::Bit8), 255);
assert_eq!(RotateFill::Black.to_value(PixelDepth::Bit8), 0);
assert_eq!(RotateFill::Color(128).to_value(PixelDepth::Bit8), 128);
}
#[test]
fn test_rotate_options_builder() {
let options = RotateOptions::with_method(RotateMethod::Sampling)
.center(25.0, 30.0)
.expand(false);
assert_eq!(options.method, RotateMethod::Sampling);
assert_eq!(options.center_x, Some(25.0));
assert_eq!(options.center_y, Some(30.0));
assert!(!options.expand);
}
#[test]
fn test_rotate_with_method_convenience() {
let pix = Pix::new(40, 40, PixelDepth::Bit8).unwrap();
let rotated = rotate_with_method(&pix, 0.25, RotateMethod::Bilinear).unwrap();
assert!(rotated.width() > 0);
}
#[test]
fn test_shear_rotation_2_shear() {
let pix = Pix::new(50, 50, PixelDepth::Bit8).unwrap();
let options = RotateOptions {
method: RotateMethod::Shear,
expand: false, ..Default::default()
};
let rotated = rotate(&pix, 0.03, &options).unwrap(); assert_eq!(rotated.width(), 50); }
#[test]
fn test_shear_rotation_3_shear() {
let pix = Pix::new(50, 50, PixelDepth::Bit8).unwrap();
let options = RotateOptions {
method: RotateMethod::Shear,
expand: false,
..Default::default()
};
let rotated = rotate(&pix, 0.15, &options).unwrap(); assert_eq!(rotated.width(), 50);
}
#[test]
fn test_rotate_am_gray_corner_smoke() {
let pix = Pix::new(50, 50, PixelDepth::Bit8).unwrap();
let rotated = rotate_am_gray_corner(&pix, 0.2, RotateFill::White).unwrap();
assert_eq!(rotated.width(), 50);
assert_eq!(rotated.height(), 50);
assert_eq!(rotated.depth(), PixelDepth::Bit8);
}
#[test]
fn test_rotate_am_gray_corner_small_angle_clones() {
let pix = Pix::new(30, 30, PixelDepth::Bit8).unwrap();
let rotated = rotate_am_gray_corner(&pix, 0.0, RotateFill::White).unwrap();
assert_eq!(rotated.width(), 30);
}
#[test]
fn test_rotate_am_color_corner_smoke() {
let pix = Pix::new(50, 50, PixelDepth::Bit32).unwrap();
let rotated = rotate_am_color_corner(&pix, 0.2, RotateFill::White).unwrap();
assert_eq!(rotated.width(), 50);
assert_eq!(rotated.height(), 50);
assert_eq!(rotated.depth(), PixelDepth::Bit32);
}
#[test]
fn test_rotate_am_corner_dispatches_gray() {
let pix = Pix::new(50, 50, PixelDepth::Bit8).unwrap();
let rotated = rotate_am_corner(&pix, 0.2, RotateFill::White).unwrap();
assert_eq!(rotated.depth(), PixelDepth::Bit8);
assert_eq!(rotated.width(), 50);
}
#[test]
fn test_rotate_am_corner_dispatches_color() {
let pix = Pix::new(50, 50, PixelDepth::Bit32).unwrap();
let rotated = rotate_am_corner(&pix, 0.2, RotateFill::White).unwrap();
assert_eq!(rotated.depth(), PixelDepth::Bit32);
assert_eq!(rotated.width(), 50);
}
#[test]
fn test_rotate_shear_pub_smoke() {
let pix = Pix::new(50, 50, PixelDepth::Bit8).unwrap();
let rotated = rotate_shear(&pix, 25, 25, 0.1, ShearFill::White).unwrap();
assert_eq!(rotated.width(), 50);
assert_eq!(rotated.height(), 50);
}
#[test]
fn test_rotate_shear_center_smoke() {
let pix = Pix::new(50, 50, PixelDepth::Bit8).unwrap();
let rotated = rotate_shear_center(&pix, 0.1, ShearFill::White).unwrap();
assert_eq!(rotated.width(), 50);
assert_eq!(rotated.height(), 50);
}
#[test]
fn test_rotate_shear_ip_smoke() {
let pix = Pix::new(50, 50, PixelDepth::Bit8).unwrap();
let mut pix_mut = pix.try_into_mut().unwrap();
rotate_shear_ip(&mut pix_mut, 25, 25, 0.1, ShearFill::White).unwrap();
assert_eq!(pix_mut.width(), 50);
assert_eq!(pix_mut.height(), 50);
}
#[test]
fn test_rotate_shear_center_ip_smoke() {
let pix = Pix::new(50, 50, PixelDepth::Bit8).unwrap();
let mut pix_mut = pix.try_into_mut().unwrap();
rotate_shear_center_ip(&mut pix_mut, 0.1, ShearFill::White).unwrap();
assert_eq!(pix_mut.width(), 50);
assert_eq!(pix_mut.height(), 50);
}
#[test]
fn test_rotate_with_alpha_uniform() {
let pix = Pix::new(50, 50, PixelDepth::Bit32).unwrap();
let rotated = rotate_with_alpha(&pix, 0.2, None, 0.5).unwrap();
assert_eq!(rotated.depth(), PixelDepth::Bit32);
assert_eq!(rotated.width(), 50);
assert_eq!(rotated.height(), 50);
}
#[test]
fn test_rotate_with_alpha_custom_alpha() {
let pix = Pix::new(50, 50, PixelDepth::Bit32).unwrap();
let alpha = Pix::new(50, 50, PixelDepth::Bit8).unwrap();
let rotated = rotate_with_alpha(&pix, 0.2, Some(&alpha), 1.0).unwrap();
assert_eq!(rotated.depth(), PixelDepth::Bit32);
}
}