eimg 0.2.1

Rust Image APIs and pixel type for working with image processing.
Documentation
//! `RGB<T>` represents a triplet of pixels (r: T, g: T, b: T).
//! RGB<u8.. i8.. i16.. u16> are all one-word COPY types.
use std::ops::*;

#[derive(Default, Debug, PartialEq, Eq, Clone, Hash)]
/// RGB represents a triplet of pixels (r, g, b).
/// u8, i8, i16, and u16 are all one-word COPY types.
pub struct RGB<N>(pub N, pub N, pub N);

#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct ParseError;


impl<P> RGB<P> {
    /// map a function across all channels of the RGB.
    /// ```
    /// # use eimg::*;
    /// assert_eq!(RGB(2_u8, 5, 8).convert_with(|channel| channel+10), RGB(12_u8, 15, 18));
    /// ```
    pub fn convert_with<Q>(self, mut convert: impl FnMut(P) -> Q) -> RGB<Q> {
        let RGB(r, g, b) = self;
        RGB(convert(r), convert(g), convert(b))
    }
    /// this higher-order function takes a function from function from P to (P, P)
    /// and creates the equivalent function that maps it across RGB<P>.
    pub fn map_across(
        mut quantize: impl FnMut(P) -> (P, P),
    ) -> impl FnMut(RGB<P>) -> (RGB<P>, RGB<P>) {
        move |RGB(r, g, b)| {
            let (r_quot, r_rem) = quantize(r);
            let (g_quot, g_rem) = quantize(g);
            let (b_quot, b_rem) = quantize(b);
            let quotient = RGB(r_quot, g_quot, b_quot);
            let remainder = RGB(r_rem, g_rem, b_rem);
            (quotient, remainder)
        }
    }
}

impl Copy for RGB<u8> {}
impl Copy for RGB<i8> {}
impl Copy for RGB<i16> {}
impl Copy for RGB<u16> {}

// ---- OPERATOR OVERLOADING ---- //

// binary vec additon, subtraction

impl<N: Add<Output = N>> Add for RGB<N> {
    type Output = Self;
    fn add(self, other: Self) -> Self {
        let RGB(r0, g0, b0) = self;
        let RGB(r1, g1, b1) = other;
        RGB(r0 + r1, g0 + g1, b0 + b1)
    }
}

impl<N: Sub<Output = N>> Sub for RGB<N> {
    type Output = Self;
    fn sub(self, other: Self) -> Self {
        let RGB(r0, g0, b0) = self;
        let RGB(r1, g1, b1) = other;
        RGB(r0 - r1, g0 - g1, b0 - b1)
    }
}

// scalar ops for RGB<N> and N
impl<S: Mul<Output = S> + Copy> Mul<S> for RGB<S> {
    type Output = Self;
    fn mul(self, s: S) -> Self {
        self.convert_with(|c| c * s)
    }
}

impl<S: Div<Output = S> + Copy> Div<S> for RGB<S> {
    type Output = Self;
    fn div(self, s: S) -> Self {
        self.convert_with(|c| c / s)
    }
}

impl<S: Rem<Output = S> + Copy> Rem<S> for RGB<S> {
    type Output = Self;
    fn rem(self, s: S) -> Self {
        self.convert_with(|c| c % s)
    }
}

// unary ops

impl<N: Neg<Output = N>> Neg for RGB<N> {
    type Output = Self;
    fn neg(self) -> Self {
        self.convert_with(|c| -c)
    }
}

impl From<RGB<u8>> for RGB<f64> {
    fn from(rgb: RGB<u8>) -> Self {
        rgb.convert_with(f64::from)
    }
}

impl<N: Copy, M: From<N>> From<[N; 3]> for RGB<M> {
    fn from(a: [N; 3]) -> Self {
        RGB(a[0], a[1], a[2]).convert_with(M::from)
    }
}

impl<N, M: From<N>> From<(N, N, N)> for RGB<M> {
    fn from((r, g, b): (N, N, N)) -> Self {
        RGB(r, g, b).convert_with(M::from)
    }
}

impl RGB<f64> {
    /// convert to grayscale, correcting for the way human beings see color; we're about three times as sensitive to green as to red, and about three times as sensitive to red as to blue.
    /// # use eimg::*;
    pub fn to_chroma_corrected_grayscale(&self) -> f64 {
        let RGB(r, g, b) = self;
        r * 0.2126 + g * 0.7152 + b * 0.0722
    }

