bingus 0.10.0

databending made easy
Documentation
use std::{
    borrow::Cow,
    convert::Infallible,
    iter::once,
    ops::{Add, Deref, DerefMut, Div, Mul, Sub},
};

use cfg_if::cfg_if;
pub use image::*;
use num::{
    traits::{FromBytes, ToBytes},
    Zero,
};
#[cfg(feature = "rayon")]
use rayon::iter::ParallelIterator;
use thiserror::Error;

use crate::{Bendable, FromDataBytes, IntoDataBytes, TryFromDataBytes};

impl<P: Pixel> IntoDataBytes for ImageBuffer<P, Vec<P::Subpixel>>
where
    Vec<P::Subpixel>: Deref<Target = [P::Subpixel]>,
    P::Subpixel: ToBytes,
{
    fn into_data_bytes(self) -> crate::Bytes {
        self.iter()
            .flat_map(|subpixel| subpixel.to_ne_bytes().as_ref().to_vec())
            .collect()
    }
}

#[derive(PartialEq, Eq)]
#[cfg_attr(debug_assertions, derive(Debug))]
pub struct Dimensions {
    pub width: u32,
    pub height: u32,
}

impl Dimensions {
    pub fn square(width: u32) -> Self {
        Self {
            width,
            height: width,
        }
    }
}

impl Div<Dimensions> for Dimensions {
    type Output = Dimensions;
    fn div(self, rhs: Dimensions) -> Self::Output {
        Dimensions {
            width: self.width / rhs.width,
            height: self.height / rhs.height,
        }
    }
}

impl Div<u32> for Dimensions {
    type Output = Dimensions;
    fn div(self, rhs: u32) -> Self::Output {
        Dimensions {
            width: self.width / rhs,
            height: self.height / rhs,
        }
    }
}

impl Mul<Dimensions> for Dimensions {
    type Output = Dimensions;
    fn mul(self, rhs: Dimensions) -> Self::Output {
        Dimensions {
            width: self.width * rhs.width,
            height: self.height * rhs.height,
        }
    }
}

impl Mul<u32> for Dimensions {
    type Output = Dimensions;
    fn mul(self, rhs: u32) -> Self::Output {
        Dimensions {
            width: self.width * rhs,
            height: self.height * rhs,
        }
    }
}

impl Add<Dimensions> for Dimensions {
    type Output = Dimensions;
    fn add(self, rhs: Dimensions) -> Self::Output {
        Dimensions {
            width: self.width + rhs.width,
            height: self.height + rhs.height,
        }
    }
}

impl Add<u32> for Dimensions {
    type Output = Dimensions;
    fn add(self, rhs: u32) -> Self::Output {
        Dimensions {
            width: self.width + rhs,
            height: self.height + rhs,
        }
    }
}

impl Sub<Dimensions> for Dimensions {
    type Output = Dimensions;
    fn sub(self, rhs: Dimensions) -> Self::Output {
        Dimensions {
            width: self.width - rhs.width,
            height: self.height - rhs.height,
        }
    }
}

impl Sub<u32> for Dimensions {
    type Output = Dimensions;
    fn sub(self, rhs: u32) -> Self::Output {
        Dimensions {
            width: self.width - rhs,
            height: self.height - rhs,
        }
    }
}

