//! Utilities for working with standard 24-bit RGB color pixels /// An RGB color value in some perceptual color space; /// ideally this will be gamma-corrected before display, /// but this form is better for doing gradient/fade/etc calculations on. #[derive(Clone, Copy, PartialEq)] pub struct Rgb(pub u8, pub u8, pub u8); impl core::ops::Add for Rgb { type Output = Rgb; #[inline] fn add(self, rhs: Rgb) -> Rgb { Rgb( self.0.saturating_add(rhs.0), self.1.saturating_add(rhs.1), self.2.saturating_add(rhs.2) ) } } impl core::ops::Mul for Rgb { type Output = Rgb; #[inline] fn mul(self, rhs: Rgb) -> Rgb { Rgb( (self.0 as usize * rhs.0 as usize / 255) as u8, (self.1 as usize * rhs.1 as usize / 255) as u8, (self.2 as usize * rhs.2 as usize / 255) as u8 ) } } impl core::ops::Mul for Rgb { type Output = Rgb; #[inline] fn mul(self, rhs: u8) -> Rgb { Rgb( self.0.saturating_mul(rhs), self.1.saturating_mul(rhs), self.2.saturating_mul(rhs) ) } } /// Construct an [Rgb] from a monochrome value. #[inline] pub const fn gray(gray: u8) -> Rgb { Rgb(gray, gray, gray) } /// Interpolate linearly between two colors. /// /// The "mix" value is RGB to permit blending channels individually, /// but most often you will probably use [gray] to generate the mix value. #[inline] pub fn blend(a: Rgb, b: Rgb, mix: Rgb) -> Rgb { (a * Rgb(255 - mix.0, 255 - mix.1, 255 - mix.2)) + (b * mix) } #[inline] pub fn blend_steps(from: Rgb, to: Rgb, steps: usize, x: usize) -> Rgb { blend(from, to, gray((x * 255 / steps) as u8)) } #[inline] pub fn linear_gradient(from: Rgb, to: Rgb, steps: usize) -> impl Iterator + DoubleEndedIterator + Clone { (0..steps).map(move |x| blend_steps(from, to, steps, x)) }