    /// convert to pseudo-rgb from color-corrected grayscale. the output image will still 'look' gray.
    pub fn from_chroma_corrected_grayscale(p: f64) -> Self {
        RGB(p, p, p)
    }


    /// returns true if all the channels are within tol of each other.
    pub fn approx_eq(&self, other: &Self, tol: f64) -> bool {
        let (RGB(r0, g0, b0), RGB(r1, g1, b1)) = (self, other);
        (r0 - r1) < tol && (g0 - g1) < tol && (b0 - b1) < tol
    }
}

impl RGB<u8> {
    /// convert a hexidecimal code to the appropriate RGB value, silently discarding the highest 8 bits, if they exist.
    /// Proper use should ensure that the input is less than or equal to `0xFFFFFF`
    /// ```rust
    /// # use eimg::*;
    /// assert_eq!(unsafe{RGB::from_hex(0xff_aa_bb)}, RGB(0xff, 0xaa, 0xbb));
    /// ```
    pub const unsafe fn from_hex(hex: u32) -> Self {
        super::RGB((hex >> 16) as u8, (hex >> 8) as u8, hex as u8)
    }
    /// convert to pseudo-rgb from color-corrected grayscale. the output image will still 'look' gray.
    pub fn from_chroma_corrected_grayscale(p: f64) -> Self {
        let clamp = crate::clamp_f64_to_u8;
        RGB(clamp(p), clamp(p), clamp(p))
    }

    /// convert to grayscale, correcting for the way human beings see color; we're about three times as sensitive to green as to red, and about three times as sensitive to red as to blue.
    /// ```rust
    /// # use eimg::*;
    /// let rgb = RGB(128, 90, 5);
    /// let grayscale = rgb.to_chroma_corrected_grayscale();
    /// let psuedo_rgb = RGB::<u8>::from_chroma_corrected_grayscale(grayscale);
    /// assert!(rgb != psuedo_rgb);
    /// assert!(f64::abs(psuedo_rgb.to_chroma_corrected_grayscale() -grayscale) < 0.1);
    /// ```
    pub fn to_chroma_corrected_grayscale(self) -> f64 {
       RGB::<f64>::from(self).to_chroma_corrected_grayscale()
    }

    /// convert to the equivalent 24-bit hexidecimal integer.
    /// ```
    /// # use eimg::*;
    /// assert_eq!(RGB(0xff, 0, 0).to_hex(), 0xff_00_00)
    /// ```
    pub fn to_hex(self) -> u32 {
        let RGB(r, g, b) = self;
        ((u32::from(r)) << 16) + (u32::from(g) << 8) + u32::from(b)
    }
}

impl std::fmt::LowerHex for RGB<u8> {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "{:06x}", self.clone().to_hex())
    }
}

impl<T: From<u8>> std::str::FromStr for RGB<T> {
    type Err = ParseError;
    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
        let s = if s.starts_with("0x") || s.starts_with("0X") {
            &s[2..]
        } else {
            s
        };
        if s.len() != 6 {
            return Err(ParseError);
        }

        if let Ok(n) = u32::from_str_radix(s, 16) {
            Ok(RGB(
                T::from((n >> 16 & 0xff) as u8),
                T::from((n >> 8 & 0xff) as u8),
                T::from((n & 0xff) as u8),
            ))
        } else {
            Err(ParseError)
        }
    }
}

#[test]
fn test_rgb_parse() {
    assert_eq!("0xff0000".parse::<RGB<u8>>(), Ok(RGB(0xff, 0, 0)));
    assert_eq!("00c200".parse::<RGB<u8>>(), Ok(RGB(0, 0xc2, 00)));
    assert!("0xm".parse::<RGB<u8>>().is_err());
    assert!("0xffffffff".parse::<RGB<f64>>().is_err());

    assert_eq!(
        format!("{:x}", RGB::<u8>(28, 51, 11)).parse(),
        Ok(RGB::<u8>(28, 51, 11))
    );
}

impl std::fmt::Display for ParseError {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "could not parse to a RGB value: bad format. must be exactly six hexidecimal characters, with optional 0x prefix")
    }
}
impl std::error::Error for ParseError {}