pnglitcher/
filter.rs

1use crate::common::BytesPerPixel;
2
3/// The byte level filter applied to scanlines to prepare them for compression.
4///
5/// Compression in general benefits from repetitive data. The filter is a content-aware method of
6/// compressing the range of occurring byte values to help the compression algorithm. Note that
7/// this does not operate on pixels but on raw bytes of a scanline.
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9#[repr(u8)]
10pub enum FilterType {
11    NoFilter = 0,
12    Sub = 1,
13    Up = 2,
14    Avg = 3,
15    Paeth = 4,
16}
17
18impl Default for FilterType {
19    fn default() -> Self {
20        FilterType::Sub
21    }
22}
23
24impl FilterType {
25    /// u8 -> Self. Temporary solution until Rust provides a canonical one.
26    pub fn from_u8(n: u8) -> Option<FilterType> {
27        match n {
28            0 => Some(FilterType::NoFilter),
29            1 => Some(FilterType::Sub),
30            2 => Some(FilterType::Up),
31            3 => Some(FilterType::Avg),
32            4 => Some(FilterType::Paeth),
33            _ => None,
34        }
35    }
36}
37
38/// The filtering method for preprocessing scanline data before compression.
39///
40/// Adaptive filtering performs additional computation in an attempt to maximize
41/// the compression of the data. [`NonAdaptive`] filtering is the default.
42///
43/// [`NonAdaptive`]: enum.AdaptiveFilterType.html#variant.NonAdaptive
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45#[repr(u8)]
46pub enum AdaptiveFilterType {
47    Adaptive,
48    NonAdaptive,
49}
50
51impl Default for AdaptiveFilterType {
52    fn default() -> Self {
53        AdaptiveFilterType::NonAdaptive
54    }
55}
56
57fn filter_paeth(a: u8, b: u8, c: u8) -> u8 {
58    let ia = i16::from(a);
59    let ib = i16::from(b);
60    let ic = i16::from(c);
61
62    let p = ia + ib - ic;
63
64    let pa = (p - ia).abs();
65    let pb = (p - ib).abs();
66    let pc = (p - ic).abs();
67
68    if pa <= pb && pa <= pc {
69        a
70    } else if pb <= pc {
71        b
72    } else {
73        c
74    }
75}
76
77fn filter_internal(
78    method: FilterType,
79    bpp: usize,
80    len: usize,
81    previous: &[u8],
82    current: &mut [u8],
83) -> FilterType {
84    use self::FilterType::*;
85
86    match method {
87        NoFilter => NoFilter,
88        Sub => {
89            for i in (bpp..len).rev() {
90                current[i] = current[i].wrapping_sub(current[i - bpp]);
91            }
92            Sub
93        }
94        Up => {
95            for i in 0..len {
96                current[i] = current[i].wrapping_sub(previous[i]);
97            }
98            Up
99        }
100        Avg => {
101            for i in (bpp..len).rev() {
102                current[i] = current[i].wrapping_sub(
103                    ((u16::from(current[i - bpp]) + u16::from(previous[i])) / 2) as u8,
104                );
105            }
106
107            for i in 0..bpp {
108                current[i] = current[i].wrapping_sub(previous[i] / 2);
109            }
110            Avg
111        }
112        Paeth => {
113            for i in (bpp..len).rev() {
114                current[i] = current[i].wrapping_sub(filter_paeth(
115                    current[i - bpp],
116                    previous[i],
117                    previous[i - bpp],
118                ));
119            }
120
121            for i in 0..bpp {
122                current[i] = current[i].wrapping_sub(filter_paeth(0, previous[i], 0));
123            }
124            Paeth
125        }
126    }
127}
128
129pub(crate) fn filter(
130    method: FilterType,
131    adaptive: AdaptiveFilterType,
132    bpp: BytesPerPixel,
133    previous: &[u8],
134    current: &mut [u8],
135) -> FilterType {
136    use FilterType::*;
137    let bpp = bpp.into_usize();
138    let len = current.len();
139
140    match adaptive {
141        AdaptiveFilterType::NonAdaptive => filter_internal(method, bpp, len, previous, current),
142        AdaptiveFilterType::Adaptive => {
143            // Filter the current buffer with each filter type. Sum the absolute
144            // values of each filtered buffer treating the bytes as signed
145            // integers. Choose the filter with the smallest sum.
146            let mut filtered_buffer = vec![0; len];
147            filtered_buffer.copy_from_slice(current);
148            let mut scratch = vec![0; len];
149
150            // Initialize min_sum with the NoFilter buffer sum
151            let mut min_sum: usize = sum_buffer(&filtered_buffer);
152            let mut filter_choice = FilterType::NoFilter;
153
154            for &filter in [Sub, Up, Avg, Paeth].iter() {
155                scratch.copy_from_slice(current);
156                filter_internal(filter, bpp, len, previous, &mut scratch);
157                let sum = sum_buffer(&scratch);
158                if sum < min_sum {
159                    min_sum = sum;
160                    filter_choice = filter;
161                    core::mem::swap(&mut filtered_buffer, &mut scratch);
162                }
163            }
164
165            current.copy_from_slice(&filtered_buffer);
166
167            filter_choice
168        }
169    }
170}
171
172// Helper function for Adaptive filter buffer summation
173fn sum_buffer(buf: &[u8]) -> usize {
174    buf.iter().fold(0, |acc, &x| {
175        acc.saturating_add(i16::from(x as i8).abs() as usize)
176    })
177}