Merge pull request #78 from shssoichiro/23-Lossy-Reductions

Enable lossy alpha reductions
This commit is contained in:
Josh Holmer 2017-07-26 07:35:16 -04:00 committed by GitHub
commit f7dd2f0323
46 changed files with 3328 additions and 1933 deletions

View file

@ -11,7 +11,7 @@ matrix:
env: TARGET=x86_64-apple-darwin env: TARGET=x86_64-apple-darwin
cache: cargo cache: cargo
- os: linux - os: linux
rust: 1.17.0 rust: 1.19.0
env: TARGET=x86_64-unknown-linux-gnu env: TARGET=x86_64-unknown-linux-gnu
cache: cargo cache: cargo
- os: linux - os: linux

View file

@ -1,129 +1,139 @@
**Version 0.16.3** ### Version 0.17.0 (unreleased)
- [SEMVER_MAJOR] Bump minimum required rustc version to 1.19.0
- [SEMVER_MINOR] Oxipng will now, by default, attempt to change all transparent pixels to `rgba(0, 0, 0, 0)` to improve compression.
It does fast trials with filters 0 and 5 to see if this is an improvement over
the existing alpha channel.
- [SEMVER_MINOR] Add a `-a` option to the command line (`alphas` in the struct) which enables 6 different
trials for optimizing the alpha channel, using the previously mentioned fast heuristic.
This option will make optimization of images with transparency somewhat slower,
but may improve compression.
### Version 0.16.3
- Fix command-line help text ([#70](https://github.com/shssoichiro/oxipng/issues/70)) - Fix command-line help text ([#70](https://github.com/shssoichiro/oxipng/issues/70))
**Version 0.16.2** ### Version 0.16.2
- Publicly export `error` module - Publicly export `error` module
**Version 0.16.1** ### Version 0.16.1
- Fix rayon's breaking changes that they made in a point release - Fix rayon's breaking changes that they made in a point release
**Version 0.16.0** ### Version 0.16.0
- [SEMVER_MAJOR] Bump minimum rustc version to 1.17.0 - [SEMVER_MAJOR] Bump minimum rustc version to 1.17.0
- Bump `image` to 0.14 - Bump `image` to 0.14
- Bump `rayon` to 0.8 - Bump `rayon` to 0.8
**Version 0.15.2** ### Version 0.15.2
- Bump `image` to 0.13 ([#65](https://github.com/shssoichiro/oxipng/pull/65)) - Bump `image` to 0.13 ([#65](https://github.com/shssoichiro/oxipng/pull/65))
- Bump `rayon` to 0.7 - Bump `rayon` to 0.7
- Bump `itertools` to 0.6 - Bump `itertools` to 0.6
**Version 0.15.1** ### Version 0.15.1
- Ignore color reductions that would increase file size ([#61](https://github.com/shssoichiro/oxipng/issues/61)) - Ignore color reductions that would increase file size ([#61](https://github.com/shssoichiro/oxipng/issues/61))
**Version 0.15.0** ### Version 0.15.0
- [SEMVER_MINOR] Check images for correctness before writing result ([#60](https://github.com/shssoichiro/oxipng/issues/60)) - [SEMVER_MINOR] Check images for correctness before writing result ([#60](https://github.com/shssoichiro/oxipng/issues/60))
- Fix invalid output when reducing image to a different color type but file size does not improve ([#60](https://github.com/shssoichiro/oxipng/issues/60)) - Fix invalid output when reducing image to a different color type but file size does not improve ([#60](https://github.com/shssoichiro/oxipng/issues/60))
- Don't write new file if moving from interlaced to non-interlaced if new file would be larger - Don't write new file if moving from interlaced to non-interlaced if new file would be larger
**Version 0.14.4** ### Version 0.14.4
- Fix bug when reducing RGBA to Indexed if image has 256 colors plus a background color - Fix bug when reducing RGBA to Indexed if image has 256 colors plus a background color
**Version 0.14.3** ### Version 0.14.3
- Fix multiple bugs when reducing transparency palettes - Fix multiple bugs when reducing transparency palettes
**Version 0.14.2** ### Version 0.14.2
- Fix a bug when reducing palette in images with bit depth less than 8 - Fix a bug when reducing palette in images with bit depth less than 8
- Fix a bug when reducing palette in images with transparency - Fix a bug when reducing palette in images with transparency
**Version 0.14.1** ### Version 0.14.1
- Remove zlib dependency and switch entirely to miniz, since zlib 1.2.11 was not working with oxipng. This costs some performance, but is better than having a broken application. - Remove zlib dependency and switch entirely to miniz, since zlib 1.2.11 was not working with oxipng. This costs some performance, but is better than having a broken application.
**Version 0.14.0** ### Version 0.14.0
- Performance optimizations - Performance optimizations
- [SEMVER_MAJOR] Bump minimum rustc version to 1.13.0 - [SEMVER_MAJOR] Bump minimum rustc version to 1.13.0
- Add categories on crates.io - Add categories on crates.io
**Version 0.13.1** ### Version 0.13.1
- Bump regex dependency to 0.2 - Bump regex dependency to 0.2
- Bump byteorder dependency to 1.0 - Bump byteorder dependency to 1.0
- Bump rayon dependency to 0.6 - Bump rayon dependency to 0.6
**Version 0.13.0** ### Version 0.13.0
- Fix bug in certain PNG headers when reducing color type ([#52](https://github.com/shssoichiro/oxipng/issues/52)) - Fix bug in certain PNG headers when reducing color type ([#52](https://github.com/shssoichiro/oxipng/issues/52))
- [SEMVER_MAJOR] Reduction functions now take `&mut PngData` and return a `bool` indicating whether the image was reduced - [SEMVER_MAJOR] Reduction functions now take `&mut PngData` and return a `bool` indicating whether the image was reduced
- [SMEVER_MAJOR] Bump minimum required rustc version to 1.12.0 - [SMEVER_MAJOR] Bump minimum required rustc version to 1.12.0
**Version 0.12.0** ### Version 0.12.0
- Performance optimizations - Performance optimizations
- Fix processing filenames that contain commas (@aliceatlas [#50](https://github.com/shssoichiro/oxipng/pull/50)) - Fix processing filenames that contain commas (@aliceatlas [#50](https://github.com/shssoichiro/oxipng/pull/50))
- [SEMVER_MINOR] Add zopfli option (-Z), disabled by default. Gives about 10% better compression, but is currently 50-100x slower. - [SEMVER_MINOR] Add zopfli option (-Z), disabled by default. Gives about 10% better compression, but is currently 50-100x slower.
**Version 0.11.0** ### Version 0.11.0
- [SEMVER_MAJOR] Bump minimum rustc version to 1.9.0, required by dependencies - [SEMVER_MAJOR] Bump minimum rustc version to 1.9.0, required by dependencies
- [SEMVER_MINOR] Allow calling optimization presets via crate using `Options::from_preset` - [SEMVER_MINOR] Allow calling optimization presets via crate using `Options::from_preset`
- [SEMVER_MAJOR] Return proper `PngError` type which implements `std::error::Error` from `Result`s - [SEMVER_MAJOR] Return proper `PngError` type which implements `std::error::Error` from `Result`s
- [SEMVER_MAJOR] Rename module `deflate::deflate` to `deflate` - [SEMVER_MAJOR] Rename module `deflate::deflate` to `deflate`
- Performance optimizations - Performance optimizations
**Version 0.10.0** ### Version 0.10.0
- [SEMVER_MINOR] Make clap and regex dependencies optional - [SEMVER_MINOR] Make clap and regex dependencies optional
- Enabled by default, needed for executable build; can be disabled for use in crates - Enabled by default, needed for executable build; can be disabled for use in crates
- Remove reduction from palette to grayscale, which was not working and provided minimal benefit - Remove reduction from palette to grayscale, which was not working and provided minimal benefit
**Version 0.9.0** ### Version 0.9.0
- [SEMVER_MAJOR] Significant refactoring of modules - [SEMVER_MAJOR] Significant refactoring of modules
- Use `itertools` to cleanup areas of code - Use `itertools` to cleanup areas of code
- Use multiple threads for filtering trials - Use multiple threads for filtering trials
**Version 0.8.2** ### Version 0.8.2
- Fix issue where images smaller than 4px width would crash on interlacing ([#42](https://github.com/shssoichiro/oxipng/issues/42)) - Fix issue where images smaller than 4px width would crash on interlacing ([#42](https://github.com/shssoichiro/oxipng/issues/42))
**Version 0.8.1** ### Version 0.8.1
- Minor optimizations - Minor optimizations
- Fix issue where interlaced images with certain widths would fail to optimize - Fix issue where interlaced images with certain widths would fail to optimize
**Version 0.8.0** ### Version 0.8.0
- [SEMVER_MINOR] Add support for optimizing PNGs already loaded into memory via library function - [SEMVER_MINOR] Add support for optimizing PNGs already loaded into memory via library function
**Version 0.7.0** ### Version 0.7.0
- Minor compression improvement on interlaced images - Minor compression improvement on interlaced images
- Performance optimizations - Performance optimizations
- [SEMVER_MINOR] Move default Options into a Default impl - [SEMVER_MINOR] Move default Options into a Default impl
- [SEMVER_MINOR] Add option for setting number of threads ([#39](https://github.com/shssoichiro/oxipng/issues/39)) - [SEMVER_MINOR] Add option for setting number of threads ([#39](https://github.com/shssoichiro/oxipng/issues/39))
**Version 0.6.0** ### Version 0.6.0
- Fix issue where output directory would not be created if it did not exist - Fix issue where output directory would not be created if it did not exist
- Use miniz for compression strategies where it outperforms zlib - Use miniz for compression strategies where it outperforms zlib
- [SEMVER_MINOR] Partially implement -p / --preserve, as far as stable Rust will allow for now - [SEMVER_MINOR] Partially implement -p / --preserve, as far as stable Rust will allow for now
- [SEMVER_MINOR] Implement --fix to ignore CRC errors and recalculate correct CRC in output - [SEMVER_MINOR] Implement --fix to ignore CRC errors and recalculate correct CRC in output
**Version 0.5.0** ### Version 0.5.0
- [SEMVER_MINOR] Palette entries can now reduced, on by default ([#11](https://github.com/shssoichiro/oxipng/issues/11)) - [SEMVER_MINOR] Palette entries can now reduced, on by default ([#11](https://github.com/shssoichiro/oxipng/issues/11))
- Don't report that we are in pretend mode if verbosity is set to none - Don't report that we are in pretend mode if verbosity is set to none
- Add cargo bench suite ([#7](https://github.com/shssoichiro/oxipng/issues/7)) - Add cargo bench suite ([#7](https://github.com/shssoichiro/oxipng/issues/7))
**Version 0.4.0** ### Version 0.4.0
- Performance optimizations - Performance optimizations
- [SEMVER_MAJOR] `-s` automatically infers `--strip safe` ([#31](https://github.com/shssoichiro/oxipng/issues/31)) - [SEMVER_MAJOR] `-s` automatically infers `--strip safe` ([#31](https://github.com/shssoichiro/oxipng/issues/31))
- Update byteorder and clap crates - Update byteorder and clap crates
- Fix issue where interlaced images incorrectly applied filters on the first line of a pass - Fix issue where interlaced images incorrectly applied filters on the first line of a pass
**Version 0.3.0** ### Version 0.3.0
- Properly decode interlaced images - Properly decode interlaced images
- [SEMVER_MINOR] Allow converting between progressive and interlaced images ([#3](https://github.com/shssoichiro/oxipng/issues/3)) - [SEMVER_MINOR] Allow converting between progressive and interlaced images ([#3](https://github.com/shssoichiro/oxipng/issues/3))
- Fix a bug that would cause oxipng to crash on very small images - Fix a bug that would cause oxipng to crash on very small images
**Version 0.2.2** ### Version 0.2.2
- Limit number of threads to 1.5x number of cores - Limit number of threads to 1.5x number of cores
- Significantly improve memory usage, especially with high optimization levels. ([#32](https://github.com/shssoichiro/oxipng/issues/32)) - Significantly improve memory usage, especially with high optimization levels. ([#32](https://github.com/shssoichiro/oxipng/issues/32))
- Refactor output code ([#19](https://github.com/shssoichiro/oxipng/issues/19)) - Refactor output code ([#19](https://github.com/shssoichiro/oxipng/issues/19))
**Version 0.2.1** ### Version 0.2.1
- Add rustdoc for public methods and structs - Add rustdoc for public methods and structs
- Improve filter mode 5 heuristic ([#16](https://github.com/shssoichiro/oxipng/issues/16)) - Improve filter mode 5 heuristic ([#16](https://github.com/shssoichiro/oxipng/issues/16))
- Add tests for edge-case images with subtitles ([#29](https://github.com/shssoichiro/oxipng/issues/29)) - Add tests for edge-case images with subtitles ([#29](https://github.com/shssoichiro/oxipng/issues/29))
**Version 0.2.0** ### Version 0.2.0
- Fix program version that is displayed when running `oxipng -V` - Fix program version that is displayed when running `oxipng -V`
- Ensure `--quiet` mode is actually quiet (@SethDusek [#20](https://github.com/shssoichiro/oxipng/pull/20)) - Ensure `--quiet` mode is actually quiet (@SethDusek [#20](https://github.com/shssoichiro/oxipng/pull/20))
- Write status/debug information to stderr instead of stdout - Write status/debug information to stderr instead of stdout
@ -131,10 +141,10 @@
- [SEMVER_MAJOR] Allow 'safe', 'all', or comma-separated list as options for `--strip` - [SEMVER_MAJOR] Allow 'safe', 'all', or comma-separated list as options for `--strip`
- [SEMVER_MINOR] Add `-s` alias for `--strip` - [SEMVER_MINOR] Add `-s` alias for `--strip`
**Version 0.1.1** ### Version 0.1.1
- Fix `oxipng *` writing all input files to one output file ([#15](https://github.com/shssoichiro/oxipng/issues/15)) - Fix `oxipng *` writing all input files to one output file ([#15](https://github.com/shssoichiro/oxipng/issues/15))
**Version 0.1.0** ### Version 0.1.0
- Initial beta release - Initial beta release
- Reduce color type and bit depth - Reduce color type and bit depth
- Recompress with zlib - Recompress with zlib

View file

@ -27,7 +27,7 @@ cargo build --release
cp target/release/oxipng /usr/local/bin cp target/release/oxipng /usr/local/bin
``` ```
The current minimum supported Rust version is **1.17.0**. Oxipng may compile on earlier versions of Rust, The current minimum supported Rust version is **1.19.0**. Oxipng may compile on earlier versions of Rust,
but there is no guarantee. but there is no guarantee.
Oxipng follows Semantic Versioning. Oxipng follows Semantic Versioning.

View file

@ -13,7 +13,9 @@ fn deflate_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 = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { deflate::deflate(png.raw_data.as_ref(), 9, 9, 0, 15).ok(); }); b.iter(|| {
deflate::deflate(png.raw_data.as_ref(), 9, 9, 0, 15).ok();
});
} }
#[bench] #[bench]
@ -21,31 +23,45 @@ fn deflate_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 = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { deflate::deflate(png.raw_data.as_ref(), 9, 9, 0, 15).ok(); }); b.iter(|| {
deflate::deflate(png.raw_data.as_ref(), 9, 9, 0, 15).ok();
});
} }
#[bench] #[bench]
fn deflate_4_bits_strategy_0(b: &mut Bencher) { fn deflate_4_bits_strategy_0(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/palette_4_should_be_palette_4.png")); let input = test::black_box(PathBuf::from(
"tests/files/palette_4_should_be_palette_4.png",
));
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { deflate::deflate(png.raw_data.as_ref(), 9, 9, 0, 15).ok(); }); b.iter(|| {
deflate::deflate(png.raw_data.as_ref(), 9, 9, 0, 15).ok();
});
} }
#[bench] #[bench]
fn deflate_2_bits_strategy_0(b: &mut Bencher) { fn deflate_2_bits_strategy_0(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/palette_2_should_be_palette_2.png")); let input = test::black_box(PathBuf::from(
"tests/files/palette_2_should_be_palette_2.png",
));
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { deflate::deflate(png.raw_data.as_ref(), 9, 9, 0, 15).ok(); }); b.iter(|| {
deflate::deflate(png.raw_data.as_ref(), 9, 9, 0, 15).ok();
});
} }
#[bench] #[bench]
fn deflate_1_bits_strategy_0(b: &mut Bencher) { fn deflate_1_bits_strategy_0(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/palette_1_should_be_palette_1.png")); let input = test::black_box(PathBuf::from(
"tests/files/palette_1_should_be_palette_1.png",
));
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { deflate::deflate(png.raw_data.as_ref(), 9, 9, 0, 15).ok(); }); b.iter(|| {
deflate::deflate(png.raw_data.as_ref(), 9, 9, 0, 15).ok();
});
} }
#[bench] #[bench]
@ -53,7 +69,9 @@ fn deflate_16_bits_strategy_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 = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { deflate::deflate(png.raw_data.as_ref(), 9, 9, 1, 15).ok(); }); b.iter(|| {
deflate::deflate(png.raw_data.as_ref(), 9, 9, 1, 15).ok();
});
} }
#[bench] #[bench]
@ -61,31 +79,45 @@ fn deflate_8_bits_strategy_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 = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { deflate::deflate(png.raw_data.as_ref(), 9, 9, 1, 15).ok(); }); b.iter(|| {
deflate::deflate(png.raw_data.as_ref(), 9, 9, 1, 15).ok();
});
} }
#[bench] #[bench]
fn deflate_4_bits_strategy_1(b: &mut Bencher) { fn deflate_4_bits_strategy_1(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/palette_4_should_be_palette_4.png")); let input = test::black_box(PathBuf::from(
"tests/files/palette_4_should_be_palette_4.png",
));
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { deflate::deflate(png.raw_data.as_ref(), 9, 9, 1, 15).ok(); }); b.iter(|| {
deflate::deflate(png.raw_data.as_ref(), 9, 9, 1, 15).ok();
});
} }
#[bench] #[bench]
fn deflate_2_bits_strategy_1(b: &mut Bencher) { fn deflate_2_bits_strategy_1(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/palette_2_should_be_palette_2.png")); let input = test::black_box(PathBuf::from(
"tests/files/palette_2_should_be_palette_2.png",
));
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { deflate::deflate(png.raw_data.as_ref(), 9, 9, 1, 15).ok(); }); b.iter(|| {
deflate::deflate(png.raw_data.as_ref(), 9, 9, 1, 15).ok();
});
} }
#[bench] #[bench]
fn deflate_1_bits_strategy_1(b: &mut Bencher) { fn deflate_1_bits_strategy_1(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/palette_1_should_be_palette_1.png")); let input = test::black_box(PathBuf::from(
"tests/files/palette_1_should_be_palette_1.png",
));
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { deflate::deflate(png.raw_data.as_ref(), 9, 9, 1, 15).ok(); }); b.iter(|| {
deflate::deflate(png.raw_data.as_ref(), 9, 9, 1, 15).ok();
});
} }
#[bench] #[bench]
@ -93,7 +125,9 @@ fn deflate_16_bits_strategy_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 = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { deflate::deflate(png.raw_data.as_ref(), 9, 9, 2, 15).ok(); }); b.iter(|| {
deflate::deflate(png.raw_data.as_ref(), 9, 9, 2, 15).ok();
});
} }
#[bench] #[bench]
@ -101,31 +135,45 @@ fn deflate_8_bits_strategy_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 = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { deflate::deflate(png.raw_data.as_ref(), 9, 9, 2, 15).ok(); }); b.iter(|| {
deflate::deflate(png.raw_data.as_ref(), 9, 9, 2, 15).ok();
});
} }
#[bench] #[bench]
fn deflate_4_bits_strategy_2(b: &mut Bencher) { fn deflate_4_bits_strategy_2(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/palette_4_should_be_palette_4.png")); let input = test::black_box(PathBuf::from(
"tests/files/palette_4_should_be_palette_4.png",
));
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { deflate::deflate(png.raw_data.as_ref(), 9, 9, 2, 15).ok(); }); b.iter(|| {
deflate::deflate(png.raw_data.as_ref(), 9, 9, 2, 15).ok();
});
} }
#[bench] #[bench]
fn deflate_2_bits_strategy_2(b: &mut Bencher) { fn deflate_2_bits_strategy_2(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/palette_2_should_be_palette_2.png")); let input = test::black_box(PathBuf::from(
"tests/files/palette_2_should_be_palette_2.png",
));
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { deflate::deflate(png.raw_data.as_ref(), 9, 9, 2, 15).ok(); }); b.iter(|| {
deflate::deflate(png.raw_data.as_ref(), 9, 9, 2, 15).ok();
});
} }
#[bench] #[bench]
fn deflate_1_bits_strategy_2(b: &mut Bencher) { fn deflate_1_bits_strategy_2(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/palette_1_should_be_palette_1.png")); let input = test::black_box(PathBuf::from(
"tests/files/palette_1_should_be_palette_1.png",
));
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { deflate::deflate(png.raw_data.as_ref(), 9, 9, 2, 15).ok(); }); b.iter(|| {
deflate::deflate(png.raw_data.as_ref(), 9, 9, 2, 15).ok();
});
} }
#[bench] #[bench]
@ -133,7 +181,9 @@ fn deflate_16_bits_strategy_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 = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { deflate::deflate(png.raw_data.as_ref(), 9, 9, 3, 15).ok(); }); b.iter(|| {
deflate::deflate(png.raw_data.as_ref(), 9, 9, 3, 15).ok();
});
} }
#[bench] #[bench]
@ -141,31 +191,45 @@ fn deflate_8_bits_strategy_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 = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { deflate::deflate(png.raw_data.as_ref(), 9, 9, 3, 15).ok(); }); b.iter(|| {
deflate::deflate(png.raw_data.as_ref(), 9, 9, 3, 15).ok();
});
} }
#[bench] #[bench]
fn deflate_4_bits_strategy_3(b: &mut Bencher) { fn deflate_4_bits_strategy_3(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/palette_4_should_be_palette_4.png")); let input = test::black_box(PathBuf::from(
"tests/files/palette_4_should_be_palette_4.png",
));
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { deflate::deflate(png.raw_data.as_ref(), 9, 9, 3, 15).ok(); }); b.iter(|| {
deflate::deflate(png.raw_data.as_ref(), 9, 9, 3, 15).ok();
});
} }
#[bench] #[bench]
fn deflate_2_bits_strategy_3(b: &mut Bencher) { fn deflate_2_bits_strategy_3(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/palette_2_should_be_palette_2.png")); let input = test::black_box(PathBuf::from(
"tests/files/palette_2_should_be_palette_2.png",
));
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { deflate::deflate(png.raw_data.as_ref(), 9, 9, 3, 15).ok(); }); b.iter(|| {
deflate::deflate(png.raw_data.as_ref(), 9, 9, 3, 15).ok();
});
} }
#[bench] #[bench]
fn deflate_1_bits_strategy_3(b: &mut Bencher) { fn deflate_1_bits_strategy_3(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/palette_1_should_be_palette_1.png")); let input = test::black_box(PathBuf::from(
"tests/files/palette_1_should_be_palette_1.png",
));
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { deflate::deflate(png.raw_data.as_ref(), 9, 9, 3, 15).ok(); }); b.iter(|| {
deflate::deflate(png.raw_data.as_ref(), 9, 9, 3, 15).ok();
});
} }
#[bench] #[bench]

