png_codec 0.1.1

A minimal pure Rust PNG encoder
Documentation
//! Algorithms for png "filtering" - A compression algorithm applied before
//! the deflate algorithm.

use crate::chunk::ImageHeader;

// FIXME: Move to `encode` module
/// Filter strategy for compression.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum FilterStrategy {
    /// Every filter at zero
    Zero,
    /// Use filter that gives minumum sum, as described in the official PNG
    /// filter heuristic.  This is a good default (balance between time to
    /// compress and size).
    MinSum,
    /// Use the filter type that gives smallest Shannon entropy for this
    /// scanline. Depending on the image, this is better or worse than minsum.
    Entropy,
    /// Brute-force-search PNG filters by compressing each filter for each
    /// scanline.  Very slow, and only rarely gives better compression than
    /// MINSUM.
    BruteForce,
}

// FIXME: Not Pub
pub(crate) fn paeth_predictor(a: i16, b: i16, c: i16) -> u8 {
    let pa = (b - c).abs();
    let pb = (a - c).abs();
    let pc = (a + b - c - c).abs();
    if pc < pa && pc < pb {
        c as u8
    } else if pb < pa {
        b as u8
    } else {
        a as u8
    }
}

fn filter_scanline(
    out: &mut [u8],
    scanline: &[u8],
    prevline: Option<&[u8]>,
    length: usize,
    bytewidth: usize,
    filter_type: u8,
) {
    match filter_type {
        0 => {
            out[..length].clone_from_slice(&scanline[..length]);
        }
        1 => {
            out[..bytewidth].clone_from_slice(&scanline[..bytewidth]);
            for i in bytewidth..length {
                out[i] = scanline[i].wrapping_sub(scanline[i - bytewidth]);
            }
        }
        2 => {
            if let Some(prevline) = prevline {
                for i in 0..length {
                    out[i] = scanline[i].wrapping_sub(prevline[i]);
                }
            } else {
                out[..length].clone_from_slice(&scanline[..length]);
            }
        }
        3 => {
            if let Some(prevline) = prevline {
                for i in 0..bytewidth {
                    out[i] = scanline[i].wrapping_sub(prevline[i] >> 1);
                }
                for i in bytewidth..length {
                    let s = scanline[i - bytewidth] as u16 + prevline[i] as u16;
                    out[i] = scanline[i].wrapping_sub((s >> 1) as u8);
                }
            } else {
                out[..bytewidth].clone_from_slice(&scanline[..bytewidth]);
                for i in bytewidth..length {
                    out[i] =
                        scanline[i].wrapping_sub(scanline[i - bytewidth] >> 1);
                }
            }
        }
        4 => {
            if let Some(prevline) = prevline {
                for i in 0..bytewidth {
                    out[i] = scanline[i].wrapping_sub(prevline[i]);
                }
                for i in bytewidth..length {
                    out[i] = scanline[i].wrapping_sub(paeth_predictor(
                        scanline[i - bytewidth].into(),
                        prevline[i].into(),
                        prevline[i - bytewidth].into(),
                    ));
                }
            } else {
                out[..bytewidth].clone_from_slice(&scanline[..bytewidth]);
                for i in bytewidth..length {
                    out[i] = scanline[i].wrapping_sub(scanline[i - bytewidth]);
                }
            }
        }
        _ => {}
    };
}

/// For PNG filter method 0 out must be a buffer with as size:
/// h + (w * h * bpp + 7) / 8, because there are the scanlines with 1 extra byte
/// per scanline
pub(super) fn filter(
    out: &mut [u8],
    inp: &[u8],
    w: usize,
    h: usize,
    header: &ImageHeader,
) {
    let color_type = header.color_type;
    let bit_depth = header.bit_depth;

    let bpp = color_type.bpp(bit_depth) as usize;

    /* the width of a scanline in bytes, not including the filter type */
    let linebytes = (w * bpp + 7) / 8;
    /* bytewidth is used for filtering, is 1 when bpp < 8, number of bytes
     * per pixel otherwise */
    let bytewidth = (bpp + 7) / 8;
    let mut prevline = None;

    for y in 0..h {
        let outindex = (1 + linebytes) * y;
        let inindex = linebytes * y;
        out[outindex] = 0u8;
        filter_scanline(
            &mut out[(outindex + 1)..],
            &inp[inindex..],
            prevline,
            linebytes,
            bytewidth,
            0u8,
        );
        prevline = Some(&inp[inindex..]);
    }
}

#[cfg(test)]
mod tests {
    /*use super::*;

    // FIXME
    #[test]
    fn test_filter() {
        let mut line1 = Vec::with_capacity(1 << 16);
        let mut line2 = Vec::with_capacity(1 << 16);
        for p in 0..256 {
            for q in 0..256 {
                line1.push(q);
                line2.push(p);
            }
        }

        let mut filtered = vec![99u8; 1 << 16];
        let mut unfiltered = vec![66u8; 1 << 16];
        for filter_type in 0..5 {
            let len = filtered.len();
            filter_scanline(
                &mut filtered,
                &line1,
                Some(&line2),
                len,
                1,
                filter_type,
            );
            unfilter_scanline(
                &mut unfiltered,
                &filtered,
                Some(&line2),
                1,
                filter_type,
                len,
            )
            .unwrap();
            assert_eq!(unfiltered, line1, "prev+filter={}", filter_type);
        }
        for filter_type in 0..5 {
            let len = filtered.len();
            filter_scanline(&mut filtered, &line1, None, len, 1, filter_type);
            unfilter_scanline(
                &mut unfiltered,
                &filtered,
                None,
                1,
                filter_type,
                len,
            )
            .unwrap();
            assert_eq!(unfiltered, line1, "none+filter={}", filter_type);
        }
    }*/
}