//! 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 } }