use crate::core::image::{Rect, RgbImage, Size};
pub fn apply_orientation(image: &mut RgbImage, orientation: u16) {
match orientation {
1 => {} 2 => flip_horizontal(image),
3 => rotate_180(image),
4 => flip_vertical(image),
5 => {
flip_horizontal(image);
rotate_90_ccw(image);
}
6 => rotate_90_cw(image),
7 => {
flip_horizontal(image);
rotate_90_cw(image);
}
8 => rotate_90_ccw(image),
_ => tracing::warn!(
"Unknown orientation value: {}, skipping transform",
orientation
),
}
}
pub fn flip_horizontal(image: &mut RgbImage) {
let w = image.width() as usize;
let h = image.height() as usize;
for row in 0..h {
for col in 0..w / 2 {
let a = (row * w + col) * 3;
let b = (row * w + (w - 1 - col)) * 3;
image.data.swap(a, b);
image.data.swap(a + 1, b + 1);
image.data.swap(a + 2, b + 2);
}
}
}
pub fn flip_vertical(image: &mut RgbImage) {
let w = image.width() as usize;
let h = image.height() as usize;
for row in 0..h / 2 {
for col in 0..w {
let a = (row * w + col) * 3;
let b = ((h - 1 - row) * w + col) * 3;
image.data.swap(a, b);
image.data.swap(a + 1, b + 1);
image.data.swap(a + 2, b + 2);
}
}
}
pub fn rotate_180(image: &mut RgbImage) {
let n = image.data.len();
let mut i = 0;
let mut j = n - 3;
while i < j {
image.data.swap(i, j);
image.data.swap(i + 1, j + 1);
image.data.swap(i + 2, j + 2);
i += 3;
j -= 3;
}
}
pub fn rotate_90_cw(image: &mut RgbImage) {
let old_w = image.width() as usize;
let old_h = image.height() as usize;
let new_w = old_h;
let new_h = old_w;
let mut new_data = vec![0u16; new_w * new_h * 3];
for old_row in 0..old_h {
for old_col in 0..old_w {
let new_row = old_col;
let new_col = old_h - 1 - old_row;
let src = (old_row * old_w + old_col) * 3;
let dst = (new_row * new_w + new_col) * 3;
new_data[dst] = image.data[src];
new_data[dst + 1] = image.data[src + 1];
new_data[dst + 2] = image.data[src + 2];
}
}
image.data = new_data;
image.set_size(Size::new(new_w as u32, new_h as u32));
}
pub fn rotate_90_ccw(image: &mut RgbImage) {
let old_w = image.width() as usize;
let old_h = image.height() as usize;
let new_w = old_h;
let new_h = old_w;
let mut new_data = vec![0u16; new_w * new_h * 3];
for old_row in 0..old_h {
for old_col in 0..old_w {
let new_row = old_w - 1 - old_col;
let new_col = old_row;
let src = (old_row * old_w + old_col) * 3;
let dst = (new_row * new_w + new_col) * 3;
new_data[dst] = image.data[src];
new_data[dst + 1] = image.data[src + 1];
new_data[dst + 2] = image.data[src + 2];
}
}
image.data = new_data;
image.set_size(Size::new(new_w as u32, new_h as u32));
}
pub fn apply_crop(image: &mut RgbImage, crop: Rect) {
let x = crop.origin.x as usize;
let y = crop.origin.y as usize;
let w = crop.size.width as usize;
let h = crop.size.height as usize;
if x + w <= image.width() as usize && y + h <= image.height() as usize {
let img_width = image.width() as usize;
let mut new_data = Vec::with_capacity(w * h * 3);
for row in 0..h {
let src_base = ((y + row) * img_width + x) * 3;
new_data.extend_from_slice(&image.data[src_base..src_base + w * 3]);
}
image.set_size(Size::new(w as u32, h as u32));
image.data = new_data;
} else {
tracing::warn!(
"Crop region out of bounds: {:?} vs {}x{}",
crop,
image.width(),
image.height()
);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::image::{Point, Size};
fn make_image(w: u32, h: u32, data: Vec<u16>) -> RgbImage {
RgbImage::new(w, h, data)
}
#[test]
fn test_flip_horizontal_2x2() {
let mut img = make_image(2, 2, vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]);
flip_horizontal(&mut img);
assert_eq!(img.data, vec![4, 5, 6, 1, 2, 3, 10, 11, 12, 7, 8, 9]);
}
#[test]
fn test_flip_vertical_2x2() {
let mut img = make_image(2, 2, vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]);
flip_vertical(&mut img);
assert_eq!(img.data, vec![7, 8, 9, 10, 11, 12, 1, 2, 3, 4, 5, 6]);
}
#[test]
fn test_rotate_180_2x2() {
let mut img = make_image(2, 2, vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]);
rotate_180(&mut img);
assert_eq!(img.data, vec![10, 11, 12, 7, 8, 9, 4, 5, 6, 1, 2, 3]);
}
#[test]
fn test_rotate_90_cw_2x2() {
let mut img = make_image(2, 2, vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]);
rotate_90_cw(&mut img);
assert_eq!(img.width(), 2);
assert_eq!(img.height(), 2);
assert_eq!(img.data, vec![7, 8, 9, 1, 2, 3, 10, 11, 12, 4, 5, 6]);
}
#[test]
fn test_rotate_90_ccw_2x2() {
let mut img = make_image(2, 2, vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]);
rotate_90_ccw(&mut img);
assert_eq!(img.width(), 2);
assert_eq!(img.height(), 2);
assert_eq!(img.data, vec![4, 5, 6, 10, 11, 12, 1, 2, 3, 7, 8, 9]);
}
#[test]
fn test_rotate_90_cw_non_square() {
let mut img = make_image(
3,
2,
vec![1, 0, 0, 2, 0, 0, 3, 0, 0, 4, 0, 0, 5, 0, 0, 6, 0, 0],
);
rotate_90_cw(&mut img);
assert_eq!(img.width(), 2);
assert_eq!(img.height(), 3);
}
#[test]
fn test_apply_orientation_identity() {
let mut img = make_image(2, 2, vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]);
let original = img.data.clone();
apply_orientation(&mut img, 1);
assert_eq!(img.data, original);
}
#[test]
fn test_crop_basic() {
let mut data = Vec::with_capacity(4 * 4 * 3);
for i in 0..16 {
data.push(i as u16);
data.push(0);
data.push(0);
}
let mut img = make_image(4, 4, data);
apply_crop(&mut img, Rect::new(Point::new(1, 1), Size::new(2, 2)));
assert_eq!(img.width(), 2);
assert_eq!(img.height(), 2);
assert_eq!(img.data[0], 5); assert_eq!(img.data[3], 6); }
#[test]
fn test_crop_out_of_bounds() {
let mut img = make_image(4, 4, vec![0u16; 4 * 4 * 3]);
let original_size = img.size();
apply_crop(&mut img, Rect::new(Point::new(3, 3), Size::new(2, 2)));
assert_eq!(img.size(), original_size);
}
}