Allow APNG with reductions disabled

This commit is contained in:
Andrew 2023-05-23 17:19:57 +12:00 committed by Josh Holmer
parent 129f1e6f76
commit f436ef62a3
4 changed files with 43 additions and 13 deletions

View file

@ -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 {

View file

@ -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};
@ -564,17 +565,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();

View file

@ -93,8 +93,16 @@ impl PngData {
let mut aux_chunks: Vec<Chunk> = 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);

View file

@ -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]