From d70b35053ea0c13eb981c12d567747ea1b4c9feb Mon Sep 17 00:00:00 2001 From: Andrew Date: Sat, 10 Dec 2022 13:45:16 +1300 Subject: [PATCH] Revamp alpha optimization --- benches/filters.rs | 60 +++++++++--------- benches/strategies.rs | 10 +-- src/evaluate.rs | 12 +++- src/filters.rs | 137 +++++++++++++++++++++++++++-------------- src/lib.rs | 34 +++++----- src/png/mod.rs | 57 +++++++++++------ src/reduction/alpha.rs | 25 -------- src/reduction/mod.rs | 2 +- 8 files changed, 192 insertions(+), 145 deletions(-) diff --git a/benches/filters.rs b/benches/filters.rs index 687f9709..b1ce453a 100644 --- a/benches/filters.rs +++ b/benches/filters.rs @@ -13,7 +13,7 @@ fn filters_16_bits_filter_0(b: &mut Bencher) { let png = PngData::new(&input, false).unwrap(); b.iter(|| { - png.raw.filter_image(RowFilter::None); + png.raw.filter_image(RowFilter::None, false); }); } @@ -23,7 +23,7 @@ fn filters_8_bits_filter_0(b: &mut Bencher) { let png = PngData::new(&input, false).unwrap(); b.iter(|| { - png.raw.filter_image(RowFilter::None); + png.raw.filter_image(RowFilter::None, false); }); } @@ -35,7 +35,7 @@ fn filters_4_bits_filter_0(b: &mut Bencher) { let png = PngData::new(&input, false).unwrap(); b.iter(|| { - png.raw.filter_image(RowFilter::None); + png.raw.filter_image(RowFilter::None, false); }); } @@ -47,7 +47,7 @@ fn filters_2_bits_filter_0(b: &mut Bencher) { let png = PngData::new(&input, false).unwrap(); b.iter(|| { - png.raw.filter_image(RowFilter::None); + png.raw.filter_image(RowFilter::None, false); }); } @@ -59,7 +59,7 @@ fn filters_1_bits_filter_0(b: &mut Bencher) { let png = PngData::new(&input, false).unwrap(); b.iter(|| { - png.raw.filter_image(RowFilter::None); + png.raw.filter_image(RowFilter::None, false); }); } @@ -69,7 +69,7 @@ fn filters_16_bits_filter_1(b: &mut Bencher) { let png = PngData::new(&input, false).unwrap(); b.iter(|| { - png.raw.filter_image(RowFilter::Sub); + png.raw.filter_image(RowFilter::Sub, false); }); } @@ -79,7 +79,7 @@ fn filters_8_bits_filter_1(b: &mut Bencher) { let png = PngData::new(&input, false).unwrap(); b.iter(|| { - png.raw.filter_image(RowFilter::Sub); + png.raw.filter_image(RowFilter::Sub, false); }); } @@ -91,7 +91,7 @@ fn filters_4_bits_filter_1(b: &mut Bencher) { let png = PngData::new(&input, false).unwrap(); b.iter(|| { - png.raw.filter_image(RowFilter::Sub); + png.raw.filter_image(RowFilter::Sub, false); }); } @@ -103,7 +103,7 @@ fn filters_2_bits_filter_1(b: &mut Bencher) { let png = PngData::new(&input, false).unwrap(); b.iter(|| { - png.raw.filter_image(RowFilter::Sub); + png.raw.filter_image(RowFilter::Sub, false); }); } @@ -115,7 +115,7 @@ fn filters_1_bits_filter_1(b: &mut Bencher) { let png = PngData::new(&input, false).unwrap(); b.iter(|| { - png.raw.filter_image(RowFilter::Sub); + png.raw.filter_image(RowFilter::Sub, false); }); } @@ -125,7 +125,7 @@ fn filters_16_bits_filter_2(b: &mut Bencher) { let png = PngData::new(&input, false).unwrap(); b.iter(|| { - png.raw.filter_image(RowFilter::Up); + png.raw.filter_image(RowFilter::Up, false); }); } @@ -135,7 +135,7 @@ fn filters_8_bits_filter_2(b: &mut Bencher) { let png = PngData::new(&input, false).unwrap(); b.iter(|| { - png.raw.filter_image(RowFilter::Up); + png.raw.filter_image(RowFilter::Up, false); }); } @@ -147,7 +147,7 @@ fn filters_4_bits_filter_2(b: &mut Bencher) { let png = PngData::new(&input, false).unwrap(); b.iter(|| { - png.raw.filter_image(RowFilter::Up); + png.raw.filter_image(RowFilter::Up, false); }); } @@ -159,7 +159,7 @@ fn filters_2_bits_filter_2(b: &mut Bencher) { let png = PngData::new(&input, false).unwrap(); b.iter(|| { - png.raw.filter_image(RowFilter::Up); + png.raw.filter_image(RowFilter::Up, false); }); } @@ -171,7 +171,7 @@ fn filters_1_bits_filter_2(b: &mut Bencher) { let png = PngData::new(&input, false).unwrap(); b.iter(|| { - png.raw.filter_image(RowFilter::Up); + png.raw.filter_image(RowFilter::Up, false); }); } @@ -181,7 +181,7 @@ fn filters_16_bits_filter_3(b: &mut Bencher) { let png = PngData::new(&input, false).unwrap(); b.iter(|| { - png.raw.filter_image(RowFilter::Average); + png.raw.filter_image(RowFilter::Average, false); }); } @@ -191,7 +191,7 @@ fn filters_8_bits_filter_3(b: &mut Bencher) { let png = PngData::new(&input, false).unwrap(); b.iter(|| { - png.raw.filter_image(RowFilter::Average); + png.raw.filter_image(RowFilter::Average, false); }); } @@ -203,7 +203,7 @@ fn filters_4_bits_filter_3(b: &mut Bencher) { let png = PngData::new(&input, false).unwrap(); b.iter(|| { - png.raw.filter_image(RowFilter::Average); + png.raw.filter_image(RowFilter::Average, false); }); } @@ -215,7 +215,7 @@ fn filters_2_bits_filter_3(b: &mut Bencher) { let png = PngData::new(&input, false).unwrap(); b.iter(|| { - png.raw.filter_image(RowFilter::Average); + png.raw.filter_image(RowFilter::Average, false); }); } @@ -227,7 +227,7 @@ fn filters_1_bits_filter_3(b: &mut Bencher) { let png = PngData::new(&input, false).unwrap(); b.iter(|| { - png.raw.filter_image(RowFilter::Average); + png.raw.filter_image(RowFilter::Average, false); }); } @@ -237,7 +237,7 @@ fn filters_16_bits_filter_4(b: &mut Bencher) { let png = PngData::new(&input, false).unwrap(); b.iter(|| { - png.raw.filter_image(RowFilter::Paeth); + png.raw.filter_image(RowFilter::Paeth, false); }); } @@ -247,7 +247,7 @@ fn filters_8_bits_filter_4(b: &mut Bencher) { let png = PngData::new(&input, false).unwrap(); b.iter(|| { - png.raw.filter_image(RowFilter::Paeth); + png.raw.filter_image(RowFilter::Paeth, false); }); } @@ -259,7 +259,7 @@ fn filters_4_bits_filter_4(b: &mut Bencher) { let png = PngData::new(&input, false).unwrap(); b.iter(|| { - png.raw.filter_image(RowFilter::Paeth); + png.raw.filter_image(RowFilter::Paeth, false); }); } @@ -271,7 +271,7 @@ fn filters_2_bits_filter_4(b: &mut Bencher) { let png = PngData::new(&input, false).unwrap(); b.iter(|| { - png.raw.filter_image(RowFilter::Paeth); + png.raw.filter_image(RowFilter::Paeth, false); }); } @@ -283,7 +283,7 @@ fn filters_1_bits_filter_4(b: &mut Bencher) { let png = PngData::new(&input, false).unwrap(); b.iter(|| { - png.raw.filter_image(RowFilter::Paeth); + png.raw.filter_image(RowFilter::Paeth, false); }); } @@ -293,7 +293,7 @@ fn filters_16_bits_filter_5(b: &mut Bencher) { let png = PngData::new(&input, false).unwrap(); b.iter(|| { - png.raw.filter_image(RowFilter::MinSum); + png.raw.filter_image(RowFilter::MinSum, false); }); } @@ -303,7 +303,7 @@ fn filters_8_bits_filter_5(b: &mut Bencher) { let png = PngData::new(&input, false).unwrap(); b.iter(|| { - png.raw.filter_image(RowFilter::MinSum); + png.raw.filter_image(RowFilter::MinSum, false); }); } @@ -315,7 +315,7 @@ fn filters_4_bits_filter_5(b: &mut Bencher) { let png = PngData::new(&input, false).unwrap(); b.iter(|| { - png.raw.filter_image(RowFilter::MinSum); + png.raw.filter_image(RowFilter::MinSum, false); }); } @@ -327,7 +327,7 @@ fn filters_2_bits_filter_5(b: &mut Bencher) { let png = PngData::new(&input, false).unwrap(); b.iter(|| { - png.raw.filter_image(RowFilter::MinSum); + png.raw.filter_image(RowFilter::MinSum, false); }); } @@ -339,6 +339,6 @@ fn filters_1_bits_filter_5(b: &mut Bencher) { let png = PngData::new(&input, false).unwrap(); b.iter(|| { - png.raw.filter_image(RowFilter::MinSum); + png.raw.filter_image(RowFilter::MinSum, false); }); } diff --git a/benches/strategies.rs b/benches/strategies.rs index a8b6c091..de5a009a 100644 --- a/benches/strategies.rs +++ b/benches/strategies.rs @@ -13,7 +13,7 @@ fn filters_minsum(b: &mut Bencher) { let png = PngData::new(&input, false).unwrap(); b.iter(|| { - png.raw.filter_image(RowFilter::MinSum); + png.raw.filter_image(RowFilter::MinSum, false); }); } @@ -23,7 +23,7 @@ fn filters_entropy(b: &mut Bencher) { let png = PngData::new(&input, false).unwrap(); b.iter(|| { - png.raw.filter_image(RowFilter::Entropy); + png.raw.filter_image(RowFilter::Entropy, false); }); } @@ -33,7 +33,7 @@ fn filters_bigrams(b: &mut Bencher) { let png = PngData::new(&input, false).unwrap(); b.iter(|| { - png.raw.filter_image(RowFilter::Bigrams); + png.raw.filter_image(RowFilter::Bigrams, false); }); } @@ -43,7 +43,7 @@ fn filters_bigent(b: &mut Bencher) { let png = PngData::new(&input, false).unwrap(); b.iter(|| { - png.raw.filter_image(RowFilter::BigEnt); + png.raw.filter_image(RowFilter::BigEnt, false); }); } @@ -53,6 +53,6 @@ fn filters_brute(b: &mut Bencher) { let png = PngData::new(&input, false).unwrap(); b.iter(|| { - png.raw.filter_image(RowFilter::Brute); + png.raw.filter_image(RowFilter::Brute, false); }); } diff --git a/src/evaluate.rs b/src/evaluate.rs index 13cc0473..23cc9832 100644 --- a/src/evaluate.rs +++ b/src/evaluate.rs @@ -44,6 +44,7 @@ pub(crate) struct Evaluator { deadline: Arc, filters: IndexSet, compression: u8, + optimize_alpha: bool, nth: AtomicUsize, best_candidate_size: Arc, /// images are sent to the caller thread for evaluation @@ -55,13 +56,19 @@ pub(crate) struct Evaluator { } impl Evaluator { - pub fn new(deadline: Arc, filters: IndexSet, compression: u8) -> Self { + pub fn new( + deadline: Arc, + filters: IndexSet, + compression: u8, + optimize_alpha: bool, + ) -> Self { #[cfg(feature = "parallel")] let eval_channel = unbounded(); Self { deadline, filters, compression, + optimize_alpha, best_candidate_size: Arc::new(AtomicMin::new(None)), nth: AtomicUsize::new(0), #[cfg(feature = "parallel")] @@ -106,6 +113,7 @@ impl Evaluator { let deadline = self.deadline.clone(); let filters = self.filters.clone(); let compression = self.compression; + let optimize_alpha = self.optimize_alpha; let best_candidate_size = self.best_candidate_size.clone(); // sends it off asynchronously for compression, // but results will be collected via the message queue @@ -122,7 +130,7 @@ impl Evaluator { if deadline.passed() { return; } - let filtered = image.filter_image(filter); + let filtered = image.filter_image(filter, optimize_alpha); if let Ok(idat_data) = deflate::deflate(&filtered, compression, &best_candidate_size) { diff --git a/src/filters.rs b/src/filters.rs index ec62e8fe..2656e323 100644 --- a/src/filters.rs +++ b/src/filters.rs @@ -56,9 +56,21 @@ impl RowFilter { pub const STANDARD: [Self; 5] = [Self::None, Self::Sub, Self::Up, Self::Average, Self::Paeth]; pub const SINGLE_LINE: [Self; 2] = [Self::None, Self::Sub]; - pub fn filter_line(self, bpp: usize, data: &[u8], last_line: &[u8], buf: &mut Vec) { + pub fn filter_line( + self, + bpp: usize, + data: &mut [u8], + prev_line: &[u8], + buf: &mut Vec, + alpha_bytes: usize, + ) { assert!(data.len() >= bpp); - assert!(last_line.is_empty() || data.len() == last_line.len()); + assert_eq!(data.len(), prev_line.len()); + + if alpha_bytes != 0 { + self.optimize_alpha(bpp, data, prev_line, bpp - alpha_bytes); + } + buf.clear(); buf.reserve(data.len() + 1); buf.push(self as u8); @@ -76,68 +88,101 @@ impl RowFilter { ); } Self::Up => { - if last_line.is_empty() { - buf.extend_from_slice(data); - } else { - assert_eq!(data.len(), last_line.len()); - buf.extend( - data.iter() - .zip(last_line.iter()) - .map(|(cur, last)| cur.wrapping_sub(*last)), - ); - }; + buf.extend( + data.iter() + .zip(prev_line.iter()) + .map(|(cur, last)| cur.wrapping_sub(*last)), + ); } Self::Average => { for (i, byte) in data.iter().enumerate() { - if last_line.is_empty() { - buf.push(match i.checked_sub(bpp) { - Some(x) => byte.wrapping_sub(data[x] >> 1), - None => *byte, - }); - } else { - buf.push(match i.checked_sub(bpp) { - Some(x) => byte.wrapping_sub( - ((u16::from(data[x]) + u16::from(last_line[i])) >> 1) as u8, - ), - None => byte.wrapping_sub(last_line[i] >> 1), - }); - }; + buf.push(match i.checked_sub(bpp) { + Some(x) => byte.wrapping_sub( + ((u16::from(data[x]) + u16::from(prev_line[i])) >> 1) as u8, + ), + None => byte.wrapping_sub(prev_line[i] >> 1), + }); } } Self::Paeth => { for (i, byte) in data.iter().enumerate() { - if last_line.is_empty() { - buf.push(match i.checked_sub(bpp) { - Some(x) => byte.wrapping_sub(data[x]), - None => *byte, - }); - } else { - buf.push(match i.checked_sub(bpp) { - Some(x) => byte.wrapping_sub(paeth_predictor( - data[x], - last_line[i], - last_line[x], - )), - None => byte.wrapping_sub(last_line[i]), - }); - }; + buf.push(match i.checked_sub(bpp) { + Some(x) => { + byte.wrapping_sub(paeth_predictor(data[x], prev_line[i], prev_line[x])) + } + None => byte.wrapping_sub(prev_line[i]), + }); } } _ => unreachable!(), } } + // Optimize fully transparent pixels of a scanline such that they will be zeroed when filtered + fn optimize_alpha(self, bpp: usize, data: &mut [u8], prev_line: &[u8], color_bytes: usize) { + if self == Self::None { + // Assume transparent pixels already set to 0 + return; + } + + let mut pixels: Vec<_> = data.chunks_mut(bpp).collect(); + let prev_pixels: Vec<_> = prev_line.chunks(bpp).collect(); + for i in 0..pixels.len() { + if pixels[i].iter().skip(color_bytes).all(|b| *b == 0) { + match self { + Self::Sub => { + for j in 0..color_bytes { + pixels[i][j] = match i { + 0 => 0, + _ => pixels[i - 1][j], + }; + } + } + Self::Up => { + pixels[i][0..color_bytes].copy_from_slice(&prev_pixels[i][0..color_bytes]); + } + Self::Average => { + for j in 0..color_bytes { + pixels[i][j] = match i { + 0 => prev_pixels[i][j] >> 1, + _ => { + ((u16::from(pixels[i - 1][j]) + u16::from(prev_pixels[i][j])) + >> 1) as u8 + } + }; + } + } + Self::Paeth => { + if i == 0 { + pixels[i][0..color_bytes] + .copy_from_slice(&prev_pixels[i][0..color_bytes]); + } else { + for j in 0..color_bytes { + pixels[i][j] = paeth_predictor( + pixels[i - 1][j], + prev_pixels[i][j], + prev_pixels[i - 1][j], + ); + } + } + } + _ => unreachable!(), + } + } + } + } + pub fn unfilter_line( self, bpp: usize, data: &[u8], - last_line: &[u8], + prev_line: &[u8], buf: &mut Vec, ) -> Result<(), PngError> { buf.clear(); buf.reserve(data.len()); assert!(data.len() >= bpp); - assert_eq!(data.len(), last_line.len()); + assert_eq!(data.len(), prev_line.len()); match self { Self::None => { buf.extend_from_slice(data); @@ -154,12 +199,12 @@ impl RowFilter { Self::Up => { buf.extend( data.iter() - .zip(last_line) + .zip(prev_line) .map(|(&cur, &last)| cur.wrapping_add(last)), ); } Self::Average => { - for (i, (&cur, &last)) in data.iter().zip(last_line).enumerate() { + for (i, (&cur, &last)) in data.iter().zip(prev_line).enumerate() { let prev_byte = i.checked_sub(bpp).and_then(|x| buf.get(x).copied()); buf.push(match prev_byte { Some(b) => cur.wrapping_add(((u16::from(b) + u16::from(last)) >> 1) as u8), @@ -168,11 +213,11 @@ impl RowFilter { } } Self::Paeth => { - for (i, (&cur, &up)) in data.iter().zip(last_line).enumerate() { + for (i, (&cur, &up)) in data.iter().zip(prev_line).enumerate() { buf.push( match i .checked_sub(bpp) - .map(|x| (buf.get(x).copied(), last_line.get(x).copied())) + .map(|x| (buf.get(x).copied(), prev_line.get(x).copied())) { Some((Some(left), Some(left_up))) => { cur.wrapping_add(paeth_predictor(left, up, left_up)) diff --git a/src/lib.rs b/src/lib.rs index cd74186a..6d3bd5e5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -491,13 +491,25 @@ fn optimize_png( perform_strip(png, opts); let stripped_png = png.clone(); + // If alpha optimization is enabled, first perform a black alpha reduction + // This can allow reductions from alpha to indexed which may not have been possible otherwise + if !opts.alphas.is_empty() { + if let Some(reduced) = filtered_alpha_channel(&png.raw, AlphaOptim::Black) { + png.raw = Arc::new(reduced); + } + } + // Must use normal (lazy) compression, as faster ones (greedy) are not representative - // Alpha reductions can benefit from higher compression but otherwise it's not beneficial let eval_compression = 5; // None and Bigrams work well together, especially for alpha reductions let eval_filters = indexset! {RowFilter::None, RowFilter::Bigrams}; // This will collect all versions of images and pick one that compresses best - let eval = Evaluator::new(deadline.clone(), eval_filters.clone(), eval_compression); + let eval = Evaluator::new( + deadline.clone(), + eval_filters.clone(), + eval_compression, + false, + ); perform_reductions(png.raw.clone(), opts, &deadline, &eval); let (reduction_occurred, mut eval_filter) = if let Some(result) = eval.get_best_candidate() { *png = result.image; @@ -519,7 +531,8 @@ fn optimize_png( if !filters.is_empty() { debug!("Evaluating: {} filters", filters.len()); - let eval = Evaluator::new(deadline, filters, eval_compression); + let eval = + Evaluator::new(deadline, filters, eval_compression, !opts.alphas.is_empty()); if eval_filter.is_some() { eval.set_best_size(png.idat_data.len()); } @@ -585,7 +598,7 @@ fn optimize_png( if deadline.passed() { return None; } - let filtered = &png.raw.filter_image(trial.filter); + let filtered = &png.raw.filter_image(trial.filter, !opts.alphas.is_empty()); perform_trial(filtered, opts, trial, &best_size) }); best.reduce_with(|i, j| { @@ -606,7 +619,7 @@ fn optimize_png( opts.filter, png.idat_data.len() ); - } else if eval_filter.is_some() { + } else { *png = stripped_png; } } else if png.idat_data.len() >= idat_original_size { @@ -726,13 +739,6 @@ fn perform_reductions( } if opts.color_type_reduction { - // Perform a black alpha reduction before color type reductions - // This can allow reductions from alpha to indexed which may not have been possible otherwise - if !opts.alphas.is_empty() { - if let Some(reduced) = filtered_alpha_channel(&png, AlphaOptim::Black) { - png = Arc::new(reduced); - } - } if let Some(reduced) = reduce_color_type(&png, opts.grayscale_reduction) { png = Arc::new(reduced); eval.try_image(png.clone()); @@ -744,10 +750,6 @@ fn perform_reductions( } } - if try_alpha_reductions(png, &opts.alphas, eval) { - reduction_occurred = true; - } - if let Some(baseline) = baseline { if reduction_occurred { eval.set_baseline(baseline); diff --git a/src/png/mod.rs b/src/png/mod.rs index 3ef33846..4f479431 100644 --- a/src/png/mod.rs +++ b/src/png/mod.rs @@ -314,31 +314,43 @@ impl PngImage { } /// Apply the specified filter type to all rows in the image - pub fn filter_image(&self, filter: RowFilter) -> Vec { + pub fn filter_image(&self, filter: RowFilter, optimize_alpha: bool) -> Vec { let mut filtered = Vec::with_capacity(self.data.len()); let bpp = ((self.ihdr.bit_depth.as_u8() * self.channels_per_pixel() + 7) / 8) as usize; - let mut last_line: &[u8] = &[]; - let mut last_pass: Option = None; + // If alpha optimization is enabled, determine how many bytes of alpha there are per pixel + let alpha_bytes = match self.ihdr.color_type { + ColorType::RGBA | ColorType::GrayscaleAlpha if optimize_alpha => { + (self.ihdr.bit_depth.as_u8() / 8) as usize + } + _ => 0, + }; + + let mut prev_line = Vec::new(); + let mut prev_pass: Option = None; let mut f_buf = Vec::new(); for line in self.scan_lines() { - if last_pass != line.pass { - last_line = &[]; + if prev_pass != line.pass || line.data.len() != prev_line.len() { + prev_line = vec![0; line.data.len()]; } + // Alpha optimisation may alter the line data, so we need a mutable copy of it + let mut line_data = line.data.to_vec(); if filter <= RowFilter::Paeth { // Standard filters - let filter = if last_pass == line.pass || filter <= RowFilter::Sub { + let filter = if prev_pass == line.pass || filter <= RowFilter::Sub { filter } else { RowFilter::None }; - filter.filter_line(bpp, line.data, last_line, &mut f_buf); + filter.filter_line(bpp, &mut line_data, &prev_line, &mut f_buf, alpha_bytes); filtered.extend_from_slice(&f_buf); + prev_line = line_data; } else { // Heuristic filter selection strategies let mut best_line = Vec::new(); + let mut best_line_raw = Vec::new(); // Avoid vertical filtering on first line of each interlacing pass - let try_filters = if last_pass == line.pass { + let try_filters = if prev_pass == line.pass { RowFilter::STANDARD.iter() } else { RowFilter::SINGLE_LINE.iter() @@ -348,8 +360,8 @@ impl PngImage { // MSAD algorithm mentioned in libpng reference docs // http://www.libpng.org/pub/png/book/chapter09.html let mut best_size = usize::MAX; - for try_filter in try_filters { - try_filter.filter_line(bpp, line.data, last_line, &mut f_buf); + for f in try_filters { + f.filter_line(bpp, &mut line_data, &prev_line, &mut f_buf, alpha_bytes); let size = f_buf.iter().fold(0, |acc, &x| { let signed = x as i8; acc + signed.unsigned_abs() as usize @@ -357,6 +369,7 @@ impl PngImage { if size < best_size { best_size = size; std::mem::swap(&mut best_line, &mut f_buf); + best_line_raw = line_data.clone(); } } } @@ -364,8 +377,8 @@ impl PngImage { // Shannon entropy algorithm, from LodePNG // https://github.com/lvandeve/lodepng let mut best_size = i32::MIN; - for try_filter in try_filters { - try_filter.filter_line(bpp, line.data, last_line, &mut f_buf); + for f in try_filters { + f.filter_line(bpp, &mut line_data, &prev_line, &mut f_buf, alpha_bytes); let mut counts = vec![0; 0x100]; for &i in f_buf.iter() { counts[i as usize] += 1; @@ -379,6 +392,7 @@ impl PngImage { if size > best_size { best_size = size; std::mem::swap(&mut best_line, &mut f_buf); + best_line_raw = line_data.clone(); } } } @@ -386,8 +400,8 @@ impl PngImage { // Count distinct bigrams, from pngwolf // https://bjoern.hoehrmann.de/pngwolf/ let mut best_size = usize::MAX; - for try_filter in try_filters { - try_filter.filter_line(bpp, line.data, last_line, &mut f_buf); + for f in try_filters { + f.filter_line(bpp, &mut line_data, &prev_line, &mut f_buf, alpha_bytes); let mut set = bitarr![0; 0x10000]; for pair in f_buf.windows(2) { let bigram = (pair[0] as usize) << 8 | pair[1] as usize; @@ -397,6 +411,7 @@ impl PngImage { if size < best_size { best_size = size; std::mem::swap(&mut best_line, &mut f_buf); + best_line_raw = line_data.clone(); } } } @@ -405,8 +420,8 @@ impl PngImage { let mut best_size = i32::MIN; // FxHasher is the fastest rust hasher currently available for this purpose let mut counts = FxHashMap::::default(); - for try_filter in try_filters { - try_filter.filter_line(bpp, line.data, last_line, &mut f_buf); + for f in try_filters { + f.filter_line(bpp, &mut line_data, &prev_line, &mut f_buf, alpha_bytes); counts.clear(); for pair in f_buf.windows(2) { let bigram = (pair[0] as u16) << 8 | pair[1] as u16; @@ -416,6 +431,7 @@ impl PngImage { if size > best_size { best_size = size; std::mem::swap(&mut best_line, &mut f_buf); + best_line_raw = line_data.clone(); } } } @@ -431,8 +447,8 @@ impl PngImage { let capacity = compressor.zlib_compress_bound(limit); let mut dest = vec![0; capacity]; - for try_filter in try_filters { - try_filter.filter_line(bpp, line.data, last_line, &mut f_buf); + for f in try_filters { + f.filter_line(bpp, &mut line_data, &prev_line, &mut f_buf, alpha_bytes); filtered[line_start..].copy_from_slice(&f_buf); let size = compressor .zlib_compress(&filtered[filtered.len() - limit..], &mut dest) @@ -440,6 +456,7 @@ impl PngImage { if size < best_size { best_size = size; std::mem::swap(&mut best_line, &mut f_buf); + best_line_raw = line_data.clone(); } } filtered.resize(line_start, 0); @@ -447,10 +464,10 @@ impl PngImage { _ => unreachable!(), } filtered.extend_from_slice(&best_line); + prev_line = best_line_raw; } - last_line = line.data; - last_pass = line.pass; + prev_pass = line.pass; } filtered } diff --git a/src/reduction/alpha.rs b/src/reduction/alpha.rs index b3c5c9a1..b5d17805 100644 --- a/src/reduction/alpha.rs +++ b/src/reduction/alpha.rs @@ -1,32 +1,7 @@ use crate::colors::AlphaOptim; use crate::colors::ColorType; -use crate::evaluate::Evaluator; use crate::headers::IhdrData; use crate::png::PngImage; -#[cfg(not(feature = "parallel"))] -use crate::rayon::prelude::*; -use indexmap::IndexSet; -#[cfg(feature = "parallel")] -use rayon::prelude::*; -use std::sync::Arc; - -pub(crate) fn try_alpha_reductions( - png: Arc, - alphas: &IndexSet, - eval: &Evaluator, -) -> bool { - match png.ihdr.color_type { - ColorType::RGBA | ColorType::GrayscaleAlpha if !alphas.is_empty() => { - alphas - .par_iter() - .with_max_len(1) - .filter_map(|&alpha| filtered_alpha_channel(&png, alpha)) - .for_each(|image| eval.try_image(Arc::new(image))); - true - } - _ => false, - } -} pub fn filtered_alpha_channel(png: &PngImage, optim: AlphaOptim) -> Option { let (bpc, bpp) = match png.ihdr.color_type { diff --git a/src/reduction/mod.rs b/src/reduction/mod.rs index b775f753..1ff56f91 100644 --- a/src/reduction/mod.rs +++ b/src/reduction/mod.rs @@ -12,7 +12,7 @@ use crate::bit_depth::reduce_bit_depth_8_or_less; pub mod color; use crate::color::*; -pub(crate) use crate::alpha::{filtered_alpha_channel, try_alpha_reductions}; +pub(crate) use crate::alpha::filtered_alpha_channel; pub(crate) use crate::bit_depth::reduce_bit_depth; /// Attempt to reduce the number of colors in the palette