From 7122214523f5b3c9334ac13d8cd7f4784db27764 Mon Sep 17 00:00:00 2001 From: Andrew Date: Thu, 15 Dec 2022 10:31:35 +1300 Subject: [PATCH] Don't include filter byte in PngImage.data --- src/interlace.rs | 57 +++++++-------------------------- src/png/mod.rs | 19 ++++------- src/png/scan_lines.rs | 65 ++++++++++---------------------------- src/reduction/alpha.rs | 8 ++--- src/reduction/bit_depth.rs | 11 +++---- src/reduction/color.rs | 9 ++---- src/reduction/mod.rs | 5 ++- 7 files changed, 46 insertions(+), 128 deletions(-) diff --git a/src/interlace.rs b/src/interlace.rs index 2ba1a104..8ac0fdc7 100644 --- a/src/interlace.rs +++ b/src/interlace.rs @@ -41,40 +41,7 @@ impl Display for Interlacing { pub fn interlace_image(png: &PngImage) -> PngImage { let mut passes: Vec> = vec![BitVec::new(); 7]; let bits_per_pixel = png.ihdr.bpp(); - for (index, line) in png.scan_lines().enumerate() { - match index % 8 { - // Add filter bytes to passes that will be in the output image - 0 => { - passes[0].extend_from_raw_slice(&[0]); - if png.ihdr.width >= 5 { - passes[1].extend_from_raw_slice(&[0]); - } - if png.ihdr.width >= 3 { - passes[3].extend_from_raw_slice(&[0]); - } - if png.ihdr.width >= 2 { - passes[5].extend_from_raw_slice(&[0]); - } - } - 4 => { - passes[2].extend_from_raw_slice(&[0]); - if png.ihdr.width >= 3 { - passes[3].extend_from_raw_slice(&[0]); - } - if png.ihdr.width >= 2 { - passes[5].extend_from_raw_slice(&[0]); - } - } - 2 | 6 => { - passes[4].extend_from_raw_slice(&[0]); - if png.ihdr.width >= 2 { - passes[5].extend_from_raw_slice(&[0]); - } - } - _ => { - passes[6].extend_from_raw_slice(&[0]); - } - } + for (index, line) in png.scan_lines(false).enumerate() { let bit_vec = line.data.view_bits::(); for (i, bit) in bit_vec.iter().by_vals().enumerate() { // Avoid moving padded 0's into new image @@ -148,15 +115,14 @@ pub fn deinterlace_image(png: &PngImage) -> PngImage { /// Deinterlace by bits, for images with less than 8bpp fn deinterlace_bits(png: &PngImage) -> Vec { let bits_per_pixel = png.ihdr.bpp(); - let bits_per_line = 8 + bits_per_pixel as usize * png.ihdr.width as usize; - // Initialize each output line with a starting filter byte of 0 - // as well as some blank data + let bits_per_line = bits_per_pixel as usize * png.ihdr.width as usize; + // Initialize each output line with blank data let mut lines: Vec> = vec![bitvec![u8, Msb0; 0; bits_per_line]; png.ihdr.height as usize]; let mut current_pass = 1; let mut pass_constants = interlaced_constants(current_pass); let mut current_y: usize = pass_constants.y_shift as usize; - for line in png.scan_lines() { + for line in png.scan_lines(false) { let bit_vec = line.data.view_bits::(); let bits_in_line = ((png.ihdr.width - u32::from(pass_constants.x_shift) + u32::from(pass_constants.x_step) @@ -170,8 +136,8 @@ fn deinterlace_bits(png: &PngImage) -> Vec { } let current_x: usize = pass_constants.x_shift as usize + (i / bits_per_pixel as usize) * pass_constants.x_step as usize; - // Copy this bit into the output line, offset by 8 because of filter byte - let index = 8 + (i % bits_per_pixel as usize) + current_x * bits_per_pixel as usize; + // Copy this bit into the output line + let index = (i % bits_per_pixel as usize) + current_x * bits_per_pixel as usize; lines[current_y].set(index, bit); } // Calculate the next line and move to next pass if necessary @@ -197,19 +163,18 @@ fn deinterlace_bits(png: &PngImage) -> Vec { /// Deinterlace by bytes, for images with at least 8bpp fn deinterlace_bytes(png: &PngImage) -> Vec { let bytes_per_pixel = png.ihdr.bpp() / 8; - let bytes_per_line = 1 + bytes_per_pixel as usize * png.ihdr.width as usize; - // Initialize each output line with a starting filter byte of 0 - // as well as some blank data + let bytes_per_line = bytes_per_pixel as usize * png.ihdr.width as usize; + // Initialize each output line with some blank data let mut lines: Vec> = vec![vec![0; bytes_per_line]; png.ihdr.height as usize]; let mut current_pass = 1; let mut pass_constants = interlaced_constants(current_pass); let mut current_y: usize = pass_constants.y_shift as usize; - for line in png.scan_lines() { + for line in png.scan_lines(false) { for (i, byte) in line.data.iter().enumerate() { let current_x: usize = pass_constants.x_shift as usize + (i / bytes_per_pixel as usize) * pass_constants.x_step as usize; - // Copy this byte into the output line, offset by 1 because of filter byte - let index = 1 + (i % bytes_per_pixel as usize) + current_x * bytes_per_pixel as usize; + // Copy this byte into the output line + let index = (i % bytes_per_pixel as usize) + current_x * bytes_per_pixel as usize; lines[current_y][index] = *byte; } // Calculate the next line and move to next pass if necessary diff --git a/src/png/mod.rs b/src/png/mod.rs index de7bf546..f3d712d0 100644 --- a/src/png/mod.rs +++ b/src/png/mod.rs @@ -18,7 +18,7 @@ use std::sync::Arc; pub(crate) mod scan_lines; -use self::scan_lines::{ScanLines, ScanLinesMut}; +use self::scan_lines::ScanLines; /// Compression level to use for the Brute filter strategy const BRUTE_LEVEL: i32 = 1; // 1 is fastest, 2-4 are not useful, 5 is slower but more effective @@ -29,7 +29,7 @@ const BRUTE_LINES: usize = 4; // Values over 8 are generally not useful pub struct PngImage { /// The headers stored in the IHDR chunk pub ihdr: IhdrData, - /// The uncompressed, optionally filtered data from the IDAT chunk + /// The uncompressed, unfiltered data from the IDAT chunk pub data: Vec, /// The palette containing colors used in an Indexed image /// Contains 3 bytes per color (R+G+B), up to 768 @@ -280,14 +280,8 @@ impl PngImage { /// Return an iterator over the scanlines of the image #[inline] - pub fn scan_lines(&self) -> ScanLines<'_> { - ScanLines::new(self) - } - - /// Return an iterator over the scanlines of the image - #[inline] - pub fn scan_lines_mut(&mut self) -> ScanLinesMut<'_> { - ScanLinesMut::new(self) + pub fn scan_lines(&self, has_filter: bool) -> ScanLines<'_> { + ScanLines::new(self, has_filter) } /// Reverse all filters applied on the image, returning an unfiltered IDAT bytestream @@ -297,7 +291,7 @@ impl PngImage { let mut last_line: Vec = Vec::new(); let mut last_pass = None; let mut unfiltered_buf = Vec::new(); - for line in self.scan_lines() { + for line in self.scan_lines(true) { if last_pass != line.pass { last_line.clear(); last_pass = line.pass; @@ -305,7 +299,6 @@ impl PngImage { last_line.resize(line.data.len(), 0); let filter = RowFilter::try_from(line.filter).map_err(|_| PngError::InvalidData)?; filter.unfilter_line(bpp, line.data, &last_line, &mut unfiltered_buf)?; - unfiltered.push(0); unfiltered.extend_from_slice(&unfiltered_buf); std::mem::swap(&mut last_line, &mut unfiltered_buf); unfiltered_buf.clear(); @@ -328,7 +321,7 @@ impl PngImage { let mut prev_line = Vec::new(); let mut prev_pass: Option = None; let mut f_buf = Vec::new(); - for line in self.scan_lines() { + for line in self.scan_lines(false) { if prev_pass != line.pass || line.data.len() != prev_line.len() { prev_line = vec![0; line.data.len()]; } diff --git a/src/png/scan_lines.rs b/src/png/scan_lines.rs index 42b0f4a9..509373b2 100644 --- a/src/png/scan_lines.rs +++ b/src/png/scan_lines.rs @@ -7,13 +7,16 @@ pub struct ScanLines<'a> { iter: ScanLineRanges, /// A reference to the PNG image being iterated upon raw_data: &'a [u8], + /// Whether the raw data contains filter bytes + has_filter: bool, } impl<'a> ScanLines<'a> { - pub fn new(png: &'a PngImage) -> Self { + pub fn new(png: &'a PngImage, has_filter: bool) -> Self { Self { - iter: ScanLineRanges::new(png), + iter: ScanLineRanges::new(png, has_filter), raw_data: &png.data, + has_filter, } } } @@ -25,43 +28,16 @@ impl<'a> Iterator for ScanLines<'a> { self.iter.next().map(|(len, pass)| { let (data, rest) = self.raw_data.split_at(len); self.raw_data = rest; - let (&filter, data) = data.split_first().unwrap(); + let (&filter, data) = if self.has_filter { + data.split_first().unwrap() + } else { + (&0, data) + }; ScanLine { filter, data, pass } }) } } -#[derive(Debug)] -/// An iterator over the scan lines of a PNG image -pub struct ScanLinesMut<'a> { - iter: ScanLineRanges, - /// A reference to the PNG image being iterated upon - raw_data: Option<&'a mut [u8]>, -} - -impl<'a> ScanLinesMut<'a> { - pub fn new(png: &'a mut PngImage) -> Self { - Self { - iter: ScanLineRanges::new(png), - raw_data: Some(&mut png.data), - } - } -} - -impl<'a> Iterator for ScanLinesMut<'a> { - type Item = ScanLineMut<'a>; - #[inline] - fn next(&mut self) -> Option { - self.iter.next().map(|(len, pass)| { - let tmp = self.raw_data.take().unwrap(); - let (data, rest) = tmp.split_at_mut(len); - self.raw_data = Some(rest); - let (&mut filter, data) = data.split_first_mut().unwrap(); - ScanLineMut { filter, data, pass } - }) - } -} - #[derive(Debug, Clone)] /// An iterator over the scan line locations of a PNG image struct ScanLineRanges { @@ -71,10 +47,11 @@ struct ScanLineRanges { width: u32, height: u32, left: usize, + has_filter: bool, } impl ScanLineRanges { - pub fn new(png: &PngImage) -> Self { + pub fn new(png: &PngImage, has_filter: bool) -> Self { Self { bits_per_pixel: png.ihdr.bit_depth.as_u8() * png.channels_per_pixel(), width: png.ihdr.width, @@ -85,6 +62,7 @@ impl ScanLineRanges { } else { None }, + has_filter, } } } @@ -166,8 +144,10 @@ impl Iterator for ScanLineRanges { (self.width, None) }; let bits_per_line = pixels_per_line * u32::from(self.bits_per_pixel); - let bytes_per_line = ((bits_per_line + 7) / 8) as usize; - let len = bytes_per_line + 1; + let mut len = ((bits_per_line + 7) / 8) as usize; + if self.has_filter { + len += 1; + } self.left = self.left.checked_sub(len)?; Some((len, current_pass)) } @@ -183,14 +163,3 @@ pub struct ScanLine<'a> { /// The current pass if the image is interlaced pub pass: Option, } - -#[derive(Debug)] -/// A scan line in a PNG image -pub struct ScanLineMut<'a> { - /// The filter type used to encode the current scan line (0-4) - pub filter: u8, - /// The byte data for the current scan line, encoded with the filter specified in the `filter` field - pub data: &'a mut [u8], - /// The current pass if the image is interlaced - pub pass: Option, -} diff --git a/src/reduction/alpha.rs b/src/reduction/alpha.rs index cd8952f6..e35d737a 100644 --- a/src/reduction/alpha.rs +++ b/src/reduction/alpha.rs @@ -16,8 +16,7 @@ pub fn cleaned_alpha_channel(png: &PngImage) -> Option { }; let mut reduced = Vec::with_capacity(png.data.len()); - for line in png.scan_lines() { - reduced.push(line.filter); + for line in png.scan_lines(false) { for pixel in line.data.chunks(bpp) { if pixel.iter().skip(bpp - bpc).all(|b| *b == 0) { reduced.resize(reduced.len() + bpp, 0); @@ -54,7 +53,7 @@ pub fn reduced_alpha_channel(png: &PngImage, optimize_alpha: bool) -> Option Option { diff --git a/src/reduction/bit_depth.rs b/src/reduction/bit_depth.rs index 7c8af41b..e6069333 100644 --- a/src/reduction/bit_depth.rs +++ b/src/reduction/bit_depth.rs @@ -38,13 +38,11 @@ pub fn reduce_bit_depth(png: &PngImage, minimum_bits: usize) -> Option // Reduce from 16 to 8 bits per channel per pixel let mut reduced = Vec::with_capacity( - (png.ihdr.width * png.ihdr.height * u32::from(png.channels_per_pixel()) + png.ihdr.height) - as usize, + (png.ihdr.width * png.ihdr.height * u32::from(png.channels_per_pixel())) as usize, ); let mut high_byte = 0; - for line in png.scan_lines() { - reduced.push(line.filter); + for line in png.scan_lines(false) { for (i, &byte) in line.data.iter().enumerate() { if i % 2 == 0 { // High byte @@ -79,7 +77,7 @@ pub fn reduce_bit_depth_8_or_less(png: &PngImage, mut minimum_bits: usize) -> Op if minimum_bits >= bit_depth { return None; } - for line in png.scan_lines() { + for line in png.scan_lines(false) { if png.ihdr.color_type == ColorType::Indexed { let line_max = line .data @@ -129,8 +127,7 @@ pub fn reduce_bit_depth_8_or_less(png: &PngImage, mut minimum_bits: usize) -> Op } let mut reduced = BitVec::::with_capacity(png.data.len() * 8); - for line in png.scan_lines() { - reduced.extend_from_raw_slice(&[line.filter]); + for line in png.scan_lines(false) { let bit_vec = line.data.view_bits::(); for (i, bit) in bit_vec.iter().by_vals().enumerate() { let bit_index = bit_depth - (i % bit_depth); diff --git a/src/reduction/color.rs b/src/reduction/color.rs index 2ada69de..260d7e9a 100644 --- a/src/reduction/color.rs +++ b/src/reduction/color.rs @@ -19,8 +19,7 @@ pub fn reduce_rgba_to_grayscale_alpha(png: &PngImage) -> Option { return None; } let colored_bytes = bpp - byte_depth; - for line in png.scan_lines() { - reduced.push(line.filter); + for line in png.scan_lines(false) { let mut low_bytes = Vec::with_capacity(4); let mut high_bytes = Vec::with_capacity(4); let mut trans_bytes = Vec::with_capacity(byte_depth as usize); @@ -117,8 +116,7 @@ pub fn reduce_to_palette(png: &PngImage) -> Option { .as_ref() .filter(|t| png.ihdr.color_type == ColorType::RGB && t.len() >= 6) .map(|t| RGB8::new(t[1], t[3], t[5])); - for line in png.scan_lines() { - raw_data.push(line.filter); + for line in png.scan_lines(false) { let ok = if png.ihdr.color_type == ColorType::RGB { reduce_scanline_to_palette( line.data.as_rgb().iter().cloned().map(|px| { @@ -221,8 +219,7 @@ pub fn reduce_rgb_to_grayscale(png: &PngImage) -> Option { let byte_depth: u8 = png.ihdr.bit_depth.as_u8() >> 3; let bpp: usize = 3 * byte_depth as usize; let mut cur_pixel = Vec::with_capacity(bpp); - for line in png.scan_lines() { - reduced.push(line.filter); + for line in png.scan_lines(false) { for (i, byte) in line.data.iter().enumerate() { cur_pixel.push(*byte); if i % bpp == bpp - 1 { diff --git a/src/reduction/mod.rs b/src/reduction/mod.rs index 4bf2ec6e..1601b2fc 100644 --- a/src/reduction/mod.rs +++ b/src/reduction/mod.rs @@ -34,7 +34,7 @@ pub fn reduced_palette(png: &PngImage, optimize_alpha: bool) -> Option let palette = png.palette.as_ref()?; // Find palette entries that are never used - for line in png.scan_lines() { + for line in png.scan_lines(false) { match png.ihdr.bit_depth { BitDepth::Eight => { for &byte in line.data { @@ -120,8 +120,7 @@ fn do_palette_reduction(png: &PngImage, palette_map: &[Option; 256]) -> Opti let mut raw_data = Vec::with_capacity(png.data.len()); // Reassign data bytes to new indices - for line in png.scan_lines() { - raw_data.push(line.filter); + for line in png.scan_lines(false) { for byte in line.data { raw_data.push(byte_map[*byte as usize]); }