diff --git a/src/image.rs b/src/image.rs index 95db9ca..f0a82b8 100644 --- a/src/image.rs +++ b/src/image.rs @@ -1,6 +1,8 @@ pub mod linear_rgb; +pub mod luminance; pub mod srgb; pub use linear_rgb::*; +pub use luminance::*; pub use srgb::*; pub mod farbfeld; diff --git a/src/image/luminance.rs b/src/image/luminance.rs new file mode 100644 index 0000000..251f668 --- /dev/null +++ b/src/image/luminance.rs @@ -0,0 +1,79 @@ +use super::*; + +use ccvl_proc_macros::Pixel; + +#[derive(Clone, Copy, Debug, PartialEq, Pixel)] +pub struct Luminance { + y: f32 +} + +const R_COEFF: f32 = 0.2126; +const G_COEFF: f32 = 0.7152; +const B_COEFF: f32 = 0.0722; + +impl Luminance { + pub fn from>(pixel: T) -> Self { + Into::::into(Into::::into(pixel)) + } + + pub fn into>(self) -> T { + T::from(Into::::into(self)) + } +} + +impl From for Luminance { + fn from(rgb: LinearRGB) -> Self { + Self { + y: rgb.r * R_COEFF + rgb.g * G_COEFF + rgb.b * B_COEFF + } + } +} + +impl Into for Luminance { + fn into(self) -> LinearRGB { + LinearRGB::new(self.y, self.y, self.y) + } +} + +impl std::ops::Deref for Luminance { + type Target = f32; + + fn deref(&self) -> &f32 { + &self.y + } +} + +impl std::ops::DerefMut for Luminance { + fn deref_mut(&mut self) -> &mut f32 { + &mut self.y + } +} + +#[cfg(test)] +mod tests { + use super::*; + use proptest::prelude::*; + + macro_rules! prop_assert_approx { + ( $act:expr, $exp:expr ) => { + prop_assert!(($act - $exp).abs() < 1e-6) + }; + } + + proptest! { + #[test] + fn to_rgb_copies_y_to_each_channel(y in 0.0f32..1.0) { + let res: LinearRGB = Luminance { y }.into(); + prop_assert_eq!(res.r, y); + prop_assert_eq!(res.g, y); + prop_assert_eq!(res.b, y); + } + + #[test] + fn approx_unchanged_from_rgb_round_trip(y in 0.0f32..1.0) { + let rgb: LinearRGB = Luminance { y }.into(); + let res: Luminance = rgb.into(); + prop_assert_approx!(res.y, y); + } + } +}