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
cache: cargo
- os: linux
rust: 1.17.0
rust: 1.19.0
env: TARGET=x86_64-unknown-linux-gnu
cache: cargo
- 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))
**Version 0.16.2**
### Version 0.16.2
- Publicly export `error` module
**Version 0.16.1**
### Version 0.16.1
- 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
- Bump `image` to 0.14
- 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 `rayon` to 0.7
- 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))
**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))
- 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
**Version 0.14.4**
### Version 0.14.4
- 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
**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 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.
**Version 0.14.0**
### Version 0.14.0
- Performance optimizations
- [SEMVER_MAJOR] Bump minimum rustc version to 1.13.0
- Add categories on crates.io
**Version 0.13.1**
### Version 0.13.1
- Bump regex dependency to 0.2
- Bump byteorder dependency to 1.0
- 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))
- [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
**Version 0.12.0**
### Version 0.12.0
- Performance optimizations
- 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.
**Version 0.11.0**
### Version 0.11.0
- [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_MAJOR] Return proper `PngError` type which implements `std::error::Error` from `Result`s
- [SEMVER_MAJOR] Rename module `deflate::deflate` to `deflate`
- Performance optimizations
**Version 0.10.0**
### Version 0.10.0
- [SEMVER_MINOR] Make clap and regex dependencies optional
- 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
**Version 0.9.0**
### Version 0.9.0
- [SEMVER_MAJOR] Significant refactoring of modules
- Use `itertools` to cleanup areas of code
- 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))
**Version 0.8.1**
### Version 0.8.1
- Minor optimizations
- 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
**Version 0.7.0**
### Version 0.7.0
- Minor compression improvement on interlaced images
- Performance optimizations
- [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))
**Version 0.6.0**
### Version 0.6.0
- Fix issue where output directory would not be created if it did not exist
- 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] 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))
- 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))
**Version 0.4.0**
### Version 0.4.0
- Performance optimizations
- [SEMVER_MAJOR] `-s` automatically infers `--strip safe` ([#31](https://github.com/shssoichiro/oxipng/issues/31))
- Update byteorder and clap crates
- 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
- [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
**Version 0.2.2**
### Version 0.2.2
- 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))
- 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
- 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))
**Version 0.2.0**
### Version 0.2.0
- 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))
- 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_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))
**Version 0.1.0**
### Version 0.1.0
- Initial beta release
- Reduce color type and bit depth
- Recompress with zlib

View file

@ -27,7 +27,7 @@ cargo build --release
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.
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 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]
@ -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 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]
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();
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]
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();
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]
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();
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]
@ -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 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]
@ -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 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]
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();
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]
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();
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]
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();
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]
@ -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 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]
@ -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 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]
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();
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]
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();
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]
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();
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]
@ -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 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]
@ -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 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]
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();
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]
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();
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]
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();
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]

View file

@ -25,7 +25,9 @@ fn filters_8_bits_filter_0(b: &mut Bencher) {
#[bench]
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();
b.iter(|| { png.filter_image(0); });
@ -33,7 +35,9 @@ fn filters_4_bits_filter_0(b: &mut Bencher) {
#[bench]
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();
b.iter(|| { png.filter_image(0); });
@ -41,7 +45,9 @@ fn filters_2_bits_filter_0(b: &mut Bencher) {
#[bench]
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();
b.iter(|| { png.filter_image(0); });
@ -65,7 +71,9 @@ fn filters_8_bits_filter_1(b: &mut Bencher) {
#[bench]
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();
b.iter(|| { png.filter_image(1); });
@ -73,7 +81,9 @@ fn filters_4_bits_filter_1(b: &mut Bencher) {
#[bench]
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();
b.iter(|| { png.filter_image(1); });
@ -81,7 +91,9 @@ fn filters_2_bits_filter_1(b: &mut Bencher) {
#[bench]
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();
b.iter(|| { png.filter_image(1); });
@ -105,7 +117,9 @@ fn filters_8_bits_filter_2(b: &mut Bencher) {
#[bench]
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();
b.iter(|| { png.filter_image(2); });
@ -113,7 +127,9 @@ fn filters_4_bits_filter_2(b: &mut Bencher) {
#[bench]
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();
b.iter(|| { png.filter_image(2); });
@ -121,7 +137,9 @@ fn filters_2_bits_filter_2(b: &mut Bencher) {
#[bench]
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();
b.iter(|| { png.filter_image(2); });
@ -145,7 +163,9 @@ fn filters_8_bits_filter_3(b: &mut Bencher) {
#[bench]
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();
b.iter(|| { png.filter_image(3); });
@ -153,7 +173,9 @@ fn filters_4_bits_filter_3(b: &mut Bencher) {
#[bench]
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();
b.iter(|| { png.filter_image(3); });
@ -161,7 +183,9 @@ fn filters_2_bits_filter_3(b: &mut Bencher) {
#[bench]
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();
b.iter(|| { png.filter_image(3); });
@ -185,7 +209,9 @@ fn filters_8_bits_filter_4(b: &mut Bencher) {
#[bench]
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();
b.iter(|| { png.filter_image(4); });
@ -193,7 +219,9 @@ fn filters_4_bits_filter_4(b: &mut Bencher) {
#[bench]
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();
b.iter(|| { png.filter_image(4); });
@ -201,7 +229,9 @@ fn filters_2_bits_filter_4(b: &mut Bencher) {
#[bench]
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();
b.iter(|| { png.filter_image(4); });
@ -225,7 +255,9 @@ fn filters_8_bits_filter_5(b: &mut Bencher) {
#[bench]
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();
b.iter(|| { png.filter_image(5); });
@ -233,7 +265,9 @@ fn filters_4_bits_filter_5(b: &mut Bencher) {
#[bench]
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();
b.iter(|| { png.filter_image(5); });
@ -241,7 +275,9 @@ fn filters_2_bits_filter_5(b: &mut Bencher) {
#[bench]
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();
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();
b.iter(|| {
let mut safe_png = png.clone();
safe_png.change_interlacing(1);
});
let mut safe_png = png.clone();
safe_png.change_interlacing(1);
});
}
#[bench]
@ -24,95 +24,111 @@ fn interlacing_8_bits(b: &mut Bencher) {
let png = png::PngData::new(&input, false).unwrap();
b.iter(|| {
let mut safe_png = png.clone();
safe_png.change_interlacing(1);
});
let mut safe_png = png.clone();
safe_png.change_interlacing(1);
});
}
#[bench]
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();
b.iter(|| {
let mut safe_png = png.clone();
safe_png.change_interlacing(1);
});
let mut safe_png = png.clone();
safe_png.change_interlacing(1);
});
}
#[bench]
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();
b.iter(|| {
let mut safe_png = png.clone();
safe_png.change_interlacing(1);
});
let mut safe_png = png.clone();
safe_png.change_interlacing(1);
});
}
#[bench]
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();
b.iter(|| {
let mut safe_png = png.clone();
safe_png.change_interlacing(1);
});
let mut safe_png = png.clone();
safe_png.change_interlacing(1);
});
}
#[bench]
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();
b.iter(|| {
let mut safe_png = png.clone();
safe_png.change_interlacing(0);
});
let mut safe_png = png.clone();
safe_png.change_interlacing(0);
});
}
#[bench]
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();
b.iter(|| {
let mut safe_png = png.clone();
safe_png.change_interlacing(0);
});
let mut safe_png = png.clone();
safe_png.change_interlacing(0);
});
}
#[bench]
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();
b.iter(|| {
let mut safe_png = png.clone();
safe_png.change_interlacing(0);
});
let mut safe_png = png.clone();
safe_png.change_interlacing(0);
});
}
#[bench]
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();
b.iter(|| {
let mut safe_png = png.clone();
safe_png.change_interlacing(0);
});
let mut safe_png = png.clone();
safe_png.change_interlacing(0);
});
}
#[bench]
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();
b.iter(|| {
let mut safe_png = png.clone();
safe_png.change_interlacing(0);
});
let mut safe_png = png.clone();
safe_png.change_interlacing(0);
});
}

