use crate::error::{ADError, ADResult};
use crate::ndarray::{NDArray, NDDataBuffer};
use crate::color_layout::ColorLayout;
use crate::pixel_cast::{PixelCast, with_buffer};
pub fn crop_roi(
source: &NDArray,
src_layout: &ColorLayout,
min_x: usize,
min_y: usize,
size_x: usize,
size_y: usize,
) -> ADResult<NDArray> {
if source.codec.is_some() {
return Err(ADError::UnsupportedConversion(
"crop_roi: cannot operate on compressed (codec) data".into(),
));
}
let source_data = &source.data;
let min_x = min_x.min(src_layout.size_x);
let min_y = min_y.min(src_layout.size_y);
let size_x = size_x.min(src_layout.size_x.saturating_sub(min_x));
let size_y = size_y.min(src_layout.size_y.saturating_sub(min_y));
if size_x == 0 || size_y == 0 {
let dst_layout = ColorLayout {
color_mode: src_layout.color_mode,
size_x: 0,
size_y: 0,
};
let dims = dst_layout.make_dims();
return Ok(NDArray::new(dims, source_data.data_type()));
}
let dst_layout = ColorLayout {
color_mode: src_layout.color_mode,
size_x,
size_y,
};
let dims = dst_layout.make_dims();
let data_type = source_data.data_type();
let mut result = NDArray::new(dims, data_type);
if min_x == 0 && min_y == 0 && size_x == src_layout.size_x && size_y == src_layout.size_y {
result.data = source_data.clone();
return Ok(result);
}
let num_elements = dst_layout.num_elements();
with_buffer!(source_data, |src_v| {
let mut dst_data = NDDataBuffer::zeros(data_type, num_elements);
crate::pixel_cast::with_buffer_mut!(&mut dst_data, |dst_v| {
for y in 0..size_y {
let src_y = min_y + y;
for x in 0..size_x {
let src_x = min_x + x;
for ch in 0..src_layout.num_colors() {
let si = src_layout.index(src_x, src_y, ch);
let di = dst_layout.index(x, y, ch);
let val = PixelCast::to_f64(src_v[si]);
dst_v[di] = PixelCast::from_f64(val);
}
}
}
});
result.data = dst_data;
});
let (x_dim, y_dim) = match src_layout.color_mode {
crate::driver::ColorMode::Mono => (0, 1),
crate::driver::ColorMode::RGB1 => (1, 2),
crate::driver::ColorMode::RGB2 => (0, 2),
crate::driver::ColorMode::RGB3 => (0, 1),
_ => (0, 1),
};
if x_dim < result.dims.len() {
result.dims[x_dim].offset = source.dims.get(x_dim).map(|d| d.offset).unwrap_or(0) + min_x;
}
if y_dim < result.dims.len() {
result.dims[y_dim].offset = source.dims.get(y_dim).map(|d| d.offset).unwrap_or(0) + min_y;
}
Ok(result)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::driver::ColorMode;
use crate::ndarray::NDDimension;
fn make_mono_array(size_x: usize, size_y: usize, data: NDDataBuffer) -> NDArray {
let dims = vec![NDDimension::new(size_x), NDDimension::new(size_y)];
let mut arr = NDArray::new(dims, data.data_type());
arr.data = data;
arr
}
fn make_rgb1_array(size_x: usize, size_y: usize, data: NDDataBuffer) -> NDArray {
let dims = vec![
NDDimension::new(3),
NDDimension::new(size_x),
NDDimension::new(size_y),
];
let mut arr = NDArray::new(dims, data.data_type());
arr.data = data;
arr
}
#[test]
fn test_crop_identity_mono() {
let layout = ColorLayout {
color_mode: ColorMode::Mono,
size_x: 4,
size_y: 3,
};
let data: Vec<f64> = (0..12).map(|x| x as f64).collect();
let arr = make_mono_array(4, 3, NDDataBuffer::F64(data.clone()));
let result = crop_roi(&arr, &layout, 0, 0, 4, 3).unwrap();
if let NDDataBuffer::F64(v) = &result.data {
assert_eq!(v, &data);
}
}
#[test]
fn test_crop_subregion_mono() {
let layout = ColorLayout {
color_mode: ColorMode::Mono,
size_x: 4,
size_y: 4,
};
let data: Vec<f64> = (0..16).map(|x| x as f64).collect();
let arr = make_mono_array(4, 4, NDDataBuffer::F64(data));
let result = crop_roi(&arr, &layout, 1, 1, 2, 2).unwrap();
if let NDDataBuffer::F64(v) = &result.data {
assert_eq!(v, &[5.0, 6.0, 9.0, 10.0]);
}
assert_eq!(result.dims.len(), 2);
assert_eq!(result.dims[0].size, 2);
assert_eq!(result.dims[1].size, 2);
assert_eq!(result.dims[0].offset, 1);
assert_eq!(result.dims[1].offset, 1);
}
#[test]
fn test_crop_identity_rgb1() {
let layout = ColorLayout {
color_mode: ColorMode::RGB1,
size_x: 2,
size_y: 2,
};
let data: Vec<u8> = (0..12).collect();
let arr = make_rgb1_array(2, 2, NDDataBuffer::U8(data.clone()));
let result = crop_roi(&arr, &layout, 0, 0, 2, 2).unwrap();
if let NDDataBuffer::U8(v) = &result.data {
assert_eq!(v, &data);
}
}
#[test]
fn test_crop_subregion_rgb1() {
let layout = ColorLayout {
color_mode: ColorMode::RGB1,
size_x: 3,
size_y: 3,
};
let mut data = vec![0u8; 27];
for y in 0..3 {
for x in 0..3 {
let base = (y * 3 + x) * 3;
data[base] = (x * 10 + y) as u8;
data[base + 1] = (x * 10 + y + 100) as u8;
data[base + 2] = (x * 10 + y + 200) as u8;
}
}
let arr = make_rgb1_array(3, 3, NDDataBuffer::U8(data));
let result = crop_roi(&arr, &layout, 1, 1, 2, 2).unwrap();
if let NDDataBuffer::U8(v) = &result.data {
assert_eq!(v.len(), 12);
assert_eq!(v[0], 11);
assert_eq!(v[1], 111);
}
assert_eq!(result.dims.len(), 3);
assert_eq!(result.dims[0].size, 3);
assert_eq!(result.dims[1].size, 2);
assert_eq!(result.dims[2].size, 2);
}
#[test]
fn test_crop_bounds_clamping() {
let layout = ColorLayout {
color_mode: ColorMode::Mono,
size_x: 4,
size_y: 4,
};
let data: Vec<u8> = (0..16).collect();
let arr = make_mono_array(4, 4, NDDataBuffer::U8(data));
let result = crop_roi(&arr, &layout, 2, 2, 10, 10).unwrap();
assert_eq!(result.dims[0].size, 2);
assert_eq!(result.dims[1].size, 2);
}
#[test]
fn test_crop_rejects_compressed() {
let layout = ColorLayout {
color_mode: ColorMode::Mono,
size_x: 4,
size_y: 4,
};
let mut arr = make_mono_array(4, 4, NDDataBuffer::U8(vec![0; 16]));
arr.codec = Some(crate::codec::Codec {
name: crate::codec::CodecName::LZ4,
compressed_size: 10,
level: 0,
shuffle: 0,
compressor: 0,
});
assert!(crop_roi(&arr, &layout, 0, 0, 4, 4).is_err());
}
}