Remove filtered from PngData

This commit is contained in:
Andrew 2023-05-20 16:09:13 +12:00
parent 4a1cd2db3e
commit 526429a522
3 changed files with 45 additions and 46 deletions

View file

@ -4,7 +4,6 @@
use crate::atomicmin::AtomicMin; use crate::atomicmin::AtomicMin;
use crate::deflate; use crate::deflate;
use crate::filters::RowFilter; use crate::filters::RowFilter;
use crate::png::PngData;
use crate::png::PngImage; use crate::png::PngImage;
#[cfg(not(feature = "parallel"))] #[cfg(not(feature = "parallel"))]
use crate::rayon; use crate::rayon;
@ -22,7 +21,9 @@ use std::sync::atomic::Ordering::SeqCst;
use std::sync::Arc; use std::sync::Arc;
pub struct Candidate { pub struct Candidate {
pub image: PngData, pub image: Arc<PngImage>,
pub idat_data: Vec<u8>,
pub filtered: Vec<u8>,
pub filter: RowFilter, pub filter: RowFilter,
pub is_reduction: bool, pub is_reduction: bool,
// first wins tie-breaker // first wins tie-breaker
@ -32,9 +33,9 @@ pub struct Candidate {
impl Candidate { impl Candidate {
fn cmp_key(&self) -> impl Ord { fn cmp_key(&self) -> impl Ord {
( (
self.image.estimated_output_size(), self.idat_data.len() + self.image.key_chunks_size(),
self.image.raw.data.len(), self.image.data.len(),
self.image.raw.ihdr.bit_depth, self.image.ihdr.bit_depth,
self.filter, self.filter,
self.nth, self.nth,
) )
@ -135,18 +136,7 @@ impl Evaluator {
let filtered = image.filter_image(filter, optimize_alpha); let filtered = image.filter_image(filter, optimize_alpha);
let idat_data = deflate::deflate(&filtered, compression, &best_candidate_size); let idat_data = deflate::deflate(&filtered, compression, &best_candidate_size);
if let Ok(idat_data) = idat_data { if let Ok(idat_data) = idat_data {
let new = Candidate { let size = idat_data.len() + image.key_chunks_size();
image: PngData {
idat_data,
filtered,
raw: Arc::clone(&image),
aux_chunks: Vec::new(),
},
filter,
is_reduction,
nth,
};
let size = new.image.estimated_output_size();
best_candidate_size.set_min(size); best_candidate_size.set_min(size);
trace!( trace!(
"Eval: {}-bit {:20} {:8} {} bytes", "Eval: {}-bit {:20} {:8} {} bytes",
@ -155,6 +145,14 @@ impl Evaluator {
filter, filter,
size size
); );
let new = Candidate {
image: image.clone(),
idat_data,
filtered,
filter,
is_reduction,
nth,
};
#[cfg(feature = "parallel")] #[cfg(feature = "parallel")]
{ {

View file

@ -632,7 +632,7 @@ fn optimize_raw(
let mut eval_result = eval.get_best_candidate(); let mut eval_result = eval.get_best_candidate();
if let Some(ref result) = eval_result { if let Some(ref result) = eval_result {
if result.is_reduction { if result.is_reduction {
png = Arc::clone(&result.image.raw); png = result.image.clone();
reduction_occurred = true; reduction_occurred = true;
} }
} }
@ -656,7 +656,7 @@ fn optimize_raw(
trace!("Evaluating: {} filters", filters.len()); trace!("Evaluating: {} filters", filters.len());
let eval = Evaluator::new(deadline, filters, eval_compression, opts.optimize_alpha); let eval = Evaluator::new(deadline, filters, eval_compression, opts.optimize_alpha);
if let Some(ref result) = eval_result { if let Some(ref result) = eval_result {
eval.set_best_size(result.image.idat_data.len()); eval.set_best_size(result.idat_data.len());
} }
eval.try_image(png.clone()); eval.try_image(png.clone());
if let Some(result) = eval.get_best_candidate() { if let Some(result) = eval.get_best_candidate() {
@ -669,12 +669,12 @@ fn optimize_raw(
match opts.deflate { match opts.deflate {
Deflaters::Libdeflater { compression } if compression <= eval_compression => { Deflaters::Libdeflater { compression } if compression <= eval_compression => {
// No further compression required // No further compression required
Some((result.filter, result.image.idat_data)) Some((result.filter, result.idat_data))
} }
_ => { _ => {
debug!("Trying: {}", result.filter); debug!("Trying: {}", result.filter);
let best_size = AtomicMin::new(max_size); let best_size = AtomicMin::new(max_size);
perform_trial(&result.image.filtered, opts, result.filter, &best_size) perform_trial(&result.filtered, opts, result.filter, &best_size)
} }
} }
} else { } else {
@ -714,8 +714,6 @@ fn optimize_raw(
if let Some((filter, idat_data)) = best { if let Some((filter, idat_data)) = best {
let image = PngData { let image = PngData {
raw: png, raw: png,
// The filtered data has not been retained here, but we don't need to return it
filtered: Vec::new(),
idat_data, idat_data,
aux_chunks: Vec::new(), aux_chunks: Vec::new(),
}; };
@ -733,7 +731,11 @@ fn optimize_raw(
} else if let Some(result) = eval_result { } else if let Some(result) = eval_result {
// If idat_recoding is off and reductions were attempted but ended up choosing the baseline, // If idat_recoding is off and reductions were attempted but ended up choosing the baseline,
// we should still check if the evaluator compressed the baseline smaller than the original. // we should still check if the evaluator compressed the baseline smaller than the original.
let image = result.image; let image = PngData {
raw: result.image,
idat_data: result.idat_data,
aux_chunks: Vec::new(),
};
if image.estimated_output_size() < max_size.unwrap_or(usize::MAX) { if image.estimated_output_size() < max_size.unwrap_or(usize::MAX) {
debug!("Found better combination:"); debug!("Found better combination:");
debug!( debug!(

View file

@ -39,8 +39,6 @@ pub struct PngData {
pub raw: Arc<PngImage>, pub raw: Arc<PngImage>,
/// The filtered and compressed data of the IDAT chunk /// The filtered and compressed data of the IDAT chunk
pub idat_data: Vec<u8>, pub idat_data: Vec<u8>,
/// The filtered, uncompressed data of the IDAT chunk
pub filtered: Vec<u8>,
/// All non-critical chunks from the PNG are stored here /// All non-critical chunks from the PNG are stored here
pub aux_chunks: Vec<Chunk>, pub aux_chunks: Vec<Chunk>,
} }
@ -135,35 +133,18 @@ impl PngData {
ihdr, ihdr,
data: raw_data, data: raw_data,
}; };
let unfiltered = raw.unfilter_image()?; raw.data = raw.unfilter_image()?;
// Return the PngData // Return the PngData
Ok(Self { Ok(Self {
idat_data, idat_data,
filtered: std::mem::replace(&mut raw.data, unfiltered),
raw: Arc::new(raw), raw: Arc::new(raw),
aux_chunks, aux_chunks,
}) })
} }
/// Return an estimate of the output size /// Return an estimate of the output size which can help with evaluation of very small data
pub fn estimated_output_size(&self) -> usize { pub fn estimated_output_size(&self) -> usize {
// Add the size of the PLTE and tRNS chunks to the compressed idat size self.idat_data.len() + self.raw.key_chunks_size()
// This can help with evaluation of very small data
let size = self.idat_data.len();
size + match &self.raw.ihdr.color_type {
ColorType::Indexed { palette } => {
let plte = 12 + palette.len() * 3;
let trns = palette.iter().filter(|p| p.a != 255).count();
if trns != 0 {
plte + 12 + trns
} else {
plte
}
}
ColorType::Grayscale { transparent_shade } if transparent_shade.is_some() => 12 + 2,
ColorType::RGB { transparent_color } if transparent_color.is_some() => 12 + 6,
_ => 0,
}
} }
/// Format the `PngData` struct into a valid PNG bytestream /// Format the `PngData` struct into a valid PNG bytestream
@ -274,6 +255,24 @@ impl PngImage {
} }
} }
/// Calculate the size of the PLTE and tRNS chunks
pub fn key_chunks_size(&self) -> usize {
match &self.ihdr.color_type {
ColorType::Indexed { palette } => {
let plte = 12 + palette.len() * 3;
let trns = palette.iter().filter(|p| p.a != 255).count();
if trns != 0 {
plte + 12 + trns
} else {
plte
}
}
ColorType::Grayscale { transparent_shade } if transparent_shade.is_some() => 12 + 2,
ColorType::RGB { transparent_color } if transparent_color.is_some() => 12 + 6,
_ => 0,
}
}
/// Return an iterator over the scanlines of the image /// Return an iterator over the scanlines of the image
#[inline] #[inline]
pub fn scan_lines(&self, has_filter: bool) -> ScanLines<'_> { pub fn scan_lines(&self, has_filter: bool) -> ScanLines<'_> {