Allow APNG with reductions disabled
This commit is contained in:
parent
129f1e6f76
commit
f436ef62a3
4 changed files with 43 additions and 13 deletions
|
|
@ -86,7 +86,9 @@ pub enum StripChunks {
|
||||||
|
|
||||||
impl StripChunks {
|
impl StripChunks {
|
||||||
/// List of chunks that will be kept when using the `Safe` option
|
/// 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 {
|
pub(crate) fn keep(&self, name: &[u8; 4]) -> bool {
|
||||||
match &self {
|
match &self {
|
||||||
|
|
|
||||||
18
src/lib.rs
18
src/lib.rs
|
|
@ -32,6 +32,7 @@ use crate::png::PngImage;
|
||||||
use crate::reduction::*;
|
use crate::reduction::*;
|
||||||
use log::{debug, info, trace, warn};
|
use log::{debug, info, trace, warn};
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
|
use std::borrow::Cow;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::fs::{copy, File, Metadata};
|
use std::fs::{copy, File, Metadata};
|
||||||
use std::io::{stdin, stdout, BufWriter, Read, Write};
|
use std::io::{stdin, stdout, BufWriter, Read, Write};
|
||||||
|
|
@ -564,17 +565,30 @@ fn optimize_png(
|
||||||
debug!(" IDAT size = {} bytes", idat_original_size);
|
debug!(" IDAT size = {} bytes", idat_original_size);
|
||||||
debug!(" File size = {} bytes", file_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 {
|
let max_size = if opts.force {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(png.estimated_output_size())
|
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.raw = new_png.raw;
|
||||||
png.idat_data = new_png.idat_data;
|
png.idat_data = new_png.idat_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
postprocess_chunks(png, opts, &raw.ihdr);
|
postprocess_chunks(png, &opts, &raw.ihdr);
|
||||||
|
|
||||||
let output = png.output();
|
let output = png.output();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -93,8 +93,16 @@ impl PngData {
|
||||||
let mut aux_chunks: Vec<Chunk> = Vec::new();
|
let mut aux_chunks: Vec<Chunk> = Vec::new();
|
||||||
while let Some(chunk) = parse_next_chunk(byte_data, &mut byte_offset, opts.fix_errors)? {
|
while let Some(chunk) = parse_next_chunk(byte_data, &mut byte_offset, opts.fix_errors)? {
|
||||||
match &chunk.name {
|
match &chunk.name {
|
||||||
b"IDAT" => idat_data.extend_from_slice(chunk.data),
|
b"IDAT" => {
|
||||||
b"acTL" => return Err(PngError::APNGNotSupported),
|
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" => {
|
b"IHDR" | b"PLTE" | b"tRNS" => {
|
||||||
key_chunks.insert(chunk.name, chunk.data.to_owned());
|
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(&[0]).ok(); // Filter method -- 5-way adaptive filtering
|
||||||
ihdr_data.write_all(&[self.raw.ihdr.interlaced as u8]).ok();
|
ihdr_data.write_all(&[self.raw.ihdr.interlaced as u8]).ok();
|
||||||
write_png_block(b"IHDR", &ihdr_data, &mut output);
|
write_png_block(b"IHDR", &ihdr_data, &mut output);
|
||||||
// Ancillary chunks
|
// Ancillary chunks - split into those that come before IDAT and those that come after
|
||||||
for chunk in self
|
let mut aux_split = self.aux_chunks.split(|c| &c.name == b"IDAT");
|
||||||
.aux_chunks
|
let aux_pre = aux_split.next().unwrap();
|
||||||
|
for chunk in aux_pre
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|c| !(&c.name == b"bKGD" || &c.name == b"hIST" || &c.name == b"tRNS"))
|
.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
|
// Special ancillary chunks that need to come after PLTE but before IDAT
|
||||||
for chunk in self
|
for chunk in aux_pre
|
||||||
.aux_chunks
|
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|c| &c.name == b"bKGD" || &c.name == b"hIST" || &c.name == b"tRNS")
|
.filter(|c| &c.name == b"bKGD" || &c.name == b"hIST" || &c.name == b"tRNS")
|
||||||
{
|
{
|
||||||
|
|
@ -211,6 +219,12 @@ impl PngData {
|
||||||
}
|
}
|
||||||
// IDAT data
|
// IDAT data
|
||||||
write_png_block(b"IDAT", &self.idat_data, &mut output);
|
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
|
// Stream end
|
||||||
write_png_block(b"IEND", &[], &mut output);
|
write_png_block(b"IEND", &[], &mut output);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ fn optimize_from_memory_apng() {
|
||||||
in_file.read_to_end(&mut in_file_buf).unwrap();
|
in_file.read_to_end(&mut in_file_buf).unwrap();
|
||||||
|
|
||||||
let result = oxipng::optimize_from_memory(&in_file_buf, &Options::default());
|
let result = oxipng::optimize_from_memory(&in_file_buf, &Options::default());
|
||||||
assert!(result.is_err());
|
assert!(result.is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -58,9 +58,9 @@ fn optimize_apng() {
|
||||||
let result = oxipng::optimize(
|
let result = oxipng::optimize(
|
||||||
&"tests/files/apng_file.png".into(),
|
&"tests/files/apng_file.png".into(),
|
||||||
&OutFile::Path(None),
|
&OutFile::Path(None),
|
||||||
&Options::default(),
|
&Options::from_preset(0),
|
||||||
);
|
);
|
||||||
assert!(result.is_err());
|
assert!(result.is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue