From ef0c23fe69688241df05f1125bea0659e691ea43 Mon Sep 17 00:00:00 2001 From: Andrew Date: Tue, 23 May 2023 17:19:57 +1200 Subject: [PATCH] Allow APNG with reductions disabled --- src/headers.rs | 4 +++- src/lib.rs | 18 ++++++++++++++++-- src/png/mod.rs | 28 +++++++++++++++++++++------- tests/lib.rs | 6 +++--- 4 files changed, 43 insertions(+), 13 deletions(-) diff --git a/src/headers.rs b/src/headers.rs index 274173d7..8a70a85e 100644 --- a/src/headers.rs +++ b/src/headers.rs @@ -86,7 +86,9 @@ pub enum StripChunks { impl StripChunks { /// List of chunks that will be kept when using the `Safe` option - pub const KEEP_SAFE: [[u8; 4]; 4] = [*b"cICP", *b"iCCP", *b"sRGB", *b"pHYs"]; + pub const KEEP_SAFE: [[u8; 4]; 7] = [ + *b"cICP", *b"iCCP", *b"sRGB", *b"pHYs", *b"acTL", *b"fcTL", *b"fdAT", + ]; pub(crate) fn keep(&self, name: &[u8; 4]) -> bool { match &self { diff --git a/src/lib.rs b/src/lib.rs index 46886f33..ddf71c1d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,6 +32,7 @@ use crate::png::PngImage; use crate::reduction::*; use log::{debug, info, trace, warn}; use rayon::prelude::*; +use std::borrow::Cow; use std::fmt; use std::fs::{copy, File, Metadata}; use std::io::{stdin, stdout, BufWriter, Read, Write}; @@ -559,17 +560,30 @@ fn optimize_png( debug!(" IDAT size = {} bytes", idat_original_size); debug!(" File size = {} bytes", file_original_size); + // Check for APNG by presence of acTL chunk + let opts = if png.aux_chunks.iter().any(|c| &c.name == b"acTL") { + warn!("APNG detected, disabling all reductions"); + let mut opts = opts.to_owned(); + opts.interlace = None; + opts.bit_depth_reduction = false; + opts.color_type_reduction = false; + opts.palette_reduction = false; + opts.grayscale_reduction = false; + Cow::Owned(opts) + } else { + Cow::Borrowed(opts) + }; let max_size = if opts.force { None } 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, max_size) { png.raw = new_png.raw; png.idat_data = new_png.idat_data; } - postprocess_chunks(png, opts, &raw.ihdr); + postprocess_chunks(png, &opts, &raw.ihdr); let output = png.output(); diff --git a/src/png/mod.rs b/src/png/mod.rs index 49f7a89d..c2008098 100644 --- a/src/png/mod.rs +++ b/src/png/mod.rs @@ -93,8 +93,16 @@ impl PngData { let mut aux_chunks: Vec = Vec::new(); while let Some(chunk) = parse_next_chunk(byte_data, &mut byte_offset, opts.fix_errors)? { match &chunk.name { - b"IDAT" => idat_data.extend_from_slice(chunk.data), - b"acTL" => return Err(PngError::APNGNotSupported), + b"IDAT" => { + if idat_data.is_empty() { + // Keep track of where the first IDAT sits relative to other chunks + aux_chunks.push(Chunk { + name: chunk.name, + data: Vec::new(), + }) + } + idat_data.extend_from_slice(chunk.data); + } b"IHDR" | b"PLTE" | b"tRNS" => { key_chunks.insert(chunk.name, chunk.data.to_owned()); } @@ -165,9 +173,10 @@ impl PngData { ihdr_data.write_all(&[0]).ok(); // Filter method -- 5-way adaptive filtering ihdr_data.write_all(&[self.raw.ihdr.interlaced as u8]).ok(); write_png_block(b"IHDR", &ihdr_data, &mut output); - // Ancillary chunks - for chunk in self - .aux_chunks + // Ancillary chunks - split into those that come before IDAT and those that come after + let mut aux_split = self.aux_chunks.split(|c| &c.name == b"IDAT"); + let aux_pre = aux_split.next().unwrap(); + for chunk in aux_pre .iter() .filter(|c| !(&c.name == b"bKGD" || &c.name == b"hIST" || &c.name == b"tRNS")) { @@ -202,8 +211,7 @@ impl PngData { _ => {} } // Special ancillary chunks that need to come after PLTE but before IDAT - for chunk in self - .aux_chunks + for chunk in aux_pre .iter() .filter(|c| &c.name == b"bKGD" || &c.name == b"hIST" || &c.name == b"tRNS") { @@ -211,6 +219,12 @@ impl PngData { } // IDAT data write_png_block(b"IDAT", &self.idat_data, &mut output); + // Ancillary chunks that come after IDAT + for aux_post in aux_split { + for chunk in aux_post { + write_png_block(&chunk.name, &chunk.data, &mut output); + } + } // Stream end write_png_block(b"IEND", &[], &mut output); diff --git a/tests/lib.rs b/tests/lib.rs index a32de8b1..3c27f8be 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -30,7 +30,7 @@ fn optimize_from_memory_apng() { in_file.read_to_end(&mut in_file_buf).unwrap(); let result = oxipng::optimize_from_memory(&in_file_buf, &Options::default()); - assert!(result.is_err()); + assert!(result.is_ok()); } #[test] @@ -58,9 +58,9 @@ fn optimize_apng() { let result = oxipng::optimize( &"tests/files/apng_file.png".into(), &OutFile::Path(None), - &Options::default(), + &Options::from_preset(0), ); - assert!(result.is_err()); + assert!(result.is_ok()); } #[test]