Refactor aux chunk handling (#505)
|
|
@ -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()));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")]
|
||||
{
|
||||
|
|
|
|||
150
src/headers.rs
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
312
src/lib.rs
|
|
@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
63
src/main.rs
|
|
@ -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,
|
||||
|
|
|
|||
143
src/png/mod.rs
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 115 KiB After Width: | Height: | Size: 115 KiB |
|
Before Width: | Height: | Size: 115 KiB After Width: | Height: | Size: 115 KiB |
|
Before Width: | Height: | Size: 115 KiB After Width: | Height: | Size: 115 KiB |
|
Before Width: | Height: | Size: 115 KiB After Width: | Height: | Size: 115 KiB |
|
|
@ -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();
|
||||
|
|
|
|||
113
tests/flags.rs
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
32
tests/lib.rs
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
16
tests/raw.rs
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||