Refactor aux chunk handling (#505)
|
|
@ -3,15 +3,15 @@
|
||||||
extern crate oxipng;
|
extern crate oxipng;
|
||||||
extern crate test;
|
extern crate test;
|
||||||
|
|
||||||
|
use oxipng::internal_tests::*;
|
||||||
|
use oxipng::*;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use test::Bencher;
|
use test::Bencher;
|
||||||
|
|
||||||
use oxipng::internal_tests::*;
|
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
fn deflate_16_bits(b: &mut Bencher) {
|
fn deflate_16_bits(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
let min = AtomicMin::new(None);
|
let min = AtomicMin::new(None);
|
||||||
|
|
@ -22,7 +22,7 @@ fn deflate_16_bits(b: &mut Bencher) {
|
||||||
#[bench]
|
#[bench]
|
||||||
fn deflate_8_bits(b: &mut Bencher) {
|
fn deflate_8_bits(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
let min = AtomicMin::new(None);
|
let min = AtomicMin::new(None);
|
||||||
|
|
@ -35,7 +35,7 @@ fn deflate_4_bits(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_4_should_be_palette_4.png",
|
"tests/files/palette_4_should_be_palette_4.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
let min = AtomicMin::new(None);
|
let min = AtomicMin::new(None);
|
||||||
|
|
@ -48,7 +48,7 @@ fn deflate_2_bits(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_2_should_be_palette_2.png",
|
"tests/files/palette_2_should_be_palette_2.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
let min = AtomicMin::new(None);
|
let min = AtomicMin::new(None);
|
||||||
|
|
@ -61,7 +61,7 @@ fn deflate_1_bits(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_1_should_be_palette_1.png",
|
"tests/files/palette_1_should_be_palette_1.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
let min = AtomicMin::new(None);
|
let min = AtomicMin::new(None);
|
||||||
|
|
@ -72,7 +72,7 @@ fn deflate_1_bits(b: &mut Bencher) {
|
||||||
#[bench]
|
#[bench]
|
||||||
fn inflate_generic(b: &mut Bencher) {
|
fn inflate_generic(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| inflate(png.idat_data.as_ref(), png.raw.ihdr.raw_data_size()));
|
b.iter(|| inflate(png.idat_data.as_ref(), png.raw.ihdr.raw_data_size()));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,15 @@
|
||||||
extern crate oxipng;
|
extern crate oxipng;
|
||||||
extern crate test;
|
extern crate test;
|
||||||
|
|
||||||
use oxipng::{internal_tests::*, RowFilter};
|
use oxipng::internal_tests::*;
|
||||||
|
use oxipng::*;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use test::Bencher;
|
use test::Bencher;
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
fn filters_16_bits_filter_0(b: &mut Bencher) {
|
fn filters_16_bits_filter_0(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::None, false);
|
png.raw.filter_image(RowFilter::None, false);
|
||||||
|
|
@ -20,7 +21,7 @@ fn filters_16_bits_filter_0(b: &mut Bencher) {
|
||||||
#[bench]
|
#[bench]
|
||||||
fn filters_8_bits_filter_0(b: &mut Bencher) {
|
fn filters_8_bits_filter_0(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::None, false);
|
png.raw.filter_image(RowFilter::None, false);
|
||||||
|
|
@ -32,7 +33,7 @@ fn filters_4_bits_filter_0(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_4_should_be_palette_4.png",
|
"tests/files/palette_4_should_be_palette_4.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::None, false);
|
png.raw.filter_image(RowFilter::None, false);
|
||||||
|
|
@ -44,7 +45,7 @@ fn filters_2_bits_filter_0(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_2_should_be_palette_2.png",
|
"tests/files/palette_2_should_be_palette_2.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::None, false);
|
png.raw.filter_image(RowFilter::None, false);
|
||||||
|
|
@ -56,7 +57,7 @@ fn filters_1_bits_filter_0(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_1_should_be_palette_1.png",
|
"tests/files/palette_1_should_be_palette_1.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::None, false);
|
png.raw.filter_image(RowFilter::None, false);
|
||||||
|
|
@ -66,7 +67,7 @@ fn filters_1_bits_filter_0(b: &mut Bencher) {
|
||||||
#[bench]
|
#[bench]
|
||||||
fn filters_16_bits_filter_1(b: &mut Bencher) {
|
fn filters_16_bits_filter_1(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Sub, false);
|
png.raw.filter_image(RowFilter::Sub, false);
|
||||||
|
|
@ -76,7 +77,7 @@ fn filters_16_bits_filter_1(b: &mut Bencher) {
|
||||||
#[bench]
|
#[bench]
|
||||||
fn filters_8_bits_filter_1(b: &mut Bencher) {
|
fn filters_8_bits_filter_1(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Sub, false);
|
png.raw.filter_image(RowFilter::Sub, false);
|
||||||
|
|
@ -88,7 +89,7 @@ fn filters_4_bits_filter_1(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_4_should_be_palette_4.png",
|
"tests/files/palette_4_should_be_palette_4.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Sub, false);
|
png.raw.filter_image(RowFilter::Sub, false);
|
||||||
|
|
@ -100,7 +101,7 @@ fn filters_2_bits_filter_1(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_2_should_be_palette_2.png",
|
"tests/files/palette_2_should_be_palette_2.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Sub, false);
|
png.raw.filter_image(RowFilter::Sub, false);
|
||||||
|
|
@ -112,7 +113,7 @@ fn filters_1_bits_filter_1(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_1_should_be_palette_1.png",
|
"tests/files/palette_1_should_be_palette_1.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Sub, false);
|
png.raw.filter_image(RowFilter::Sub, false);
|
||||||
|
|
@ -122,7 +123,7 @@ fn filters_1_bits_filter_1(b: &mut Bencher) {
|
||||||
#[bench]
|
#[bench]
|
||||||
fn filters_16_bits_filter_2(b: &mut Bencher) {
|
fn filters_16_bits_filter_2(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Up, false);
|
png.raw.filter_image(RowFilter::Up, false);
|
||||||
|
|
@ -132,7 +133,7 @@ fn filters_16_bits_filter_2(b: &mut Bencher) {
|
||||||
#[bench]
|
#[bench]
|
||||||
fn filters_8_bits_filter_2(b: &mut Bencher) {
|
fn filters_8_bits_filter_2(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Up, false);
|
png.raw.filter_image(RowFilter::Up, false);
|
||||||
|
|
@ -144,7 +145,7 @@ fn filters_4_bits_filter_2(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_4_should_be_palette_4.png",
|
"tests/files/palette_4_should_be_palette_4.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Up, false);
|
png.raw.filter_image(RowFilter::Up, false);
|
||||||
|
|
@ -156,7 +157,7 @@ fn filters_2_bits_filter_2(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_2_should_be_palette_2.png",
|
"tests/files/palette_2_should_be_palette_2.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Up, false);
|
png.raw.filter_image(RowFilter::Up, false);
|
||||||
|
|
@ -168,7 +169,7 @@ fn filters_1_bits_filter_2(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_1_should_be_palette_1.png",
|
"tests/files/palette_1_should_be_palette_1.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Up, false);
|
png.raw.filter_image(RowFilter::Up, false);
|
||||||
|
|
@ -178,7 +179,7 @@ fn filters_1_bits_filter_2(b: &mut Bencher) {
|
||||||
#[bench]
|
#[bench]
|
||||||
fn filters_16_bits_filter_3(b: &mut Bencher) {
|
fn filters_16_bits_filter_3(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Average, false);
|
png.raw.filter_image(RowFilter::Average, false);
|
||||||
|
|
@ -188,7 +189,7 @@ fn filters_16_bits_filter_3(b: &mut Bencher) {
|
||||||
#[bench]
|
#[bench]
|
||||||
fn filters_8_bits_filter_3(b: &mut Bencher) {
|
fn filters_8_bits_filter_3(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Average, false);
|
png.raw.filter_image(RowFilter::Average, false);
|
||||||
|
|
@ -200,7 +201,7 @@ fn filters_4_bits_filter_3(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_4_should_be_palette_4.png",
|
"tests/files/palette_4_should_be_palette_4.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Average, false);
|
png.raw.filter_image(RowFilter::Average, false);
|
||||||
|
|
@ -212,7 +213,7 @@ fn filters_2_bits_filter_3(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_2_should_be_palette_2.png",
|
"tests/files/palette_2_should_be_palette_2.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Average, false);
|
png.raw.filter_image(RowFilter::Average, false);
|
||||||
|
|
@ -224,7 +225,7 @@ fn filters_1_bits_filter_3(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_1_should_be_palette_1.png",
|
"tests/files/palette_1_should_be_palette_1.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Average, false);
|
png.raw.filter_image(RowFilter::Average, false);
|
||||||
|
|
@ -234,7 +235,7 @@ fn filters_1_bits_filter_3(b: &mut Bencher) {
|
||||||
#[bench]
|
#[bench]
|
||||||
fn filters_16_bits_filter_4(b: &mut Bencher) {
|
fn filters_16_bits_filter_4(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Paeth, false);
|
png.raw.filter_image(RowFilter::Paeth, false);
|
||||||
|
|
@ -244,7 +245,7 @@ fn filters_16_bits_filter_4(b: &mut Bencher) {
|
||||||
#[bench]
|
#[bench]
|
||||||
fn filters_8_bits_filter_4(b: &mut Bencher) {
|
fn filters_8_bits_filter_4(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Paeth, false);
|
png.raw.filter_image(RowFilter::Paeth, false);
|
||||||
|
|
@ -256,7 +257,7 @@ fn filters_4_bits_filter_4(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_4_should_be_palette_4.png",
|
"tests/files/palette_4_should_be_palette_4.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Paeth, false);
|
png.raw.filter_image(RowFilter::Paeth, false);
|
||||||
|
|
@ -268,7 +269,7 @@ fn filters_2_bits_filter_4(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_2_should_be_palette_2.png",
|
"tests/files/palette_2_should_be_palette_2.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Paeth, false);
|
png.raw.filter_image(RowFilter::Paeth, false);
|
||||||
|
|
@ -280,7 +281,7 @@ fn filters_1_bits_filter_4(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_1_should_be_palette_1.png",
|
"tests/files/palette_1_should_be_palette_1.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Paeth, false);
|
png.raw.filter_image(RowFilter::Paeth, false);
|
||||||
|
|
@ -290,7 +291,7 @@ fn filters_1_bits_filter_4(b: &mut Bencher) {
|
||||||
#[bench]
|
#[bench]
|
||||||
fn filters_16_bits_filter_5(b: &mut Bencher) {
|
fn filters_16_bits_filter_5(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::MinSum, false);
|
png.raw.filter_image(RowFilter::MinSum, false);
|
||||||
|
|
@ -300,7 +301,7 @@ fn filters_16_bits_filter_5(b: &mut Bencher) {
|
||||||
#[bench]
|
#[bench]
|
||||||
fn filters_8_bits_filter_5(b: &mut Bencher) {
|
fn filters_8_bits_filter_5(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::MinSum, false);
|
png.raw.filter_image(RowFilter::MinSum, false);
|
||||||
|
|
@ -312,7 +313,7 @@ fn filters_4_bits_filter_5(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_4_should_be_palette_4.png",
|
"tests/files/palette_4_should_be_palette_4.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::MinSum, false);
|
png.raw.filter_image(RowFilter::MinSum, false);
|
||||||
|
|
@ -324,7 +325,7 @@ fn filters_2_bits_filter_5(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_2_should_be_palette_2.png",
|
"tests/files/palette_2_should_be_palette_2.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::MinSum, false);
|
png.raw.filter_image(RowFilter::MinSum, false);
|
||||||
|
|
@ -336,7 +337,7 @@ fn filters_1_bits_filter_5(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_1_should_be_palette_1.png",
|
"tests/files/palette_1_should_be_palette_1.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::MinSum, false);
|
png.raw.filter_image(RowFilter::MinSum, false);
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,15 @@
|
||||||
extern crate oxipng;
|
extern crate oxipng;
|
||||||
extern crate test;
|
extern crate test;
|
||||||
|
|
||||||
use oxipng::{internal_tests::*, Interlacing};
|
use oxipng::internal_tests::*;
|
||||||
|
use oxipng::*;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use test::Bencher;
|
use test::Bencher;
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
fn interlacing_16_bits(b: &mut Bencher) {
|
fn interlacing_16_bits(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| png.raw.change_interlacing(Interlacing::Adam7));
|
b.iter(|| png.raw.change_interlacing(Interlacing::Adam7));
|
||||||
}
|
}
|
||||||
|
|
@ -18,7 +19,7 @@ fn interlacing_16_bits(b: &mut Bencher) {
|
||||||
#[bench]
|
#[bench]
|
||||||
fn interlacing_8_bits(b: &mut Bencher) {
|
fn interlacing_8_bits(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| png.raw.change_interlacing(Interlacing::Adam7));
|
b.iter(|| png.raw.change_interlacing(Interlacing::Adam7));
|
||||||
}
|
}
|
||||||
|
|
@ -28,7 +29,7 @@ fn interlacing_4_bits(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_4_should_be_palette_4.png",
|
"tests/files/palette_4_should_be_palette_4.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| png.raw.change_interlacing(Interlacing::Adam7));
|
b.iter(|| png.raw.change_interlacing(Interlacing::Adam7));
|
||||||
}
|
}
|
||||||
|
|
@ -38,7 +39,7 @@ fn interlacing_2_bits(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_2_should_be_palette_2.png",
|
"tests/files/palette_2_should_be_palette_2.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| png.raw.change_interlacing(Interlacing::Adam7));
|
b.iter(|| png.raw.change_interlacing(Interlacing::Adam7));
|
||||||
}
|
}
|
||||||
|
|
@ -48,7 +49,7 @@ fn interlacing_1_bits(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_1_should_be_palette_1.png",
|
"tests/files/palette_1_should_be_palette_1.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| png.raw.change_interlacing(Interlacing::Adam7));
|
b.iter(|| png.raw.change_interlacing(Interlacing::Adam7));
|
||||||
}
|
}
|
||||||
|
|
@ -58,7 +59,7 @@ fn deinterlacing_16_bits(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/interlaced_rgb_16_should_be_rgb_16.png",
|
"tests/files/interlaced_rgb_16_should_be_rgb_16.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| png.raw.change_interlacing(Interlacing::None));
|
b.iter(|| png.raw.change_interlacing(Interlacing::None));
|
||||||
}
|
}
|
||||||
|
|
@ -68,7 +69,7 @@ fn deinterlacing_8_bits(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/interlaced_rgb_8_should_be_rgb_8.png",
|
"tests/files/interlaced_rgb_8_should_be_rgb_8.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| png.raw.change_interlacing(Interlacing::None));
|
b.iter(|| png.raw.change_interlacing(Interlacing::None));
|
||||||
}
|
}
|
||||||
|
|
@ -78,7 +79,7 @@ fn deinterlacing_4_bits(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/interlaced_palette_4_should_be_palette_4.png",
|
"tests/files/interlaced_palette_4_should_be_palette_4.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| png.raw.change_interlacing(Interlacing::None));
|
b.iter(|| png.raw.change_interlacing(Interlacing::None));
|
||||||
}
|
}
|
||||||
|
|
@ -88,7 +89,7 @@ fn deinterlacing_2_bits(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/interlaced_palette_2_should_be_palette_2.png",
|
"tests/files/interlaced_palette_2_should_be_palette_2.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| png.raw.change_interlacing(Interlacing::None));
|
b.iter(|| png.raw.change_interlacing(Interlacing::None));
|
||||||
}
|
}
|
||||||
|
|
@ -98,7 +99,7 @@ fn deinterlacing_1_bits(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/interlaced_palette_1_should_be_palette_1.png",
|
"tests/files/interlaced_palette_1_should_be_palette_1.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| png.raw.change_interlacing(Interlacing::None));
|
b.iter(|| png.raw.change_interlacing(Interlacing::None));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,14 @@ extern crate oxipng;
|
||||||
extern crate test;
|
extern crate test;
|
||||||
|
|
||||||
use oxipng::internal_tests::*;
|
use oxipng::internal_tests::*;
|
||||||
|
use oxipng::*;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use test::Bencher;
|
use test::Bencher;
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
fn reductions_16_to_8_bits(b: &mut Bencher) {
|
fn reductions_16_to_8_bits(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_8.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_8.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| bit_depth::reduced_bit_depth_16_to_8(&png.raw));
|
b.iter(|| bit_depth::reduced_bit_depth_16_to_8(&png.raw));
|
||||||
}
|
}
|
||||||
|
|
@ -20,7 +21,7 @@ fn reductions_8_to_4_bits(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_8_should_be_palette_4.png",
|
"tests/files/palette_8_should_be_palette_4.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| bit_depth::reduced_bit_depth_8_or_less(&png.raw, 1));
|
b.iter(|| bit_depth::reduced_bit_depth_8_or_less(&png.raw, 1));
|
||||||
}
|
}
|
||||||
|
|
@ -30,7 +31,7 @@ fn reductions_8_to_2_bits(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_8_should_be_palette_2.png",
|
"tests/files/palette_8_should_be_palette_2.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| bit_depth::reduced_bit_depth_8_or_less(&png.raw, 1));
|
b.iter(|| bit_depth::reduced_bit_depth_8_or_less(&png.raw, 1));
|
||||||
}
|
}
|
||||||
|
|
@ -40,7 +41,7 @@ fn reductions_8_to_1_bits(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_8_should_be_palette_1.png",
|
"tests/files/palette_8_should_be_palette_1.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| bit_depth::reduced_bit_depth_8_or_less(&png.raw, 1));
|
b.iter(|| bit_depth::reduced_bit_depth_8_or_less(&png.raw, 1));
|
||||||
}
|
}
|
||||||
|
|
@ -50,7 +51,7 @@ fn reductions_4_to_2_bits(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_4_should_be_palette_2.png",
|
"tests/files/palette_4_should_be_palette_2.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| bit_depth::reduced_bit_depth_8_or_less(&png.raw, 1));
|
b.iter(|| bit_depth::reduced_bit_depth_8_or_less(&png.raw, 1));
|
||||||
}
|
}
|
||||||
|
|
@ -60,7 +61,7 @@ fn reductions_4_to_1_bits(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_4_should_be_palette_1.png",
|
"tests/files/palette_4_should_be_palette_1.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| bit_depth::reduced_bit_depth_8_or_less(&png.raw, 1));
|
b.iter(|| bit_depth::reduced_bit_depth_8_or_less(&png.raw, 1));
|
||||||
}
|
}
|
||||||
|
|
@ -70,7 +71,7 @@ fn reductions_2_to_1_bits(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_2_should_be_palette_1.png",
|
"tests/files/palette_2_should_be_palette_1.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| bit_depth::reduced_bit_depth_8_or_less(&png.raw, 1));
|
b.iter(|| bit_depth::reduced_bit_depth_8_or_less(&png.raw, 1));
|
||||||
}
|
}
|
||||||
|
|
@ -80,7 +81,7 @@ fn reductions_grayscale_8_to_4_bits(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/grayscale_8_should_be_grayscale_4.png",
|
"tests/files/grayscale_8_should_be_grayscale_4.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| bit_depth::reduced_bit_depth_8_or_less(&png.raw, 1));
|
b.iter(|| bit_depth::reduced_bit_depth_8_or_less(&png.raw, 1));
|
||||||
}
|
}
|
||||||
|
|
@ -90,7 +91,7 @@ fn reductions_grayscale_8_to_2_bits(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/grayscale_8_should_be_grayscale_2.png",
|
"tests/files/grayscale_8_should_be_grayscale_2.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| bit_depth::reduced_bit_depth_8_or_less(&png.raw, 1));
|
b.iter(|| bit_depth::reduced_bit_depth_8_or_less(&png.raw, 1));
|
||||||
}
|
}
|
||||||
|
|
@ -100,7 +101,7 @@ fn reductions_grayscale_8_to_1_bits(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/grayscale_8_should_be_grayscale_1.png",
|
"tests/files/grayscale_8_should_be_grayscale_1.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| bit_depth::reduced_bit_depth_8_or_less(&png.raw, 1));
|
b.iter(|| bit_depth::reduced_bit_depth_8_or_less(&png.raw, 1));
|
||||||
}
|
}
|
||||||
|
|
@ -110,7 +111,7 @@ fn reductions_grayscale_4_to_2_bits(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/grayscale_4_should_be_grayscale_2.png",
|
"tests/files/grayscale_4_should_be_grayscale_2.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| bit_depth::reduced_bit_depth_8_or_less(&png.raw, 1));
|
b.iter(|| bit_depth::reduced_bit_depth_8_or_less(&png.raw, 1));
|
||||||
}
|
}
|
||||||
|
|
@ -120,7 +121,7 @@ fn reductions_grayscale_4_to_1_bits(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/grayscale_4_should_be_grayscale_1.png",
|
"tests/files/grayscale_4_should_be_grayscale_1.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| bit_depth::reduced_bit_depth_8_or_less(&png.raw, 1));
|
b.iter(|| bit_depth::reduced_bit_depth_8_or_less(&png.raw, 1));
|
||||||
}
|
}
|
||||||
|
|
@ -130,7 +131,7 @@ fn reductions_grayscale_2_to_1_bits(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/grayscale_2_should_be_grayscale_1.png",
|
"tests/files/grayscale_2_should_be_grayscale_1.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| bit_depth::reduced_bit_depth_8_or_less(&png.raw, 1));
|
b.iter(|| bit_depth::reduced_bit_depth_8_or_less(&png.raw, 1));
|
||||||
}
|
}
|
||||||
|
|
@ -138,7 +139,7 @@ fn reductions_grayscale_2_to_1_bits(b: &mut Bencher) {
|
||||||
#[bench]
|
#[bench]
|
||||||
fn reductions_rgba_to_rgb_16(b: &mut Bencher) {
|
fn reductions_rgba_to_rgb_16(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgba_16_should_be_rgb_16.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgba_16_should_be_rgb_16.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| alpha::reduced_alpha_channel(&png.raw, false));
|
b.iter(|| alpha::reduced_alpha_channel(&png.raw, false));
|
||||||
}
|
}
|
||||||
|
|
@ -146,7 +147,7 @@ fn reductions_rgba_to_rgb_16(b: &mut Bencher) {
|
||||||
#[bench]
|
#[bench]
|
||||||
fn reductions_rgba_to_rgb_8(b: &mut Bencher) {
|
fn reductions_rgba_to_rgb_8(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgba_8_should_be_rgb_8.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgba_8_should_be_rgb_8.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| alpha::reduced_alpha_channel(&png.raw, false));
|
b.iter(|| alpha::reduced_alpha_channel(&png.raw, false));
|
||||||
}
|
}
|
||||||
|
|
@ -156,7 +157,7 @@ fn reductions_rgba_to_grayscale_alpha_16(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/rgba_16_should_be_grayscale_alpha_16.png",
|
"tests/files/rgba_16_should_be_grayscale_alpha_16.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| color::reduced_rgb_to_grayscale(&png.raw));
|
b.iter(|| color::reduced_rgb_to_grayscale(&png.raw));
|
||||||
}
|
}
|
||||||
|
|
@ -166,7 +167,7 @@ fn reductions_rgba_to_grayscale_alpha_8(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/rgba_8_should_be_grayscale_alpha_8.png",
|
"tests/files/rgba_8_should_be_grayscale_alpha_8.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| color::reduced_rgb_to_grayscale(&png.raw));
|
b.iter(|| color::reduced_rgb_to_grayscale(&png.raw));
|
||||||
}
|
}
|
||||||
|
|
@ -176,7 +177,7 @@ fn reductions_rgba_to_grayscale_16(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/rgba_16_should_be_grayscale_16.png",
|
"tests/files/rgba_16_should_be_grayscale_16.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
color::reduced_rgb_to_grayscale(&png.raw)
|
color::reduced_rgb_to_grayscale(&png.raw)
|
||||||
|
|
@ -189,7 +190,7 @@ fn reductions_rgba_to_grayscale_8(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/rgba_8_should_be_grayscale_8.png",
|
"tests/files/rgba_8_should_be_grayscale_8.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
color::reduced_rgb_to_grayscale(&png.raw)
|
color::reduced_rgb_to_grayscale(&png.raw)
|
||||||
|
|
@ -202,7 +203,7 @@ fn reductions_rgb_to_grayscale_16(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/rgb_16_should_be_grayscale_16.png",
|
"tests/files/rgb_16_should_be_grayscale_16.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| color::reduced_rgb_to_grayscale(&png.raw));
|
b.iter(|| color::reduced_rgb_to_grayscale(&png.raw));
|
||||||
}
|
}
|
||||||
|
|
@ -210,7 +211,7 @@ fn reductions_rgb_to_grayscale_16(b: &mut Bencher) {
|
||||||
#[bench]
|
#[bench]
|
||||||
fn reductions_rgb_to_grayscale_8(b: &mut Bencher) {
|
fn reductions_rgb_to_grayscale_8(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_grayscale_8.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_grayscale_8.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| color::reduced_rgb_to_grayscale(&png.raw));
|
b.iter(|| color::reduced_rgb_to_grayscale(&png.raw));
|
||||||
}
|
}
|
||||||
|
|
@ -218,7 +219,7 @@ fn reductions_rgb_to_grayscale_8(b: &mut Bencher) {
|
||||||
#[bench]
|
#[bench]
|
||||||
fn reductions_rgba_to_palette_8(b: &mut Bencher) {
|
fn reductions_rgba_to_palette_8(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgba_8_should_be_palette_8.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgba_8_should_be_palette_8.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| color::reduced_to_indexed(&png.raw));
|
b.iter(|| color::reduced_to_indexed(&png.raw));
|
||||||
}
|
}
|
||||||
|
|
@ -226,7 +227,7 @@ fn reductions_rgba_to_palette_8(b: &mut Bencher) {
|
||||||
#[bench]
|
#[bench]
|
||||||
fn reductions_rgb_to_palette_8(b: &mut Bencher) {
|
fn reductions_rgb_to_palette_8(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_palette_8.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_palette_8.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| color::reduced_to_indexed(&png.raw));
|
b.iter(|| color::reduced_to_indexed(&png.raw));
|
||||||
}
|
}
|
||||||
|
|
@ -236,7 +237,7 @@ fn reductions_grayscale_8_to_palette_8(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/grayscale_8_should_be_palette_8.png",
|
"tests/files/grayscale_8_should_be_palette_8.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| color::reduced_to_indexed(&png.raw));
|
b.iter(|| color::reduced_to_indexed(&png.raw));
|
||||||
}
|
}
|
||||||
|
|
@ -246,7 +247,7 @@ fn reductions_palette_8_to_grayscale_8(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_8_should_be_grayscale_8.png",
|
"tests/files/palette_8_should_be_grayscale_8.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| color::indexed_to_channels(&png.raw));
|
b.iter(|| color::indexed_to_channels(&png.raw));
|
||||||
}
|
}
|
||||||
|
|
@ -256,7 +257,7 @@ fn reductions_palette_duplicate_reduction(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_should_be_reduced_with_dupes.png",
|
"tests/files/palette_should_be_reduced_with_dupes.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| palette::reduced_palette(&png.raw, false));
|
b.iter(|| palette::reduced_palette(&png.raw, false));
|
||||||
}
|
}
|
||||||
|
|
@ -266,7 +267,7 @@ fn reductions_palette_unused_reduction(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_should_be_reduced_with_unused.png",
|
"tests/files/palette_should_be_reduced_with_unused.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| palette::reduced_palette(&png.raw, false));
|
b.iter(|| palette::reduced_palette(&png.raw, false));
|
||||||
}
|
}
|
||||||
|
|
@ -276,7 +277,7 @@ fn reductions_palette_full_reduction(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_should_be_reduced_with_both.png",
|
"tests/files/palette_should_be_reduced_with_both.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| palette::reduced_palette(&png.raw, false));
|
b.iter(|| palette::reduced_palette(&png.raw, false));
|
||||||
}
|
}
|
||||||
|
|
@ -286,7 +287,7 @@ fn reductions_palette_sort(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_8_should_be_palette_8.png",
|
"tests/files/palette_8_should_be_palette_8.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| palette::sorted_palette(&png.raw));
|
b.iter(|| palette::sorted_palette(&png.raw));
|
||||||
}
|
}
|
||||||
|
|
@ -294,7 +295,7 @@ fn reductions_palette_sort(b: &mut Bencher) {
|
||||||
#[bench]
|
#[bench]
|
||||||
fn reductions_alpha(b: &mut Bencher) {
|
fn reductions_alpha(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgba_8_reduce_alpha.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgba_8_reduce_alpha.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| alpha::cleaned_alpha_channel(&png.raw));
|
b.iter(|| alpha::cleaned_alpha_channel(&png.raw));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,15 @@
|
||||||
extern crate oxipng;
|
extern crate oxipng;
|
||||||
extern crate test;
|
extern crate test;
|
||||||
|
|
||||||
use oxipng::{internal_tests::*, RowFilter};
|
use oxipng::internal_tests::*;
|
||||||
|
use oxipng::*;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use test::Bencher;
|
use test::Bencher;
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
fn filters_minsum(b: &mut Bencher) {
|
fn filters_minsum(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::MinSum, false);
|
png.raw.filter_image(RowFilter::MinSum, false);
|
||||||
|
|
@ -20,7 +21,7 @@ fn filters_minsum(b: &mut Bencher) {
|
||||||
#[bench]
|
#[bench]
|
||||||
fn filters_entropy(b: &mut Bencher) {
|
fn filters_entropy(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Entropy, false);
|
png.raw.filter_image(RowFilter::Entropy, false);
|
||||||
|
|
@ -30,7 +31,7 @@ fn filters_entropy(b: &mut Bencher) {
|
||||||
#[bench]
|
#[bench]
|
||||||
fn filters_bigrams(b: &mut Bencher) {
|
fn filters_bigrams(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Bigrams, false);
|
png.raw.filter_image(RowFilter::Bigrams, false);
|
||||||
|
|
@ -40,7 +41,7 @@ fn filters_bigrams(b: &mut Bencher) {
|
||||||
#[bench]
|
#[bench]
|
||||||
fn filters_bigent(b: &mut Bencher) {
|
fn filters_bigent(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::BigEnt, false);
|
png.raw.filter_image(RowFilter::BigEnt, false);
|
||||||
|
|
@ -50,7 +51,7 @@ fn filters_bigent(b: &mut Bencher) {
|
||||||
#[bench]
|
#[bench]
|
||||||
fn filters_brute(b: &mut Bencher) {
|
fn filters_brute(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Brute, false);
|
png.raw.filter_image(RowFilter::Brute, false);
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ extern crate oxipng;
|
||||||
extern crate test;
|
extern crate test;
|
||||||
|
|
||||||
use oxipng::internal_tests::*;
|
use oxipng::internal_tests::*;
|
||||||
|
use oxipng::*;
|
||||||
use std::num::NonZeroU8;
|
use std::num::NonZeroU8;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use test::Bencher;
|
use test::Bencher;
|
||||||
|
|
@ -14,7 +15,7 @@ const DEFAULT_ZOPFLI_ITERATIONS: NonZeroU8 = unsafe { NonZeroU8::new_unchecked(1
|
||||||
#[bench]
|
#[bench]
|
||||||
fn zopfli_16_bits_strategy_0(b: &mut Bencher) {
|
fn zopfli_16_bits_strategy_0(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
zopfli_deflate(png.raw.data.as_ref(), DEFAULT_ZOPFLI_ITERATIONS).ok();
|
zopfli_deflate(png.raw.data.as_ref(), DEFAULT_ZOPFLI_ITERATIONS).ok();
|
||||||
|
|
@ -24,7 +25,7 @@ fn zopfli_16_bits_strategy_0(b: &mut Bencher) {
|
||||||
#[bench]
|
#[bench]
|
||||||
fn zopfli_8_bits_strategy_0(b: &mut Bencher) {
|
fn zopfli_8_bits_strategy_0(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
zopfli_deflate(png.raw.data.as_ref(), DEFAULT_ZOPFLI_ITERATIONS).ok();
|
zopfli_deflate(png.raw.data.as_ref(), DEFAULT_ZOPFLI_ITERATIONS).ok();
|
||||||
|
|
@ -36,7 +37,7 @@ fn zopfli_4_bits_strategy_0(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_4_should_be_palette_4.png",
|
"tests/files/palette_4_should_be_palette_4.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
zopfli_deflate(png.raw.data.as_ref(), DEFAULT_ZOPFLI_ITERATIONS).ok();
|
zopfli_deflate(png.raw.data.as_ref(), DEFAULT_ZOPFLI_ITERATIONS).ok();
|
||||||
|
|
@ -48,7 +49,7 @@ fn zopfli_2_bits_strategy_0(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_2_should_be_palette_2.png",
|
"tests/files/palette_2_should_be_palette_2.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
zopfli_deflate(png.raw.data.as_ref(), DEFAULT_ZOPFLI_ITERATIONS).ok();
|
zopfli_deflate(png.raw.data.as_ref(), DEFAULT_ZOPFLI_ITERATIONS).ok();
|
||||||
|
|
@ -60,7 +61,7 @@ fn zopfli_1_bits_strategy_0(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_1_should_be_palette_1.png",
|
"tests/files/palette_1_should_be_palette_1.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
zopfli_deflate(png.raw.data.as_ref(), DEFAULT_ZOPFLI_ITERATIONS).ok();
|
zopfli_deflate(png.raw.data.as_ref(), DEFAULT_ZOPFLI_ITERATIONS).ok();
|
||||||
|
|
|
||||||
|
|
@ -70,14 +70,6 @@ impl ColorType {
|
||||||
matches!(self, ColorType::RGB { .. } | ColorType::RGBA)
|
matches!(self, ColorType::RGB { .. } | ColorType::RGBA)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn is_grayscale(&self) -> bool {
|
|
||||||
matches!(
|
|
||||||
self,
|
|
||||||
ColorType::Grayscale { .. } | ColorType::GrayscaleAlpha
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn has_alpha(&self) -> bool {
|
pub(crate) fn has_alpha(&self) -> bool {
|
||||||
matches!(self, ColorType::GrayscaleAlpha | ColorType::RGBA)
|
matches!(self, ColorType::GrayscaleAlpha | ColorType::RGBA)
|
||||||
|
|
|
||||||
|
|
@ -13,13 +13,8 @@ pub fn deflate(data: &[u8], level: u8, max_size: &AtomicMin) -> PngResult<Vec<u8
|
||||||
let len = compressor
|
let len = compressor
|
||||||
.zlib_compress(data, &mut dest)
|
.zlib_compress(data, &mut dest)
|
||||||
.map_err(|err| match err {
|
.map_err(|err| match err {
|
||||||
CompressionError::InsufficientSpace => PngError::DeflatedDataTooLong(capacity),
|
CompressionError::InsufficientSpace => PngError::DeflatedDataTooLong(capacity - 9),
|
||||||
})?;
|
})?;
|
||||||
if let Some(max) = max_size.get() {
|
|
||||||
if len > max {
|
|
||||||
return Err(PngError::DeflatedDataTooLong(max));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dest.truncate(len);
|
dest.truncate(len);
|
||||||
Ok(dest)
|
Ok(dest)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
mod deflater;
|
mod deflater;
|
||||||
|
use crate::AtomicMin;
|
||||||
|
use crate::{PngError, PngResult};
|
||||||
pub use deflater::crc32;
|
pub use deflater::crc32;
|
||||||
pub use deflater::deflate;
|
pub use deflater::deflate;
|
||||||
pub use deflater::inflate;
|
pub use deflater::inflate;
|
||||||
|
use std::{fmt, fmt::Display};
|
||||||
|
|
||||||
#[cfg(feature = "zopfli")]
|
#[cfg(feature = "zopfli")]
|
||||||
use std::num::NonZeroU8;
|
use std::num::NonZeroU8;
|
||||||
|
|
@ -27,3 +30,30 @@ pub enum Deflaters {
|
||||||
iterations: NonZeroU8,
|
iterations: NonZeroU8,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Deflaters {
|
||||||
|
pub(crate) fn deflate(self, data: &[u8], max_size: &AtomicMin) -> PngResult<Vec<u8>> {
|
||||||
|
let compressed = match self {
|
||||||
|
Self::Libdeflater { compression } => deflate(data, compression, max_size)?,
|
||||||
|
#[cfg(feature = "zopfli")]
|
||||||
|
Self::Zopfli { iterations } => zopfli_deflate(data, iterations)?,
|
||||||
|
};
|
||||||
|
if let Some(max) = max_size.get() {
|
||||||
|
if compressed.len() > max {
|
||||||
|
return Err(PngError::DeflatedDataTooLong(max));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(compressed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Deflaters {
|
||||||
|
#[inline]
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Libdeflater { compression } => Display::fmt(compression, f),
|
||||||
|
#[cfg(feature = "zopfli")]
|
||||||
|
Self::Zopfli { .. } => Display::fmt("zopfli", f),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@
|
||||||
use crate::atomicmin::AtomicMin;
|
use crate::atomicmin::AtomicMin;
|
||||||
use crate::deflate;
|
use crate::deflate;
|
||||||
use crate::filters::RowFilter;
|
use crate::filters::RowFilter;
|
||||||
use crate::png::PngData;
|
|
||||||
use crate::png::PngImage;
|
use crate::png::PngImage;
|
||||||
#[cfg(not(feature = "parallel"))]
|
#[cfg(not(feature = "parallel"))]
|
||||||
use crate::rayon;
|
use crate::rayon;
|
||||||
|
|
@ -22,7 +21,9 @@ use std::sync::atomic::Ordering::SeqCst;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub struct Candidate {
|
pub struct Candidate {
|
||||||
pub image: PngData,
|
pub image: Arc<PngImage>,
|
||||||
|
pub idat_data: Vec<u8>,
|
||||||
|
pub filtered: Vec<u8>,
|
||||||
pub filter: RowFilter,
|
pub filter: RowFilter,
|
||||||
pub is_reduction: bool,
|
pub is_reduction: bool,
|
||||||
// first wins tie-breaker
|
// first wins tie-breaker
|
||||||
|
|
@ -32,9 +33,9 @@ pub struct Candidate {
|
||||||
impl Candidate {
|
impl Candidate {
|
||||||
fn cmp_key(&self) -> impl Ord {
|
fn cmp_key(&self) -> impl Ord {
|
||||||
(
|
(
|
||||||
self.image.estimated_output_size(),
|
self.idat_data.len() + self.image.key_chunks_size(),
|
||||||
self.image.raw.data.len(),
|
self.image.data.len(),
|
||||||
self.image.raw.ihdr.bit_depth,
|
self.image.ihdr.bit_depth,
|
||||||
self.filter,
|
self.filter,
|
||||||
self.nth,
|
self.nth,
|
||||||
)
|
)
|
||||||
|
|
@ -135,17 +136,7 @@ impl Evaluator {
|
||||||
let filtered = image.filter_image(filter, optimize_alpha);
|
let filtered = image.filter_image(filter, optimize_alpha);
|
||||||
let idat_data = deflate::deflate(&filtered, compression, &best_candidate_size);
|
let idat_data = deflate::deflate(&filtered, compression, &best_candidate_size);
|
||||||
if let Ok(idat_data) = idat_data {
|
if let Ok(idat_data) = idat_data {
|
||||||
let new = Candidate {
|
let size = idat_data.len() + image.key_chunks_size();
|
||||||
image: PngData {
|
|
||||||
idat_data,
|
|
||||||
filtered,
|
|
||||||
raw: Arc::clone(&image),
|
|
||||||
},
|
|
||||||
filter,
|
|
||||||
is_reduction,
|
|
||||||
nth,
|
|
||||||
};
|
|
||||||
let size = new.image.estimated_output_size();
|
|
||||||
best_candidate_size.set_min(size);
|
best_candidate_size.set_min(size);
|
||||||
trace!(
|
trace!(
|
||||||
"Eval: {}-bit {:20} {:8} {} bytes",
|
"Eval: {}-bit {:20} {:8} {} bytes",
|
||||||
|
|
@ -154,6 +145,14 @@ impl Evaluator {
|
||||||
filter,
|
filter,
|
||||||
size
|
size
|
||||||
);
|
);
|
||||||
|
let new = Candidate {
|
||||||
|
image: image.clone(),
|
||||||
|
idat_data,
|
||||||
|
filtered,
|
||||||
|
filter,
|
||||||
|
is_reduction,
|
||||||
|
nth,
|
||||||
|
};
|
||||||
|
|
||||||
#[cfg(feature = "parallel")]
|
#[cfg(feature = "parallel")]
|
||||||
{
|
{
|
||||||
|
|
|
||||||
150
src/headers.rs
|
|
@ -1,12 +1,13 @@
|
||||||
use crate::colors::{BitDepth, ColorType};
|
use crate::colors::{BitDepth, ColorType};
|
||||||
use crate::deflate::crc32;
|
use crate::deflate::{crc32, inflate};
|
||||||
use crate::error::PngError;
|
use crate::error::PngError;
|
||||||
use crate::interlace::Interlacing;
|
use crate::interlace::Interlacing;
|
||||||
|
use crate::AtomicMin;
|
||||||
|
use crate::Deflaters;
|
||||||
use crate::PngResult;
|
use crate::PngResult;
|
||||||
use indexmap::IndexSet;
|
use indexmap::IndexSet;
|
||||||
|
use log::warn;
|
||||||
use rgb::{RGB16, RGBA8};
|
use rgb::{RGB16, RGBA8};
|
||||||
use std::io;
|
|
||||||
use std::io::{Cursor, Read};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
/// Headers from the IHDR chunk of the image
|
/// Headers from the IHDR chunk of the image
|
||||||
|
|
@ -62,21 +63,42 @@ impl IhdrData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Chunk {
|
||||||
|
pub name: [u8; 4],
|
||||||
|
pub data: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
/// Options to use for performing operations on headers (such as stripping)
|
/// Options to use when stripping chunks
|
||||||
pub enum Headers {
|
pub enum StripChunks {
|
||||||
/// None
|
/// None
|
||||||
None,
|
None,
|
||||||
/// Remove specific chunks
|
/// Remove specific chunks
|
||||||
Strip(Vec<String>),
|
Strip(IndexSet<[u8; 4]>),
|
||||||
/// Headers that won't affect rendering (all but cICP, iCCP, sBIT, sRGB, pHYs)
|
/// Remove all chunks that won't affect rendering
|
||||||
Safe,
|
Safe,
|
||||||
/// Remove all non-critical chunks except these
|
/// Remove all non-critical chunks except these
|
||||||
Keep(IndexSet<String>),
|
Keep(IndexSet<[u8; 4]>),
|
||||||
/// All non-critical headers
|
/// All non-critical chunks
|
||||||
All,
|
All,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl StripChunks {
|
||||||
|
/// List of chunks that will be kept when using the `Safe` option
|
||||||
|
pub const KEEP_SAFE: [[u8; 4]; 4] = [*b"cICP", *b"iCCP", *b"sRGB", *b"pHYs"];
|
||||||
|
|
||||||
|
pub(crate) fn keep(&self, name: &[u8; 4]) -> bool {
|
||||||
|
match &self {
|
||||||
|
StripChunks::None => true,
|
||||||
|
StripChunks::Keep(names) => names.contains(name),
|
||||||
|
StripChunks::Strip(names) => !names.contains(name),
|
||||||
|
StripChunks::Safe => Self::KEEP_SAFE.contains(name),
|
||||||
|
StripChunks::All => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn file_header_is_valid(bytes: &[u8]) -> bool {
|
pub fn file_header_is_valid(bytes: &[u8]) -> bool {
|
||||||
let expected_header: [u8; 8] = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];
|
let expected_header: [u8; 8] = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];
|
||||||
|
|
@ -85,27 +107,26 @@ pub fn file_header_is_valid(bytes: &[u8]) -> bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct RawHeader<'a> {
|
pub struct RawChunk<'a> {
|
||||||
pub name: [u8; 4],
|
pub name: [u8; 4],
|
||||||
pub data: &'a [u8],
|
pub data: &'a [u8],
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_next_header<'a>(
|
pub fn parse_next_chunk<'a>(
|
||||||
byte_data: &'a [u8],
|
byte_data: &'a [u8],
|
||||||
byte_offset: &mut usize,
|
byte_offset: &mut usize,
|
||||||
fix_errors: bool,
|
fix_errors: bool,
|
||||||
) -> PngResult<Option<RawHeader<'a>>> {
|
) -> PngResult<Option<RawChunk<'a>>> {
|
||||||
let mut rdr = Cursor::new(
|
let length = read_be_u32(
|
||||||
byte_data
|
byte_data
|
||||||
.get(*byte_offset..*byte_offset + 4)
|
.get(*byte_offset..*byte_offset + 4)
|
||||||
.ok_or(PngError::TruncatedData)?,
|
.ok_or(PngError::TruncatedData)?,
|
||||||
);
|
);
|
||||||
let length = read_be_u32(&mut rdr).unwrap();
|
|
||||||
*byte_offset += 4;
|
*byte_offset += 4;
|
||||||
|
|
||||||
let header_start = *byte_offset;
|
let chunk_start = *byte_offset;
|
||||||
let chunk_name = byte_data
|
let chunk_name = byte_data
|
||||||
.get(header_start..header_start + 4)
|
.get(chunk_start..chunk_start + 4)
|
||||||
.ok_or(PngError::TruncatedData)?;
|
.ok_or(PngError::TruncatedData)?;
|
||||||
if chunk_name == b"IEND" {
|
if chunk_name == b"IEND" {
|
||||||
// End of data
|
// End of data
|
||||||
|
|
@ -117,37 +138,34 @@ pub fn parse_next_header<'a>(
|
||||||
.get(*byte_offset..*byte_offset + length as usize)
|
.get(*byte_offset..*byte_offset + length as usize)
|
||||||
.ok_or(PngError::TruncatedData)?;
|
.ok_or(PngError::TruncatedData)?;
|
||||||
*byte_offset += length as usize;
|
*byte_offset += length as usize;
|
||||||
let mut rdr = Cursor::new(
|
let crc = read_be_u32(
|
||||||
byte_data
|
byte_data
|
||||||
.get(*byte_offset..*byte_offset + 4)
|
.get(*byte_offset..*byte_offset + 4)
|
||||||
.ok_or(PngError::TruncatedData)?,
|
.ok_or(PngError::TruncatedData)?,
|
||||||
);
|
);
|
||||||
let crc = read_be_u32(&mut rdr).unwrap();
|
|
||||||
*byte_offset += 4;
|
*byte_offset += 4;
|
||||||
|
|
||||||
let header_bytes = byte_data
|
let chunk_bytes = byte_data
|
||||||
.get(header_start..header_start + 4 + length as usize)
|
.get(chunk_start..chunk_start + 4 + length as usize)
|
||||||
.ok_or(PngError::TruncatedData)?;
|
.ok_or(PngError::TruncatedData)?;
|
||||||
if !fix_errors && crc32(header_bytes) != crc {
|
if !fix_errors && crc32(chunk_bytes) != crc {
|
||||||
return Err(PngError::new(&format!(
|
return Err(PngError::new(&format!(
|
||||||
"CRC Mismatch in {} header; May be recoverable by using --fix",
|
"CRC Mismatch in {} chunk; May be recoverable by using --fix",
|
||||||
String::from_utf8_lossy(chunk_name)
|
String::from_utf8_lossy(chunk_name)
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut name = [0_u8; 4];
|
let name: [u8; 4] = chunk_name.try_into().unwrap();
|
||||||
name.copy_from_slice(chunk_name);
|
Ok(Some(RawChunk { name, data }))
|
||||||
Ok(Some(RawHeader { name, data }))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_ihdr_header(
|
pub fn parse_ihdr_chunk(
|
||||||
byte_data: &[u8],
|
byte_data: &[u8],
|
||||||
palette_data: Option<Vec<u8>>,
|
palette_data: Option<Vec<u8>>,
|
||||||
trns_data: Option<Vec<u8>>,
|
trns_data: Option<Vec<u8>>,
|
||||||
) -> PngResult<IhdrData> {
|
) -> PngResult<IhdrData> {
|
||||||
// This eliminates bounds checks for the rest of the function
|
// This eliminates bounds checks for the rest of the function
|
||||||
let interlaced = byte_data.get(12).copied().ok_or(PngError::TruncatedData)?;
|
let interlaced = byte_data.get(12).copied().ok_or(PngError::TruncatedData)?;
|
||||||
let mut rdr = Cursor::new(&byte_data[0..8]);
|
|
||||||
Ok(IhdrData {
|
Ok(IhdrData {
|
||||||
color_type: match byte_data[9] {
|
color_type: match byte_data[9] {
|
||||||
0 => ColorType::Grayscale {
|
0 => ColorType::Grayscale {
|
||||||
|
|
@ -170,8 +188,8 @@ pub fn parse_ihdr_header(
|
||||||
_ => return Err(PngError::new("Unexpected color type in header")),
|
_ => return Err(PngError::new("Unexpected color type in header")),
|
||||||
},
|
},
|
||||||
bit_depth: byte_data[8].try_into()?,
|
bit_depth: byte_data[8].try_into()?,
|
||||||
width: read_be_u32(&mut rdr).map_err(|_| PngError::TruncatedData)?,
|
width: read_be_u32(&byte_data[0..4]),
|
||||||
height: read_be_u32(&mut rdr).map_err(|_| PngError::TruncatedData)?,
|
height: read_be_u32(&byte_data[4..8]),
|
||||||
interlaced: interlaced.try_into()?,
|
interlaced: interlaced.try_into()?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -196,8 +214,74 @@ fn palette_to_rgba(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn read_be_u32<T: AsRef<[u8]>>(rdr: &mut Cursor<T>) -> Result<u32, io::Error> {
|
fn read_be_u32(bytes: &[u8]) -> u32 {
|
||||||
let mut int_buf = [0; 4];
|
u32::from_be_bytes(bytes.try_into().unwrap())
|
||||||
rdr.read_exact(&mut int_buf)?;
|
}
|
||||||
Ok(u32::from_be_bytes(int_buf))
|
|
||||||
|
/// Extract and decompress the ICC profile from an iCCP chunk
|
||||||
|
pub fn extract_icc(iccp: &Chunk) -> Option<Vec<u8>> {
|
||||||
|
// Skip (useless) profile name
|
||||||
|
let mut data = iccp.data.as_slice();
|
||||||
|
loop {
|
||||||
|
let (&n, rest) = data.split_first()?;
|
||||||
|
data = rest;
|
||||||
|
if n == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let (&compression_method, compressed_data) = data.split_first()?;
|
||||||
|
if compression_method != 0 {
|
||||||
|
return None; // The profile is supposed to be compressed (method 0)
|
||||||
|
}
|
||||||
|
// The decompressed size is unknown so we have to guess the required buffer size
|
||||||
|
let max_size = compressed_data.len() * 2 + 1000;
|
||||||
|
match inflate(compressed_data, max_size) {
|
||||||
|
Ok(icc) => Some(icc),
|
||||||
|
Err(e) => {
|
||||||
|
// Log the error so we can know if the buffer size needs to be adjusted
|
||||||
|
warn!("Failed to decompress icc: {}", e);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Construct an iCCP chunk by compressing the ICC profile
|
||||||
|
pub fn construct_iccp(icc: &[u8], deflater: Deflaters) -> PngResult<Chunk> {
|
||||||
|
let mut compressed = deflater.deflate(icc, &AtomicMin::new(None))?;
|
||||||
|
let mut data = Vec::with_capacity(compressed.len() + 5);
|
||||||
|
data.extend(b"icc"); // Profile name - generally unused, can be anything
|
||||||
|
data.extend([0, 0]); // Null separator, zlib compression method
|
||||||
|
data.append(&mut compressed);
|
||||||
|
Ok(Chunk {
|
||||||
|
name: *b"iCCP",
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If the profile is sRGB, extracts the rendering intent value from it
|
||||||
|
pub fn srgb_rendering_intent(icc_data: &[u8]) -> Option<u8> {
|
||||||
|
let rendering_intent = *icc_data.get(67)?;
|
||||||
|
|
||||||
|
// The known profiles are the same as in libpng's `png_sRGB_checks`.
|
||||||
|
// The Profile ID header of ICC has a fixed layout,
|
||||||
|
// and is supposed to contain MD5 of profile data at this offset
|
||||||
|
match icc_data.get(84..100)? {
|
||||||
|
b"\x29\xf8\x3d\xde\xaf\xf2\x55\xae\x78\x42\xfa\xe4\xca\x83\x39\x0d"
|
||||||
|
| b"\xc9\x5b\xd6\x37\xe9\x5d\x8a\x3b\x0d\xf3\x8f\x99\xc1\x32\x03\x89"
|
||||||
|
| b"\xfc\x66\x33\x78\x37\xe2\x88\x6b\xfd\x72\xe9\x83\x82\x28\xf1\xb8"
|
||||||
|
| b"\x34\x56\x2a\xbf\x99\x4c\xcd\x06\x6d\x2c\x57\x21\xd0\xd6\x8c\x5d" => {
|
||||||
|
Some(rendering_intent)
|
||||||
|
}
|
||||||
|
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" => {
|
||||||
|
// Known-bad profiles are identified by their CRC
|
||||||
|
match (crc32(icc_data), icc_data.len()) {
|
||||||
|
(0x5d51_29ce, 3024) | (0x182e_a552, 3144) | (0xf29e_526d, 3144) => {
|
||||||
|
Some(rendering_intent)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,6 @@ pub fn interlace_image(png: &PngImage) -> PngImage {
|
||||||
interlaced: Interlacing::Adam7,
|
interlaced: Interlacing::Adam7,
|
||||||
..png.ihdr
|
..png.ihdr
|
||||||
},
|
},
|
||||||
aux_headers: png.aux_headers.clone(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -105,7 +104,6 @@ pub fn deinterlace_image(png: &PngImage) -> PngImage {
|
||||||
interlaced: Interlacing::None,
|
interlaced: Interlacing::None,
|
||||||
..png.ihdr
|
..png.ihdr
|
||||||
},
|
},
|
||||||
aux_headers: png.aux_headers.clone(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
312
src/lib.rs
|
|
@ -25,9 +25,8 @@ extern crate rayon;
|
||||||
mod rayon;
|
mod rayon;
|
||||||
|
|
||||||
use crate::atomicmin::AtomicMin;
|
use crate::atomicmin::AtomicMin;
|
||||||
use crate::deflate::{crc32, inflate};
|
|
||||||
use crate::evaluate::Evaluator;
|
use crate::evaluate::Evaluator;
|
||||||
use crate::headers::IhdrData;
|
use crate::headers::*;
|
||||||
use crate::png::PngData;
|
use crate::png::PngData;
|
||||||
use crate::png::PngImage;
|
use crate::png::PngImage;
|
||||||
use crate::reduction::*;
|
use crate::reduction::*;
|
||||||
|
|
@ -45,9 +44,9 @@ pub use crate::colors::{BitDepth, ColorType};
|
||||||
pub use crate::deflate::Deflaters;
|
pub use crate::deflate::Deflaters;
|
||||||
pub use crate::error::PngError;
|
pub use crate::error::PngError;
|
||||||
pub use crate::filters::RowFilter;
|
pub use crate::filters::RowFilter;
|
||||||
pub use crate::headers::Headers;
|
pub use crate::headers::StripChunks;
|
||||||
pub use crate::interlace::Interlacing;
|
pub use crate::interlace::Interlacing;
|
||||||
pub use indexmap::{indexset, IndexMap, IndexSet};
|
pub use indexmap::{indexset, IndexSet};
|
||||||
pub use rgb::{RGB16, RGBA8};
|
pub use rgb::{RGB16, RGBA8};
|
||||||
|
|
||||||
mod atomicmin;
|
mod atomicmin;
|
||||||
|
|
@ -67,9 +66,7 @@ mod sanity_checks;
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub mod internal_tests {
|
pub mod internal_tests {
|
||||||
pub use crate::atomicmin::*;
|
pub use crate::atomicmin::*;
|
||||||
pub use crate::colors::*;
|
|
||||||
pub use crate::deflate::*;
|
pub use crate::deflate::*;
|
||||||
pub use crate::headers::*;
|
|
||||||
pub use crate::png::*;
|
pub use crate::png::*;
|
||||||
pub use crate::reduction::*;
|
pub use crate::reduction::*;
|
||||||
#[cfg(feature = "sanity-checks")]
|
#[cfg(feature = "sanity-checks")]
|
||||||
|
|
@ -189,10 +186,10 @@ pub struct Options {
|
||||||
///
|
///
|
||||||
/// Default: `true`
|
/// Default: `true`
|
||||||
pub idat_recoding: bool,
|
pub idat_recoding: bool,
|
||||||
/// Which headers to strip from the PNG file, if any
|
/// Which chunks to strip from the PNG file, if any
|
||||||
///
|
///
|
||||||
/// Default: `None`
|
/// Default: `None`
|
||||||
pub strip: Headers,
|
pub strip: StripChunks,
|
||||||
/// Which DEFLATE algorithm to use
|
/// Which DEFLATE algorithm to use
|
||||||
///
|
///
|
||||||
/// Default: `Libdeflater`
|
/// Default: `Libdeflater`
|
||||||
|
|
@ -306,7 +303,7 @@ impl Default for Options {
|
||||||
palette_reduction: true,
|
palette_reduction: true,
|
||||||
grayscale_reduction: true,
|
grayscale_reduction: true,
|
||||||
idat_recoding: true,
|
idat_recoding: true,
|
||||||
strip: Headers::None,
|
strip: StripChunks::None,
|
||||||
deflate: Deflaters::Libdeflater { compression: 11 },
|
deflate: Deflaters::Libdeflater { compression: 11 },
|
||||||
fast_evaluation: true,
|
fast_evaluation: true,
|
||||||
timeout: None,
|
timeout: None,
|
||||||
|
|
@ -318,6 +315,7 @@ impl Default for Options {
|
||||||
/// A raw image definition which can be used to create an optimized png
|
/// A raw image definition which can be used to create an optimized png
|
||||||
pub struct RawImage {
|
pub struct RawImage {
|
||||||
png: Arc<PngImage>,
|
png: Arc<PngImage>,
|
||||||
|
aux_chunks: Vec<Chunk>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RawImage {
|
impl RawImage {
|
||||||
|
|
@ -363,35 +361,40 @@ impl RawImage {
|
||||||
interlaced: Interlacing::None,
|
interlaced: Interlacing::None,
|
||||||
},
|
},
|
||||||
data,
|
data,
|
||||||
aux_headers: IndexMap::new(),
|
|
||||||
}),
|
}),
|
||||||
|
aux_chunks: Vec::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a png chunk, such as "iTXt", to be included in the output
|
/// Add a png chunk, such as "iTXt", to be included in the output
|
||||||
pub fn add_png_chunk(&mut self, chunk_type: [u8; 4], data: Vec<u8>) {
|
pub fn add_png_chunk(&mut self, name: [u8; 4], data: Vec<u8>) {
|
||||||
// We can guarantee this will succeed - failure indicates a bug
|
self.aux_chunks.push(Chunk { name, data });
|
||||||
let png = Arc::get_mut(&mut self.png).unwrap();
|
|
||||||
png.aux_headers.insert(chunk_type, data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add an ICC profile for the image
|
/// Add an ICC profile for the image
|
||||||
pub fn add_icc_profile(&mut self, data: &[u8]) {
|
pub fn add_icc_profile(&mut self, data: &[u8]) {
|
||||||
// Compress with default compression level
|
// Compress with fastest compression level - will be recompressed during optimization
|
||||||
if let Ok(mut compressed) = deflate::deflate(data, 11, &AtomicMin::new(None)) {
|
let deflater = Deflaters::Libdeflater { compression: 1 };
|
||||||
let mut iccp = Vec::with_capacity(compressed.len() + 13);
|
if let Ok(iccp) = construct_iccp(data, deflater) {
|
||||||
iccp.extend(b"icc"); // Profile name - generally unused, can be anything
|
self.aux_chunks.push(iccp);
|
||||||
iccp.extend([0, 0]); // Null separator, zlib compression method
|
|
||||||
iccp.append(&mut compressed);
|
|
||||||
self.add_png_chunk(*b"iCCP", iccp);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create an optimized png from the raw image data using the options provided
|
/// Create an optimized png from the raw image data using the options provided
|
||||||
pub fn create_optimized_png(&self, opts: &Options) -> PngResult<Vec<u8>> {
|
pub fn create_optimized_png(&self, opts: &Options) -> PngResult<Vec<u8>> {
|
||||||
let deadline = Arc::new(Deadline::new(opts.timeout));
|
let deadline = Arc::new(Deadline::new(opts.timeout));
|
||||||
let png = optimize_raw(Arc::clone(&self.png), opts, deadline, None)
|
let mut png = optimize_raw(self.png.clone(), opts, deadline, None)
|
||||||
.ok_or_else(|| PngError::new("Failed to optimize input data"))?;
|
.ok_or_else(|| PngError::new("Failed to optimize input data"))?;
|
||||||
|
|
||||||
|
// Process aux chunks
|
||||||
|
png.aux_chunks = self
|
||||||
|
.aux_chunks
|
||||||
|
.iter()
|
||||||
|
.filter(|c| opts.strip.keep(&c.name))
|
||||||
|
.cloned()
|
||||||
|
.collect();
|
||||||
|
postprocess_chunks(&mut png, opts, &self.png.ihdr);
|
||||||
|
|
||||||
Ok(png.output())
|
Ok(png.output())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -434,7 +437,7 @@ pub fn optimize(input: &InFile, output: &OutFile, opts: &Options) -> PngResult<(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut png = PngData::from_slice(&in_data, opts.fix_errors)?;
|
let mut png = PngData::from_slice(&in_data, opts)?;
|
||||||
|
|
||||||
if opts.check {
|
if opts.check {
|
||||||
info!("Running in check mode, not optimizing");
|
info!("Running in check mode, not optimizing");
|
||||||
|
|
@ -522,7 +525,7 @@ pub fn optimize_from_memory(data: &[u8], opts: &Options) -> PngResult<Vec<u8>> {
|
||||||
let deadline = Arc::new(Deadline::new(opts.timeout));
|
let deadline = Arc::new(Deadline::new(opts.timeout));
|
||||||
|
|
||||||
let original_size = data.len();
|
let original_size = data.len();
|
||||||
let mut png = PngData::from_slice(data, opts.fix_errors)?;
|
let mut png = PngData::from_slice(data, opts)?;
|
||||||
|
|
||||||
// Run the optimizer on the decoded PNG.
|
// Run the optimizer on the decoded PNG.
|
||||||
let optimized_output = optimize_png(&mut png, data, opts, deadline)?;
|
let optimized_output = optimize_png(&mut png, data, opts, deadline)?;
|
||||||
|
|
@ -535,13 +538,7 @@ pub fn optimize_from_memory(data: &[u8], opts: &Options) -> PngResult<Vec<u8>> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, PartialOrd, Clone, Copy)]
|
type TrialResult = (RowFilter, Vec<u8>);
|
||||||
/// Defines options to be used for a single compression trial
|
|
||||||
struct TrialOptions {
|
|
||||||
pub filter: RowFilter,
|
|
||||||
pub compression: u8,
|
|
||||||
}
|
|
||||||
type TrialWithData = (TrialOptions, Vec<u8>);
|
|
||||||
|
|
||||||
/// Perform optimization on the input PNG object using the options provided
|
/// Perform optimization on the input PNG object using the options provided
|
||||||
fn optimize_png(
|
fn optimize_png(
|
||||||
|
|
@ -553,27 +550,27 @@ fn optimize_png(
|
||||||
// Print png info
|
// Print png info
|
||||||
let file_original_size = original_data.len();
|
let file_original_size = original_data.len();
|
||||||
let idat_original_size = png.idat_data.len();
|
let idat_original_size = png.idat_data.len();
|
||||||
|
let raw = png.raw.clone();
|
||||||
debug!(
|
debug!(
|
||||||
" {}x{} pixels, PNG format",
|
" {}x{} pixels, PNG format",
|
||||||
png.raw.ihdr.width, png.raw.ihdr.height
|
raw.ihdr.width, raw.ihdr.height
|
||||||
);
|
);
|
||||||
report_format(" ", &png.raw);
|
report_format(" ", &raw);
|
||||||
debug!(" IDAT size = {} bytes", idat_original_size);
|
debug!(" IDAT size = {} bytes", idat_original_size);
|
||||||
debug!(" File size = {} bytes", file_original_size);
|
debug!(" File size = {} bytes", file_original_size);
|
||||||
|
|
||||||
// Do this first so that reductions can ignore certain chunks such as bKGD
|
|
||||||
perform_strip(png, opts);
|
|
||||||
|
|
||||||
let max_size = if opts.force {
|
let max_size = if opts.force {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(png.estimated_output_size())
|
Some(png.estimated_output_size())
|
||||||
};
|
};
|
||||||
if let Some(new_png) = optimize_raw(png.raw.clone(), opts, deadline, max_size) {
|
if let Some(new_png) = optimize_raw(raw.clone(), opts, deadline, max_size) {
|
||||||
png.raw = new_png.raw;
|
png.raw = new_png.raw;
|
||||||
png.idat_data = new_png.idat_data;
|
png.idat_data = new_png.idat_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
postprocess_chunks(png, opts, &raw.ihdr);
|
||||||
|
|
||||||
let output = png.output();
|
let output = png.output();
|
||||||
|
|
||||||
if idat_original_size >= png.idat_data.len() {
|
if idat_original_size >= png.idat_data.len() {
|
||||||
|
|
@ -635,7 +632,7 @@ fn optimize_raw(
|
||||||
let mut eval_result = eval.get_best_candidate();
|
let mut eval_result = eval.get_best_candidate();
|
||||||
if let Some(ref result) = eval_result {
|
if let Some(ref result) = eval_result {
|
||||||
if result.is_reduction {
|
if result.is_reduction {
|
||||||
png = Arc::clone(&result.image.raw);
|
png = result.image.clone();
|
||||||
reduction_occurred = true;
|
reduction_occurred = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -647,7 +644,7 @@ fn optimize_raw(
|
||||||
if opts.idat_recoding || reduction_occurred {
|
if opts.idat_recoding || reduction_occurred {
|
||||||
let mut filters = opts.filter.clone();
|
let mut filters = opts.filter.clone();
|
||||||
let fast_eval = opts.fast_evaluation && (filters.len() > 1 || eval_result.is_some());
|
let fast_eval = opts.fast_evaluation && (filters.len() > 1 || eval_result.is_some());
|
||||||
let best: Option<TrialWithData> = if fast_eval {
|
let best: Option<TrialResult> = if fast_eval {
|
||||||
// Perform a fast evaluation of selected filters followed by a single main compression trial
|
// Perform a fast evaluation of selected filters followed by a single main compression trial
|
||||||
|
|
||||||
if eval_result.is_some() {
|
if eval_result.is_some() {
|
||||||
|
|
@ -659,7 +656,7 @@ fn optimize_raw(
|
||||||
trace!("Evaluating: {} filters", filters.len());
|
trace!("Evaluating: {} filters", filters.len());
|
||||||
let eval = Evaluator::new(deadline, filters, eval_compression, opts.optimize_alpha);
|
let eval = Evaluator::new(deadline, filters, eval_compression, opts.optimize_alpha);
|
||||||
if let Some(ref result) = eval_result {
|
if let Some(ref result) = eval_result {
|
||||||
eval.set_best_size(result.image.idat_data.len());
|
eval.set_best_size(result.idat_data.len());
|
||||||
}
|
}
|
||||||
eval.try_image(png.clone());
|
eval.try_image(png.clone());
|
||||||
if let Some(result) = eval.get_best_candidate() {
|
if let Some(result) = eval.get_best_candidate() {
|
||||||
|
|
@ -667,22 +664,18 @@ fn optimize_raw(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// We should have a result here - fail if not (e.g. deadline passed)
|
// We should have a result here - fail if not (e.g. deadline passed)
|
||||||
let eval_result = eval_result?;
|
let result = eval_result?;
|
||||||
|
|
||||||
let trial = TrialOptions {
|
match opts.deflate {
|
||||||
filter: eval_result.filter,
|
Deflaters::Libdeflater { compression } if compression <= eval_compression => {
|
||||||
compression: match opts.deflate {
|
// No further compression required
|
||||||
Deflaters::Libdeflater { compression } => compression,
|
Some((result.filter, result.idat_data))
|
||||||
_ => 0,
|
}
|
||||||
},
|
_ => {
|
||||||
};
|
debug!("Trying: {}", result.filter);
|
||||||
if trial.compression > 0 && trial.compression <= eval_compression {
|
let best_size = AtomicMin::new(max_size);
|
||||||
// No further compression required
|
perform_trial(&result.filtered, opts, result.filter, &best_size)
|
||||||
Some((trial, eval_result.image.idat_data))
|
}
|
||||||
} else {
|
|
||||||
debug!("Trying: {}", trial.filter);
|
|
||||||
let best_size = AtomicMin::new(max_size);
|
|
||||||
perform_trial(&eval_result.image.filtered, opts, trial, &best_size)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Perform full compression trials of selected filters and determine the best
|
// Perform full compression trials of selected filters and determine the best
|
||||||
|
|
@ -698,28 +691,16 @@ fn optimize_raw(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut results: Vec<TrialOptions> = Vec::with_capacity(filters.len());
|
debug!("Trying: {} filters", filters.len());
|
||||||
|
|
||||||
for f in &filters {
|
|
||||||
results.push(TrialOptions {
|
|
||||||
filter: *f,
|
|
||||||
compression: match opts.deflate {
|
|
||||||
Deflaters::Libdeflater { compression } => compression,
|
|
||||||
_ => 0,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
debug!("Trying: {} filters", results.len());
|
|
||||||
|
|
||||||
let best_size = AtomicMin::new(max_size);
|
let best_size = AtomicMin::new(max_size);
|
||||||
let results_iter = results.into_par_iter().with_max_len(1);
|
let results_iter = filters.into_par_iter().with_max_len(1);
|
||||||
let best = results_iter.filter_map(|trial| {
|
let best = results_iter.filter_map(|filter| {
|
||||||
if deadline.passed() {
|
if deadline.passed() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let filtered = &png.filter_image(trial.filter, opts.optimize_alpha);
|
let filtered = &png.filter_image(filter, opts.optimize_alpha);
|
||||||
perform_trial(filtered, opts, trial, &best_size)
|
perform_trial(filtered, opts, filter, &best_size)
|
||||||
});
|
});
|
||||||
best.reduce_with(|i, j| {
|
best.reduce_with(|i, j| {
|
||||||
if i.1.len() < j.1.len() || (i.1.len() == j.1.len() && i.0 < j.0) {
|
if i.1.len() < j.1.len() || (i.1.len() == j.1.len() && i.0 < j.0) {
|
||||||
|
|
@ -730,19 +711,18 @@ fn optimize_raw(
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some((trial, idat_data)) = best {
|
if let Some((filter, idat_data)) = best {
|
||||||
let image = PngData {
|
let image = PngData {
|
||||||
raw: png,
|
raw: png,
|
||||||
// The filtered data has not been retained here, but we don't need to return it
|
|
||||||
filtered: vec![],
|
|
||||||
idat_data,
|
idat_data,
|
||||||
|
aux_chunks: Vec::new(),
|
||||||
};
|
};
|
||||||
if image.estimated_output_size() < max_size.unwrap_or(usize::MAX) {
|
if image.estimated_output_size() < max_size.unwrap_or(usize::MAX) {
|
||||||
debug!("Found better combination:");
|
debug!("Found better combination:");
|
||||||
debug!(
|
debug!(
|
||||||
" zc = {} f = {:8} {} bytes",
|
" zc = {} f = {:8} {} bytes",
|
||||||
trial.compression,
|
opts.deflate,
|
||||||
trial.filter,
|
filter,
|
||||||
image.idat_data.len()
|
image.idat_data.len()
|
||||||
);
|
);
|
||||||
return Some(image);
|
return Some(image);
|
||||||
|
|
@ -751,7 +731,11 @@ fn optimize_raw(
|
||||||
} else if let Some(result) = eval_result {
|
} else if let Some(result) = eval_result {
|
||||||
// If idat_recoding is off and reductions were attempted but ended up choosing the baseline,
|
// If idat_recoding is off and reductions were attempted but ended up choosing the baseline,
|
||||||
// we should still check if the evaluator compressed the baseline smaller than the original.
|
// we should still check if the evaluator compressed the baseline smaller than the original.
|
||||||
let image = result.image;
|
let image = PngData {
|
||||||
|
raw: result.image,
|
||||||
|
idat_data: result.idat_data,
|
||||||
|
aux_chunks: Vec::new(),
|
||||||
|
};
|
||||||
if image.estimated_output_size() < max_size.unwrap_or(usize::MAX) {
|
if image.estimated_output_size() < max_size.unwrap_or(usize::MAX) {
|
||||||
debug!("Found better combination:");
|
debug!("Found better combination:");
|
||||||
debug!(
|
debug!(
|
||||||
|
|
@ -771,37 +755,26 @@ fn optimize_raw(
|
||||||
fn perform_trial(
|
fn perform_trial(
|
||||||
filtered: &[u8],
|
filtered: &[u8],
|
||||||
opts: &Options,
|
opts: &Options,
|
||||||
trial: TrialOptions,
|
filter: RowFilter,
|
||||||
best_size: &AtomicMin,
|
best_size: &AtomicMin,
|
||||||
) -> Option<TrialWithData> {
|
) -> Option<TrialResult> {
|
||||||
let new_idat = match opts.deflate {
|
match opts.deflate.deflate(filtered, best_size) {
|
||||||
Deflaters::Libdeflater { .. } => deflate::deflate(filtered, trial.compression, best_size),
|
Ok(new_idat) => {
|
||||||
#[cfg(feature = "zopfli")]
|
let bytes = new_idat.len();
|
||||||
Deflaters::Zopfli { iterations } => deflate::zopfli_deflate(filtered, iterations),
|
best_size.set_min(bytes);
|
||||||
};
|
|
||||||
|
|
||||||
// update best size or convert to error if not smaller
|
|
||||||
let new_idat = match new_idat {
|
|
||||||
Ok(n) if !best_size.set_min(n.len()) => Err(PngError::DeflatedDataTooLong(n.len())),
|
|
||||||
_ => new_idat,
|
|
||||||
};
|
|
||||||
|
|
||||||
match new_idat {
|
|
||||||
Ok(n) => {
|
|
||||||
let bytes = n.len();
|
|
||||||
trace!(
|
trace!(
|
||||||
" zc = {} f = {:8} {} bytes",
|
" zc = {} f = {:8} {} bytes",
|
||||||
trial.compression,
|
opts.deflate,
|
||||||
trial.filter,
|
filter,
|
||||||
bytes
|
bytes
|
||||||
);
|
);
|
||||||
Some((trial, n))
|
Some((filter, new_idat))
|
||||||
}
|
}
|
||||||
Err(PngError::DeflatedDataTooLong(bytes)) => {
|
Err(PngError::DeflatedDataTooLong(bytes)) => {
|
||||||
trace!(
|
trace!(
|
||||||
" zc = {} f = {:8} >{} bytes",
|
" zc = {} f = {:8} >{} bytes",
|
||||||
trial.compression,
|
opts.deflate,
|
||||||
trial.filter,
|
filter,
|
||||||
bytes,
|
bytes,
|
||||||
);
|
);
|
||||||
None
|
None
|
||||||
|
|
@ -867,100 +840,59 @@ fn report_format(prefix: &str, png: &PngImage) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Strip headers from the `PngData` object, as requested by the passed `Options`
|
/// Perform cleanup of certain chunks from the `PngData` object, after optimization has been completed
|
||||||
fn perform_strip(png: &mut PngData, opts: &Options) {
|
fn postprocess_chunks(png: &mut PngData, opts: &Options, orig_ihdr: &IhdrData) {
|
||||||
let raw = Arc::make_mut(&mut png.raw);
|
if let Some(iccp_idx) = png.aux_chunks.iter().position(|c| &c.name == b"iCCP") {
|
||||||
match opts.strip {
|
// See if we can replace an iCCP chunk with an sRGB chunk
|
||||||
// Strip headers
|
let may_replace_iccp = opts.strip != StripChunks::None && opts.strip.keep(b"sRGB");
|
||||||
Headers::None => (),
|
if may_replace_iccp && png.aux_chunks.iter().any(|c| &c.name == b"sRGB") {
|
||||||
Headers::Keep(ref hdrs) => raw
|
|
||||||
.aux_headers
|
|
||||||
.retain(|hdr, _| std::str::from_utf8(hdr).map_or(false, |name| hdrs.contains(name))),
|
|
||||||
Headers::Strip(ref hdrs) => {
|
|
||||||
for hdr in hdrs {
|
|
||||||
raw.aux_headers.remove(hdr.as_bytes());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Headers::Safe => {
|
|
||||||
const PRESERVED_HEADERS: [[u8; 4]; 5] =
|
|
||||||
[*b"cICP", *b"iCCP", *b"sBIT", *b"sRGB", *b"pHYs"];
|
|
||||||
let keys: Vec<[u8; 4]> = raw.aux_headers.keys().cloned().collect();
|
|
||||||
for hdr in &keys {
|
|
||||||
if !PRESERVED_HEADERS.contains(hdr) {
|
|
||||||
raw.aux_headers.remove(hdr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Headers::All => {
|
|
||||||
raw.aux_headers = IndexMap::new();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let may_replace_iccp = match opts.strip {
|
|
||||||
Headers::Keep(ref hdrs) => hdrs.contains("sRGB"),
|
|
||||||
Headers::Strip(ref hdrs) => !hdrs.iter().any(|v| v == "sRGB"),
|
|
||||||
Headers::Safe => true,
|
|
||||||
Headers::None | Headers::All => false,
|
|
||||||
};
|
|
||||||
|
|
||||||
if may_replace_iccp {
|
|
||||||
if raw.aux_headers.get(b"sRGB").is_some() {
|
|
||||||
// Files aren't supposed to have both chunks, so we chose to honor sRGB
|
// Files aren't supposed to have both chunks, so we chose to honor sRGB
|
||||||
raw.aux_headers.remove(b"iCCP");
|
trace!("Removing iCCP chunk due to conflict with sRGB chunk");
|
||||||
} else if let Some(intent) = raw
|
png.aux_chunks.remove(iccp_idx);
|
||||||
.aux_headers
|
} else if let Some(icc) = extract_icc(&png.aux_chunks[iccp_idx]) {
|
||||||
.get(b"iCCP")
|
let intent = if may_replace_iccp {
|
||||||
.and_then(|iccp| srgb_rendering_intent(iccp))
|
srgb_rendering_intent(&icc)
|
||||||
{
|
} else {
|
||||||
// sRGB-like profile can be safely replaced with
|
None
|
||||||
// an sRGB chunk with the same rendering intent
|
};
|
||||||
raw.aux_headers.remove(b"iCCP");
|
// sRGB-like profile can be replaced with an sRGB chunk with the same rendering intent
|
||||||
raw.aux_headers.insert(*b"sRGB", vec![intent]);
|
// Otherwise try recompressing the profile
|
||||||
}
|
if let Some(intent) = intent {
|
||||||
}
|
trace!("Replacing iCCP chunk with equivalent sRGB chunk");
|
||||||
}
|
png.aux_chunks[iccp_idx] = Chunk {
|
||||||
|
name: *b"sRGB",
|
||||||
/// If the profile is sRGB, extracts the rendering intent value from it
|
data: vec![intent],
|
||||||
fn srgb_rendering_intent(mut iccp: &[u8]) -> Option<u8> {
|
};
|
||||||
// Skip (useless) profile name
|
} else if let Ok(iccp) = construct_iccp(&icc, opts.deflate) {
|
||||||
loop {
|
let cur_len = png.aux_chunks[iccp_idx].data.len();
|
||||||
let (&n, rest) = iccp.split_first()?;
|
let new_len = iccp.data.len();
|
||||||
iccp = rest;
|
if new_len < cur_len {
|
||||||
if n == 0 {
|
debug!(
|
||||||
break;
|
"Recompressed iCCP chunk: {} ({} bytes decrease)",
|
||||||
}
|
new_len,
|
||||||
}
|
cur_len - new_len
|
||||||
|
);
|
||||||
let (&compression_method, compressed_data) = iccp.split_first()?;
|
png.aux_chunks[iccp_idx] = iccp;
|
||||||
if compression_method != 0 {
|
|
||||||
return None; // The profile is supposed to be compressed (method 0)
|
|
||||||
}
|
|
||||||
// The decompressed size is unknown so we have to guess the required buffer size
|
|
||||||
let max_size = (compressed_data.len() * 2).max(1000);
|
|
||||||
let icc_data = inflate(compressed_data, max_size).ok()?;
|
|
||||||
|
|
||||||
let rendering_intent = *icc_data.get(67)?;
|
|
||||||
|
|
||||||
// The known profiles are the same as in libpng's `png_sRGB_checks`.
|
|
||||||
// The Profile ID header of ICC has a fixed layout,
|
|
||||||
// and is supposed to contain MD5 of profile data at this offset
|
|
||||||
match icc_data.get(84..100)? {
|
|
||||||
b"\x29\xf8\x3d\xde\xaf\xf2\x55\xae\x78\x42\xfa\xe4\xca\x83\x39\x0d"
|
|
||||||
| b"\xc9\x5b\xd6\x37\xe9\x5d\x8a\x3b\x0d\xf3\x8f\x99\xc1\x32\x03\x89"
|
|
||||||
| b"\xfc\x66\x33\x78\x37\xe2\x88\x6b\xfd\x72\xe9\x83\x82\x28\xf1\xb8"
|
|
||||||
| b"\x34\x56\x2a\xbf\x99\x4c\xcd\x06\x6d\x2c\x57\x21\xd0\xd6\x8c\x5d" => {
|
|
||||||
Some(rendering_intent)
|
|
||||||
}
|
|
||||||
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" => {
|
|
||||||
// Known-bad profiles are identified by their CRC
|
|
||||||
match (crc32(&icc_data), icc_data.len()) {
|
|
||||||
(0x5d51_29ce, 3024) | (0x182e_a552, 3144) | (0xf29e_526d, 3144) => {
|
|
||||||
Some(rendering_intent)
|
|
||||||
}
|
}
|
||||||
_ => None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => None,
|
}
|
||||||
|
|
||||||
|
// If the depth/color type has changed, some chunks may be invalid and should be dropped
|
||||||
|
// While these could potentially be converted, they have no known use case today and are
|
||||||
|
// generally more trouble than they're worth
|
||||||
|
let ihdr = &png.raw.ihdr;
|
||||||
|
if orig_ihdr.bit_depth != ihdr.bit_depth || orig_ihdr.color_type != ihdr.color_type {
|
||||||
|
png.aux_chunks.retain(|c| {
|
||||||
|
let invalid = &c.name == b"bKGD" || &c.name == b"sBIT" || &c.name == b"hIST";
|
||||||
|
if invalid {
|
||||||
|
warn!(
|
||||||
|
"Removing {} chunk as it no longer matches the image data",
|
||||||
|
std::str::from_utf8(&c.name).unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
!invalid
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
63
src/main.rs
|
|
@ -17,9 +17,9 @@ use clap::{AppSettings, Arg, ArgAction, ArgMatches, Command};
|
||||||
use indexmap::IndexSet;
|
use indexmap::IndexSet;
|
||||||
use log::{error, warn};
|
use log::{error, warn};
|
||||||
use oxipng::Deflaters;
|
use oxipng::Deflaters;
|
||||||
use oxipng::Headers;
|
|
||||||
use oxipng::Options;
|
use oxipng::Options;
|
||||||
use oxipng::RowFilter;
|
use oxipng::RowFilter;
|
||||||
|
use oxipng::StripChunks;
|
||||||
use oxipng::{InFile, OutFile};
|
use oxipng::{InFile, OutFile};
|
||||||
use std::fs::DirBuilder;
|
use std::fs::DirBuilder;
|
||||||
#[cfg(feature = "zopfli")]
|
#[cfg(feature = "zopfli")]
|
||||||
|
|
@ -496,40 +496,44 @@ fn parse_opts_into_struct(
|
||||||
opts.idat_recoding = false;
|
opts.idat_recoding = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(hdrs) = matches.value_of("keep") {
|
if let Some(keep) = matches.value_of("keep") {
|
||||||
opts.strip = Headers::Keep(hdrs.split(',').map(|x| x.trim().to_owned()).collect())
|
let names = keep
|
||||||
|
.split(',')
|
||||||
|
.map(parse_chunk_name)
|
||||||
|
.collect::<Result<_, _>>()?;
|
||||||
|
opts.strip = StripChunks::Keep(names)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(hdrs) = matches.value_of("strip") {
|
if let Some(strip) = matches.value_of("strip") {
|
||||||
let hdrs = hdrs
|
if strip == "safe" {
|
||||||
.split(',')
|
opts.strip = StripChunks::Safe;
|
||||||
.map(|x| x.trim().to_owned())
|
} else if strip == "all" {
|
||||||
.collect::<Vec<String>>();
|
opts.strip = StripChunks::All;
|
||||||
if hdrs.contains(&"safe".to_owned()) || hdrs.contains(&"all".to_owned()) {
|
|
||||||
if hdrs.len() > 1 {
|
|
||||||
return Err(
|
|
||||||
"'safe' or 'all' presets for --strip should be used by themselves".to_owned(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if hdrs[0] == "safe" {
|
|
||||||
opts.strip = Headers::Safe;
|
|
||||||
} else {
|
|
||||||
opts.strip = Headers::All;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
const FORBIDDEN_CHUNKS: [[u8; 4]; 5] =
|
const FORBIDDEN_CHUNKS: [[u8; 4]; 5] =
|
||||||
[*b"IHDR", *b"IDAT", *b"tRNS", *b"PLTE", *b"IEND"];
|
[*b"IHDR", *b"IDAT", *b"tRNS", *b"PLTE", *b"IEND"];
|
||||||
for i in &hdrs {
|
let names = strip
|
||||||
if FORBIDDEN_CHUNKS.iter().any(|chunk| chunk == i.as_bytes()) {
|
.split(',')
|
||||||
return Err(format!("{} chunk is not allowed to be stripped", i));
|
.map(|x| {
|
||||||
}
|
if x == "safe" || x == "all" {
|
||||||
}
|
return Err(
|
||||||
opts.strip = Headers::Strip(hdrs);
|
"'safe' or 'all' presets for --strip should be used by themselves"
|
||||||
|
.to_owned(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let name = parse_chunk_name(x)?;
|
||||||
|
if FORBIDDEN_CHUNKS.contains(&name) {
|
||||||
|
return Err(format!("{} chunk is not allowed to be stripped", x));
|
||||||
|
}
|
||||||
|
Ok(name)
|
||||||
|
})
|
||||||
|
.collect::<Result<_, _>>()?;
|
||||||
|
opts.strip = StripChunks::Strip(names);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if matches.is_present("strip-safe") {
|
if matches.is_present("strip-safe") {
|
||||||
opts.strip = Headers::Safe;
|
opts.strip = StripChunks::Safe;
|
||||||
}
|
}
|
||||||
|
|
||||||
if matches.is_present("zopfli") {
|
if matches.is_present("zopfli") {
|
||||||
|
|
@ -556,6 +560,13 @@ fn parse_opts_into_struct(
|
||||||
Ok((out_file, out_dir, opts))
|
Ok((out_file, out_dir, opts))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_chunk_name(name: &str) -> Result<[u8; 4], String> {
|
||||||
|
name.trim()
|
||||||
|
.as_bytes()
|
||||||
|
.try_into()
|
||||||
|
.map_err(|_| format!("Invalid chunk name {}", name))
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_numeric_range_opts(
|
fn parse_numeric_range_opts(
|
||||||
input: &str,
|
input: &str,
|
||||||
min_value: u8,
|
min_value: u8,
|
||||||
|
|
|
||||||
143
src/png/mod.rs
|
|
@ -4,8 +4,8 @@ use crate::error::PngError;
|
||||||
use crate::filters::*;
|
use crate::filters::*;
|
||||||
use crate::headers::*;
|
use crate::headers::*;
|
||||||
use crate::interlace::{deinterlace_image, interlace_image, Interlacing};
|
use crate::interlace::{deinterlace_image, interlace_image, Interlacing};
|
||||||
|
use crate::Options;
|
||||||
use bitvec::bitarr;
|
use bitvec::bitarr;
|
||||||
use indexmap::IndexMap;
|
|
||||||
use libdeflater::{CompressionLvl, Compressor};
|
use libdeflater::{CompressionLvl, Compressor};
|
||||||
use rgb::ComponentSlice;
|
use rgb::ComponentSlice;
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
|
|
@ -30,8 +30,6 @@ pub struct PngImage {
|
||||||
pub ihdr: IhdrData,
|
pub ihdr: IhdrData,
|
||||||
/// The uncompressed, unfiltered data from the IDAT chunk
|
/// The uncompressed, unfiltered data from the IDAT chunk
|
||||||
pub data: Vec<u8>,
|
pub data: Vec<u8>,
|
||||||
/// All non-critical headers from the PNG are stored here
|
|
||||||
pub aux_headers: IndexMap<[u8; 4], Vec<u8>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Contains all data relevant to a PNG image
|
/// Contains all data relevant to a PNG image
|
||||||
|
|
@ -41,17 +39,17 @@ pub struct PngData {
|
||||||
pub raw: Arc<PngImage>,
|
pub raw: Arc<PngImage>,
|
||||||
/// The filtered and compressed data of the IDAT chunk
|
/// The filtered and compressed data of the IDAT chunk
|
||||||
pub idat_data: Vec<u8>,
|
pub idat_data: Vec<u8>,
|
||||||
/// The filtered, uncompressed data of the IDAT chunk
|
/// All non-critical chunks from the PNG are stored here
|
||||||
pub filtered: Vec<u8>,
|
pub aux_chunks: Vec<Chunk>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PngData {
|
impl PngData {
|
||||||
/// Create a new `PngData` struct by opening a file
|
/// Create a new `PngData` struct by opening a file
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn new(filepath: &Path, fix_errors: bool) -> Result<Self, PngError> {
|
pub fn new(filepath: &Path, opts: &Options) -> Result<Self, PngError> {
|
||||||
let byte_data = Self::read_file(filepath)?;
|
let byte_data = Self::read_file(filepath)?;
|
||||||
|
|
||||||
Self::from_slice(&byte_data, fix_errors)
|
Self::from_slice(&byte_data, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_file(filepath: &Path) -> Result<Vec<u8>, PngError> {
|
pub fn read_file(filepath: &Path) -> Result<Vec<u8>, PngError> {
|
||||||
|
|
@ -80,7 +78,7 @@ impl PngData {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new `PngData` struct by reading a slice
|
/// Create a new `PngData` struct by reading a slice
|
||||||
pub fn from_slice(byte_data: &[u8], fix_errors: bool) -> Result<Self, PngError> {
|
pub fn from_slice(byte_data: &[u8], opts: &Options) -> Result<Self, PngError> {
|
||||||
let mut byte_offset: usize = 0;
|
let mut byte_offset: usize = 0;
|
||||||
// Test that png header is valid
|
// Test that png header is valid
|
||||||
let header = byte_data.get(0..8).ok_or(PngError::TruncatedData)?;
|
let header = byte_data.get(0..8).ok_or(PngError::TruncatedData)?;
|
||||||
|
|
@ -88,71 +86,65 @@ impl PngData {
|
||||||
return Err(PngError::NotPNG);
|
return Err(PngError::NotPNG);
|
||||||
}
|
}
|
||||||
byte_offset += 8;
|
byte_offset += 8;
|
||||||
// Read the data headers
|
|
||||||
let mut aux_headers: IndexMap<[u8; 4], Vec<u8>> = IndexMap::new();
|
// Read the data chunks
|
||||||
let mut idat_headers: Vec<u8> = Vec::new();
|
let mut idat_data: Vec<u8> = Vec::new();
|
||||||
while let Some(header) = parse_next_header(byte_data, &mut byte_offset, fix_errors)? {
|
let mut key_chunks: FxHashMap<[u8; 4], Vec<u8>> = FxHashMap::default();
|
||||||
match &header.name {
|
let mut aux_chunks: Vec<Chunk> = Vec::new();
|
||||||
b"IDAT" => idat_headers.extend_from_slice(header.data),
|
while let Some(chunk) = parse_next_chunk(byte_data, &mut byte_offset, opts.fix_errors)? {
|
||||||
|
match &chunk.name {
|
||||||
|
b"IDAT" => idat_data.extend_from_slice(chunk.data),
|
||||||
b"acTL" => return Err(PngError::APNGNotSupported),
|
b"acTL" => return Err(PngError::APNGNotSupported),
|
||||||
|
b"IHDR" | b"PLTE" | b"tRNS" => {
|
||||||
|
key_chunks.insert(chunk.name, chunk.data.to_owned());
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
aux_headers.insert(header.name, header.data.to_owned());
|
if opts.strip.keep(&chunk.name) {
|
||||||
|
aux_chunks.push(Chunk {
|
||||||
|
name: chunk.name,
|
||||||
|
data: chunk.data.to_owned(),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Parse the headers into our PngData
|
|
||||||
if idat_headers.is_empty() {
|
// Parse the chunks into our PngData
|
||||||
|
if idat_data.is_empty() {
|
||||||
return Err(PngError::ChunkMissing("IDAT"));
|
return Err(PngError::ChunkMissing("IDAT"));
|
||||||
}
|
}
|
||||||
let ihdr = match aux_headers.remove(b"IHDR") {
|
let ihdr_chunk = match key_chunks.remove(b"IHDR") {
|
||||||
Some(ihdr) => ihdr,
|
Some(ihdr) => ihdr,
|
||||||
None => return Err(PngError::ChunkMissing("IHDR")),
|
None => return Err(PngError::ChunkMissing("IHDR")),
|
||||||
};
|
};
|
||||||
let ihdr_header = parse_ihdr_header(
|
let ihdr = parse_ihdr_chunk(
|
||||||
&ihdr,
|
&ihdr_chunk,
|
||||||
aux_headers.remove(b"PLTE"),
|
key_chunks.remove(b"PLTE"),
|
||||||
aux_headers.remove(b"tRNS"),
|
key_chunks.remove(b"tRNS"),
|
||||||
)?;
|
)?;
|
||||||
let raw_data = deflate::inflate(idat_headers.as_ref(), ihdr_header.raw_data_size())?;
|
let raw_data = deflate::inflate(idat_data.as_ref(), ihdr.raw_data_size())?;
|
||||||
|
|
||||||
// Reject files with incorrect width/height or truncated data
|
// Reject files with incorrect width/height or truncated data
|
||||||
if raw_data.len() != ihdr_header.raw_data_size() {
|
if raw_data.len() != ihdr.raw_data_size() {
|
||||||
return Err(PngError::TruncatedData);
|
return Err(PngError::TruncatedData);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut raw = PngImage {
|
let mut raw = PngImage {
|
||||||
ihdr: ihdr_header,
|
ihdr,
|
||||||
data: raw_data,
|
data: raw_data,
|
||||||
aux_headers,
|
|
||||||
};
|
};
|
||||||
let unfiltered = raw.unfilter_image()?;
|
raw.data = raw.unfilter_image()?;
|
||||||
// Return the PngData
|
// Return the PngData
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
idat_data: idat_headers,
|
idat_data,
|
||||||
filtered: std::mem::replace(&mut raw.data, unfiltered),
|
|
||||||
raw: Arc::new(raw),
|
raw: Arc::new(raw),
|
||||||
|
aux_chunks,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return an estimate of the output size
|
/// Return an estimate of the output size which can help with evaluation of very small data
|
||||||
pub fn estimated_output_size(&self) -> usize {
|
pub fn estimated_output_size(&self) -> usize {
|
||||||
// Add the size of the PLTE and tRNS chunks to the compressed idat size
|
self.idat_data.len() + self.raw.key_chunks_size()
|
||||||
// This can help with evaluation of very small data
|
|
||||||
let size = self.idat_data.len();
|
|
||||||
size + match &self.raw.ihdr.color_type {
|
|
||||||
ColorType::Indexed { palette } => {
|
|
||||||
let plte = 12 + palette.len() * 3;
|
|
||||||
let trns = palette.iter().filter(|p| p.a != 255).count();
|
|
||||||
if trns != 0 {
|
|
||||||
plte + 12 + trns
|
|
||||||
} else {
|
|
||||||
plte
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ColorType::Grayscale { transparent_shade } if transparent_shade.is_some() => 12 + 2,
|
|
||||||
ColorType::RGB { transparent_color } if transparent_color.is_some() => 12 + 6,
|
|
||||||
_ => 0,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Format the `PngData` struct into a valid PNG bytestream
|
/// Format the `PngData` struct into a valid PNG bytestream
|
||||||
|
|
@ -173,14 +165,13 @@ impl PngData {
|
||||||
ihdr_data.write_all(&[0]).ok(); // Filter method -- 5-way adaptive filtering
|
ihdr_data.write_all(&[0]).ok(); // Filter method -- 5-way adaptive filtering
|
||||||
ihdr_data.write_all(&[self.raw.ihdr.interlaced as u8]).ok();
|
ihdr_data.write_all(&[self.raw.ihdr.interlaced as u8]).ok();
|
||||||
write_png_block(b"IHDR", &ihdr_data, &mut output);
|
write_png_block(b"IHDR", &ihdr_data, &mut output);
|
||||||
// Ancillary headers
|
// Ancillary chunks
|
||||||
for (key, header) in self
|
for chunk in self
|
||||||
.raw
|
.aux_chunks
|
||||||
.aux_headers
|
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|&(key, _)| !(key == b"bKGD" || key == b"hIST" || key == b"tRNS"))
|
.filter(|c| !(&c.name == b"bKGD" || &c.name == b"hIST" || &c.name == b"tRNS"))
|
||||||
{
|
{
|
||||||
write_png_block(key, header, &mut output);
|
write_png_block(&chunk.name, &chunk.data, &mut output);
|
||||||
}
|
}
|
||||||
// Palette and transparency
|
// Palette and transparency
|
||||||
match &self.raw.ihdr.color_type {
|
match &self.raw.ihdr.color_type {
|
||||||
|
|
@ -210,14 +201,13 @@ impl PngData {
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
// Special ancillary headers that need to come after PLTE but before IDAT
|
// Special ancillary chunks that need to come after PLTE but before IDAT
|
||||||
for (key, header) in self
|
for chunk in self
|
||||||
.raw
|
.aux_chunks
|
||||||
.aux_headers
|
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|&(key, _)| key == b"bKGD" || key == b"hIST" || key == b"tRNS")
|
.filter(|c| &c.name == b"bKGD" || &c.name == b"hIST" || &c.name == b"tRNS")
|
||||||
{
|
{
|
||||||
write_png_block(key, header, &mut output);
|
write_png_block(&chunk.name, &chunk.data, &mut output);
|
||||||
}
|
}
|
||||||
// IDAT data
|
// IDAT data
|
||||||
write_png_block(b"IDAT", &self.idat_data, &mut output);
|
write_png_block(b"IDAT", &self.idat_data, &mut output);
|
||||||
|
|
@ -265,6 +255,24 @@ impl PngImage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Calculate the size of the PLTE and tRNS chunks
|
||||||
|
pub fn key_chunks_size(&self) -> usize {
|
||||||
|
match &self.ihdr.color_type {
|
||||||
|
ColorType::Indexed { palette } => {
|
||||||
|
let plte = 12 + palette.len() * 3;
|
||||||
|
let trns = palette.iter().filter(|p| p.a != 255).count();
|
||||||
|
if trns != 0 {
|
||||||
|
plte + 12 + trns
|
||||||
|
} else {
|
||||||
|
plte
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ColorType::Grayscale { transparent_shade } if transparent_shade.is_some() => 12 + 2,
|
||||||
|
ColorType::RGB { transparent_color } if transparent_color.is_some() => 12 + 6,
|
||||||
|
_ => 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Return an iterator over the scanlines of the image
|
/// Return an iterator over the scanlines of the image
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn scan_lines(&self, has_filter: bool) -> ScanLines<'_> {
|
pub fn scan_lines(&self, has_filter: bool) -> ScanLines<'_> {
|
||||||
|
|
@ -451,14 +459,15 @@ impl PngImage {
|
||||||
filtered
|
filtered
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn write_png_block(key: &[u8], header: &[u8], output: &mut Vec<u8>) {
|
|
||||||
let mut header_data = Vec::with_capacity(header.len() + 4);
|
fn write_png_block(key: &[u8], chunk: &[u8], output: &mut Vec<u8>) {
|
||||||
header_data.extend_from_slice(key);
|
let mut chunk_data = Vec::with_capacity(chunk.len() + 4);
|
||||||
header_data.extend_from_slice(header);
|
chunk_data.extend_from_slice(key);
|
||||||
output.reserve(header_data.len() + 8);
|
chunk_data.extend_from_slice(chunk);
|
||||||
output.extend_from_slice(&(header_data.len() as u32 - 4).to_be_bytes());
|
output.reserve(chunk_data.len() + 8);
|
||||||
let crc = deflate::crc32(&header_data);
|
output.extend_from_slice(&(chunk_data.len() as u32 - 4).to_be_bytes());
|
||||||
output.append(&mut header_data);
|
let crc = deflate::crc32(&chunk_data);
|
||||||
|
output.append(&mut chunk_data);
|
||||||
output.extend_from_slice(&crc.to_be_bytes());
|
output.extend_from_slice(&crc.to_be_bytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,6 @@ pub fn cleaned_alpha_channel(png: &PngImage) -> Option<PngImage> {
|
||||||
Some(PngImage {
|
Some(PngImage {
|
||||||
data: reduced,
|
data: reduced,
|
||||||
ihdr: png.ihdr.clone(),
|
ihdr: png.ihdr.clone(),
|
||||||
aux_headers: png.aux_headers.clone(),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -88,20 +87,11 @@ pub fn reduced_alpha_channel(png: &PngImage, optimize_alpha: bool) -> Option<Png
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut aux_headers = png.aux_headers.clone();
|
|
||||||
// sBIT contains information about alpha channel's original depth,
|
|
||||||
// and alpha has just been removed
|
|
||||||
if let Some(sbit_header) = png.aux_headers.get(b"sBIT") {
|
|
||||||
// Some programs save the sBIT header as RGB even if the image is RGBA.
|
|
||||||
aux_headers.insert(*b"sBIT", sbit_header.iter().cloned().take(3).collect());
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(PngImage {
|
Some(PngImage {
|
||||||
data: raw_data,
|
data: raw_data,
|
||||||
ihdr: IhdrData {
|
ihdr: IhdrData {
|
||||||
color_type: target_color_type,
|
color_type: target_color_type,
|
||||||
..png.ihdr
|
..png.ihdr
|
||||||
},
|
},
|
||||||
aux_headers,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@ pub fn reduced_bit_depth_16_to_8(png: &PngImage) -> Option<PngImage> {
|
||||||
bit_depth: BitDepth::Eight,
|
bit_depth: BitDepth::Eight,
|
||||||
..png.ihdr
|
..png.ihdr
|
||||||
},
|
},
|
||||||
aux_headers: png.aux_headers.clone(),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -37,37 +36,22 @@ pub fn reduced_bit_depth_8_or_less(png: &PngImage, mut minimum_bits: usize) -> O
|
||||||
// Calculate the current number of pixels per byte
|
// Calculate the current number of pixels per byte
|
||||||
let ppb = 8 / bit_depth;
|
let ppb = 8 / bit_depth;
|
||||||
|
|
||||||
if let ColorType::Indexed { .. } = png.ihdr.color_type {
|
if let ColorType::Indexed { palette } = &png.ihdr.color_type {
|
||||||
for line in png.scan_lines(false) {
|
// We can easily determine minimum depth by the palette size
|
||||||
let line_max = line
|
let required_bits = match palette.len() {
|
||||||
.data
|
0..=2 => 1,
|
||||||
.iter()
|
3..=4 => 2,
|
||||||
.map(|&byte| match png.ihdr.bit_depth {
|
5..=16 => 4,
|
||||||
BitDepth::Two => (byte & 0x3)
|
_ => 8,
|
||||||
.max((byte >> 2) & 0x3)
|
};
|
||||||
.max((byte >> 4) & 0x3)
|
if required_bits >= bit_depth {
|
||||||
.max(byte >> 6),
|
// Not reducable
|
||||||
BitDepth::Four => (byte & 0xF).max(byte >> 4),
|
return None;
|
||||||
_ => byte,
|
} else if required_bits > minimum_bits {
|
||||||
})
|
minimum_bits = required_bits;
|
||||||
.max()
|
|
||||||
.unwrap_or(0);
|
|
||||||
let required_bits = match line_max {
|
|
||||||
x if x > 0x0F => 8,
|
|
||||||
x if x > 0x03 => 4,
|
|
||||||
x if x > 0x01 => 2,
|
|
||||||
_ => 1,
|
|
||||||
};
|
|
||||||
if required_bits > minimum_bits {
|
|
||||||
minimum_bits = required_bits;
|
|
||||||
if minimum_bits >= bit_depth {
|
|
||||||
// Not reducable
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Checking for grayscale depth reduction is quite different than for indexed
|
// Finding minimum depth for grayscale is much more complicated
|
||||||
let mut mask = (1 << minimum_bits) - 1;
|
let mut mask = (1 << minimum_bits) - 1;
|
||||||
let mut divisions = 1..(bit_depth / minimum_bits);
|
let mut divisions = 1..(bit_depth / minimum_bits);
|
||||||
for &b in &png.data {
|
for &b in &png.data {
|
||||||
|
|
@ -155,6 +139,5 @@ pub fn reduced_bit_depth_8_or_less(png: &PngImage, mut minimum_bits: usize) -> O
|
||||||
bit_depth: (minimum_bits as u8).try_into().unwrap(),
|
bit_depth: (minimum_bits as u8).try_into().unwrap(),
|
||||||
..png.ihdr
|
..png.ihdr
|
||||||
},
|
},
|
||||||
aux_headers: png.aux_headers.clone(),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use crate::headers::IhdrData;
|
||||||
use crate::png::PngImage;
|
use crate::png::PngImage;
|
||||||
use indexmap::IndexSet;
|
use indexmap::IndexSet;
|
||||||
use rgb::alt::Gray;
|
use rgb::alt::Gray;
|
||||||
use rgb::{ComponentMap, ComponentSlice, FromSlice, RGB, RGBA, RGBA8};
|
use rgb::{ComponentMap, ComponentSlice, FromSlice, RGB, RGBA};
|
||||||
use rustc_hash::FxHasher;
|
use rustc_hash::FxHasher;
|
||||||
use std::hash::{BuildHasherDefault, Hash};
|
use std::hash::{BuildHasherDefault, Hash};
|
||||||
|
|
||||||
|
|
@ -41,7 +41,7 @@ pub fn reduced_to_indexed(png: &PngImage) -> Option<PngImage> {
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut raw_data = Vec::with_capacity(png.data.len() / png.channels_per_pixel());
|
let mut raw_data = Vec::with_capacity(png.data.len() / png.channels_per_pixel());
|
||||||
let mut palette: Vec<_> = match png.ihdr.color_type {
|
let palette: Vec<_> = match png.ihdr.color_type {
|
||||||
ColorType::Grayscale { transparent_shade } => {
|
ColorType::Grayscale { transparent_shade } => {
|
||||||
let pmap = build_palette(png.data.as_gray().iter().cloned(), &mut raw_data)?;
|
let pmap = build_palette(png.data.as_gray().iter().cloned(), &mut raw_data)?;
|
||||||
// Convert the Gray16 transparency to Gray8
|
// Convert the Gray16 transparency to Gray8
|
||||||
|
|
@ -81,51 +81,12 @@ pub fn reduced_to_indexed(png: &PngImage) -> Option<PngImage> {
|
||||||
_ => return None,
|
_ => return None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut aux_headers = png.aux_headers.clone();
|
|
||||||
if let Some(bkgd_header) = aux_headers.remove(b"bKGD") {
|
|
||||||
let bg = if png.ihdr.color_type.is_rgb() && bkgd_header.len() == 6 {
|
|
||||||
// In bKGD 16-bit values are used even for 8-bit images
|
|
||||||
Some(RGBA8::new(
|
|
||||||
bkgd_header[1],
|
|
||||||
bkgd_header[3],
|
|
||||||
bkgd_header[5],
|
|
||||||
255,
|
|
||||||
))
|
|
||||||
} else if png.ihdr.color_type.is_grayscale() && bkgd_header.len() == 2 {
|
|
||||||
Some(RGBA8::new(
|
|
||||||
bkgd_header[1],
|
|
||||||
bkgd_header[1],
|
|
||||||
bkgd_header[1],
|
|
||||||
255,
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
if let Some(bg) = bg {
|
|
||||||
let idx = palette.iter().position(|&px| px == bg).or_else(|| {
|
|
||||||
if palette.len() < 256 {
|
|
||||||
palette.push(bg);
|
|
||||||
Some(palette.len() - 1)
|
|
||||||
} else {
|
|
||||||
None // No space in palette to store the bg as an index
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
aux_headers.insert(*b"bKGD", vec![idx as u8]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(sbit_header) = png.aux_headers.get(b"sBIT") {
|
|
||||||
// Some programs save the sBIT header as RGB even if the image is RGBA.
|
|
||||||
aux_headers.insert(*b"sBIT", sbit_header.iter().cloned().take(3).collect());
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(PngImage {
|
Some(PngImage {
|
||||||
data: raw_data,
|
data: raw_data,
|
||||||
ihdr: IhdrData {
|
ihdr: IhdrData {
|
||||||
color_type: ColorType::Indexed { palette },
|
color_type: ColorType::Indexed { palette },
|
||||||
..png.ihdr
|
..png.ihdr
|
||||||
},
|
},
|
||||||
aux_headers,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -150,18 +111,6 @@ pub fn reduced_rgb_to_grayscale(png: &PngImage) -> Option<PngImage> {
|
||||||
reduced.extend_from_slice(&pixel[last_color..]);
|
reduced.extend_from_slice(&pixel[last_color..]);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut aux_headers = png.aux_headers.clone();
|
|
||||||
if let Some(sbit_header) = png.aux_headers.get(b"sBIT") {
|
|
||||||
if let Some(&byte) = sbit_header.first() {
|
|
||||||
aux_headers.insert(*b"sBIT", vec![byte]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(bkgd_header) = png.aux_headers.get(b"bKGD") {
|
|
||||||
if let Some(b) = bkgd_header.get(0..2) {
|
|
||||||
aux_headers.insert(*b"bKGD", b.to_owned());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let color_type = match png.ihdr.color_type {
|
let color_type = match png.ihdr.color_type {
|
||||||
ColorType::RGB { transparent_color } => ColorType::Grayscale {
|
ColorType::RGB { transparent_color } => ColorType::Grayscale {
|
||||||
// Copy the transparent component if it is also gray
|
// Copy the transparent component if it is also gray
|
||||||
|
|
@ -178,7 +127,6 @@ pub fn reduced_rgb_to_grayscale(png: &PngImage) -> Option<PngImage> {
|
||||||
color_type,
|
color_type,
|
||||||
..png.ihdr
|
..png.ihdr
|
||||||
},
|
},
|
||||||
aux_headers,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -223,25 +171,11 @@ pub fn indexed_to_channels(png: &PngImage) -> Option<PngImage> {
|
||||||
data.extend_from_slice(&color.as_slice()[ch_start..=ch_end]);
|
data.extend_from_slice(&color.as_slice()[ch_start..=ch_end]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update bKGD if it exists
|
|
||||||
let mut aux_headers = png.aux_headers.clone();
|
|
||||||
if let Some(idx) = aux_headers.remove(b"bKGD").and_then(|b| b.first().cloned()) {
|
|
||||||
if let Some(color) = palette.get(idx as usize) {
|
|
||||||
let bkgd = if is_gray {
|
|
||||||
vec![0, color.r]
|
|
||||||
} else {
|
|
||||||
vec![0, color.r, 0, color.g, 0, color.b]
|
|
||||||
};
|
|
||||||
aux_headers.insert(*b"bKGD", bkgd);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(PngImage {
|
Some(PngImage {
|
||||||
ihdr: IhdrData {
|
ihdr: IhdrData {
|
||||||
color_type,
|
color_type,
|
||||||
..png.ihdr
|
..png.ihdr
|
||||||
},
|
},
|
||||||
data,
|
data,
|
||||||
aux_headers,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,15 +30,6 @@ pub fn reduced_palette(png: &PngImage, optimize_alpha: bool) -> Option<PngImage>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update bKGD if it exists, ensuring it comes last in the palette if otherwise unused
|
|
||||||
let mut aux_headers = png.aux_headers.clone();
|
|
||||||
if let Some(idx) = aux_headers.remove(b"bKGD").and_then(|b| b.first().cloned()) {
|
|
||||||
if let Some(&color) = palette.get(idx as usize) {
|
|
||||||
let idx = add_color_to_set(color, &mut condensed, optimize_alpha);
|
|
||||||
aux_headers.insert(*b"bKGD", vec![idx]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let data = if did_change {
|
let data = if did_change {
|
||||||
// Reassign data bytes to new indices
|
// Reassign data bytes to new indices
|
||||||
let byte_map = palette_map_to_byte_map(png.ihdr.bit_depth, &palette_map);
|
let byte_map = palette_map_to_byte_map(png.ihdr.bit_depth, &palette_map);
|
||||||
|
|
@ -59,7 +50,6 @@ pub fn reduced_palette(png: &PngImage, optimize_alpha: bool) -> Option<PngImage>
|
||||||
..png.ihdr
|
..png.ihdr
|
||||||
},
|
},
|
||||||
data,
|
data,
|
||||||
aux_headers,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -145,15 +135,6 @@ pub fn sorted_palette(png: &PngImage) -> Option<PngImage> {
|
||||||
|
|
||||||
let mut enumerated: Vec<_> = palette.iter().enumerate().collect();
|
let mut enumerated: Vec<_> = palette.iter().enumerate().collect();
|
||||||
|
|
||||||
// If the background is the last entry in the palette we should make sure it stays last
|
|
||||||
// Otherwise an entry that's unused by the idat could prevent reduction to a lower depth
|
|
||||||
let mut aux_headers = png.aux_headers.clone();
|
|
||||||
let bkgd_idx = aux_headers.remove(b"bKGD").and_then(|b| b.first().cloned());
|
|
||||||
let bkgd_last = match bkgd_idx {
|
|
||||||
Some(idx) if idx as usize + 1 == palette.len() => enumerated.pop(),
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Sort the palette
|
// Sort the palette
|
||||||
enumerated.sort_by(|a, b| {
|
enumerated.sort_by(|a, b| {
|
||||||
// Sort by ascending alpha and descending luma
|
// Sort by ascending alpha and descending luma
|
||||||
|
|
@ -167,10 +148,6 @@ pub fn sorted_palette(png: &PngImage) -> Option<PngImage> {
|
||||||
color_val(a.1).cmp(&color_val(b.1))
|
color_val(a.1).cmp(&color_val(b.1))
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some(bkgd) = bkgd_last {
|
|
||||||
enumerated.push(bkgd);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract the new palette and determine if anything changed
|
// Extract the new palette and determine if anything changed
|
||||||
let (old_map, palette): (Vec<_>, Vec<RGBA8>) = enumerated.into_iter().unzip();
|
let (old_map, palette): (Vec<_>, Vec<RGBA8>) = enumerated.into_iter().unzip();
|
||||||
if old_map.iter().enumerate().all(|(a, b)| a == *b) {
|
if old_map.iter().enumerate().all(|(a, b)| a == *b) {
|
||||||
|
|
@ -185,17 +162,11 @@ pub fn sorted_palette(png: &PngImage) -> Option<PngImage> {
|
||||||
let byte_map = palette_map_to_byte_map(png.ihdr.bit_depth, &new_map);
|
let byte_map = palette_map_to_byte_map(png.ihdr.bit_depth, &new_map);
|
||||||
let data = png.data.iter().map(|&b| byte_map[b as usize]).collect();
|
let data = png.data.iter().map(|&b| byte_map[b as usize]).collect();
|
||||||
|
|
||||||
// Update bKGD if it exists
|
|
||||||
if let Some(idx) = bkgd_idx.map(|idx| new_map[idx as usize]) {
|
|
||||||
aux_headers.insert(*b"bKGD", vec![idx]);
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(PngImage {
|
Some(PngImage {
|
||||||
ihdr: IhdrData {
|
ihdr: IhdrData {
|
||||||
color_type: ColorType::Indexed { palette },
|
color_type: ColorType::Indexed { palette },
|
||||||
..png.ihdr
|
..png.ihdr
|
||||||
},
|
},
|
||||||
data,
|
data,
|
||||||
aux_headers,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
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 indexmap::IndexSet;
|
||||||
use oxipng::{internal_tests::*, RowFilter};
|
use oxipng::internal_tests::*;
|
||||||
use oxipng::{InFile, OutFile};
|
use oxipng::*;
|
||||||
use std::fs::remove_file;
|
use std::fs::remove_file;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
@ -37,7 +37,7 @@ fn test_it_converts(
|
||||||
let input = PathBuf::from(input);
|
let input = PathBuf::from(input);
|
||||||
|
|
||||||
let (output, mut opts) = get_opts(&input);
|
let (output, mut opts) = get_opts(&input);
|
||||||
let png = PngData::new(&input, opts.fix_errors).unwrap();
|
let png = PngData::new(&input, &opts).unwrap();
|
||||||
opts.filter = IndexSet::new();
|
opts.filter = IndexSet::new();
|
||||||
opts.filter.insert(filter);
|
opts.filter.insert(filter);
|
||||||
assert_eq!(png.raw.ihdr.color_type.png_header_code(), color_type_in);
|
assert_eq!(png.raw.ihdr.color_type.png_header_code(), color_type_in);
|
||||||
|
|
@ -50,7 +50,7 @@ fn test_it_converts(
|
||||||
let output = output.path().unwrap();
|
let output = output.path().unwrap();
|
||||||
assert!(output.exists());
|
assert!(output.exists());
|
||||||
|
|
||||||
let png = match PngData::new(output, opts.fix_errors) {
|
let png = match PngData::new(output, &opts) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(x) => {
|
Err(x) => {
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
|
|
|
||||||
113
tests/flags.rs
|
|
@ -1,6 +1,6 @@
|
||||||
use indexmap::IndexSet;
|
use indexmap::{indexset, IndexSet};
|
||||||
use oxipng::{internal_tests::*, Interlacing, RowFilter};
|
use oxipng::internal_tests::*;
|
||||||
use oxipng::{InFile, OutFile};
|
use oxipng::*;
|
||||||
#[cfg(feature = "filetime")]
|
#[cfg(feature = "filetime")]
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::fs::remove_file;
|
use std::fs::remove_file;
|
||||||
|
|
@ -47,7 +47,7 @@ fn test_it_converts_callbacks<CBPRE, CBPOST>(
|
||||||
CBPOST: FnMut(&Path),
|
CBPOST: FnMut(&Path),
|
||||||
CBPRE: FnMut(&Path),
|
CBPRE: FnMut(&Path),
|
||||||
{
|
{
|
||||||
let png = PngData::new(&input, opts.fix_errors).unwrap();
|
let png = PngData::new(&input, &opts).unwrap();
|
||||||
|
|
||||||
assert_eq!(png.raw.ihdr.color_type.png_header_code(), color_type_in);
|
assert_eq!(png.raw.ihdr.color_type.png_header_code(), color_type_in);
|
||||||
assert_eq!(png.raw.ihdr.bit_depth, bit_depth_in);
|
assert_eq!(png.raw.ihdr.bit_depth, bit_depth_in);
|
||||||
|
|
@ -63,7 +63,7 @@ fn test_it_converts_callbacks<CBPRE, CBPOST>(
|
||||||
|
|
||||||
callback_post(output);
|
callback_post(output);
|
||||||
|
|
||||||
let png = match PngData::new(output, opts.fix_errors) {
|
let png = match PngData::new(output, &opts) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(x) => {
|
Err(x) => {
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
|
|
@ -203,17 +203,24 @@ fn verbose_mode() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn count_chunk(png: &PngData, name: &[u8; 4]) -> usize {
|
||||||
|
png.aux_chunks
|
||||||
|
.iter()
|
||||||
|
.filter(|chunk| &chunk.name == name)
|
||||||
|
.count()
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn strip_headers_list() {
|
fn strip_headers_list() {
|
||||||
let input = PathBuf::from("tests/files/strip_headers_list.png");
|
let input = PathBuf::from("tests/files/strip_headers_list.png");
|
||||||
let (output, mut opts) = get_opts(&input);
|
let (output, mut opts) = get_opts(&input);
|
||||||
opts.strip = Headers::Strip(vec!["iCCP".to_owned(), "tEXt".to_owned()]);
|
opts.strip = StripChunks::Strip(indexset![*b"iCCP", *b"tEXt"]);
|
||||||
|
|
||||||
let png = PngData::new(&input, opts.fix_errors).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
assert!(png.raw.aux_headers.contains_key(b"tEXt"));
|
assert_eq!(count_chunk(&png, b"tEXt"), 3);
|
||||||
assert!(png.raw.aux_headers.contains_key(b"iTXt"));
|
assert_eq!(count_chunk(&png, b"iTXt"), 1);
|
||||||
assert!(png.raw.aux_headers.contains_key(b"iCCP"));
|
assert_eq!(count_chunk(&png, b"iCCP"), 1);
|
||||||
|
|
||||||
match oxipng::optimize(&InFile::Path(input), &output, &opts) {
|
match oxipng::optimize(&InFile::Path(input), &output, &opts) {
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
|
|
@ -222,7 +229,7 @@ fn strip_headers_list() {
|
||||||
let output = output.path().unwrap();
|
let output = output.path().unwrap();
|
||||||
assert!(output.exists());
|
assert!(output.exists());
|
||||||
|
|
||||||
let png = match PngData::new(output, opts.fix_errors) {
|
let png = match PngData::new(output, &opts) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(x) => {
|
Err(x) => {
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
|
|
@ -230,9 +237,9 @@ fn strip_headers_list() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
assert!(!png.raw.aux_headers.contains_key(b"tEXt"));
|
assert_eq!(count_chunk(&png, b"tEXt"), 0);
|
||||||
assert!(png.raw.aux_headers.contains_key(b"iTXt"));
|
assert_eq!(count_chunk(&png, b"iTXt"), 1);
|
||||||
assert!(!png.raw.aux_headers.contains_key(b"iCCP"));
|
assert_eq!(count_chunk(&png, b"iCCP"), 0);
|
||||||
|
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
}
|
}
|
||||||
|
|
@ -241,13 +248,13 @@ fn strip_headers_list() {
|
||||||
fn strip_headers_safe() {
|
fn strip_headers_safe() {
|
||||||
let input = PathBuf::from("tests/files/strip_headers_safe.png");
|
let input = PathBuf::from("tests/files/strip_headers_safe.png");
|
||||||
let (output, mut opts) = get_opts(&input);
|
let (output, mut opts) = get_opts(&input);
|
||||||
opts.strip = Headers::Safe;
|
opts.strip = StripChunks::Safe;
|
||||||
|
|
||||||
let png = PngData::new(&input, opts.fix_errors).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
assert!(png.raw.aux_headers.contains_key(b"tEXt"));
|
assert_eq!(count_chunk(&png, b"tEXt"), 3);
|
||||||
assert!(png.raw.aux_headers.contains_key(b"iTXt"));
|
assert_eq!(count_chunk(&png, b"iTXt"), 1);
|
||||||
assert!(png.raw.aux_headers.contains_key(b"iCCP"));
|
assert_eq!(count_chunk(&png, b"iCCP"), 1);
|
||||||
|
|
||||||
match oxipng::optimize(&InFile::Path(input), &output, &opts) {
|
match oxipng::optimize(&InFile::Path(input), &output, &opts) {
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
|
|
@ -256,7 +263,7 @@ fn strip_headers_safe() {
|
||||||
let output = output.path().unwrap();
|
let output = output.path().unwrap();
|
||||||
assert!(output.exists());
|
assert!(output.exists());
|
||||||
|
|
||||||
let png = match PngData::new(output, opts.fix_errors) {
|
let png = match PngData::new(output, &opts) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(x) => {
|
Err(x) => {
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
|
|
@ -264,9 +271,9 @@ fn strip_headers_safe() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
assert!(!png.raw.aux_headers.contains_key(b"tEXt"));
|
assert_eq!(count_chunk(&png, b"tEXt"), 0);
|
||||||
assert!(!png.raw.aux_headers.contains_key(b"iTXt"));
|
assert_eq!(count_chunk(&png, b"iTXt"), 0);
|
||||||
assert!(png.raw.aux_headers.contains_key(b"sRGB"));
|
assert_eq!(count_chunk(&png, b"sRGB"), 1);
|
||||||
|
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
}
|
}
|
||||||
|
|
@ -275,13 +282,13 @@ fn strip_headers_safe() {
|
||||||
fn strip_headers_all() {
|
fn strip_headers_all() {
|
||||||
let input = PathBuf::from("tests/files/strip_headers_all.png");
|
let input = PathBuf::from("tests/files/strip_headers_all.png");
|
||||||
let (output, mut opts) = get_opts(&input);
|
let (output, mut opts) = get_opts(&input);
|
||||||
opts.strip = Headers::All;
|
opts.strip = StripChunks::All;
|
||||||
|
|
||||||
let png = PngData::new(&input, opts.fix_errors).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
assert!(png.raw.aux_headers.contains_key(b"tEXt"));
|
assert_eq!(count_chunk(&png, b"tEXt"), 3);
|
||||||
assert!(png.raw.aux_headers.contains_key(b"iTXt"));
|
assert_eq!(count_chunk(&png, b"iTXt"), 1);
|
||||||
assert!(png.raw.aux_headers.contains_key(b"iCCP"));
|
assert_eq!(count_chunk(&png, b"iCCP"), 1);
|
||||||
|
|
||||||
match oxipng::optimize(&InFile::Path(input), &output, &opts) {
|
match oxipng::optimize(&InFile::Path(input), &output, &opts) {
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
|
|
@ -290,7 +297,7 @@ fn strip_headers_all() {
|
||||||
let output = output.path().unwrap();
|
let output = output.path().unwrap();
|
||||||
assert!(output.exists());
|
assert!(output.exists());
|
||||||
|
|
||||||
let png = match PngData::new(output, opts.fix_errors) {
|
let png = match PngData::new(output, &opts) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(x) => {
|
Err(x) => {
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
|
|
@ -298,9 +305,9 @@ fn strip_headers_all() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
assert!(!png.raw.aux_headers.contains_key(b"tEXt"));
|
assert_eq!(count_chunk(&png, b"tEXt"), 0);
|
||||||
assert!(!png.raw.aux_headers.contains_key(b"iTXt"));
|
assert_eq!(count_chunk(&png, b"iTXt"), 0);
|
||||||
assert!(!png.raw.aux_headers.contains_key(b"iCCP"));
|
assert_eq!(count_chunk(&png, b"iCCP"), 0);
|
||||||
|
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
}
|
}
|
||||||
|
|
@ -309,13 +316,13 @@ fn strip_headers_all() {
|
||||||
fn strip_headers_none() {
|
fn strip_headers_none() {
|
||||||
let input = PathBuf::from("tests/files/strip_headers_none.png");
|
let input = PathBuf::from("tests/files/strip_headers_none.png");
|
||||||
let (output, mut opts) = get_opts(&input);
|
let (output, mut opts) = get_opts(&input);
|
||||||
opts.strip = Headers::None;
|
opts.strip = StripChunks::None;
|
||||||
|
|
||||||
let png = PngData::new(&input, opts.fix_errors).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
assert!(png.raw.aux_headers.contains_key(b"tEXt"));
|
assert_eq!(count_chunk(&png, b"tEXt"), 3);
|
||||||
assert!(png.raw.aux_headers.contains_key(b"iTXt"));
|
assert_eq!(count_chunk(&png, b"iTXt"), 1);
|
||||||
assert!(png.raw.aux_headers.contains_key(b"iCCP"));
|
assert_eq!(count_chunk(&png, b"iCCP"), 1);
|
||||||
|
|
||||||
match oxipng::optimize(&InFile::Path(input), &output, &opts) {
|
match oxipng::optimize(&InFile::Path(input), &output, &opts) {
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
|
|
@ -324,7 +331,7 @@ fn strip_headers_none() {
|
||||||
let output = output.path().unwrap();
|
let output = output.path().unwrap();
|
||||||
assert!(output.exists());
|
assert!(output.exists());
|
||||||
|
|
||||||
let png = match PngData::new(output, opts.fix_errors) {
|
let png = match PngData::new(output, &opts) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(x) => {
|
Err(x) => {
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
|
|
@ -332,9 +339,9 @@ fn strip_headers_none() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
assert!(png.raw.aux_headers.contains_key(b"tEXt"));
|
assert_eq!(count_chunk(&png, b"tEXt"), 3);
|
||||||
assert!(png.raw.aux_headers.contains_key(b"iTXt"));
|
assert_eq!(count_chunk(&png, b"iTXt"), 1);
|
||||||
assert!(png.raw.aux_headers.contains_key(b"iCCP"));
|
assert_eq!(count_chunk(&png, b"iCCP"), 1);
|
||||||
|
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
}
|
}
|
||||||
|
|
@ -345,7 +352,7 @@ fn interlacing_0_to_1() {
|
||||||
let (output, mut opts) = get_opts(&input);
|
let (output, mut opts) = get_opts(&input);
|
||||||
opts.interlace = Some(Interlacing::Adam7);
|
opts.interlace = Some(Interlacing::Adam7);
|
||||||
|
|
||||||
let png = PngData::new(&input, opts.fix_errors).unwrap();
|
let png = PngData::new(&input, &opts).unwrap();
|
||||||
|
|
||||||
assert_eq!(png.raw.ihdr.interlaced, Interlacing::None);
|
assert_eq!(png.raw.ihdr.interlaced, Interlacing::None);
|
||||||
|
|
||||||
|
|
@ -356,7 +363,7 @@ fn interlacing_0_to_1() {
|
||||||
let output = output.path().unwrap();
|
let output = output.path().unwrap();
|
||||||
assert!(output.exists());
|
assert!(output.exists());
|
||||||
|
|
||||||
let png = match PngData::new(output, opts.fix_errors) {
|
let png = match PngData::new(output, &opts) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(x) => {
|
Err(x) => {
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
|
|
@ -375,7 +382,7 @@ fn interlacing_1_to_0() {
|
||||||
let (output, mut opts) = get_opts(&input);
|
let (output, mut opts) = get_opts(&input);
|
||||||
opts.interlace = Some(Interlacing::None);
|
opts.interlace = Some(Interlacing::None);
|
||||||
|
|
||||||
let png = PngData::new(&input, opts.fix_errors).unwrap();
|
let png = PngData::new(&input, &opts).unwrap();
|
||||||
|
|
||||||
assert_eq!(png.raw.ihdr.interlaced, Interlacing::Adam7);
|
assert_eq!(png.raw.ihdr.interlaced, Interlacing::Adam7);
|
||||||
|
|
||||||
|
|
@ -386,7 +393,7 @@ fn interlacing_1_to_0() {
|
||||||
let output = output.path().unwrap();
|
let output = output.path().unwrap();
|
||||||
assert!(output.exists());
|
assert!(output.exists());
|
||||||
|
|
||||||
let png = match PngData::new(output, opts.fix_errors) {
|
let png = match PngData::new(output, &opts) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(x) => {
|
Err(x) => {
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
|
|
@ -405,7 +412,7 @@ fn interlacing_0_to_1_small_files() {
|
||||||
let (output, mut opts) = get_opts(&input);
|
let (output, mut opts) = get_opts(&input);
|
||||||
opts.interlace = Some(Interlacing::Adam7);
|
opts.interlace = Some(Interlacing::Adam7);
|
||||||
|
|
||||||
let png = PngData::new(&input, opts.fix_errors).unwrap();
|
let png = PngData::new(&input, &opts).unwrap();
|
||||||
|
|
||||||
assert_eq!(png.raw.ihdr.interlaced, Interlacing::None);
|
assert_eq!(png.raw.ihdr.interlaced, Interlacing::None);
|
||||||
assert_eq!(png.raw.ihdr.color_type.png_header_code(), INDEXED);
|
assert_eq!(png.raw.ihdr.color_type.png_header_code(), INDEXED);
|
||||||
|
|
@ -418,7 +425,7 @@ fn interlacing_0_to_1_small_files() {
|
||||||
let output = output.path().unwrap();
|
let output = output.path().unwrap();
|
||||||
assert!(output.exists());
|
assert!(output.exists());
|
||||||
|
|
||||||
let png = match PngData::new(output, opts.fix_errors) {
|
let png = match PngData::new(output, &opts) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(x) => {
|
Err(x) => {
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
|
|
@ -439,7 +446,7 @@ fn interlacing_1_to_0_small_files() {
|
||||||
let (output, mut opts) = get_opts(&input);
|
let (output, mut opts) = get_opts(&input);
|
||||||
opts.interlace = Some(Interlacing::None);
|
opts.interlace = Some(Interlacing::None);
|
||||||
|
|
||||||
let png = PngData::new(&input, opts.fix_errors).unwrap();
|
let png = PngData::new(&input, &opts).unwrap();
|
||||||
|
|
||||||
assert_eq!(png.raw.ihdr.interlaced, Interlacing::Adam7);
|
assert_eq!(png.raw.ihdr.interlaced, Interlacing::Adam7);
|
||||||
assert_eq!(png.raw.ihdr.color_type.png_header_code(), INDEXED);
|
assert_eq!(png.raw.ihdr.color_type.png_header_code(), INDEXED);
|
||||||
|
|
@ -452,7 +459,7 @@ fn interlacing_1_to_0_small_files() {
|
||||||
let output = output.path().unwrap();
|
let output = output.path().unwrap();
|
||||||
assert!(output.exists());
|
assert!(output.exists());
|
||||||
|
|
||||||
let png = match PngData::new(output, opts.fix_errors) {
|
let png = match PngData::new(output, &opts) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(x) => {
|
Err(x) => {
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
|
|
@ -476,7 +483,7 @@ fn interlaced_0_to_1_other_filter_mode() {
|
||||||
filter.insert(RowFilter::Paeth);
|
filter.insert(RowFilter::Paeth);
|
||||||
opts.filter = filter;
|
opts.filter = filter;
|
||||||
|
|
||||||
let png = PngData::new(&input, opts.fix_errors).unwrap();
|
let png = PngData::new(&input, &opts).unwrap();
|
||||||
|
|
||||||
assert_eq!(png.raw.ihdr.interlaced, Interlacing::None);
|
assert_eq!(png.raw.ihdr.interlaced, Interlacing::None);
|
||||||
|
|
||||||
|
|
@ -487,7 +494,7 @@ fn interlaced_0_to_1_other_filter_mode() {
|
||||||
let output = output.path().unwrap();
|
let output = output.path().unwrap();
|
||||||
assert!(output.exists());
|
assert!(output.exists());
|
||||||
|
|
||||||
let png = match PngData::new(output, opts.fix_errors) {
|
let png = match PngData::new(output, &opts) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(x) => {
|
Err(x) => {
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
|
|
@ -570,7 +577,7 @@ fn fix_errors() {
|
||||||
let (output, mut opts) = get_opts(&input);
|
let (output, mut opts) = get_opts(&input);
|
||||||
opts.fix_errors = true;
|
opts.fix_errors = true;
|
||||||
|
|
||||||
let png = PngData::new(&input, opts.fix_errors).unwrap();
|
let png = PngData::new(&input, &opts).unwrap();
|
||||||
|
|
||||||
assert_eq!(png.raw.ihdr.color_type.png_header_code(), RGBA);
|
assert_eq!(png.raw.ihdr.color_type.png_header_code(), RGBA);
|
||||||
assert_eq!(png.raw.ihdr.bit_depth, BitDepth::Eight);
|
assert_eq!(png.raw.ihdr.bit_depth, BitDepth::Eight);
|
||||||
|
|
@ -582,7 +589,7 @@ fn fix_errors() {
|
||||||
let output = output.path().unwrap();
|
let output = output.path().unwrap();
|
||||||
assert!(output.exists());
|
assert!(output.exists());
|
||||||
|
|
||||||
let png = match PngData::new(output, false) {
|
let png = match PngData::new(output, &Options::default()) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(x) => {
|
Err(x) => {
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use indexmap::IndexSet;
|
use indexmap::IndexSet;
|
||||||
use oxipng::{internal_tests::*, Interlacing, RowFilter};
|
use oxipng::internal_tests::*;
|
||||||
use oxipng::{InFile, OutFile};
|
use oxipng::*;
|
||||||
use std::fs::remove_file;
|
use std::fs::remove_file;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
@ -35,7 +35,7 @@ fn test_it_converts(
|
||||||
) {
|
) {
|
||||||
let input = PathBuf::from(input);
|
let input = PathBuf::from(input);
|
||||||
let (output, opts) = get_opts(&input);
|
let (output, opts) = get_opts(&input);
|
||||||
let png = PngData::new(&input, opts.fix_errors).unwrap();
|
let png = PngData::new(&input, &opts).unwrap();
|
||||||
|
|
||||||
assert_eq!(png.raw.ihdr.color_type.png_header_code(), color_type_in);
|
assert_eq!(png.raw.ihdr.color_type.png_header_code(), color_type_in);
|
||||||
assert_eq!(png.raw.ihdr.bit_depth, bit_depth_in);
|
assert_eq!(png.raw.ihdr.bit_depth, bit_depth_in);
|
||||||
|
|
@ -48,7 +48,7 @@ fn test_it_converts(
|
||||||
let output = output.path().unwrap();
|
let output = output.path().unwrap();
|
||||||
assert!(output.exists());
|
assert!(output.exists());
|
||||||
|
|
||||||
let png = match PngData::new(output, opts.fix_errors) {
|
let png = match PngData::new(output, &opts) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(x) => {
|
Err(x) => {
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use indexmap::IndexSet;
|
use indexmap::IndexSet;
|
||||||
use oxipng::{internal_tests::*, Interlacing, RowFilter};
|
use oxipng::internal_tests::*;
|
||||||
use oxipng::{InFile, OutFile};
|
use oxipng::*;
|
||||||
use std::fs::remove_file;
|
use std::fs::remove_file;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
@ -33,7 +33,7 @@ fn test_it_converts(
|
||||||
) {
|
) {
|
||||||
let input = PathBuf::from(input);
|
let input = PathBuf::from(input);
|
||||||
let (output, mut opts) = get_opts(&input);
|
let (output, mut opts) = get_opts(&input);
|
||||||
let png = PngData::new(&input, opts.fix_errors).unwrap();
|
let png = PngData::new(&input, &opts).unwrap();
|
||||||
opts.interlace = Some(interlace);
|
opts.interlace = Some(interlace);
|
||||||
assert_eq!(png.raw.ihdr.color_type.png_header_code(), color_type_in);
|
assert_eq!(png.raw.ihdr.color_type.png_header_code(), color_type_in);
|
||||||
assert_eq!(png.raw.ihdr.bit_depth, bit_depth_in);
|
assert_eq!(png.raw.ihdr.bit_depth, bit_depth_in);
|
||||||
|
|
@ -53,7 +53,7 @@ fn test_it_converts(
|
||||||
let output = output.path().unwrap();
|
let output = output.path().unwrap();
|
||||||
assert!(output.exists());
|
assert!(output.exists());
|
||||||
|
|
||||||
let png = match PngData::new(output, opts.fix_errors) {
|
let png = match PngData::new(output, &opts) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(x) => {
|
Err(x) => {
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
|
|
|
||||||
32
tests/lib.rs
|
|
@ -1,6 +1,4 @@
|
||||||
use oxipng::Headers;
|
use oxipng::*;
|
||||||
use oxipng::OutFile;
|
|
||||||
use std::default::Default;
|
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
|
|
@ -11,9 +9,7 @@ fn optimize_from_memory() {
|
||||||
let mut in_file_buf: Vec<u8> = Vec::new();
|
let mut in_file_buf: Vec<u8> = Vec::new();
|
||||||
in_file.read_to_end(&mut in_file_buf).unwrap();
|
in_file.read_to_end(&mut in_file_buf).unwrap();
|
||||||
|
|
||||||
let opts: oxipng::Options = Default::default();
|
let result = oxipng::optimize_from_memory(&in_file_buf, &Options::default());
|
||||||
|
|
||||||
let result = oxipng::optimize_from_memory(&in_file_buf, &opts);
|
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -23,9 +19,7 @@ fn optimize_from_memory_corrupted() {
|
||||||
let mut in_file_buf: Vec<u8> = Vec::new();
|
let mut in_file_buf: Vec<u8> = Vec::new();
|
||||||
in_file.read_to_end(&mut in_file_buf).unwrap();
|
in_file.read_to_end(&mut in_file_buf).unwrap();
|
||||||
|
|
||||||
let opts: oxipng::Options = Default::default();
|
let result = oxipng::optimize_from_memory(&in_file_buf, &Options::default());
|
||||||
|
|
||||||
let result = oxipng::optimize_from_memory(&in_file_buf, &opts);
|
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -35,44 +29,36 @@ fn optimize_from_memory_apng() {
|
||||||
let mut in_file_buf: Vec<u8> = Vec::new();
|
let mut in_file_buf: Vec<u8> = Vec::new();
|
||||||
in_file.read_to_end(&mut in_file_buf).unwrap();
|
in_file.read_to_end(&mut in_file_buf).unwrap();
|
||||||
|
|
||||||
let opts: oxipng::Options = Default::default();
|
let result = oxipng::optimize_from_memory(&in_file_buf, &Options::default());
|
||||||
|
|
||||||
let result = oxipng::optimize_from_memory(&in_file_buf, &opts);
|
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn optimize() {
|
fn optimize() {
|
||||||
let opts: oxipng::Options = Default::default();
|
|
||||||
|
|
||||||
let result = oxipng::optimize(
|
let result = oxipng::optimize(
|
||||||
&"tests/files/fully_optimized.png".into(),
|
&"tests/files/fully_optimized.png".into(),
|
||||||
&OutFile::Path(None),
|
&OutFile::Path(None),
|
||||||
&opts,
|
&Options::default(),
|
||||||
);
|
);
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn optimize_corrupted() {
|
fn optimize_corrupted() {
|
||||||
let opts: oxipng::Options = Default::default();
|
|
||||||
|
|
||||||
let result = oxipng::optimize(
|
let result = oxipng::optimize(
|
||||||
&"tests/files/corrupted_header.png".into(),
|
&"tests/files/corrupted_header.png".into(),
|
||||||
&OutFile::Path(None),
|
&OutFile::Path(None),
|
||||||
&opts,
|
&Options::default(),
|
||||||
);
|
);
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn optimize_apng() {
|
fn optimize_apng() {
|
||||||
let opts: oxipng::Options = Default::default();
|
|
||||||
|
|
||||||
let result = oxipng::optimize(
|
let result = oxipng::optimize(
|
||||||
&"tests/files/apng_file.png".into(),
|
&"tests/files/apng_file.png".into(),
|
||||||
&OutFile::Path(None),
|
&OutFile::Path(None),
|
||||||
&opts,
|
&Options::default(),
|
||||||
);
|
);
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
}
|
}
|
||||||
|
|
@ -80,12 +66,12 @@ fn optimize_apng() {
|
||||||
#[test]
|
#[test]
|
||||||
fn optimize_srgb_icc() {
|
fn optimize_srgb_icc() {
|
||||||
let file = fs::read("tests/files/badsrgb.png").unwrap();
|
let file = fs::read("tests/files/badsrgb.png").unwrap();
|
||||||
let mut opts: oxipng::Options = Default::default();
|
let mut opts = Options::default();
|
||||||
|
|
||||||
let result = oxipng::optimize_from_memory(&file, &opts);
|
let result = oxipng::optimize_from_memory(&file, &opts);
|
||||||
assert!(result.unwrap().len() > 1000);
|
assert!(result.unwrap().len() > 1000);
|
||||||
|
|
||||||
opts.strip = Headers::Safe;
|
opts.strip = StripChunks::Safe;
|
||||||
let result = oxipng::optimize_from_memory(&file, &opts);
|
let result = oxipng::optimize_from_memory(&file, &opts);
|
||||||
assert!(result.unwrap().len() < 1000);
|
assert!(result.unwrap().len() < 1000);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
16
tests/raw.rs
|
|
@ -16,11 +16,11 @@ fn test_it_converts(input: &str) {
|
||||||
let opts = get_opts();
|
let opts = get_opts();
|
||||||
|
|
||||||
let original_data = PngData::read_file(&PathBuf::from(input)).unwrap();
|
let original_data = PngData::read_file(&PathBuf::from(input)).unwrap();
|
||||||
let png = PngData::from_slice(&original_data, opts.fix_errors).unwrap();
|
let image = PngData::from_slice(&original_data, &opts).unwrap();
|
||||||
let png = Arc::try_unwrap(png.raw).unwrap();
|
let png = Arc::try_unwrap(image.raw).unwrap();
|
||||||
|
|
||||||
let num_headers = png.aux_headers.len();
|
let num_chunks = image.aux_chunks.len();
|
||||||
assert!(num_headers > 0);
|
assert!(num_chunks > 0);
|
||||||
|
|
||||||
let mut raw = RawImage::new(
|
let mut raw = RawImage::new(
|
||||||
png.ihdr.width,
|
png.ihdr.width,
|
||||||
|
|
@ -31,14 +31,14 @@ fn test_it_converts(input: &str) {
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
for (chunk_type, data) in png.aux_headers {
|
for chunk in image.aux_chunks {
|
||||||
raw.add_png_chunk(chunk_type, data);
|
raw.add_png_chunk(chunk.name, chunk.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
let output = raw.create_optimized_png(&opts).unwrap();
|
let output = raw.create_optimized_png(&opts).unwrap();
|
||||||
|
|
||||||
let new = PngData::from_slice(&output, opts.fix_errors).unwrap();
|
let new = PngData::from_slice(&output, &opts).unwrap();
|
||||||
assert!(new.raw.aux_headers.len() == num_headers);
|
assert!(new.aux_chunks.len() == num_chunks);
|
||||||
|
|
||||||
#[cfg(feature = "sanity-checks")]
|
#[cfg(feature = "sanity-checks")]
|
||||||
assert!(validate_output(&output, &original_data));
|
assert!(validate_output(&output, &original_data));
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use indexmap::IndexSet;
|
use indexmap::IndexSet;
|
||||||
use oxipng::{internal_tests::*, Interlacing, RowFilter};
|
use oxipng::internal_tests::*;
|
||||||
use oxipng::{InFile, OutFile};
|
use oxipng::*;
|
||||||
use std::fs::remove_file;
|
use std::fs::remove_file;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
@ -37,7 +37,7 @@ fn test_it_converts(
|
||||||
let input = PathBuf::from(input);
|
let input = PathBuf::from(input);
|
||||||
let (output, mut opts) = get_opts(&input);
|
let (output, mut opts) = get_opts(&input);
|
||||||
opts.optimize_alpha = optimize_alpha;
|
opts.optimize_alpha = optimize_alpha;
|
||||||
let png = PngData::new(&input, opts.fix_errors).unwrap();
|
let png = PngData::new(&input, &opts).unwrap();
|
||||||
|
|
||||||
assert_eq!(png.raw.ihdr.color_type.png_header_code(), color_type_in);
|
assert_eq!(png.raw.ihdr.color_type.png_header_code(), color_type_in);
|
||||||
assert_eq!(png.raw.ihdr.bit_depth, bit_depth_in, "test file is broken");
|
assert_eq!(png.raw.ihdr.bit_depth, bit_depth_in, "test file is broken");
|
||||||
|
|
@ -50,7 +50,7 @@ fn test_it_converts(
|
||||||
let output = output.path().unwrap();
|
let output = output.path().unwrap();
|
||||||
assert!(output.exists());
|
assert!(output.exists());
|
||||||
|
|
||||||
let png = match PngData::new(output, opts.fix_errors) {
|
let png = match PngData::new(output, &opts) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(x) => {
|
Err(x) => {
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
|
|
@ -933,7 +933,7 @@ fn palette_should_be_reduced_with_dupes() {
|
||||||
let input = PathBuf::from("tests/files/palette_should_be_reduced_with_dupes.png");
|
let input = PathBuf::from("tests/files/palette_should_be_reduced_with_dupes.png");
|
||||||
let (output, opts) = get_opts(&input);
|
let (output, opts) = get_opts(&input);
|
||||||
|
|
||||||
let png = PngData::new(&input, opts.fix_errors).unwrap();
|
let png = PngData::new(&input, &opts).unwrap();
|
||||||
|
|
||||||
assert_eq!(png.raw.ihdr.color_type.png_header_code(), INDEXED);
|
assert_eq!(png.raw.ihdr.color_type.png_header_code(), INDEXED);
|
||||||
assert_eq!(png.raw.ihdr.bit_depth, BitDepth::Eight);
|
assert_eq!(png.raw.ihdr.bit_depth, BitDepth::Eight);
|
||||||
|
|
@ -948,7 +948,7 @@ fn palette_should_be_reduced_with_dupes() {
|
||||||
let output = output.path().unwrap();
|
let output = output.path().unwrap();
|
||||||
assert!(output.exists());
|
assert!(output.exists());
|
||||||
|
|
||||||
let png = match PngData::new(output, opts.fix_errors) {
|
let png = match PngData::new(output, &opts) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(x) => {
|
Err(x) => {
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
|
|
@ -970,7 +970,7 @@ fn palette_should_be_reduced_with_unused() {
|
||||||
let input = PathBuf::from("tests/files/palette_should_be_reduced_with_unused.png");
|
let input = PathBuf::from("tests/files/palette_should_be_reduced_with_unused.png");
|
||||||
let (output, opts) = get_opts(&input);
|
let (output, opts) = get_opts(&input);
|
||||||
|
|
||||||
let png = PngData::new(&input, opts.fix_errors).unwrap();
|
let png = PngData::new(&input, &opts).unwrap();
|
||||||
|
|
||||||
assert_eq!(png.raw.ihdr.color_type.png_header_code(), INDEXED);
|
assert_eq!(png.raw.ihdr.color_type.png_header_code(), INDEXED);
|
||||||
assert_eq!(png.raw.ihdr.bit_depth, BitDepth::Eight);
|
assert_eq!(png.raw.ihdr.bit_depth, BitDepth::Eight);
|
||||||
|
|
@ -985,7 +985,7 @@ fn palette_should_be_reduced_with_unused() {
|
||||||
let output = output.path().unwrap();
|
let output = output.path().unwrap();
|
||||||
assert!(output.exists());
|
assert!(output.exists());
|
||||||
|
|
||||||
let png = match PngData::new(output, opts.fix_errors) {
|
let png = match PngData::new(output, &opts) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(x) => {
|
Err(x) => {
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
|
|
@ -1007,7 +1007,7 @@ fn palette_should_be_reduced_with_both() {
|
||||||
let input = PathBuf::from("tests/files/palette_should_be_reduced_with_both.png");
|
let input = PathBuf::from("tests/files/palette_should_be_reduced_with_both.png");
|
||||||
let (output, opts) = get_opts(&input);
|
let (output, opts) = get_opts(&input);
|
||||||
|
|
||||||
let png = PngData::new(&input, opts.fix_errors).unwrap();
|
let png = PngData::new(&input, &opts).unwrap();
|
||||||
|
|
||||||
assert_eq!(png.raw.ihdr.color_type.png_header_code(), INDEXED);
|
assert_eq!(png.raw.ihdr.color_type.png_header_code(), INDEXED);
|
||||||
assert_eq!(png.raw.ihdr.bit_depth, BitDepth::Eight);
|
assert_eq!(png.raw.ihdr.bit_depth, BitDepth::Eight);
|
||||||
|
|
@ -1022,7 +1022,7 @@ fn palette_should_be_reduced_with_both() {
|
||||||
let output = output.path().unwrap();
|
let output = output.path().unwrap();
|
||||||
assert!(output.exists());
|
assert!(output.exists());
|
||||||
|
|
||||||
let png = match PngData::new(output, opts.fix_errors) {
|
let png = match PngData::new(output, &opts) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(x) => {
|
Err(x) => {
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use indexmap::IndexSet;
|
use indexmap::IndexSet;
|
||||||
use oxipng::{internal_tests::*, Interlacing, RowFilter};
|
use oxipng::internal_tests::*;
|
||||||
use oxipng::{InFile, OutFile};
|
use oxipng::*;
|
||||||
use std::fs::remove_file;
|
use std::fs::remove_file;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
@ -36,7 +36,7 @@ fn test_it_converts(
|
||||||
) {
|
) {
|
||||||
let input = PathBuf::from(input);
|
let input = PathBuf::from(input);
|
||||||
let (output, opts) = custom.unwrap_or_else(|| get_opts(&input));
|
let (output, opts) = custom.unwrap_or_else(|| get_opts(&input));
|
||||||
let png = PngData::new(&input, opts.fix_errors).unwrap();
|
let png = PngData::new(&input, &opts).unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
png.raw.ihdr.color_type.png_header_code(),
|
png.raw.ihdr.color_type.png_header_code(),
|
||||||
|
|
@ -52,7 +52,7 @@ fn test_it_converts(
|
||||||
let output = output.path().unwrap();
|
let output = output.path().unwrap();
|
||||||
assert!(output.exists());
|
assert!(output.exists());
|
||||||
|
|
||||||
let png = match PngData::new(output, opts.fix_errors) {
|
let png = match PngData::new(output, &opts) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(x) => {
|
Err(x) => {
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
|
|
@ -70,13 +70,7 @@ fn test_it_converts(
|
||||||
"optimized to wrong bit depth"
|
"optimized to wrong bit depth"
|
||||||
);
|
);
|
||||||
if let ColorType::Indexed { palette } = &png.raw.ihdr.color_type {
|
if let ColorType::Indexed { palette } = &png.raw.ihdr.color_type {
|
||||||
let mut max_palette_size = 1 << (png.raw.ihdr.bit_depth as u8);
|
assert!(palette.len() <= 1 << (png.raw.ihdr.bit_depth as u8));
|
||||||
// Ensure bKGD color is valid
|
|
||||||
if let Some(&idx) = png.raw.aux_headers.get(b"bKGD").and_then(|b| b.first()) {
|
|
||||||
assert!(palette.len() > idx as usize);
|
|
||||||
max_palette_size = max_palette_size.max(idx as usize + 1);
|
|
||||||
}
|
|
||||||
assert!(palette.len() <= max_palette_size);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
|
|
@ -100,7 +94,7 @@ fn issue_42() {
|
||||||
let (output, mut opts) = get_opts(&input);
|
let (output, mut opts) = get_opts(&input);
|
||||||
opts.interlace = Some(Interlacing::Adam7);
|
opts.interlace = Some(Interlacing::Adam7);
|
||||||
|
|
||||||
let png = PngData::new(&input, opts.fix_errors).unwrap();
|
let png = PngData::new(&input, &opts).unwrap();
|
||||||
|
|
||||||
assert_eq!(png.raw.ihdr.interlaced, Interlacing::None);
|
assert_eq!(png.raw.ihdr.interlaced, Interlacing::None);
|
||||||
assert_eq!(png.raw.ihdr.color_type, ColorType::GrayscaleAlpha);
|
assert_eq!(png.raw.ihdr.color_type, ColorType::GrayscaleAlpha);
|
||||||
|
|
@ -113,7 +107,7 @@ fn issue_42() {
|
||||||
let output = output.path().unwrap();
|
let output = output.path().unwrap();
|
||||||
assert!(output.exists());
|
assert!(output.exists());
|
||||||
|
|
||||||
let png = match PngData::new(output, opts.fix_errors) {
|
let png = match PngData::new(output, &opts) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(x) => {
|
Err(x) => {
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
|
|
@ -231,7 +225,7 @@ fn issue_59() {
|
||||||
None,
|
None,
|
||||||
RGBA,
|
RGBA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
RGBA,
|
INDEXED,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use indexmap::IndexSet;
|
use indexmap::IndexSet;
|
||||||
use oxipng::{internal_tests::*, RowFilter};
|
use oxipng::internal_tests::*;
|
||||||
use oxipng::{InFile, OutFile};
|
use oxipng::*;
|
||||||
use std::fs::remove_file;
|
use std::fs::remove_file;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
@ -36,7 +36,7 @@ fn test_it_converts(
|
||||||
let input = PathBuf::from(input);
|
let input = PathBuf::from(input);
|
||||||
|
|
||||||
let (output, mut opts) = get_opts(&input);
|
let (output, mut opts) = get_opts(&input);
|
||||||
let png = PngData::new(&input, opts.fix_errors).unwrap();
|
let png = PngData::new(&input, &opts).unwrap();
|
||||||
opts.filter = IndexSet::new();
|
opts.filter = IndexSet::new();
|
||||||
opts.filter.insert(filter);
|
opts.filter.insert(filter);
|
||||||
assert_eq!(png.raw.ihdr.color_type.png_header_code(), color_type_in);
|
assert_eq!(png.raw.ihdr.color_type.png_header_code(), color_type_in);
|
||||||
|
|
@ -49,7 +49,7 @@ fn test_it_converts(
|
||||||
let output = output.path().unwrap();
|
let output = output.path().unwrap();
|
||||||
assert!(output.exists());
|
assert!(output.exists());
|
||||||
|
|
||||||
let png = match PngData::new(output, opts.fix_errors) {
|
let png = match PngData::new(output, &opts) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(x) => {
|
Err(x) => {
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
|
|
|
||||||