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 test;
use oxipng::internal_tests::*;
use oxipng::*;
use std::path::PathBuf;
use test::Bencher;
use oxipng::internal_tests::*;
#[bench]
fn deflate_16_bits(b: &mut Bencher) {
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(|| {
let min = AtomicMin::new(None);
@ -22,7 +22,7 @@ fn deflate_16_bits(b: &mut Bencher) {
#[bench]
fn deflate_8_bits(b: &mut Bencher) {
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(|| {
let min = AtomicMin::new(None);
@ -35,7 +35,7 @@ fn deflate_4_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from(
"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(|| {
let min = AtomicMin::new(None);
@ -48,7 +48,7 @@ fn deflate_2_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from(
"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(|| {
let min = AtomicMin::new(None);
@ -61,7 +61,7 @@ fn deflate_1_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from(
"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(|| {
let min = AtomicMin::new(None);
@ -72,7 +72,7 @@ fn deflate_1_bits(b: &mut Bencher) {
#[bench]
fn inflate_generic(b: &mut Bencher) {
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()));
}

View file

@ -3,14 +3,15 @@
extern crate oxipng;
extern crate test;
use oxipng::{internal_tests::*, RowFilter};
use oxipng::internal_tests::*;
use oxipng::*;
use std::path::PathBuf;
use test::Bencher;
#[bench]
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 png = PngData::new(&input, false).unwrap();
let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| {
png.raw.filter_image(RowFilter::None, false);
@ -20,7 +21,7 @@ fn filters_16_bits_filter_0(b: &mut Bencher) {
#[bench]
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 png = PngData::new(&input, false).unwrap();
let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| {
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(
"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.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(
"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.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(
"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.filter_image(RowFilter::None, false);
@ -66,7 +67,7 @@ fn filters_1_bits_filter_0(b: &mut Bencher) {
#[bench]
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 png = PngData::new(&input, false).unwrap();
let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| {
png.raw.filter_image(RowFilter::Sub, false);
@ -76,7 +77,7 @@ fn filters_16_bits_filter_1(b: &mut Bencher) {
#[bench]
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 png = PngData::new(&input, false).unwrap();
let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| {
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(
"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.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(
"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.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(
"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.filter_image(RowFilter::Sub, false);
@ -122,7 +123,7 @@ fn filters_1_bits_filter_1(b: &mut Bencher) {
#[bench]
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 png = PngData::new(&input, false).unwrap();
let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| {
png.raw.filter_image(RowFilter::Up, false);
@ -132,7 +133,7 @@ fn filters_16_bits_filter_2(b: &mut Bencher) {
#[bench]
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 png = PngData::new(&input, false).unwrap();
let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| {
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(
"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.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(
"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.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(
"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.filter_image(RowFilter::Up, false);
@ -178,7 +179,7 @@ fn filters_1_bits_filter_2(b: &mut Bencher) {
#[bench]
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 png = PngData::new(&input, false).unwrap();
let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| {
png.raw.filter_image(RowFilter::Average, false);
@ -188,7 +189,7 @@ fn filters_16_bits_filter_3(b: &mut Bencher) {
#[bench]
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 png = PngData::new(&input, false).unwrap();
let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| {
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(
"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.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(
"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.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(
"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.filter_image(RowFilter::Average, false);
@ -234,7 +235,7 @@ fn filters_1_bits_filter_3(b: &mut Bencher) {
#[bench]
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 png = PngData::new(&input, false).unwrap();
let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| {
png.raw.filter_image(RowFilter::Paeth, false);
@ -244,7 +245,7 @@ fn filters_16_bits_filter_4(b: &mut Bencher) {
#[bench]
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 png = PngData::new(&input, false).unwrap();
let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| {
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(
"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.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(
"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.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(
"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.filter_image(RowFilter::Paeth, false);
@ -290,7 +291,7 @@ fn filters_1_bits_filter_4(b: &mut Bencher) {
#[bench]
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 png = PngData::new(&input, false).unwrap();
let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| {
png.raw.filter_image(RowFilter::MinSum, false);
@ -300,7 +301,7 @@ fn filters_16_bits_filter_5(b: &mut Bencher) {
#[bench]
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 png = PngData::new(&input, false).unwrap();
let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| {
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(
"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.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(
"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.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(
"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.filter_image(RowFilter::MinSum, false);

View file

@ -3,14 +3,15 @@
extern crate oxipng;
extern crate test;
use oxipng::{internal_tests::*, Interlacing};
use oxipng::internal_tests::*;
use oxipng::*;
use std::path::PathBuf;
use test::Bencher;
#[bench]
fn interlacing_16_bits(b: &mut Bencher) {
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));
}
@ -18,7 +19,7 @@ fn interlacing_16_bits(b: &mut Bencher) {
#[bench]
fn interlacing_8_bits(b: &mut Bencher) {
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));
}
@ -28,7 +29,7 @@ fn interlacing_4_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from(
"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));
}
@ -38,7 +39,7 @@ fn interlacing_2_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from(
"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));
}
@ -48,7 +49,7 @@ fn interlacing_1_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from(
"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));
}
@ -58,7 +59,7 @@ fn deinterlacing_16_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from(
"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));
}
@ -68,7 +69,7 @@ fn deinterlacing_8_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from(
"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));
}
@ -78,7 +79,7 @@ fn deinterlacing_4_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from(
"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));
}
@ -88,7 +89,7 @@ fn deinterlacing_2_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from(
"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));
}
@ -98,7 +99,7 @@ fn deinterlacing_1_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from(
"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));
}

View file

@ -4,13 +4,14 @@ extern crate oxipng;
extern crate test;
use oxipng::internal_tests::*;
use oxipng::*;
use std::path::PathBuf;
use test::Bencher;
#[bench]
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 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));
}
@ -20,7 +21,7 @@ fn reductions_8_to_4_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from(
"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));
}
@ -30,7 +31,7 @@ fn reductions_8_to_2_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from(
"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));
}
@ -40,7 +41,7 @@ fn reductions_8_to_1_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from(
"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));
}
@ -50,7 +51,7 @@ fn reductions_4_to_2_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from(
"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));
}
@ -60,7 +61,7 @@ fn reductions_4_to_1_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from(
"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));
}
@ -70,7 +71,7 @@ fn reductions_2_to_1_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from(
"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));
}
@ -80,7 +81,7 @@ fn reductions_grayscale_8_to_4_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from(
"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));
}
@ -90,7 +91,7 @@ fn reductions_grayscale_8_to_2_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from(
"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));
}
@ -100,7 +101,7 @@ fn reductions_grayscale_8_to_1_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from(
"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));
}
@ -110,7 +111,7 @@ fn reductions_grayscale_4_to_2_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from(
"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));
}
@ -120,7 +121,7 @@ fn reductions_grayscale_4_to_1_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from(
"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));
}
@ -130,7 +131,7 @@ fn reductions_grayscale_2_to_1_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from(
"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));
}
@ -138,7 +139,7 @@ fn reductions_grayscale_2_to_1_bits(b: &mut Bencher) {
#[bench]
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 png = PngData::new(&input, false).unwrap();
let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| alpha::reduced_alpha_channel(&png.raw, false));
}
@ -146,7 +147,7 @@ fn reductions_rgba_to_rgb_16(b: &mut Bencher) {
#[bench]
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 png = PngData::new(&input, false).unwrap();
let png = PngData::new(&input, &Options::default()).unwrap();
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(
"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));
}
@ -166,7 +167,7 @@ fn reductions_rgba_to_grayscale_alpha_8(b: &mut Bencher) {
let input = test::black_box(PathBuf::from(
"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));
}
@ -176,7 +177,7 @@ fn reductions_rgba_to_grayscale_16(b: &mut Bencher) {
let input = test::black_box(PathBuf::from(
"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(|| {
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(
"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(|| {
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(
"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));
}
@ -210,7 +211,7 @@ fn reductions_rgb_to_grayscale_16(b: &mut Bencher) {
#[bench]
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 png = PngData::new(&input, false).unwrap();
let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| color::reduced_rgb_to_grayscale(&png.raw));
}
@ -218,7 +219,7 @@ fn reductions_rgb_to_grayscale_8(b: &mut Bencher) {
#[bench]
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 png = PngData::new(&input, false).unwrap();
let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| color::reduced_to_indexed(&png.raw));
}
@ -226,7 +227,7 @@ fn reductions_rgba_to_palette_8(b: &mut Bencher) {
#[bench]
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 png = PngData::new(&input, false).unwrap();
let png = PngData::new(&input, &Options::default()).unwrap();
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(
"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));
}
@ -246,7 +247,7 @@ fn reductions_palette_8_to_grayscale_8(b: &mut Bencher) {
let input = test::black_box(PathBuf::from(
"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));
}
@ -256,7 +257,7 @@ fn reductions_palette_duplicate_reduction(b: &mut Bencher) {
let input = test::black_box(PathBuf::from(
"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));
}
@ -266,7 +267,7 @@ fn reductions_palette_unused_reduction(b: &mut Bencher) {
let input = test::black_box(PathBuf::from(
"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));
}
@ -276,7 +277,7 @@ fn reductions_palette_full_reduction(b: &mut Bencher) {
let input = test::black_box(PathBuf::from(
"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));
}
@ -286,7 +287,7 @@ fn reductions_palette_sort(b: &mut Bencher) {
let input = test::black_box(PathBuf::from(
"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));
}
@ -294,7 +295,7 @@ fn reductions_palette_sort(b: &mut Bencher) {
#[bench]
fn reductions_alpha(b: &mut Bencher) {
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));
}

View file

@ -3,14 +3,15 @@
extern crate oxipng;
extern crate test;
use oxipng::{internal_tests::*, RowFilter};
use oxipng::internal_tests::*;
use oxipng::*;
use std::path::PathBuf;
use test::Bencher;
#[bench]
fn filters_minsum(b: &mut Bencher) {
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.filter_image(RowFilter::MinSum, false);
@ -20,7 +21,7 @@ fn filters_minsum(b: &mut Bencher) {
#[bench]
fn filters_entropy(b: &mut Bencher) {
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.filter_image(RowFilter::Entropy, false);
@ -30,7 +31,7 @@ fn filters_entropy(b: &mut Bencher) {
#[bench]
fn filters_bigrams(b: &mut Bencher) {
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.filter_image(RowFilter::Bigrams, false);
@ -40,7 +41,7 @@ fn filters_bigrams(b: &mut Bencher) {
#[bench]
fn filters_bigent(b: &mut Bencher) {
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.filter_image(RowFilter::BigEnt, false);
@ -50,7 +51,7 @@ fn filters_bigent(b: &mut Bencher) {
#[bench]
fn filters_brute(b: &mut Bencher) {
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.filter_image(RowFilter::Brute, false);

View file

@ -4,6 +4,7 @@ extern crate oxipng;
extern crate test;
use oxipng::internal_tests::*;
use oxipng::*;
use std::num::NonZeroU8;
use std::path::PathBuf;
use test::Bencher;
@ -14,7 +15,7 @@ const DEFAULT_ZOPFLI_ITERATIONS: NonZeroU8 = unsafe { NonZeroU8::new_unchecked(1
#[bench]
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 png = PngData::new(&input, false).unwrap();
let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| {
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]
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 png = PngData::new(&input, false).unwrap();
let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| {
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(
"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(|| {
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(
"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(|| {
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(
"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(|| {
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)
}
#[inline]
pub(crate) fn is_grayscale(&self) -> bool {
matches!(
self,
ColorType::Grayscale { .. } | ColorType::GrayscaleAlpha
)
}
#[inline]
pub(crate) fn has_alpha(&self) -> bool {
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
.zlib_compress(data, &mut dest)
.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);
Ok(dest)
}

View file

@ -1,7 +1,10 @@
mod deflater;
use crate::AtomicMin;
use crate::{PngError, PngResult};
pub use deflater::crc32;
pub use deflater::deflate;
pub use deflater::inflate;
use std::{fmt, fmt::Display};
#[cfg(feature = "zopfli")]
use std::num::NonZeroU8;
@ -27,3 +30,30 @@ pub enum Deflaters {
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::deflate;
use crate::filters::RowFilter;
use crate::png::PngData;
use crate::png::PngImage;
#[cfg(not(feature = "parallel"))]
use crate::rayon;
@ -22,7 +21,9 @@ use std::sync::atomic::Ordering::SeqCst;
use std::sync::Arc;
pub struct Candidate {
pub image: PngData,
pub image: Arc<PngImage>,
pub idat_data: Vec<u8>,
pub filtered: Vec<u8>,
pub filter: RowFilter,
pub is_reduction: bool,
// first wins tie-breaker
@ -32,9 +33,9 @@ pub struct Candidate {
impl Candidate {
fn cmp_key(&self) -> impl Ord {
(
self.image.estimated_output_size(),
self.image.raw.data.len(),
self.image.raw.ihdr.bit_depth,
self.idat_data.len() + self.image.key_chunks_size(),
self.image.data.len(),
self.image.ihdr.bit_depth,
self.filter,
self.nth,
)
@ -135,17 +136,7 @@ impl Evaluator {
let filtered = image.filter_image(filter, optimize_alpha);
let idat_data = deflate::deflate(&filtered, compression, &best_candidate_size);
if let Ok(idat_data) = idat_data {
let new = Candidate {
image: PngData {
idat_data,
filtered,
raw: Arc::clone(&image),
},
filter,
is_reduction,
nth,
};
let size = new.image.estimated_output_size();
let size = idat_data.len() + image.key_chunks_size();
best_candidate_size.set_min(size);
trace!(
"Eval: {}-bit {:20} {:8} {} bytes",
@ -154,6 +145,14 @@ impl Evaluator {
filter,
size
);
let new = Candidate {
image: image.clone(),
idat_data,
filtered,
filter,
is_reduction,
nth,
};
#[cfg(feature = "parallel")]
{

View file

@ -1,12 +1,13 @@
use crate::colors::{BitDepth, ColorType};
use crate::deflate::crc32;
use crate::deflate::{crc32, inflate};
use crate::error::PngError;
use crate::interlace::Interlacing;
use crate::AtomicMin;
use crate::Deflaters;
use crate::PngResult;
use indexmap::IndexSet;
use log::warn;
use rgb::{RGB16, RGBA8};
use std::io;
use std::io::{Cursor, Read};
#[derive(Debug, Clone)]
/// 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)]
/// Options to use for performing operations on headers (such as stripping)
pub enum Headers {
/// Options to use when stripping chunks
pub enum StripChunks {
/// None
None,
/// Remove specific chunks
Strip(Vec<String>),
/// Headers that won't affect rendering (all but cICP, iCCP, sBIT, sRGB, pHYs)
Strip(IndexSet<[u8; 4]>),
/// Remove all chunks that won't affect rendering
Safe,
/// Remove all non-critical chunks except these
Keep(IndexSet<String>),
/// All non-critical headers
Keep(IndexSet<[u8; 4]>),
/// All non-critical chunks
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]
pub fn file_header_is_valid(bytes: &[u8]) -> bool {
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)]
pub struct RawHeader<'a> {
pub struct RawChunk<'a> {
pub name: [u8; 4],
pub data: &'a [u8],
}
pub fn parse_next_header<'a>(
pub fn parse_next_chunk<'a>(
byte_data: &'a [u8],
byte_offset: &mut usize,
fix_errors: bool,
) -> PngResult<Option<RawHeader<'a>>> {
let mut rdr = Cursor::new(
) -> PngResult<Option<RawChunk<'a>>> {
let length = read_be_u32(
byte_data
.get(*byte_offset..*byte_offset + 4)
.ok_or(PngError::TruncatedData)?,
);
let length = read_be_u32(&mut rdr).unwrap();
*byte_offset += 4;
let header_start = *byte_offset;
let chunk_start = *byte_offset;
let chunk_name = byte_data
.get(header_start..header_start + 4)
.get(chunk_start..chunk_start + 4)
.ok_or(PngError::TruncatedData)?;
if chunk_name == b"IEND" {
// End of data
@ -117,37 +138,34 @@ pub fn parse_next_header<'a>(
.get(*byte_offset..*byte_offset + length as usize)
.ok_or(PngError::TruncatedData)?;
*byte_offset += length as usize;
let mut rdr = Cursor::new(
let crc = read_be_u32(
byte_data
.get(*byte_offset..*byte_offset + 4)
.ok_or(PngError::TruncatedData)?,
);
let crc = read_be_u32(&mut rdr).unwrap();
*byte_offset += 4;
let header_bytes = byte_data
.get(header_start..header_start + 4 + length as usize)
let chunk_bytes = byte_data
.get(chunk_start..chunk_start + 4 + length as usize)
.ok_or(PngError::TruncatedData)?;
if !fix_errors && crc32(header_bytes) != crc {
if !fix_errors && crc32(chunk_bytes) != crc {
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)
)));
}
let mut name = [0_u8; 4];
name.copy_from_slice(chunk_name);
Ok(Some(RawHeader { name, data }))
let name: [u8; 4] = chunk_name.try_into().unwrap();
Ok(Some(RawChunk { name, data }))
}
pub fn parse_ihdr_header(
pub fn parse_ihdr_chunk(
byte_data: &[u8],
palette_data: Option<Vec<u8>>,
trns_data: Option<Vec<u8>>,
) -> PngResult<IhdrData> {
// This eliminates bounds checks for the rest of the function
let interlaced = byte_data.get(12).copied().ok_or(PngError::TruncatedData)?;
let mut rdr = Cursor::new(&byte_data[0..8]);
Ok(IhdrData {
color_type: match byte_data[9] {
0 => ColorType::Grayscale {
@ -170,8 +188,8 @@ pub fn parse_ihdr_header(
_ => return Err(PngError::new("Unexpected color type in header")),
},
bit_depth: byte_data[8].try_into()?,
width: read_be_u32(&mut rdr).map_err(|_| PngError::TruncatedData)?,
height: read_be_u32(&mut rdr).map_err(|_| PngError::TruncatedData)?,
width: read_be_u32(&byte_data[0..4]),
height: read_be_u32(&byte_data[4..8]),
interlaced: interlaced.try_into()?,
})
}
@ -196,8 +214,74 @@ fn palette_to_rgba(
}
#[inline]
fn read_be_u32<T: AsRef<[u8]>>(rdr: &mut Cursor<T>) -> Result<u32, io::Error> {
let mut int_buf = [0; 4];
rdr.read_exact(&mut int_buf)?;
Ok(u32::from_be_bytes(int_buf))
fn read_be_u32(bytes: &[u8]) -> u32 {
u32::from_be_bytes(bytes.try_into().unwrap())
}
/// 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,
..png.ihdr
},
aux_headers: png.aux_headers.clone(),
}
}
@ -105,7 +104,6 @@ pub fn deinterlace_image(png: &PngImage) -> PngImage {
interlaced: Interlacing::None,
..png.ihdr
},
aux_headers: png.aux_headers.clone(),
}
}

View file

@ -25,9 +25,8 @@ extern crate rayon;
mod rayon;
use crate::atomicmin::AtomicMin;
use crate::deflate::{crc32, inflate};
use crate::evaluate::Evaluator;
use crate::headers::IhdrData;
use crate::headers::*;
use crate::png::PngData;
use crate::png::PngImage;
use crate::reduction::*;
@ -45,9 +44,9 @@ pub use crate::colors::{BitDepth, ColorType};
pub use crate::deflate::Deflaters;
pub use crate::error::PngError;
pub use crate::filters::RowFilter;
pub use crate::headers::Headers;
pub use crate::headers::StripChunks;
pub use crate::interlace::Interlacing;
pub use indexmap::{indexset, IndexMap, IndexSet};
pub use indexmap::{indexset, IndexSet};
pub use rgb::{RGB16, RGBA8};
mod atomicmin;
@ -67,9 +66,7 @@ mod sanity_checks;
#[doc(hidden)]
pub mod internal_tests {
pub use crate::atomicmin::*;
pub use crate::colors::*;
pub use crate::deflate::*;
pub use crate::headers::*;
pub use crate::png::*;
pub use crate::reduction::*;
#[cfg(feature = "sanity-checks")]
@ -189,10 +186,10 @@ pub struct Options {
///
/// Default: `true`
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`
pub strip: Headers,
pub strip: StripChunks,
/// Which DEFLATE algorithm to use
///
/// Default: `Libdeflater`
@ -306,7 +303,7 @@ impl Default for Options {
palette_reduction: true,
grayscale_reduction: true,
idat_recoding: true,
strip: Headers::None,
strip: StripChunks::None,
deflate: Deflaters::Libdeflater { compression: 11 },
fast_evaluation: true,
timeout: None,
@ -318,6 +315,7 @@ impl Default for Options {
/// A raw image definition which can be used to create an optimized png
pub struct RawImage {
png: Arc<PngImage>,
aux_chunks: Vec<Chunk>,
}
impl RawImage {
@ -363,35 +361,40 @@ impl RawImage {
interlaced: Interlacing::None,
},
data,
aux_headers: IndexMap::new(),
}),
aux_chunks: Vec::new(),
})
}
/// 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>) {
// We can guarantee this will succeed - failure indicates a bug
let png = Arc::get_mut(&mut self.png).unwrap();
png.aux_headers.insert(chunk_type, data);
pub fn add_png_chunk(&mut self, name: [u8; 4], data: Vec<u8>) {
self.aux_chunks.push(Chunk { name, data });
}
/// Add an ICC profile for the image
pub fn add_icc_profile(&mut self, data: &[u8]) {
// Compress with default compression level
if let Ok(mut compressed) = deflate::deflate(data, 11, &AtomicMin::new(None)) {
let mut iccp = Vec::with_capacity(compressed.len() + 13);
iccp.extend(b"icc"); // Profile name - generally unused, can be anything
iccp.extend([0, 0]); // Null separator, zlib compression method
iccp.append(&mut compressed);
self.add_png_chunk(*b"iCCP", iccp);
// Compress with fastest compression level - will be recompressed during optimization
let deflater = Deflaters::Libdeflater { compression: 1 };
if let Ok(iccp) = construct_iccp(data, deflater) {
self.aux_chunks.push(iccp);
}
}
/// Create an optimized png from the raw image data using the options provided
pub fn create_optimized_png(&self, opts: &Options) -> PngResult<Vec<u8>> {
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"))?;
// 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())
}
}
@ -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 {
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 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.
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)]
/// Defines options to be used for a single compression trial
struct TrialOptions {
pub filter: RowFilter,
pub compression: u8,
}
type TrialWithData = (TrialOptions, Vec<u8>);
type TrialResult = (RowFilter, Vec<u8>);
/// Perform optimization on the input PNG object using the options provided
fn optimize_png(
@ -553,27 +550,27 @@ fn optimize_png(
// Print png info
let file_original_size = original_data.len();
let idat_original_size = png.idat_data.len();
let raw = png.raw.clone();
debug!(
" {}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!(" 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 {
None
} else {
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.idat_data = new_png.idat_data;
}
postprocess_chunks(png, opts, &raw.ihdr);
let output = png.output();
if idat_original_size >= png.idat_data.len() {
@ -635,7 +632,7 @@ fn optimize_raw(
let mut eval_result = eval.get_best_candidate();
if let Some(ref result) = eval_result {
if result.is_reduction {
png = Arc::clone(&result.image.raw);
png = result.image.clone();
reduction_occurred = true;
}
}
@ -647,7 +644,7 @@ fn optimize_raw(
if opts.idat_recoding || reduction_occurred {
let mut filters = opts.filter.clone();
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
if eval_result.is_some() {
@ -659,7 +656,7 @@ fn optimize_raw(
trace!("Evaluating: {} filters", filters.len());
let eval = Evaluator::new(deadline, filters, eval_compression, opts.optimize_alpha);
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());
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)
let eval_result = eval_result?;
let result = eval_result?;
let trial = TrialOptions {
filter: eval_result.filter,
compression: match opts.deflate {
Deflaters::Libdeflater { compression } => compression,
_ => 0,
},
};
if trial.compression > 0 && trial.compression <= eval_compression {
// No further compression required
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)
match opts.deflate {
Deflaters::Libdeflater { compression } if compression <= eval_compression => {
// No further compression required
Some((result.filter, result.idat_data))
}
_ => {
debug!("Trying: {}", result.filter);
let best_size = AtomicMin::new(max_size);
perform_trial(&result.filtered, opts, result.filter, &best_size)
}
}
} else {
// 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());
for f in &filters {
results.push(TrialOptions {
filter: *f,
compression: match opts.deflate {
Deflaters::Libdeflater { compression } => compression,
_ => 0,
},
});
}
debug!("Trying: {} filters", results.len());
debug!("Trying: {} filters", filters.len());
let best_size = AtomicMin::new(max_size);
let results_iter = results.into_par_iter().with_max_len(1);
let best = results_iter.filter_map(|trial| {
let results_iter = filters.into_par_iter().with_max_len(1);
let best = results_iter.filter_map(|filter| {
if deadline.passed() {
return None;
}
let filtered = &png.filter_image(trial.filter, opts.optimize_alpha);
perform_trial(filtered, opts, trial, &best_size)
let filtered = &png.filter_image(filter, opts.optimize_alpha);
perform_trial(filtered, opts, filter, &best_size)
});
best.reduce_with(|i, j| {
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 {
raw: png,
// The filtered data has not been retained here, but we don't need to return it
filtered: vec![],
idat_data,
aux_chunks: Vec::new(),
};
if image.estimated_output_size() < max_size.unwrap_or(usize::MAX) {
debug!("Found better combination:");
debug!(
" zc = {} f = {:8} {} bytes",
trial.compression,
trial.filter,
opts.deflate,
filter,
image.idat_data.len()
);
return Some(image);
@ -751,7 +731,11 @@ fn optimize_raw(
} else if let Some(result) = eval_result {
// 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.
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) {
debug!("Found better combination:");
debug!(
@ -771,37 +755,26 @@ fn optimize_raw(
fn perform_trial(
filtered: &[u8],
opts: &Options,
trial: TrialOptions,
filter: RowFilter,
best_size: &AtomicMin,
) -> Option<TrialWithData> {
let new_idat = match opts.deflate {
Deflaters::Libdeflater { .. } => deflate::deflate(filtered, trial.compression, best_size),
#[cfg(feature = "zopfli")]
Deflaters::Zopfli { iterations } => deflate::zopfli_deflate(filtered, iterations),
};
// 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();
) -> Option<TrialResult> {
match opts.deflate.deflate(filtered, best_size) {
Ok(new_idat) => {
let bytes = new_idat.len();
best_size.set_min(bytes);
trace!(
" zc = {} f = {:8} {} bytes",
trial.compression,
trial.filter,
opts.deflate,
filter,
bytes
);
Some((trial, n))
Some((filter, new_idat))
}
Err(PngError::DeflatedDataTooLong(bytes)) => {
trace!(
" zc = {} f = {:8} >{} bytes",
trial.compression,
trial.filter,
opts.deflate,
filter,
bytes,
);
None
@ -867,100 +840,59 @@ fn report_format(prefix: &str, png: &PngImage) {
);
}
/// Strip headers from the `PngData` object, as requested by the passed `Options`
fn perform_strip(png: &mut PngData, opts: &Options) {
let raw = Arc::make_mut(&mut png.raw);
match opts.strip {
// Strip headers
Headers::None => (),
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() {
/// Perform cleanup of certain chunks from the `PngData` object, after optimization has been completed
fn postprocess_chunks(png: &mut PngData, opts: &Options, orig_ihdr: &IhdrData) {
if let Some(iccp_idx) = png.aux_chunks.iter().position(|c| &c.name == b"iCCP") {
// See if we can replace an iCCP chunk with an sRGB chunk
let may_replace_iccp = opts.strip != StripChunks::None && opts.strip.keep(b"sRGB");
if may_replace_iccp && png.aux_chunks.iter().any(|c| &c.name == b"sRGB") {
// Files aren't supposed to have both chunks, so we chose to honor sRGB
raw.aux_headers.remove(b"iCCP");
} else if let Some(intent) = raw
.aux_headers
.get(b"iCCP")
.and_then(|iccp| srgb_rendering_intent(iccp))
{
// sRGB-like profile can be safely replaced with
// an sRGB chunk with the same rendering intent
raw.aux_headers.remove(b"iCCP");
raw.aux_headers.insert(*b"sRGB", vec![intent]);
}
}
}
/// If the profile is sRGB, extracts the rendering intent value from it
fn srgb_rendering_intent(mut iccp: &[u8]) -> Option<u8> {
// Skip (useless) profile name
loop {
let (&n, rest) = iccp.split_first()?;
iccp = rest;
if n == 0 {
break;
}
}
let (&compression_method, compressed_data) = iccp.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).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)
trace!("Removing iCCP chunk due to conflict with sRGB chunk");
png.aux_chunks.remove(iccp_idx);
} else if let Some(icc) = extract_icc(&png.aux_chunks[iccp_idx]) {
let intent = if may_replace_iccp {
srgb_rendering_intent(&icc)
} else {
None
};
// sRGB-like profile can be replaced with an sRGB chunk with the same rendering 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",
data: vec![intent],
};
} else if let Ok(iccp) = construct_iccp(&icc, opts.deflate) {
let cur_len = png.aux_chunks[iccp_idx].data.len();
let new_len = iccp.data.len();
if new_len < cur_len {
debug!(
"Recompressed iCCP chunk: {} ({} bytes decrease)",
new_len,
cur_len - new_len
);
png.aux_chunks[iccp_idx] = iccp;
}
_ => 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 log::{error, warn};
use oxipng::Deflaters;
use oxipng::Headers;
use oxipng::Options;
use oxipng::RowFilter;
use oxipng::StripChunks;
use oxipng::{InFile, OutFile};
use std::fs::DirBuilder;
#[cfg(feature = "zopfli")]
@ -496,40 +496,44 @@ fn parse_opts_into_struct(
opts.idat_recoding = false;
}
if let Some(hdrs) = matches.value_of("keep") {
opts.strip = Headers::Keep(hdrs.split(',').map(|x| x.trim().to_owned()).collect())
if let Some(keep) = matches.value_of("keep") {
let names = keep
.split(',')
.map(parse_chunk_name)
.collect::<Result<_, _>>()?;
opts.strip = StripChunks::Keep(names)
}
if let Some(hdrs) = matches.value_of("strip") {
let hdrs = hdrs
.split(',')
.map(|x| x.trim().to_owned())
.collect::<Vec<String>>();
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;
}
if let Some(strip) = matches.value_of("strip") {
if strip == "safe" {
opts.strip = StripChunks::Safe;
} else if strip == "all" {
opts.strip = StripChunks::All;
} else {
const FORBIDDEN_CHUNKS: [[u8; 4]; 5] =
[*b"IHDR", *b"IDAT", *b"tRNS", *b"PLTE", *b"IEND"];
for i in &hdrs {
if FORBIDDEN_CHUNKS.iter().any(|chunk| chunk == i.as_bytes()) {
return Err(format!("{} chunk is not allowed to be stripped", i));
}
}
opts.strip = Headers::Strip(hdrs);
let names = strip
.split(',')
.map(|x| {
if x == "safe" || x == "all" {
return Err(
"'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") {
opts.strip = Headers::Safe;
opts.strip = StripChunks::Safe;
}
if matches.is_present("zopfli") {
@ -556,6 +560,13 @@ fn parse_opts_into_struct(
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(
input: &str,
min_value: u8,

View file

@ -4,8 +4,8 @@ use crate::error::PngError;
use crate::filters::*;
use crate::headers::*;
use crate::interlace::{deinterlace_image, interlace_image, Interlacing};
use crate::Options;
use bitvec::bitarr;
use indexmap::IndexMap;
use libdeflater::{CompressionLvl, Compressor};
use rgb::ComponentSlice;
use rustc_hash::FxHashMap;
@ -30,8 +30,6 @@ pub struct PngImage {
pub ihdr: IhdrData,
/// The uncompressed, unfiltered data from the IDAT chunk
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
@ -41,17 +39,17 @@ pub struct PngData {
pub raw: Arc<PngImage>,
/// The filtered and compressed data of the IDAT chunk
pub idat_data: Vec<u8>,
/// The filtered, uncompressed data of the IDAT chunk
pub filtered: Vec<u8>,
/// All non-critical chunks from the PNG are stored here
pub aux_chunks: Vec<Chunk>,
}
impl PngData {
/// Create a new `PngData` struct by opening a file
#[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)?;
Self::from_slice(&byte_data, fix_errors)
Self::from_slice(&byte_data, opts)
}
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
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;
// Test that png header is valid
let header = byte_data.get(0..8).ok_or(PngError::TruncatedData)?;
@ -88,71 +86,65 @@ impl PngData {
return Err(PngError::NotPNG);
}
byte_offset += 8;
// Read the data headers
let mut aux_headers: IndexMap<[u8; 4], Vec<u8>> = IndexMap::new();
let mut idat_headers: Vec<u8> = Vec::new();
while let Some(header) = parse_next_header(byte_data, &mut byte_offset, fix_errors)? {
match &header.name {
b"IDAT" => idat_headers.extend_from_slice(header.data),
// Read the data chunks
let mut idat_data: Vec<u8> = Vec::new();
let mut key_chunks: FxHashMap<[u8; 4], Vec<u8>> = FxHashMap::default();
let mut aux_chunks: Vec<Chunk> = Vec::new();
while let Some(chunk) = parse_next_chunk(byte_data, &mut byte_offset, opts.fix_errors)? {
match &chunk.name {
b"IDAT" => idat_data.extend_from_slice(chunk.data),
b"acTL" => return Err(PngError::APNGNotSupported),
b"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"));
}
let ihdr = match aux_headers.remove(b"IHDR") {
let ihdr_chunk = match key_chunks.remove(b"IHDR") {
Some(ihdr) => ihdr,
None => return Err(PngError::ChunkMissing("IHDR")),
};
let ihdr_header = parse_ihdr_header(
&ihdr,
aux_headers.remove(b"PLTE"),
aux_headers.remove(b"tRNS"),
let ihdr = parse_ihdr_chunk(
&ihdr_chunk,
key_chunks.remove(b"PLTE"),
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
if raw_data.len() != ihdr_header.raw_data_size() {
if raw_data.len() != ihdr.raw_data_size() {
return Err(PngError::TruncatedData);
}
let mut raw = PngImage {
ihdr: ihdr_header,
ihdr,
data: raw_data,
aux_headers,
};
let unfiltered = raw.unfilter_image()?;
raw.data = raw.unfilter_image()?;
// Return the PngData
Ok(Self {
idat_data: idat_headers,
filtered: std::mem::replace(&mut raw.data, unfiltered),
idat_data,
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 {
// Add the size of the PLTE and tRNS chunks to the compressed idat 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,
}
self.idat_data.len() + self.raw.key_chunks_size()
}
/// 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(&[self.raw.ihdr.interlaced as u8]).ok();
write_png_block(b"IHDR", &ihdr_data, &mut output);
// Ancillary headers
for (key, header) in self
.raw
.aux_headers
// Ancillary chunks
for chunk in self
.aux_chunks
.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
match &self.raw.ihdr.color_type {
@ -210,14 +201,13 @@ impl PngData {
}
_ => {}
}
// Special ancillary headers that need to come after PLTE but before IDAT
for (key, header) in self
.raw
.aux_headers
// Special ancillary chunks that need to come after PLTE but before IDAT
for chunk in self
.aux_chunks
.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
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
#[inline]
pub fn scan_lines(&self, has_filter: bool) -> ScanLines<'_> {
@ -451,14 +459,15 @@ impl PngImage {
filtered
}
}
fn write_png_block(key: &[u8], header: &[u8], output: &mut Vec<u8>) {
let mut header_data = Vec::with_capacity(header.len() + 4);
header_data.extend_from_slice(key);
header_data.extend_from_slice(header);
output.reserve(header_data.len() + 8);
output.extend_from_slice(&(header_data.len() as u32 - 4).to_be_bytes());
let crc = deflate::crc32(&header_data);
output.append(&mut header_data);
fn write_png_block(key: &[u8], chunk: &[u8], output: &mut Vec<u8>) {
let mut chunk_data = Vec::with_capacity(chunk.len() + 4);
chunk_data.extend_from_slice(key);
chunk_data.extend_from_slice(chunk);
output.reserve(chunk_data.len() + 8);
output.extend_from_slice(&(chunk_data.len() as u32 - 4).to_be_bytes());
let crc = deflate::crc32(&chunk_data);
output.append(&mut chunk_data);
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 {
data: reduced,
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 {
data: raw_data,
ihdr: IhdrData {
color_type: target_color_type,
..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,
..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
let ppb = 8 / bit_depth;
if let ColorType::Indexed { .. } = png.ihdr.color_type {
for line in png.scan_lines(false) {
let line_max = line
.data
.iter()
.map(|&byte| match png.ihdr.bit_depth {
BitDepth::Two => (byte & 0x3)
.max((byte >> 2) & 0x3)
.max((byte >> 4) & 0x3)
.max(byte >> 6),
BitDepth::Four => (byte & 0xF).max(byte >> 4),
_ => byte,
})
.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;
}
}
if let ColorType::Indexed { palette } = &png.ihdr.color_type {
// We can easily determine minimum depth by the palette size
let required_bits = match palette.len() {
0..=2 => 1,
3..=4 => 2,
5..=16 => 4,
_ => 8,
};
if required_bits >= bit_depth {
// Not reducable
return None;
} else if required_bits > minimum_bits {
minimum_bits = required_bits;
}
} 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 divisions = 1..(bit_depth / minimum_bits);
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(),
..png.ihdr
},
aux_headers: png.aux_headers.clone(),
})
}

View file

@ -3,7 +3,7 @@ use crate::headers::IhdrData;
use crate::png::PngImage;
use indexmap::IndexSet;
use rgb::alt::Gray;
use rgb::{ComponentMap, ComponentSlice, FromSlice, RGB, RGBA, RGBA8};
use rgb::{ComponentMap, ComponentSlice, FromSlice, RGB, RGBA};
use rustc_hash::FxHasher;
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 palette: Vec<_> = match png.ihdr.color_type {
let palette: Vec<_> = match png.ihdr.color_type {
ColorType::Grayscale { transparent_shade } => {
let pmap = build_palette(png.data.as_gray().iter().cloned(), &mut raw_data)?;
// Convert the Gray16 transparency to Gray8
@ -81,51 +81,12 @@ pub fn reduced_to_indexed(png: &PngImage) -> Option<PngImage> {
_ => 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 {
data: raw_data,
ihdr: IhdrData {
color_type: ColorType::Indexed { palette },
..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..]);
}
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 {
ColorType::RGB { transparent_color } => ColorType::Grayscale {
// 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,
..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]);
}
// 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 {
ihdr: IhdrData {
color_type,
..png.ihdr
},
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 {
// Reassign data bytes to new indices
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
},
data,
aux_headers,
})
}
@ -145,15 +135,6 @@ pub fn sorted_palette(png: &PngImage) -> Option<PngImage> {
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
enumerated.sort_by(|a, b| {
// 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))
});
if let Some(bkgd) = bkgd_last {
enumerated.push(bkgd);
}
// Extract the new palette and determine if anything changed
let (old_map, palette): (Vec<_>, Vec<RGBA8>) = enumerated.into_iter().unzip();
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 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 {
ihdr: IhdrData {
color_type: ColorType::Indexed { palette },
..png.ihdr
},
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 oxipng::{internal_tests::*, RowFilter};
use oxipng::{InFile, OutFile};
use oxipng::internal_tests::*;
use oxipng::*;
use std::fs::remove_file;
use std::path::Path;
use std::path::PathBuf;
@ -37,7 +37,7 @@ fn test_it_converts(
let input = PathBuf::from(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.insert(filter);
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();
assert!(output.exists());
let png = match PngData::new(output, opts.fix_errors) {
let png = match PngData::new(output, &opts) {
Ok(x) => x,
Err(x) => {
remove_file(output).ok();

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,6 +1,6 @@
use indexmap::IndexSet;
use oxipng::{internal_tests::*, Interlacing, RowFilter};
use oxipng::{InFile, OutFile};
use oxipng::internal_tests::*;
use oxipng::*;
use std::fs::remove_file;
use std::path::Path;
use std::path::PathBuf;
@ -37,7 +37,7 @@ fn test_it_converts(
let input = PathBuf::from(input);
let (output, mut opts) = get_opts(&input);
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.bit_depth, bit_depth_in, "test file is broken");
@ -50,7 +50,7 @@ fn test_it_converts(
let output = output.path().unwrap();
assert!(output.exists());
let png = match PngData::new(output, opts.fix_errors) {
let png = match PngData::new(output, &opts) {
Ok(x) => x,
Err(x) => {
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 (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.bit_depth, BitDepth::Eight);
@ -948,7 +948,7 @@ fn palette_should_be_reduced_with_dupes() {
let output = output.path().unwrap();
assert!(output.exists());
let png = match PngData::new(output, opts.fix_errors) {
let png = match PngData::new(output, &opts) {
Ok(x) => x,
Err(x) => {
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 (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.bit_depth, BitDepth::Eight);
@ -985,7 +985,7 @@ fn palette_should_be_reduced_with_unused() {
let output = output.path().unwrap();
assert!(output.exists());
let png = match PngData::new(output, opts.fix_errors) {
let png = match PngData::new(output, &opts) {
Ok(x) => x,
Err(x) => {
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 (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.bit_depth, BitDepth::Eight);
@ -1022,7 +1022,7 @@ fn palette_should_be_reduced_with_both() {
let output = output.path().unwrap();
assert!(output.exists());
let png = match PngData::new(output, opts.fix_errors) {
let png = match PngData::new(output, &opts) {
Ok(x) => x,
Err(x) => {
remove_file(output).ok();

View file

@ -1,6 +1,6 @@
use indexmap::IndexSet;
use oxipng::{internal_tests::*, Interlacing, RowFilter};
use oxipng::{InFile, OutFile};
use oxipng::internal_tests::*;
use oxipng::*;
use std::fs::remove_file;
use std::path::Path;
use std::path::PathBuf;
@ -36,7 +36,7 @@ fn test_it_converts(
) {
let input = PathBuf::from(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!(
png.raw.ihdr.color_type.png_header_code(),
@ -52,7 +52,7 @@ fn test_it_converts(
let output = output.path().unwrap();
assert!(output.exists());
let png = match PngData::new(output, opts.fix_errors) {
let png = match PngData::new(output, &opts) {
Ok(x) => x,
Err(x) => {
remove_file(output).ok();
@ -70,13 +70,7 @@ fn test_it_converts(
"optimized to wrong bit depth"
);
if let ColorType::Indexed { palette } = &png.raw.ihdr.color_type {
let mut max_palette_size = 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);
assert!(palette.len() <= 1 << (png.raw.ihdr.bit_depth as u8));
}
remove_file(output).ok();
@ -100,7 +94,7 @@ fn issue_42() {
let (output, mut opts) = get_opts(&input);
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.color_type, ColorType::GrayscaleAlpha);
@ -113,7 +107,7 @@ fn issue_42() {
let output = output.path().unwrap();
assert!(output.exists());
let png = match PngData::new(output, opts.fix_errors) {
let png = match PngData::new(output, &opts) {
Ok(x) => x,
Err(x) => {
remove_file(output).ok();
@ -231,7 +225,7 @@ fn issue_59() {
None,
RGBA,
BitDepth::Eight,
RGBA,
INDEXED,
BitDepth::Eight,
);
}

View file

@ -1,6 +1,6 @@
use indexmap::IndexSet;
use oxipng::{internal_tests::*, RowFilter};
use oxipng::{InFile, OutFile};
use oxipng::internal_tests::*;
use oxipng::*;
use std::fs::remove_file;
use std::path::Path;
use std::path::PathBuf;
@ -36,7 +36,7 @@ fn test_it_converts(
let input = PathBuf::from(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.insert(filter);
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();
assert!(output.exists());
let png = match PngData::new(output, opts.fix_errors) {
let png = match PngData::new(output, &opts) {
Ok(x) => x,
Err(x) => {
remove_file(output).ok();