Skip to main content

oximedia_codec/png/
filter.rs

1//! PNG filter implementation.
2//!
3//! Implements the five PNG filter types as defined in the PNG specification:
4//! - Filter 0: None
5//! - Filter 1: Sub
6//! - Filter 2: Up
7//! - Filter 3: Average
8//! - Filter 4: Paeth
9//!
10//! Also includes filter selection heuristics for optimal compression.
11
12use crate::error::{CodecError, CodecResult};
13
14/// PNG filter types.
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16#[repr(u8)]
17pub enum FilterType {
18    /// No filtering.
19    None = 0,
20    /// Difference from left pixel (Sub).
21    Sub = 1,
22    /// Difference from above pixel (Up).
23    Up = 2,
24    /// Average of left and above pixels.
25    Average = 3,
26    /// Paeth predictor.
27    Paeth = 4,
28}
29
30impl FilterType {
31    /// Create filter type from byte.
32    ///
33    /// # Errors
34    ///
35    /// Returns error if filter type is invalid.
36    pub fn from_u8(value: u8) -> CodecResult<Self> {
37        match value {
38            0 => Ok(Self::None),
39            1 => Ok(Self::Sub),
40            2 => Ok(Self::Up),
41            3 => Ok(Self::Average),
42            4 => Ok(Self::Paeth),
43            _ => Err(CodecError::InvalidData(format!(
44                "Invalid filter type: {value}"
45            ))),
46        }
47    }
48
49    /// Convert to byte value.
50    #[must_use]
51    pub const fn to_u8(self) -> u8 {
52        self as u8
53    }
54}
55
56/// Apply PNG filter to a scanline.
57///
58/// # Arguments
59///
60/// * `filter_type` - Type of filter to apply
61/// * `scanline` - Current scanline data
62/// * `prev_scanline` - Previous scanline data (for Up, Average, Paeth)
63/// * `bytes_per_pixel` - Number of bytes per pixel
64///
65/// # Returns
66///
67/// Filtered scanline data.
68#[allow(clippy::needless_pass_by_value)]
69pub fn apply_filter(
70    filter_type: FilterType,
71    scanline: &[u8],
72    prev_scanline: Option<&[u8]>,
73    bytes_per_pixel: usize,
74) -> Vec<u8> {
75    match filter_type {
76        FilterType::None => scanline.to_vec(),
77        FilterType::Sub => apply_sub_filter(scanline, bytes_per_pixel),
78        FilterType::Up => apply_up_filter(scanline, prev_scanline.unwrap_or(&[])),
79        FilterType::Average => {
80            apply_average_filter(scanline, prev_scanline.unwrap_or(&[]), bytes_per_pixel)
81        }
82        FilterType::Paeth => {
83            apply_paeth_filter(scanline, prev_scanline.unwrap_or(&[]), bytes_per_pixel)
84        }
85    }
86}
87
88/// Apply Sub filter.
89fn apply_sub_filter(scanline: &[u8], bytes_per_pixel: usize) -> Vec<u8> {
90    let mut filtered = Vec::with_capacity(scanline.len());
91
92    for i in 0..scanline.len() {
93        let left = if i >= bytes_per_pixel {
94            scanline[i - bytes_per_pixel]
95        } else {
96            0
97        };
98        filtered.push(scanline[i].wrapping_sub(left));
99    }
100
101    filtered
102}
103
104/// Apply Up filter.
105fn apply_up_filter(scanline: &[u8], prev_scanline: &[u8]) -> Vec<u8> {
106    let mut filtered = Vec::with_capacity(scanline.len());
107
108    for i in 0..scanline.len() {
109        let above = if i < prev_scanline.len() {
110            prev_scanline[i]
111        } else {
112            0
113        };
114        filtered.push(scanline[i].wrapping_sub(above));
115    }
116
117    filtered
118}
119
120/// Apply Average filter.
121fn apply_average_filter(scanline: &[u8], prev_scanline: &[u8], bytes_per_pixel: usize) -> Vec<u8> {
122    let mut filtered = Vec::with_capacity(scanline.len());
123
124    for i in 0..scanline.len() {
125        let left = if i >= bytes_per_pixel {
126            scanline[i - bytes_per_pixel]
127        } else {
128            0
129        };
130        let above = if i < prev_scanline.len() {
131            prev_scanline[i]
132        } else {
133            0
134        };
135        let avg = ((u16::from(left) + u16::from(above)) / 2) as u8;
136        filtered.push(scanline[i].wrapping_sub(avg));
137    }
138
139    filtered
140}
141
142/// Apply Paeth filter.
143fn apply_paeth_filter(scanline: &[u8], prev_scanline: &[u8], bytes_per_pixel: usize) -> Vec<u8> {
144    let mut filtered = Vec::with_capacity(scanline.len());
145
146    for i in 0..scanline.len() {
147        let left = if i >= bytes_per_pixel {
148            scanline[i - bytes_per_pixel]
149        } else {
150            0
151        };
152        let above = if i < prev_scanline.len() {
153            prev_scanline[i]
154        } else {
155            0
156        };
157        let upper_left = if i >= bytes_per_pixel && i < prev_scanline.len() {
158            prev_scanline[i - bytes_per_pixel]
159        } else {
160            0
161        };
162
163        let paeth = paeth_predictor(left, above, upper_left);
164        filtered.push(scanline[i].wrapping_sub(paeth));
165    }
166
167    filtered
168}
169
170/// Paeth predictor function.
171///
172/// Returns the value among a, b, c that is closest to p = a + b - c.
173#[allow(clippy::cast_possible_wrap)]
174fn paeth_predictor(a: u8, b: u8, c: u8) -> u8 {
175    let a = i32::from(a);
176    let b = i32::from(b);
177    let c = i32::from(c);
178
179    let p = a + b - c;
180    let pa = (p - a).abs();
181    let pb = (p - b).abs();
182    let pc = (p - c).abs();
183
184    if pa <= pb && pa <= pc {
185        a as u8
186    } else if pb <= pc {
187        b as u8
188    } else {
189        c as u8
190    }
191}
192
193/// Unfilter a scanline.
194///
195/// # Arguments
196///
197/// * `filter_type` - Type of filter applied
198/// * `filtered` - Filtered scanline data
199/// * `prev_scanline` - Previous scanline data (for Up, Average, Paeth)
200/// * `bytes_per_pixel` - Number of bytes per pixel
201///
202/// # Errors
203///
204/// Returns error if unfiltering fails.
205pub fn unfilter(
206    filter_type: FilterType,
207    filtered: &[u8],
208    prev_scanline: Option<&[u8]>,
209    bytes_per_pixel: usize,
210) -> CodecResult<Vec<u8>> {
211    match filter_type {
212        FilterType::None => Ok(filtered.to_vec()),
213        FilterType::Sub => Ok(unfilter_sub(filtered, bytes_per_pixel)),
214        FilterType::Up => Ok(unfilter_up(filtered, prev_scanline.unwrap_or(&[]))),
215        FilterType::Average => Ok(unfilter_average(
216            filtered,
217            prev_scanline.unwrap_or(&[]),
218            bytes_per_pixel,
219        )),
220        FilterType::Paeth => Ok(unfilter_paeth(
221            filtered,
222            prev_scanline.unwrap_or(&[]),
223            bytes_per_pixel,
224        )),
225    }
226}
227
228/// Unfilter Sub filter.
229fn unfilter_sub(filtered: &[u8], bytes_per_pixel: usize) -> Vec<u8> {
230    let mut unfiltered = Vec::with_capacity(filtered.len());
231
232    for i in 0..filtered.len() {
233        let left = if i >= bytes_per_pixel {
234            unfiltered[i - bytes_per_pixel]
235        } else {
236            0
237        };
238        unfiltered.push(filtered[i].wrapping_add(left));
239    }
240
241    unfiltered
242}
243
244/// Unfilter Up filter.
245fn unfilter_up(filtered: &[u8], prev_scanline: &[u8]) -> Vec<u8> {
246    let mut unfiltered = Vec::with_capacity(filtered.len());
247
248    for i in 0..filtered.len() {
249        let above = if i < prev_scanline.len() {
250            prev_scanline[i]
251        } else {
252            0
253        };
254        unfiltered.push(filtered[i].wrapping_add(above));
255    }
256
257    unfiltered
258}
259
260/// Unfilter Average filter.
261fn unfilter_average(filtered: &[u8], prev_scanline: &[u8], bytes_per_pixel: usize) -> Vec<u8> {
262    let mut unfiltered = Vec::with_capacity(filtered.len());
263
264    for i in 0..filtered.len() {
265        let left = if i >= bytes_per_pixel {
266            unfiltered[i - bytes_per_pixel]
267        } else {
268            0
269        };
270        let above = if i < prev_scanline.len() {
271            prev_scanline[i]
272        } else {
273            0
274        };
275        let avg = ((u16::from(left) + u16::from(above)) / 2) as u8;
276        unfiltered.push(filtered[i].wrapping_add(avg));
277    }
278
279    unfiltered
280}
281
282/// Unfilter Paeth filter.
283fn unfilter_paeth(filtered: &[u8], prev_scanline: &[u8], bytes_per_pixel: usize) -> Vec<u8> {
284    let mut unfiltered = Vec::with_capacity(filtered.len());
285
286    for i in 0..filtered.len() {
287        let left = if i >= bytes_per_pixel {
288            unfiltered[i - bytes_per_pixel]
289        } else {
290            0
291        };
292        let above = if i < prev_scanline.len() {
293            prev_scanline[i]
294        } else {
295            0
296        };
297        let upper_left = if i >= bytes_per_pixel && i < prev_scanline.len() {
298            prev_scanline[i - bytes_per_pixel]
299        } else {
300            0
301        };
302
303        let paeth = paeth_predictor(left, above, upper_left);
304        unfiltered.push(filtered[i].wrapping_add(paeth));
305    }
306
307    unfiltered
308}
309
310/// Calculate sum of absolute differences for a filtered scanline.
311///
312/// Used for heuristic filter selection.
313#[must_use]
314pub fn sum_abs_diff(filtered: &[u8]) -> u64 {
315    filtered.iter().map(|&b| u64::from(b.abs_diff(128))).sum()
316}
317
318/// Select best filter type for a scanline using heuristic evaluation.
319///
320/// Tries all filter types and selects the one with minimum sum of absolute differences.
321///
322/// # Arguments
323///
324/// * `scanline` - Current scanline data
325/// * `prev_scanline` - Previous scanline data
326/// * `bytes_per_pixel` - Number of bytes per pixel
327///
328/// # Returns
329///
330/// Tuple of (best filter type, filtered data).
331#[must_use]
332pub fn select_best_filter(
333    scanline: &[u8],
334    prev_scanline: Option<&[u8]>,
335    bytes_per_pixel: usize,
336) -> (FilterType, Vec<u8>) {
337    let mut best_filter = FilterType::None;
338    let mut best_data = scanline.to_vec();
339    let mut best_score = sum_abs_diff(&best_data);
340
341    let filters = [
342        FilterType::None,
343        FilterType::Sub,
344        FilterType::Up,
345        FilterType::Average,
346        FilterType::Paeth,
347    ];
348
349    for &filter in &filters {
350        let filtered = apply_filter(filter, scanline, prev_scanline, bytes_per_pixel);
351        let score = sum_abs_diff(&filtered);
352
353        if score < best_score {
354            best_score = score;
355            best_filter = filter;
356            best_data = filtered;
357        }
358    }
359
360    (best_filter, best_data)
361}
362
363/// Fast filter selection that only considers None, Sub, and Up filters.
364///
365/// This is faster than `select_best_filter` but may not achieve optimal compression.
366#[must_use]
367pub fn select_fast_filter(
368    scanline: &[u8],
369    prev_scanline: Option<&[u8]>,
370    bytes_per_pixel: usize,
371) -> (FilterType, Vec<u8>) {
372    let mut best_filter = FilterType::None;
373    let mut best_data = scanline.to_vec();
374    let mut best_score = sum_abs_diff(&best_data);
375
376    // Try Sub filter
377    let sub_filtered = apply_sub_filter(scanline, bytes_per_pixel);
378    let sub_score = sum_abs_diff(&sub_filtered);
379    if sub_score < best_score {
380        best_score = sub_score;
381        best_filter = FilterType::Sub;
382        best_data = sub_filtered;
383    }
384
385    // Try Up filter if previous scanline available
386    if let Some(prev) = prev_scanline {
387        let up_filtered = apply_up_filter(scanline, prev);
388        let up_score = sum_abs_diff(&up_filtered);
389        if up_score < best_score {
390            best_filter = FilterType::Up;
391            best_data = up_filtered;
392        }
393    }
394
395    (best_filter, best_data)
396}
397
398/// Filter strategy for encoding.
399#[derive(Debug, Clone, Copy, PartialEq, Eq)]
400pub enum FilterStrategy {
401    /// No filtering (fastest).
402    None,
403    /// Use Sub filter only.
404    Sub,
405    /// Use Up filter only.
406    Up,
407    /// Use Average filter only.
408    Average,
409    /// Use Paeth filter only.
410    Paeth,
411    /// Fast heuristic selection (None, Sub, Up).
412    Fast,
413    /// Best compression (tries all filters).
414    Best,
415}
416
417impl FilterStrategy {
418    /// Apply filter strategy to a scanline.
419    ///
420    /// # Returns
421    ///
422    /// Tuple of (filter type, filtered data).
423    #[must_use]
424    pub fn apply(
425        &self,
426        scanline: &[u8],
427        prev_scanline: Option<&[u8]>,
428        bytes_per_pixel: usize,
429    ) -> (FilterType, Vec<u8>) {
430        match self {
431            Self::None => (FilterType::None, scanline.to_vec()),
432            Self::Sub => (FilterType::Sub, apply_sub_filter(scanline, bytes_per_pixel)),
433            Self::Up => (
434                FilterType::Up,
435                apply_up_filter(scanline, prev_scanline.unwrap_or(&[])),
436            ),
437            Self::Average => (
438                FilterType::Average,
439                apply_average_filter(scanline, prev_scanline.unwrap_or(&[]), bytes_per_pixel),
440            ),
441            Self::Paeth => (
442                FilterType::Paeth,
443                apply_paeth_filter(scanline, prev_scanline.unwrap_or(&[]), bytes_per_pixel),
444            ),
445            Self::Fast => select_fast_filter(scanline, prev_scanline, bytes_per_pixel),
446            Self::Best => select_best_filter(scanline, prev_scanline, bytes_per_pixel),
447        }
448    }
449}
450
451impl Default for FilterStrategy {
452    fn default() -> Self {
453        Self::Fast
454    }
455}