Skip to content

Commit

Permalink
Add missing map2/apply2 functions to Pixel trait
Browse files Browse the repository at this point in the history
* 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)
  • Loading branch information
jnickg committed May 20, 2024
1 parent 1207aca commit b7b4d9f
Show file tree
Hide file tree
Showing 2 changed files with 117 additions and 0 deletions.
88 changes: 88 additions & 0 deletions src/color.rs
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,37 @@ impl<T: $($bound+)*> Pixel for $ident<T> {
}
}

fn map2_with_alpha<F, G>(&self, other: &Self, f: F, g: G) -> $ident<T> 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<F, G>(&mut self, other: &$ident<T>, 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<F>(&self, other: &Self, f: F) -> $ident<T> where F: FnMut(T, T) -> T {
let mut this = (*self).clone();
this.apply2_without_alpha(other, f);
this
}

fn apply2_without_alpha<F>(&mut self, other: &$ident<T>, 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)
}
Expand Down Expand Up @@ -924,6 +955,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]);
Expand Down
29 changes: 29 additions & 0 deletions src/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,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<F, G>(&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<F, G>(&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<F>(&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<F>(&mut self, other: &Self, f: F)
where
F: FnMut(Self::Subpixel, Self::Subpixel) -> Self::Subpixel;

/// Invert this pixel
fn invert(&mut self);

Expand Down

0 comments on commit b7b4d9f

Please sign in to comment.