diff --git a/src/lib.rs b/src/lib.rs index 12c4200a..5a8aaac1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -389,7 +389,7 @@ impl RawImage { /// Create an optimized png from the raw image data using the options provided pub fn create_optimized_png(&self, opts: &Options) -> PngResult> { let deadline = Arc::new(Deadline::new(opts.timeout)); - let mut png = optimize_raw(self.png.clone(), opts, deadline, None) + let mut png = optimize_raw(self.png.clone(), opts, deadline.clone(), None) .ok_or_else(|| PngError::new("Failed to optimize input data"))?; // Process aux chunks @@ -399,7 +399,7 @@ impl RawImage { .filter(|c| opts.strip.keep(&c.name)) .cloned() .collect(); - postprocess_chunks(&mut png, opts, &self.png.ihdr); + postprocess_chunks(&mut png, opts, deadline, &self.png.ihdr); Ok(png.output()) } @@ -583,12 +583,12 @@ fn optimize_png( } else { Some(png.estimated_output_size()) }; - if let Some(new_png) = optimize_raw(raw.clone(), &opts, deadline, max_size) { + if let Some(new_png) = optimize_raw(raw.clone(), &opts, deadline.clone(), max_size) { png.raw = new_png.raw; png.idat_data = new_png.idat_data; } - postprocess_chunks(png, &opts, &raw.ihdr); + postprocess_chunks(png, &opts, deadline, &raw.ihdr); let output = png.output(); @@ -860,7 +860,12 @@ fn report_format(prefix: &str, png: &PngImage) { } /// Perform cleanup of certain chunks from the `PngData` object, after optimization has been completed -fn postprocess_chunks(png: &mut PngData, opts: &Options, orig_ihdr: &IhdrData) { +fn postprocess_chunks( + png: &mut PngData, + opts: &Options, + deadline: Arc, + orig_ihdr: &IhdrData, +) { if let Some(iccp_idx) = png.aux_chunks.iter().position(|c| &c.name == b"iCCP") { // See if we can replace an iCCP chunk with an sRGB chunk let may_replace_iccp = opts.strip != StripChunks::None && opts.strip.keep(b"sRGB"); @@ -913,6 +918,38 @@ fn postprocess_chunks(png: &mut PngData, opts: &Options, orig_ihdr: &IhdrData) { !invalid }); } + + // Find fdAT chunks and attempt to recompress them + // Note if there are multiple fdATs per frame then decompression will fail and nothing will change + let mut fdat: Vec<_> = png + .aux_chunks + .iter_mut() + .filter(|c| &c.name == b"fdAT") + .collect(); + if !fdat.is_empty() { + let buffer_size = orig_ihdr.raw_data_size(); + fdat.par_iter_mut() + .with_max_len(1) + .enumerate() + .for_each(|(i, c)| { + if deadline.passed() || c.data.len() <= 4 { + return; + } + if let Ok(mut data) = deflate::inflate(&c.data[4..], buffer_size).and_then(|data| { + let max_size = AtomicMin::new(Some(c.data.len() - 5)); + opts.deflate.deflate(&data, &max_size) + }) { + debug!( + "Recompressed fdAT #{:<2}: {} ({} bytes decrease)", + i, + c.data.len(), + c.data.len() - 4 - data.len() + ); + c.data.truncate(4); + c.data.append(&mut data); + } + }) + } } /// Check if an image was already optimized prior to oxipng's operations