impl<P: Pixel> TryFromDataBytes for ImageBuffer<P, Vec<P::Subpixel>>
where
    Vec<P::Subpixel>: Deref<Target = [P::Subpixel]>,
    P::Subpixel: ToBytes + FromBytes + Zero,
    <P::Subpixel as FromBytes>::Bytes: for<'a> TryFrom<&'a [u8]>,
{
    type Error = Infallible;
    type Format = Dimensions;
    fn try_from_data_bytes(
        bytes: crate::Bytes,
        format: Self::Format,
        crop: crate::Crop,
    ) -> Result<Self, Self::Error> {
        ImageBuffer::from_raw(
            format.width,
            format.height,
            match crop {
                // TODO: separate outer crop from inner crop
                crate::Crop::End => bytes
                    .chunks_exact(P::Subpixel::zero().to_ne_bytes().as_ref().len())
                    .map(|p| {
                        P::Subpixel::from_ne_bytes(
                            &match <P::Subpixel as FromBytes>::Bytes::try_from(p) {
                                Ok(v) => v,
                                Err(_) => unreachable!("you messed up chunk size!"),
                            },
                        )
                    })
                    .chain(once(P::Subpixel::zero()).cycle())
                    .take(
                        format.width as usize * format.height as usize * P::CHANNEL_COUNT as usize,
                    )
                    .collect::<Vec<P::Subpixel>>(),
                crate::Crop::Start => bytes
                    .rchunks_exact(P::Subpixel::zero().to_ne_bytes().as_ref().len())
                    .map(|p| {
                        P::Subpixel::from_ne_bytes(
                            &match <P::Subpixel as FromBytes>::Bytes::try_from(p) {
                                Ok(v) => v,
                                Err(_) => unreachable!("you messed up chunk size!"),
                            },
                        )
                    })
                    .chain(once(P::Subpixel::zero()).cycle())
                    .take(
                        format.width as usize * format.height as usize * P::CHANNEL_COUNT as usize,
                    )
                    .collect::<Vec<P::Subpixel>>(),
            },
        )
        .ok_or_else(|| unreachable!())
    }
}

impl<P: Pixel> Bendable for ImageBuffer<P, Vec<P::Subpixel>>
where
    Vec<P::Subpixel>: Deref<Target = [P::Subpixel]> + DerefMut,
    P::Subpixel: ToBytes + FromBytes + Send + Sync,
    <P::Subpixel as FromBytes>::Bytes: for<'a> TryFrom<&'a [u8]>,
    P: Send + Sync,
{
    type Unit = P;
    fn map<F: Fn(Cow<Self::Unit>) -> Self::Unit + Sync>(mut self, f: F) -> Self {
        cfg_if! {
            if #[cfg(feature = "rayon")] {
                let iter = self.par_pixels_mut();
            } else {
                let iter = self.pixels_mut();
            }
        }
        iter.for_each(|p| *p = f(Cow::Borrowed(p)));
        self
    }
}

impl IntoDataBytes for DynamicImage {
    fn into_data_bytes(self) -> crate::Bytes {
        self.into_bytes()
    }
}

#[derive(Debug, Error)]
#[error("this color type is not supported yet... sorry")]
pub struct UnsupportedColorType;

impl TryFromDataBytes for DynamicImage {
    type Format = (Dimensions, ColorType);
    type Error = UnsupportedColorType;
    fn try_from_data_bytes(
        bytes: crate::Bytes,
        format: Self::Format,
        crop: crate::Crop,
    ) -> Result<Self, Self::Error> {
        match format.1 {
            ColorType::L8 => Ok(DynamicImage::ImageLuma8(ImageBuffer::from_data_bytes(
                bytes, format.0, crop,
            ))),
            ColorType::La8 => Ok(DynamicImage::ImageLumaA8(ImageBuffer::from_data_bytes(
                bytes, format.0, crop,
            ))),
            ColorType::Rgb8 => Ok(DynamicImage::ImageRgb8(ImageBuffer::from_data_bytes(
                bytes, format.0, crop,
            ))),
            ColorType::Rgba8 => Ok(DynamicImage::ImageRgba8(ImageBuffer::from_data_bytes(
                bytes, format.0, crop,
            ))),
            ColorType::L16 => Ok(DynamicImage::ImageLuma16(ImageBuffer::from_data_bytes(
                bytes, format.0, crop,
            ))),
            ColorType::La16 => Ok(DynamicImage::ImageLumaA16(ImageBuffer::from_data_bytes(
                bytes, format.0, crop,
            ))),
            ColorType::Rgb16 => Ok(DynamicImage::ImageRgb16(ImageBuffer::from_data_bytes(
                bytes, format.0, crop,
            ))),
            ColorType::Rgba16 => Ok(DynamicImage::ImageRgba16(ImageBuffer::from_data_bytes(
                bytes, format.0, crop,
            ))),
            ColorType::Rgb32F => Ok(DynamicImage::ImageRgb32F(ImageBuffer::from_data_bytes(
                bytes, format.0, crop,
            ))),
            ColorType::Rgba32F => Ok(DynamicImage::ImageRgba32F(ImageBuffer::from_data_bytes(
                bytes, format.0, crop,
            ))),
            _ => Err(UnsupportedColorType),
        }
    }
}