View file

@ -25,7 +25,9 @@ fn filters_8_bits_filter_0(b: &mut Bencher) {
#[bench] #[bench]
fn filters_4_bits_filter_0(b: &mut Bencher) { fn filters_4_bits_filter_0(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/palette_4_should_be_palette_4.png")); let input = test::black_box(PathBuf::from(
"tests/files/palette_4_should_be_palette_4.png",
));
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { png.filter_image(0); }); b.iter(|| { png.filter_image(0); });
@ -33,7 +35,9 @@ fn filters_4_bits_filter_0(b: &mut Bencher) {
#[bench] #[bench]
fn filters_2_bits_filter_0(b: &mut Bencher) { fn filters_2_bits_filter_0(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/palette_2_should_be_palette_2.png")); let input = test::black_box(PathBuf::from(
"tests/files/palette_2_should_be_palette_2.png",
));
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { png.filter_image(0); }); b.iter(|| { png.filter_image(0); });
@ -41,7 +45,9 @@ fn filters_2_bits_filter_0(b: &mut Bencher) {
#[bench] #[bench]
fn filters_1_bits_filter_0(b: &mut Bencher) { fn filters_1_bits_filter_0(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/palette_1_should_be_palette_1.png")); let input = test::black_box(PathBuf::from(
"tests/files/palette_1_should_be_palette_1.png",
));
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { png.filter_image(0); }); b.iter(|| { png.filter_image(0); });
@ -65,7 +71,9 @@ fn filters_8_bits_filter_1(b: &mut Bencher) {
#[bench] #[bench]
fn filters_4_bits_filter_1(b: &mut Bencher) { fn filters_4_bits_filter_1(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/palette_4_should_be_palette_4.png")); let input = test::black_box(PathBuf::from(
"tests/files/palette_4_should_be_palette_4.png",
));
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { png.filter_image(1); }); b.iter(|| { png.filter_image(1); });
@ -73,7 +81,9 @@ fn filters_4_bits_filter_1(b: &mut Bencher) {
#[bench] #[bench]
fn filters_2_bits_filter_1(b: &mut Bencher) { fn filters_2_bits_filter_1(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/palette_2_should_be_palette_2.png")); let input = test::black_box(PathBuf::from(
"tests/files/palette_2_should_be_palette_2.png",
));
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { png.filter_image(1); }); b.iter(|| { png.filter_image(1); });
@ -81,7 +91,9 @@ fn filters_2_bits_filter_1(b: &mut Bencher) {
#[bench] #[bench]
fn filters_1_bits_filter_1(b: &mut Bencher) { fn filters_1_bits_filter_1(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/palette_1_should_be_palette_1.png")); let input = test::black_box(PathBuf::from(
"tests/files/palette_1_should_be_palette_1.png",
));
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { png.filter_image(1); }); b.iter(|| { png.filter_image(1); });
@ -105,7 +117,9 @@ fn filters_8_bits_filter_2(b: &mut Bencher) {
#[bench] #[bench]
fn filters_4_bits_filter_2(b: &mut Bencher) { fn filters_4_bits_filter_2(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/palette_4_should_be_palette_4.png")); let input = test::black_box(PathBuf::from(
"tests/files/palette_4_should_be_palette_4.png",
));
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { png.filter_image(2); }); b.iter(|| { png.filter_image(2); });
@ -113,7 +127,9 @@ fn filters_4_bits_filter_2(b: &mut Bencher) {
#[bench] #[bench]
fn filters_2_bits_filter_2(b: &mut Bencher) { fn filters_2_bits_filter_2(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/palette_2_should_be_palette_2.png")); let input = test::black_box(PathBuf::from(
"tests/files/palette_2_should_be_palette_2.png",
));
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { png.filter_image(2); }); b.iter(|| { png.filter_image(2); });
@ -121,7 +137,9 @@ fn filters_2_bits_filter_2(b: &mut Bencher) {
#[bench] #[bench]
fn filters_1_bits_filter_2(b: &mut Bencher) { fn filters_1_bits_filter_2(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/palette_1_should_be_palette_1.png")); let input = test::black_box(PathBuf::from(
"tests/files/palette_1_should_be_palette_1.png",
));
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { png.filter_image(2); }); b.iter(|| { png.filter_image(2); });
@ -145,7 +163,9 @@ fn filters_8_bits_filter_3(b: &mut Bencher) {
#[bench] #[bench]
fn filters_4_bits_filter_3(b: &mut Bencher) { fn filters_4_bits_filter_3(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/palette_4_should_be_palette_4.png")); let input = test::black_box(PathBuf::from(
"tests/files/palette_4_should_be_palette_4.png",
));
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { png.filter_image(3); }); b.iter(|| { png.filter_image(3); });
@ -153,7 +173,9 @@ fn filters_4_bits_filter_3(b: &mut Bencher) {
#[bench] #[bench]
fn filters_2_bits_filter_3(b: &mut Bencher) { fn filters_2_bits_filter_3(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/palette_2_should_be_palette_2.png")); let input = test::black_box(PathBuf::from(
"tests/files/palette_2_should_be_palette_2.png",
));
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { png.filter_image(3); }); b.iter(|| { png.filter_image(3); });
@ -161,7 +183,9 @@ fn filters_2_bits_filter_3(b: &mut Bencher) {
#[bench] #[bench]
fn filters_1_bits_filter_3(b: &mut Bencher) { fn filters_1_bits_filter_3(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/palette_1_should_be_palette_1.png")); let input = test::black_box(PathBuf::from(
"tests/files/palette_1_should_be_palette_1.png",
));
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { png.filter_image(3); }); b.iter(|| { png.filter_image(3); });
@ -185,7 +209,9 @@ fn filters_8_bits_filter_4(b: &mut Bencher) {
#[bench] #[bench]
fn filters_4_bits_filter_4(b: &mut Bencher) { fn filters_4_bits_filter_4(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/palette_4_should_be_palette_4.png")); let input = test::black_box(PathBuf::from(
"tests/files/palette_4_should_be_palette_4.png",
));
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { png.filter_image(4); }); b.iter(|| { png.filter_image(4); });
@ -193,7 +219,9 @@ fn filters_4_bits_filter_4(b: &mut Bencher) {
#[bench] #[bench]
fn filters_2_bits_filter_4(b: &mut Bencher) { fn filters_2_bits_filter_4(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/palette_2_should_be_palette_2.png")); let input = test::black_box(PathBuf::from(
"tests/files/palette_2_should_be_palette_2.png",
));
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { png.filter_image(4); }); b.iter(|| { png.filter_image(4); });
@ -201,7 +229,9 @@ fn filters_2_bits_filter_4(b: &mut Bencher) {
#[bench] #[bench]
fn filters_1_bits_filter_4(b: &mut Bencher) { fn filters_1_bits_filter_4(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/palette_1_should_be_palette_1.png")); let input = test::black_box(PathBuf::from(
"tests/files/palette_1_should_be_palette_1.png",
));
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { png.filter_image(4); }); b.iter(|| { png.filter_image(4); });
@ -225,7 +255,9 @@ fn filters_8_bits_filter_5(b: &mut Bencher) {
#[bench] #[bench]
fn filters_4_bits_filter_5(b: &mut Bencher) { fn filters_4_bits_filter_5(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/palette_4_should_be_palette_4.png")); let input = test::black_box(PathBuf::from(
"tests/files/palette_4_should_be_palette_4.png",
));
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { png.filter_image(5); }); b.iter(|| { png.filter_image(5); });
@ -233,7 +265,9 @@ fn filters_4_bits_filter_5(b: &mut Bencher) {
#[bench] #[bench]
fn filters_2_bits_filter_5(b: &mut Bencher) { fn filters_2_bits_filter_5(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/palette_2_should_be_palette_2.png")); let input = test::black_box(PathBuf::from(
"tests/files/palette_2_should_be_palette_2.png",
));
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { png.filter_image(5); }); b.iter(|| { png.filter_image(5); });
@ -241,7 +275,9 @@ fn filters_2_bits_filter_5(b: &mut Bencher) {
#[bench] #[bench]
fn filters_1_bits_filter_5(b: &mut Bencher) { fn filters_1_bits_filter_5(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/palette_1_should_be_palette_1.png")); let input = test::black_box(PathBuf::from(
"tests/files/palette_1_should_be_palette_1.png",
));
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { png.filter_image(5); }); b.iter(|| { png.filter_image(5); });

