use super::{ColorSpace, CvError, Image};
use crate::error::NumRs2Error;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum PadMode {
#[default]
Constant,
Reflect,
Replicate,
Wrap,
}
pub fn resize_nearest(
img: &Image,
new_width: usize,
new_height: usize,
) -> Result<Image, NumRs2Error> {
if new_width == 0 || new_height == 0 {
return Err(CvError::InvalidParameter(
"Target dimensions must be greater than zero".to_string(),
)
.into());
}
let old_w = img.width();
let old_h = img.height();
let channels = img.channels();
let mut result = create_output_image(new_width, new_height, img.color_space())?;
let x_ratio = old_w as f64 / new_width as f64;
let y_ratio = old_h as f64 / new_height as f64;
for row in 0..new_height {
for col in 0..new_width {
let src_row = ((row as f64 + 0.5) * y_ratio - 0.5)
.round()
.max(0.0)
.min((old_h - 1) as f64) as usize;
let src_col = ((col as f64 + 0.5) * x_ratio - 0.5)
.round()
.max(0.0)
.min((old_w - 1) as f64) as usize;
for ch in 0..channels {
let val = img.get_pixel(src_row, src_col, ch).unwrap_or(0.0);
result.set_pixel(row, col, ch, val).map_err(|e| {
NumRs2Error::ComputationError(format!(
"resize_nearest set pixel ({}, {}, {}): {}",
row, col, ch, e
))
})?;
}
}
}
Ok(result)
}
pub fn resize_bilinear(
img: &Image,
new_width: usize,
new_height: usize,
) -> Result<Image, NumRs2Error> {
if new_width == 0 || new_height == 0 {
return Err(CvError::InvalidParameter(
"Target dimensions must be greater than zero".to_string(),
)
.into());
}
let old_w = img.width();
let old_h = img.height();
let channels = img.channels();
let mut result = create_output_image(new_width, new_height, img.color_space())?;
let x_ratio = if new_width > 1 {
(old_w as f64 - 1.0) / (new_width as f64 - 1.0)
} else {
0.0
};
let y_ratio = if new_height > 1 {
(old_h as f64 - 1.0) / (new_height as f64 - 1.0)
} else {
0.0
};
for row in 0..new_height {
for col in 0..new_width {
let src_y = row as f64 * y_ratio;
let src_x = col as f64 * x_ratio;
let y0 = src_y.floor() as usize;
let x0 = src_x.floor() as usize;
let y1 = (y0 + 1).min(old_h - 1);
let x1 = (x0 + 1).min(old_w - 1);
let dy = src_y - y0 as f64;
let dx = src_x - x0 as f64;
for ch in 0..channels {
let p00 = img.get_pixel(y0, x0, ch).unwrap_or(0.0);
let p01 = img.get_pixel(y0, x1, ch).unwrap_or(0.0);
let p10 = img.get_pixel(y1, x0, ch).unwrap_or(0.0);
let p11 = img.get_pixel(y1, x1, ch).unwrap_or(0.0);
let val = p00 * (1.0 - dx) * (1.0 - dy)
+ p01 * dx * (1.0 - dy)
+ p10 * (1.0 - dx) * dy
+ p11 * dx * dy;
result.set_pixel(row, col, ch, val).map_err(|e| {
NumRs2Error::ComputationError(format!(
"resize_bilinear set pixel ({}, {}, {}): {}",
row, col, ch, e
))
})?;
}
}
}
Ok(result)
}
pub fn rotate(img: &Image, angle_degrees: f64) -> Result<Image, NumRs2Error> {
let h = img.height();
let w = img.width();
let channels = img.channels();
let mut result = create_output_image(w, h, img.color_space())?;
let angle_rad = angle_degrees * std::f64::consts::PI / 180.0;
let cos_a = angle_rad.cos();
let sin_a = angle_rad.sin();
let cx = (w as f64 - 1.0) / 2.0;
let cy = (h as f64 - 1.0) / 2.0;
for row in 0..h {
for col in 0..w {
let dx = col as f64 - cx;
let dy = row as f64 - cy;
let src_x = dx * cos_a + dy * sin_a + cx;
let src_y = -dx * sin_a + dy * cos_a + cy;
if src_x >= 0.0 && src_x < (w - 1) as f64 && src_y >= 0.0 && src_y < (h - 1) as f64 {
let x0 = src_x.floor() as usize;
let y0 = src_y.floor() as usize;
let x1 = (x0 + 1).min(w - 1);
let y1 = (y0 + 1).min(h - 1);
let dx_frac = src_x - x0 as f64;
let dy_frac = src_y - y0 as f64;
for ch in 0..channels {
let p00 = img.get_pixel(y0, x0, ch).unwrap_or(0.0);
let p01 = img.get_pixel(y0, x1, ch).unwrap_or(0.0);
let p10 = img.get_pixel(y1, x0, ch).unwrap_or(0.0);
let p11 = img.get_pixel(y1, x1, ch).unwrap_or(0.0);
let val = p00 * (1.0 - dx_frac) * (1.0 - dy_frac)
+ p01 * dx_frac * (1.0 - dy_frac)
+ p10 * (1.0 - dx_frac) * dy_frac
+ p11 * dx_frac * dy_frac;
result.set_pixel(row, col, ch, val).map_err(|e| {
NumRs2Error::ComputationError(format!(
"rotate set pixel ({}, {}, {}): {}",
row, col, ch, e
))
})?;
}
}
}
}
Ok(result)
}
pub fn flip_horizontal(img: &Image) -> Result<Image, NumRs2Error> {
let h = img.height();
let w = img.width();
let channels = img.channels();
let mut result = create_output_image(w, h, img.color_space())?;
for row in 0..h {
for col in 0..w {
let src_col = w - 1 - col;
for ch in 0..channels {
let val = img.get_pixel(row, src_col, ch).unwrap_or(0.0);
result.set_pixel(row, col, ch, val).map_err(|e| {
NumRs2Error::ComputationError(format!(
"flip_horizontal set pixel ({}, {}, {}): {}",
row, col, ch, e
))
})?;
}
}
}
Ok(result)
}
pub fn flip_vertical(img: &Image) -> Result<Image, NumRs2Error> {
let h = img.height();
let w = img.width();
let channels = img.channels();
let mut result = create_output_image(w, h, img.color_space())?;
for row in 0..h {
let src_row = h - 1 - row;
for col in 0..w {
for ch in 0..channels {
let val = img.get_pixel(src_row, col, ch).unwrap_or(0.0);
result.set_pixel(row, col, ch, val).map_err(|e| {
NumRs2Error::ComputationError(format!(
"flip_vertical set pixel ({}, {}, {}): {}",
row, col, ch, e
))
})?;
}
}
}
Ok(result)
}
pub fn crop(
img: &Image,
x: usize,
y: usize,
width: usize,
height: usize,
) -> Result<Image, NumRs2Error> {
if width == 0 || height == 0 {
return Err(CvError::InvalidParameter(
"Crop dimensions must be greater than zero".to_string(),
)
.into());
}
if x + width > img.width() || y + height > img.height() {
return Err(CvError::InvalidParameter(format!(
"Crop region (x={}, y={}, w={}, h={}) exceeds image bounds ({}x{})",
x,
y,
width,
height,
img.width(),
img.height()
))
.into());
}
let channels = img.channels();
let mut result = create_output_image(width, height, img.color_space())?;
for row in 0..height {
for col in 0..width {
let src_row = y + row;
let src_col = x + col;
for ch in 0..channels {
let val = img.get_pixel(src_row, src_col, ch).unwrap_or(0.0);
result.set_pixel(row, col, ch, val).map_err(|e| {
NumRs2Error::ComputationError(format!(
"crop set pixel ({}, {}, {}): {}",
row, col, ch, e
))
})?;
}
}
}
Ok(result)
}
pub fn pad(
img: &Image,
top: usize,
bottom: usize,
left: usize,
right: usize,
mode: PadMode,
) -> Result<Image, NumRs2Error> {
let old_h = img.height();
let old_w = img.width();
let new_h = old_h + top + bottom;
let new_w = old_w + left + right;
let channels = img.channels();
if new_h == 0 || new_w == 0 {
return Err(CvError::InvalidParameter(
"Padded image dimensions must be greater than zero".to_string(),
)
.into());
}
let mut result = create_output_image(new_w, new_h, img.color_space())?;
for row in 0..new_h {
for col in 0..new_w {
let src_row = map_pad_coordinate(row, top, old_h, mode);
let src_col = map_pad_coordinate(col, left, old_w, mode);
for ch in 0..channels {
let val = match (src_row, src_col) {
(Some(sr), Some(sc)) => img.get_pixel(sr, sc, ch).unwrap_or(0.0),
_ => 0.0, };
result.set_pixel(row, col, ch, val).map_err(|e| {
NumRs2Error::ComputationError(format!(
"pad set pixel ({}, {}, {}): {}",
row, col, ch, e
))
})?;
}
}
}
Ok(result)
}
fn map_pad_coordinate(
out_idx: usize,
pad_before: usize,
src_size: usize,
mode: PadMode,
) -> Option<usize> {
if src_size == 0 {
return None;
}
let signed_idx = out_idx as isize - pad_before as isize;
if signed_idx >= 0 && (signed_idx as usize) < src_size {
return Some(signed_idx as usize);
}
match mode {
PadMode::Constant => None,
PadMode::Reflect => Some(reflect_coordinate(signed_idx, src_size as isize)),
PadMode::Replicate => {
if signed_idx < 0 {
Some(0)
} else {
Some(src_size - 1)
}
}
PadMode::Wrap => {
let wrapped =
((signed_idx % src_size as isize) + src_size as isize) % src_size as isize;
Some(wrapped as usize)
}
}
}
fn reflect_coordinate(idx: isize, len: isize) -> usize {
if len <= 1 {
return 0;
}
let period = 2 * (len - 1);
let normalized = ((idx % period) + period) % period;
if normalized < len {
normalized as usize
} else {
(period - normalized) as usize
}
}
pub fn affine_transform(img: &Image, matrix: &[[f64; 3]; 2]) -> Result<Image, NumRs2Error> {
let h = img.height();
let w = img.width();
let channels = img.channels();
let mut result = create_output_image(w, h, img.color_space())?;
let a00 = matrix[0][0];
let a01 = matrix[0][1];
let a02 = matrix[0][2];
let a10 = matrix[1][0];
let a11 = matrix[1][1];
let a12 = matrix[1][2];
for row in 0..h {
for col in 0..w {
let src_x = a00 * col as f64 + a01 * row as f64 + a02;
let src_y = a10 * col as f64 + a11 * row as f64 + a12;
if src_x >= 0.0 && src_x < (w - 1) as f64 && src_y >= 0.0 && src_y < (h - 1) as f64 {
let x0 = src_x.floor() as usize;
let y0 = src_y.floor() as usize;
let x1 = (x0 + 1).min(w - 1);
let y1 = (y0 + 1).min(h - 1);
let dx = src_x - x0 as f64;
let dy = src_y - y0 as f64;
for ch in 0..channels {
let p00 = img.get_pixel(y0, x0, ch).unwrap_or(0.0);
let p01 = img.get_pixel(y0, x1, ch).unwrap_or(0.0);
let p10 = img.get_pixel(y1, x0, ch).unwrap_or(0.0);
let p11 = img.get_pixel(y1, x1, ch).unwrap_or(0.0);
let val = p00 * (1.0 - dx) * (1.0 - dy)
+ p01 * dx * (1.0 - dy)
+ p10 * (1.0 - dx) * dy
+ p11 * dx * dy;
result.set_pixel(row, col, ch, val).map_err(|e| {
NumRs2Error::ComputationError(format!(
"affine_transform set pixel ({}, {}, {}): {}",
row, col, ch, e
))
})?;
}
}
}
}
Ok(result)
}
fn create_output_image(
width: usize,
height: usize,
color_space: ColorSpace,
) -> Result<Image, NumRs2Error> {
match color_space {
ColorSpace::Grayscale => Ok(Image::zeros_grayscale(width, height)),
ColorSpace::Rgb => {
let data = vec![0.0; width * height * 3];
Image::from_rgb(width, height, &data)
}
ColorSpace::Rgba => {
let data = vec![0.0; width * height * 4];
let arr = crate::array::Array::from_vec(data).reshape(&[height, width, 4]);
Image::from_array(arr, ColorSpace::Rgba)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_test_image(w: usize, h: usize) -> Image {
let mut data = vec![0.0; w * h];
for row in 0..h {
for col in 0..w {
data[row * w + col] = (row * w + col) as f64 / (w * h) as f64;
}
}
Image::from_grayscale(w, h, &data).expect("test: image creation should succeed")
}
fn make_centered_square(size: usize) -> Image {
let mut data = vec![0.0; size * size];
let quarter = size / 4;
for row in quarter..(size - quarter) {
for col in quarter..(size - quarter) {
data[row * size + col] = 1.0;
}
}
Image::from_grayscale(size, size, &data).expect("test: image creation should succeed")
}
#[test]
fn test_resize_nearest_upscale() {
let img = make_test_image(4, 4);
let resized =
resize_nearest(&img, 8, 8).expect("test: nearest resize upscale should succeed");
assert_eq!(resized.width(), 8);
assert_eq!(resized.height(), 8);
for row in 0..8 {
for col in 0..8 {
let val = resized
.get_pixel(row, col, 0)
.expect("test: pixel access should succeed");
assert!(
(0.0..=1.0).contains(&val),
"Pixel at ({},{}) out of range: {}",
row,
col,
val
);
}
}
}
#[test]
fn test_resize_nearest_downscale() {
let img = make_test_image(8, 8);
let resized = resize_nearest(&img, 4, 4).expect("test: nearest downscale should succeed");
assert_eq!(resized.width(), 4);
assert_eq!(resized.height(), 4);
}
#[test]
fn test_resize_nearest_invalid_dimensions() {
let img = make_test_image(4, 4);
assert!(
resize_nearest(&img, 0, 4).is_err(),
"Should reject zero width"
);
assert!(
resize_nearest(&img, 4, 0).is_err(),
"Should reject zero height"
);
}
#[test]
fn test_resize_bilinear_upscale() {
let img = make_test_image(4, 4);
let resized =
resize_bilinear(&img, 8, 8).expect("test: bilinear resize upscale should succeed");
assert_eq!(resized.width(), 8);
assert_eq!(resized.height(), 8);
}
#[test]
fn test_resize_bilinear_preserves_corners() {
let img = make_test_image(4, 4);
let resized = resize_bilinear(&img, 8, 8).expect("test: bilinear resize should succeed");
let orig_tl = img
.get_pixel(0, 0, 0)
.expect("test: pixel access should succeed");
let resized_tl = resized
.get_pixel(0, 0, 0)
.expect("test: pixel access should succeed");
assert!(
(orig_tl - resized_tl).abs() < 1e-6,
"Top-left corner should be preserved: orig={}, resized={}",
orig_tl,
resized_tl
);
let orig_br = img
.get_pixel(3, 3, 0)
.expect("test: pixel access should succeed");
let resized_br = resized
.get_pixel(7, 7, 0)
.expect("test: pixel access should succeed");
assert!(
(orig_br - resized_br).abs() < 1e-6,
"Bottom-right corner should be preserved: orig={}, resized={}",
orig_br,
resized_br
);
}
#[test]
fn test_resize_bilinear_constant_image() {
let data = vec![0.5; 4 * 4];
let img = Image::from_grayscale(4, 4, &data).expect("test: image creation should succeed");
let resized = resize_bilinear(&img, 8, 8).expect("test: bilinear resize should succeed");
for row in 0..8 {
for col in 0..8 {
let v = resized
.get_pixel(row, col, 0)
.expect("test: pixel access should succeed");
assert!(
(v - 0.5).abs() < 1e-6,
"Constant image should remain constant at ({}, {}): got {}",
row,
col,
v
);
}
}
}
#[test]
fn test_rotate_zero_degrees() {
let img = make_test_image(8, 8);
let rotated = rotate(&img, 0.0).expect("test: zero rotation should succeed");
assert_eq!(rotated.width(), 8);
assert_eq!(rotated.height(), 8);
for row in 1..7 {
for col in 1..7 {
let orig = img
.get_pixel(row, col, 0)
.expect("test: pixel access should succeed");
let rot = rotated
.get_pixel(row, col, 0)
.expect("test: pixel access should succeed");
assert!(
(orig - rot).abs() < 1e-6,
"Zero rotation at ({}, {}): orig={}, rot={}",
row,
col,
orig,
rot
);
}
}
}
#[test]
fn test_rotate_180_degrees() {
let img = make_test_image(8, 8);
let rotated = rotate(&img, 180.0).expect("test: 180-degree rotation should succeed");
for row in 2..6 {
for col in 2..6 {
let orig = img
.get_pixel(7 - row, 7 - col, 0)
.expect("test: pixel access should succeed");
let rot = rotated
.get_pixel(row, col, 0)
.expect("test: pixel access should succeed");
assert!(
(orig - rot).abs() < 0.05,
"180-deg rotation at ({}, {}): expected ~{}, got {}",
row,
col,
orig,
rot
);
}
}
}
#[test]
fn test_rotate_symmetric_image_90() {
let img = make_centered_square(16);
let rotated = rotate(&img, 90.0).expect("test: 90-degree rotation should succeed");
let center = rotated
.get_pixel(8, 8, 0)
.expect("test: pixel access should succeed");
assert!(
center > 0.5,
"Center of symmetric image should remain bright after 90-degree rotation: got {}",
center
);
}
#[test]
fn test_flip_horizontal_pixel_values() {
let img = make_test_image(8, 8);
let flipped = flip_horizontal(&img).expect("test: horizontal flip should succeed");
assert_eq!(flipped.width(), 8);
assert_eq!(flipped.height(), 8);
for row in 0..8 {
for col in 0..8 {
let orig = img
.get_pixel(row, 7 - col, 0)
.expect("test: pixel access should succeed");
let flip = flipped
.get_pixel(row, col, 0)
.expect("test: pixel access should succeed");
assert!(
(orig - flip).abs() < 1e-10,
"Horizontal flip at ({}, {}): expected {}, got {}",
row,
col,
orig,
flip
);
}
}
}
#[test]
fn test_flip_horizontal_double_is_identity() {
let img = make_test_image(8, 8);
let flipped = flip_horizontal(&img).expect("test: first flip");
let double_flipped = flip_horizontal(&flipped).expect("test: second flip");
for row in 0..8 {
for col in 0..8 {
let orig = img.get_pixel(row, col, 0).expect("test: orig pixel");
let df = double_flipped
.get_pixel(row, col, 0)
.expect("test: df pixel");
assert!(
(orig - df).abs() < 1e-10,
"Double horizontal flip should be identity at ({}, {})",
row,
col
);
}
}
}
#[test]
fn test_flip_vertical_pixel_values() {
let img = make_test_image(8, 8);
let flipped = flip_vertical(&img).expect("test: vertical flip should succeed");
for row in 0..8 {
for col in 0..8 {
let orig = img
.get_pixel(7 - row, col, 0)
.expect("test: pixel access should succeed");
let flip = flipped
.get_pixel(row, col, 0)
.expect("test: pixel access should succeed");
assert!(
(orig - flip).abs() < 1e-10,
"Vertical flip at ({}, {}): expected {}, got {}",
row,
col,
orig,
flip
);
}
}
}
#[test]
fn test_flip_vertical_double_is_identity() {
let img = make_test_image(8, 8);
let flipped = flip_vertical(&img).expect("test: first flip");
let double_flipped = flip_vertical(&flipped).expect("test: second flip");
for row in 0..8 {
for col in 0..8 {
let orig = img.get_pixel(row, col, 0).expect("test: orig pixel");
let df = double_flipped
.get_pixel(row, col, 0)
.expect("test: df pixel");
assert!(
(orig - df).abs() < 1e-10,
"Double vertical flip should be identity at ({}, {})",
row,
col
);
}
}
}
#[test]
fn test_crop_basic() {
let img = make_test_image(8, 8);
let cropped = crop(&img, 2, 2, 4, 4).expect("test: crop should succeed");
assert_eq!(cropped.width(), 4);
assert_eq!(cropped.height(), 4);
for row in 0..4 {
for col in 0..4 {
let orig = img
.get_pixel(row + 2, col + 2, 0)
.expect("test: orig pixel");
let cr = cropped.get_pixel(row, col, 0).expect("test: cropped pixel");
assert!(
(orig - cr).abs() < 1e-10,
"Cropped pixel at ({}, {}): expected {}, got {}",
row,
col,
orig,
cr
);
}
}
}
#[test]
fn test_crop_full_image() {
let img = make_test_image(8, 8);
let cropped = crop(&img, 0, 0, 8, 8).expect("test: full crop should succeed");
assert_eq!(cropped.width(), 8);
assert_eq!(cropped.height(), 8);
}
#[test]
fn test_crop_out_of_bounds() {
let img = make_test_image(8, 8);
assert!(
crop(&img, 6, 6, 4, 4).is_err(),
"Should fail when region exceeds bounds"
);
}
#[test]
fn test_crop_zero_dimensions() {
let img = make_test_image(8, 8);
assert!(
crop(&img, 0, 0, 0, 4).is_err(),
"Should fail with zero width"
);
assert!(
crop(&img, 0, 0, 4, 0).is_err(),
"Should fail with zero height"
);
}
#[test]
fn test_pad_constant_values() {
let img = make_test_image(4, 4);
let padded =
pad(&img, 2, 2, 2, 2, PadMode::Constant).expect("test: constant pad should succeed");
assert_eq!(padded.width(), 8);
assert_eq!(padded.height(), 8);
let corner = padded.get_pixel(0, 0, 0).expect("test: corner pixel");
assert!(
corner.abs() < 1e-10,
"Constant pad border should be 0.0: got {}",
corner
);
for row in 0..4 {
for col in 0..4 {
let orig = img.get_pixel(row, col, 0).expect("test: orig pixel");
let padded_val = padded
.get_pixel(row + 2, col + 2, 0)
.expect("test: padded pixel");
assert!(
(orig - padded_val).abs() < 1e-10,
"Padded interior at ({}, {}): expected {}, got {}",
row,
col,
orig,
padded_val
);
}
}
}
#[test]
fn test_pad_replicate() {
let data = vec![
0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5,
];
let img = Image::from_grayscale(4, 4, &data).expect("test: image creation should succeed");
let padded =
pad(&img, 1, 1, 1, 1, PadMode::Replicate).expect("test: replicate pad should succeed");
assert_eq!(padded.width(), 6);
assert_eq!(padded.height(), 6);
let corner = padded.get_pixel(0, 0, 0).expect("test: corner pixel");
assert!(
(corner - 0.1).abs() < 1e-10,
"Replicate pad top-left: expected 0.1, got {}",
corner
);
let br = padded.get_pixel(5, 5, 0).expect("test: bottom-right pixel");
assert!(
(br - 0.5).abs() < 1e-10,
"Replicate pad bottom-right: expected 0.5, got {}",
br
);
}
#[test]
fn test_pad_wrap() {
let data = vec![1.0, 2.0, 3.0, 4.0];
let img = Image::from_grayscale(2, 2, &data).expect("test: image creation should succeed");
let padded = pad(&img, 1, 1, 1, 1, PadMode::Wrap).expect("test: wrap pad should succeed");
assert_eq!(padded.width(), 4);
assert_eq!(padded.height(), 4);
let wrapped = padded.get_pixel(0, 0, 0).expect("test: wrapped pixel");
assert!(
(wrapped - 4.0).abs() < 1e-10,
"Wrap pad (-1,-1) should be 4.0: got {}",
wrapped
);
}
#[test]
fn test_pad_reflect_interior_preserved() {
let data = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0];
let img = Image::from_grayscale(3, 3, &data).expect("test: image creation should succeed");
let padded =
pad(&img, 1, 1, 1, 1, PadMode::Reflect).expect("test: reflect pad should succeed");
assert_eq!(padded.width(), 5);
assert_eq!(padded.height(), 5);
for row in 0..3 {
for col in 0..3 {
let orig = img.get_pixel(row, col, 0).expect("test: orig pixel");
let padded_val = padded
.get_pixel(row + 1, col + 1, 0)
.expect("test: padded pixel");
assert!(
(orig - padded_val).abs() < 1e-10,
"Reflect pad interior at ({}, {}): expected {}, got {}",
row,
col,
orig,
padded_val
);
}
}
}
#[test]
fn test_affine_identity() {
let img = make_test_image(8, 8);
let matrix = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
let result = affine_transform(&img, &matrix).expect("test: identity affine should succeed");
assert_eq!(result.width(), 8);
assert_eq!(result.height(), 8);
for row in 1..7 {
for col in 1..7 {
let orig = img.get_pixel(row, col, 0).expect("test: orig pixel");
let aff = result.get_pixel(row, col, 0).expect("test: affine pixel");
assert!(
(orig - aff).abs() < 1e-6,
"Identity affine at ({}, {}): expected {}, got {}",
row,
col,
orig,
aff
);
}
}
}
#[test]
fn test_affine_translation() {
let mut data = vec![0.0; 8 * 8];
data[3 * 8 + 3] = 1.0; let img = Image::from_grayscale(8, 8, &data).expect("test: image creation should succeed");
let matrix = [[1.0, 0.0, 1.0], [0.0, 1.0, 1.0]];
let result =
affine_transform(&img, &matrix).expect("test: translation affine should succeed");
let shifted = result.get_pixel(2, 2, 0).expect("test: shifted pixel");
assert!(
(shifted - 1.0).abs() < 1e-6,
"Translated bright pixel should appear at (2,2): got {}",
shifted
);
}
#[test]
fn test_affine_scale() {
let img = make_test_image(8, 8);
let matrix = [[0.5, 0.0, 0.0], [0.0, 0.5, 0.0]];
let result = affine_transform(&img, &matrix).expect("test: scale affine should succeed");
let orig = img.get_pixel(0, 0, 0).expect("test: orig pixel");
let scaled = result.get_pixel(0, 0, 0).expect("test: scaled pixel");
assert!(
(orig - scaled).abs() < 1e-6,
"Affine scale origin: expected {}, got {}",
orig,
scaled
);
}
#[test]
fn test_resize_rgb_image() {
let data = vec![0.5; 4 * 4 * 3];
let img = Image::from_rgb(4, 4, &data).expect("test: RGB image creation should succeed");
let resized =
resize_bilinear(&img, 8, 8).expect("test: RGB bilinear resize should succeed");
assert_eq!(resized.width(), 8);
assert_eq!(resized.height(), 8);
assert_eq!(resized.channels(), 3);
for row in 0..8 {
for col in 0..8 {
for ch in 0..3 {
let v = resized
.get_pixel(row, col, ch)
.expect("test: pixel access should succeed");
assert!(
(v - 0.5).abs() < 1e-6,
"RGB resize constant at ({}, {}, {}): got {}",
row,
col,
ch,
v
);
}
}
}
}
#[test]
fn test_reflect_coordinate_values() {
assert_eq!(reflect_coordinate(3, 10), 3);
assert_eq!(reflect_coordinate(0, 10), 0);
assert_eq!(reflect_coordinate(9, 10), 9);
assert_eq!(reflect_coordinate(-1, 10), 1);
assert_eq!(reflect_coordinate(-2, 10), 2);
assert_eq!(reflect_coordinate(10, 10), 8);
assert_eq!(reflect_coordinate(11, 10), 7);
assert_eq!(reflect_coordinate(-1, 1), 0);
assert_eq!(reflect_coordinate(1, 1), 0);
}
#[test]
fn test_map_pad_coordinate_modes() {
assert_eq!(map_pad_coordinate(5, 2, 10, PadMode::Constant), Some(3));
assert_eq!(map_pad_coordinate(5, 2, 10, PadMode::Reflect), Some(3));
assert_eq!(map_pad_coordinate(5, 2, 10, PadMode::Replicate), Some(3));
assert_eq!(map_pad_coordinate(5, 2, 10, PadMode::Wrap), Some(3));
assert_eq!(map_pad_coordinate(0, 2, 10, PadMode::Constant), None);
assert_eq!(map_pad_coordinate(0, 2, 10, PadMode::Replicate), Some(0));
assert_eq!(map_pad_coordinate(15, 2, 10, PadMode::Constant), None);
assert_eq!(map_pad_coordinate(15, 2, 10, PadMode::Replicate), Some(9));
}
}