dithr 0.3.0

Buffer-first rust dithering and halftoning library.
Documentation
pub trait PixelLayout: Copy + Send + Sync + 'static {
    const CHANNELS: usize;
    const COLOR_CHANNELS: usize;
    const HAS_ALPHA: bool;
    const IS_GRAY: bool;
}

pub(crate) fn validate_layout_invariants<L: PixelLayout>() -> crate::Result<()> {
    if L::CHANNELS == 0 {
        return Err(crate::Error::UnsupportedFormat(
            "pixel layout must define at least one channel",
        ));
    }

    if L::COLOR_CHANNELS == 0 {
        return Err(crate::Error::UnsupportedFormat(
            "pixel layout must define at least one color channel",
        ));
    }

    if L::COLOR_CHANNELS > L::CHANNELS {
        return Err(crate::Error::UnsupportedFormat(
            "pixel layout color channels cannot exceed total channels",
        ));
    }

    if L::HAS_ALPHA && L::COLOR_CHANNELS == L::CHANNELS {
        return Err(crate::Error::UnsupportedFormat(
            "alpha pixel layouts must reserve a non-color channel",
        ));
    }

    if L::IS_GRAY {
        if L::COLOR_CHANNELS != 1 {
            return Err(crate::Error::UnsupportedFormat(
                "gray pixel layouts must use exactly one color channel",
            ));
        }
        if L::HAS_ALPHA {
            return Err(crate::Error::UnsupportedFormat(
                "gray pixel layouts with alpha are not supported",
            ));
        }
    } else if L::COLOR_CHANNELS < 3 {
        return Err(crate::Error::UnsupportedFormat(
            "color pixel layouts must define at least three color channels",
        ));
    }

    Ok(())
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Gray;

#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Rgb;

#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Rgba;

impl PixelLayout for Gray {
    const CHANNELS: usize = 1;
    const COLOR_CHANNELS: usize = 1;
    const HAS_ALPHA: bool = false;
    const IS_GRAY: bool = true;
}

impl PixelLayout for Rgb {
    const CHANNELS: usize = 3;
    const COLOR_CHANNELS: usize = 3;
    const HAS_ALPHA: bool = false;
    const IS_GRAY: bool = false;
}

impl PixelLayout for Rgba {
    const CHANNELS: usize = 4;
    const COLOR_CHANNELS: usize = 3;
    const HAS_ALPHA: bool = true;
    const IS_GRAY: bool = false;
}

#[cfg(test)]
mod tests {
    use super::{validate_layout_invariants, Gray, PixelLayout, Rgb, Rgba};
    use crate::Error;

    #[test]
    fn layout_constants_match_expected() {
        assert_eq!(Gray::CHANNELS, 1);
        assert_eq!(Gray::COLOR_CHANNELS, 1);
        assert!(!std::hint::black_box(Gray::HAS_ALPHA));
        assert!(std::hint::black_box(Gray::IS_GRAY));

        assert_eq!(Rgb::CHANNELS, 3);
        assert_eq!(Rgb::COLOR_CHANNELS, 3);
        assert!(!std::hint::black_box(Rgb::HAS_ALPHA));
        assert!(!std::hint::black_box(Rgb::IS_GRAY));

        assert_eq!(Rgba::CHANNELS, 4);
        assert_eq!(Rgba::COLOR_CHANNELS, 3);
        assert!(std::hint::black_box(Rgba::HAS_ALPHA));
        assert!(!std::hint::black_box(Rgba::IS_GRAY));
    }

    #[derive(Clone, Copy)]
    struct InvalidZeroChannels;

    impl PixelLayout for InvalidZeroChannels {
        const CHANNELS: usize = 0;
        const COLOR_CHANNELS: usize = 1;
        const HAS_ALPHA: bool = false;
        const IS_GRAY: bool = true;
    }

    #[derive(Clone, Copy)]
    struct InvalidColorOverflow;

    impl PixelLayout for InvalidColorOverflow {
        const CHANNELS: usize = 3;
        const COLOR_CHANNELS: usize = 4;
        const HAS_ALPHA: bool = false;
        const IS_GRAY: bool = false;
    }

    #[test]
    fn validate_layout_invariants_accepts_builtin_layouts() {
        assert_eq!(validate_layout_invariants::<Gray>(), Ok(()));
        assert_eq!(validate_layout_invariants::<Rgb>(), Ok(()));
        assert_eq!(validate_layout_invariants::<Rgba>(), Ok(()));
    }

    #[test]
    fn validate_layout_invariants_rejects_zero_channels() {
        assert_eq!(
            validate_layout_invariants::<InvalidZeroChannels>(),
            Err(Error::UnsupportedFormat(
                "pixel layout must define at least one channel"
            ))
        );
    }

    #[test]
    fn validate_layout_invariants_rejects_color_overflow() {
        assert_eq!(
            validate_layout_invariants::<InvalidColorOverflow>(),
            Err(Error::UnsupportedFormat(
                "pixel layout color channels cannot exceed total channels"
            ))
        );
    }
}