plumers 1.0.2

Multi-format image library with first-class support for paletted images
Documentation
//! Supported color formats.
//!
//! Quoting from libplum's documentation:
//!
//! > Regardless of the actual formats used for color and pixel data in image files, the library uses a number of standard
//! > color formats and converts to and from those formats respectively when loading and generating image files.
//! > The user chooses which color format to use via an argument when loading an image (or a struct member when creating one
//! > manually); therefore, while the library can process data in all supported color formats, users only need to choose one
//! > and use the one they prefer.
//! >
//! > All color formats contain red, green, blue and alpha channels.
//! > There is no dedicated support for grayscale or no-transparency formats; images using those formats will be converted
//! > to RGBA when loaded.
//! >
//! > All color formats use fixed-width integers for color values.
//! > The channels are always in RGBA order from least significant to most significant bit; in other words, the red channel
//! > always takes up the least significant bits and the alpha channel always takes up the most significant bits.
//! > There are no unused bits, and the three color channels (red, green and blue) always have the same bit width.
//!
//! Each of these color formats is meant to be used as a parameter to the [`Image`][crate::image::Image] trait.
//!
//! The [`ColorFmt`] trait can also be used for code that wishes to be generic over color formats (e.g. image truncation).
//!
//! # Comparison
//!
//! - **[`Rgb32`]**: The bog-standard, that you'll typically find everywhere.
//!                  Also tends to be the most convenient to manipulate.
//! - **[`Rgb32X`]**: Same size as [`Rgb32`], except the alpha channel is coarser in order to improve precision of R, G, and B.
//! - **[`Rgb16`]**: Half the size of [`Rgb32`], which makes it more memory-efficient and thus often faster to process.
//!                  This comes at the cost of reduced precision, which can be a deal-breaker for some use cases.
//! - **[`Rgb64`]**: Double the size of [`Rgb32`], which gives ludicrous precision to all channels.
//!                  This much precision is rarely useful outside of some niches.

use std::{
    fmt::{Debug, Display},
    hash::Hash,
};

/// 5-bit RGB plus 1-bit alpha.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
#[repr(transparent)] // Necessary for treating the raw data as the format, and thus casting pointers.
pub struct Rgb16(pub u16);
/// 8-bit RGB plus 8-bit alpha.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
#[repr(transparent)] // Necessary for treating the raw data as the format, and thus casting pointers.
pub struct Rgb32(pub u32);
/// 10-bit RGB plus 2-bit alpha.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
#[repr(transparent)] // Necessary for treating the raw data as the format, and thus casting pointers.
pub struct Rgb32X(pub u32);
/// 16-bit RGB plus 16-bit alpha.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
#[repr(transparent)] // Necessary for treating the raw data as the format, and thus casting pointers.
pub struct Rgb64(pub u64);

/// Common interface for all suitable color formats within libplum.
///
/// This trait is [sealed], so that it cannot be implemented on types other than those provided by this crate.
/// It is provided as a convenience, for applications that wish to handle images of various formats.
///
/// [sealed]: https://rust-lang.github.io/api-guidelines/future-proofing.html#sealed-traits-protect-against-downstream-implementations-c-sealed
pub trait ColorFmt: private::ColorFmt + Debug + Copy + Hash + Eq + Ord + Display + Sized {
    /// Type suitable for storing the whole colour.
    type Raw: Copy + Hash + Eq + Ord + Default + Debug + Display + Send + Sync + Sized;
    /// Type suitable for storing each individual channel.
    type Component: Copy + Hash + Eq + Ord + Default + Debug + Display + Send + Sync + Sized;
    /// Number of bits allocated to red, green, and blue channels.
    const RGB_BIT_WIDTH: u8;
    /// Number of bits allocated to the alpha channel.
    const ALPHA_BIT_WIDTH: u8;

    /// Breaks `self` down into its individual components.
    fn decode(
        &self,
    ) -> (
        Self::Component,
        Self::Component,
        Self::Component,
        Self::Component,
    );
    /// Gathers individual components into `Self`.
    fn encode(
        red: Self::Component,
        green: Self::Component,
        blue: Self::Component,
        alpha: Self::Component,
    ) -> Self;
    /// Inverts the alpha channel.
    fn invert_alpha(&mut self);
}
// The private part of the "color format" API. Essentially, the low-level stuff.
mod private {
    /// This trait acts as a private API *and* a "seal", preventing other types from implementing [`super::ColorFmt`].
    pub trait ColorFmt {
        fn raw_constant() -> libplum_sys::plum_flags;
    }
}

macro_rules! impl_display {
    ($t:ty { $adjust_rgb:expr, $adjust_alpha:expr }) => {
        /// This prints the color in CSS notation (`#02468ace`), always exactly 9 characters long.
        ///
        /// Except for [`Rgb32`], each component has to be adjusted to fit in a byte, so sometimes the printed color is somewhat inaccurate.
        ///
        /// However, if the alternate format flag (`#`) is passed, then the color is printed as a 4-tuple of its components;
        /// this is more verbose, has inconsistent length, but is always exact.
        impl Display for $t {
            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                let (r, g, b, a) = self.decode();
                if f.alternate() {
                    // Output a more verbose format, but that yields exact info.
                    write!(f, "(R: {r}, G: {g}, B: {b}, A: {a})")
                } else {
                    // Use CSS notation approximating what the format exactly indicates.
                    let adjust_rgb = $adjust_rgb;
                    let adjust_alpha = $adjust_alpha;
                    write!(
                        f,
                        "#{:02x}{:02x}{:02x}{:02x}",
                        adjust_rgb(r),
                        adjust_rgb(g),
                        adjust_rgb(b),
                        adjust_alpha(a),
                    )
                }
            }
        }
    };
}
impl_display!(Rgb16  { |rgb| rgb << 3 | rgb >> 2, |alpha| if alpha == 0 { 0 } else { 0xFF } });
impl_display!(Rgb32  { |rgb| rgb,                 |alpha| alpha });
impl_display!(Rgb32X { |rgb| (rgb >> 2) as u8,    |alpha| alpha * 0x55 });
impl_display!(Rgb64  { |rgb| (rgb >> 8) as u8,    |alpha| (alpha >> 8) as u8 });

