use crate::core::colour_models::*;
use crate::core::traits::PixelBound;
use ndarray::prelude::*;
use ndarray::{s, Data, DataMut, OwnedRepr, RawDataClone, ViewRepr};
use num_traits::cast::{FromPrimitive, NumCast};
use num_traits::Num;
use std::{fmt, hash, marker::PhantomData};
pub type Image<T, C> = ImageBase<OwnedRepr<T>, C>;
pub type ImageView<'a, T, C> = ImageBase<ViewRepr<&'a T>, C>;
pub struct ImageBase<T, C>
where
C: ColourModel,
T: Data,
{
pub data: ArrayBase<T, Ix3>,
pub(crate) model: PhantomData<C>,
}
impl<T, U, C> ImageBase<U, C>
where
U: Data<Elem = T>,
T: Copy + Clone + FromPrimitive + Num + NumCast + PixelBound,
C: ColourModel,
{
pub fn into_type<T2>(self) -> Image<T2, C>
where
T2: Copy + Clone + FromPrimitive + Num + NumCast + PixelBound,
{
let rescale = |x: &T| {
let scaled = normalise_pixel_value(*x)
* (T2::max_pixel() - T2::min_pixel())
.to_f64()
.unwrap_or(0.0f64);
T2::from_f64(scaled).unwrap_or_else(T2::zero) + T2::min_pixel()
};
let data = self.data.map(rescale);
Image::<_, C>::from_data(data)
}
}
impl<S, T, C> ImageBase<S, C>
where
S: Data<Elem = T>,
T: Clone,
C: ColourModel,
{
pub fn to_owned(&self) -> Image<T, C> {
Image {
data: self.data.to_owned(),
model: PhantomData,
}
}
pub fn from_shape_data(rows: usize, cols: usize, data: Vec<T>) -> Image<T, C> {
let data = Array3::from_shape_vec((rows, cols, C::channels()), data).unwrap();
Image {
data,
model: PhantomData,
}
}
}
impl<T, C> Image<T, C>
where
T: Clone + Num,
C: ColourModel,
{
pub fn new(rows: usize, columns: usize) -> Self {
Image {
data: Array3::zeros((rows, columns, C::channels())),
model: PhantomData,
}
}
}
impl<T, U, C> ImageBase<T, C>
where
T: Data<Elem = U>,
C: ColourModel,
{
pub fn from_data(data: ArrayBase<T, Ix3>) -> Self {
Self {
data,
model: PhantomData,
}
}
pub fn rows(&self) -> usize {
self.data.shape()[0]
}
pub fn cols(&self) -> usize {
self.data.shape()[1]
}
pub fn channels(&self) -> usize {
C::channels()
}
pub fn pixel(&self, row: usize, col: usize) -> ArrayView<U, Ix1> {
self.data.slice(s![row, col, ..])
}
pub fn into_type_raw<C2>(self) -> ImageBase<T, C2>
where
C2: ColourModel,
{
assert_eq!(C2::channels(), C::channels());
ImageBase::<T, C2>::from_data(self.data)
}
}
impl<T, U, C> ImageBase<T, C>
where
T: DataMut<Elem = U>,
C: ColourModel,
{
pub fn pixel_mut(&mut self, row: usize, col: usize) -> ArrayViewMut<U, Ix1> {
self.data.slice_mut(s![row, col, ..])
}
}
impl<T, U, C> fmt::Debug for ImageBase<U, C>
where
U: Data<Elem = T>,
T: fmt::Debug,
C: ColourModel,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "ColourModel={:?} Data={:?}", self.model, self.data)?;
Ok(())
}
}
impl<T, U, C> PartialEq<ImageBase<U, C>> for ImageBase<U, C>
where
U: Data<Elem = T>,
T: PartialEq,
C: ColourModel,
{
fn eq(&self, other: &Self) -> bool {
self.model == other.model && self.data == other.data
}
}
impl<S, C> Clone for ImageBase<S, C>
where
S: RawDataClone + Data,
C: ColourModel,
{
fn clone(&self) -> Self {
Self {
data: self.data.clone(),
model: PhantomData,
}
}
fn clone_from(&mut self, other: &Self) {
self.data.clone_from(&other.data)
}
}
impl<'a, S, C> hash::Hash for ImageBase<S, C>
where
S: Data,
S::Elem: hash::Hash,
C: ColourModel,
{
fn hash<H: hash::Hasher>(&self, state: &mut H) {
self.model.hash(state);
self.data.hash(state);
}
}
pub fn normalise_pixel_value<T>(t: T) -> f64
where
T: PixelBound + Num + NumCast,
{
let numerator = (t + T::min_pixel()).to_f64();
let denominator = (T::max_pixel() - T::min_pixel()).to_f64();
let numerator = numerator.unwrap_or(0.0f64);
let denominator = denominator.unwrap_or(1.0f64);
numerator / denominator
}
#[cfg(test)]
mod tests {
use super::*;
use ndarray::arr1;
#[test]
fn image_consistency_checks() {
let i = Image::<u8, RGB>::new(1, 2);
assert_eq!(i.rows(), 1);
assert_eq!(i.cols(), 2);
assert_eq!(i.channels(), 3);
assert_eq!(i.channels(), i.data.shape()[2]);
}
#[test]
fn image_type_conversion() {
let mut i = Image::<u8, RGB>::new(1, 1);
i.pixel_mut(0, 0)
.assign(&arr1(&[u8::max_value(), 0, u8::max_value() / 3]));
let t: Image<u16, RGB> = i.into_type();
assert_eq!(
t.pixel(0, 0),
arr1(&[u16::max_value(), 0, u16::max_value() / 3])
);
}
}