auto_palette/image/
filter.rs

1use crate::image::Rgba;
2
3/// A trait for filtering pixels in an image.
4pub trait Filter: Send + Sync + 'static {
5    /// Tests whether a pixel passes the filter.
6    ///
7    /// # Arguments
8    /// * `pixel` - The pixel to apply the filter to.
9    ///
10    /// # Returns
11    /// `true` if the pixel passes the filter; `false` otherwise.
12    #[must_use]
13    fn test(&self, pixel: &Rgba) -> bool;
14
15    /// Composites this filter with another filter.
16    ///
17    /// # Type Parameters
18    /// * `F` - The type of the filter to compose with.
19    ///
20    /// # Arguments
21    /// * `other` - The other filter to compose with.
22    ///
23    /// # Returns
24    /// A new filter that applies this filter and then the given filter.
25    #[must_use]
26    fn composite<F>(self, other: F) -> CompositeFilter<Self, F>
27    where
28        Self: Sized,
29        F: Filter,
30    {
31        CompositeFilter::new(self, other)
32    }
33}
34
35/// A filter that applies a closure to a pixel.
36///
37/// This filter is useful for creating custom filters that can be passed to the `apply` method.
38///
39/// # Type Parameters
40/// * `F` - The type of the closure.
41impl<F> Filter for F
42where
43    F: Fn(&Rgba) -> bool + Send + Sync + 'static,
44{
45    #[inline(always)]
46    fn test(&self, pixel: &Rgba) -> bool {
47        self(pixel)
48    }
49}
50
51/// A filter that composites two filters together.
52///
53/// This filter applies the first filter and then the second filter.
54///
55/// # Type Parameters
56/// * `F1` - The type of the 1st filter.
57/// * `F2` - The type of the 2nd filter.
58#[derive(Debug)]
59pub struct CompositeFilter<F1, F2>
60where
61    F1: Filter,
62    F2: Filter,
63{
64    first: F1,
65    second: F2,
66}
67
68impl<F1, F2> CompositeFilter<F1, F2>
69where
70    F1: Filter,
71    F2: Filter,
72{
73    /// Creates a new `CompositeFilter` instance.
74    ///
75    /// # Arguments
76    /// * `first` - The first filter.
77    /// * `second` - The second filter.
78    ///
79    /// # Returns
80    /// A new `CompositeFilter` instance.
81    #[must_use]
82    pub fn new(first: F1, second: F2) -> Self {
83        Self { first, second }
84    }
85}
86
87impl<F1, F2> Filter for CompositeFilter<F1, F2>
88where
89    F1: Filter,
90    F2: Filter,
91{
92    #[inline(always)]
93    fn test(&self, pixel: &Rgba) -> bool {
94        // Apply the first filter and then the second filter
95        // https://doc.rust-lang.org/reference/expressions/operator-expr.html#lazy-boolean-operators
96        self.first.test(pixel) && self.second.test(pixel)
97    }
98}
99
100/// A filter that filters alpha values.
101#[derive(Debug)]
102pub struct AlphaFilter {
103    threshold: u8,
104}
105
106impl AlphaFilter {
107    /// Creates a new `AlphaFilter` instance.
108    ///
109    /// # Arguments
110    /// * `threshold` - The alpha threshold for the filter.
111    ///
112    /// # Returns
113    /// A new `AlphaFilter` instance.
114    #[must_use]
115    pub fn new(threshold: u8) -> Self {
116        Self { threshold }
117    }
118}
119
120impl Filter for AlphaFilter {
121    #[inline(always)]
122    fn test(&self, pixel: &Rgba) -> bool {
123        pixel[3] > self.threshold
124    }
125}
126
127impl Default for AlphaFilter {
128    fn default() -> Self {
129        Self::new(0)
130    }
131}
132
133#[cfg(test)]
134mod tests {
135    use super::*;
136
137    #[test]
138    fn test_closure_filter() {
139        // Act & Assert
140        let filter = |pixel: &Rgba| pixel[0] > 128; // Filter for red channel > 128
141        assert_eq!(filter.test(&[255, 0, 0, 255]), true);
142        assert_eq!(filter.test(&[129, 0, 0, 255]), true);
143        assert_eq!(filter.test(&[128, 0, 0, 255]), false);
144        assert_eq!(filter.test(&[0, 0, 0, 255]), false);
145    }
146
147    #[test]
148    fn test_composite_filter() {
149        // Arrange
150        let alpha_filter = |pixel: &Rgba| pixel[3] != 0;
151        let green_filter = |pixel: &Rgba| pixel[1] >= 128;
152
153        // Act
154        let filter = alpha_filter.composite(green_filter);
155
156        // Assert
157        assert_eq!(filter.test(&[255, 127, 255, 0]), false); // Alpha == 0 && green channel < 128
158        assert_eq!(filter.test(&[255, 255, 255, 0]), false); // Alpha == 0 && green channel >= 128
159        assert_eq!(filter.test(&[255, 127, 255, 255]), false); // Alpha != 0 && green channel < 128
160        assert_eq!(filter.test(&[255, 128, 255, 255]), true); // Alpha != 0 && green channel >= 128
161        assert_eq!(filter.test(&[255, 255, 255, 255]), true); // Alpha != 0 && green channel >= 128
162    }
163
164    #[test]
165    fn test_alpha_filter() {
166        // Act & Assert
167        let filter = AlphaFilter::new(127);
168        assert_eq!(filter.test(&[255, 0, 0, 255]), true);
169        assert_eq!(filter.test(&[255, 0, 0, 128]), true);
170        assert_eq!(filter.test(&[255, 0, 0, 127]), false);
171        assert_eq!(filter.test(&[255, 0, 0, 0]), false);
172    }
173}