// Implementations of those traits.

impl ColorFmt for Rgb16 {
    type Raw = u16;
    type Component = u8;
    const RGB_BIT_WIDTH: u8 = 5;
    const ALPHA_BIT_WIDTH: u8 = 1;

    fn decode(
        &self,
    ) -> (
        Self::Component,
        Self::Component,
        Self::Component,
        Self::Component,
    ) {
        let trunc = |shifted| (shifted & 0x1f) as u8;
        (
            trunc(self.0),
            trunc(self.0 >> 5),
            trunc(self.0 >> 10),
            (self.0 >> 15) as u8,
        )
    }
    fn encode(
        red: Self::Component,
        green: Self::Component,
        blue: Self::Component,
        alpha: Self::Component,
    ) -> Self {
        Self(red as u16 | (green as u16) << 5 | (blue as u16) << 10 | (alpha as u16) << 15)
    }
    fn invert_alpha(&mut self) {
        self.0 ^= 0x8000;
    }
}
impl private::ColorFmt for Rgb16 {
    fn raw_constant() -> libplum_sys::plum_flags {
        libplum_sys::PLUM_COLOR_16
    }
}

impl ColorFmt for Rgb32 {
    type Raw = u32;
    type Component = u8;
    const RGB_BIT_WIDTH: u8 = 8;
    const ALPHA_BIT_WIDTH: u8 = 8;

    fn decode(
        &self,
    ) -> (
        Self::Component,
        Self::Component,
        Self::Component,
        Self::Component,
    ) {
        let [red, green, blue, alpha] = self.0.to_le_bytes();
        (red, green, blue, alpha)
    }
    fn encode(
        red: Self::Component,
        green: Self::Component,
        blue: Self::Component,
        alpha: Self::Component,
    ) -> Self {
        Self(u32::from_le_bytes([red, green, blue, alpha]))
    }
    fn invert_alpha(&mut self) {
        self.0 ^= 0xFF000000;
    }
}
impl private::ColorFmt for Rgb32 {
    fn raw_constant() -> libplum_sys::plum_flags {
        libplum_sys::PLUM_COLOR_32
    }
}

impl ColorFmt for Rgb32X {
    type Raw = u32;
    type Component = u16;
    const RGB_BIT_WIDTH: u8 = 10;
    const ALPHA_BIT_WIDTH: u8 = 2;

    fn decode(
        &self,
    ) -> (
        Self::Component,
        Self::Component,
        Self::Component,
        Self::Component,
    ) {
        let trunc = |shifted| (shifted & 0x3ff) as u16;
        (
            trunc(self.0),
            trunc(self.0 >> 10),
            trunc(self.0 >> 20),
            trunc(self.0 >> 30),
        )
    }
    fn encode(
        red: Self::Component,
        green: Self::Component,
        blue: Self::Component,
        alpha: Self::Component,
    ) -> Self {
        Self(red as u32 | (green as u32) << 10 | (blue as u32) << 20 | (alpha as u32) << 30)
    }
    fn invert_alpha(&mut self) {
        self.0 ^= 0xC0000000;
    }
}
impl private::ColorFmt for Rgb32X {
    fn raw_constant() -> libplum_sys::plum_flags {
        libplum_sys::PLUM_COLOR_32X
    }
}

impl ColorFmt for Rgb64 {
    type Raw = u64;
    type Component = u16;
    const RGB_BIT_WIDTH: u8 = 16;
    const ALPHA_BIT_WIDTH: u8 = 16;

    fn decode(
        &self,
    ) -> (
        Self::Component,
        Self::Component,
        Self::Component,
        Self::Component,
    ) {
        let [red_l, red_h, green_l, green_h, blue_l, blue_h, alpha_l, alpha_h] =
            self.0.to_le_bytes();
        (
            u16::from_le_bytes([red_l, red_h]),
            u16::from_le_bytes([green_l, green_h]),
            u16::from_le_bytes([blue_l, blue_h]),
            u16::from_le_bytes([alpha_l, alpha_h]),
        )
    }
    fn encode(
        red: Self::Component,
        green: Self::Component,
        blue: Self::Component,
        alpha: Self::Component,
    ) -> Self {
        Self(red as u64 | (green as u64) << 16 | (blue as u64) << 32 | (alpha as u64) << 48)
    }
    fn invert_alpha(&mut self) {
        self.0 ^= 0xFFFF000000000000;
    }
}
impl private::ColorFmt for Rgb64 {
    fn raw_constant() -> libplum_sys::plum_flags {
        libplum_sys::PLUM_COLOR_64
    }
}