View file

@ -4,6 +4,7 @@ extern crate test;
extern crate oxipng;
use oxipng::png;
use oxipng::colors::AlphaOptim;
use test::Bencher;
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();
b.iter(|| {
let mut safe_png = png.clone();
safe_png.reduce_bit_depth();
});
let mut safe_png = png.clone();
safe_png.reduce_bit_depth();
});
}
#[bench]
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();
b.iter(|| {
let mut safe_png = png.clone();
safe_png.reduce_bit_depth();
});
let mut safe_png = png.clone();
safe_png.reduce_bit_depth();
});
}
#[bench]
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();
b.iter(|| {
let mut safe_png = png.clone();
safe_png.reduce_bit_depth();
});
let mut safe_png = png.clone();
safe_png.reduce_bit_depth();
});
}
#[bench]
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();
b.iter(|| {
let mut safe_png = png.clone();
safe_png.reduce_bit_depth();
});
let mut safe_png = png.clone();
safe_png.reduce_bit_depth();
});
}
#[bench]
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();
b.iter(|| {
let mut safe_png = png.clone();
safe_png.reduce_bit_depth();
});
let mut safe_png = png.clone();
safe_png.reduce_bit_depth();
});
}
#[bench]
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();
b.iter(|| {
let mut safe_png = png.clone();
safe_png.reduce_bit_depth();
});
let mut safe_png = png.clone();
safe_png.reduce_bit_depth();
});
}
#[bench]
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();
b.iter(|| {
let mut safe_png = png.clone();
safe_png.reduce_bit_depth();
});
let mut safe_png = png.clone();
safe_png.reduce_bit_depth();
});
}
#[bench]
@ -90,9 +103,9 @@ fn reductions_rgba_to_rgb_16(b: &mut Bencher) {
let png = png::PngData::new(&input, false).unwrap();
b.iter(|| {
let mut safe_png = png.clone();
safe_png.reduce_color_type();
});
let mut safe_png = png.clone();
safe_png.reduce_color_type();
});
}
#[bench]
@ -101,64 +114,74 @@ fn reductions_rgba_to_rgb_8(b: &mut Bencher) {
let png = png::PngData::new(&input, false).unwrap();
b.iter(|| {
let mut safe_png = png.clone();
safe_png.reduce_color_type();
});
let mut safe_png = png.clone();
safe_png.reduce_color_type();
});
}
#[bench]
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();
b.iter(|| {
let mut safe_png = png.clone();
safe_png.reduce_color_type();
});
let mut safe_png = png.clone();
safe_png.reduce_color_type();
});
}
#[bench]
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();
b.iter(|| {
let mut safe_png = png.clone();
safe_png.reduce_color_type();
});
let mut safe_png = png.clone();
safe_png.reduce_color_type();
});
}
#[bench]
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();
b.iter(|| {
let mut safe_png = png.clone();
safe_png.reduce_color_type();
});
let mut safe_png = png.clone();
safe_png.reduce_color_type();
});
}
#[bench]
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();
b.iter(|| {
let mut safe_png = png.clone();
safe_png.reduce_color_type();
});
let mut safe_png = png.clone();
safe_png.reduce_color_type();
});
}
#[bench]
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();
b.iter(|| {
let mut safe_png = png.clone();
safe_png.reduce_color_type();
});
let mut safe_png = png.clone();
safe_png.reduce_color_type();
});
}
#[bench]
@ -167,9 +190,9 @@ fn reductions_rgb_to_grayscale_8(b: &mut Bencher) {
let png = png::PngData::new(&input, false).unwrap();
b.iter(|| {
let mut safe_png = png.clone();
safe_png.reduce_color_type();
});
let mut safe_png = png.clone();
safe_png.reduce_color_type();
});
}
#[bench]
@ -178,9 +201,9 @@ fn reductions_rgba_to_palette_8(b: &mut Bencher) {
let png = png::PngData::new(&input, false).unwrap();
b.iter(|| {
let mut safe_png = png.clone();
safe_png.reduce_color_type();
});
let mut safe_png = png.clone();
safe_png.reduce_color_type();
});
}
#[bench]
@ -189,40 +212,124 @@ fn reductions_rgb_to_palette_8(b: &mut Bencher) {
let png = png::PngData::new(&input, false).unwrap();
b.iter(|| {
let mut safe_png = png.clone();
safe_png.reduce_color_type();
});
let mut safe_png = png.clone();
safe_png.reduce_color_type();
});
}
#[bench]
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();
b.iter(|| {
let mut safe_png = png.clone();
safe_png.reduce_palette();
});
let mut safe_png = png.clone();
safe_png.reduce_palette();
});
}
#[bench]
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();
b.iter(|| {
let mut safe_png = png.clone();
safe_png.reduce_palette();
});
let mut safe_png = png.clone();
safe_png.reduce_palette();
});
}
#[bench]
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();
b.iter(|| {
let mut safe_png = png.clone();
safe_png.reduce_palette();
});
let mut safe_png = png.clone();
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]
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();
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]
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();
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]
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();
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 {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", match *self {
ColorType::Grayscale => "Grayscale",
ColorType::RGB => "RGB",
ColorType::Indexed => "Indexed",
ColorType::GrayscaleAlpha => "Grayscale + Alpha",
ColorType::RGBA => "RGB + Alpha",
})
write!(
f,
"{}",
match *self {
ColorType::Grayscale => "Grayscale",
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 {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", match *self {
BitDepth::One => "1",
BitDepth::Two => "2",
BitDepth::Four => "4",
BitDepth::Eight => "8",
BitDepth::Sixteen => "16",
})
write!(
f,
"{}",
match *self {
BitDepth::One => "1",
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> {
pub fn new_compress(lvl: c_int,
window_bits: c_int,
mem_size: c_int,
strategy: c_int)
-> Stream<Compress> {
pub fn new_compress(
lvl: c_int,
window_bits: c_int,
mem_size: c_int,
strategy: c_int,
) -> Stream<Compress> {
unsafe {
let mut state: miniz_sys::mz_stream = mem::zeroed();
let ret = miniz_sys::mz_deflateInit2(&mut state,
lvl,
miniz_sys::MZ_DEFLATED,
window_bits,
mem_size,
strategy);
let ret = miniz_sys::mz_deflateInit2(
&mut state,
lvl,
miniz_sys::MZ_DEFLATED,
window_bits,
mem_size,
strategy,
);
debug_assert_eq!(ret, 0);
Stream {
raw: state,
@ -91,12 +94,14 @@ impl Stream<Compress> {
unsafe {
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);
let rc = miniz_sys::mz_deflate(&mut self.raw,
if self.raw.avail_in > 0 {
miniz_sys::MZ_NO_FLUSH
} else {
miniz_sys::MZ_FINISH
});
let rc = miniz_sys::mz_deflate(
&mut self.raw,
if self.raw.avail_in > 0 {
miniz_sys::MZ_NO_FLUSH
} else {
miniz_sys::MZ_FINISH
},
);
output.set_len(self.total_out() as usize);
rc
}

View file

@ -6,37 +6,33 @@ pub fn filter_line(filter: u8, bpp: usize, data: &[u8], last_line: &[u8]) -> Vec
}
1 => {
filtered.extend_from_slice(&data[0..bpp]);
filtered.extend(data.iter()
.skip(bpp)
.zip(data.iter())
.map(|(cur, last)| cur.wrapping_sub(*last)));
filtered.extend(data.iter().skip(bpp).zip(data.iter()).map(|(cur, last)| {
cur.wrapping_sub(*last)
}));
}
2 => {
if last_line.is_empty() {
filtered.extend_from_slice(data);
} else {
filtered.extend(data.iter()
.zip(last_line.iter())
.map(|(cur, last)| cur.wrapping_sub(*last)));
filtered.extend(data.iter().zip(last_line.iter()).map(|(cur, last)| {
cur.wrapping_sub(*last)
}));
};
}
3 => {
for (i, byte) in data.iter().enumerate() {
if last_line.is_empty() {
filtered.push(match i.checked_sub(bpp) {
Some(x) => byte.wrapping_sub(data[x] >> 1),
None => *byte,
});
Some(x) => byte.wrapping_sub(data[x] >> 1),
None => *byte,
});
} else {
filtered.push(match i.checked_sub(bpp) {
Some(x) => {
byte.wrapping_sub(((data[x] as u16 +
last_line[i] as u16) >>
1) as
u8)
}
None => byte.wrapping_sub(last_line[i] >> 1),
});
Some(x) => {
byte.wrapping_sub(((data[x] as u16 + last_line[i] as u16) >> 1) as 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() {
if last_line.is_empty() {
filtered.push(match i.checked_sub(bpp) {
Some(x) => byte.wrapping_sub(data[x]),
None => *byte,
});
Some(x) => byte.wrapping_sub(data[x]),
None => *byte,
});
} else {
filtered.push(match i.checked_sub(bpp) {
Some(x) => {
byte.wrapping_sub(paeth_predictor(data[x],
last_line[i],
last_line[x]))
}
None => byte.wrapping_sub(last_line[i]),
});
Some(x) => {
byte.wrapping_sub(paeth_predictor(data[x], last_line[i], last_line[x]))
}
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() {
unfiltered.extend_from_slice(data);
} else {
unfiltered.extend(data.iter()
.zip(last_line.iter())
.map(|(cur, last)| cur.wrapping_add(*last)));
unfiltered.extend(data.iter().zip(last_line.iter()).map(|(cur, last)| {
cur.wrapping_add(*last)
}));
};
}
3 => {
@ -108,9 +102,9 @@ pub fn unfilter_line(filter: u8, bpp: usize, data: &[u8], last_line: &[u8]) -> V
match i.checked_sub(bpp) {
Some(x) => {
let b = unfiltered[x];
unfiltered
.push(byte.wrapping_add(((b as u16 + last_line[i] as u16) >> 1) as
u8));
unfiltered.push(byte.wrapping_add(
((b as u16 + last_line[i] as u16) >> 1) as u8,
));
}
None => {
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) {
Some(x) => {
let b = unfiltered[x];
unfiltered.push(byte.wrapping_add(paeth_predictor(b,
last_line[i],
last_line[x])));
unfiltered.push(byte.wrapping_add(
paeth_predictor(b, last_line[i], last_line[x]),
));
}
None => {
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
}
pub fn parse_next_header(byte_data: &[u8],
byte_offset: &mut usize,
fix_errors: bool)
-> Result<Option<(String, Vec<u8>)>, PngError> {
let mut rdr = Cursor::new(byte_data
.iter()
.skip(*byte_offset)
.take(4)
.cloned()
.collect::<Vec<u8>>());
pub fn parse_next_header(
byte_data: &[u8],
byte_offset: &mut usize,
fix_errors: bool,
) -> Result<Option<(String, Vec<u8>)>, PngError> {
let mut rdr = Cursor::new(
byte_data
.iter()
.skip(*byte_offset)
.take(4)
.cloned()
.collect::<Vec<u8>>(),
);
let length: u32 = match rdr.read_u32::<BigEndian>() {
Ok(x) => x,
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()
.collect();
*byte_offset += length as usize;
let mut rdr = Cursor::new(byte_data
.iter()
.skip(*byte_offset)
.take(4)
.cloned()
.collect::<Vec<u8>>());
let mut rdr = Cursor::new(
byte_data
.iter()
.skip(*byte_offset)
.take(4)
.cloned()
.collect::<Vec<u8>>(),
);
let crc: u32 = match rdr.read_u32::<BigEndian>() {
Ok(x) => x,
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;
header_bytes.extend_from_slice(&data);
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",
header)));
return Err(PngError::new(&format!(
"CRC Mismatch in {} header; May be recoverable by using --fix",
header
)));
}
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> {
let mut rdr = Cursor::new(&byte_data[0..8]);
Ok(IhdrData {
color_type: match byte_data[9] {
0 => ColorType::Grayscale,
2 => ColorType::RGB,
3 => ColorType::Indexed,
4 => ColorType::GrayscaleAlpha,
6 => ColorType::RGBA,
_ => return Err(PngError::new("Unexpected color type in header")),
},
bit_depth: match byte_data[8] {
1 => BitDepth::One,
2 => BitDepth::Two,
4 => BitDepth::Four,
8 => BitDepth::Eight,
16 => BitDepth::Sixteen,
_ => return Err(PngError::new("Unexpected bit depth in header")),
},
width: rdr.read_u32::<BigEndian>().unwrap(),
height: rdr.read_u32::<BigEndian>().unwrap(),
compression: byte_data[10],
filter: byte_data[11],
interlaced: byte_data[12],
})
color_type: match byte_data[9] {
0 => ColorType::Grayscale,
2 => ColorType::RGB,
3 => ColorType::Indexed,
4 => ColorType::GrayscaleAlpha,
6 => ColorType::RGBA,
_ => return Err(PngError::new("Unexpected color type in header")),
},
bit_depth: match byte_data[8] {
1 => BitDepth::One,
2 => BitDepth::Two,
4 => BitDepth::Four,
8 => BitDepth::Eight,
16 => BitDepth::Sixteen,
_ => return Err(PngError::new("Unexpected bit depth in header")),
},
width: rdr.read_u32::<BigEndian>().unwrap(),
height: rdr.read_u32::<BigEndian>().unwrap(),
compression: byte_data[10],
filter: byte_data[11],
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;
// Initialize each output line with a starting filter byte of 0
// as well as some blank data
let mut lines: Vec<BitVec> = vec![BitVec::from_elem(bits_per_line, false);
png.ihdr_data.height as usize];
let mut lines: Vec<BitVec> =
vec![BitVec::from_elem(bits_per_line, false); png.ihdr_data.height as usize];
let mut current_pass = 1;
let mut pass_constants = interlaced_constants(current_pass);
let mut current_y: usize = pass_constants.y_shift as usize;
for line in png.scan_lines() {
let bit_vec = BitVec::from_bytes(&line.data);
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;
for (i, bit) in bit_vec.iter().enumerate() {
// Avoid moving padded 0's into new image
@ -108,7 +108,7 @@ pub fn deinterlace_image(png: &mut PngData) {
break;
}
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
let index = 8 + (i % bits_per_pixel as usize) + current_x * bits_per_pixel as usize;
lines[current_y].set(index, bit);

View file

@ -20,7 +20,7 @@ use png::PngData;
use rayon::prelude::*;
use std::collections::{HashMap, HashSet};
use std::fs::{File, copy};
use std::io::{BufWriter, Write, stderr, stdout};
use std::io::{BufWriter, Write, stdout};
use std::path::{Path, PathBuf};
pub mod colors;
@ -90,6 +90,8 @@ pub struct Options {
/// 8-15 are valid values
/// Default: `15`
pub window: u8,
/// Alpha filtering strategies to use
pub alphas: HashSet<colors::AlphaOptim>,
/// Whether to attempt bit depth reduction
/// Default: `true`
pub bit_depth_reduction: bool,
@ -241,6 +243,9 @@ impl Default for Options {
for i in 0..4 {
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
let num_cpus = num_cpus::get();
@ -265,6 +270,7 @@ impl Default for Options {
memory: memory,
strategies: strategies,
window: 15,
alphas,
bit_depth_reduction: true,
color_type_reduction: true,
palette_reduction: true,
@ -281,11 +287,11 @@ impl Default for Options {
pub fn optimize(filepath: &Path, opts: &Options) -> Result<(), PngError> {
// Initialize the thread pool with correct number of 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.
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);
@ -296,27 +302,29 @@ pub fn optimize(filepath: &Path, opts: &Options) -> Result<(), PngError> {
let optimized_output = optimize_png(&mut png, &in_data, 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(());
}
if opts.pretend {
if opts.verbosity.is_some() {
writeln!(&mut stderr(), "Running in pretend mode, no output").ok();
eprintln!("Running in pretend mode, no output");
}
} else {
if opts.backup {
match copy(in_file,
in_file.with_extension(format!("bak.{}",
in_file
.extension()
.unwrap()
.to_str()
.unwrap()))) {
match copy(
in_file,
in_file.with_extension(format!(
"bak.{}",
in_file.extension().unwrap().to_str().unwrap()
)),
) {
Ok(x) => x,
Err(_) => {
return Err(PngError::new(&format!("Unable to write to backup file at {}",
opts.out_file.display())))
return Err(PngError::new(&format!(
"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) {
Ok(x) => x,
Err(_) => {
return Err(PngError::new(&format!("Unable to write to file {}",
opts.out_file.display())))
return Err(PngError::new(&format!(
"Unable to write to file {}",
opts.out_file.display()
)))
}
};
@ -352,9 +362,9 @@ pub fn optimize(filepath: &Path, opts: &Options) -> Result<(), PngError> {
}
Err(_) => {
if opts.verbosity.is_some() {
writeln!(&mut stderr(),
"Failed to set permissions on output file")
.ok();
eprintln!(
"Failed to set permissions on output file"
);
}
}
}
@ -362,17 +372,14 @@ pub fn optimize(filepath: &Path, opts: &Options) -> Result<(), PngError> {
}
Err(_) => {
if opts.verbosity.is_some() {
writeln!(&mut stderr(),
"Failed to read permissions on input file")
.ok();
eprintln!("Failed to read permissions on input file");
}
}
}
}
Err(_) => {
if opts.verbosity.is_some() {
writeln!(&mut stderr(), "Failed to read permissions on input file")
.ok();
eprintln!("Failed to read permissions on input file");
}
}
};
@ -382,12 +389,14 @@ pub fn optimize(filepath: &Path, opts: &Options) -> Result<(), PngError> {
match buffer.write_all(&optimized_output) {
Ok(_) => {
if opts.verbosity.is_some() {
writeln!(&mut stderr(), "Output: {}", opts.out_file.display()).ok();
eprintln!("Output: {}", opts.out_file.display());
}
}
Err(_) => {
return Err(PngError::new(&format!("Unable to write to file {}",
opts.out_file.display())))
return Err(PngError::new(&format!(
"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> {
// Initialize the thread pool with correct number of 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.
if opts.verbosity.is_some() {
writeln!(&mut stderr(), "Processing from memory").ok();
eprintln!("Processing from memory");
}
let original_size = data.len() as usize;
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)?;
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())
} else {
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
fn optimize_png(png: &mut PngData,
original_data: &[u8],
opts: &Options)
-> Result<Vec<u8>, PngError> {
type TrialWithData = (u8, u8, u8, u8, Vec<u8>);
fn optimize_png(
png: &mut PngData,
original_data: &[u8],
opts: &Options,
) -> Result<Vec<u8>, PngError> {
type TrialWithData = (TrialOptions, Vec<u8>);
let original_png = png.clone();
@ -433,33 +443,27 @@ fn optimize_png(png: &mut PngData,
let file_original_size = original_data.len();
let idat_original_size = png.idat_data.len();
if opts.verbosity.is_some() {
writeln!(&mut stderr(),
" {}x{} pixels, PNG format",
png.ihdr_data.width,
png.ihdr_data.height)
.ok();
eprintln!(
" {}x{} pixels, PNG format",
png.ihdr_data.width,
png.ihdr_data.height
);
if let Some(ref palette) = png.palette {
writeln!(&mut stderr(),
" {} bits/pixel, {} colors in palette",
png.ihdr_data.bit_depth,
palette.len() / 3)
.ok();
eprintln!(
" {} bits/pixel, {} colors in palette",
png.ihdr_data.bit_depth,
palette.len() / 3
);
} else {
writeln!(&mut stderr(),
" {}x{} bits/pixel, {:?}",
png.channels_per_pixel(),
png.ihdr_data.bit_depth,
png.ihdr_data.color_type)
.ok();
eprintln!(
" {}x{} bits/pixel, {:?}",
png.channels_per_pixel(),
png.ihdr_data.bit_depth,
png.ihdr_data.color_type
);
}
writeln!(&mut stderr(),
" IDAT size = {} bytes",
idat_original_size)
.ok();
writeln!(&mut stderr(),
" File size = {} bytes",
file_original_size)
.ok();
eprintln!(" IDAT size = {} bytes", idat_original_size);
eprintln!(" File size = {} bytes", file_original_size);
}
let mut filter = opts.filter.iter().cloned().collect::<Vec<u8>>();
@ -470,7 +474,8 @@ fn optimize_png(png: &mut PngData,
if opts.use_heuristics {
// Heuristically determine which set of options to use
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() {
filter.push(5);
}
@ -496,9 +501,9 @@ fn optimize_png(png: &mut PngData,
} else {
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() {
writeln!(&mut stderr(), "Trying: {} combinations", combinations).ok();
eprintln!("Trying: {} combinations", combinations);
}
for f in &filter {
@ -506,24 +511,34 @@ fn optimize_png(png: &mut PngData,
for zc in compression {
for zm in memory {
for zs in &strategies {
results.push((*f, *zc, *zm, *zs));
results.push(TrialOptions {
filter: *f,
compression: *zc,
memory: *zm,
strategy: *zs,
});
}
}
}
} else {
// 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());
filter
let filters: HashMap<u8, Vec<u8>> = filter
.par_iter()
.with_max_len(1)
.map(|f| (*f, png.filter_image(*f)))
.collect_into(&mut filters_tmp);
let filters: HashMap<u8, Vec<u8>> = filters_tmp.into_iter().collect();
.map(|f| {
let png = png.clone();
(*f, png.filter_image(*f))
})
.collect();
let original_len = original_png.idat_data.len();
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()
.with_max_len(1)
.filter_map(|trial| {
let filtered = &filters[&trial.0];
let filtered = &filters[&trial.filter];
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 {
deflate::zopfli_deflate(filtered)
}.unwrap();
if opts.verbosity == Some(1) {
writeln!(&mut stderr(),
" zc = {} zm = {} zs = {} f = {} {} bytes",
trial.1,
trial.2,
trial.3,
trial.0,
new_idat.len())
.ok();
eprintln!(
" zc = {} zm = {} zs = {} f = {} {} bytes",
trial.compression,
trial.memory,
trial.strategy,
trial.filter,
new_idat.len()
);
}
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 {
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 {
png.idat_data = better.4;
png.idat_data = better.1;
if opts.verbosity.is_some() {
writeln!(&mut stderr(), "Found better combination:").ok();
writeln!(&mut stderr(),
" zc = {} zm = {} zs = {} f = {} {} bytes",
better.1,
better.2,
better.3,
better.0,
png.idat_data.len())
.ok();
let opts = better.0;
eprintln!("Found better combination:");
eprintln!(
" zc = {} zm = {} zs = {} f = {} {} bytes",
opts.compression,
opts.memory,
opts.strategy,
opts.filter,
png.idat_data.len()
);
}
} 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 idat_original_size >= png.idat_data.len() {
writeln!(&mut stderr(),
" IDAT size = {} bytes ({} bytes decrease)",
png.idat_data.len(),
idat_original_size - png.idat_data.len())
.ok();
eprintln!(
" IDAT size = {} bytes ({} bytes decrease)",
png.idat_data.len(),
idat_original_size - png.idat_data.len()
);
} else {
writeln!(&mut stderr(),
" IDAT size = {} bytes ({} bytes increase)",
png.idat_data.len(),
png.idat_data.len() - idat_original_size)
.ok();
eprintln!(
" IDAT size = {} bytes ({} bytes increase)",
png.idat_data.len(),
png.idat_data.len() - idat_original_size
);
}
if file_original_size >= output.len() {
writeln!(&mut stderr(),
" file size = {} bytes ({} bytes = {:.2}% decrease)",
output.len(),
file_original_size - output.len(),
(file_original_size - output.len()) as f64 / file_original_size as f64 *
100f64)
.ok();
eprintln!(
" file size = {} bytes ({} bytes = {:.2}% decrease)",
output.len(),
file_original_size - output.len(),
(file_original_size - output.len()) as f64 / file_original_size as f64 * 100f64
);
} else {
writeln!(&mut stderr(),
" file size = {} bytes ({} bytes = {:.2}% increase)",
output.len(),
output.len() - file_original_size,
(output.len() - file_original_size) as f64 / file_original_size as f64 *
100f64)
.ok();
eprintln!(
" file size = {} bytes ({} bytes = {:.2}% increase)",
output.len(),
output.len() - file_original_size,
(output.len() - file_original_size) as f64 / file_original_size as f64 * 100f64
);
}
}
@ -620,13 +640,16 @@ fn optimize_png(png: &mut PngData,
if let Ok(new_png) = new_png {
if let Ok(old_png) = old_png {
if old_png
.pixels()
.map(|x| x.2.channels().to_owned())
.collect::<Vec<Vec<u8>>>() ==
new_png
.pixels()
.map(|x| x.2.channels().to_owned())
.collect::<Vec<Vec<u8>>>() {
.pixels()
.map(|x| x.2.channels().to_owned())
.filter(|p| !(p.len() == 4 && p[3] == 0))
.collect::<Vec<Vec<u8>>>() ==
new_png
.pixels()
.map(|x| x.2.channels().to_owned())
.filter(|p| !(p.len() == 4 && p[3] == 0))
.collect::<Vec<Vec<u8>>>()
{
return Ok(output);
}
} 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"))
}
@ -678,6 +703,8 @@ fn perform_reductions(png: &mut png::PngData, opts: &Options) -> bool {
}
}
png.try_alpha_reduction(&opts.alphas);
reduction_occurred
}
@ -685,18 +712,18 @@ fn perform_reductions(png: &mut png::PngData, opts: &Options) -> bool {
#[inline]
fn report_reduction(png: &png::PngData) {
if let Some(ref palette) = png.palette {
writeln!(&mut stderr(),
"Reducing image to {} bits/pixel, {} colors in palette",
png.ihdr_data.bit_depth,
palette.len() / 3)
.ok();
eprintln!(
"Reducing image to {} bits/pixel, {} colors in palette",
png.ihdr_data.bit_depth,
palette.len() / 3
);
} else {
writeln!(&mut stderr(),
"Reducing image to {}x{} bits/pixel, {}",
png.channels_per_pixel(),
png.ihdr_data.bit_depth,
png.ihdr_data.color_type)
.ok();
eprintln!(
"Reducing image to {}x{} bits/pixel, {}",
png.channels_per_pixel(),
png.ihdr_data.bit_depth,
png.ihdr_data.color_type
);
}
}
@ -711,8 +738,17 @@ fn perform_strip(png: &mut png::PngData, opts: &Options) {
}
}
Headers::Safe => {
const PRESERVED_HEADERS: [&'static str; 9] = ["cHRM", "gAMA", "iCCP", "sBIT", "sRGB",
"bKGD", "hIST", "pHYs", "sPLT"];
const PRESERVED_HEADERS: [&'static str; 9] = [
"cHRM",
"gAMA",
"iCCP",
"sBIT",
"sRGB",
"bKGD",
"hIST",
"pHYs",
"sPLT",
];
let hdrs = png.aux_headers.keys().cloned().collect::<Vec<String>>();
for hdr in hdrs {
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 {
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;
use clap::{App, Arg, ArgMatches};
use oxipng::colors::AlphaOptim;
use oxipng::deflate::Deflaters;
use oxipng::headers::Headers;
use oxipng::Options;
use regex::Regex;
use std::collections::HashSet;
use std::fs::DirBuilder;
use std::io::{Write, stderr};
use std::path::PathBuf;
fn main() {
@ -157,6 +157,10 @@ fn main() {
.possible_value("8k")
.possible_value("16k")
.possible_value("32k"))
.arg(Arg::with_name("alpha")
.help("Perform additional alpha optimizations")
.short("a")
.long("alpha"))
.arg(Arg::with_name("no-bit-reduction")
.help("No bit depth reduction")
.long("nb"))
@ -222,17 +226,19 @@ fn main() {
let opts = match parse_opts_into_struct(&matches) {
Ok(x) => x,
Err(x) => {
writeln!(&mut stderr(), "{}", x).ok();
eprintln!("{}", x);
return ();
}
};
handle_optimization(matches
.values_of("files")
.unwrap()
.map(PathBuf::from)
.collect(),
&opts);
handle_optimization(
matches
.values_of("files")
.unwrap()
.map(PathBuf::from)
.collect(),
&opts,
);
}
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();
if input.is_dir() {
if current_opts.recursive {
handle_optimization(input
.read_dir()
.unwrap()
.map(|x| x.unwrap().path())
.collect(),
&current_opts)
handle_optimization(
input
.read_dir()
.unwrap()
.map(|x| x.unwrap().path())
.collect(),
&current_opts,
)
} else {
writeln!(&mut stderr(),
"{} is a directory, skipping",
input.display())
.ok();
eprintln!("{} is a directory, skipping", input.display());
}
continue;
}
@ -262,7 +267,7 @@ fn handle_optimization(inputs: Vec<PathBuf>, opts: &Options) {
match oxipng::optimize(&input, &current_opts) {
Ok(_) => (),
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)),
};
} else if !path.is_dir() {
return Err(format!("{} is an existing file (not a directory), cannot create directory",
x));
return Err(format!(
"{} is an existing file (not a directory), cannot create directory",
x
));
}
opts.out_dir = Some(path);
}
@ -334,6 +341,14 @@ fn parse_opts_into_struct(matches: &ArgMatches) -> Result<Options, String> {
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") {
opts.backup = true;
}
@ -398,8 +413,10 @@ fn parse_opts_into_struct(matches: &ArgMatches) -> Result<Options, String> {
.collect::<Vec<String>>();
if hdrs.contains(&"safe".to_owned()) || hdrs.contains(&"all".to_owned()) {
if hdrs.len() > 1 {
return Err("'safe' or 'all' presets for --strip should be used by themselves"
.to_owned());
return Err(
"'safe' or 'all' presets for --strip should be used by themselves"
.to_owned(),
);
}
if hdrs[0] == "safe" {
opts.strip = Headers::Safe;
@ -432,18 +449,21 @@ fn parse_opts_into_struct(matches: &ArgMatches) -> Result<Options, String> {
Ok(opts)
}
fn parse_numeric_range_opts(input: &str,
min_value: u8,
max_value: u8)
-> Result<HashSet<u8>, String> {
fn parse_numeric_range_opts(
input: &str,
min_value: u8,
max_value: u8,
) -> Result<HashSet<u8>, String> {
let one_item = Regex::new(format!(r"^[{}-{}]$", min_value, max_value).as_ref()).unwrap();
let multiple_items = Regex::new(format!(r"^([{}-{}])(,|-)([{}-{}])$",
min_value,
max_value,
min_value,
max_value)
.as_ref())
.unwrap();
let multiple_items = Regex::new(
format!(
r"^([{}-{}])(,|-)([{}-{}])$",
min_value,
max_value,
min_value,
max_value
).as_ref(),
).unwrap();
let mut items = HashSet::new();
if one_item.is_match(input) {

View file

@ -1,6 +1,6 @@
use bit_vec::BitVec;
use byteorder::{BigEndian, WriteBytesExt};
use colors::{BitDepth, ColorType};
use colors::{BitDepth, ColorType, AlphaOptim};
use crc::crc32;
use deflate;
use error::PngError;
@ -15,6 +15,12 @@ use std::io::Read;
use std::iter::Iterator;
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)]
/// An iterator over the scan lines of a PNG image
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 *
self.png.channels_per_pixel() as u32;
self.png.channels_per_pixel() as u32;
let y_steps;
let pixels_factor;
match self.pass {
@ -129,23 +135,23 @@ impl<'a> Iterator for ScanLines<'a> {
}
}
Some(ScanLine {
filter: self.png.raw_data[self.start],
data: self.png.raw_data[(self.start + 1)..self.end].to_owned(),
pass: current_pass,
})
filter: self.png.raw_data[self.start],
data: self.png.raw_data[(self.start + 1)..self.end].to_owned(),
pass: current_pass,
})
} else {
// Standard, non-interlaced PNG scanlines
let bits_per_line = self.png.ihdr_data.width as usize *
self.png.ihdr_data.bit_depth.as_u8() as usize *
self.png.channels_per_pixel() as usize;
self.png.ihdr_data.bit_depth.as_u8() as usize *
self.png.channels_per_pixel() as usize;
let bytes_per_line = (bits_per_line as f32 / 8f32).ceil() as usize;
self.start = self.end;
self.end = self.start + bytes_per_line + 1;
Some(ScanLine {
filter: self.png.raw_data[self.start],
data: self.png.raw_data[(self.start + 1)..self.end].to_owned(),
pass: None,
})
filter: self.png.raw_data[self.start],
data: self.png.raw_data[(self.start + 1)..self.end].to_owned(),
pass: None,
})
}
}
}
@ -280,14 +286,14 @@ impl PngData {
}
#[doc(hidden)]
pub fn reset_from_original(&mut self, original: PngData) {
self.idat_data = original.idat_data;
pub fn reset_from_original(&mut self, original: &PngData) {
self.idat_data = original.idat_data.clone();
self.ihdr_data = original.ihdr_data;
self.raw_data = original.raw_data;
self.palette = original.palette;
self.transparency_pixel = original.transparency_pixel;
self.transparency_palette = original.transparency_palette;
self.aux_headers = original.aux_headers;
self.raw_data = original.raw_data.clone();
self.palette = original.palette.clone();
self.transparency_pixel = original.transparency_pixel.clone();
self.transparency_palette = original.transparency_palette.clone();
self.aux_headers = original.aux_headers.clone();
}
/// 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];
// IHDR
let mut ihdr_data = Vec::with_capacity(13);
ihdr_data.write_u32::<BigEndian>(self.ihdr_data.width).ok();
ihdr_data.write_u32::<BigEndian>(self.ihdr_data.height).ok();
ihdr_data.write_u8(self.ihdr_data.bit_depth.as_u8()).ok();
ihdr_data
.write_u8(self.ihdr_data.color_type.png_header_code())
.ok();
ihdr_data.write_u8(0).ok(); // Compression -- deflate
ihdr_data.write_u8(0).ok(); // Filter method -- 5-way adaptive filtering
ihdr_data.write_u8(self.ihdr_data.interlaced).ok();
let _ = ihdr_data.write_u32::<BigEndian>(self.ihdr_data.width);
let _ = ihdr_data.write_u32::<BigEndian>(self.ihdr_data.height);
let _ = ihdr_data.write_u8(self.ihdr_data.bit_depth.as_u8());
let _ = ihdr_data.write_u8(self.ihdr_data.color_type.png_header_code());
let _ = ihdr_data.write_u8(0); // Compression -- deflate
let _ = ihdr_data.write_u8(0); // Filter method -- 5-way adaptive filtering
let _ = ihdr_data.write_u8(self.ihdr_data.interlaced);
write_png_block(b"IHDR", &ihdr_data, &mut output);
// Ancillary headers
for (key, header) in
self.aux_headers
.iter()
.filter(|&(key, _)| !(*key == "bKGD" || *key == "hIST" || *key == "tRNS")) {
for (key, header) in self.aux_headers.iter().filter(|&(key, _)| {
!(*key == "bKGD" || *key == "hIST" || *key == "tRNS")
})
{
write_png_block(key.as_bytes(), header, &mut output);
}
// Palette
@ -336,10 +340,10 @@ impl PngData {
write_png_block(b"tRNS", transparency_pixel, &mut output);
}
// Special ancillary headers that need to come after PLTE but before IDAT
for (key, header) in
self.aux_headers
.iter()
.filter(|&(key, _)| *key == "bKGD" || *key == "hIST" || *key == "tRNS") {
for (key, header) in self.aux_headers.iter().filter(|&(key, _)| {
*key == "bKGD" || *key == "hIST" || *key == "tRNS"
})
{
write_png_block(key.as_bytes(), header, &mut output);
}
// IDAT data
@ -365,7 +369,7 @@ impl PngData {
pub fn unfilter_image(&self) -> Vec<u8> {
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) /
8f32)
8f32)
.ceil() as usize;
let mut last_line: Vec<u8> = Vec::new();
for line in self.scan_lines() {
@ -387,7 +391,7 @@ impl PngData {
pub fn filter_image(&self, filter: u8) -> Vec<u8> {
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) /
8f32)
8f32)
.ceil() as usize;
let mut last_line: Vec<u8> = Vec::new();
let mut last_pass: Option<u8> = None;
@ -396,8 +400,9 @@ impl PngData {
0 | 1 | 2 | 3 | 4 => {
if last_pass == line.pass || filter <= 1 {
filtered.push(filter);
filtered
.extend_from_slice(&filter_line(filter, bpp, &line.data, &last_line));
filtered.extend_from_slice(
&filter_line(filter, bpp, &line.data, &last_line),
);
} else {
// Avoid vertical filtering on first line of each interlacing pass
filtered.push(0);
@ -438,17 +443,18 @@ impl PngData {
pub fn reduce_bit_depth(&mut self) -> bool {
if self.ihdr_data.bit_depth != BitDepth::Sixteen {
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 false;
}
// Reduce from 16 to 8 bits per channel per pixel
let mut reduced = Vec::with_capacity((self.ihdr_data.width * self.ihdr_data.height *
self.channels_per_pixel() as u32 +
self.ihdr_data.height) as
usize);
let mut reduced = Vec::with_capacity(
(self.ihdr_data.width * self.ihdr_data.height * self.channels_per_pixel() as u32 +
self.ihdr_data.height) as usize,
);
let mut high_byte = 0;
for line in self.scan_lines() {
@ -494,20 +500,20 @@ impl PngData {
.chunks(3)
.zip(trns.iter().chain([255].iter().cycle()))
.flat_map(|(pixel, trns)| {
let mut pixel = pixel.to_owned();
pixel.push(*trns);
pixel
})
let mut pixel = pixel.to_owned();
pixel.push(*trns);
pixel
})
.collect()
} else {
self.palette.clone().unwrap()
};
let mut indexed_palette: Vec<&[u8]> = palette
.chunks(if self.transparency_palette.is_some() {
4
} else {
3
})
4
} else {
3
})
.collect();
// A map of old indexes to new ones, for any moved
let mut index_map: HashMap<u8, u8> = HashMap::new();
@ -591,10 +597,12 @@ impl PngData {
true
}
fn do_palette_reduction(&mut self,
indices: &[u8],
index_map: &mut HashMap<u8, u8>,
indexed_palette: &mut Vec<&[u8]>) {
fn do_palette_reduction(
&mut self,
indices: &[u8],
index_map: &mut HashMap<u8, u8>,
indexed_palette: &mut Vec<&[u8]>,
) {
let mut new_data = Vec::with_capacity(self.raw_data.len());
let original_len = indexed_palette.len();
for idx in indices.iter().sorted_by(|a, b| b.cmp(a)) {
@ -687,7 +695,9 @@ impl PngData {
.cloned()
.flatten()
.enumerate()
.filter(|&(i, _)| !(self.transparency_palette.is_some() && i % 4 == 3))
.filter(|&(i, _)| {
!(self.transparency_palette.is_some() && i % 4 == 3)
})
.map(|(_, x)| *x)
.collect::<Vec<u8>>();
self.palette = Some(new_palette);
@ -711,13 +721,15 @@ impl PngData {
}
if self.ihdr_data.color_type == ColorType::GrayscaleAlpha &&
reduce_grayscale_alpha_to_grayscale(self) {
reduce_grayscale_alpha_to_grayscale(self)
{
changed = true;
should_reduce_bit_depth = true;
}
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;
should_reduce_bit_depth = true;
}
@ -731,6 +743,174 @@ impl PngData {
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
/// Returns true if the interlacing was changed, false otherwise
/// 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(header);
output.reserve(header_data.len() + 8);
output
.write_u32::<BigEndian>(header_data.len() as u32 - 4)
.ok();
let _ = output.write_u32::<BigEndian>(header_data.len() as u32 - 4);
let crc = crc32::checksum_ieee(&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 +
if png.aux_headers
.contains_key(&"bKGD".to_string()) {
6
} else {
0
});
let mut color_palette = Vec::with_capacity(
palette.len() * 3 +
if png.aux_headers.contains_key(&"bKGD".to_string()) {
6
} else {
0
},
);
let mut trans_palette = Vec::with_capacity(palette.len());
for color in &palette {
for (i, byte) in color.iter().enumerate() {
@ -166,9 +167,10 @@ pub fn reduce_rgba_to_palette(png: &mut PngData) -> bool {
.step(2)
.cloned()
.collect::<Vec<u8>>();
if let Some(entry) = color_palette
.chunks(3)
.position(|x| x == header_pixels.as_slice()) {
if let Some(entry) = color_palette.chunks(3).position(
|x| x == header_pixels.as_slice(),
)
{
*bkgd_header = vec![entry as u8];
} else if color_palette.len() / 3 == 256 {
return false;
@ -244,9 +246,10 @@ pub fn reduce_rgb_to_palette(png: &mut PngData) -> bool {
.step(2)
.cloned()
.collect::<Vec<u8>>();
if let Some(entry) = color_palette
.chunks(3)
.position(|x| x == header_pixels.as_slice()) {
if let Some(entry) = color_palette.chunks(3).position(
|x| x == header_pixels.as_slice(),
)
{
*bkgd_header = vec![entry as u8];
} else if color_palette.len() == 255 {
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
}
fn test_it_converts(input: &Path,
output: &Path,
opts: &oxipng::Options,
color_type_in: ColorType,
bit_depth_in: BitDepth,
color_type_out: ColorType,
bit_depth_out: BitDepth) {
fn test_it_converts(
input: &Path,
output: &Path,
opts: &oxipng::Options,
color_type_in: ColorType,
bit_depth_in: BitDepth,
color_type_out: ColorType,
bit_depth_out: BitDepth,
) {
let png = png::PngData::new(input, opts.fix_errors).unwrap();
assert_eq!(png.ihdr_data.color_type, color_type_in);
@ -61,13 +63,15 @@ fn verbose_mode() {
opts.verbosity = Some(1);
let output = opts.out_file.clone();
test_it_converts(&input,
&output,
&opts,
ColorType::RGB,
BitDepth::Eight,
ColorType::RGB,
BitDepth::Eight);
test_it_converts(
&input,
&output,
&opts,
ColorType::RGB,
BitDepth::Eight,
ColorType::RGB,
BitDepth::Eight,
);
}
#[test]
@ -374,13 +378,15 @@ fn preserve_attrs() {
opts.preserve_attrs = true;
let output = opts.out_file.clone();
test_it_converts(&input,
&output,
&opts,
ColorType::RGB,
BitDepth::Eight,
ColorType::RGB,
BitDepth::Eight);
test_it_converts(
&input,
&output,
&opts,
ColorType::RGB,
BitDepth::Eight,
ColorType::RGB,
BitDepth::Eight,
);
// TODO: Actually check permissions
}
@ -425,11 +431,13 @@ fn zopfli_mode() {
opts.deflate = Deflaters::Zopfli;
let output = opts.out_file.clone();
test_it_converts(&input,
&output,
&opts,
ColorType::RGB,
BitDepth::Eight,
ColorType::RGB,
BitDepth::Eight);
test_it_converts(
&input,
&output,
&opts,
ColorType::RGB,
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
}
fn test_it_converts(input: &Path,
output: &Path,
opts: &oxipng::Options,
color_type_in: ColorType,
bit_depth_in: BitDepth,
color_type_out: ColorType,
bit_depth_out: BitDepth) {
fn test_it_converts(
input: &Path,
output: &Path,
opts: &oxipng::Options,
color_type_in: ColorType,
bit_depth_in: BitDepth,
color_type_out: ColorType,
bit_depth_out: BitDepth,
) {
let png = png::PngData::new(input, opts.fix_errors).unwrap();
assert_eq!(png.ihdr_data.color_type, color_type_in);
@ -58,13 +60,15 @@ fn issue_29() {
let opts = get_opts(&input);
let output = opts.out_file.clone();
test_it_converts(&input,
&output,
&opts,
ColorType::RGB,
BitDepth::Eight,
ColorType::RGB,
BitDepth::Eight);
test_it_converts(
&input,
&output,
&opts,
ColorType::RGB,
BitDepth::Eight,
ColorType::RGB,
BitDepth::Eight,
);
}
#[test]
@ -107,13 +111,15 @@ fn issue_52_01() {
let opts = get_opts(&input);
let output = opts.out_file.clone();
test_it_converts(&input,
&output,
&opts,
ColorType::RGBA,
BitDepth::Eight,
ColorType::Indexed,
BitDepth::Eight);
test_it_converts(
&input,
&output,
&opts,
ColorType::RGBA,
BitDepth::Eight,
ColorType::Indexed,
BitDepth::Eight,
);
}
#[test]
@ -122,13 +128,15 @@ fn issue_52_02() {
let opts = get_opts(&input);
let output = opts.out_file.clone();
test_it_converts(&input,
&output,
&opts,
ColorType::RGBA,
BitDepth::Eight,
ColorType::Indexed,
BitDepth::Eight);
test_it_converts(
&input,
&output,
&opts,
ColorType::RGBA,
BitDepth::Eight,
ColorType::Indexed,
BitDepth::Eight,
);
}
#[test]
@ -137,13 +145,15 @@ fn issue_52_03() {
let opts = get_opts(&input);
let output = opts.out_file.clone();
test_it_converts(&input,
&output,
&opts,
ColorType::RGBA,
BitDepth::Eight,
ColorType::Indexed,
BitDepth::Eight);
test_it_converts(
&input,
&output,
&opts,
ColorType::RGBA,
BitDepth::Eight,
ColorType::Indexed,
BitDepth::Eight,
);
}
#[test]
@ -152,13 +162,15 @@ fn issue_52_04() {
let opts = get_opts(&input);
let output = opts.out_file.clone();
test_it_converts(&input,
&output,
&opts,
ColorType::RGBA,
BitDepth::Eight,
ColorType::Indexed,
BitDepth::One);
test_it_converts(
&input,
&output,
&opts,
ColorType::RGBA,
BitDepth::Eight,
ColorType::Indexed,
BitDepth::One,
);
}
#[test]
@ -167,13 +179,15 @@ fn issue_52_05() {
let opts = get_opts(&input);
let output = opts.out_file.clone();
test_it_converts(&input,
&output,
&opts,
ColorType::RGBA,
BitDepth::Eight,
ColorType::Indexed,
BitDepth::One);
test_it_converts(
&input,
&output,
&opts,
ColorType::RGBA,
BitDepth::Eight,
ColorType::Indexed,
BitDepth::One,
);
}
#[test]
@ -182,13 +196,15 @@ fn issue_52_06() {
let opts = get_opts(&input);
let output = opts.out_file.clone();
test_it_converts(&input,
&output,
&opts,
ColorType::RGBA,
BitDepth::Eight,
ColorType::Indexed,
BitDepth::Two);
test_it_converts(
&input,
&output,
&opts,
ColorType::RGBA,
BitDepth::Eight,
ColorType::Indexed,
BitDepth::Two,
);
}
#[test]
@ -197,13 +213,15 @@ fn issue_56() {
let opts = get_opts(&input);
let output = opts.out_file.clone();
test_it_converts(&input,
&output,
&opts,
ColorType::Indexed,
BitDepth::Four,
ColorType::Indexed,
BitDepth::Four);
test_it_converts(
&input,
&output,
&opts,
ColorType::Indexed,
BitDepth::Four,
ColorType::Indexed,
BitDepth::Four,
);
}
#[test]
@ -212,13 +230,15 @@ fn issue_58() {
let opts = get_opts(&input);
let output = opts.out_file.clone();
test_it_converts(&input,
&output,
&opts,
ColorType::Indexed,
BitDepth::Four,
ColorType::Indexed,
BitDepth::Four);
test_it_converts(
&input,
&output,
&opts,
ColorType::Indexed,
BitDepth::Four,
ColorType::Indexed,
BitDepth::Four,
);
}
#[test]
@ -227,13 +247,15 @@ fn issue_59() {
let opts = get_opts(&input);
let output = opts.out_file.clone();
test_it_converts(&input,
&output,
&opts,
ColorType::RGBA,
BitDepth::Eight,
ColorType::RGBA,
BitDepth::Eight);
test_it_converts(
&input,
&output,
&opts,
ColorType::RGBA,
BitDepth::Eight,
ColorType::RGBA,
BitDepth::Eight,
);
}
#[test]
@ -242,11 +264,13 @@ fn issue_60() {
let opts = get_opts(&input);
let output = opts.out_file.clone();
test_it_converts(&input,
&output,
&opts,
ColorType::RGBA,
BitDepth::Eight,
ColorType::GrayscaleAlpha,
BitDepth::Eight);
test_it_converts(
&input,
&output,
&opts,
ColorType::RGBA,
BitDepth::Eight,
ColorType::GrayscaleAlpha,
BitDepth::Eight,
);
}