Refactor aux chunk handling (#505)

This commit is contained in:
andrews05 2023-05-22 07:34:23 +12:00 committed by GitHub
parent a5832706bd
commit ea5f1884be
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 621 additions and 701 deletions

View file

@ -3,15 +3,15 @@
extern crate oxipng; extern crate oxipng;
extern crate test; extern crate test;
use oxipng::internal_tests::*;
use oxipng::*;
use std::path::PathBuf; use std::path::PathBuf;
use test::Bencher; use test::Bencher;
use oxipng::internal_tests::*;
#[bench] #[bench]
fn deflate_16_bits(b: &mut Bencher) { fn deflate_16_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png")); let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png"));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| { b.iter(|| {
let min = AtomicMin::new(None); let min = AtomicMin::new(None);
@ -22,7 +22,7 @@ fn deflate_16_bits(b: &mut Bencher) {
#[bench] #[bench]
fn deflate_8_bits(b: &mut Bencher) { fn deflate_8_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png")); let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| { b.iter(|| {
let min = AtomicMin::new(None); let min = AtomicMin::new(None);
@ -35,7 +35,7 @@ fn deflate_4_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from( let input = test::black_box(PathBuf::from(
"tests/files/palette_4_should_be_palette_4.png", "tests/files/palette_4_should_be_palette_4.png",
)); ));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| { b.iter(|| {
let min = AtomicMin::new(None); let min = AtomicMin::new(None);
@ -48,7 +48,7 @@ fn deflate_2_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from( let input = test::black_box(PathBuf::from(
"tests/files/palette_2_should_be_palette_2.png", "tests/files/palette_2_should_be_palette_2.png",
)); ));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| { b.iter(|| {
let min = AtomicMin::new(None); let min = AtomicMin::new(None);
@ -61,7 +61,7 @@ fn deflate_1_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from( let input = test::black_box(PathBuf::from(
"tests/files/palette_1_should_be_palette_1.png", "tests/files/palette_1_should_be_palette_1.png",
)); ));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| { b.iter(|| {
let min = AtomicMin::new(None); let min = AtomicMin::new(None);
@ -72,7 +72,7 @@ fn deflate_1_bits(b: &mut Bencher) {
#[bench] #[bench]
fn inflate_generic(b: &mut Bencher) { fn inflate_generic(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png")); let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png"));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| inflate(png.idat_data.as_ref(), png.raw.ihdr.raw_data_size())); b.iter(|| inflate(png.idat_data.as_ref(), png.raw.ihdr.raw_data_size()));
} }

View file

@ -3,14 +3,15 @@
extern crate oxipng; extern crate oxipng;
extern crate test; extern crate test;
use oxipng::{internal_tests::*, RowFilter}; use oxipng::internal_tests::*;
use oxipng::*;
use std::path::PathBuf; use std::path::PathBuf;
use test::Bencher; use test::Bencher;
#[bench] #[bench]
fn filters_16_bits_filter_0(b: &mut Bencher) { fn filters_16_bits_filter_0(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png")); let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png"));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| { b.iter(|| {
png.raw.filter_image(RowFilter::None, false); png.raw.filter_image(RowFilter::None, false);
@ -20,7 +21,7 @@ fn filters_16_bits_filter_0(b: &mut Bencher) {
#[bench] #[bench]
fn filters_8_bits_filter_0(b: &mut Bencher) { fn filters_8_bits_filter_0(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png")); let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| { b.iter(|| {
png.raw.filter_image(RowFilter::None, false); png.raw.filter_image(RowFilter::None, false);
@ -32,7 +33,7 @@ fn filters_4_bits_filter_0(b: &mut Bencher) {
let input = test::black_box(PathBuf::from( let input = test::black_box(PathBuf::from(
"tests/files/palette_4_should_be_palette_4.png", "tests/files/palette_4_should_be_palette_4.png",
)); ));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| { b.iter(|| {
png.raw.filter_image(RowFilter::None, false); png.raw.filter_image(RowFilter::None, false);
@ -44,7 +45,7 @@ fn filters_2_bits_filter_0(b: &mut Bencher) {
let input = test::black_box(PathBuf::from( let input = test::black_box(PathBuf::from(
"tests/files/palette_2_should_be_palette_2.png", "tests/files/palette_2_should_be_palette_2.png",
)); ));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| { b.iter(|| {
png.raw.filter_image(RowFilter::None, false); png.raw.filter_image(RowFilter::None, false);
@ -56,7 +57,7 @@ fn filters_1_bits_filter_0(b: &mut Bencher) {
let input = test::black_box(PathBuf::from( let input = test::black_box(PathBuf::from(
"tests/files/palette_1_should_be_palette_1.png", "tests/files/palette_1_should_be_palette_1.png",
)); ));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| { b.iter(|| {
png.raw.filter_image(RowFilter::None, false); png.raw.filter_image(RowFilter::None, false);
@ -66,7 +67,7 @@ fn filters_1_bits_filter_0(b: &mut Bencher) {
#[bench] #[bench]
fn filters_16_bits_filter_1(b: &mut Bencher) { fn filters_16_bits_filter_1(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png")); let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png"));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| { b.iter(|| {
png.raw.filter_image(RowFilter::Sub, false); png.raw.filter_image(RowFilter::Sub, false);
@ -76,7 +77,7 @@ fn filters_16_bits_filter_1(b: &mut Bencher) {
#[bench] #[bench]
fn filters_8_bits_filter_1(b: &mut Bencher) { fn filters_8_bits_filter_1(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png")); let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| { b.iter(|| {
png.raw.filter_image(RowFilter::Sub, false); png.raw.filter_image(RowFilter::Sub, false);
@ -88,7 +89,7 @@ fn filters_4_bits_filter_1(b: &mut Bencher) {
let input = test::black_box(PathBuf::from( let input = test::black_box(PathBuf::from(
"tests/files/palette_4_should_be_palette_4.png", "tests/files/palette_4_should_be_palette_4.png",
)); ));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| { b.iter(|| {
png.raw.filter_image(RowFilter::Sub, false); png.raw.filter_image(RowFilter::Sub, false);
@ -100,7 +101,7 @@ fn filters_2_bits_filter_1(b: &mut Bencher) {
let input = test::black_box(PathBuf::from( let input = test::black_box(PathBuf::from(
"tests/files/palette_2_should_be_palette_2.png", "tests/files/palette_2_should_be_palette_2.png",
)); ));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| { b.iter(|| {
png.raw.filter_image(RowFilter::Sub, false); png.raw.filter_image(RowFilter::Sub, false);
@ -112,7 +113,7 @@ fn filters_1_bits_filter_1(b: &mut Bencher) {
let input = test::black_box(PathBuf::from( let input = test::black_box(PathBuf::from(
"tests/files/palette_1_should_be_palette_1.png", "tests/files/palette_1_should_be_palette_1.png",
)); ));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| { b.iter(|| {
png.raw.filter_image(RowFilter::Sub, false); png.raw.filter_image(RowFilter::Sub, false);
@ -122,7 +123,7 @@ fn filters_1_bits_filter_1(b: &mut Bencher) {
#[bench] #[bench]
fn filters_16_bits_filter_2(b: &mut Bencher) { fn filters_16_bits_filter_2(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png")); let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png"));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| { b.iter(|| {
png.raw.filter_image(RowFilter::Up, false); png.raw.filter_image(RowFilter::Up, false);
@ -132,7 +133,7 @@ fn filters_16_bits_filter_2(b: &mut Bencher) {
#[bench] #[bench]
fn filters_8_bits_filter_2(b: &mut Bencher) { fn filters_8_bits_filter_2(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png")); let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| { b.iter(|| {
png.raw.filter_image(RowFilter::Up, false); png.raw.filter_image(RowFilter::Up, false);
@ -144,7 +145,7 @@ fn filters_4_bits_filter_2(b: &mut Bencher) {
let input = test::black_box(PathBuf::from( let input = test::black_box(PathBuf::from(
"tests/files/palette_4_should_be_palette_4.png", "tests/files/palette_4_should_be_palette_4.png",
)); ));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| { b.iter(|| {
png.raw.filter_image(RowFilter::Up, false); png.raw.filter_image(RowFilter::Up, false);
@ -156,7 +157,7 @@ fn filters_2_bits_filter_2(b: &mut Bencher) {
let input = test::black_box(PathBuf::from( let input = test::black_box(PathBuf::from(
"tests/files/palette_2_should_be_palette_2.png", "tests/files/palette_2_should_be_palette_2.png",
)); ));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| { b.iter(|| {
png.raw.filter_image(RowFilter::Up, false); png.raw.filter_image(RowFilter::Up, false);
@ -168,7 +169,7 @@ fn filters_1_bits_filter_2(b: &mut Bencher) {
let input = test::black_box(PathBuf::from( let input = test::black_box(PathBuf::from(
"tests/files/palette_1_should_be_palette_1.png", "tests/files/palette_1_should_be_palette_1.png",
)); ));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| { b.iter(|| {
png.raw.filter_image(RowFilter::Up, false); png.raw.filter_image(RowFilter::Up, false);
@ -178,7 +179,7 @@ fn filters_1_bits_filter_2(b: &mut Bencher) {
#[bench] #[bench]
fn filters_16_bits_filter_3(b: &mut Bencher) { fn filters_16_bits_filter_3(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png")); let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png"));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| { b.iter(|| {
png.raw.filter_image(RowFilter::Average, false); png.raw.filter_image(RowFilter::Average, false);
@ -188,7 +189,7 @@ fn filters_16_bits_filter_3(b: &mut Bencher) {
#[bench] #[bench]
fn filters_8_bits_filter_3(b: &mut Bencher) { fn filters_8_bits_filter_3(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png")); let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| { b.iter(|| {
png.raw.filter_image(RowFilter::Average, false); png.raw.filter_image(RowFilter::Average, false);
@ -200,7 +201,7 @@ fn filters_4_bits_filter_3(b: &mut Bencher) {
let input = test::black_box(PathBuf::from( let input = test::black_box(PathBuf::from(
"tests/files/palette_4_should_be_palette_4.png", "tests/files/palette_4_should_be_palette_4.png",
)); ));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| { b.iter(|| {
png.raw.filter_image(RowFilter::Average, false); png.raw.filter_image(RowFilter::Average, false);
@ -212,7 +213,7 @@ fn filters_2_bits_filter_3(b: &mut Bencher) {
let input = test::black_box(PathBuf::from( let input = test::black_box(PathBuf::from(
"tests/files/palette_2_should_be_palette_2.png", "tests/files/palette_2_should_be_palette_2.png",
)); ));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| { b.iter(|| {
png.raw.filter_image(RowFilter::Average, false); png.raw.filter_image(RowFilter::Average, false);
@ -224,7 +225,7 @@ fn filters_1_bits_filter_3(b: &mut Bencher) {
let input = test::black_box(PathBuf::from( let input = test::black_box(PathBuf::from(
"tests/files/palette_1_should_be_palette_1.png", "tests/files/palette_1_should_be_palette_1.png",
)); ));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| { b.iter(|| {
png.raw.filter_image(RowFilter::Average, false); png.raw.filter_image(RowFilter::Average, false);
@ -234,7 +235,7 @@ fn filters_1_bits_filter_3(b: &mut Bencher) {
#[bench] #[bench]
fn filters_16_bits_filter_4(b: &mut Bencher) { fn filters_16_bits_filter_4(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png")); let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png"));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| { b.iter(|| {
png.raw.filter_image(RowFilter::Paeth, false); png.raw.filter_image(RowFilter::Paeth, false);
@ -244,7 +245,7 @@ fn filters_16_bits_filter_4(b: &mut Bencher) {
#[bench] #[bench]
fn filters_8_bits_filter_4(b: &mut Bencher) { fn filters_8_bits_filter_4(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png")); let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| { b.iter(|| {
png.raw.filter_image(RowFilter::Paeth, false); png.raw.filter_image(RowFilter::Paeth, false);
@ -256,7 +257,7 @@ fn filters_4_bits_filter_4(b: &mut Bencher) {
let input = test::black_box(PathBuf::from( let input = test::black_box(PathBuf::from(
"tests/files/palette_4_should_be_palette_4.png", "tests/files/palette_4_should_be_palette_4.png",
)); ));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| { b.iter(|| {
png.raw.filter_image(RowFilter::Paeth, false); png.raw.filter_image(RowFilter::Paeth, false);
@ -268,7 +269,7 @@ fn filters_2_bits_filter_4(b: &mut Bencher) {
let input = test::black_box(PathBuf::from( let input = test::black_box(PathBuf::from(
"tests/files/palette_2_should_be_palette_2.png", "tests/files/palette_2_should_be_palette_2.png",
)); ));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| { b.iter(|| {
png.raw.filter_image(RowFilter::Paeth, false); png.raw.filter_image(RowFilter::Paeth, false);
@ -280,7 +281,7 @@ fn filters_1_bits_filter_4(b: &mut Bencher) {
let input = test::black_box(PathBuf::from( let input = test::black_box(PathBuf::from(
"tests/files/palette_1_should_be_palette_1.png", "tests/files/palette_1_should_be_palette_1.png",
)); ));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| { b.iter(|| {
png.raw.filter_image(RowFilter::Paeth, false); png.raw.filter_image(RowFilter::Paeth, false);
@ -290,7 +291,7 @@ fn filters_1_bits_filter_4(b: &mut Bencher) {
#[bench] #[bench]
fn filters_16_bits_filter_5(b: &mut Bencher) { fn filters_16_bits_filter_5(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png")); let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png"));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| { b.iter(|| {
png.raw.filter_image(RowFilter::MinSum, false); png.raw.filter_image(RowFilter::MinSum, false);
@ -300,7 +301,7 @@ fn filters_16_bits_filter_5(b: &mut Bencher) {
#[bench] #[bench]
fn filters_8_bits_filter_5(b: &mut Bencher) { fn filters_8_bits_filter_5(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png")); let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| { b.iter(|| {
png.raw.filter_image(RowFilter::MinSum, false); png.raw.filter_image(RowFilter::MinSum, false);
@ -312,7 +313,7 @@ fn filters_4_bits_filter_5(b: &mut Bencher) {
let input = test::black_box(PathBuf::from( let input = test::black_box(PathBuf::from(
"tests/files/palette_4_should_be_palette_4.png", "tests/files/palette_4_should_be_palette_4.png",
)); ));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| { b.iter(|| {
png.raw.filter_image(RowFilter::MinSum, false); png.raw.filter_image(RowFilter::MinSum, false);
@ -324,7 +325,7 @@ fn filters_2_bits_filter_5(b: &mut Bencher) {
let input = test::black_box(PathBuf::from( let input = test::black_box(PathBuf::from(
"tests/files/palette_2_should_be_palette_2.png", "tests/files/palette_2_should_be_palette_2.png",
)); ));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| { b.iter(|| {
png.raw.filter_image(RowFilter::MinSum, false); png.raw.filter_image(RowFilter::MinSum, false);
@ -336,7 +337,7 @@ fn filters_1_bits_filter_5(b: &mut Bencher) {
let input = test::black_box(PathBuf::from( let input = test::black_box(PathBuf::from(
"tests/files/palette_1_should_be_palette_1.png", "tests/files/palette_1_should_be_palette_1.png",
)); ));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| { b.iter(|| {
png.raw.filter_image(RowFilter::MinSum, false); png.raw.filter_image(RowFilter::MinSum, false);

View file

@ -3,14 +3,15 @@
extern crate oxipng; extern crate oxipng;
extern crate test; extern crate test;
use oxipng::{internal_tests::*, Interlacing}; use oxipng::internal_tests::*;
use oxipng::*;
use std::path::PathBuf; use std::path::PathBuf;
use test::Bencher; use test::Bencher;
#[bench] #[bench]
fn interlacing_16_bits(b: &mut Bencher) { fn interlacing_16_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png")); let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png"));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| png.raw.change_interlacing(Interlacing::Adam7)); b.iter(|| png.raw.change_interlacing(Interlacing::Adam7));
} }
@ -18,7 +19,7 @@ fn interlacing_16_bits(b: &mut Bencher) {
#[bench] #[bench]
fn interlacing_8_bits(b: &mut Bencher) { fn interlacing_8_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png")); let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| png.raw.change_interlacing(Interlacing::Adam7)); b.iter(|| png.raw.change_interlacing(Interlacing::Adam7));
} }
@ -28,7 +29,7 @@ fn interlacing_4_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from( let input = test::black_box(PathBuf::from(
"tests/files/palette_4_should_be_palette_4.png", "tests/files/palette_4_should_be_palette_4.png",
)); ));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| png.raw.change_interlacing(Interlacing::Adam7)); b.iter(|| png.raw.change_interlacing(Interlacing::Adam7));
} }
@ -38,7 +39,7 @@ fn interlacing_2_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from( let input = test::black_box(PathBuf::from(
"tests/files/palette_2_should_be_palette_2.png", "tests/files/palette_2_should_be_palette_2.png",
)); ));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| png.raw.change_interlacing(Interlacing::Adam7)); b.iter(|| png.raw.change_interlacing(Interlacing::Adam7));
} }
@ -48,7 +49,7 @@ fn interlacing_1_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from( let input = test::black_box(PathBuf::from(
"tests/files/palette_1_should_be_palette_1.png", "tests/files/palette_1_should_be_palette_1.png",
)); ));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| png.raw.change_interlacing(Interlacing::Adam7)); b.iter(|| png.raw.change_interlacing(Interlacing::Adam7));
} }
@ -58,7 +59,7 @@ fn deinterlacing_16_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from( let input = test::black_box(PathBuf::from(
"tests/files/interlaced_rgb_16_should_be_rgb_16.png", "tests/files/interlaced_rgb_16_should_be_rgb_16.png",
)); ));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| png.raw.change_interlacing(Interlacing::None)); b.iter(|| png.raw.change_interlacing(Interlacing::None));
} }
@ -68,7 +69,7 @@ fn deinterlacing_8_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from( let input = test::black_box(PathBuf::from(
"tests/files/interlaced_rgb_8_should_be_rgb_8.png", "tests/files/interlaced_rgb_8_should_be_rgb_8.png",
)); ));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| png.raw.change_interlacing(Interlacing::None)); b.iter(|| png.raw.change_interlacing(Interlacing::None));
} }
@ -78,7 +79,7 @@ fn deinterlacing_4_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from( let input = test::black_box(PathBuf::from(
"tests/files/interlaced_palette_4_should_be_palette_4.png", "tests/files/interlaced_palette_4_should_be_palette_4.png",
)); ));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| png.raw.change_interlacing(Interlacing::None)); b.iter(|| png.raw.change_interlacing(Interlacing::None));
} }
@ -88,7 +89,7 @@ fn deinterlacing_2_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from( let input = test::black_box(PathBuf::from(
"tests/files/interlaced_palette_2_should_be_palette_2.png", "tests/files/interlaced_palette_2_should_be_palette_2.png",
)); ));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| png.raw.change_interlacing(Interlacing::None)); b.iter(|| png.raw.change_interlacing(Interlacing::None));
} }
@ -98,7 +99,7 @@ fn deinterlacing_1_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from( let input = test::black_box(PathBuf::from(
"tests/files/interlaced_palette_1_should_be_palette_1.png", "tests/files/interlaced_palette_1_should_be_palette_1.png",
)); ));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| png.raw.change_interlacing(Interlacing::None)); b.iter(|| png.raw.change_interlacing(Interlacing::None));
} }

View file

@ -4,13 +4,14 @@ extern crate oxipng;
extern crate test; extern crate test;
use oxipng::internal_tests::*; use oxipng::internal_tests::*;
use oxipng::*;
use std::path::PathBuf; use std::path::PathBuf;
use test::Bencher; use test::Bencher;
#[bench] #[bench]
fn reductions_16_to_8_bits(b: &mut Bencher) { fn reductions_16_to_8_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_8.png")); let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_8.png"));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| bit_depth::reduced_bit_depth_16_to_8(&png.raw)); b.iter(|| bit_depth::reduced_bit_depth_16_to_8(&png.raw));
} }
@ -20,7 +21,7 @@ fn reductions_8_to_4_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from( let input = test::black_box(PathBuf::from(
"tests/files/palette_8_should_be_palette_4.png", "tests/files/palette_8_should_be_palette_4.png",
)); ));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| bit_depth::reduced_bit_depth_8_or_less(&png.raw, 1)); b.iter(|| bit_depth::reduced_bit_depth_8_or_less(&png.raw, 1));
} }
@ -30,7 +31,7 @@ fn reductions_8_to_2_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from( let input = test::black_box(PathBuf::from(
"tests/files/palette_8_should_be_palette_2.png", "tests/files/palette_8_should_be_palette_2.png",
)); ));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| bit_depth::reduced_bit_depth_8_or_less(&png.raw, 1)); b.iter(|| bit_depth::reduced_bit_depth_8_or_less(&png.raw, 1));
} }
@ -40,7 +41,7 @@ fn reductions_8_to_1_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from( let input = test::black_box(PathBuf::from(
"tests/files/palette_8_should_be_palette_1.png", "tests/files/palette_8_should_be_palette_1.png",
)); ));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| bit_depth::reduced_bit_depth_8_or_less(&png.raw, 1)); b.iter(|| bit_depth::reduced_bit_depth_8_or_less(&png.raw, 1));
} }
@ -50,7 +51,7 @@ fn reductions_4_to_2_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from( let input = test::black_box(PathBuf::from(
"tests/files/palette_4_should_be_palette_2.png", "tests/files/palette_4_should_be_palette_2.png",
)); ));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| bit_depth::reduced_bit_depth_8_or_less(&png.raw, 1)); b.iter(|| bit_depth::reduced_bit_depth_8_or_less(&png.raw, 1));
} }
@ -60,7 +61,7 @@ fn reductions_4_to_1_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from( let input = test::black_box(PathBuf::from(
"tests/files/palette_4_should_be_palette_1.png", "tests/files/palette_4_should_be_palette_1.png",
)); ));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| bit_depth::reduced_bit_depth_8_or_less(&png.raw, 1)); b.iter(|| bit_depth::reduced_bit_depth_8_or_less(&png.raw, 1));
} }
@ -70,7 +71,7 @@ fn reductions_2_to_1_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from( let input = test::black_box(PathBuf::from(
"tests/files/palette_2_should_be_palette_1.png", "tests/files/palette_2_should_be_palette_1.png",
)); ));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| bit_depth::reduced_bit_depth_8_or_less(&png.raw, 1)); b.iter(|| bit_depth::reduced_bit_depth_8_or_less(&png.raw, 1));
} }
@ -80,7 +81,7 @@ fn reductions_grayscale_8_to_4_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from( let input = test::black_box(PathBuf::from(
"tests/files/grayscale_8_should_be_grayscale_4.png", "tests/files/grayscale_8_should_be_grayscale_4.png",
)); ));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| bit_depth::reduced_bit_depth_8_or_less(&png.raw, 1)); b.iter(|| bit_depth::reduced_bit_depth_8_or_less(&png.raw, 1));
} }
@ -90,7 +91,7 @@ fn reductions_grayscale_8_to_2_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from( let input = test::black_box(PathBuf::from(
"tests/files/grayscale_8_should_be_grayscale_2.png", "tests/files/grayscale_8_should_be_grayscale_2.png",
)); ));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| bit_depth::reduced_bit_depth_8_or_less(&png.raw, 1)); b.iter(|| bit_depth::reduced_bit_depth_8_or_less(&png.raw, 1));
} }
@ -100,7 +101,7 @@ fn reductions_grayscale_8_to_1_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from( let input = test::black_box(PathBuf::from(
"tests/files/grayscale_8_should_be_grayscale_1.png", "tests/files/grayscale_8_should_be_grayscale_1.png",
)); ));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| bit_depth::reduced_bit_depth_8_or_less(&png.raw, 1)); b.iter(|| bit_depth::reduced_bit_depth_8_or_less(&png.raw, 1));
} }
@ -110,7 +111,7 @@ fn reductions_grayscale_4_to_2_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from( let input = test::black_box(PathBuf::from(
"tests/files/grayscale_4_should_be_grayscale_2.png", "tests/files/grayscale_4_should_be_grayscale_2.png",
)); ));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| bit_depth::reduced_bit_depth_8_or_less(&png.raw, 1)); b.iter(|| bit_depth::reduced_bit_depth_8_or_less(&png.raw, 1));
} }
@ -120,7 +121,7 @@ fn reductions_grayscale_4_to_1_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from( let input = test::black_box(PathBuf::from(
"tests/files/grayscale_4_should_be_grayscale_1.png", "tests/files/grayscale_4_should_be_grayscale_1.png",
)); ));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| bit_depth::reduced_bit_depth_8_or_less(&png.raw, 1)); b.iter(|| bit_depth::reduced_bit_depth_8_or_less(&png.raw, 1));
} }
@ -130,7 +131,7 @@ fn reductions_grayscale_2_to_1_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from( let input = test::black_box(PathBuf::from(
"tests/files/grayscale_2_should_be_grayscale_1.png", "tests/files/grayscale_2_should_be_grayscale_1.png",
)); ));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| bit_depth::reduced_bit_depth_8_or_less(&png.raw, 1)); b.iter(|| bit_depth::reduced_bit_depth_8_or_less(&png.raw, 1));
} }
@ -138,7 +139,7 @@ fn reductions_grayscale_2_to_1_bits(b: &mut Bencher) {
#[bench] #[bench]
fn reductions_rgba_to_rgb_16(b: &mut Bencher) { fn reductions_rgba_to_rgb_16(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/rgba_16_should_be_rgb_16.png")); let input = test::black_box(PathBuf::from("tests/files/rgba_16_should_be_rgb_16.png"));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| alpha::reduced_alpha_channel(&png.raw, false)); b.iter(|| alpha::reduced_alpha_channel(&png.raw, false));
} }
@ -146,7 +147,7 @@ fn reductions_rgba_to_rgb_16(b: &mut Bencher) {
#[bench] #[bench]
fn reductions_rgba_to_rgb_8(b: &mut Bencher) { fn reductions_rgba_to_rgb_8(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/rgba_8_should_be_rgb_8.png")); let input = test::black_box(PathBuf::from("tests/files/rgba_8_should_be_rgb_8.png"));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| alpha::reduced_alpha_channel(&png.raw, false)); b.iter(|| alpha::reduced_alpha_channel(&png.raw, false));
} }
@ -156,7 +157,7 @@ fn reductions_rgba_to_grayscale_alpha_16(b: &mut Bencher) {
let input = test::black_box(PathBuf::from( let input = test::black_box(PathBuf::from(
"tests/files/rgba_16_should_be_grayscale_alpha_16.png", "tests/files/rgba_16_should_be_grayscale_alpha_16.png",
)); ));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| color::reduced_rgb_to_grayscale(&png.raw)); b.iter(|| color::reduced_rgb_to_grayscale(&png.raw));
} }
@ -166,7 +167,7 @@ fn reductions_rgba_to_grayscale_alpha_8(b: &mut Bencher) {
let input = test::black_box(PathBuf::from( let input = test::black_box(PathBuf::from(
"tests/files/rgba_8_should_be_grayscale_alpha_8.png", "tests/files/rgba_8_should_be_grayscale_alpha_8.png",
)); ));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| color::reduced_rgb_to_grayscale(&png.raw)); b.iter(|| color::reduced_rgb_to_grayscale(&png.raw));
} }
@ -176,7 +177,7 @@ fn reductions_rgba_to_grayscale_16(b: &mut Bencher) {
let input = test::black_box(PathBuf::from( let input = test::black_box(PathBuf::from(
"tests/files/rgba_16_should_be_grayscale_16.png", "tests/files/rgba_16_should_be_grayscale_16.png",
)); ));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| { b.iter(|| {
color::reduced_rgb_to_grayscale(&png.raw) color::reduced_rgb_to_grayscale(&png.raw)
@ -189,7 +190,7 @@ fn reductions_rgba_to_grayscale_8(b: &mut Bencher) {
let input = test::black_box(PathBuf::from( let input = test::black_box(PathBuf::from(
"tests/files/rgba_8_should_be_grayscale_8.png", "tests/files/rgba_8_should_be_grayscale_8.png",
)); ));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| { b.iter(|| {
color::reduced_rgb_to_grayscale(&png.raw) color::reduced_rgb_to_grayscale(&png.raw)
@ -202,7 +203,7 @@ fn reductions_rgb_to_grayscale_16(b: &mut Bencher) {
let input = test::black_box(PathBuf::from( let input = test::black_box(PathBuf::from(
"tests/files/rgb_16_should_be_grayscale_16.png", "tests/files/rgb_16_should_be_grayscale_16.png",
)); ));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| color::reduced_rgb_to_grayscale(&png.raw)); b.iter(|| color::reduced_rgb_to_grayscale(&png.raw));
} }
@ -210,7 +211,7 @@ fn reductions_rgb_to_grayscale_16(b: &mut Bencher) {
#[bench] #[bench]
fn reductions_rgb_to_grayscale_8(b: &mut Bencher) { fn reductions_rgb_to_grayscale_8(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_grayscale_8.png")); let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_grayscale_8.png"));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| color::reduced_rgb_to_grayscale(&png.raw)); b.iter(|| color::reduced_rgb_to_grayscale(&png.raw));
} }
@ -218,7 +219,7 @@ fn reductions_rgb_to_grayscale_8(b: &mut Bencher) {
#[bench] #[bench]
fn reductions_rgba_to_palette_8(b: &mut Bencher) { fn reductions_rgba_to_palette_8(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/rgba_8_should_be_palette_8.png")); let input = test::black_box(PathBuf::from("tests/files/rgba_8_should_be_palette_8.png"));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| color::reduced_to_indexed(&png.raw)); b.iter(|| color::reduced_to_indexed(&png.raw));
} }
@ -226,7 +227,7 @@ fn reductions_rgba_to_palette_8(b: &mut Bencher) {
#[bench] #[bench]
fn reductions_rgb_to_palette_8(b: &mut Bencher) { fn reductions_rgb_to_palette_8(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_palette_8.png")); let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_palette_8.png"));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| color::reduced_to_indexed(&png.raw)); b.iter(|| color::reduced_to_indexed(&png.raw));
} }
@ -236,7 +237,7 @@ fn reductions_grayscale_8_to_palette_8(b: &mut Bencher) {
let input = test::black_box(PathBuf::from( let input = test::black_box(PathBuf::from(
"tests/files/grayscale_8_should_be_palette_8.png", "tests/files/grayscale_8_should_be_palette_8.png",
)); ));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| color::reduced_to_indexed(&png.raw)); b.iter(|| color::reduced_to_indexed(&png.raw));
} }
@ -246,7 +247,7 @@ fn reductions_palette_8_to_grayscale_8(b: &mut Bencher) {
let input = test::black_box(PathBuf::from( let input = test::black_box(PathBuf::from(
"tests/files/palette_8_should_be_grayscale_8.png", "tests/files/palette_8_should_be_grayscale_8.png",
)); ));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| color::indexed_to_channels(&png.raw)); b.iter(|| color::indexed_to_channels(&png.raw));
} }
@ -256,7 +257,7 @@ fn reductions_palette_duplicate_reduction(b: &mut Bencher) {
let input = test::black_box(PathBuf::from( let input = test::black_box(PathBuf::from(
"tests/files/palette_should_be_reduced_with_dupes.png", "tests/files/palette_should_be_reduced_with_dupes.png",
)); ));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| palette::reduced_palette(&png.raw, false)); b.iter(|| palette::reduced_palette(&png.raw, false));
} }
@ -266,7 +267,7 @@ fn reductions_palette_unused_reduction(b: &mut Bencher) {
let input = test::black_box(PathBuf::from( let input = test::black_box(PathBuf::from(
"tests/files/palette_should_be_reduced_with_unused.png", "tests/files/palette_should_be_reduced_with_unused.png",
)); ));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| palette::reduced_palette(&png.raw, false)); b.iter(|| palette::reduced_palette(&png.raw, false));
} }
@ -276,7 +277,7 @@ fn reductions_palette_full_reduction(b: &mut Bencher) {
let input = test::black_box(PathBuf::from( let input = test::black_box(PathBuf::from(
"tests/files/palette_should_be_reduced_with_both.png", "tests/files/palette_should_be_reduced_with_both.png",
)); ));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| palette::reduced_palette(&png.raw, false)); b.iter(|| palette::reduced_palette(&png.raw, false));
} }
@ -286,7 +287,7 @@ fn reductions_palette_sort(b: &mut Bencher) {
let input = test::black_box(PathBuf::from( let input = test::black_box(PathBuf::from(
"tests/files/palette_8_should_be_palette_8.png", "tests/files/palette_8_should_be_palette_8.png",
)); ));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| palette::sorted_palette(&png.raw)); b.iter(|| palette::sorted_palette(&png.raw));
} }
@ -294,7 +295,7 @@ fn reductions_palette_sort(b: &mut Bencher) {
#[bench] #[bench]
fn reductions_alpha(b: &mut Bencher) { fn reductions_alpha(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/rgba_8_reduce_alpha.png")); let input = test::black_box(PathBuf::from("tests/files/rgba_8_reduce_alpha.png"));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| alpha::cleaned_alpha_channel(&png.raw)); b.iter(|| alpha::cleaned_alpha_channel(&png.raw));
} }

View file

@ -3,14 +3,15 @@
extern crate oxipng; extern crate oxipng;
extern crate test; extern crate test;
use oxipng::{internal_tests::*, RowFilter}; use oxipng::internal_tests::*;
use oxipng::*;
use std::path::PathBuf; use std::path::PathBuf;
use test::Bencher; use test::Bencher;
#[bench] #[bench]
fn filters_minsum(b: &mut Bencher) { fn filters_minsum(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png")); let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| { b.iter(|| {
png.raw.filter_image(RowFilter::MinSum, false); png.raw.filter_image(RowFilter::MinSum, false);
@ -20,7 +21,7 @@ fn filters_minsum(b: &mut Bencher) {
#[bench] #[bench]
fn filters_entropy(b: &mut Bencher) { fn filters_entropy(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png")); let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| { b.iter(|| {
png.raw.filter_image(RowFilter::Entropy, false); png.raw.filter_image(RowFilter::Entropy, false);
@ -30,7 +31,7 @@ fn filters_entropy(b: &mut Bencher) {
#[bench] #[bench]
fn filters_bigrams(b: &mut Bencher) { fn filters_bigrams(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png")); let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| { b.iter(|| {
png.raw.filter_image(RowFilter::Bigrams, false); png.raw.filter_image(RowFilter::Bigrams, false);
@ -40,7 +41,7 @@ fn filters_bigrams(b: &mut Bencher) {
#[bench] #[bench]
fn filters_bigent(b: &mut Bencher) { fn filters_bigent(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png")); let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| { b.iter(|| {
png.raw.filter_image(RowFilter::BigEnt, false); png.raw.filter_image(RowFilter::BigEnt, false);
@ -50,7 +51,7 @@ fn filters_bigent(b: &mut Bencher) {
#[bench] #[bench]
fn filters_brute(b: &mut Bencher) { fn filters_brute(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png")); let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| { b.iter(|| {
png.raw.filter_image(RowFilter::Brute, false); png.raw.filter_image(RowFilter::Brute, false);

View file

@ -4,6 +4,7 @@ extern crate oxipng;
extern crate test; extern crate test;
use oxipng::internal_tests::*; use oxipng::internal_tests::*;
use oxipng::*;
use std::num::NonZeroU8; use std::num::NonZeroU8;
use std::path::PathBuf; use std::path::PathBuf;
use test::Bencher; use test::Bencher;
@ -14,7 +15,7 @@ const DEFAULT_ZOPFLI_ITERATIONS: NonZeroU8 = unsafe { NonZeroU8::new_unchecked(1
#[bench] #[bench]
fn zopfli_16_bits_strategy_0(b: &mut Bencher) { fn zopfli_16_bits_strategy_0(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png")); let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png"));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| { b.iter(|| {
zopfli_deflate(png.raw.data.as_ref(), DEFAULT_ZOPFLI_ITERATIONS).ok(); zopfli_deflate(png.raw.data.as_ref(), DEFAULT_ZOPFLI_ITERATIONS).ok();
@ -24,7 +25,7 @@ fn zopfli_16_bits_strategy_0(b: &mut Bencher) {
#[bench] #[bench]
fn zopfli_8_bits_strategy_0(b: &mut Bencher) { fn zopfli_8_bits_strategy_0(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png")); let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| { b.iter(|| {
zopfli_deflate(png.raw.data.as_ref(), DEFAULT_ZOPFLI_ITERATIONS).ok(); zopfli_deflate(png.raw.data.as_ref(), DEFAULT_ZOPFLI_ITERATIONS).ok();
@ -36,7 +37,7 @@ fn zopfli_4_bits_strategy_0(b: &mut Bencher) {
let input = test::black_box(PathBuf::from( let input = test::black_box(PathBuf::from(
"tests/files/palette_4_should_be_palette_4.png", "tests/files/palette_4_should_be_palette_4.png",
)); ));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| { b.iter(|| {
zopfli_deflate(png.raw.data.as_ref(), DEFAULT_ZOPFLI_ITERATIONS).ok(); zopfli_deflate(png.raw.data.as_ref(), DEFAULT_ZOPFLI_ITERATIONS).ok();
@ -48,7 +49,7 @@ fn zopfli_2_bits_strategy_0(b: &mut Bencher) {
let input = test::black_box(PathBuf::from( let input = test::black_box(PathBuf::from(
"tests/files/palette_2_should_be_palette_2.png", "tests/files/palette_2_should_be_palette_2.png",
)); ));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| { b.iter(|| {
zopfli_deflate(png.raw.data.as_ref(), DEFAULT_ZOPFLI_ITERATIONS).ok(); zopfli_deflate(png.raw.data.as_ref(), DEFAULT_ZOPFLI_ITERATIONS).ok();
@ -60,7 +61,7 @@ fn zopfli_1_bits_strategy_0(b: &mut Bencher) {
let input = test::black_box(PathBuf::from( let input = test::black_box(PathBuf::from(
"tests/files/palette_1_should_be_palette_1.png", "tests/files/palette_1_should_be_palette_1.png",
)); ));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| { b.iter(|| {
zopfli_deflate(png.raw.data.as_ref(), DEFAULT_ZOPFLI_ITERATIONS).ok(); zopfli_deflate(png.raw.data.as_ref(), DEFAULT_ZOPFLI_ITERATIONS).ok();

View file

@ -70,14 +70,6 @@ impl ColorType {
matches!(self, ColorType::RGB { .. } | ColorType::RGBA) matches!(self, ColorType::RGB { .. } | ColorType::RGBA)
} }
#[inline]
pub(crate) fn is_grayscale(&self) -> bool {
matches!(
self,
ColorType::Grayscale { .. } | ColorType::GrayscaleAlpha
)
}
#[inline] #[inline]
pub(crate) fn has_alpha(&self) -> bool { pub(crate) fn has_alpha(&self) -> bool {
matches!(self, ColorType::GrayscaleAlpha | ColorType::RGBA) matches!(self, ColorType::GrayscaleAlpha | ColorType::RGBA)

View file

@ -13,13 +13,8 @@ pub fn deflate(data: &[u8], level: u8, max_size: &AtomicMin) -> PngResult<Vec<u8
let len = compressor let len = compressor
.zlib_compress(data, &mut dest) .zlib_compress(data, &mut dest)
.map_err(|err| match err { .map_err(|err| match err {
CompressionError::InsufficientSpace => PngError::DeflatedDataTooLong(capacity), CompressionError::InsufficientSpace => PngError::DeflatedDataTooLong(capacity - 9),
})?; })?;
if let Some(max) = max_size.get() {
if len > max {
return Err(PngError::DeflatedDataTooLong(max));
}
}
dest.truncate(len); dest.truncate(len);
Ok(dest) Ok(dest)
} }

View file

@ -1,7 +1,10 @@
mod deflater; mod deflater;
use crate::AtomicMin;
use crate::{PngError, PngResult};
pub use deflater::crc32; pub use deflater::crc32;
pub use deflater::deflate; pub use deflater::deflate;
pub use deflater::inflate; pub use deflater::inflate;
use std::{fmt, fmt::Display};
#[cfg(feature = "zopfli")] #[cfg(feature = "zopfli")]
use std::num::NonZeroU8; use std::num::NonZeroU8;
@ -27,3 +30,30 @@ pub enum Deflaters {
iterations: NonZeroU8, iterations: NonZeroU8,
}, },
} }
impl Deflaters {
pub(crate) fn deflate(self, data: &[u8], max_size: &AtomicMin) -> PngResult<Vec<u8>> {
let compressed = match self {
Self::Libdeflater { compression } => deflate(data, compression, max_size)?,
#[cfg(feature = "zopfli")]
Self::Zopfli { iterations } => zopfli_deflate(data, iterations)?,
};
if let Some(max) = max_size.get() {
if compressed.len() > max {
return Err(PngError::DeflatedDataTooLong(max));
}
}
Ok(compressed)
}
}
impl Display for Deflaters {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Libdeflater { compression } => Display::fmt(compression, f),
#[cfg(feature = "zopfli")]
Self::Zopfli { .. } => Display::fmt("zopfli", f),
}
}
}

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,17 +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),
},
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",
@ -154,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

@ -1,12 +1,13 @@
use crate::colors::{BitDepth, ColorType}; use crate::colors::{BitDepth, ColorType};
use crate::deflate::crc32; use crate::deflate::{crc32, inflate};
use crate::error::PngError; use crate::error::PngError;
use crate::interlace::Interlacing; use crate::interlace::Interlacing;
use crate::AtomicMin;
use crate::Deflaters;
use crate::PngResult; use crate::PngResult;
use indexmap::IndexSet; use indexmap::IndexSet;
use log::warn;
use rgb::{RGB16, RGBA8}; use rgb::{RGB16, RGBA8};
use std::io;
use std::io::{Cursor, Read};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
/// Headers from the IHDR chunk of the image /// Headers from the IHDR chunk of the image
@ -62,21 +63,42 @@ impl IhdrData {
} }
} }
#[derive(Debug, Clone)]
pub struct Chunk {
pub name: [u8; 4],
pub data: Vec<u8>,
}
#[derive(Debug, PartialEq, Eq, Clone)] #[derive(Debug, PartialEq, Eq, Clone)]
/// Options to use for performing operations on headers (such as stripping) /// Options to use when stripping chunks
pub enum Headers { pub enum StripChunks {
/// None /// None
None, None,
/// Remove specific chunks /// Remove specific chunks
Strip(Vec<String>), Strip(IndexSet<[u8; 4]>),
/// Headers that won't affect rendering (all but cICP, iCCP, sBIT, sRGB, pHYs) /// Remove all chunks that won't affect rendering
Safe, Safe,
/// Remove all non-critical chunks except these /// Remove all non-critical chunks except these
Keep(IndexSet<String>), Keep(IndexSet<[u8; 4]>),
/// All non-critical headers /// All non-critical chunks
All, All,
} }
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(crate) fn keep(&self, name: &[u8; 4]) -> bool {
match &self {
StripChunks::None => true,
StripChunks::Keep(names) => names.contains(name),
StripChunks::Strip(names) => !names.contains(name),
StripChunks::Safe => Self::KEEP_SAFE.contains(name),
StripChunks::All => false,
}
}
}
#[inline] #[inline]
pub fn file_header_is_valid(bytes: &[u8]) -> bool { pub fn file_header_is_valid(bytes: &[u8]) -> bool {
let expected_header: [u8; 8] = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]; let expected_header: [u8; 8] = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];
@ -85,27 +107,26 @@ pub fn file_header_is_valid(bytes: &[u8]) -> bool {
} }
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct RawHeader<'a> { pub struct RawChunk<'a> {
pub name: [u8; 4], pub name: [u8; 4],
pub data: &'a [u8], pub data: &'a [u8],
} }
pub fn parse_next_header<'a>( pub fn parse_next_chunk<'a>(
byte_data: &'a [u8], byte_data: &'a [u8],
byte_offset: &mut usize, byte_offset: &mut usize,
fix_errors: bool, fix_errors: bool,
) -> PngResult<Option<RawHeader<'a>>> { ) -> PngResult<Option<RawChunk<'a>>> {
let mut rdr = Cursor::new( let length = read_be_u32(
byte_data byte_data
.get(*byte_offset..*byte_offset + 4) .get(*byte_offset..*byte_offset + 4)
.ok_or(PngError::TruncatedData)?, .ok_or(PngError::TruncatedData)?,
); );
let length = read_be_u32(&mut rdr).unwrap();
*byte_offset += 4; *byte_offset += 4;
let header_start = *byte_offset; let chunk_start = *byte_offset;
let chunk_name = byte_data let chunk_name = byte_data
.get(header_start..header_start + 4) .get(chunk_start..chunk_start + 4)
.ok_or(PngError::TruncatedData)?; .ok_or(PngError::TruncatedData)?;
if chunk_name == b"IEND" { if chunk_name == b"IEND" {
// End of data // End of data
@ -117,37 +138,34 @@ pub fn parse_next_header<'a>(
.get(*byte_offset..*byte_offset + length as usize) .get(*byte_offset..*byte_offset + length as usize)
.ok_or(PngError::TruncatedData)?; .ok_or(PngError::TruncatedData)?;
*byte_offset += length as usize; *byte_offset += length as usize;
let mut rdr = Cursor::new( let crc = read_be_u32(
byte_data byte_data
.get(*byte_offset..*byte_offset + 4) .get(*byte_offset..*byte_offset + 4)
.ok_or(PngError::TruncatedData)?, .ok_or(PngError::TruncatedData)?,
); );
let crc = read_be_u32(&mut rdr).unwrap();
*byte_offset += 4; *byte_offset += 4;
let header_bytes = byte_data let chunk_bytes = byte_data
.get(header_start..header_start + 4 + length as usize) .get(chunk_start..chunk_start + 4 + length as usize)
.ok_or(PngError::TruncatedData)?; .ok_or(PngError::TruncatedData)?;
if !fix_errors && crc32(header_bytes) != crc { if !fix_errors && crc32(chunk_bytes) != crc {
return Err(PngError::new(&format!( return Err(PngError::new(&format!(
"CRC Mismatch in {} header; May be recoverable by using --fix", "CRC Mismatch in {} chunk; May be recoverable by using --fix",
String::from_utf8_lossy(chunk_name) String::from_utf8_lossy(chunk_name)
))); )));
} }
let mut name = [0_u8; 4]; let name: [u8; 4] = chunk_name.try_into().unwrap();
name.copy_from_slice(chunk_name); Ok(Some(RawChunk { name, data }))
Ok(Some(RawHeader { name, data }))
} }
pub fn parse_ihdr_header( pub fn parse_ihdr_chunk(
byte_data: &[u8], byte_data: &[u8],
palette_data: Option<Vec<u8>>, palette_data: Option<Vec<u8>>,
trns_data: Option<Vec<u8>>, trns_data: Option<Vec<u8>>,
) -> PngResult<IhdrData> { ) -> PngResult<IhdrData> {
// This eliminates bounds checks for the rest of the function // This eliminates bounds checks for the rest of the function
let interlaced = byte_data.get(12).copied().ok_or(PngError::TruncatedData)?; let interlaced = byte_data.get(12).copied().ok_or(PngError::TruncatedData)?;
let mut rdr = Cursor::new(&byte_data[0..8]);
Ok(IhdrData { Ok(IhdrData {
color_type: match byte_data[9] { color_type: match byte_data[9] {
0 => ColorType::Grayscale { 0 => ColorType::Grayscale {
@ -170,8 +188,8 @@ pub fn parse_ihdr_header(
_ => return Err(PngError::new("Unexpected color type in header")), _ => return Err(PngError::new("Unexpected color type in header")),
}, },
bit_depth: byte_data[8].try_into()?, bit_depth: byte_data[8].try_into()?,
width: read_be_u32(&mut rdr).map_err(|_| PngError::TruncatedData)?, width: read_be_u32(&byte_data[0..4]),
height: read_be_u32(&mut rdr).map_err(|_| PngError::TruncatedData)?, height: read_be_u32(&byte_data[4..8]),
interlaced: interlaced.try_into()?, interlaced: interlaced.try_into()?,
}) })
} }
@ -196,8 +214,74 @@ fn palette_to_rgba(
} }
#[inline] #[inline]
fn read_be_u32<T: AsRef<[u8]>>(rdr: &mut Cursor<T>) -> Result<u32, io::Error> { fn read_be_u32(bytes: &[u8]) -> u32 {
let mut int_buf = [0; 4]; u32::from_be_bytes(bytes.try_into().unwrap())
rdr.read_exact(&mut int_buf)?; }
Ok(u32::from_be_bytes(int_buf))
/// Extract and decompress the ICC profile from an iCCP chunk
pub fn extract_icc(iccp: &Chunk) -> Option<Vec<u8>> {
// Skip (useless) profile name
let mut data = iccp.data.as_slice();
loop {
let (&n, rest) = data.split_first()?;
data = rest;
if n == 0 {
break;
}
}
let (&compression_method, compressed_data) = data.split_first()?;
if compression_method != 0 {
return None; // The profile is supposed to be compressed (method 0)
}
// The decompressed size is unknown so we have to guess the required buffer size
let max_size = compressed_data.len() * 2 + 1000;
match inflate(compressed_data, max_size) {
Ok(icc) => Some(icc),
Err(e) => {
// Log the error so we can know if the buffer size needs to be adjusted
warn!("Failed to decompress icc: {}", e);
None
}
}
}
/// Construct an iCCP chunk by compressing the ICC profile
pub fn construct_iccp(icc: &[u8], deflater: Deflaters) -> PngResult<Chunk> {
let mut compressed = deflater.deflate(icc, &AtomicMin::new(None))?;
let mut data = Vec::with_capacity(compressed.len() + 5);
data.extend(b"icc"); // Profile name - generally unused, can be anything
data.extend([0, 0]); // Null separator, zlib compression method
data.append(&mut compressed);
Ok(Chunk {
name: *b"iCCP",
data,
})
}
/// If the profile is sRGB, extracts the rendering intent value from it
pub fn srgb_rendering_intent(icc_data: &[u8]) -> Option<u8> {
let rendering_intent = *icc_data.get(67)?;
// The known profiles are the same as in libpng's `png_sRGB_checks`.
// The Profile ID header of ICC has a fixed layout,
// and is supposed to contain MD5 of profile data at this offset
match icc_data.get(84..100)? {
b"\x29\xf8\x3d\xde\xaf\xf2\x55\xae\x78\x42\xfa\xe4\xca\x83\x39\x0d"
| b"\xc9\x5b\xd6\x37\xe9\x5d\x8a\x3b\x0d\xf3\x8f\x99\xc1\x32\x03\x89"
| b"\xfc\x66\x33\x78\x37\xe2\x88\x6b\xfd\x72\xe9\x83\x82\x28\xf1\xb8"
| b"\x34\x56\x2a\xbf\x99\x4c\xcd\x06\x6d\x2c\x57\x21\xd0\xd6\x8c\x5d" => {
Some(rendering_intent)
}
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" => {
// Known-bad profiles are identified by their CRC
match (crc32(icc_data), icc_data.len()) {
(0x5d51_29ce, 3024) | (0x182e_a552, 3144) | (0xf29e_526d, 3144) => {
Some(rendering_intent)
}
_ => None,
}
}
_ => None,
}
} }

View file

@ -90,7 +90,6 @@ pub fn interlace_image(png: &PngImage) -> PngImage {
interlaced: Interlacing::Adam7, interlaced: Interlacing::Adam7,
..png.ihdr ..png.ihdr
}, },
aux_headers: png.aux_headers.clone(),
} }
} }
@ -105,7 +104,6 @@ pub fn deinterlace_image(png: &PngImage) -> PngImage {
interlaced: Interlacing::None, interlaced: Interlacing::None,
..png.ihdr ..png.ihdr
}, },
aux_headers: png.aux_headers.clone(),
} }
} }

View file

@ -25,9 +25,8 @@ extern crate rayon;
mod rayon; mod rayon;
use crate::atomicmin::AtomicMin; use crate::atomicmin::AtomicMin;
use crate::deflate::{crc32, inflate};
use crate::evaluate::Evaluator; use crate::evaluate::Evaluator;
use crate::headers::IhdrData; use crate::headers::*;
use crate::png::PngData; use crate::png::PngData;
use crate::png::PngImage; use crate::png::PngImage;
use crate::reduction::*; use crate::reduction::*;
@ -45,9 +44,9 @@ pub use crate::colors::{BitDepth, ColorType};
pub use crate::deflate::Deflaters; pub use crate::deflate::Deflaters;
pub use crate::error::PngError; pub use crate::error::PngError;
pub use crate::filters::RowFilter; pub use crate::filters::RowFilter;
pub use crate::headers::Headers; pub use crate::headers::StripChunks;
pub use crate::interlace::Interlacing; pub use crate::interlace::Interlacing;
pub use indexmap::{indexset, IndexMap, IndexSet}; pub use indexmap::{indexset, IndexSet};
pub use rgb::{RGB16, RGBA8}; pub use rgb::{RGB16, RGBA8};
mod atomicmin; mod atomicmin;
@ -67,9 +66,7 @@ mod sanity_checks;
#[doc(hidden)] #[doc(hidden)]
pub mod internal_tests { pub mod internal_tests {
pub use crate::atomicmin::*; pub use crate::atomicmin::*;
pub use crate::colors::*;
pub use crate::deflate::*; pub use crate::deflate::*;
pub use crate::headers::*;
pub use crate::png::*; pub use crate::png::*;
pub use crate::reduction::*; pub use crate::reduction::*;
#[cfg(feature = "sanity-checks")] #[cfg(feature = "sanity-checks")]
@ -189,10 +186,10 @@ pub struct Options {
/// ///
/// Default: `true` /// Default: `true`
pub idat_recoding: bool, pub idat_recoding: bool,
/// Which headers to strip from the PNG file, if any /// Which chunks to strip from the PNG file, if any
/// ///
/// Default: `None` /// Default: `None`
pub strip: Headers, pub strip: StripChunks,
/// Which DEFLATE algorithm to use /// Which DEFLATE algorithm to use
/// ///
/// Default: `Libdeflater` /// Default: `Libdeflater`
@ -306,7 +303,7 @@ impl Default for Options {
palette_reduction: true, palette_reduction: true,
grayscale_reduction: true, grayscale_reduction: true,
idat_recoding: true, idat_recoding: true,
strip: Headers::None, strip: StripChunks::None,
deflate: Deflaters::Libdeflater { compression: 11 }, deflate: Deflaters::Libdeflater { compression: 11 },
fast_evaluation: true, fast_evaluation: true,
timeout: None, timeout: None,
@ -318,6 +315,7 @@ impl Default for Options {
/// A raw image definition which can be used to create an optimized png /// A raw image definition which can be used to create an optimized png
pub struct RawImage { pub struct RawImage {
png: Arc<PngImage>, png: Arc<PngImage>,
aux_chunks: Vec<Chunk>,
} }
impl RawImage { impl RawImage {
@ -363,35 +361,40 @@ impl RawImage {
interlaced: Interlacing::None, interlaced: Interlacing::None,
}, },
data, data,
aux_headers: IndexMap::new(),
}), }),
aux_chunks: Vec::new(),
}) })
} }
/// Add a png chunk, such as "iTXt", to be included in the output /// Add a png chunk, such as "iTXt", to be included in the output
pub fn add_png_chunk(&mut self, chunk_type: [u8; 4], data: Vec<u8>) { pub fn add_png_chunk(&mut self, name: [u8; 4], data: Vec<u8>) {
// We can guarantee this will succeed - failure indicates a bug self.aux_chunks.push(Chunk { name, data });
let png = Arc::get_mut(&mut self.png).unwrap();
png.aux_headers.insert(chunk_type, data);
} }
/// Add an ICC profile for the image /// Add an ICC profile for the image
pub fn add_icc_profile(&mut self, data: &[u8]) { pub fn add_icc_profile(&mut self, data: &[u8]) {
// Compress with default compression level // Compress with fastest compression level - will be recompressed during optimization
if let Ok(mut compressed) = deflate::deflate(data, 11, &AtomicMin::new(None)) { let deflater = Deflaters::Libdeflater { compression: 1 };
let mut iccp = Vec::with_capacity(compressed.len() + 13); if let Ok(iccp) = construct_iccp(data, deflater) {
iccp.extend(b"icc"); // Profile name - generally unused, can be anything self.aux_chunks.push(iccp);
iccp.extend([0, 0]); // Null separator, zlib compression method
iccp.append(&mut compressed);
self.add_png_chunk(*b"iCCP", iccp);
} }
} }
/// Create an optimized png from the raw image data using the options provided /// Create an optimized png from the raw image data using the options provided
pub fn create_optimized_png(&self, opts: &Options) -> PngResult<Vec<u8>> { pub fn create_optimized_png(&self, opts: &Options) -> PngResult<Vec<u8>> {
let deadline = Arc::new(Deadline::new(opts.timeout)); let deadline = Arc::new(Deadline::new(opts.timeout));
let png = optimize_raw(Arc::clone(&self.png), opts, deadline, None) let mut png = optimize_raw(self.png.clone(), opts, deadline, None)
.ok_or_else(|| PngError::new("Failed to optimize input data"))?; .ok_or_else(|| PngError::new("Failed to optimize input data"))?;
// Process aux chunks
png.aux_chunks = self
.aux_chunks
.iter()
.filter(|c| opts.strip.keep(&c.name))
.cloned()
.collect();
postprocess_chunks(&mut png, opts, &self.png.ihdr);
Ok(png.output()) Ok(png.output())
} }
} }
@ -434,7 +437,7 @@ pub fn optimize(input: &InFile, output: &OutFile, opts: &Options) -> PngResult<(
} }
}; };
let mut png = PngData::from_slice(&in_data, opts.fix_errors)?; let mut png = PngData::from_slice(&in_data, opts)?;
if opts.check { if opts.check {
info!("Running in check mode, not optimizing"); info!("Running in check mode, not optimizing");
@ -522,7 +525,7 @@ pub fn optimize_from_memory(data: &[u8], opts: &Options) -> PngResult<Vec<u8>> {
let deadline = Arc::new(Deadline::new(opts.timeout)); let deadline = Arc::new(Deadline::new(opts.timeout));
let original_size = data.len(); let original_size = data.len();
let mut png = PngData::from_slice(data, opts.fix_errors)?; let mut png = PngData::from_slice(data, opts)?;
// Run the optimizer on the decoded PNG. // Run the optimizer on the decoded PNG.
let optimized_output = optimize_png(&mut png, data, opts, deadline)?; let optimized_output = optimize_png(&mut png, data, opts, deadline)?;
@ -535,13 +538,7 @@ pub fn optimize_from_memory(data: &[u8], opts: &Options) -> PngResult<Vec<u8>> {
} }
} }
#[derive(Debug, PartialEq, PartialOrd, Clone, Copy)] type TrialResult = (RowFilter, Vec<u8>);
/// Defines options to be used for a single compression trial
struct TrialOptions {
pub filter: RowFilter,
pub compression: u8,
}
type TrialWithData = (TrialOptions, Vec<u8>);
/// Perform optimization on the input PNG object using the options provided /// Perform optimization on the input PNG object using the options provided
fn optimize_png( fn optimize_png(
@ -553,27 +550,27 @@ fn optimize_png(
// Print png info // Print png info
let file_original_size = original_data.len(); let file_original_size = original_data.len();
let idat_original_size = png.idat_data.len(); let idat_original_size = png.idat_data.len();
let raw = png.raw.clone();
debug!( debug!(
" {}x{} pixels, PNG format", " {}x{} pixels, PNG format",
png.raw.ihdr.width, png.raw.ihdr.height raw.ihdr.width, raw.ihdr.height
); );
report_format(" ", &png.raw); report_format(" ", &raw);
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);
// Do this first so that reductions can ignore certain chunks such as bKGD
perform_strip(png, 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(png.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);
let output = png.output(); let output = png.output();
if idat_original_size >= png.idat_data.len() { if idat_original_size >= png.idat_data.len() {
@ -635,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;
} }
} }
@ -647,7 +644,7 @@ fn optimize_raw(
if opts.idat_recoding || reduction_occurred { if opts.idat_recoding || reduction_occurred {
let mut filters = opts.filter.clone(); let mut filters = opts.filter.clone();
let fast_eval = opts.fast_evaluation && (filters.len() > 1 || eval_result.is_some()); let fast_eval = opts.fast_evaluation && (filters.len() > 1 || eval_result.is_some());
let best: Option<TrialWithData> = if fast_eval { let best: Option<TrialResult> = if fast_eval {
// Perform a fast evaluation of selected filters followed by a single main compression trial // Perform a fast evaluation of selected filters followed by a single main compression trial
if eval_result.is_some() { if eval_result.is_some() {
@ -659,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() {
@ -667,22 +664,18 @@ fn optimize_raw(
} }
} }
// We should have a result here - fail if not (e.g. deadline passed) // We should have a result here - fail if not (e.g. deadline passed)
let eval_result = eval_result?; let result = eval_result?;
let trial = TrialOptions { match opts.deflate {
filter: eval_result.filter, Deflaters::Libdeflater { compression } if compression <= eval_compression => {
compression: match opts.deflate { // No further compression required
Deflaters::Libdeflater { compression } => compression, Some((result.filter, result.idat_data))
_ => 0, }
}, _ => {
}; debug!("Trying: {}", result.filter);
if trial.compression > 0 && trial.compression <= eval_compression { let best_size = AtomicMin::new(max_size);
// No further compression required perform_trial(&result.filtered, opts, result.filter, &best_size)
Some((trial, eval_result.image.idat_data)) }
} else {
debug!("Trying: {}", trial.filter);
let best_size = AtomicMin::new(max_size);
perform_trial(&eval_result.image.filtered, opts, trial, &best_size)
} }
} else { } else {
// Perform full compression trials of selected filters and determine the best // Perform full compression trials of selected filters and determine the best
@ -698,28 +691,16 @@ fn optimize_raw(
} }
} }
let mut results: Vec<TrialOptions> = Vec::with_capacity(filters.len()); debug!("Trying: {} filters", filters.len());
for f in &filters {
results.push(TrialOptions {
filter: *f,
compression: match opts.deflate {
Deflaters::Libdeflater { compression } => compression,
_ => 0,
},
});
}
debug!("Trying: {} filters", results.len());
let best_size = AtomicMin::new(max_size); let best_size = AtomicMin::new(max_size);
let results_iter = results.into_par_iter().with_max_len(1); let results_iter = filters.into_par_iter().with_max_len(1);
let best = results_iter.filter_map(|trial| { let best = results_iter.filter_map(|filter| {
if deadline.passed() { if deadline.passed() {
return None; return None;
} }
let filtered = &png.filter_image(trial.filter, opts.optimize_alpha); let filtered = &png.filter_image(filter, opts.optimize_alpha);
perform_trial(filtered, opts, trial, &best_size) perform_trial(filtered, opts, filter, &best_size)
}); });
best.reduce_with(|i, j| { best.reduce_with(|i, j| {
if i.1.len() < j.1.len() || (i.1.len() == j.1.len() && i.0 < j.0) { if i.1.len() < j.1.len() || (i.1.len() == j.1.len() && i.0 < j.0) {
@ -730,19 +711,18 @@ fn optimize_raw(
}) })
}; };
if let Some((trial, 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![],
idat_data, 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!(
" zc = {} f = {:8} {} bytes", " zc = {} f = {:8} {} bytes",
trial.compression, opts.deflate,
trial.filter, filter,
image.idat_data.len() image.idat_data.len()
); );
return Some(image); return Some(image);
@ -751,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!(
@ -771,37 +755,26 @@ fn optimize_raw(
fn perform_trial( fn perform_trial(
filtered: &[u8], filtered: &[u8],
opts: &Options, opts: &Options,
trial: TrialOptions, filter: RowFilter,
best_size: &AtomicMin, best_size: &AtomicMin,
) -> Option<TrialWithData> { ) -> Option<TrialResult> {
let new_idat = match opts.deflate { match opts.deflate.deflate(filtered, best_size) {
Deflaters::Libdeflater { .. } => deflate::deflate(filtered, trial.compression, best_size), Ok(new_idat) => {
#[cfg(feature = "zopfli")] let bytes = new_idat.len();
Deflaters::Zopfli { iterations } => deflate::zopfli_deflate(filtered, iterations), best_size.set_min(bytes);
};
// update best size or convert to error if not smaller
let new_idat = match new_idat {
Ok(n) if !best_size.set_min(n.len()) => Err(PngError::DeflatedDataTooLong(n.len())),
_ => new_idat,
};
match new_idat {
Ok(n) => {
let bytes = n.len();
trace!( trace!(
" zc = {} f = {:8} {} bytes", " zc = {} f = {:8} {} bytes",
trial.compression, opts.deflate,
trial.filter, filter,
bytes bytes
); );
Some((trial, n)) Some((filter, new_idat))
} }
Err(PngError::DeflatedDataTooLong(bytes)) => { Err(PngError::DeflatedDataTooLong(bytes)) => {
trace!( trace!(
" zc = {} f = {:8} >{} bytes", " zc = {} f = {:8} >{} bytes",
trial.compression, opts.deflate,
trial.filter, filter,
bytes, bytes,
); );
None None
@ -867,100 +840,59 @@ fn report_format(prefix: &str, png: &PngImage) {
); );
} }
/// Strip headers from the `PngData` object, as requested by the passed `Options` /// Perform cleanup of certain chunks from the `PngData` object, after optimization has been completed
fn perform_strip(png: &mut PngData, opts: &Options) { fn postprocess_chunks(png: &mut PngData, opts: &Options, orig_ihdr: &IhdrData) {
let raw = Arc::make_mut(&mut png.raw); if let Some(iccp_idx) = png.aux_chunks.iter().position(|c| &c.name == b"iCCP") {
match opts.strip { // See if we can replace an iCCP chunk with an sRGB chunk
// Strip headers let may_replace_iccp = opts.strip != StripChunks::None && opts.strip.keep(b"sRGB");
Headers::None => (), if may_replace_iccp && png.aux_chunks.iter().any(|c| &c.name == b"sRGB") {
Headers::Keep(ref hdrs) => raw
.aux_headers
.retain(|hdr, _| std::str::from_utf8(hdr).map_or(false, |name| hdrs.contains(name))),
Headers::Strip(ref hdrs) => {
for hdr in hdrs {
raw.aux_headers.remove(hdr.as_bytes());
}
}
Headers::Safe => {
const PRESERVED_HEADERS: [[u8; 4]; 5] =
[*b"cICP", *b"iCCP", *b"sBIT", *b"sRGB", *b"pHYs"];
let keys: Vec<[u8; 4]> = raw.aux_headers.keys().cloned().collect();
for hdr in &keys {
if !PRESERVED_HEADERS.contains(hdr) {
raw.aux_headers.remove(hdr);
}
}
}
Headers::All => {
raw.aux_headers = IndexMap::new();
}
}
let may_replace_iccp = match opts.strip {
Headers::Keep(ref hdrs) => hdrs.contains("sRGB"),
Headers::Strip(ref hdrs) => !hdrs.iter().any(|v| v == "sRGB"),
Headers::Safe => true,
Headers::None | Headers::All => false,
};
if may_replace_iccp {
if raw.aux_headers.get(b"sRGB").is_some() {
// Files aren't supposed to have both chunks, so we chose to honor sRGB // Files aren't supposed to have both chunks, so we chose to honor sRGB
raw.aux_headers.remove(b"iCCP"); trace!("Removing iCCP chunk due to conflict with sRGB chunk");
} else if let Some(intent) = raw png.aux_chunks.remove(iccp_idx);
.aux_headers } else if let Some(icc) = extract_icc(&png.aux_chunks[iccp_idx]) {
.get(b"iCCP") let intent = if may_replace_iccp {
.and_then(|iccp| srgb_rendering_intent(iccp)) srgb_rendering_intent(&icc)
{ } else {
// sRGB-like profile can be safely replaced with None
// an sRGB chunk with the same rendering intent };
raw.aux_headers.remove(b"iCCP"); // sRGB-like profile can be replaced with an sRGB chunk with the same rendering intent
raw.aux_headers.insert(*b"sRGB", vec![intent]); // Otherwise try recompressing the profile
} if let Some(intent) = intent {
} trace!("Replacing iCCP chunk with equivalent sRGB chunk");
} png.aux_chunks[iccp_idx] = Chunk {
name: *b"sRGB",
/// If the profile is sRGB, extracts the rendering intent value from it data: vec![intent],
fn srgb_rendering_intent(mut iccp: &[u8]) -> Option<u8> { };
// Skip (useless) profile name } else if let Ok(iccp) = construct_iccp(&icc, opts.deflate) {
loop { let cur_len = png.aux_chunks[iccp_idx].data.len();
let (&n, rest) = iccp.split_first()?; let new_len = iccp.data.len();
iccp = rest; if new_len < cur_len {
if n == 0 { debug!(
break; "Recompressed iCCP chunk: {} ({} bytes decrease)",
} new_len,
} cur_len - new_len
);
let (&compression_method, compressed_data) = iccp.split_first()?; png.aux_chunks[iccp_idx] = iccp;
if compression_method != 0 {
return None; // The profile is supposed to be compressed (method 0)
}
// The decompressed size is unknown so we have to guess the required buffer size
let max_size = (compressed_data.len() * 2).max(1000);
let icc_data = inflate(compressed_data, max_size).ok()?;
let rendering_intent = *icc_data.get(67)?;
// The known profiles are the same as in libpng's `png_sRGB_checks`.
// The Profile ID header of ICC has a fixed layout,
// and is supposed to contain MD5 of profile data at this offset
match icc_data.get(84..100)? {
b"\x29\xf8\x3d\xde\xaf\xf2\x55\xae\x78\x42\xfa\xe4\xca\x83\x39\x0d"
| b"\xc9\x5b\xd6\x37\xe9\x5d\x8a\x3b\x0d\xf3\x8f\x99\xc1\x32\x03\x89"
| b"\xfc\x66\x33\x78\x37\xe2\x88\x6b\xfd\x72\xe9\x83\x82\x28\xf1\xb8"
| b"\x34\x56\x2a\xbf\x99\x4c\xcd\x06\x6d\x2c\x57\x21\xd0\xd6\x8c\x5d" => {
Some(rendering_intent)
}
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" => {
// Known-bad profiles are identified by their CRC
match (crc32(&icc_data), icc_data.len()) {
(0x5d51_29ce, 3024) | (0x182e_a552, 3144) | (0xf29e_526d, 3144) => {
Some(rendering_intent)
} }
_ => None,
} }
} }
_ => None, }
// If the depth/color type has changed, some chunks may be invalid and should be dropped
// While these could potentially be converted, they have no known use case today and are
// generally more trouble than they're worth
let ihdr = &png.raw.ihdr;
if orig_ihdr.bit_depth != ihdr.bit_depth || orig_ihdr.color_type != ihdr.color_type {
png.aux_chunks.retain(|c| {
let invalid = &c.name == b"bKGD" || &c.name == b"sBIT" || &c.name == b"hIST";
if invalid {
warn!(
"Removing {} chunk as it no longer matches the image data",
std::str::from_utf8(&c.name).unwrap()
);
}
!invalid
});
} }
} }

View file

@ -17,9 +17,9 @@ use clap::{AppSettings, Arg, ArgAction, ArgMatches, Command};
use indexmap::IndexSet; use indexmap::IndexSet;
use log::{error, warn}; use log::{error, warn};
use oxipng::Deflaters; use oxipng::Deflaters;
use oxipng::Headers;
use oxipng::Options; use oxipng::Options;
use oxipng::RowFilter; use oxipng::RowFilter;
use oxipng::StripChunks;
use oxipng::{InFile, OutFile}; use oxipng::{InFile, OutFile};
use std::fs::DirBuilder; use std::fs::DirBuilder;
#[cfg(feature = "zopfli")] #[cfg(feature = "zopfli")]
@ -496,40 +496,44 @@ fn parse_opts_into_struct(
opts.idat_recoding = false; opts.idat_recoding = false;
} }
if let Some(hdrs) = matches.value_of("keep") { if let Some(keep) = matches.value_of("keep") {
opts.strip = Headers::Keep(hdrs.split(',').map(|x| x.trim().to_owned()).collect()) let names = keep
.split(',')
.map(parse_chunk_name)
.collect::<Result<_, _>>()?;
opts.strip = StripChunks::Keep(names)
} }
if let Some(hdrs) = matches.value_of("strip") { if let Some(strip) = matches.value_of("strip") {
let hdrs = hdrs if strip == "safe" {
.split(',') opts.strip = StripChunks::Safe;
.map(|x| x.trim().to_owned()) } else if strip == "all" {
.collect::<Vec<String>>(); opts.strip = StripChunks::All;
if hdrs.contains(&"safe".to_owned()) || hdrs.contains(&"all".to_owned()) {
if hdrs.len() > 1 {
return Err(
"'safe' or 'all' presets for --strip should be used by themselves".to_owned(),
);
}
if hdrs[0] == "safe" {
opts.strip = Headers::Safe;
} else {
opts.strip = Headers::All;
}
} else { } else {
const FORBIDDEN_CHUNKS: [[u8; 4]; 5] = const FORBIDDEN_CHUNKS: [[u8; 4]; 5] =
[*b"IHDR", *b"IDAT", *b"tRNS", *b"PLTE", *b"IEND"]; [*b"IHDR", *b"IDAT", *b"tRNS", *b"PLTE", *b"IEND"];
for i in &hdrs { let names = strip
if FORBIDDEN_CHUNKS.iter().any(|chunk| chunk == i.as_bytes()) { .split(',')
return Err(format!("{} chunk is not allowed to be stripped", i)); .map(|x| {
} if x == "safe" || x == "all" {
} return Err(
opts.strip = Headers::Strip(hdrs); "'safe' or 'all' presets for --strip should be used by themselves"
.to_owned(),
);
}
let name = parse_chunk_name(x)?;
if FORBIDDEN_CHUNKS.contains(&name) {
return Err(format!("{} chunk is not allowed to be stripped", x));
}
Ok(name)
})
.collect::<Result<_, _>>()?;
opts.strip = StripChunks::Strip(names);
} }
} }
if matches.is_present("strip-safe") { if matches.is_present("strip-safe") {
opts.strip = Headers::Safe; opts.strip = StripChunks::Safe;
} }
if matches.is_present("zopfli") { if matches.is_present("zopfli") {
@ -556,6 +560,13 @@ fn parse_opts_into_struct(
Ok((out_file, out_dir, opts)) Ok((out_file, out_dir, opts))
} }
fn parse_chunk_name(name: &str) -> Result<[u8; 4], String> {
name.trim()
.as_bytes()
.try_into()
.map_err(|_| format!("Invalid chunk name {}", name))
}
fn parse_numeric_range_opts( fn parse_numeric_range_opts(
input: &str, input: &str,
min_value: u8, min_value: u8,

View file

@ -4,8 +4,8 @@ use crate::error::PngError;
use crate::filters::*; use crate::filters::*;
use crate::headers::*; use crate::headers::*;
use crate::interlace::{deinterlace_image, interlace_image, Interlacing}; use crate::interlace::{deinterlace_image, interlace_image, Interlacing};
use crate::Options;
use bitvec::bitarr; use bitvec::bitarr;
use indexmap::IndexMap;
use libdeflater::{CompressionLvl, Compressor}; use libdeflater::{CompressionLvl, Compressor};
use rgb::ComponentSlice; use rgb::ComponentSlice;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
@ -30,8 +30,6 @@ pub struct PngImage {
pub ihdr: IhdrData, pub ihdr: IhdrData,
/// The uncompressed, unfiltered data from the IDAT chunk /// The uncompressed, unfiltered data from the IDAT chunk
pub data: Vec<u8>, pub data: Vec<u8>,
/// All non-critical headers from the PNG are stored here
pub aux_headers: IndexMap<[u8; 4], Vec<u8>>,
} }
/// Contains all data relevant to a PNG image /// Contains all data relevant to a PNG image
@ -41,17 +39,17 @@ 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 /// All non-critical chunks from the PNG are stored here
pub filtered: Vec<u8>, pub aux_chunks: Vec<Chunk>,
} }
impl PngData { impl PngData {
/// Create a new `PngData` struct by opening a file /// Create a new `PngData` struct by opening a file
#[inline] #[inline]
pub fn new(filepath: &Path, fix_errors: bool) -> Result<Self, PngError> { pub fn new(filepath: &Path, opts: &Options) -> Result<Self, PngError> {
let byte_data = Self::read_file(filepath)?; let byte_data = Self::read_file(filepath)?;
Self::from_slice(&byte_data, fix_errors) Self::from_slice(&byte_data, opts)
} }
pub fn read_file(filepath: &Path) -> Result<Vec<u8>, PngError> { pub fn read_file(filepath: &Path) -> Result<Vec<u8>, PngError> {
@ -80,7 +78,7 @@ impl PngData {
} }
/// Create a new `PngData` struct by reading a slice /// Create a new `PngData` struct by reading a slice
pub fn from_slice(byte_data: &[u8], fix_errors: bool) -> Result<Self, PngError> { pub fn from_slice(byte_data: &[u8], opts: &Options) -> Result<Self, PngError> {
let mut byte_offset: usize = 0; let mut byte_offset: usize = 0;
// Test that png header is valid // Test that png header is valid
let header = byte_data.get(0..8).ok_or(PngError::TruncatedData)?; let header = byte_data.get(0..8).ok_or(PngError::TruncatedData)?;
@ -88,71 +86,65 @@ impl PngData {
return Err(PngError::NotPNG); return Err(PngError::NotPNG);
} }
byte_offset += 8; byte_offset += 8;
// Read the data headers
let mut aux_headers: IndexMap<[u8; 4], Vec<u8>> = IndexMap::new(); // Read the data chunks
let mut idat_headers: Vec<u8> = Vec::new(); let mut idat_data: Vec<u8> = Vec::new();
while let Some(header) = parse_next_header(byte_data, &mut byte_offset, fix_errors)? { let mut key_chunks: FxHashMap<[u8; 4], Vec<u8>> = FxHashMap::default();
match &header.name { let mut aux_chunks: Vec<Chunk> = Vec::new();
b"IDAT" => idat_headers.extend_from_slice(header.data), 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"acTL" => return Err(PngError::APNGNotSupported),
b"IHDR" | b"PLTE" | b"tRNS" => {
key_chunks.insert(chunk.name, chunk.data.to_owned());
}
_ => { _ => {
aux_headers.insert(header.name, header.data.to_owned()); if opts.strip.keep(&chunk.name) {
aux_chunks.push(Chunk {
name: chunk.name,
data: chunk.data.to_owned(),
})
}
} }
} }
} }
// Parse the headers into our PngData
if idat_headers.is_empty() { // Parse the chunks into our PngData
if idat_data.is_empty() {
return Err(PngError::ChunkMissing("IDAT")); return Err(PngError::ChunkMissing("IDAT"));
} }
let ihdr = match aux_headers.remove(b"IHDR") { let ihdr_chunk = match key_chunks.remove(b"IHDR") {
Some(ihdr) => ihdr, Some(ihdr) => ihdr,
None => return Err(PngError::ChunkMissing("IHDR")), None => return Err(PngError::ChunkMissing("IHDR")),
}; };
let ihdr_header = parse_ihdr_header( let ihdr = parse_ihdr_chunk(
&ihdr, &ihdr_chunk,
aux_headers.remove(b"PLTE"), key_chunks.remove(b"PLTE"),
aux_headers.remove(b"tRNS"), key_chunks.remove(b"tRNS"),
)?; )?;
let raw_data = deflate::inflate(idat_headers.as_ref(), ihdr_header.raw_data_size())?; let raw_data = deflate::inflate(idat_data.as_ref(), ihdr.raw_data_size())?;
// Reject files with incorrect width/height or truncated data // Reject files with incorrect width/height or truncated data
if raw_data.len() != ihdr_header.raw_data_size() { if raw_data.len() != ihdr.raw_data_size() {
return Err(PngError::TruncatedData); return Err(PngError::TruncatedData);
} }
let mut raw = PngImage { let mut raw = PngImage {
ihdr: ihdr_header, ihdr,
data: raw_data, data: raw_data,
aux_headers,
}; };
let unfiltered = raw.unfilter_image()?; raw.data = raw.unfilter_image()?;
// Return the PngData // Return the PngData
Ok(Self { Ok(Self {
idat_data: idat_headers, idat_data,
filtered: std::mem::replace(&mut raw.data, unfiltered),
raw: Arc::new(raw), raw: Arc::new(raw),
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
@ -173,14 +165,13 @@ 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 headers // Ancillary chunks
for (key, header) in self for chunk in self
.raw .aux_chunks
.aux_headers
.iter() .iter()
.filter(|&(key, _)| !(key == b"bKGD" || key == b"hIST" || key == b"tRNS")) .filter(|c| !(&c.name == b"bKGD" || &c.name == b"hIST" || &c.name == b"tRNS"))
{ {
write_png_block(key, header, &mut output); write_png_block(&chunk.name, &chunk.data, &mut output);
} }
// Palette and transparency // Palette and transparency
match &self.raw.ihdr.color_type { match &self.raw.ihdr.color_type {
@ -210,14 +201,13 @@ impl PngData {
} }
_ => {} _ => {}
} }
// Special ancillary headers that need to come after PLTE but before IDAT // Special ancillary chunks that need to come after PLTE but before IDAT
for (key, header) in self for chunk in self
.raw .aux_chunks
.aux_headers
.iter() .iter()
.filter(|&(key, _)| key == b"bKGD" || key == b"hIST" || key == b"tRNS") .filter(|c| &c.name == b"bKGD" || &c.name == b"hIST" || &c.name == b"tRNS")
{ {
write_png_block(key, header, &mut output); write_png_block(&chunk.name, &chunk.data, &mut output);
} }
// IDAT data // IDAT data
write_png_block(b"IDAT", &self.idat_data, &mut output); write_png_block(b"IDAT", &self.idat_data, &mut output);
@ -265,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<'_> {
@ -451,14 +459,15 @@ impl PngImage {
filtered filtered
} }
} }
fn write_png_block(key: &[u8], header: &[u8], output: &mut Vec<u8>) {
let mut header_data = Vec::with_capacity(header.len() + 4); fn write_png_block(key: &[u8], chunk: &[u8], output: &mut Vec<u8>) {
header_data.extend_from_slice(key); let mut chunk_data = Vec::with_capacity(chunk.len() + 4);
header_data.extend_from_slice(header); chunk_data.extend_from_slice(key);
output.reserve(header_data.len() + 8); chunk_data.extend_from_slice(chunk);
output.extend_from_slice(&(header_data.len() as u32 - 4).to_be_bytes()); output.reserve(chunk_data.len() + 8);
let crc = deflate::crc32(&header_data); output.extend_from_slice(&(chunk_data.len() as u32 - 4).to_be_bytes());
output.append(&mut header_data); let crc = deflate::crc32(&chunk_data);
output.append(&mut chunk_data);
output.extend_from_slice(&crc.to_be_bytes()); output.extend_from_slice(&crc.to_be_bytes());
} }

View file

@ -25,7 +25,6 @@ pub fn cleaned_alpha_channel(png: &PngImage) -> Option<PngImage> {
Some(PngImage { Some(PngImage {
data: reduced, data: reduced,
ihdr: png.ihdr.clone(), ihdr: png.ihdr.clone(),
aux_headers: png.aux_headers.clone(),
}) })
} }
@ -88,20 +87,11 @@ pub fn reduced_alpha_channel(png: &PngImage, optimize_alpha: bool) -> Option<Png
}, },
}; };
let mut aux_headers = png.aux_headers.clone();
// sBIT contains information about alpha channel's original depth,
// and alpha has just been removed
if let Some(sbit_header) = png.aux_headers.get(b"sBIT") {
// Some programs save the sBIT header as RGB even if the image is RGBA.
aux_headers.insert(*b"sBIT", sbit_header.iter().cloned().take(3).collect());
}
Some(PngImage { Some(PngImage {
data: raw_data, data: raw_data,
ihdr: IhdrData { ihdr: IhdrData {
color_type: target_color_type, color_type: target_color_type,
..png.ihdr ..png.ihdr
}, },
aux_headers,
}) })
} }

View file

@ -22,7 +22,6 @@ pub fn reduced_bit_depth_16_to_8(png: &PngImage) -> Option<PngImage> {
bit_depth: BitDepth::Eight, bit_depth: BitDepth::Eight,
..png.ihdr ..png.ihdr
}, },
aux_headers: png.aux_headers.clone(),
}) })
} }
@ -37,37 +36,22 @@ pub fn reduced_bit_depth_8_or_less(png: &PngImage, mut minimum_bits: usize) -> O
// Calculate the current number of pixels per byte // Calculate the current number of pixels per byte
let ppb = 8 / bit_depth; let ppb = 8 / bit_depth;
if let ColorType::Indexed { .. } = png.ihdr.color_type { if let ColorType::Indexed { palette } = &png.ihdr.color_type {
for line in png.scan_lines(false) { // We can easily determine minimum depth by the palette size
let line_max = line let required_bits = match palette.len() {
.data 0..=2 => 1,
.iter() 3..=4 => 2,
.map(|&byte| match png.ihdr.bit_depth { 5..=16 => 4,
BitDepth::Two => (byte & 0x3) _ => 8,
.max((byte >> 2) & 0x3) };
.max((byte >> 4) & 0x3) if required_bits >= bit_depth {
.max(byte >> 6), // Not reducable
BitDepth::Four => (byte & 0xF).max(byte >> 4), return None;
_ => byte, } else if required_bits > minimum_bits {
}) minimum_bits = required_bits;
.max()
.unwrap_or(0);
let required_bits = match line_max {
x if x > 0x0F => 8,
x if x > 0x03 => 4,
x if x > 0x01 => 2,
_ => 1,
};
if required_bits > minimum_bits {
minimum_bits = required_bits;
if minimum_bits >= bit_depth {
// Not reducable
return None;
}
}
} }
} else { } else {
// Checking for grayscale depth reduction is quite different than for indexed // Finding minimum depth for grayscale is much more complicated
let mut mask = (1 << minimum_bits) - 1; let mut mask = (1 << minimum_bits) - 1;
let mut divisions = 1..(bit_depth / minimum_bits); let mut divisions = 1..(bit_depth / minimum_bits);
for &b in &png.data { for &b in &png.data {
@ -155,6 +139,5 @@ pub fn reduced_bit_depth_8_or_less(png: &PngImage, mut minimum_bits: usize) -> O
bit_depth: (minimum_bits as u8).try_into().unwrap(), bit_depth: (minimum_bits as u8).try_into().unwrap(),
..png.ihdr ..png.ihdr
}, },
aux_headers: png.aux_headers.clone(),
}) })
} }

View file

@ -3,7 +3,7 @@ use crate::headers::IhdrData;
use crate::png::PngImage; use crate::png::PngImage;
use indexmap::IndexSet; use indexmap::IndexSet;
use rgb::alt::Gray; use rgb::alt::Gray;
use rgb::{ComponentMap, ComponentSlice, FromSlice, RGB, RGBA, RGBA8}; use rgb::{ComponentMap, ComponentSlice, FromSlice, RGB, RGBA};
use rustc_hash::FxHasher; use rustc_hash::FxHasher;
use std::hash::{BuildHasherDefault, Hash}; use std::hash::{BuildHasherDefault, Hash};
@ -41,7 +41,7 @@ pub fn reduced_to_indexed(png: &PngImage) -> Option<PngImage> {
} }
let mut raw_data = Vec::with_capacity(png.data.len() / png.channels_per_pixel()); let mut raw_data = Vec::with_capacity(png.data.len() / png.channels_per_pixel());
let mut palette: Vec<_> = match png.ihdr.color_type { let palette: Vec<_> = match png.ihdr.color_type {
ColorType::Grayscale { transparent_shade } => { ColorType::Grayscale { transparent_shade } => {
let pmap = build_palette(png.data.as_gray().iter().cloned(), &mut raw_data)?; let pmap = build_palette(png.data.as_gray().iter().cloned(), &mut raw_data)?;
// Convert the Gray16 transparency to Gray8 // Convert the Gray16 transparency to Gray8
@ -81,51 +81,12 @@ pub fn reduced_to_indexed(png: &PngImage) -> Option<PngImage> {
_ => return None, _ => return None,
}; };
let mut aux_headers = png.aux_headers.clone();
if let Some(bkgd_header) = aux_headers.remove(b"bKGD") {
let bg = if png.ihdr.color_type.is_rgb() && bkgd_header.len() == 6 {
// In bKGD 16-bit values are used even for 8-bit images
Some(RGBA8::new(
bkgd_header[1],
bkgd_header[3],
bkgd_header[5],
255,
))
} else if png.ihdr.color_type.is_grayscale() && bkgd_header.len() == 2 {
Some(RGBA8::new(
bkgd_header[1],
bkgd_header[1],
bkgd_header[1],
255,
))
} else {
None
};
if let Some(bg) = bg {
let idx = palette.iter().position(|&px| px == bg).or_else(|| {
if palette.len() < 256 {
palette.push(bg);
Some(palette.len() - 1)
} else {
None // No space in palette to store the bg as an index
}
})?;
aux_headers.insert(*b"bKGD", vec![idx as u8]);
}
}
if let Some(sbit_header) = png.aux_headers.get(b"sBIT") {
// Some programs save the sBIT header as RGB even if the image is RGBA.
aux_headers.insert(*b"sBIT", sbit_header.iter().cloned().take(3).collect());
}
Some(PngImage { Some(PngImage {
data: raw_data, data: raw_data,
ihdr: IhdrData { ihdr: IhdrData {
color_type: ColorType::Indexed { palette }, color_type: ColorType::Indexed { palette },
..png.ihdr ..png.ihdr
}, },
aux_headers,
}) })
} }
@ -150,18 +111,6 @@ pub fn reduced_rgb_to_grayscale(png: &PngImage) -> Option<PngImage> {
reduced.extend_from_slice(&pixel[last_color..]); reduced.extend_from_slice(&pixel[last_color..]);
} }
let mut aux_headers = png.aux_headers.clone();
if let Some(sbit_header) = png.aux_headers.get(b"sBIT") {
if let Some(&byte) = sbit_header.first() {
aux_headers.insert(*b"sBIT", vec![byte]);
}
}
if let Some(bkgd_header) = png.aux_headers.get(b"bKGD") {
if let Some(b) = bkgd_header.get(0..2) {
aux_headers.insert(*b"bKGD", b.to_owned());
}
}
let color_type = match png.ihdr.color_type { let color_type = match png.ihdr.color_type {
ColorType::RGB { transparent_color } => ColorType::Grayscale { ColorType::RGB { transparent_color } => ColorType::Grayscale {
// Copy the transparent component if it is also gray // Copy the transparent component if it is also gray
@ -178,7 +127,6 @@ pub fn reduced_rgb_to_grayscale(png: &PngImage) -> Option<PngImage> {
color_type, color_type,
..png.ihdr ..png.ihdr
}, },
aux_headers,
}) })
} }
@ -223,25 +171,11 @@ pub fn indexed_to_channels(png: &PngImage) -> Option<PngImage> {
data.extend_from_slice(&color.as_slice()[ch_start..=ch_end]); data.extend_from_slice(&color.as_slice()[ch_start..=ch_end]);
} }
// Update bKGD if it exists
let mut aux_headers = png.aux_headers.clone();
if let Some(idx) = aux_headers.remove(b"bKGD").and_then(|b| b.first().cloned()) {
if let Some(color) = palette.get(idx as usize) {
let bkgd = if is_gray {
vec![0, color.r]
} else {
vec![0, color.r, 0, color.g, 0, color.b]
};
aux_headers.insert(*b"bKGD", bkgd);
}
}
Some(PngImage { Some(PngImage {
ihdr: IhdrData { ihdr: IhdrData {
color_type, color_type,
..png.ihdr ..png.ihdr
}, },
data, data,
aux_headers,
}) })
} }

View file

@ -30,15 +30,6 @@ pub fn reduced_palette(png: &PngImage, optimize_alpha: bool) -> Option<PngImage>
} }
} }
// Update bKGD if it exists, ensuring it comes last in the palette if otherwise unused
let mut aux_headers = png.aux_headers.clone();
if let Some(idx) = aux_headers.remove(b"bKGD").and_then(|b| b.first().cloned()) {
if let Some(&color) = palette.get(idx as usize) {
let idx = add_color_to_set(color, &mut condensed, optimize_alpha);
aux_headers.insert(*b"bKGD", vec![idx]);
}
}
let data = if did_change { let data = if did_change {
// Reassign data bytes to new indices // Reassign data bytes to new indices
let byte_map = palette_map_to_byte_map(png.ihdr.bit_depth, &palette_map); let byte_map = palette_map_to_byte_map(png.ihdr.bit_depth, &palette_map);
@ -59,7 +50,6 @@ pub fn reduced_palette(png: &PngImage, optimize_alpha: bool) -> Option<PngImage>
..png.ihdr ..png.ihdr
}, },
data, data,
aux_headers,
}) })
} }
@ -145,15 +135,6 @@ pub fn sorted_palette(png: &PngImage) -> Option<PngImage> {
let mut enumerated: Vec<_> = palette.iter().enumerate().collect(); let mut enumerated: Vec<_> = palette.iter().enumerate().collect();
// If the background is the last entry in the palette we should make sure it stays last
// Otherwise an entry that's unused by the idat could prevent reduction to a lower depth
let mut aux_headers = png.aux_headers.clone();
let bkgd_idx = aux_headers.remove(b"bKGD").and_then(|b| b.first().cloned());
let bkgd_last = match bkgd_idx {
Some(idx) if idx as usize + 1 == palette.len() => enumerated.pop(),
_ => None,
};
// Sort the palette // Sort the palette
enumerated.sort_by(|a, b| { enumerated.sort_by(|a, b| {
// Sort by ascending alpha and descending luma // Sort by ascending alpha and descending luma
@ -167,10 +148,6 @@ pub fn sorted_palette(png: &PngImage) -> Option<PngImage> {
color_val(a.1).cmp(&color_val(b.1)) color_val(a.1).cmp(&color_val(b.1))
}); });
if let Some(bkgd) = bkgd_last {
enumerated.push(bkgd);
}
// Extract the new palette and determine if anything changed // Extract the new palette and determine if anything changed
let (old_map, palette): (Vec<_>, Vec<RGBA8>) = enumerated.into_iter().unzip(); let (old_map, palette): (Vec<_>, Vec<RGBA8>) = enumerated.into_iter().unzip();
if old_map.iter().enumerate().all(|(a, b)| a == *b) { if old_map.iter().enumerate().all(|(a, b)| a == *b) {
@ -185,17 +162,11 @@ pub fn sorted_palette(png: &PngImage) -> Option<PngImage> {
let byte_map = palette_map_to_byte_map(png.ihdr.bit_depth, &new_map); let byte_map = palette_map_to_byte_map(png.ihdr.bit_depth, &new_map);
let data = png.data.iter().map(|&b| byte_map[b as usize]).collect(); let data = png.data.iter().map(|&b| byte_map[b as usize]).collect();
// Update bKGD if it exists
if let Some(idx) = bkgd_idx.map(|idx| new_map[idx as usize]) {
aux_headers.insert(*b"bKGD", vec![idx]);
}
Some(PngImage { Some(PngImage {
ihdr: IhdrData { ihdr: IhdrData {
color_type: ColorType::Indexed { palette }, color_type: ColorType::Indexed { palette },
..png.ihdr ..png.ihdr
}, },
data, data,
aux_headers,
}) })
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

After

Width:  |  Height:  |  Size: 115 KiB

View file

@ -1,6 +1,6 @@
use indexmap::IndexSet; use indexmap::IndexSet;
use oxipng::{internal_tests::*, RowFilter}; use oxipng::internal_tests::*;
use oxipng::{InFile, OutFile}; use oxipng::*;
use std::fs::remove_file; use std::fs::remove_file;
use std::path::Path; use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
@ -37,7 +37,7 @@ fn test_it_converts(
let input = PathBuf::from(input); let input = PathBuf::from(input);
let (output, mut opts) = get_opts(&input); let (output, mut opts) = get_opts(&input);
let png = PngData::new(&input, opts.fix_errors).unwrap(); let png = PngData::new(&input, &opts).unwrap();
opts.filter = IndexSet::new(); opts.filter = IndexSet::new();
opts.filter.insert(filter); opts.filter.insert(filter);
assert_eq!(png.raw.ihdr.color_type.png_header_code(), color_type_in); assert_eq!(png.raw.ihdr.color_type.png_header_code(), color_type_in);
@ -50,7 +50,7 @@ fn test_it_converts(
let output = output.path().unwrap(); let output = output.path().unwrap();
assert!(output.exists()); assert!(output.exists());
let png = match PngData::new(output, opts.fix_errors) { let png = match PngData::new(output, &opts) {
Ok(x) => x, Ok(x) => x,
Err(x) => { Err(x) => {
remove_file(output).ok(); remove_file(output).ok();

View file

@ -1,6 +1,6 @@
use indexmap::IndexSet; use indexmap::{indexset, IndexSet};
use oxipng::{internal_tests::*, Interlacing, RowFilter}; use oxipng::internal_tests::*;
use oxipng::{InFile, OutFile}; use oxipng::*;
#[cfg(feature = "filetime")] #[cfg(feature = "filetime")]
use std::cell::RefCell; use std::cell::RefCell;
use std::fs::remove_file; use std::fs::remove_file;
@ -47,7 +47,7 @@ fn test_it_converts_callbacks<CBPRE, CBPOST>(
CBPOST: FnMut(&Path), CBPOST: FnMut(&Path),
CBPRE: FnMut(&Path), CBPRE: FnMut(&Path),
{ {
let png = PngData::new(&input, opts.fix_errors).unwrap(); let png = PngData::new(&input, &opts).unwrap();
assert_eq!(png.raw.ihdr.color_type.png_header_code(), color_type_in); assert_eq!(png.raw.ihdr.color_type.png_header_code(), color_type_in);
assert_eq!(png.raw.ihdr.bit_depth, bit_depth_in); assert_eq!(png.raw.ihdr.bit_depth, bit_depth_in);
@ -63,7 +63,7 @@ fn test_it_converts_callbacks<CBPRE, CBPOST>(
callback_post(output); callback_post(output);
let png = match PngData::new(output, opts.fix_errors) { let png = match PngData::new(output, &opts) {
Ok(x) => x, Ok(x) => x,
Err(x) => { Err(x) => {
remove_file(output).ok(); remove_file(output).ok();
@ -203,17 +203,24 @@ fn verbose_mode() {
} }
} }
fn count_chunk(png: &PngData, name: &[u8; 4]) -> usize {
png.aux_chunks
.iter()
.filter(|chunk| &chunk.name == name)
.count()
}
#[test] #[test]
fn strip_headers_list() { fn strip_headers_list() {
let input = PathBuf::from("tests/files/strip_headers_list.png"); let input = PathBuf::from("tests/files/strip_headers_list.png");
let (output, mut opts) = get_opts(&input); let (output, mut opts) = get_opts(&input);
opts.strip = Headers::Strip(vec!["iCCP".to_owned(), "tEXt".to_owned()]); opts.strip = StripChunks::Strip(indexset![*b"iCCP", *b"tEXt"]);
let png = PngData::new(&input, opts.fix_errors).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
assert!(png.raw.aux_headers.contains_key(b"tEXt")); assert_eq!(count_chunk(&png, b"tEXt"), 3);
assert!(png.raw.aux_headers.contains_key(b"iTXt")); assert_eq!(count_chunk(&png, b"iTXt"), 1);
assert!(png.raw.aux_headers.contains_key(b"iCCP")); assert_eq!(count_chunk(&png, b"iCCP"), 1);
match oxipng::optimize(&InFile::Path(input), &output, &opts) { match oxipng::optimize(&InFile::Path(input), &output, &opts) {
Ok(_) => (), Ok(_) => (),
@ -222,7 +229,7 @@ fn strip_headers_list() {
let output = output.path().unwrap(); let output = output.path().unwrap();
assert!(output.exists()); assert!(output.exists());
let png = match PngData::new(output, opts.fix_errors) { let png = match PngData::new(output, &opts) {
Ok(x) => x, Ok(x) => x,
Err(x) => { Err(x) => {
remove_file(output).ok(); remove_file(output).ok();
@ -230,9 +237,9 @@ fn strip_headers_list() {
} }
}; };
assert!(!png.raw.aux_headers.contains_key(b"tEXt")); assert_eq!(count_chunk(&png, b"tEXt"), 0);
assert!(png.raw.aux_headers.contains_key(b"iTXt")); assert_eq!(count_chunk(&png, b"iTXt"), 1);
assert!(!png.raw.aux_headers.contains_key(b"iCCP")); assert_eq!(count_chunk(&png, b"iCCP"), 0);
remove_file(output).ok(); remove_file(output).ok();
} }
@ -241,13 +248,13 @@ fn strip_headers_list() {
fn strip_headers_safe() { fn strip_headers_safe() {
let input = PathBuf::from("tests/files/strip_headers_safe.png"); let input = PathBuf::from("tests/files/strip_headers_safe.png");
let (output, mut opts) = get_opts(&input); let (output, mut opts) = get_opts(&input);
opts.strip = Headers::Safe; opts.strip = StripChunks::Safe;
let png = PngData::new(&input, opts.fix_errors).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
assert!(png.raw.aux_headers.contains_key(b"tEXt")); assert_eq!(count_chunk(&png, b"tEXt"), 3);
assert!(png.raw.aux_headers.contains_key(b"iTXt")); assert_eq!(count_chunk(&png, b"iTXt"), 1);
assert!(png.raw.aux_headers.contains_key(b"iCCP")); assert_eq!(count_chunk(&png, b"iCCP"), 1);
match oxipng::optimize(&InFile::Path(input), &output, &opts) { match oxipng::optimize(&InFile::Path(input), &output, &opts) {
Ok(_) => (), Ok(_) => (),
@ -256,7 +263,7 @@ fn strip_headers_safe() {
let output = output.path().unwrap(); let output = output.path().unwrap();
assert!(output.exists()); assert!(output.exists());
let png = match PngData::new(output, opts.fix_errors) { let png = match PngData::new(output, &opts) {
Ok(x) => x, Ok(x) => x,
Err(x) => { Err(x) => {
remove_file(output).ok(); remove_file(output).ok();
@ -264,9 +271,9 @@ fn strip_headers_safe() {
} }
}; };
assert!(!png.raw.aux_headers.contains_key(b"tEXt")); assert_eq!(count_chunk(&png, b"tEXt"), 0);
assert!(!png.raw.aux_headers.contains_key(b"iTXt")); assert_eq!(count_chunk(&png, b"iTXt"), 0);
assert!(png.raw.aux_headers.contains_key(b"sRGB")); assert_eq!(count_chunk(&png, b"sRGB"), 1);
remove_file(output).ok(); remove_file(output).ok();
} }
@ -275,13 +282,13 @@ fn strip_headers_safe() {
fn strip_headers_all() { fn strip_headers_all() {
let input = PathBuf::from("tests/files/strip_headers_all.png"); let input = PathBuf::from("tests/files/strip_headers_all.png");
let (output, mut opts) = get_opts(&input); let (output, mut opts) = get_opts(&input);
opts.strip = Headers::All; opts.strip = StripChunks::All;
let png = PngData::new(&input, opts.fix_errors).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
assert!(png.raw.aux_headers.contains_key(b"tEXt")); assert_eq!(count_chunk(&png, b"tEXt"), 3);
assert!(png.raw.aux_headers.contains_key(b"iTXt")); assert_eq!(count_chunk(&png, b"iTXt"), 1);
assert!(png.raw.aux_headers.contains_key(b"iCCP")); assert_eq!(count_chunk(&png, b"iCCP"), 1);
match oxipng::optimize(&InFile::Path(input), &output, &opts) { match oxipng::optimize(&InFile::Path(input), &output, &opts) {
Ok(_) => (), Ok(_) => (),
@ -290,7 +297,7 @@ fn strip_headers_all() {
let output = output.path().unwrap(); let output = output.path().unwrap();
assert!(output.exists()); assert!(output.exists());
let png = match PngData::new(output, opts.fix_errors) { let png = match PngData::new(output, &opts) {
Ok(x) => x, Ok(x) => x,
Err(x) => { Err(x) => {
remove_file(output).ok(); remove_file(output).ok();
@ -298,9 +305,9 @@ fn strip_headers_all() {
} }
}; };
assert!(!png.raw.aux_headers.contains_key(b"tEXt")); assert_eq!(count_chunk(&png, b"tEXt"), 0);
assert!(!png.raw.aux_headers.contains_key(b"iTXt")); assert_eq!(count_chunk(&png, b"iTXt"), 0);
assert!(!png.raw.aux_headers.contains_key(b"iCCP")); assert_eq!(count_chunk(&png, b"iCCP"), 0);
remove_file(output).ok(); remove_file(output).ok();
} }
@ -309,13 +316,13 @@ fn strip_headers_all() {
fn strip_headers_none() { fn strip_headers_none() {
let input = PathBuf::from("tests/files/strip_headers_none.png"); let input = PathBuf::from("tests/files/strip_headers_none.png");
let (output, mut opts) = get_opts(&input); let (output, mut opts) = get_opts(&input);
opts.strip = Headers::None; opts.strip = StripChunks::None;
let png = PngData::new(&input, opts.fix_errors).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
assert!(png.raw.aux_headers.contains_key(b"tEXt")); assert_eq!(count_chunk(&png, b"tEXt"), 3);
assert!(png.raw.aux_headers.contains_key(b"iTXt")); assert_eq!(count_chunk(&png, b"iTXt"), 1);
assert!(png.raw.aux_headers.contains_key(b"iCCP")); assert_eq!(count_chunk(&png, b"iCCP"), 1);
match oxipng::optimize(&InFile::Path(input), &output, &opts) { match oxipng::optimize(&InFile::Path(input), &output, &opts) {
Ok(_) => (), Ok(_) => (),
@ -324,7 +331,7 @@ fn strip_headers_none() {
let output = output.path().unwrap(); let output = output.path().unwrap();
assert!(output.exists()); assert!(output.exists());
let png = match PngData::new(output, opts.fix_errors) { let png = match PngData::new(output, &opts) {
Ok(x) => x, Ok(x) => x,
Err(x) => { Err(x) => {
remove_file(output).ok(); remove_file(output).ok();
@ -332,9 +339,9 @@ fn strip_headers_none() {
} }
}; };
assert!(png.raw.aux_headers.contains_key(b"tEXt")); assert_eq!(count_chunk(&png, b"tEXt"), 3);
assert!(png.raw.aux_headers.contains_key(b"iTXt")); assert_eq!(count_chunk(&png, b"iTXt"), 1);
assert!(png.raw.aux_headers.contains_key(b"iCCP")); assert_eq!(count_chunk(&png, b"iCCP"), 1);
remove_file(output).ok(); remove_file(output).ok();
} }
@ -345,7 +352,7 @@ fn interlacing_0_to_1() {
let (output, mut opts) = get_opts(&input); let (output, mut opts) = get_opts(&input);
opts.interlace = Some(Interlacing::Adam7); opts.interlace = Some(Interlacing::Adam7);
let png = PngData::new(&input, opts.fix_errors).unwrap(); let png = PngData::new(&input, &opts).unwrap();
assert_eq!(png.raw.ihdr.interlaced, Interlacing::None); assert_eq!(png.raw.ihdr.interlaced, Interlacing::None);
@ -356,7 +363,7 @@ fn interlacing_0_to_1() {
let output = output.path().unwrap(); let output = output.path().unwrap();
assert!(output.exists()); assert!(output.exists());
let png = match PngData::new(output, opts.fix_errors) { let png = match PngData::new(output, &opts) {
Ok(x) => x, Ok(x) => x,
Err(x) => { Err(x) => {
remove_file(output).ok(); remove_file(output).ok();
@ -375,7 +382,7 @@ fn interlacing_1_to_0() {
let (output, mut opts) = get_opts(&input); let (output, mut opts) = get_opts(&input);
opts.interlace = Some(Interlacing::None); opts.interlace = Some(Interlacing::None);
let png = PngData::new(&input, opts.fix_errors).unwrap(); let png = PngData::new(&input, &opts).unwrap();
assert_eq!(png.raw.ihdr.interlaced, Interlacing::Adam7); assert_eq!(png.raw.ihdr.interlaced, Interlacing::Adam7);
@ -386,7 +393,7 @@ fn interlacing_1_to_0() {
let output = output.path().unwrap(); let output = output.path().unwrap();
assert!(output.exists()); assert!(output.exists());
let png = match PngData::new(output, opts.fix_errors) { let png = match PngData::new(output, &opts) {
Ok(x) => x, Ok(x) => x,
Err(x) => { Err(x) => {
remove_file(output).ok(); remove_file(output).ok();
@ -405,7 +412,7 @@ fn interlacing_0_to_1_small_files() {
let (output, mut opts) = get_opts(&input); let (output, mut opts) = get_opts(&input);
opts.interlace = Some(Interlacing::Adam7); opts.interlace = Some(Interlacing::Adam7);
let png = PngData::new(&input, opts.fix_errors).unwrap(); let png = PngData::new(&input, &opts).unwrap();
assert_eq!(png.raw.ihdr.interlaced, Interlacing::None); assert_eq!(png.raw.ihdr.interlaced, Interlacing::None);
assert_eq!(png.raw.ihdr.color_type.png_header_code(), INDEXED); assert_eq!(png.raw.ihdr.color_type.png_header_code(), INDEXED);
@ -418,7 +425,7 @@ fn interlacing_0_to_1_small_files() {
let output = output.path().unwrap(); let output = output.path().unwrap();
assert!(output.exists()); assert!(output.exists());
let png = match PngData::new(output, opts.fix_errors) { let png = match PngData::new(output, &opts) {
Ok(x) => x, Ok(x) => x,
Err(x) => { Err(x) => {
remove_file(output).ok(); remove_file(output).ok();
@ -439,7 +446,7 @@ fn interlacing_1_to_0_small_files() {
let (output, mut opts) = get_opts(&input); let (output, mut opts) = get_opts(&input);
opts.interlace = Some(Interlacing::None); opts.interlace = Some(Interlacing::None);
let png = PngData::new(&input, opts.fix_errors).unwrap(); let png = PngData::new(&input, &opts).unwrap();
assert_eq!(png.raw.ihdr.interlaced, Interlacing::Adam7); assert_eq!(png.raw.ihdr.interlaced, Interlacing::Adam7);
assert_eq!(png.raw.ihdr.color_type.png_header_code(), INDEXED); assert_eq!(png.raw.ihdr.color_type.png_header_code(), INDEXED);
@ -452,7 +459,7 @@ fn interlacing_1_to_0_small_files() {
let output = output.path().unwrap(); let output = output.path().unwrap();
assert!(output.exists()); assert!(output.exists());
let png = match PngData::new(output, opts.fix_errors) { let png = match PngData::new(output, &opts) {
Ok(x) => x, Ok(x) => x,
Err(x) => { Err(x) => {
remove_file(output).ok(); remove_file(output).ok();
@ -476,7 +483,7 @@ fn interlaced_0_to_1_other_filter_mode() {
filter.insert(RowFilter::Paeth); filter.insert(RowFilter::Paeth);
opts.filter = filter; opts.filter = filter;
let png = PngData::new(&input, opts.fix_errors).unwrap(); let png = PngData::new(&input, &opts).unwrap();
assert_eq!(png.raw.ihdr.interlaced, Interlacing::None); assert_eq!(png.raw.ihdr.interlaced, Interlacing::None);
@ -487,7 +494,7 @@ fn interlaced_0_to_1_other_filter_mode() {
let output = output.path().unwrap(); let output = output.path().unwrap();
assert!(output.exists()); assert!(output.exists());
let png = match PngData::new(output, opts.fix_errors) { let png = match PngData::new(output, &opts) {
Ok(x) => x, Ok(x) => x,
Err(x) => { Err(x) => {
remove_file(output).ok(); remove_file(output).ok();
@ -570,7 +577,7 @@ fn fix_errors() {
let (output, mut opts) = get_opts(&input); let (output, mut opts) = get_opts(&input);
opts.fix_errors = true; opts.fix_errors = true;
let png = PngData::new(&input, opts.fix_errors).unwrap(); let png = PngData::new(&input, &opts).unwrap();
assert_eq!(png.raw.ihdr.color_type.png_header_code(), RGBA); assert_eq!(png.raw.ihdr.color_type.png_header_code(), RGBA);
assert_eq!(png.raw.ihdr.bit_depth, BitDepth::Eight); assert_eq!(png.raw.ihdr.bit_depth, BitDepth::Eight);
@ -582,7 +589,7 @@ fn fix_errors() {
let output = output.path().unwrap(); let output = output.path().unwrap();
assert!(output.exists()); assert!(output.exists());
let png = match PngData::new(output, false) { let png = match PngData::new(output, &Options::default()) {
Ok(x) => x, Ok(x) => x,
Err(x) => { Err(x) => {
remove_file(output).ok(); remove_file(output).ok();

View file

@ -1,6 +1,6 @@
use indexmap::IndexSet; use indexmap::IndexSet;
use oxipng::{internal_tests::*, Interlacing, RowFilter}; use oxipng::internal_tests::*;
use oxipng::{InFile, OutFile}; use oxipng::*;
use std::fs::remove_file; use std::fs::remove_file;
use std::path::Path; use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
@ -35,7 +35,7 @@ fn test_it_converts(
) { ) {
let input = PathBuf::from(input); let input = PathBuf::from(input);
let (output, opts) = get_opts(&input); let (output, opts) = get_opts(&input);
let png = PngData::new(&input, opts.fix_errors).unwrap(); let png = PngData::new(&input, &opts).unwrap();
assert_eq!(png.raw.ihdr.color_type.png_header_code(), color_type_in); assert_eq!(png.raw.ihdr.color_type.png_header_code(), color_type_in);
assert_eq!(png.raw.ihdr.bit_depth, bit_depth_in); assert_eq!(png.raw.ihdr.bit_depth, bit_depth_in);
@ -48,7 +48,7 @@ fn test_it_converts(
let output = output.path().unwrap(); let output = output.path().unwrap();
assert!(output.exists()); assert!(output.exists());
let png = match PngData::new(output, opts.fix_errors) { let png = match PngData::new(output, &opts) {
Ok(x) => x, Ok(x) => x,
Err(x) => { Err(x) => {
remove_file(output).ok(); remove_file(output).ok();

View file

@ -1,6 +1,6 @@
use indexmap::IndexSet; use indexmap::IndexSet;
use oxipng::{internal_tests::*, Interlacing, RowFilter}; use oxipng::internal_tests::*;
use oxipng::{InFile, OutFile}; use oxipng::*;
use std::fs::remove_file; use std::fs::remove_file;
use std::path::Path; use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
@ -33,7 +33,7 @@ fn test_it_converts(
) { ) {
let input = PathBuf::from(input); let input = PathBuf::from(input);
let (output, mut opts) = get_opts(&input); let (output, mut opts) = get_opts(&input);
let png = PngData::new(&input, opts.fix_errors).unwrap(); let png = PngData::new(&input, &opts).unwrap();
opts.interlace = Some(interlace); opts.interlace = Some(interlace);
assert_eq!(png.raw.ihdr.color_type.png_header_code(), color_type_in); assert_eq!(png.raw.ihdr.color_type.png_header_code(), color_type_in);
assert_eq!(png.raw.ihdr.bit_depth, bit_depth_in); assert_eq!(png.raw.ihdr.bit_depth, bit_depth_in);
@ -53,7 +53,7 @@ fn test_it_converts(
let output = output.path().unwrap(); let output = output.path().unwrap();
assert!(output.exists()); assert!(output.exists());
let png = match PngData::new(output, opts.fix_errors) { let png = match PngData::new(output, &opts) {
Ok(x) => x, Ok(x) => x,
Err(x) => { Err(x) => {
remove_file(output).ok(); remove_file(output).ok();

View file

@ -1,6 +1,4 @@
use oxipng::Headers; use oxipng::*;
use oxipng::OutFile;
use std::default::Default;
use std::fs; use std::fs;
use std::fs::File; use std::fs::File;
use std::io::prelude::*; use std::io::prelude::*;
@ -11,9 +9,7 @@ fn optimize_from_memory() {
let mut in_file_buf: Vec<u8> = Vec::new(); let mut in_file_buf: Vec<u8> = Vec::new();
in_file.read_to_end(&mut in_file_buf).unwrap(); in_file.read_to_end(&mut in_file_buf).unwrap();
let opts: oxipng::Options = Default::default(); let result = oxipng::optimize_from_memory(&in_file_buf, &Options::default());
let result = oxipng::optimize_from_memory(&in_file_buf, &opts);
assert!(result.is_ok()); assert!(result.is_ok());
} }
@ -23,9 +19,7 @@ fn optimize_from_memory_corrupted() {
let mut in_file_buf: Vec<u8> = Vec::new(); let mut in_file_buf: Vec<u8> = Vec::new();
in_file.read_to_end(&mut in_file_buf).unwrap(); in_file.read_to_end(&mut in_file_buf).unwrap();
let opts: oxipng::Options = Default::default(); let result = oxipng::optimize_from_memory(&in_file_buf, &Options::default());
let result = oxipng::optimize_from_memory(&in_file_buf, &opts);
assert!(result.is_err()); assert!(result.is_err());
} }
@ -35,44 +29,36 @@ fn optimize_from_memory_apng() {
let mut in_file_buf: Vec<u8> = Vec::new(); let mut in_file_buf: Vec<u8> = Vec::new();
in_file.read_to_end(&mut in_file_buf).unwrap(); in_file.read_to_end(&mut in_file_buf).unwrap();
let opts: oxipng::Options = Default::default(); let result = oxipng::optimize_from_memory(&in_file_buf, &Options::default());
let result = oxipng::optimize_from_memory(&in_file_buf, &opts);
assert!(result.is_err()); assert!(result.is_err());
} }
#[test] #[test]
fn optimize() { fn optimize() {
let opts: oxipng::Options = Default::default();
let result = oxipng::optimize( let result = oxipng::optimize(
&"tests/files/fully_optimized.png".into(), &"tests/files/fully_optimized.png".into(),
&OutFile::Path(None), &OutFile::Path(None),
&opts, &Options::default(),
); );
assert!(result.is_ok()); assert!(result.is_ok());
} }
#[test] #[test]
fn optimize_corrupted() { fn optimize_corrupted() {
let opts: oxipng::Options = Default::default();
let result = oxipng::optimize( let result = oxipng::optimize(
&"tests/files/corrupted_header.png".into(), &"tests/files/corrupted_header.png".into(),
&OutFile::Path(None), &OutFile::Path(None),
&opts, &Options::default(),
); );
assert!(result.is_err()); assert!(result.is_err());
} }
#[test] #[test]
fn optimize_apng() { fn optimize_apng() {
let opts: oxipng::Options = Default::default();
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),
&opts, &Options::default(),
); );
assert!(result.is_err()); assert!(result.is_err());
} }
@ -80,12 +66,12 @@ fn optimize_apng() {
#[test] #[test]
fn optimize_srgb_icc() { fn optimize_srgb_icc() {
let file = fs::read("tests/files/badsrgb.png").unwrap(); let file = fs::read("tests/files/badsrgb.png").unwrap();
let mut opts: oxipng::Options = Default::default(); let mut opts = Options::default();
let result = oxipng::optimize_from_memory(&file, &opts); let result = oxipng::optimize_from_memory(&file, &opts);
assert!(result.unwrap().len() > 1000); assert!(result.unwrap().len() > 1000);
opts.strip = Headers::Safe; opts.strip = StripChunks::Safe;
let result = oxipng::optimize_from_memory(&file, &opts); let result = oxipng::optimize_from_memory(&file, &opts);
assert!(result.unwrap().len() < 1000); assert!(result.unwrap().len() < 1000);
} }

View file

@ -16,11 +16,11 @@ fn test_it_converts(input: &str) {
let opts = get_opts(); let opts = get_opts();
let original_data = PngData::read_file(&PathBuf::from(input)).unwrap(); let original_data = PngData::read_file(&PathBuf::from(input)).unwrap();
let png = PngData::from_slice(&original_data, opts.fix_errors).unwrap(); let image = PngData::from_slice(&original_data, &opts).unwrap();
let png = Arc::try_unwrap(png.raw).unwrap(); let png = Arc::try_unwrap(image.raw).unwrap();
let num_headers = png.aux_headers.len(); let num_chunks = image.aux_chunks.len();
assert!(num_headers > 0); assert!(num_chunks > 0);
let mut raw = RawImage::new( let mut raw = RawImage::new(
png.ihdr.width, png.ihdr.width,
@ -31,14 +31,14 @@ fn test_it_converts(input: &str) {
) )
.unwrap(); .unwrap();
for (chunk_type, data) in png.aux_headers { for chunk in image.aux_chunks {
raw.add_png_chunk(chunk_type, data); raw.add_png_chunk(chunk.name, chunk.data);
} }
let output = raw.create_optimized_png(&opts).unwrap(); let output = raw.create_optimized_png(&opts).unwrap();
let new = PngData::from_slice(&output, opts.fix_errors).unwrap(); let new = PngData::from_slice(&output, &opts).unwrap();
assert!(new.raw.aux_headers.len() == num_headers); assert!(new.aux_chunks.len() == num_chunks);
#[cfg(feature = "sanity-checks")] #[cfg(feature = "sanity-checks")]
assert!(validate_output(&output, &original_data)); assert!(validate_output(&output, &original_data));

View file

@ -1,6 +1,6 @@
use indexmap::IndexSet; use indexmap::IndexSet;
use oxipng::{internal_tests::*, Interlacing, RowFilter}; use oxipng::internal_tests::*;
use oxipng::{InFile, OutFile}; use oxipng::*;
use std::fs::remove_file; use std::fs::remove_file;
use std::path::Path; use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
@ -37,7 +37,7 @@ fn test_it_converts(
let input = PathBuf::from(input); let input = PathBuf::from(input);
let (output, mut opts) = get_opts(&input); let (output, mut opts) = get_opts(&input);
opts.optimize_alpha = optimize_alpha; opts.optimize_alpha = optimize_alpha;
let png = PngData::new(&input, opts.fix_errors).unwrap(); let png = PngData::new(&input, &opts).unwrap();
assert_eq!(png.raw.ihdr.color_type.png_header_code(), color_type_in); assert_eq!(png.raw.ihdr.color_type.png_header_code(), color_type_in);
assert_eq!(png.raw.ihdr.bit_depth, bit_depth_in, "test file is broken"); assert_eq!(png.raw.ihdr.bit_depth, bit_depth_in, "test file is broken");
@ -50,7 +50,7 @@ fn test_it_converts(
let output = output.path().unwrap(); let output = output.path().unwrap();
assert!(output.exists()); assert!(output.exists());
let png = match PngData::new(output, opts.fix_errors) { let png = match PngData::new(output, &opts) {
Ok(x) => x, Ok(x) => x,
Err(x) => { Err(x) => {
remove_file(output).ok(); remove_file(output).ok();
@ -933,7 +933,7 @@ fn palette_should_be_reduced_with_dupes() {
let input = PathBuf::from("tests/files/palette_should_be_reduced_with_dupes.png"); let input = PathBuf::from("tests/files/palette_should_be_reduced_with_dupes.png");
let (output, opts) = get_opts(&input); let (output, opts) = get_opts(&input);
let png = PngData::new(&input, opts.fix_errors).unwrap(); let png = PngData::new(&input, &opts).unwrap();
assert_eq!(png.raw.ihdr.color_type.png_header_code(), INDEXED); assert_eq!(png.raw.ihdr.color_type.png_header_code(), INDEXED);
assert_eq!(png.raw.ihdr.bit_depth, BitDepth::Eight); assert_eq!(png.raw.ihdr.bit_depth, BitDepth::Eight);
@ -948,7 +948,7 @@ fn palette_should_be_reduced_with_dupes() {
let output = output.path().unwrap(); let output = output.path().unwrap();
assert!(output.exists()); assert!(output.exists());
let png = match PngData::new(output, opts.fix_errors) { let png = match PngData::new(output, &opts) {
Ok(x) => x, Ok(x) => x,
Err(x) => { Err(x) => {
remove_file(output).ok(); remove_file(output).ok();
@ -970,7 +970,7 @@ fn palette_should_be_reduced_with_unused() {
let input = PathBuf::from("tests/files/palette_should_be_reduced_with_unused.png"); let input = PathBuf::from("tests/files/palette_should_be_reduced_with_unused.png");
let (output, opts) = get_opts(&input); let (output, opts) = get_opts(&input);
let png = PngData::new(&input, opts.fix_errors).unwrap(); let png = PngData::new(&input, &opts).unwrap();
assert_eq!(png.raw.ihdr.color_type.png_header_code(), INDEXED); assert_eq!(png.raw.ihdr.color_type.png_header_code(), INDEXED);
assert_eq!(png.raw.ihdr.bit_depth, BitDepth::Eight); assert_eq!(png.raw.ihdr.bit_depth, BitDepth::Eight);
@ -985,7 +985,7 @@ fn palette_should_be_reduced_with_unused() {
let output = output.path().unwrap(); let output = output.path().unwrap();
assert!(output.exists()); assert!(output.exists());
let png = match PngData::new(output, opts.fix_errors) { let png = match PngData::new(output, &opts) {
Ok(x) => x, Ok(x) => x,
Err(x) => { Err(x) => {
remove_file(output).ok(); remove_file(output).ok();
@ -1007,7 +1007,7 @@ fn palette_should_be_reduced_with_both() {
let input = PathBuf::from("tests/files/palette_should_be_reduced_with_both.png"); let input = PathBuf::from("tests/files/palette_should_be_reduced_with_both.png");
let (output, opts) = get_opts(&input); let (output, opts) = get_opts(&input);
let png = PngData::new(&input, opts.fix_errors).unwrap(); let png = PngData::new(&input, &opts).unwrap();
assert_eq!(png.raw.ihdr.color_type.png_header_code(), INDEXED); assert_eq!(png.raw.ihdr.color_type.png_header_code(), INDEXED);
assert_eq!(png.raw.ihdr.bit_depth, BitDepth::Eight); assert_eq!(png.raw.ihdr.bit_depth, BitDepth::Eight);
@ -1022,7 +1022,7 @@ fn palette_should_be_reduced_with_both() {
let output = output.path().unwrap(); let output = output.path().unwrap();
assert!(output.exists()); assert!(output.exists());
let png = match PngData::new(output, opts.fix_errors) { let png = match PngData::new(output, &opts) {
Ok(x) => x, Ok(x) => x,
Err(x) => { Err(x) => {
remove_file(output).ok(); remove_file(output).ok();

View file

@ -1,6 +1,6 @@
use indexmap::IndexSet; use indexmap::IndexSet;
use oxipng::{internal_tests::*, Interlacing, RowFilter}; use oxipng::internal_tests::*;
use oxipng::{InFile, OutFile}; use oxipng::*;
use std::fs::remove_file; use std::fs::remove_file;
use std::path::Path; use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
@ -36,7 +36,7 @@ fn test_it_converts(
) { ) {
let input = PathBuf::from(input); let input = PathBuf::from(input);
let (output, opts) = custom.unwrap_or_else(|| get_opts(&input)); let (output, opts) = custom.unwrap_or_else(|| get_opts(&input));
let png = PngData::new(&input, opts.fix_errors).unwrap(); let png = PngData::new(&input, &opts).unwrap();
assert_eq!( assert_eq!(
png.raw.ihdr.color_type.png_header_code(), png.raw.ihdr.color_type.png_header_code(),
@ -52,7 +52,7 @@ fn test_it_converts(
let output = output.path().unwrap(); let output = output.path().unwrap();
assert!(output.exists()); assert!(output.exists());
let png = match PngData::new(output, opts.fix_errors) { let png = match PngData::new(output, &opts) {
Ok(x) => x, Ok(x) => x,
Err(x) => { Err(x) => {
remove_file(output).ok(); remove_file(output).ok();
@ -70,13 +70,7 @@ fn test_it_converts(
"optimized to wrong bit depth" "optimized to wrong bit depth"
); );
if let ColorType::Indexed { palette } = &png.raw.ihdr.color_type { if let ColorType::Indexed { palette } = &png.raw.ihdr.color_type {
let mut max_palette_size = 1 << (png.raw.ihdr.bit_depth as u8); assert!(palette.len() <= 1 << (png.raw.ihdr.bit_depth as u8));
// Ensure bKGD color is valid
if let Some(&idx) = png.raw.aux_headers.get(b"bKGD").and_then(|b| b.first()) {
assert!(palette.len() > idx as usize);
max_palette_size = max_palette_size.max(idx as usize + 1);
}
assert!(palette.len() <= max_palette_size);
} }
remove_file(output).ok(); remove_file(output).ok();
@ -100,7 +94,7 @@ fn issue_42() {
let (output, mut opts) = get_opts(&input); let (output, mut opts) = get_opts(&input);
opts.interlace = Some(Interlacing::Adam7); opts.interlace = Some(Interlacing::Adam7);
let png = PngData::new(&input, opts.fix_errors).unwrap(); let png = PngData::new(&input, &opts).unwrap();
assert_eq!(png.raw.ihdr.interlaced, Interlacing::None); assert_eq!(png.raw.ihdr.interlaced, Interlacing::None);
assert_eq!(png.raw.ihdr.color_type, ColorType::GrayscaleAlpha); assert_eq!(png.raw.ihdr.color_type, ColorType::GrayscaleAlpha);
@ -113,7 +107,7 @@ fn issue_42() {
let output = output.path().unwrap(); let output = output.path().unwrap();
assert!(output.exists()); assert!(output.exists());
let png = match PngData::new(output, opts.fix_errors) { let png = match PngData::new(output, &opts) {
Ok(x) => x, Ok(x) => x,
Err(x) => { Err(x) => {
remove_file(output).ok(); remove_file(output).ok();
@ -231,7 +225,7 @@ fn issue_59() {
None, None,
RGBA, RGBA,
BitDepth::Eight, BitDepth::Eight,
RGBA, INDEXED,
BitDepth::Eight, BitDepth::Eight,
); );
} }

View file

@ -1,6 +1,6 @@
use indexmap::IndexSet; use indexmap::IndexSet;
use oxipng::{internal_tests::*, RowFilter}; use oxipng::internal_tests::*;
use oxipng::{InFile, OutFile}; use oxipng::*;
use std::fs::remove_file; use std::fs::remove_file;
use std::path::Path; use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
@ -36,7 +36,7 @@ fn test_it_converts(
let input = PathBuf::from(input); let input = PathBuf::from(input);
let (output, mut opts) = get_opts(&input); let (output, mut opts) = get_opts(&input);
let png = PngData::new(&input, opts.fix_errors).unwrap(); let png = PngData::new(&input, &opts).unwrap();
opts.filter = IndexSet::new(); opts.filter = IndexSet::new();
opts.filter.insert(filter); opts.filter.insert(filter);
assert_eq!(png.raw.ihdr.color_type.png_header_code(), color_type_in); assert_eq!(png.raw.ihdr.color_type.png_header_code(), color_type_in);
@ -49,7 +49,7 @@ fn test_it_converts(
let output = output.path().unwrap(); let output = output.path().unwrap();
assert!(output.exists()); assert!(output.exists());
let png = match PngData::new(output, opts.fix_errors) { let png = match PngData::new(output, &opts) {
Ok(x) => x, Ok(x) => x,
Err(x) => { Err(x) => {
remove_file(output).ok(); remove_file(output).ok();