View file

@ -13,9 +13,9 @@ fn interlacing_16_bits(b: &mut Bencher) {
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { b.iter(|| {
let mut safe_png = png.clone(); let mut safe_png = png.clone();
safe_png.change_interlacing(1); safe_png.change_interlacing(1);
}); });
} }
#[bench] #[bench]
@ -24,95 +24,111 @@ fn interlacing_8_bits(b: &mut Bencher) {
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { b.iter(|| {
let mut safe_png = png.clone(); let mut safe_png = png.clone();
safe_png.change_interlacing(1); safe_png.change_interlacing(1);
}); });
} }
#[bench] #[bench]
fn interlacing_4_bits(b: &mut Bencher) { fn interlacing_4_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/palette_4_should_be_palette_4.png")); let input = test::black_box(PathBuf::from(
"tests/files/palette_4_should_be_palette_4.png",
));
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { b.iter(|| {
let mut safe_png = png.clone(); let mut safe_png = png.clone();
safe_png.change_interlacing(1); safe_png.change_interlacing(1);
}); });
} }
#[bench] #[bench]
fn interlacing_2_bits(b: &mut Bencher) { fn interlacing_2_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/palette_2_should_be_palette_2.png")); let input = test::black_box(PathBuf::from(
"tests/files/palette_2_should_be_palette_2.png",
));
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { b.iter(|| {
let mut safe_png = png.clone(); let mut safe_png = png.clone();
safe_png.change_interlacing(1); safe_png.change_interlacing(1);
}); });
} }
#[bench] #[bench]
fn interlacing_1_bits(b: &mut Bencher) { fn interlacing_1_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/palette_1_should_be_palette_1.png")); let input = test::black_box(PathBuf::from(
"tests/files/palette_1_should_be_palette_1.png",
));
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { b.iter(|| {
let mut safe_png = png.clone(); let mut safe_png = png.clone();
safe_png.change_interlacing(1); safe_png.change_interlacing(1);
}); });
} }
#[bench] #[bench]
fn deinterlacing_16_bits(b: &mut Bencher) { fn deinterlacing_16_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/interlaced_rgb_16_should_be_rgb_16.png")); let input = test::black_box(PathBuf::from(
"tests/files/interlaced_rgb_16_should_be_rgb_16.png",
));
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { b.iter(|| {
let mut safe_png = png.clone(); let mut safe_png = png.clone();
safe_png.change_interlacing(0); safe_png.change_interlacing(0);
}); });
} }
#[bench] #[bench]
fn deinterlacing_8_bits(b: &mut Bencher) { fn deinterlacing_8_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/interlaced_rgb_8_should_be_rgb_8.png")); let input = test::black_box(PathBuf::from(
"tests/files/interlaced_rgb_8_should_be_rgb_8.png",
));
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { b.iter(|| {
let mut safe_png = png.clone(); let mut safe_png = png.clone();
safe_png.change_interlacing(0); safe_png.change_interlacing(0);
}); });
} }
#[bench] #[bench]
fn deinterlacing_4_bits(b: &mut Bencher) { fn deinterlacing_4_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/interlaced_palette_4_should_be_palette_4.png")); let input = test::black_box(PathBuf::from(
"tests/files/interlaced_palette_4_should_be_palette_4.png",
));
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { b.iter(|| {
let mut safe_png = png.clone(); let mut safe_png = png.clone();
safe_png.change_interlacing(0); safe_png.change_interlacing(0);
}); });
} }
#[bench] #[bench]
fn deinterlacing_2_bits(b: &mut Bencher) { fn deinterlacing_2_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/interlaced_palette_2_should_be_palette_2.png")); let input = test::black_box(PathBuf::from(
"tests/files/interlaced_palette_2_should_be_palette_2.png",
));
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { b.iter(|| {
let mut safe_png = png.clone(); let mut safe_png = png.clone();
safe_png.change_interlacing(0); safe_png.change_interlacing(0);
}); });
} }
#[bench] #[bench]
fn deinterlacing_1_bits(b: &mut Bencher) { fn deinterlacing_1_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/interlaced_palette_1_should_be_palette_1.png")); let input = test::black_box(PathBuf::from(
"tests/files/interlaced_palette_1_should_be_palette_1.png",
));
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { b.iter(|| {
let mut safe_png = png.clone(); let mut safe_png = png.clone();
safe_png.change_interlacing(0); safe_png.change_interlacing(0);
}); });
} }

View file

@ -4,6 +4,7 @@ extern crate test;
extern crate oxipng; extern crate oxipng;
use oxipng::png; use oxipng::png;
use oxipng::colors::AlphaOptim;
use test::Bencher; use test::Bencher;
use std::path::PathBuf; use std::path::PathBuf;
@ -13,75 +14,87 @@ fn reductions_16_to_8_bits(b: &mut Bencher) {
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { b.iter(|| {
let mut safe_png = png.clone(); let mut safe_png = png.clone();
safe_png.reduce_bit_depth(); safe_png.reduce_bit_depth();
}); });
} }
#[bench] #[bench]
fn reductions_8_to_4_bits(b: &mut Bencher) { fn reductions_8_to_4_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/palette_8_should_be_palette_4.png")); let input = test::black_box(PathBuf::from(
"tests/files/palette_8_should_be_palette_4.png",
));
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { b.iter(|| {
let mut safe_png = png.clone(); let mut safe_png = png.clone();
safe_png.reduce_bit_depth(); safe_png.reduce_bit_depth();
}); });
} }
#[bench] #[bench]
fn reductions_8_to_2_bits(b: &mut Bencher) { fn reductions_8_to_2_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/palette_8_should_be_palette_2.png")); let input = test::black_box(PathBuf::from(
"tests/files/palette_8_should_be_palette_2.png",
));
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { b.iter(|| {
let mut safe_png = png.clone(); let mut safe_png = png.clone();
safe_png.reduce_bit_depth(); safe_png.reduce_bit_depth();
}); });
} }
#[bench] #[bench]
fn reductions_8_to_1_bits(b: &mut Bencher) { fn reductions_8_to_1_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/palette_8_should_be_palette_1.png")); let input = test::black_box(PathBuf::from(
"tests/files/palette_8_should_be_palette_1.png",
));
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { b.iter(|| {
let mut safe_png = png.clone(); let mut safe_png = png.clone();
safe_png.reduce_bit_depth(); safe_png.reduce_bit_depth();
}); });
} }
#[bench] #[bench]
fn reductions_4_to_2_bits(b: &mut Bencher) { fn reductions_4_to_2_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/palette_4_should_be_palette_2.png")); let input = test::black_box(PathBuf::from(
"tests/files/palette_4_should_be_palette_2.png",
));
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { b.iter(|| {
let mut safe_png = png.clone(); let mut safe_png = png.clone();
safe_png.reduce_bit_depth(); safe_png.reduce_bit_depth();
}); });
} }
#[bench] #[bench]
fn reductions_4_to_1_bits(b: &mut Bencher) { fn reductions_4_to_1_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/palette_4_should_be_palette_1.png")); let input = test::black_box(PathBuf::from(
"tests/files/palette_4_should_be_palette_1.png",
));
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { b.iter(|| {
let mut safe_png = png.clone(); let mut safe_png = png.clone();
safe_png.reduce_bit_depth(); safe_png.reduce_bit_depth();
}); });
} }
#[bench] #[bench]
fn reductions_2_to_1_bits(b: &mut Bencher) { fn reductions_2_to_1_bits(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/palette_2_should_be_palette_1.png")); let input = test::black_box(PathBuf::from(
"tests/files/palette_2_should_be_palette_1.png",
));
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { b.iter(|| {
let mut safe_png = png.clone(); let mut safe_png = png.clone();
safe_png.reduce_bit_depth(); safe_png.reduce_bit_depth();
}); });
} }
#[bench] #[bench]
@ -90,9 +103,9 @@ fn reductions_rgba_to_rgb_16(b: &mut Bencher) {
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { b.iter(|| {
let mut safe_png = png.clone(); let mut safe_png = png.clone();
safe_png.reduce_color_type(); safe_png.reduce_color_type();
}); });
} }
#[bench] #[bench]
@ -101,64 +114,74 @@ fn reductions_rgba_to_rgb_8(b: &mut Bencher) {
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { b.iter(|| {
let mut safe_png = png.clone(); let mut safe_png = png.clone();
safe_png.reduce_color_type(); safe_png.reduce_color_type();
}); });
} }
#[bench] #[bench]
fn reductions_rgba_to_grayscale_alpha_16(b: &mut Bencher) { fn reductions_rgba_to_grayscale_alpha_16(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/rgba_16_should_be_grayscale_alpha_16.png")); let input = test::black_box(PathBuf::from(
"tests/files/rgba_16_should_be_grayscale_alpha_16.png",
));
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { b.iter(|| {
let mut safe_png = png.clone(); let mut safe_png = png.clone();
safe_png.reduce_color_type(); safe_png.reduce_color_type();
}); });
} }
#[bench] #[bench]
fn reductions_rgba_to_grayscale_alpha_8(b: &mut Bencher) { fn reductions_rgba_to_grayscale_alpha_8(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/rgba_8_should_be_grayscale_alpha_8.png")); let input = test::black_box(PathBuf::from(
"tests/files/rgba_8_should_be_grayscale_alpha_8.png",
));
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { b.iter(|| {
let mut safe_png = png.clone(); let mut safe_png = png.clone();
safe_png.reduce_color_type(); safe_png.reduce_color_type();
}); });
} }
#[bench] #[bench]
fn reductions_rgba_to_grayscale_16(b: &mut Bencher) { fn reductions_rgba_to_grayscale_16(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/rgba_16_should_be_grayscale_16.png")); let input = test::black_box(PathBuf::from(
"tests/files/rgba_16_should_be_grayscale_16.png",
));
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { b.iter(|| {
let mut safe_png = png.clone(); let mut safe_png = png.clone();
safe_png.reduce_color_type(); safe_png.reduce_color_type();
}); });
} }
#[bench] #[bench]
fn reductions_rgba_to_grayscale_8(b: &mut Bencher) { fn reductions_rgba_to_grayscale_8(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/rgba_8_should_be_grayscale_8.png")); let input = test::black_box(PathBuf::from(
"tests/files/rgba_8_should_be_grayscale_8.png",
));
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { b.iter(|| {
let mut safe_png = png.clone(); let mut safe_png = png.clone();
safe_png.reduce_color_type(); safe_png.reduce_color_type();
}); });
} }
#[bench] #[bench]
fn reductions_rgb_to_grayscale_16(b: &mut Bencher) { fn reductions_rgb_to_grayscale_16(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_grayscale_16.png")); let input = test::black_box(PathBuf::from(
"tests/files/rgb_16_should_be_grayscale_16.png",
));
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { b.iter(|| {
let mut safe_png = png.clone(); let mut safe_png = png.clone();
safe_png.reduce_color_type(); safe_png.reduce_color_type();
}); });
} }
#[bench] #[bench]
@ -167,9 +190,9 @@ fn reductions_rgb_to_grayscale_8(b: &mut Bencher) {
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { b.iter(|| {
let mut safe_png = png.clone(); let mut safe_png = png.clone();
safe_png.reduce_color_type(); safe_png.reduce_color_type();
}); });
} }
#[bench] #[bench]
@ -178,9 +201,9 @@ fn reductions_rgba_to_palette_8(b: &mut Bencher) {
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { b.iter(|| {
let mut safe_png = png.clone(); let mut safe_png = png.clone();
safe_png.reduce_color_type(); safe_png.reduce_color_type();
}); });
} }
#[bench] #[bench]
@ -189,40 +212,124 @@ fn reductions_rgb_to_palette_8(b: &mut Bencher) {
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { b.iter(|| {
let mut safe_png = png.clone(); let mut safe_png = png.clone();
safe_png.reduce_color_type(); safe_png.reduce_color_type();
}); });
} }
#[bench] #[bench]
fn reductions_palette_duplicate_reduction(b: &mut Bencher) { fn reductions_palette_duplicate_reduction(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/palette_should_be_reduced_with_dupes.png")); let input = test::black_box(PathBuf::from(
"tests/files/palette_should_be_reduced_with_dupes.png",
));
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { b.iter(|| {
let mut safe_png = png.clone(); let mut safe_png = png.clone();
safe_png.reduce_palette(); safe_png.reduce_palette();
}); });
} }
#[bench] #[bench]
fn reductions_palette_unused_reduction(b: &mut Bencher) { fn reductions_palette_unused_reduction(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/palette_should_be_reduced_with_unused.png")); let input = test::black_box(PathBuf::from(
"tests/files/palette_should_be_reduced_with_unused.png",
));
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { b.iter(|| {
let mut safe_png = png.clone(); let mut safe_png = png.clone();
safe_png.reduce_palette(); safe_png.reduce_palette();
}); });
} }
#[bench] #[bench]
fn reductions_palette_full_reduction(b: &mut Bencher) { fn reductions_palette_full_reduction(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/palette_should_be_reduced_with_both.png")); let input = test::black_box(PathBuf::from(
"tests/files/palette_should_be_reduced_with_both.png",
));
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { b.iter(|| {
let mut safe_png = png.clone(); let mut safe_png = png.clone();
safe_png.reduce_palette(); safe_png.reduce_palette();
}); });
}
#[bench]
fn reductions_alpha_black(b: &mut Bencher) {
let input = test::black_box(PathBuf::from(
"tests/files/rgba_8_reduce_alpha_black.png",
));
let png = png::PngData::new(&input, false).unwrap();
b.iter(|| {
let mut safe_png = png.clone();
safe_png.reduce_alpha_channel(AlphaOptim::Black);
});
}
#[bench]
fn reductions_alpha_white(b: &mut Bencher) {
let input = test::black_box(PathBuf::from(
"tests/files/rgba_8_reduce_alpha_white.png",
));
let png = png::PngData::new(&input, false).unwrap();
b.iter(|| {
let mut safe_png = png.clone();
safe_png.reduce_alpha_channel(AlphaOptim::White);
});
}
#[bench]
fn reductions_alpha_left(b: &mut Bencher) {
let input = test::black_box(PathBuf::from(
"tests/files/rgba_8_reduce_alpha_left.png",
));
let png = png::PngData::new(&input, false).unwrap();
b.iter(|| {
let mut safe_png = png.clone();
safe_png.reduce_alpha_channel(AlphaOptim::Left);
});
}
#[bench]
fn reductions_alpha_right(b: &mut Bencher) {
let input = test::black_box(PathBuf::from(
"tests/files/rgba_8_reduce_alpha_right.png",
));
let png = png::PngData::new(&input, false).unwrap();
b.iter(|| {
let mut safe_png = png.clone();
safe_png.reduce_alpha_channel(AlphaOptim::Right);
});
}
#[bench]
fn reductions_alpha_up(b: &mut Bencher) {
let input = test::black_box(PathBuf::from(
"tests/files/rgba_8_reduce_alpha_up.png",
));
let png = png::PngData::new(&input, false).unwrap();
b.iter(|| {
let mut safe_png = png.clone();
safe_png.reduce_alpha_channel(AlphaOptim::Up);
});
}
#[bench]
fn reductions_alpha_down(b: &mut Bencher) {
let input = test::black_box(PathBuf::from(
"tests/files/rgba_8_reduce_alpha_down.png",
));
let png = png::PngData::new(&input, false).unwrap();
b.iter(|| {
let mut safe_png = png.clone();
safe_png.reduce_alpha_channel(AlphaOptim::Down);
});
} }

