use std::f32::consts::PI;
use crate::image::{Image, ImageSize};
use crate::resize::{interpolate_pixel, meshgrid, InterpolationMode};
use anyhow::Result;
use ndarray::stack;
type AffineMatrix = (f32, f32, f32, f32, f32, f32);
fn invert_affine_transform(m: AffineMatrix) -> AffineMatrix {
let (a, b, c, d, e, f) = m;
let determinant = a * e - b * d;
let inv_determinant = if determinant != 0.0 {
1.0 / determinant
} else {
0.0
};
let new_a = e * inv_determinant;
let new_b = -b * inv_determinant;
let new_d = -d * inv_determinant;
let new_e = a * inv_determinant;
let new_c = -(new_a * c + new_b * f);
let new_f = -(new_d * c + new_e * f);
(new_a, new_b, new_c, new_d, new_e, new_f)
}
pub fn get_rotation_matrix2d(center: (f32, f32), angle: f32, scale: f32) -> AffineMatrix {
let angle = angle * PI / 180.0f32;
let alpha = scale * angle.cos();
let beta = scale * angle.sin();
let tx = (1.0 - alpha) * center.0 - beta * center.1;
let ty = beta * center.0 + (1.0 - alpha) * center.1;
(alpha, beta, tx, -beta, alpha, ty)
}
pub fn warp_affine<const CHANNELS: usize>(
src: &Image<f32, CHANNELS>,
m: AffineMatrix,
new_size: ImageSize,
interpolation: InterpolationMode,
) -> Result<Image<f32, CHANNELS>> {
let m_inv = invert_affine_transform(m);
let mut output = Image::from_size_val(new_size, 0.0)?;
let x = ndarray::Array::range(0.0, new_size.width as f32, 1.0).insert_axis(ndarray::Axis(0));
let y = ndarray::Array::range(0.0, new_size.height as f32, 1.0).insert_axis(ndarray::Axis(0));
let (xx, yy) = meshgrid(&x, &y);
let xy = stack![ndarray::Axis(2), xx, yy];
ndarray::Zip::from(xy.rows())
.and(output.data.rows_mut())
.par_for_each(|uv, mut out| {
assert_eq!(uv.len(), 2);
let (u, v) = (uv[0], uv[1]);
let u_src = m_inv.0 * u + m_inv.1 * v + m_inv.2;
let v_src = m_inv.3 * u + m_inv.4 * v + m_inv.5;
if u_src < 0.0
|| u_src > (src.width() - 1) as f32
|| v_src < 0.0
|| v_src > (src.height() - 1) as f32
{
return;
}
let pixels = (0..src.num_channels())
.map(|k| interpolate_pixel(&src.data, u_src, v_src, k, interpolation));
for (k, pixel) in pixels.enumerate() {
out[k] = pixel;
}
});
Ok(output)
}
#[cfg(test)]
mod tests {
use anyhow::Result;
#[test]
fn warp_affine_smoke_ch3() -> Result<()> {
use crate::image::{Image, ImageSize};
let image = Image::<_, 3>::new(
ImageSize {
width: 4,
height: 5,
},
vec![0f32; 4 * 5 * 3],
)?;
let image_transformed = super::warp_affine(
&image,
(0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
ImageSize {
width: 2,
height: 3,
},
super::InterpolationMode::Bilinear,
)?;
assert_eq!(image_transformed.num_channels(), 3);
assert_eq!(image_transformed.size().width, 2);
assert_eq!(image_transformed.size().height, 3);
Ok(())
}
#[test]
fn warp_affine_smoke_ch1() -> Result<()> {
use crate::image::{Image, ImageSize};
let image = Image::<_, 1>::new(
ImageSize {
width: 4,
height: 5,
},
vec![0f32; 4 * 5],
)?;
let image_transformed = super::warp_affine(
&image,
(1.0, 0.0, 0.0, 0.0, 1.0, 0.0),
ImageSize {
width: 2,
height: 3,
},
super::InterpolationMode::Nearest,
)?;
assert_eq!(image_transformed.num_channels(), 1);
assert_eq!(image_transformed.size().width, 2);
assert_eq!(image_transformed.size().height, 3);
Ok(())
}
#[test]
fn warp_affine_correctness_identity() -> Result<()> {
use crate::image::{Image, ImageSize};
let image = Image::<_, 1>::new(
ImageSize {
width: 4,
height: 5,
},
(0..20).map(|x| x as f32).collect(),
)?;
let image_transformed = super::warp_affine(
&image,
(1.0, 0.0, 0.0, 0.0, 1.0, 0.0),
ImageSize {
width: 4,
height: 5,
},
super::InterpolationMode::Nearest,
)?;
assert_eq!(image_transformed.data, image.data);
assert_eq!(image_transformed.size(), image.size());
Ok(())
}
#[test]
fn warp_affine_correctness_rot90() -> Result<()> {
use crate::image::{Image, ImageSize};
let image = Image::<_, 1>::new(
ImageSize {
width: 2,
height: 2,
},
vec![0.0f32, 1.0f32, 2.0f32, 3.0f32],
)?;
let image_transformed = super::warp_affine(
&image,
super::get_rotation_matrix2d((0.5, 0.5), 90.0, 1.0),
ImageSize {
width: 2,
height: 2,
},
super::InterpolationMode::Nearest,
)?;
assert_eq!(
image_transformed.data,
ndarray::array![[[1.0f32], [3.0f32]], [[0.0f32], [2.0f32]]]
);
Ok(())
}
}