use super::error::{RadSymError, Result};
use super::scalar::Scalar;
#[derive(Copy, Clone)]
pub struct ImageView<'a, T> {
data: &'a [T],
width: usize,
height: usize,
stride: usize,
}
impl<'a, T> ImageView<'a, T> {
pub fn from_slice(data: &'a [T], width: usize, height: usize) -> Result<Self> {
Self::new(data, width, height, width)
}
pub fn new(data: &'a [T], width: usize, height: usize, stride: usize) -> Result<Self> {
let needed = required_len(width, height, stride)?;
if data.len() < needed {
return Err(RadSymError::BufferTooSmall {
needed,
got: data.len(),
});
}
Ok(Self {
data,
width,
height,
stride,
})
}
#[inline]
pub fn width(&self) -> usize {
self.width
}
#[inline]
pub fn height(&self) -> usize {
self.height
}
#[inline]
pub fn stride(&self) -> usize {
self.stride
}
#[inline]
pub fn as_slice(&self) -> &'a [T] {
self.data
}
#[inline]
pub fn get(&self, x: usize, y: usize) -> Option<&'a T> {
if x >= self.width || y >= self.height {
return None;
}
self.data.get(y * self.stride + x)
}
#[inline]
pub fn row(&self, y: usize) -> Option<&'a [T]> {
if y >= self.height {
return None;
}
let start = y * self.stride;
self.data.get(start..start + self.width)
}
pub fn roi(&self, x: usize, y: usize, width: usize, height: usize) -> Result<ImageView<'a, T>> {
if width == 0 || height == 0 {
return Err(RadSymError::InvalidDimensions { width, height });
}
let end_x = x.checked_add(width).ok_or(RadSymError::InvalidDimensions {
width: self.width,
height: self.height,
})?;
let end_y = y
.checked_add(height)
.ok_or(RadSymError::InvalidDimensions {
width: self.width,
height: self.height,
})?;
if end_x > self.width || end_y > self.height {
return Err(RadSymError::InvalidDimensions {
width: self.width,
height: self.height,
});
}
let start = y * self.stride + x;
let data = &self.data[start..];
ImageView::new(data, width, height, self.stride)
}
}
impl ImageView<'_, f32> {
#[inline]
pub fn sample(&self, x: Scalar, y: Scalar) -> Option<Scalar> {
if x < 0.0 || y < 0.0 {
return None;
}
let ix = x as usize;
let iy = y as usize;
if ix + 1 >= self.width || iy + 1 >= self.height {
return None;
}
let fx = x - ix as Scalar;
let fy = y - iy as Scalar;
let v00 = self.data[iy * self.stride + ix];
let v10 = self.data[iy * self.stride + ix + 1];
let v01 = self.data[(iy + 1) * self.stride + ix];
let v11 = self.data[(iy + 1) * self.stride + ix + 1];
let top = v00 + fx * (v10 - v00);
let bot = v01 + fx * (v11 - v01);
Some(top + fy * (bot - top))
}
}
impl ImageView<'_, u8> {
#[inline]
pub fn sample(&self, x: Scalar, y: Scalar) -> Option<Scalar> {
if x < 0.0 || y < 0.0 {
return None;
}
let ix = x as usize;
let iy = y as usize;
if ix + 1 >= self.width || iy + 1 >= self.height {
return None;
}
let fx = x - ix as Scalar;
let fy = y - iy as Scalar;
let v00 = self.data[iy * self.stride + ix] as Scalar;
let v10 = self.data[iy * self.stride + ix + 1] as Scalar;
let v01 = self.data[(iy + 1) * self.stride + ix] as Scalar;
let v11 = self.data[(iy + 1) * self.stride + ix + 1] as Scalar;
let top = v00 + fx * (v10 - v00);
let bot = v01 + fx * (v11 - v01);
Some(top + fy * (bot - top))
}
}
#[derive(Debug, Clone)]
pub struct OwnedImage<T> {
data: Vec<T>,
width: usize,
height: usize,
}
impl<T> OwnedImage<T> {
pub fn from_vec(data: Vec<T>, width: usize, height: usize) -> Result<Self> {
if width == 0 || height == 0 {
return Err(RadSymError::InvalidDimensions { width, height });
}
let needed = width
.checked_mul(height)
.ok_or(RadSymError::InvalidDimensions { width, height })?;
if data.len() < needed {
return Err(RadSymError::BufferTooSmall {
needed,
got: data.len(),
});
}
Ok(Self {
data,
width,
height,
})
}
pub fn view(&self) -> ImageView<'_, T> {
ImageView {
data: &self.data,
width: self.width,
height: self.height,
stride: self.width,
}
}
#[inline]
pub fn width(&self) -> usize {
self.width
}
#[inline]
pub fn height(&self) -> usize {
self.height
}
#[inline]
pub fn data(&self) -> &[T] {
&self.data
}
#[inline]
pub fn data_mut(&mut self) -> &mut [T] {
&mut self.data
}
#[inline]
pub fn into_data(self) -> Vec<T> {
self.data
}
}
impl<T: Clone + Default> OwnedImage<T> {
pub fn zeros(width: usize, height: usize) -> Result<Self> {
if width == 0 || height == 0 {
return Err(RadSymError::InvalidDimensions { width, height });
}
let len = width
.checked_mul(height)
.ok_or(RadSymError::InvalidDimensions { width, height })?;
Ok(Self {
data: vec![T::default(); len],
width,
height,
})
}
}
impl OwnedImage<Scalar> {
#[inline]
pub fn get_mut(&mut self, x: usize, y: usize) -> Option<&mut Scalar> {
if x >= self.width || y >= self.height {
return None;
}
self.data.get_mut(y * self.width + x)
}
#[inline]
pub fn get(&self, x: usize, y: usize) -> Option<Scalar> {
if x >= self.width || y >= self.height {
return None;
}
self.data.get(y * self.width + x).copied()
}
}
fn required_len(width: usize, height: usize, stride: usize) -> Result<usize> {
if width == 0 || height == 0 {
return Err(RadSymError::InvalidDimensions { width, height });
}
if stride < width {
return Err(RadSymError::InvalidStride { width, stride });
}
let needed = (height - 1)
.checked_mul(stride)
.and_then(|v| v.checked_add(width))
.ok_or(RadSymError::InvalidDimensions { width, height })?;
Ok(needed)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn view_from_slice() {
let data: Vec<f32> = (0..12).map(|i| i as f32).collect();
let view = ImageView::from_slice(&data, 4, 3).unwrap();
assert_eq!(view.width(), 4);
assert_eq!(view.height(), 3);
assert_eq!(*view.get(2, 1).unwrap(), 6.0);
}
#[test]
fn view_with_stride() {
let data = [1.0f32, 2.0, 3.0, 0.0, 4.0, 5.0, 6.0, 0.0];
let view = ImageView::new(&data, 3, 2, 4).unwrap();
assert_eq!(*view.get(0, 1).unwrap(), 4.0);
assert_eq!(*view.get(2, 1).unwrap(), 6.0);
}
#[test]
fn view_out_of_bounds() {
let data = [0.0f32; 4];
let view = ImageView::from_slice(&data, 2, 2).unwrap();
assert!(view.get(2, 0).is_none());
assert!(view.get(0, 2).is_none());
}
#[test]
fn bilinear_sample_f32() {
let data = [0.0f32, 1.0, 2.0, 3.0];
let view = ImageView::from_slice(&data, 2, 2).unwrap();
let val = view.sample(0.5, 0.5).unwrap();
assert!((val - 1.5).abs() < 1e-6, "expected 1.5, got {val}");
}
#[test]
fn bilinear_sample_u8() {
let data = [0u8, 100, 200, 50];
let view = ImageView::from_slice(&data, 2, 2).unwrap();
let val = view.sample(0.0, 0.0).unwrap();
assert!((val - 0.0).abs() < 1e-3);
}
#[test]
fn owned_image_zeros() {
let img: OwnedImage<f32> = OwnedImage::zeros(8, 8).unwrap();
assert_eq!(img.width(), 8);
assert_eq!(img.height(), 8);
assert_eq!(img.data().len(), 64);
assert!(img.data().iter().all(|&v| v == 0.0));
}
#[test]
fn owned_image_view_roundtrip() {
let data: Vec<f32> = (0..6).map(|i| i as f32).collect();
let img = OwnedImage::from_vec(data, 3, 2).unwrap();
let view = img.view();
assert_eq!(*view.get(1, 0).unwrap(), 1.0);
assert_eq!(*view.get(2, 1).unwrap(), 5.0);
}
#[test]
fn roi_extraction() {
let data: Vec<f32> = (0..16).map(|i| i as f32).collect();
let view = ImageView::from_slice(&data, 4, 4).unwrap();
let sub = view.roi(1, 1, 2, 2).unwrap();
assert_eq!(sub.width(), 2);
assert_eq!(sub.height(), 2);
assert_eq!(*sub.get(0, 0).unwrap(), 5.0); assert_eq!(*sub.get(1, 1).unwrap(), 10.0); }
#[test]
fn buffer_too_small() {
let data = [0.0f32; 3];
let result = ImageView::from_slice(&data, 2, 2);
assert!(result.is_err());
}
}