View file

@ -26,7 +26,9 @@ fn zopfli_8_bits_strategy_0(b: &mut Bencher) {
#[bench] #[bench]
fn zopfli_4_bits_strategy_0(b: &mut Bencher) { fn zopfli_4_bits_strategy_0(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/palette_4_should_be_palette_4.png")); let input = test::black_box(PathBuf::from(
"tests/files/palette_4_should_be_palette_4.png",
));
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { deflate::zopfli_deflate(png.raw_data.as_ref()).ok(); }); b.iter(|| { deflate::zopfli_deflate(png.raw_data.as_ref()).ok(); });
@ -34,7 +36,9 @@ fn zopfli_4_bits_strategy_0(b: &mut Bencher) {
#[bench] #[bench]
fn zopfli_2_bits_strategy_0(b: &mut Bencher) { fn zopfli_2_bits_strategy_0(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/palette_2_should_be_palette_2.png")); let input = test::black_box(PathBuf::from(
"tests/files/palette_2_should_be_palette_2.png",
));
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { deflate::zopfli_deflate(png.raw_data.as_ref()).ok(); }); b.iter(|| { deflate::zopfli_deflate(png.raw_data.as_ref()).ok(); });
@ -42,7 +46,9 @@ fn zopfli_2_bits_strategy_0(b: &mut Bencher) {
#[bench] #[bench]
fn zopfli_1_bits_strategy_0(b: &mut Bencher) { fn zopfli_1_bits_strategy_0(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/palette_1_should_be_palette_1.png")); let input = test::black_box(PathBuf::from(
"tests/files/palette_1_should_be_palette_1.png",
));
let png = png::PngData::new(&input, false).unwrap(); let png = png::PngData::new(&input, false).unwrap();
b.iter(|| { deflate::zopfli_deflate(png.raw_data.as_ref()).ok(); }); b.iter(|| { deflate::zopfli_deflate(png.raw_data.as_ref()).ok(); });

View file

@ -18,13 +18,17 @@ pub enum ColorType {
impl fmt::Display for ColorType { impl fmt::Display for ColorType {
#[inline] #[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", match *self { write!(
ColorType::Grayscale => "Grayscale", f,
ColorType::RGB => "RGB", "{}",
ColorType::Indexed => "Indexed", match *self {
ColorType::GrayscaleAlpha => "Grayscale + Alpha", ColorType::Grayscale => "Grayscale",
ColorType::RGBA => "RGB + Alpha", ColorType::RGB => "RGB",
}) ColorType::Indexed => "Indexed",
ColorType::GrayscaleAlpha => "Grayscale + Alpha",
ColorType::RGBA => "RGB + Alpha",
}
)
} }
} }
@ -60,13 +64,17 @@ pub enum BitDepth {
impl fmt::Display for BitDepth { impl fmt::Display for BitDepth {
#[inline] #[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", match *self { write!(
BitDepth::One => "1", f,
BitDepth::Two => "2", "{}",
BitDepth::Four => "4", match *self {
BitDepth::Eight => "8", BitDepth::One => "1",
BitDepth::Sixteen => "16", BitDepth::Two => "2",
}) BitDepth::Four => "4",
BitDepth::Eight => "8",
BitDepth::Sixteen => "16",
}
)
} }
} }
@ -95,3 +103,33 @@ impl BitDepth {
} }
} }
} }
#[derive(Debug, PartialEq, Clone, Copy, Eq, Hash)]
/// Potential optimization methods for alpha channel
pub enum AlphaOptim {
NoOp,
Black,
White,
Up,
Right,
Down,
Left,
}
impl fmt::Display for AlphaOptim {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{}",
match *self {
AlphaOptim::NoOp => "_",
AlphaOptim::Black => "B",
AlphaOptim::White => "W",
AlphaOptim::Up => "U",
AlphaOptim::Right => "R",
AlphaOptim::Down => "D",
AlphaOptim::Left => "L",
}
)
}
}

View file