#[cfg(test)]
mod tests {
    #[cfg(test)]
    mod ser_de {
        use super::super::{
            Dimensions, ImageBuffer, IntoDataBytes, Rgb, Rgb32FImage, RgbImage, RgbaImage,
            TryFromDataBytes,
        };

        #[test]
        fn empty() {
            let image = RgbImage::new(0, 0);
            assert_eq!(
                Ok(image.clone()),
                RgbImage::try_from_data_bytes(
                    image.into_data_bytes(),
                    Dimensions {
                        width: 0,
                        height: 0
                    },
                    Default::default()
                )
            )
        }

        #[test]
        fn simple() {
            let image = RgbImage::from_raw(3, 1, vec![1, 2, 3, 4, 5, 6, 7, 8, 9]).unwrap();
            assert_eq!(
                Ok(image.clone()),
                RgbImage::try_from_data_bytes(
                    image.into_data_bytes(),
                    Dimensions {
                        width: 3,
                        height: 1
                    },
                    Default::default()
                )
            )
        }

        #[test]
        fn rgba() {
            let image =
                RgbaImage::from_raw(3, 1, vec![1, 2, 3, 0, 4, 5, 6, 1, 7, 8, 9, 2]).unwrap();
            assert_eq!(
                Ok(image.clone()),
                RgbaImage::try_from_data_bytes(
                    image.into_data_bytes(),
                    Dimensions {
                        width: 3,
                        height: 1
                    },
                    Default::default()
                )
            )
        }

        #[test]
        fn rgb_u16() {
            let image = ImageBuffer::<Rgb<u16>, Vec<u16>>::from_raw(
                3,
                1,
                vec![1, 2, 3, 254, 255, 256, 307, 308, 309],
            )
            .unwrap();
            assert_eq!(
                Ok(image.clone()),
                ImageBuffer::<Rgb<u16>, Vec<u16>>::try_from_data_bytes(
                    image.into_data_bytes(),
                    Dimensions {
                        width: 3,
                        height: 1
                    },
                    Default::default()
                )
            )
        }

        #[test]
        fn rgb_signed() {
            let image = ImageBuffer::<Rgb<i16>, Vec<i16>>::from_raw(
                3,
                1,
                vec![1, 2, 3, 254, 255, 256, -307, 308, 309],
            )
            .unwrap();
            assert_eq!(
                Ok(image.clone()),
                ImageBuffer::<Rgb<i16>, Vec<i16>>::try_from_data_bytes(
                    image.into_data_bytes(),
                    Dimensions {
                        width: 3,
                        height: 1
                    },
                    Default::default()
                )
            )
        }

        #[test]
        fn rgb_f32() {
            let image = Rgb32FImage::from_raw(
                3,
                1,
                vec![1.0, 2.0, 3.0, 254.0, 255.0, 256.1, 307.0, 308.0, 309.0],
            )
            .unwrap();
            assert_eq!(
                Ok(image.clone()),
                Rgb32FImage::try_from_data_bytes(
                    image.into_data_bytes(),
                    Dimensions {
                        width: 3,
                        height: 1
                    },
                    Default::default()
                )
            )
        }
    }

    #[cfg(test)]
    mod effects {
        use crate::Bendable;

        use super::super::{Pixel, RgbImage};

        #[test]
        fn fill_with_funny_number() {
            let image = RgbImage::new(8, 16);
            let new_image = image.clone().map(|p| p.map(|_channel| 42u8));
            assert_ne!(image.clone(), new_image);
            assert_eq!(42, new_image.get_pixel(1, 2).channels()[1])
        }
    }
}