From dbfdbf892f929b02ce7e4a4d84b33f4105c55afe Mon Sep 17 00:00:00 2001 From: jnickg Date: Sun, 19 May 2024 22:44:08 -0700 Subject: [PATCH 1/2] Add missing map2/apply2 functions to `Pixel` trait * Account for alpha channel to simplify some image math operations where naively operating on all pixel components won't work (such as summing images, where usually picking one alpha value from `self` or `other` makes more sense) --- src/color.rs | 88 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/traits.rs | 29 +++++++++++++++++ 2 files changed, 117 insertions(+) diff --git a/src/color.rs b/src/color.rs index 1027611858..37249f1b62 100644 --- a/src/color.rs +++ b/src/color.rs @@ -371,6 +371,37 @@ impl Pixel for $ident { } } + fn map2_with_alpha(&self, other: &Self, f: F, g: G) -> $ident where F: FnMut(T, T) -> T, G: FnMut(T, T) -> T { + let mut this = (*self).clone(); + this.apply2_with_alpha(other, f, g); + this + } + + fn apply2_with_alpha(&mut self, other: &$ident, mut f: F, mut g: G) where F: FnMut(T, T) -> T, G: FnMut(T, T) -> T { + const ALPHA: usize = $channels - $alphas; + for (a, &b) in self.0[..ALPHA].iter_mut().zip(other.0[..ALPHA].iter()) { + *a = f(*a, b) + } + // The branch of this match is `const`. This way ensures that no subexpression fails the + // `const_err` lint (the expression `self.0[ALPHA]` would). + if let (Some(a), Some(b)) = (self.0.get_mut(ALPHA), other.0.get(ALPHA)) { + *a = g(*a, *b) + } + } + + fn map2_without_alpha(&self, other: &Self, f: F) -> $ident where F: FnMut(T, T) -> T { + let mut this = (*self).clone(); + this.apply2_without_alpha(other, f); + this + } + + fn apply2_without_alpha(&mut self, other: &$ident, mut f: F) where F: FnMut(T, T) -> T { + const ALPHA: usize = $channels - $alphas; + for (a, &b) in self.0[..ALPHA].iter_mut().zip(other.0[..ALPHA].iter()) { + *a = f(*a, b) + } + } + fn invert(&mut self) { Invert::invert(self) } @@ -931,6 +962,63 @@ mod tests { assert_eq!(rgb, Rgb([0, 0, 0])); } + #[test] + fn test_apply2_with_alpha_rgba() { + let mut rgba = Rgba([1, 2, 3, 64]); + rgba.apply2_with_alpha(&Rgba([4, 5, 6, 128]), |s, o| s + o, |s, o| (s + o) / 2); + assert_eq!(rgba, Rgba([5, 7, 9, 96])); + } + + #[test] + fn test_apply2_with_alpha_rgb() { + let mut rgb = Rgb([1, 2, 3]); + rgb.apply2_with_alpha(&Rgb([4, 5, 6]), |s, o| s + o, |_s, _o| panic!("bug")); + assert_eq!(rgb, Rgb([5, 7, 9])); + } + + #[test] + fn test_map2_with_alpha_rgba() { + let rgba = Rgba([1, 2, 3, 64]).map2_with_alpha( + &Rgba([4, 5, 6, 128]), + |s, o| s + o, + |s, o| (s + o) / 2, + ); + assert_eq!(rgba, Rgba([5, 7, 9, 96])); + } + + #[test] + fn test_map2_with_alpha_rgb() { + let rgb = + Rgb([1, 2, 3]).map2_with_alpha(&Rgb([4, 5, 6]), |s, o| s + o, |_s, _o| panic!("bug")); + assert_eq!(rgb, Rgb([5, 7, 9])); + } + + #[test] + fn test_apply2_without_alpha_rgba() { + let mut rgba = Rgba([1, 2, 3, 64]); + rgba.apply2_without_alpha(&Rgba([4, 5, 6, 128]), |s, o| s + o); + assert_eq!(rgba, Rgba([5, 7, 9, 64])); + } + + #[test] + fn test_apply2_without_alpha_rgb() { + let mut rgb = Rgb([1, 2, 3]); + rgb.apply2_without_alpha(&Rgb([4, 5, 6]), |s, o| s + o); + assert_eq!(rgb, Rgb([5, 7, 9])); + } + + #[test] + fn test_map2_without_alpha_rgba() { + let rgba = Rgba([1, 2, 3, 64]).map2_without_alpha(&Rgba([4, 5, 6, 128]), |s, o| s + o); + assert_eq!(rgba, Rgba([5, 7, 9, 64])); + } + + #[test] + fn test_map2_without_alpha_rgb() { + let rgb = Rgb([1, 2, 3]).map2_without_alpha(&Rgb([4, 5, 6]), |s, o| s + o); + assert_eq!(rgb, Rgb([5, 7, 9])); + } + #[test] fn test_blend_luma_alpha() { let a = &mut LumaA([255_u8, 255]); diff --git a/src/traits.rs b/src/traits.rs index 325926d2ad..8af918b515 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -354,6 +354,35 @@ pub trait Pixel: Copy + Clone { where F: FnMut(Self::Subpixel, Self::Subpixel) -> Self::Subpixel; + /// Apply the function ```f``` to each channel except alpha channel. + /// Apply the function ```g``` to the alpha channel. + fn map2_with_alpha(&self, other: &Self, f: F, g: G) -> Self + where + F: FnMut(Self::Subpixel, Self::Subpixel) -> Self::Subpixel, + G: FnMut(Self::Subpixel, Self::Subpixel) -> Self::Subpixel; + + /// Apply the function ```f``` to each channel (except alpha) of this + /// pixel and ```other``` pairwise. Then apply the function ```g``` to + /// the alpha channel of this pixel and ```other``` pairwise. + /// + /// Works in place. + fn apply2_with_alpha(&mut self, other: &Self, f: F, g: G) + where + F: FnMut(Self::Subpixel, Self::Subpixel) -> Self::Subpixel, + G: FnMut(Self::Subpixel, Self::Subpixel) -> Self::Subpixel; + + /// Apply the function ```f``` to each channel except the alpha channel, + /// of this pixel and ```other``` pairwise. + fn map2_without_alpha(&self, other: &Self, f: F) -> Self + where + F: FnMut(Self::Subpixel, Self::Subpixel) -> Self::Subpixel; + + /// Apply the function ```f``` to each channel except the alpha channel, + /// of this pixel and ```other``` pairwise. Works in place. + fn apply2_without_alpha(&mut self, other: &Self, f: F) + where + F: FnMut(Self::Subpixel, Self::Subpixel) -> Self::Subpixel; + /// Invert this pixel fn invert(&mut self); From 220f39feec96c99d0739def7643b6cd09227706e Mon Sep 17 00:00:00 2001 From: jnickg Date: Tue, 11 Jun 2024 14:59:42 -0700 Subject: [PATCH 2/2] Switch {map, apply}2_without_alpha Pixel functions to trait defaults - This makes their implementations parallel with {map, apply}_without_alpha implementations - Per PR feedback in #2239 --- src/color.rs | 13 ------------- src/traits.rs | 12 ++++++++++-- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/src/color.rs b/src/color.rs index 37249f1b62..496c7b320f 100644 --- a/src/color.rs +++ b/src/color.rs @@ -389,19 +389,6 @@ impl Pixel for $ident { } } - fn map2_without_alpha(&self, other: &Self, f: F) -> $ident where F: FnMut(T, T) -> T { - let mut this = (*self).clone(); - this.apply2_without_alpha(other, f); - this - } - - fn apply2_without_alpha(&mut self, other: &$ident, mut f: F) where F: FnMut(T, T) -> T { - const ALPHA: usize = $channels - $alphas; - for (a, &b) in self.0[..ALPHA].iter_mut().zip(other.0[..ALPHA].iter()) { - *a = f(*a, b) - } - } - fn invert(&mut self) { Invert::invert(self) } diff --git a/src/traits.rs b/src/traits.rs index 8af918b515..62e0779a68 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -375,13 +375,21 @@ pub trait Pixel: Copy + Clone { /// of this pixel and ```other``` pairwise. fn map2_without_alpha(&self, other: &Self, f: F) -> Self where - F: FnMut(Self::Subpixel, Self::Subpixel) -> Self::Subpixel; + F: FnMut(Self::Subpixel, Self::Subpixel) -> Self::Subpixel, + { + let mut this = *self; + this.apply2_with_alpha(other, f, |x, _| x); + this + } /// Apply the function ```f``` to each channel except the alpha channel, /// of this pixel and ```other``` pairwise. Works in place. fn apply2_without_alpha(&mut self, other: &Self, f: F) where - F: FnMut(Self::Subpixel, Self::Subpixel) -> Self::Subpixel; + F: FnMut(Self::Subpixel, Self::Subpixel) -> Self::Subpixel, + { + self.apply2_with_alpha(other, f, |x, _| x); + } /// Invert this pixel fn invert(&mut self);