@ -22,19 +22,22 @@ pub trait Direction {
} }
impl Stream<Compress> { impl Stream<Compress> {
pub fn new_compress(lvl: c_int, pub fn new_compress(
window_bits: c_int, lvl: c_int,
mem_size: c_int, window_bits: c_int,
strategy: c_int) mem_size: c_int,
-> Stream<Compress> { strategy: c_int,
) -> Stream<Compress> {
unsafe { unsafe {
let mut state: miniz_sys::mz_stream = mem::zeroed(); let mut state: miniz_sys::mz_stream = mem::zeroed();
let ret = miniz_sys::mz_deflateInit2(&mut state, let ret = miniz_sys::mz_deflateInit2(
lvl, &mut state,
miniz_sys::MZ_DEFLATED, lvl,
window_bits, miniz_sys::MZ_DEFLATED,
mem_size, window_bits,
strategy); mem_size,
strategy,
);
debug_assert_eq!(ret, 0); debug_assert_eq!(ret, 0);
Stream { Stream {
raw: state, raw: state,
@ -91,12 +94,14 @@ impl Stream<Compress> {
unsafe { unsafe {
self.raw.next_in = input.as_mut_ptr().offset(self.total_in() as isize); self.raw.next_in = input.as_mut_ptr().offset(self.total_in() as isize);
self.raw.next_out = output.as_mut_ptr().offset(self.total_out() as isize); self.raw.next_out = output.as_mut_ptr().offset(self.total_out() as isize);
let rc = miniz_sys::mz_deflate(&mut self.raw, let rc = miniz_sys::mz_deflate(
if self.raw.avail_in > 0 { &mut self.raw,
miniz_sys::MZ_NO_FLUSH if self.raw.avail_in > 0 {
} else { miniz_sys::MZ_NO_FLUSH
miniz_sys::MZ_FINISH } else {
}); miniz_sys::MZ_FINISH
},
);
output.set_len(self.total_out() as usize); output.set_len(self.total_out() as usize);
rc rc
} }

View file

@ -6,37 +6,33 @@ pub fn filter_line(filter: u8, bpp: usize, data: &[u8], last_line: &[u8]) -> Vec
} }
1 => { 1 => {
filtered.extend_from_slice(&data[0..bpp]); filtered.extend_from_slice(&data[0..bpp]);
filtered.extend(data.iter() filtered.extend(data.iter().skip(bpp).zip(data.iter()).map(|(cur, last)| {
.skip(bpp) cur.wrapping_sub(*last)
.zip(data.iter()) }));
.map(|(cur, last)| cur.wrapping_sub(*last)));
} }
2 => { 2 => {
if last_line.is_empty() { if last_line.is_empty() {
filtered.extend_from_slice(data); filtered.extend_from_slice(data);
} else { } else {
filtered.extend(data.iter() filtered.extend(data.iter().zip(last_line.iter()).map(|(cur, last)| {
.zip(last_line.iter()) cur.wrapping_sub(*last)
.map(|(cur, last)| cur.wrapping_sub(*last))); }));
}; };
} }
3 => { 3 => {
for (i, byte) in data.iter().enumerate() { for (i, byte) in data.iter().enumerate() {
if last_line.is_empty() { if last_line.is_empty() {
filtered.push(match i.checked_sub(bpp) { filtered.push(match i.checked_sub(bpp) {
Some(x) => byte.wrapping_sub(data[x] >> 1), Some(x) => byte.wrapping_sub(data[x] >> 1),
None => *byte, None => *byte,
}); });
} else { } else {
filtered.push(match i.checked_sub(bpp) { filtered.push(match i.checked_sub(bpp) {
Some(x) => { Some(x) => {
byte.wrapping_sub(((data[x] as u16 + byte.wrapping_sub(((data[x] as u16 + last_line[i] as u16) >> 1) as u8)
last_line[i] as u16) >> }
1) as None => byte.wrapping_sub(last_line[i] >> 1),
u8) });
}
None => byte.wrapping_sub(last_line[i] >> 1),
});
}; };
} }
} }
@ -44,18 +40,16 @@ pub fn filter_line(filter: u8, bpp: usize, data: &[u8], last_line: &[u8]) -> Vec
for (i, byte) in data.iter().enumerate() { for (i, byte) in data.iter().enumerate() {
if last_line.is_empty() { if last_line.is_empty() {
filtered.push(match i.checked_sub(bpp) { filtered.push(match i.checked_sub(bpp) {
Some(x) => byte.wrapping_sub(data[x]), Some(x) => byte.wrapping_sub(data[x]),
None => *byte, None => *byte,
}); });
} else { } else {
filtered.push(match i.checked_sub(bpp) { filtered.push(match i.checked_sub(bpp) {
Some(x) => { Some(x) => {
byte.wrapping_sub(paeth_predictor(data[x], byte.wrapping_sub(paeth_predictor(data[x], last_line[i], last_line[x]))
last_line[i], }
last_line[x])) None => byte.wrapping_sub(last_line[i]),
} });
None => byte.wrapping_sub(last_line[i]),
});
}; };
} }
} }
@ -87,9 +81,9 @@ pub fn unfilter_line(filter: u8, bpp: usize, data: &[u8], last_line: &[u8]) -> V
if last_line.is_empty() { if last_line.is_empty() {
unfiltered.extend_from_slice(data); unfiltered.extend_from_slice(data);
} else { } else {
unfiltered.extend(data.iter() unfiltered.extend(data.iter().zip(last_line.iter()).map(|(cur, last)| {
.zip(last_line.iter()) cur.wrapping_add(*last)
.map(|(cur, last)| cur.wrapping_add(*last))); }));
}; };
} }
3 => { 3 => {
@ -108,9 +102,9 @@ pub fn unfilter_line(filter: u8, bpp: usize, data: &[u8], last_line: &[u8]) -> V
match i.checked_sub(bpp) { match i.checked_sub(bpp) {
Some(x) => { Some(x) => {
let b = unfiltered[x]; let b = unfiltered[x];
unfiltered unfiltered.push(byte.wrapping_add(
.push(byte.wrapping_add(((b as u16 + last_line[i] as u16) >> 1) as ((b as u16 + last_line[i] as u16) >> 1) as u8,
u8)); ));
} }
None => { None => {
unfiltered.push(byte.wrapping_add(last_line[i] >> 1)); unfiltered.push(byte.wrapping_add(last_line[i] >> 1));
@ -135,9 +129,9 @@ pub fn unfilter_line(filter: u8, bpp: usize, data: &[u8], last_line: &[u8]) -> V
match i.checked_sub(bpp) { match i.checked_sub(bpp) {
Some(x) => { Some(x) => {
let b = unfiltered[x]; let b = unfiltered[x];
unfiltered.push(byte.wrapping_add(paeth_predictor(b, unfiltered.push(byte.wrapping_add(
last_line[i], paeth_predictor(b, last_line[i], last_line[x]),
last_line[x]))); ));
} }
None => { None => {
unfiltered.push(byte.wrapping_add(last_line[i])); unfiltered.push(byte.wrapping_add(last_line[i]));

View file

@ -43,16 +43,19 @@ pub fn file_header_is_valid(bytes: &[u8]) -> bool {
*bytes == expected_header *bytes == expected_header
} }
pub fn parse_next_header(byte_data: &[u8], pub fn parse_next_header(
byte_offset: &mut usize, byte_data: &[u8],
fix_errors: bool) byte_offset: &mut usize,
-> Result<Option<(String, Vec<u8>)>, PngError> { fix_errors: bool,
let mut rdr = Cursor::new(byte_data ) -> Result<Option<(String, Vec<u8>)>, PngError> {
.iter() let mut rdr = Cursor::new(
.skip(*byte_offset) byte_data
.take(4) .iter()
.cloned() .skip(*byte_offset)
.collect::<Vec<u8>>()); .take(4)
.cloned()
.collect::<Vec<u8>>(),
);
let length: u32 = match rdr.read_u32::<BigEndian>() { let length: u32 = match rdr.read_u32::<BigEndian>() {
Ok(x) => x, Ok(x) => x,
Err(_) => return Err(PngError::new("Invalid data found; unable to read PNG file")), Err(_) => return Err(PngError::new("Invalid data found; unable to read PNG file")),
@ -82,12 +85,14 @@ pub fn parse_next_header(byte_data: &[u8],
.cloned() .cloned()
.collect(); .collect();
*byte_offset += length as usize; *byte_offset += length as usize;
let mut rdr = Cursor::new(byte_data let mut rdr = Cursor::new(
.iter() byte_data
.skip(*byte_offset) .iter()
.take(4) .skip(*byte_offset)
.cloned() .take(4)
.collect::<Vec<u8>>()); .cloned()
.collect::<Vec<u8>>(),
);
let crc: u32 = match rdr.read_u32::<BigEndian>() { let crc: u32 = match rdr.read_u32::<BigEndian>() {
Ok(x) => x, Ok(x) => x,
Err(_) => return Err(PngError::new("Invalid data found; unable to read PNG file")), Err(_) => return Err(PngError::new("Invalid data found; unable to read PNG file")),
@ -95,8 +100,10 @@ pub fn parse_next_header(byte_data: &[u8],
*byte_offset += 4; *byte_offset += 4;
header_bytes.extend_from_slice(&data); header_bytes.extend_from_slice(&data);
if !fix_errors && crc32::checksum_ieee(header_bytes.as_ref()) != crc { if !fix_errors && crc32::checksum_ieee(header_bytes.as_ref()) != crc {
return Err(PngError::new(&format!("CRC Mismatch in {} header; May be recoverable by using --fix", return Err(PngError::new(&format!(
header))); "CRC Mismatch in {} header; May be recoverable by using --fix",
header
)));
} }
Ok(Some((header, data))) Ok(Some((header, data)))
@ -105,26 +112,26 @@ pub fn parse_next_header(byte_data: &[u8],
pub fn parse_ihdr_header(byte_data: &[u8]) -> Result<IhdrData, PngError> { pub fn parse_ihdr_header(byte_data: &[u8]) -> Result<IhdrData, PngError> {
let mut rdr = Cursor::new(&byte_data[0..8]); 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,
2 => ColorType::RGB, 2 => ColorType::RGB,
3 => ColorType::Indexed, 3 => ColorType::Indexed,
4 => ColorType::GrayscaleAlpha, 4 => ColorType::GrayscaleAlpha,
6 => ColorType::RGBA, 6 => ColorType::RGBA,
_ => return Err(PngError::new("Unexpected color type in header")), _ => return Err(PngError::new("Unexpected color type in header")),
}, },
bit_depth: match byte_data[8] { bit_depth: match byte_data[8] {
1 => BitDepth::One, 1 => BitDepth::One,
2 => BitDepth::Two, 2 => BitDepth::Two,
4 => BitDepth::Four, 4 => BitDepth::Four,
8 => BitDepth::Eight, 8 => BitDepth::Eight,
16 => BitDepth::Sixteen, 16 => BitDepth::Sixteen,
_ => return Err(PngError::new("Unexpected bit depth in header")), _ => return Err(PngError::new("Unexpected bit depth in header")),
}, },
width: rdr.read_u32::<BigEndian>().unwrap(), width: rdr.read_u32::<BigEndian>().unwrap(),
height: rdr.read_u32::<BigEndian>().unwrap(), height: rdr.read_u32::<BigEndian>().unwrap(),
compression: byte_data[10], compression: byte_data[10],
filter: byte_data[11], filter: byte_data[11],
interlaced: byte_data[12], interlaced: byte_data[12],
}) })
} }

View file

@ -92,15 +92,15 @@ pub fn deinterlace_image(png: &mut PngData) {
let bits_per_line = 8 + bits_per_pixel as usize * png.ihdr_data.width as usize; let bits_per_line = 8 + bits_per_pixel as usize * png.ihdr_data.width as usize;
// Initialize each output line with a starting filter byte of 0 // Initialize each output line with a starting filter byte of 0
// as well as some blank data // as well as some blank data
let mut lines: Vec<BitVec> = vec![BitVec::from_elem(bits_per_line, false); let mut lines: Vec<BitVec> =
png.ihdr_data.height as usize]; vec![BitVec::from_elem(bits_per_line, false); png.ihdr_data.height as usize];
let mut current_pass = 1; let mut current_pass = 1;
let mut pass_constants = interlaced_constants(current_pass); let mut pass_constants = interlaced_constants(current_pass);
let mut current_y: usize = pass_constants.y_shift as usize; let mut current_y: usize = pass_constants.y_shift as usize;
for line in png.scan_lines() { for line in png.scan_lines() {
let bit_vec = BitVec::from_bytes(&line.data); let bit_vec = BitVec::from_bytes(&line.data);
let bits_in_line = ((png.ihdr_data.width - pass_constants.x_shift as u32) as f32 / let bits_in_line = ((png.ihdr_data.width - pass_constants.x_shift as u32) as f32 /
pass_constants.x_step as f32) pass_constants.x_step as f32)
.ceil() as usize * bits_per_pixel as usize; .ceil() as usize * bits_per_pixel as usize;
for (i, bit) in bit_vec.iter().enumerate() { for (i, bit) in bit_vec.iter().enumerate() {
// Avoid moving padded 0's into new image // Avoid moving padded 0's into new image
@ -108,7 +108,7 @@ pub fn deinterlace_image(png: &mut PngData) {
break; break;
} }
let current_x: usize = pass_constants.x_shift as usize + let current_x: usize = pass_constants.x_shift as usize +
(i / bits_per_pixel as usize) * pass_constants.x_step as usize; (i / bits_per_pixel as usize) * pass_constants.x_step as usize;
// Copy this bit into the output line, offset by 8 because of filter byte // Copy this bit into the output line, offset by 8 because of filter byte
let index = 8 + (i % bits_per_pixel as usize) + current_x * bits_per_pixel as usize; let index = 8 + (i % bits_per_pixel as usize) + current_x * bits_per_pixel as usize;
lines[current_y].set(index, bit); lines[current_y].set(index, bit);

View file

@ -20,7 +20,7 @@ use png::PngData;
use rayon::prelude::*; use rayon::prelude::*;
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::fs::{File, copy}; use std::fs::{File, copy};
use std::io::{BufWriter, Write, stderr, stdout}; use std::io::{BufWriter, Write, stdout};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
pub mod colors; pub mod colors;
@ -90,6 +90,8 @@ pub struct Options {
/// 8-15 are valid values /// 8-15 are valid values
/// Default: `15` /// Default: `15`
pub window: u8, pub window: u8,
/// Alpha filtering strategies to use
pub alphas: HashSet<colors::AlphaOptim>,
/// Whether to attempt bit depth reduction /// Whether to attempt bit depth reduction
/// Default: `true` /// Default: `true`
pub bit_depth_reduction: bool, pub bit_depth_reduction: bool,
@ -241,6 +243,9 @@ impl Default for Options {
for i in 0..4 { for i in 0..4 {
strategies.insert(i); strategies.insert(i);
} }
let mut alphas = HashSet::new();
alphas.insert(colors::AlphaOptim::NoOp);
alphas.insert(colors::AlphaOptim::Black);
// Default to 1 thread on single-core, otherwise use threads = 1.5x CPU cores // Default to 1 thread on single-core, otherwise use threads = 1.5x CPU cores
let num_cpus = num_cpus::get(); let num_cpus = num_cpus::get();
@ -265,6 +270,7 @@ impl Default for Options {
memory: memory, memory: memory,
strategies: strategies, strategies: strategies,
window: 15, window: 15,
alphas,
bit_depth_reduction: true, bit_depth_reduction: true,
color_type_reduction: true, color_type_reduction: true,
palette_reduction: true, palette_reduction: true,
@ -281,11 +287,11 @@ impl Default for Options {
pub fn optimize(filepath: &Path, opts: &Options) -> Result<(), PngError> { pub fn optimize(filepath: &Path, opts: &Options) -> Result<(), PngError> {
// Initialize the thread pool with correct number of threads // Initialize the thread pool with correct number of threads
let thread_count = opts.threads; let thread_count = opts.threads;
rayon::initialize(rayon::Configuration::new().num_threads(thread_count)).ok(); let _ = rayon::initialize(rayon::Configuration::new().num_threads(thread_count));
// Read in the file and try to decode as PNG. // Read in the file and try to decode as PNG.
if opts.verbosity.is_some() { if opts.verbosity.is_some() {
writeln!(&mut stderr(), "Processing: {}", filepath.to_str().unwrap()).ok(); eprintln!("Processing: {}", filepath.to_str().unwrap());
} }
let in_file = Path::new(filepath); let in_file = Path::new(filepath);
@ -296,27 +302,29 @@ pub fn optimize(filepath: &Path, opts: &Options) -> Result<(), PngError> {
let optimized_output = optimize_png(&mut png, &in_data, opts)?; let optimized_output = optimize_png(&mut png, &in_data, opts)?;
if is_fully_optimized(in_data.len(), optimized_output.len(), opts) { if is_fully_optimized(in_data.len(), optimized_output.len(), opts) {
writeln!(&mut stderr(), "File already optimized").ok(); eprintln!("File already optimized");
return Ok(()); return Ok(());
} }
if opts.pretend { if opts.pretend {
if opts.verbosity.is_some() { if opts.verbosity.is_some() {
writeln!(&mut stderr(), "Running in pretend mode, no output").ok(); eprintln!("Running in pretend mode, no output");
} }
} else { } else {
if opts.backup { if opts.backup {
match copy(in_file, match copy(
in_file.with_extension(format!("bak.{}", in_file,
in_file in_file.with_extension(format!(
.extension() "bak.{}",
.unwrap() in_file.extension().unwrap().to_str().unwrap()
.to_str() )),
.unwrap()))) { ) {
Ok(x) => x, Ok(x) => x,
Err(_) => { Err(_) => {
return Err(PngError::new(&format!("Unable to write to backup file at {}", return Err(PngError::new(&format!(
opts.out_file.display()))) "Unable to write to backup file at {}",
opts.out_file.display()
)))
} }
}; };
} }
@ -331,8 +339,10 @@ pub fn optimize(filepath: &Path, opts: &Options) -> Result<(), PngError> {
let out_file = match File::create(&opts.out_file) { let out_file = match File::create(&opts.out_file) {
Ok(x) => x, Ok(x) => x,
Err(_) => { Err(_) => {
return Err(PngError::new(&format!("Unable to write to file {}", return Err(PngError::new(&format!(
opts.out_file.display()))) "Unable to write to file {}",
opts.out_file.display()
)))
} }
}; };
@ -352,9 +362,9 @@ pub fn optimize(filepath: &Path, opts: &Options) -> Result<(), PngError> {
} }
Err(_) => { Err(_) => {
if opts.verbosity.is_some() { if opts.verbosity.is_some() {
writeln!(&mut stderr(), eprintln!(
"Failed to set permissions on output file") "Failed to set permissions on output file"
.ok(); );
} }
} }
} }
@ -362,17 +372,14 @@ pub fn optimize(filepath: &Path, opts: &Options) -> Result<(), PngError> {
} }
Err(_) => { Err(_) => {
if opts.verbosity.is_some() { if opts.verbosity.is_some() {
writeln!(&mut stderr(), eprintln!("Failed to read permissions on input file");
"Failed to read permissions on input file")
.ok();
} }
} }
} }
} }
Err(_) => { Err(_) => {
if opts.verbosity.is_some() { if opts.verbosity.is_some() {
writeln!(&mut stderr(), "Failed to read permissions on input file") eprintln!("Failed to read permissions on input file");
.ok();
} }
} }
}; };
@ -382,12 +389,14 @@ pub fn optimize(filepath: &Path, opts: &Options) -> Result<(), PngError> {
match buffer.write_all(&optimized_output) { match buffer.write_all(&optimized_output) {
Ok(_) => { Ok(_) => {
if opts.verbosity.is_some() { if opts.verbosity.is_some() {
writeln!(&mut stderr(), "Output: {}", opts.out_file.display()).ok(); eprintln!("Output: {}", opts.out_file.display());
} }
} }
Err(_) => { Err(_) => {
return Err(PngError::new(&format!("Unable to write to file {}", return Err(PngError::new(&format!(
opts.out_file.display()))) "Unable to write to file {}",
opts.out_file.display()
)))
} }
} }
} }
@ -400,11 +409,11 @@ pub fn optimize(filepath: &Path, opts: &Options) -> Result<(), PngError> {
pub fn optimize_from_memory(data: &[u8], opts: &Options) -> Result<Vec<u8>, PngError> { pub fn optimize_from_memory(data: &[u8], opts: &Options) -> Result<Vec<u8>, PngError> {
// Initialize the thread pool with correct number of threads // Initialize the thread pool with correct number of threads
let thread_count = opts.threads; let thread_count = opts.threads;
rayon::initialize(rayon::Configuration::new().num_threads(thread_count)).ok(); let _ = rayon::initialize(rayon::Configuration::new().num_threads(thread_count));
// Read in the file and try to decode as PNG. // Read in the file and try to decode as PNG.
if opts.verbosity.is_some() { if opts.verbosity.is_some() {
writeln!(&mut stderr(), "Processing from memory").ok(); eprintln!("Processing from memory");
} }
let original_size = data.len() as usize; let original_size = data.len() as usize;
let mut png = PngData::from_slice(data, opts.fix_errors)?; let mut png = PngData::from_slice(data, opts.fix_errors)?;
@ -413,7 +422,7 @@ pub fn optimize_from_memory(data: &[u8], opts: &Options) -> Result<Vec<u8>, PngE
let optimized_output = optimize_png(&mut png, data, opts)?; let optimized_output = optimize_png(&mut png, data, opts)?;
if is_fully_optimized(original_size, optimized_output.len(), opts) { if is_fully_optimized(original_size, optimized_output.len(), opts) {
writeln!(&mut stderr(), "Image already optimized").ok(); eprintln!("Image already optimized");
Ok(data.to_vec()) Ok(data.to_vec())
} else { } else {
Ok(optimized_output) Ok(optimized_output)
@ -421,11 +430,12 @@ pub fn optimize_from_memory(data: &[u8], opts: &Options) -> Result<Vec<u8>, PngE
} }
/// 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(png: &mut PngData, fn optimize_png(
original_data: &[u8], png: &mut PngData,
opts: &Options) original_data: &[u8],
-> Result<Vec<u8>, PngError> { opts: &Options,
type TrialWithData = (u8, u8, u8, u8, Vec<u8>); ) -> Result<Vec<u8>, PngError> {
type TrialWithData = (TrialOptions, Vec<u8>);
let original_png = png.clone(); let original_png = png.clone();
@ -433,33 +443,27 @@ fn optimize_png(png: &mut PngData,
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();
if opts.verbosity.is_some() { if opts.verbosity.is_some() {
writeln!(&mut stderr(), eprintln!(
" {}x{} pixels, PNG format", " {}x{} pixels, PNG format",
png.ihdr_data.width, png.ihdr_data.width,
png.ihdr_data.height) png.ihdr_data.height
.ok(); );
if let Some(ref palette) = png.palette { if let Some(ref palette) = png.palette {
writeln!(&mut stderr(), eprintln!(
" {} bits/pixel, {} colors in palette", " {} bits/pixel, {} colors in palette",
png.ihdr_data.bit_depth, png.ihdr_data.bit_depth,
palette.len() / 3) palette.len() / 3
.ok(); );
} else { } else {
writeln!(&mut stderr(), eprintln!(
" {}x{} bits/pixel, {:?}", " {}x{} bits/pixel, {:?}",
png.channels_per_pixel(), png.channels_per_pixel(),
png.ihdr_data.bit_depth, png.ihdr_data.bit_depth,
png.ihdr_data.color_type) png.ihdr_data.color_type
.ok(); );
} }
writeln!(&mut stderr(), eprintln!(" IDAT size = {} bytes", idat_original_size);
" IDAT size = {} bytes", eprintln!(" File size = {} bytes", file_original_size);
idat_original_size)
.ok();
writeln!(&mut stderr(),
" File size = {} bytes",
file_original_size)
.ok();
} }
let mut filter = opts.filter.iter().cloned().collect::<Vec<u8>>(); let mut filter = opts.filter.iter().cloned().collect::<Vec<u8>>();
@ -470,7 +474,8 @@ fn optimize_png(png: &mut PngData,
if opts.use_heuristics { if opts.use_heuristics {
// Heuristically determine which set of options to use // Heuristically determine which set of options to use
if png.ihdr_data.bit_depth.as_u8() >= 8 && if png.ihdr_data.bit_depth.as_u8() >= 8 &&
png.ihdr_data.color_type != colors::ColorType::Indexed { png.ihdr_data.color_type != colors::ColorType::Indexed
{
if filter.is_empty() { if filter.is_empty() {
filter.push(5); filter.push(5);
} }
@ -496,9 +501,9 @@ fn optimize_png(png: &mut PngData,
} else { } else {
filter.len() filter.len()
}; };
let mut results: Vec<(u8, u8, u8, u8)> = Vec::with_capacity(combinations); let mut results: Vec<TrialOptions> = Vec::with_capacity(combinations);
if opts.verbosity.is_some() { if opts.verbosity.is_some() {
writeln!(&mut stderr(), "Trying: {} combinations", combinations).ok(); eprintln!("Trying: {} combinations", combinations);
} }
for f in &filter { for f in &filter {
@ -506,24 +511,34 @@ fn optimize_png(png: &mut PngData,
for zc in compression { for zc in compression {
for zm in memory { for zm in memory {
for zs in &strategies { for zs in &strategies {
results.push((*f, *zc, *zm, *zs)); results.push(TrialOptions {
filter: *f,
compression: *zc,
memory: *zm,
strategy: *zs,
});
} }
} }
} }
} else { } else {
// Zopfli compression has no additional options // Zopfli compression has no additional options
results.push((*f, 0, 0, 0)); results.push(TrialOptions {
filter: *f,
compression: 0,
memory: 0,
strategy: 0,
});
} }
} }
let mut filters_tmp: Vec<(u8, Vec<u8>)> = Vec::with_capacity(filter.len()); let filters: HashMap<u8, Vec<u8>> = filter
filter
.par_iter() .par_iter()
.with_max_len(1) .with_max_len(1)
.map(|f| (*f, png.filter_image(*f))) .map(|f| {
.collect_into(&mut filters_tmp); let png = png.clone();
(*f, png.filter_image(*f))
let filters: HashMap<u8, Vec<u8>> = filters_tmp.into_iter().collect(); })
.collect();
let original_len = original_png.idat_data.len(); let original_len = original_png.idat_data.len();
let added_interlacing = opts.interlace == Some(1) && original_png.ihdr_data.interlaced == 0; let added_interlacing = opts.interlace == Some(1) && original_png.ihdr_data.interlaced == 0;
@ -533,47 +548,54 @@ fn optimize_png(png: &mut PngData,
.into_par_iter() .into_par_iter()
.with_max_len(1) .with_max_len(1)
.filter_map(|trial| { .filter_map(|trial| {
let filtered = &filters[&trial.0]; let filtered = &filters[&trial.filter];
let new_idat = if opts.deflate == Deflaters::Zlib { let new_idat = if opts.deflate == Deflaters::Zlib {
deflate::deflate(filtered, trial.1, trial.2, trial.3, opts.window) deflate::deflate(
filtered,
trial.compression,
trial.memory,
trial.strategy,
opts.window,
)
} else { } else {
deflate::zopfli_deflate(filtered) deflate::zopfli_deflate(filtered)
}.unwrap(); }.unwrap();
if opts.verbosity == Some(1) { if opts.verbosity == Some(1) {
writeln!(&mut stderr(), eprintln!(
" zc = {} zm = {} zs = {} f = {} {} bytes", " zc = {} zm = {} zs = {} f = {} {} bytes",
trial.1, trial.compression,
trial.2, trial.memory,
trial.3, trial.strategy,
trial.0, trial.filter,
new_idat.len()) new_idat.len()
.ok(); );
} }
if new_idat.len() < original_len || added_interlacing || opts.force { if new_idat.len() < original_len || added_interlacing || opts.force {
Some((trial.0, trial.1, trial.2, trial.3, new_idat)) Some((trial, new_idat))
} else { } else {
None None
} }
}) })
.reduce_with(|i, j| if i.4.len() <= j.4.len() { i } else { j }); .reduce_with(|i, j| if i.1.len() <= j.1.len() { i } else { j });
if let Some(better) = best { if let Some(better) = best {
png.idat_data = better.4; png.idat_data = better.1;
if opts.verbosity.is_some() { if opts.verbosity.is_some() {
writeln!(&mut stderr(), "Found better combination:").ok(); let opts = better.0;
writeln!(&mut stderr(), eprintln!("Found better combination:");
" zc = {} zm = {} zs = {} f = {} {} bytes", eprintln!(
better.1, " zc = {} zm = {} zs = {} f = {} {} bytes",
better.2, opts.compression,
better.3, opts.memory,
better.0, opts.strategy,
png.idat_data.len()) opts.filter,
.ok(); png.idat_data.len()
);
} }
} else if reduction_occurred { } else if reduction_occurred {
png.reset_from_original(original_png); png.reset_from_original(&original_png);
} }
} }
@ -583,34 +605,32 @@ fn optimize_png(png: &mut PngData,
if opts.verbosity.is_some() { if opts.verbosity.is_some() {
if idat_original_size >= png.idat_data.len() { if idat_original_size >= png.idat_data.len() {
writeln!(&mut stderr(), eprintln!(
" IDAT size = {} bytes ({} bytes decrease)", " IDAT size = {} bytes ({} bytes decrease)",
png.idat_data.len(), png.idat_data.len(),
idat_original_size - png.idat_data.len()) idat_original_size - png.idat_data.len()
.ok(); );
} else { } else {
writeln!(&mut stderr(), eprintln!(
" IDAT size = {} bytes ({} bytes increase)", " IDAT size = {} bytes ({} bytes increase)",
png.idat_data.len(), png.idat_data.len(),
png.idat_data.len() - idat_original_size) png.idat_data.len() - idat_original_size
.ok(); );
} }
if file_original_size >= output.len() { if file_original_size >= output.len() {
writeln!(&mut stderr(), eprintln!(
" file size = {} bytes ({} bytes = {:.2}% decrease)", " file size = {} bytes ({} bytes = {:.2}% decrease)",
output.len(), output.len(),
file_original_size - output.len(), file_original_size - output.len(),
(file_original_size - output.len()) as f64 / file_original_size as f64 * (file_original_size - output.len()) as f64 / file_original_size as f64 * 100f64
100f64) );
.ok();
} else { } else {
writeln!(&mut stderr(), eprintln!(
" file size = {} bytes ({} bytes = {:.2}% increase)", " file size = {} bytes ({} bytes = {:.2}% increase)",
output.len(), output.len(),
output.len() - file_original_size, output.len() - file_original_size,
(output.len() - file_original_size) as f64 / file_original_size as f64 * (output.len() - file_original_size) as f64 / file_original_size as f64 * 100f64
100f64) );
.ok();
} }
} }
@ -620,13 +640,16 @@ fn optimize_png(png: &mut PngData,
if let Ok(new_png) = new_png { if let Ok(new_png) = new_png {
if let Ok(old_png) = old_png { if let Ok(old_png) = old_png {
if old_png if old_png
.pixels() .pixels()
.map(|x| x.2.channels().to_owned()) .map(|x| x.2.channels().to_owned())
.collect::<Vec<Vec<u8>>>() == .filter(|p| !(p.len() == 4 && p[3] == 0))
new_png .collect::<Vec<Vec<u8>>>() ==
.pixels() new_png
.map(|x| x.2.channels().to_owned()) .pixels()
.collect::<Vec<Vec<u8>>>() { .map(|x| x.2.channels().to_owned())
.filter(|p| !(p.len() == 4 && p[3] == 0))
.collect::<Vec<Vec<u8>>>()
{
return Ok(output); return Ok(output);
} }
} else { } else {
@ -637,7 +660,9 @@ fn optimize_png(png: &mut PngData,
} }
} }
writeln!(&mut stderr(), "The resulting image is corrupted and will not be outputted.\nThis is a bug! Please report it at https://github.com/shssoichiro/oxipng/issues").ok(); eprintln!(
"The resulting image is corrupted and will not be outputted.\nThis is a bug! Please report it at https://github.com/shssoichiro/oxipng/issues"
);
Err(PngError::new("The resulting image is corrupted")) Err(PngError::new("The resulting image is corrupted"))
} }
@ -678,6 +703,8 @@ fn perform_reductions(png: &mut png::PngData, opts: &Options) -> bool {
} }
} }
png.try_alpha_reduction(&opts.alphas);
reduction_occurred reduction_occurred
} }
@ -685,18 +712,18 @@ fn perform_reductions(png: &mut png::PngData, opts: &Options) -> bool {
#[inline] #[inline]
fn report_reduction(png: &png::PngData) { fn report_reduction(png: &png::PngData) {
if let Some(ref palette) = png.palette { if let Some(ref palette) = png.palette {
writeln!(&mut stderr(), eprintln!(
"Reducing image to {} bits/pixel, {} colors in palette", "Reducing image to {} bits/pixel, {} colors in palette",
png.ihdr_data.bit_depth, png.ihdr_data.bit_depth,
palette.len() / 3) palette.len() / 3
.ok(); );
} else { } else {
writeln!(&mut stderr(), eprintln!(
"Reducing image to {}x{} bits/pixel, {}", "Reducing image to {}x{} bits/pixel, {}",
png.channels_per_pixel(), png.channels_per_pixel(),
png.ihdr_data.bit_depth, png.ihdr_data.bit_depth,
png.ihdr_data.color_type) png.ihdr_data.color_type
.ok(); );
} }
} }
@ -711,8 +738,17 @@ fn perform_strip(png: &mut png::PngData, opts: &Options) {
} }
} }
Headers::Safe => { Headers::Safe => {
const PRESERVED_HEADERS: [&'static str; 9] = ["cHRM", "gAMA", "iCCP", "sBIT", "sRGB", const PRESERVED_HEADERS: [&'static str; 9] = [
"bKGD", "hIST", "pHYs", "sPLT"]; "cHRM",
"gAMA",
"iCCP",
"sBIT",
"sRGB",
"bKGD",
"hIST",
"pHYs",
"sPLT",
];
let hdrs = png.aux_headers.keys().cloned().collect::<Vec<String>>(); let hdrs = png.aux_headers.keys().cloned().collect::<Vec<String>>();
for hdr in hdrs { for hdr in hdrs {
if !PRESERVED_HEADERS.contains(&hdr.as_ref()) { if !PRESERVED_HEADERS.contains(&hdr.as_ref()) {
@ -731,3 +767,12 @@ fn perform_strip(png: &mut png::PngData, opts: &Options) {
fn is_fully_optimized(original_size: usize, optimized_size: usize, opts: &Options) -> bool { fn is_fully_optimized(original_size: usize, optimized_size: usize, opts: &Options) -> bool {
original_size <= optimized_size && !opts.force && opts.interlace.is_none() original_size <= optimized_size && !opts.force && opts.interlace.is_none()
} }
#[derive(Debug, PartialEq, Clone, Copy)]
/// Defines options to be used for a single compression trial
struct TrialOptions {
pub filter: u8,
pub compression: u8,
pub memory: u8,
pub strategy: u8,
}

View file

@ -12,13 +12,13 @@ extern crate clap;
extern crate regex; extern crate regex;
use clap::{App, Arg, ArgMatches}; use clap::{App, Arg, ArgMatches};
use oxipng::colors::AlphaOptim;
use oxipng::deflate::Deflaters; use oxipng::deflate::Deflaters;
use oxipng::headers::Headers; use oxipng::headers::Headers;
use oxipng::Options; use oxipng::Options;
use regex::Regex; use regex::Regex;
use std::collections::HashSet; use std::collections::HashSet;
use std::fs::DirBuilder; use std::fs::DirBuilder;
use std::io::{Write, stderr};
use std::path::PathBuf; use std::path::PathBuf;
fn main() { fn main() {
@ -157,6 +157,10 @@ fn main() {
.possible_value("8k") .possible_value("8k")
.possible_value("16k") .possible_value("16k")
.possible_value("32k")) .possible_value("32k"))
.arg(Arg::with_name("alpha")
.help("Perform additional alpha optimizations")
.short("a")
.long("alpha"))
.arg(Arg::with_name("no-bit-reduction") .arg(Arg::with_name("no-bit-reduction")
.help("No bit depth reduction") .help("No bit depth reduction")
.long("nb")) .long("nb"))
@ -222,17 +226,19 @@ fn main() {
let opts = match parse_opts_into_struct(&matches) { let opts = match parse_opts_into_struct(&matches) {
Ok(x) => x, Ok(x) => x,
Err(x) => { Err(x) => {
writeln!(&mut stderr(), "{}", x).ok(); eprintln!("{}", x);
return (); return ();
} }
}; };
handle_optimization(matches handle_optimization(
.values_of("files") matches
.unwrap() .values_of("files")
.map(PathBuf::from) .unwrap()
.collect(), .map(PathBuf::from)
&opts); .collect(),
&opts,
);
} }
fn handle_optimization(inputs: Vec<PathBuf>, opts: &Options) { fn handle_optimization(inputs: Vec<PathBuf>, opts: &Options) {
@ -240,17 +246,16 @@ fn handle_optimization(inputs: Vec<PathBuf>, opts: &Options) {
let mut current_opts = opts.clone(); let mut current_opts = opts.clone();
if input.is_dir() { if input.is_dir() {
if current_opts.recursive { if current_opts.recursive {
handle_optimization(input handle_optimization(
.read_dir() input
.unwrap() .read_dir()
.map(|x| x.unwrap().path()) .unwrap()
.collect(), .map(|x| x.unwrap().path())
&current_opts) .collect(),
&current_opts,
)
} else { } else {
writeln!(&mut stderr(), eprintln!("{} is a directory, skipping", input.display());
"{} is a directory, skipping",
input.display())
.ok();
} }
continue; continue;
} }
@ -262,7 +267,7 @@ fn handle_optimization(inputs: Vec<PathBuf>, opts: &Options) {
match oxipng::optimize(&input, &current_opts) { match oxipng::optimize(&input, &current_opts) {
Ok(_) => (), Ok(_) => (),
Err(x) => { Err(x) => {
writeln!(&mut stderr(), "{}", x).ok(); eprintln!("{}", x);
} }
}; };
} }
@ -320,8 +325,10 @@ fn parse_opts_into_struct(matches: &ArgMatches) -> Result<Options, String> {
Err(x) => return Err(format!("Could not create output directory {}", x)), Err(x) => return Err(format!("Could not create output directory {}", x)),
}; };
} else if !path.is_dir() { } else if !path.is_dir() {
return Err(format!("{} is an existing file (not a directory), cannot create directory", return Err(format!(
x)); "{} is an existing file (not a directory), cannot create directory",
x
));
} }
opts.out_dir = Some(path); opts.out_dir = Some(path);
} }
@ -334,6 +341,14 @@ fn parse_opts_into_struct(matches: &ArgMatches) -> Result<Options, String> {
opts.stdout = true; opts.stdout = true;
} }
if matches.is_present("alpha") {
opts.alphas.insert(AlphaOptim::White);
opts.alphas.insert(AlphaOptim::Up);
opts.alphas.insert(AlphaOptim::Down);
opts.alphas.insert(AlphaOptim::Left);
opts.alphas.insert(AlphaOptim::Right);
}
if matches.is_present("backup") { if matches.is_present("backup") {
opts.backup = true; opts.backup = true;
} }
@ -398,8 +413,10 @@ fn parse_opts_into_struct(matches: &ArgMatches) -> Result<Options, String> {
.collect::<Vec<String>>(); .collect::<Vec<String>>();
if hdrs.contains(&"safe".to_owned()) || hdrs.contains(&"all".to_owned()) { if hdrs.contains(&"safe".to_owned()) || hdrs.contains(&"all".to_owned()) {
if hdrs.len() > 1 { if hdrs.len() > 1 {
return Err("'safe' or 'all' presets for --strip should be used by themselves" return Err(
.to_owned()); "'safe' or 'all' presets for --strip should be used by themselves"
.to_owned(),
);
} }
if hdrs[0] == "safe" { if hdrs[0] == "safe" {
opts.strip = Headers::Safe; opts.strip = Headers::Safe;
@ -432,18 +449,21 @@ fn parse_opts_into_struct(matches: &ArgMatches) -> Result<Options, String> {
Ok(opts) Ok(opts)
} }
fn parse_numeric_range_opts(input: &str, fn parse_numeric_range_opts(
min_value: u8, input: &str,
max_value: u8) min_value: u8,
-> Result<HashSet<u8>, String> { max_value: u8,
) -> Result<HashSet<u8>, String> {
let one_item = Regex::new(format!(r"^[{}-{}]$", min_value, max_value).as_ref()).unwrap(); let one_item = Regex::new(format!(r"^[{}-{}]$", min_value, max_value).as_ref()).unwrap();
let multiple_items = Regex::new(format!(r"^([{}-{}])(,|-)([{}-{}])$", let multiple_items = Regex::new(
min_value, format!(
max_value, r"^([{}-{}])(,|-)([{}-{}])$",
min_value, min_value,
max_value) max_value,
.as_ref()) min_value,
.unwrap(); max_value
).as_ref(),
).unwrap();
let mut items = HashSet::new(); let mut items = HashSet::new();
if one_item.is_match(input) { if one_item.is_match(input) {

View file

@ -1,6 +1,6 @@
use bit_vec::BitVec; use bit_vec::BitVec;
use byteorder::{BigEndian, WriteBytesExt}; use byteorder::{BigEndian, WriteBytesExt};
use colors::{BitDepth, ColorType}; use colors::{BitDepth, ColorType, AlphaOptim};
use crc::crc32; use crc::crc32;
use deflate; use deflate;
use error::PngError; use error::PngError;
@ -15,6 +15,12 @@ use std::io::Read;
use std::iter::Iterator; use std::iter::Iterator;
use std::path::Path; use std::path::Path;
const STD_COMPRESSION: u8 = 8;
const STD_MEMORY: u8 = 9;
const STD_STRATEGY: u8 = 2; // Huffman only
const STD_WINDOW: u8 = 15;
const STD_FILTERS: [u8; 2] = [0, 5];
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
/// An iterator over the scan lines of a PNG image /// An iterator over the scan lines of a PNG image
pub struct ScanLines<'a> { pub struct ScanLines<'a> {
@ -51,7 +57,7 @@ impl<'a> Iterator for ScanLines<'a> {
} }
} }
let bits_per_pixel = self.png.ihdr_data.bit_depth.as_u8() as u32 * let bits_per_pixel = self.png.ihdr_data.bit_depth.as_u8() as u32 *
self.png.channels_per_pixel() as u32; self.png.channels_per_pixel() as u32;
let y_steps; let y_steps;
let pixels_factor; let pixels_factor;
match self.pass { match self.pass {
@ -129,23 +135,23 @@ impl<'a> Iterator for ScanLines<'a> {
} }
} }
Some(ScanLine { Some(ScanLine {
filter: self.png.raw_data[self.start], filter: self.png.raw_data[self.start],
data: self.png.raw_data[(self.start + 1)..self.end].to_owned(), data: self.png.raw_data[(self.start + 1)..self.end].to_owned(),
pass: current_pass, pass: current_pass,
}) })
} else { } else {
// Standard, non-interlaced PNG scanlines // Standard, non-interlaced PNG scanlines
let bits_per_line = self.png.ihdr_data.width as usize * let bits_per_line = self.png.ihdr_data.width as usize *
self.png.ihdr_data.bit_depth.as_u8() as usize * self.png.ihdr_data.bit_depth.as_u8() as usize *
self.png.channels_per_pixel() as usize; self.png.channels_per_pixel() as usize;
let bytes_per_line = (bits_per_line as f32 / 8f32).ceil() as usize; let bytes_per_line = (bits_per_line as f32 / 8f32).ceil() as usize;
self.start = self.end; self.start = self.end;
self.end = self.start + bytes_per_line + 1; self.end = self.start + bytes_per_line + 1;
Some(ScanLine { Some(ScanLine {
filter: self.png.raw_data[self.start], filter: self.png.raw_data[self.start],
data: self.png.raw_data[(self.start + 1)..self.end].to_owned(), data: self.png.raw_data[(self.start + 1)..self.end].to_owned(),
pass: None, pass: None,
}) })
} }
} }
} }
@ -280,14 +286,14 @@ impl PngData {
} }
#[doc(hidden)] #[doc(hidden)]
pub fn reset_from_original(&mut self, original: PngData) { pub fn reset_from_original(&mut self, original: &PngData) {
self.idat_data = original.idat_data; self.idat_data = original.idat_data.clone();
self.ihdr_data = original.ihdr_data; self.ihdr_data = original.ihdr_data;
self.raw_data = original.raw_data; self.raw_data = original.raw_data.clone();
self.palette = original.palette; self.palette = original.palette.clone();
self.transparency_pixel = original.transparency_pixel; self.transparency_pixel = original.transparency_pixel.clone();
self.transparency_palette = original.transparency_palette; self.transparency_palette = original.transparency_palette.clone();
self.aux_headers = original.aux_headers; self.aux_headers = original.aux_headers.clone();
} }
/// Return the number of channels in the image, based on color type /// Return the number of channels in the image, based on color type
@ -307,21 +313,19 @@ impl PngData {
let mut output = vec![0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]; let mut output = vec![0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];
// IHDR // IHDR
let mut ihdr_data = Vec::with_capacity(13); let mut ihdr_data = Vec::with_capacity(13);
ihdr_data.write_u32::<BigEndian>(self.ihdr_data.width).ok(); let _ = ihdr_data.write_u32::<BigEndian>(self.ihdr_data.width);
ihdr_data.write_u32::<BigEndian>(self.ihdr_data.height).ok(); let _ = ihdr_data.write_u32::<BigEndian>(self.ihdr_data.height);
ihdr_data.write_u8(self.ihdr_data.bit_depth.as_u8()).ok(); let _ = ihdr_data.write_u8(self.ihdr_data.bit_depth.as_u8());
ihdr_data let _ = ihdr_data.write_u8(self.ihdr_data.color_type.png_header_code());
.write_u8(self.ihdr_data.color_type.png_header_code()) let _ = ihdr_data.write_u8(0); // Compression -- deflate
.ok(); let _ = ihdr_data.write_u8(0); // Filter method -- 5-way adaptive filtering
ihdr_data.write_u8(0).ok(); // Compression -- deflate let _ = ihdr_data.write_u8(self.ihdr_data.interlaced);
ihdr_data.write_u8(0).ok(); // Filter method -- 5-way adaptive filtering
ihdr_data.write_u8(self.ihdr_data.interlaced).ok();
write_png_block(b"IHDR", &ihdr_data, &mut output); write_png_block(b"IHDR", &ihdr_data, &mut output);
// Ancillary headers // Ancillary headers
for (key, header) in for (key, header) in self.aux_headers.iter().filter(|&(key, _)| {
self.aux_headers !(*key == "bKGD" || *key == "hIST" || *key == "tRNS")
.iter() })
.filter(|&(key, _)| !(*key == "bKGD" || *key == "hIST" || *key == "tRNS")) { {
write_png_block(key.as_bytes(), header, &mut output); write_png_block(key.as_bytes(), header, &mut output);
} }
// Palette // Palette
@ -336,10 +340,10 @@ impl PngData {
write_png_block(b"tRNS", transparency_pixel, &mut output); write_png_block(b"tRNS", transparency_pixel, &mut output);
} }
// Special ancillary headers that need to come after PLTE but before IDAT // Special ancillary headers that need to come after PLTE but before IDAT
for (key, header) in for (key, header) in self.aux_headers.iter().filter(|&(key, _)| {
self.aux_headers *key == "bKGD" || *key == "hIST" || *key == "tRNS"
.iter() })
.filter(|&(key, _)| *key == "bKGD" || *key == "hIST" || *key == "tRNS") { {
write_png_block(key.as_bytes(), header, &mut output); write_png_block(key.as_bytes(), header, &mut output);
} }
// IDAT data // IDAT data
@ -365,7 +369,7 @@ impl PngData {
pub fn unfilter_image(&self) -> Vec<u8> { pub fn unfilter_image(&self) -> Vec<u8> {
let mut unfiltered = Vec::with_capacity(self.raw_data.len()); let mut unfiltered = Vec::with_capacity(self.raw_data.len());
let bpp = (((self.ihdr_data.bit_depth.as_u8() * self.channels_per_pixel()) as f32) / let bpp = (((self.ihdr_data.bit_depth.as_u8() * self.channels_per_pixel()) as f32) /
8f32) 8f32)
.ceil() as usize; .ceil() as usize;
let mut last_line: Vec<u8> = Vec::new(); let mut last_line: Vec<u8> = Vec::new();
for line in self.scan_lines() { for line in self.scan_lines() {
@ -387,7 +391,7 @@ impl PngData {
pub fn filter_image(&self, filter: u8) -> Vec<u8> { pub fn filter_image(&self, filter: u8) -> Vec<u8> {
let mut filtered = Vec::with_capacity(self.raw_data.len()); let mut filtered = Vec::with_capacity(self.raw_data.len());
let bpp = (((self.ihdr_data.bit_depth.as_u8() * self.channels_per_pixel()) as f32) / let bpp = (((self.ihdr_data.bit_depth.as_u8() * self.channels_per_pixel()) as f32) /
8f32) 8f32)
.ceil() as usize; .ceil() as usize;
let mut last_line: Vec<u8> = Vec::new(); let mut last_line: Vec<u8> = Vec::new();
let mut last_pass: Option<u8> = None; let mut last_pass: Option<u8> = None;
@ -396,8 +400,9 @@ impl PngData {
0 | 1 | 2 | 3 | 4 => { 0 | 1 | 2 | 3 | 4 => {
if last_pass == line.pass || filter <= 1 { if last_pass == line.pass || filter <= 1 {
filtered.push(filter); filtered.push(filter);
filtered filtered.extend_from_slice(
.extend_from_slice(&filter_line(filter, bpp, &line.data, &last_line)); &filter_line(filter, bpp, &line.data, &last_line),
);
} else { } else {
// Avoid vertical filtering on first line of each interlacing pass // Avoid vertical filtering on first line of each interlacing pass
filtered.push(0); filtered.push(0);
@ -438,17 +443,18 @@ impl PngData {
pub fn reduce_bit_depth(&mut self) -> bool { pub fn reduce_bit_depth(&mut self) -> bool {
if self.ihdr_data.bit_depth != BitDepth::Sixteen { if self.ihdr_data.bit_depth != BitDepth::Sixteen {
if self.ihdr_data.color_type == ColorType::Indexed || if self.ihdr_data.color_type == ColorType::Indexed ||
self.ihdr_data.color_type == ColorType::Grayscale { self.ihdr_data.color_type == ColorType::Grayscale
{
return reduce_bit_depth_8_or_less(self); return reduce_bit_depth_8_or_less(self);
} }
return false; return false;
} }
// Reduce from 16 to 8 bits per channel per pixel // Reduce from 16 to 8 bits per channel per pixel
let mut reduced = Vec::with_capacity((self.ihdr_data.width * self.ihdr_data.height * let mut reduced = Vec::with_capacity(
self.channels_per_pixel() as u32 + (self.ihdr_data.width * self.ihdr_data.height * self.channels_per_pixel() as u32 +
self.ihdr_data.height) as self.ihdr_data.height) as usize,
usize); );
let mut high_byte = 0; let mut high_byte = 0;
for line in self.scan_lines() { for line in self.scan_lines() {
@ -494,20 +500,20 @@ impl PngData {
.chunks(3) .chunks(3)
.zip(trns.iter().chain([255].iter().cycle())) .zip(trns.iter().chain([255].iter().cycle()))
.flat_map(|(pixel, trns)| { .flat_map(|(pixel, trns)| {
let mut pixel = pixel.to_owned(); let mut pixel = pixel.to_owned();
pixel.push(*trns); pixel.push(*trns);
pixel pixel
}) })
.collect() .collect()
} else { } else {
self.palette.clone().unwrap() self.palette.clone().unwrap()
}; };
let mut indexed_palette: Vec<&[u8]> = palette let mut indexed_palette: Vec<&[u8]> = palette
.chunks(if self.transparency_palette.is_some() { .chunks(if self.transparency_palette.is_some() {
4 4
} else { } else {
3 3
}) })
.collect(); .collect();
// A map of old indexes to new ones, for any moved // A map of old indexes to new ones, for any moved
let mut index_map: HashMap<u8, u8> = HashMap::new(); let mut index_map: HashMap<u8, u8> = HashMap::new();
@ -591,10 +597,12 @@ impl PngData {
true true
} }
fn do_palette_reduction(&mut self, fn do_palette_reduction(
indices: &[u8], &mut self,
index_map: &mut HashMap<u8, u8>, indices: &[u8],
indexed_palette: &mut Vec<&[u8]>) { index_map: &mut HashMap<u8, u8>,
indexed_palette: &mut Vec<&[u8]>,
) {
let mut new_data = Vec::with_capacity(self.raw_data.len()); let mut new_data = Vec::with_capacity(self.raw_data.len());
let original_len = indexed_palette.len(); let original_len = indexed_palette.len();
for idx in indices.iter().sorted_by(|a, b| b.cmp(a)) { for idx in indices.iter().sorted_by(|a, b| b.cmp(a)) {
@ -687,7 +695,9 @@ impl PngData {
.cloned() .cloned()
.flatten() .flatten()
.enumerate() .enumerate()
.filter(|&(i, _)| !(self.transparency_palette.is_some() && i % 4 == 3)) .filter(|&(i, _)| {
!(self.transparency_palette.is_some() && i % 4 == 3)
})
.map(|(_, x)| *x) .map(|(_, x)| *x)
.collect::<Vec<u8>>(); .collect::<Vec<u8>>();
self.palette = Some(new_palette); self.palette = Some(new_palette);
@ -711,13 +721,15 @@ impl PngData {
} }
if self.ihdr_data.color_type == ColorType::GrayscaleAlpha && if self.ihdr_data.color_type == ColorType::GrayscaleAlpha &&
reduce_grayscale_alpha_to_grayscale(self) { reduce_grayscale_alpha_to_grayscale(self)
{
changed = true; changed = true;
should_reduce_bit_depth = true; should_reduce_bit_depth = true;
} }
if self.ihdr_data.color_type == ColorType::RGB && if self.ihdr_data.color_type == ColorType::RGB &&
(reduce_rgb_to_grayscale(self) || reduce_rgb_to_palette(self)) { (reduce_rgb_to_grayscale(self) || reduce_rgb_to_palette(self))
{
changed = true; changed = true;
should_reduce_bit_depth = true; should_reduce_bit_depth = true;
} }
@ -731,6 +743,174 @@ impl PngData {
changed changed
} }
pub fn try_alpha_reduction(&mut self, alphas: &HashSet<AlphaOptim>) {
assert!(!alphas.is_empty());
let best = alphas
.iter()
.map(|alpha| {
let mut image = self.clone();
image.reduce_alpha_channel(*alpha);
let size = STD_FILTERS
.iter()
.map(|f| {
deflate::deflate(
&image.filter_image(*f),
STD_COMPRESSION,
STD_MEMORY,
STD_STRATEGY,
STD_WINDOW,
).unwrap()
.len()
})
.min()
.unwrap();
(size, image)
})
.min_by_key(|&(size, _)| size)
.unwrap();
self.raw_data = best.1.raw_data;
}
pub fn reduce_alpha_channel(&mut self, optim: AlphaOptim) -> bool {
let (bpc, bpp) = match self.ihdr_data.color_type {
ColorType::RGBA => {
match self.ihdr_data.bit_depth {
BitDepth::Sixteen => (2, 8),
BitDepth::Eight => (1, 4),
_ => unreachable!(),
}
}
ColorType::GrayscaleAlpha => {
match self.ihdr_data.bit_depth {
BitDepth::Sixteen => (2, 4),
BitDepth::Eight => (1, 2),
_ => unreachable!(),
}
}
_ => {
return false;
}
};
let mut reduced = Vec::with_capacity(self.raw_data.len());
match optim {
AlphaOptim::NoOp => {
return false;
}
AlphaOptim::Black => {
for line in self.scan_lines() {
reduced.push(line.filter);
for pixel in line.data.chunks(bpp) {
if pixel.iter().skip(bpp - bpc).fold(0, |sum, i| sum | i) == 0 {
for _ in 0..bpp {
reduced.push(0);
}
} else {
reduced.extend_from_slice(pixel);
}
}
}
}
AlphaOptim::White => {
for line in self.scan_lines() {
reduced.push(line.filter);
for pixel in line.data.chunks(bpp) {
if pixel.iter().skip(bpp - bpc).fold(0, |sum, i| sum | i) == 0 {
for _ in 0..(bpp - bpc) {
reduced.push(255);
}
for _ in 0..bpc {
reduced.push(0);
}
} else {
reduced.extend_from_slice(pixel);
}
}
}
}
AlphaOptim::Up => {
let mut lines = Vec::new();
let scan_lines = self.scan_lines().collect::<Vec<ScanLine>>();
let mut last_line = vec![0; scan_lines[0].data.len()];
let mut current_line = Vec::with_capacity(last_line.len());
for line in scan_lines.into_iter().rev() {
current_line.push(line.filter);
for (pixel, last_pixel) in line.data.chunks(bpp).zip(last_line.chunks(bpp)) {
if pixel.iter().skip(bpp - bpc).fold(0, |sum, i| sum | i) == 0 {
current_line.extend_from_slice(&last_pixel[0..(bpp - bpc)]);
for _ in 0..bpc {
current_line.push(0);
}
} else {
current_line.extend_from_slice(pixel);
}
}
last_line = current_line.clone();
lines.push(current_line.clone());
current_line.clear();
}
reduced.extend(lines.into_iter().rev().flatten());
}
AlphaOptim::Down => {
let mut last_line = vec![0; self.scan_lines().next().unwrap().data.len()];
for line in self.scan_lines() {
reduced.push(line.filter);
for (pixel, last_pixel) in line.data.chunks(bpp).zip(last_line.chunks(bpp)) {
if pixel.iter().skip(bpp - bpc).fold(0, |sum, i| sum | i) == 0 {
reduced.extend_from_slice(&last_pixel[0..(bpp - bpc)]);
for _ in 0..bpc {
reduced.push(0);
}
} else {
reduced.extend_from_slice(pixel);
}
}
last_line = reduced.clone();
}
}
AlphaOptim::Left => {
for line in self.scan_lines() {
let mut line_bytes = Vec::with_capacity(line.data.len());
let mut last_pixel = vec![0; bpp];
for pixel in line.data.chunks(bpp).rev() {
if pixel.iter().skip(bpp - bpc).fold(0, |sum, i| sum | i) == 0 {
line_bytes.extend_from_slice(&last_pixel[0..(bpp - bpc)]);
for _ in 0..bpc {
line_bytes.push(0);
}
} else {
line_bytes.extend_from_slice(pixel);
}
last_pixel = pixel.to_owned();
}
reduced.push(line.filter);
reduced.extend(line_bytes.chunks(bpp).rev().flatten());
}
}
AlphaOptim::Right => {
for line in self.scan_lines() {
reduced.push(line.filter);
let mut last_pixel = vec![0; bpp];
for pixel in line.data.chunks(bpp) {
if pixel.iter().skip(bpp - bpc).fold(0, |sum, i| sum | i) == 0 {
reduced.extend_from_slice(&last_pixel[0..(bpp - bpc)]);
for _ in 0..bpc {
reduced.push(0);
}
} else {
reduced.extend_from_slice(pixel);
}
last_pixel = pixel.to_owned();
}
}
}
}
self.raw_data = reduced;
true
}
/// Convert the image to the specified interlacing type /// Convert the image to the specified interlacing type
/// Returns true if the interlacing was changed, false otherwise /// Returns true if the interlacing was changed, false otherwise
/// The `interlace` parameter specifies the *new* interlacing mode /// The `interlace` parameter specifies the *new* interlacing mode
@ -758,10 +938,8 @@ fn write_png_block(key: &[u8], header: &[u8], output: &mut Vec<u8>) {
header_data.extend_from_slice(key); header_data.extend_from_slice(key);
header_data.extend_from_slice(header); header_data.extend_from_slice(header);
output.reserve(header_data.len() + 8); output.reserve(header_data.len() + 8);
output let _ = output.write_u32::<BigEndian>(header_data.len() as u32 - 4);
.write_u32::<BigEndian>(header_data.len() as u32 - 4)
.ok();
let crc = crc32::checksum_ieee(&header_data); let crc = crc32::checksum_ieee(&header_data);
output.append(&mut header_data); output.append(&mut header_data);
output.write_u32::<BigEndian>(crc).ok(); let _ = output.write_u32::<BigEndian>(crc);
} }

View file

@ -134,13 +134,14 @@ pub fn reduce_rgba_to_palette(png: &mut PngData) -> bool {
} }
} }
let mut color_palette = Vec::with_capacity(palette.len() * 3 + let mut color_palette = Vec::with_capacity(
if png.aux_headers palette.len() * 3 +
.contains_key(&"bKGD".to_string()) { if png.aux_headers.contains_key(&"bKGD".to_string()) {
6 6
} else { } else {
0 0
}); },
);
let mut trans_palette = Vec::with_capacity(palette.len()); let mut trans_palette = Vec::with_capacity(palette.len());
for color in &palette { for color in &palette {
for (i, byte) in color.iter().enumerate() { for (i, byte) in color.iter().enumerate() {
@ -166,9 +167,10 @@ pub fn reduce_rgba_to_palette(png: &mut PngData) -> bool {
.step(2) .step(2)
.cloned() .cloned()
.collect::<Vec<u8>>(); .collect::<Vec<u8>>();
if let Some(entry) = color_palette if let Some(entry) = color_palette.chunks(3).position(
.chunks(3) |x| x == header_pixels.as_slice(),
.position(|x| x == header_pixels.as_slice()) { )
{
*bkgd_header = vec![entry as u8]; *bkgd_header = vec![entry as u8];
} else if color_palette.len() / 3 == 256 { } else if color_palette.len() / 3 == 256 {
return false; return false;
@ -244,9 +246,10 @@ pub fn reduce_rgb_to_palette(png: &mut PngData) -> bool {
.step(2) .step(2)
.cloned() .cloned()
.collect::<Vec<u8>>(); .collect::<Vec<u8>>();
if let Some(entry) = color_palette if let Some(entry) = color_palette.chunks(3).position(
.chunks(3) |x| x == header_pixels.as_slice(),
.position(|x| x == header_pixels.as_slice()) { )
{
*bkgd_header = vec![entry as u8]; *bkgd_header = vec![entry as u8];
} else if color_palette.len() == 255 { } else if color_palette.len() == 255 {
return false; return false;

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

File diff suppressed because it is too large Load diff

View file

@ -22,13 +22,15 @@ fn get_opts(input: &Path) -> oxipng::Options {
options options
} }
fn test_it_converts(input: &Path, fn test_it_converts(
output: &Path, input: &Path,
opts: &oxipng::Options, output: &Path,
color_type_in: ColorType, opts: &oxipng::Options,
bit_depth_in: BitDepth, color_type_in: ColorType,
color_type_out: ColorType, bit_depth_in: BitDepth,
bit_depth_out: BitDepth) { color_type_out: ColorType,
bit_depth_out: BitDepth,
) {
let png = png::PngData::new(input, opts.fix_errors).unwrap(); let png = png::PngData::new(input, opts.fix_errors).unwrap();
assert_eq!(png.ihdr_data.color_type, color_type_in); assert_eq!(png.ihdr_data.color_type, color_type_in);
@ -61,13 +63,15 @@ fn verbose_mode() {
opts.verbosity = Some(1); opts.verbosity = Some(1);
let output = opts.out_file.clone(); let output = opts.out_file.clone();
test_it_converts(&input, test_it_converts(
&output, &input,
&opts, &output,
ColorType::RGB, &opts,
BitDepth::Eight, ColorType::RGB,
ColorType::RGB, BitDepth::Eight,
BitDepth::Eight); ColorType::RGB,
BitDepth::Eight,
);
} }
#[test] #[test]
@ -374,13 +378,15 @@ fn preserve_attrs() {
opts.preserve_attrs = true; opts.preserve_attrs = true;
let output = opts.out_file.clone(); let output = opts.out_file.clone();
test_it_converts(&input, test_it_converts(
&output, &input,
&opts, &output,
ColorType::RGB, &opts,
BitDepth::Eight, ColorType::RGB,
ColorType::RGB, BitDepth::Eight,
BitDepth::Eight); ColorType::RGB,
BitDepth::Eight,
);
// TODO: Actually check permissions // TODO: Actually check permissions
} }
@ -425,11 +431,13 @@ fn zopfli_mode() {
opts.deflate = Deflaters::Zopfli; opts.deflate = Deflaters::Zopfli;
let output = opts.out_file.clone(); let output = opts.out_file.clone();
test_it_converts(&input, test_it_converts(
&output, &input,
&opts, &output,
ColorType::RGB, &opts,
BitDepth::Eight, ColorType::RGB,
ColorType::RGB, BitDepth::Eight,
BitDepth::Eight); ColorType::RGB,
BitDepth::Eight,
);
} }

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -20,13 +20,15 @@ fn get_opts(input: &Path) -> oxipng::Options {
options options
} }
fn test_it_converts(input: &Path, fn test_it_converts(
output: &Path, input: &Path,
opts: &oxipng::Options, output: &Path,
color_type_in: ColorType, opts: &oxipng::Options,
bit_depth_in: BitDepth, color_type_in: ColorType,
color_type_out: ColorType, bit_depth_in: BitDepth,
bit_depth_out: BitDepth) { color_type_out: ColorType,
bit_depth_out: BitDepth,
) {
let png = png::PngData::new(input, opts.fix_errors).unwrap(); let png = png::PngData::new(input, opts.fix_errors).unwrap();
assert_eq!(png.ihdr_data.color_type, color_type_in); assert_eq!(png.ihdr_data.color_type, color_type_in);
@ -58,13 +60,15 @@ fn issue_29() {
let opts = get_opts(&input); let opts = get_opts(&input);
let output = opts.out_file.clone(); let output = opts.out_file.clone();
test_it_converts(&input, test_it_converts(
&output, &input,
&opts, &output,
ColorType::RGB, &opts,
BitDepth::Eight, ColorType::RGB,
ColorType::RGB, BitDepth::Eight,
BitDepth::Eight); ColorType::RGB,
BitDepth::Eight,
);
} }
#[test] #[test]
@ -107,13 +111,15 @@ fn issue_52_01() {
let opts = get_opts(&input); let opts = get_opts(&input);
let output = opts.out_file.clone(); let output = opts.out_file.clone();
test_it_converts(&input, test_it_converts(
&output, &input,
&opts, &output,
ColorType::RGBA, &opts,
BitDepth::Eight, ColorType::RGBA,
ColorType::Indexed, BitDepth::Eight,
BitDepth::Eight); ColorType::Indexed,
BitDepth::Eight,
);
} }
#[test] #[test]
@ -122,13 +128,15 @@ fn issue_52_02() {
let opts = get_opts(&input); let opts = get_opts(&input);
let output = opts.out_file.clone(); let output = opts.out_file.clone();
test_it_converts(&input, test_it_converts(
&output, &input,
&opts, &output,
ColorType::RGBA, &opts,
BitDepth::Eight, ColorType::RGBA,
ColorType::Indexed, BitDepth::Eight,
BitDepth::Eight); ColorType::Indexed,
BitDepth::Eight,
);
} }
#[test] #[test]
@ -137,13 +145,15 @@ fn issue_52_03() {
let opts = get_opts(&input); let opts = get_opts(&input);
let output = opts.out_file.clone(); let output = opts.out_file.clone();
test_it_converts(&input, test_it_converts(
&output, &input,
&opts, &output,
ColorType::RGBA, &opts,
BitDepth::Eight, ColorType::RGBA,
ColorType::Indexed, BitDepth::Eight,
BitDepth::Eight); ColorType::Indexed,
BitDepth::Eight,
);
} }
#[test] #[test]
@ -152,13 +162,15 @@ fn issue_52_04() {
let opts = get_opts(&input); let opts = get_opts(&input);
let output = opts.out_file.clone(); let output = opts.out_file.clone();
test_it_converts(&input, test_it_converts(
&output, &input,
&opts, &output,
ColorType::RGBA, &opts,
BitDepth::Eight, ColorType::RGBA,
ColorType::Indexed, BitDepth::Eight,
BitDepth::One); ColorType::Indexed,
BitDepth::One,
);
} }
#[test] #[test]
@ -167,13 +179,15 @@ fn issue_52_05() {
let opts = get_opts(&input); let opts = get_opts(&input);
let output = opts.out_file.clone(); let output = opts.out_file.clone();
test_it_converts(&input, test_it_converts(
&output, &input,
&opts, &output,
ColorType::RGBA, &opts,
BitDepth::Eight, ColorType::RGBA,
ColorType::Indexed, BitDepth::Eight,
BitDepth::One); ColorType::Indexed,
BitDepth::One,
);
} }
#[test] #[test]
@ -182,13 +196,15 @@ fn issue_52_06() {
let opts = get_opts(&input); let opts = get_opts(&input);
let output = opts.out_file.clone(); let output = opts.out_file.clone();
test_it_converts(&input, test_it_converts(
&output, &input,
&opts, &output,
ColorType::RGBA, &opts,
BitDepth::Eight, ColorType::RGBA,
ColorType::Indexed, BitDepth::Eight,
BitDepth::Two); ColorType::Indexed,
BitDepth::Two,
);
} }
#[test] #[test]
@ -197,13 +213,15 @@ fn issue_56() {
let opts = get_opts(&input); let opts = get_opts(&input);
let output = opts.out_file.clone(); let output = opts.out_file.clone();
test_it_converts(&input, test_it_converts(
&output, &input,
&opts, &output,
ColorType::Indexed, &opts,
BitDepth::Four, ColorType::Indexed,
ColorType::Indexed, BitDepth::Four,
BitDepth::Four); ColorType::Indexed,
BitDepth::Four,
);
} }
#[test] #[test]
@ -212,13 +230,15 @@ fn issue_58() {
let opts = get_opts(&input); let opts = get_opts(&input);
let output = opts.out_file.clone(); let output = opts.out_file.clone();
test_it_converts(&input, test_it_converts(
&output, &input,
&opts, &output,
ColorType::Indexed, &opts,
BitDepth::Four, ColorType::Indexed,
ColorType::Indexed, BitDepth::Four,
BitDepth::Four); ColorType::Indexed,
BitDepth::Four,
);
} }
#[test] #[test]
@ -227,13 +247,15 @@ fn issue_59() {
let opts = get_opts(&input); let opts = get_opts(&input);
let output = opts.out_file.clone(); let output = opts.out_file.clone();
test_it_converts(&input, test_it_converts(
&output, &input,
&opts, &output,
ColorType::RGBA, &opts,
BitDepth::Eight, ColorType::RGBA,
ColorType::RGBA, BitDepth::Eight,
BitDepth::Eight); ColorType::RGBA,
BitDepth::Eight,
);
} }
#[test] #[test]
@ -242,11 +264,13 @@ fn issue_60() {
let opts = get_opts(&input); let opts = get_opts(&input);
let output = opts.out_file.clone(); let output = opts.out_file.clone();
test_it_converts(&input, test_it_converts(
&output, &input,
&opts, &output,
ColorType::RGBA, &opts,
BitDepth::Eight, ColorType::RGBA,
ColorType::GrayscaleAlpha, BitDepth::Eight,
BitDepth::Eight); ColorType::GrayscaleAlpha,
BitDepth::Eight,
);
} }