Version 2.0.0

This commit is contained in:
Josh Holmer 2018-07-20 19:19:20 -04:00
parent 879ca01042
commit d110949d2a
25 changed files with 337 additions and 216 deletions

View file

@ -1,3 +1,16 @@
### Version 2.0.0
- [SEMVER_MAJOR] Make PngError a proper Error enum
- [SEMVER_MINOR] Bump minimum Rust version to 1.27.0
- [SEMVER_MINOR] Make Rayon an optional dependency (enabled by default)
- [SEMVER_MINOR] Option to limit wall clock time spent in optimization trials
- [SEMVER_MINOR] `--keep` option (works similar to `--strip`, but takes a comma-separated list of headers to keep, and removes all other non-critical headers)
- Use faster Cloudflare zlib compression on platforms that support it
- Allow specifying more than 2 filter types via the CLI
- Avoid double glob processing on unix
- Fix reading from stdin
- Cleanup help text
- Various performance improvements
### Version 1.0.4 ### Version 1.0.4
- Bump `image` to 0.19.0 - Bump `image` to 0.19.0
- Bump `bit-vec` to 0.5.0 - Bump `bit-vec` to 0.5.0

2
Cargo.lock generated
View file

@ -265,7 +265,7 @@ dependencies = [
[[package]] [[package]]
name = "oxipng" name = "oxipng"
version = "1.0.4" version = "2.0.0"
dependencies = [ dependencies = [
"bit-vec 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "bit-vec 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
"byteorder 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)",

View file

@ -14,7 +14,7 @@ homepage = "https://github.com/shssoichiro/oxipng"
license = "MIT" license = "MIT"
name = "oxipng" name = "oxipng"
repository = "https://github.com/shssoichiro/oxipng" repository = "https://github.com/shssoichiro/oxipng"
version = "1.0.4" version = "2.0.0"
[badges] [badges]
travis-ci = { repository = "shssoichiro/oxipng", branch = "master" } travis-ci = { repository = "shssoichiro/oxipng", branch = "master" }

View file

@ -94,39 +94,39 @@ Oxipng is open-source software, distributed under the MIT license.
## Benchmarks ## Benchmarks
Tested oxipng 1.0.3 (compiled on rustc 1.25.0 (84203cac6 2018-03-25)) against OptiPNG version 0.7.7 on Intel(R) Core(TM) i7-6700HQ CPU @ 2.60GHz with 8 logical cores Tested oxipng 2.0.0 (compiled on rustc 1.27.2 (58cc626de 2018-07-18)) against OptiPNG version 0.7.7 on Intel(R) Core(TM) i7-4770 CPU @ 3.40GHz with 8 logical cores
Benchmark #1: ./target/release/oxipng -P ./tests/files/rgb_16_should_be_grayscale_8.png Benchmark #1: ./target/release/oxipng -P ./tests/files/rgb_16_should_be_grayscale_8.png
Time (mean ± σ): 201.1 ms ± 4.1 ms [User: 340.1 ms, System: 42.8 ms] Time (mean ± σ): 91.2 ms ± 2.4 ms [User: 173.7 ms, System: 15.7 ms]
Range (min … max): 195.3 ms … 210.1 ms Range (min … max): 86.4 ms … 97.7 ms
Benchmark #2: optipng -simulate ./tests/files/rgb_16_should_be_grayscale_8.png Benchmark #2: optipng -simulate ./tests/files/rgb_16_should_be_grayscale_8.png
Time (mean ± σ): 418.8 ms ± 4.6 ms [User: 415.0 ms, System: 2.7 ms] Time (mean ± σ): 281.0 ms ± 2.1 ms [User: 280.4 ms, System: 0.8 ms]
Range (min … max): 411.0 ms … 425.4 ms Range (min … max): 279.3 ms … 286.4 ms
Summary Summary
'./target/release/oxipng -P ./tests/files/rgb_16_should_be_grayscale_8.png' ran './target/release/oxipng -P ./tests/files/rgb_16_should_be_grayscale_8.png' ran
2.08x faster than 'optipng -simulate ./tests/files/rgb_16_should_be_grayscale_8.png' 3.08x faster than 'optipng -simulate ./tests/files/rgb_16_should_be_grayscale_8.png'
Benchmark #1: ./target/release/oxipng -o4 -P ./tests/files/rgb_16_should_be_grayscale_8.png Benchmark #1: ./target/release/oxipng -o4 -P ./tests/files/rgb_16_should_be_grayscale_8.png
Time (mean ± σ): 330.9 ms ± 10.3 ms [User: 1.062 s, System: 0.045 s] Time (mean ± σ): 116.5 ms ± 3.0 ms [User: 438.8 ms, System: 20.0 ms]
Range (min … max): 315.3 ms … 344.0 ms Range (min … max): 111.6 ms … 122.3 ms
Benchmark #2: optipng -o 4 -simulate ./tests/files/rgb_16_should_be_grayscale_8.png Benchmark #2: optipng -o 4 -simulate ./tests/files/rgb_16_should_be_grayscale_8.png
Time (mean ± σ): 1.424 s ± 0.011 s [User: 1.418 s, System: 0.002 s] Time (mean ± σ): 942.6 ms ± 1.4 ms [User: 939.3 ms, System: 2.7 ms]
Range (min … max): 1.412 s … 1.448 s Range (min … max): 941.0 ms … 945.5 ms
Summary Summary
'./target/release/oxipng -o4 -P ./tests/files/rgb_16_should_be_grayscale_8.png' ran './target/release/oxipng -o4 -P ./tests/files/rgb_16_should_be_grayscale_8.png' ran
4.30x faster than 'optipng -o 4 -simulate ./tests/files/rgb_16_should_be_grayscale_8.png' 8.09x faster than 'optipng -o 4 -simulate ./tests/files/rgb_16_should_be_grayscale_8.png'

View file

@ -4,8 +4,8 @@ extern crate oxipng;
extern crate test; extern crate test;
use oxipng::internal_tests::*; use oxipng::internal_tests::*;
use test::Bencher;
use std::path::PathBuf; use std::path::PathBuf;
use test::Bencher;
#[bench] #[bench]
fn deflate_16_bits_strategy_0(b: &mut Bencher) { fn deflate_16_bits_strategy_0(b: &mut Bencher) {
@ -256,7 +256,5 @@ fn inflate_generic(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png")); let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png"));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, false).unwrap();
b.iter(|| { b.iter(|| inflate(png.idat_data.as_ref()));
inflate(png.idat_data.as_ref())
});
} }

View file

@ -4,8 +4,8 @@ extern crate oxipng;
extern crate test; extern crate test;
use oxipng::internal_tests::*; use oxipng::internal_tests::*;
use test::Bencher;
use std::path::PathBuf; use std::path::PathBuf;
use test::Bencher;
#[bench] #[bench]
fn filters_16_bits_filter_0(b: &mut Bencher) { fn filters_16_bits_filter_0(b: &mut Bencher) {

View file

@ -4,8 +4,8 @@ extern crate oxipng;
extern crate test; extern crate test;
use oxipng::internal_tests::*; use oxipng::internal_tests::*;
use test::Bencher;
use std::path::PathBuf; use std::path::PathBuf;
use test::Bencher;
#[bench] #[bench]
fn interlacing_16_bits(b: &mut Bencher) { fn interlacing_16_bits(b: &mut Bencher) {

View file

@ -4,8 +4,8 @@ extern crate oxipng;
extern crate test; extern crate test;
use oxipng::internal_tests::*; use oxipng::internal_tests::*;
use test::Bencher;
use std::path::PathBuf; use std::path::PathBuf;
use test::Bencher;
#[bench] #[bench]
fn reductions_16_to_8_bits(b: &mut Bencher) { fn reductions_16_to_8_bits(b: &mut Bencher) {

View file

@ -4,8 +4,8 @@ extern crate oxipng;
extern crate test; extern crate test;
use oxipng::internal_tests::*; use oxipng::internal_tests::*;
use test::Bencher;
use std::path::PathBuf; use std::path::PathBuf;
use test::Bencher;
#[bench] #[bench]
fn zopfli_16_bits_strategy_0(b: &mut Bencher) { fn zopfli_16_bits_strategy_0(b: &mut Bencher) {

104
package-lock.json generated
View file

@ -34,8 +34,8 @@
"integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=",
"dev": true, "dev": true,
"requires": { "requires": {
"camelcase": "2.1.1", "camelcase": "^2.0.0",
"map-obj": "1.0.1" "map-obj": "^1.0.0"
} }
}, },
"currently-unhandled": { "currently-unhandled": {
@ -44,7 +44,7 @@
"integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=",
"dev": true, "dev": true,
"requires": { "requires": {
"array-find-index": "1.0.2" "array-find-index": "^1.0.1"
} }
}, },
"decamelize": { "decamelize": {
@ -59,7 +59,7 @@
"integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=",
"dev": true, "dev": true,
"requires": { "requires": {
"is-arrayish": "0.2.1" "is-arrayish": "^0.2.1"
} }
}, },
"find-up": { "find-up": {
@ -68,8 +68,8 @@
"integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=",
"dev": true, "dev": true,
"requires": { "requires": {
"path-exists": "2.1.0", "path-exists": "^2.0.0",
"pinkie-promise": "2.0.1" "pinkie-promise": "^2.0.0"
} }
}, },
"get-stdin": { "get-stdin": {
@ -96,7 +96,7 @@
"integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=",
"dev": true, "dev": true,
"requires": { "requires": {
"repeating": "2.0.1" "repeating": "^2.0.0"
} }
}, },
"is-arrayish": { "is-arrayish": {
@ -111,7 +111,7 @@
"integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=",
"dev": true, "dev": true,
"requires": { "requires": {
"builtin-modules": "1.1.1" "builtin-modules": "^1.0.0"
} }
}, },
"is-finite": { "is-finite": {
@ -120,7 +120,7 @@
"integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=",
"dev": true, "dev": true,
"requires": { "requires": {
"number-is-nan": "1.0.1" "number-is-nan": "^1.0.0"
} }
}, },
"is-utf8": { "is-utf8": {
@ -135,11 +135,11 @@
"integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
"dev": true, "dev": true,
"requires": { "requires": {
"graceful-fs": "4.1.11", "graceful-fs": "^4.1.2",
"parse-json": "2.2.0", "parse-json": "^2.2.0",
"pify": "2.3.0", "pify": "^2.0.0",
"pinkie-promise": "2.0.1", "pinkie-promise": "^2.0.0",
"strip-bom": "2.0.0" "strip-bom": "^2.0.0"
} }
}, },
"loud-rejection": { "loud-rejection": {
@ -148,8 +148,8 @@
"integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=",
"dev": true, "dev": true,
"requires": { "requires": {
"currently-unhandled": "0.4.1", "currently-unhandled": "^0.4.1",
"signal-exit": "3.0.2" "signal-exit": "^3.0.0"
} }
}, },
"map-obj": { "map-obj": {
@ -164,16 +164,16 @@
"integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=",
"dev": true, "dev": true,
"requires": { "requires": {
"camelcase-keys": "2.1.0", "camelcase-keys": "^2.0.0",
"decamelize": "1.2.0", "decamelize": "^1.1.2",
"loud-rejection": "1.6.0", "loud-rejection": "^1.0.0",
"map-obj": "1.0.1", "map-obj": "^1.0.1",
"minimist": "1.2.0", "minimist": "^1.1.3",
"normalize-package-data": "2.4.0", "normalize-package-data": "^2.3.4",
"object-assign": "4.1.1", "object-assign": "^4.0.1",
"read-pkg-up": "1.0.1", "read-pkg-up": "^1.0.1",
"redent": "1.0.0", "redent": "^1.0.0",
"trim-newlines": "1.0.0" "trim-newlines": "^1.0.0"
} }
}, },
"minimist": { "minimist": {
@ -188,10 +188,10 @@
"integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==",
"dev": true, "dev": true,
"requires": { "requires": {
"hosted-git-info": "2.5.0", "hosted-git-info": "^2.1.4",
"is-builtin-module": "1.0.0", "is-builtin-module": "^1.0.0",
"semver": "5.5.0", "semver": "2 || 3 || 4 || 5",
"validate-npm-package-license": "3.0.1" "validate-npm-package-license": "^3.0.1"
} }
}, },
"number-is-nan": { "number-is-nan": {
@ -212,7 +212,7 @@
"integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=",
"dev": true, "dev": true,
"requires": { "requires": {
"error-ex": "1.3.1" "error-ex": "^1.2.0"
} }
}, },
"path-exists": { "path-exists": {
@ -221,7 +221,7 @@
"integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=",
"dev": true, "dev": true,
"requires": { "requires": {
"pinkie-promise": "2.0.1" "pinkie-promise": "^2.0.0"
} }
}, },
"path-type": { "path-type": {
@ -230,9 +230,9 @@
"integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=",
"dev": true, "dev": true,
"requires": { "requires": {
"graceful-fs": "4.1.11", "graceful-fs": "^4.1.2",
"pify": "2.3.0", "pify": "^2.0.0",
"pinkie-promise": "2.0.1" "pinkie-promise": "^2.0.0"
} }
}, },
"pify": { "pify": {
@ -253,7 +253,7 @@
"integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
"dev": true, "dev": true,
"requires": { "requires": {
"pinkie": "2.0.4" "pinkie": "^2.0.0"
} }
}, },
"read-pkg": { "read-pkg": {
@ -262,9 +262,9 @@
"integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=",
"dev": true, "dev": true,
"requires": { "requires": {
"load-json-file": "1.1.0", "load-json-file": "^1.0.0",
"normalize-package-data": "2.4.0", "normalize-package-data": "^2.3.2",
"path-type": "1.1.0" "path-type": "^1.0.0"
} }
}, },
"read-pkg-up": { "read-pkg-up": {
@ -273,8 +273,8 @@
"integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=",
"dev": true, "dev": true,
"requires": { "requires": {
"find-up": "1.1.2", "find-up": "^1.0.0",
"read-pkg": "1.1.0" "read-pkg": "^1.0.0"
} }
}, },
"redent": { "redent": {
@ -283,8 +283,8 @@
"integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=",
"dev": true, "dev": true,
"requires": { "requires": {
"indent-string": "2.1.0", "indent-string": "^2.1.0",
"strip-indent": "1.0.1" "strip-indent": "^1.0.1"
} }
}, },
"repeating": { "repeating": {
@ -293,7 +293,7 @@
"integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=",
"dev": true, "dev": true,
"requires": { "requires": {
"is-finite": "1.0.2" "is-finite": "^1.0.0"
} }
}, },
"semver": { "semver": {
@ -314,7 +314,7 @@
"integrity": "sha1-SzBz2TP/UfORLwOsVRlJikFQ20A=", "integrity": "sha1-SzBz2TP/UfORLwOsVRlJikFQ20A=",
"dev": true, "dev": true,
"requires": { "requires": {
"spdx-license-ids": "1.2.2" "spdx-license-ids": "^1.0.2"
} }
}, },
"spdx-expression-parse": { "spdx-expression-parse": {
@ -335,7 +335,7 @@
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
"dev": true, "dev": true,
"requires": { "requires": {
"ansi-regex": "3.0.0" "ansi-regex": "^3.0.0"
} }
}, },
"strip-ansi-cli": { "strip-ansi-cli": {
@ -344,8 +344,8 @@
"integrity": "sha1-D91Cq86DJTm/cv+ROSpxqzVGIIw=", "integrity": "sha1-D91Cq86DJTm/cv+ROSpxqzVGIIw=",
"dev": true, "dev": true,
"requires": { "requires": {
"meow": "3.7.0", "meow": "^3.7.0",
"strip-ansi": "4.0.0" "strip-ansi": "^4.0.0"
} }
}, },
"strip-bom": { "strip-bom": {
@ -354,7 +354,7 @@
"integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=",
"dev": true, "dev": true,
"requires": { "requires": {
"is-utf8": "0.2.1" "is-utf8": "^0.2.0"
} }
}, },
"strip-indent": { "strip-indent": {
@ -363,7 +363,7 @@
"integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=",
"dev": true, "dev": true,
"requires": { "requires": {
"get-stdin": "4.0.1" "get-stdin": "^4.0.1"
} }
}, },
"trim-newlines": { "trim-newlines": {
@ -378,8 +378,8 @@
"integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w=", "integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w=",
"dev": true, "dev": true,
"requires": { "requires": {
"spdx-correct": "1.0.2", "spdx-correct": "~1.0.0",
"spdx-expression-parse": "1.0.4" "spdx-expression-parse": "~1.0.0"
} }
} }
} }

View file

@ -1,5 +1,5 @@
use std::sync::atomic::AtomicUsize; use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering::{SeqCst, Relaxed}; use std::sync::atomic::Ordering::{Relaxed, SeqCst};
pub struct AtomicMin { pub struct AtomicMin {
val: AtomicUsize, val: AtomicUsize,
@ -8,20 +8,26 @@ pub struct AtomicMin {
impl AtomicMin { impl AtomicMin {
pub fn new(init: Option<usize>) -> Self { pub fn new(init: Option<usize>) -> Self {
Self { Self {
val: AtomicUsize::new(init.unwrap_or(usize::max_value())) val: AtomicUsize::new(init.unwrap_or(usize::max_value())),
} }
} }
pub fn get(&self) -> Option<usize> { pub fn get(&self) -> Option<usize> {
let val = self.val.load(SeqCst); let val = self.val.load(SeqCst);
if val == usize::max_value() {None} else {Some(val)} if val == usize::max_value() {
None
} else {
Some(val)
}
} }
pub fn set_min(&self, new_val: usize) { pub fn set_min(&self, new_val: usize) {
let mut current_val = self.val.load(Relaxed); let mut current_val = self.val.load(Relaxed);
loop { loop {
if new_val < current_val { if new_val < current_val {
if let Err(v) = self.val.compare_exchange(current_val, new_val, SeqCst, Relaxed) { if let Err(v) = self.val
.compare_exchange(current_val, new_val, SeqCst, Relaxed)
{
current_val = v; current_val = v;
continue; continue;
} }

View file

@ -2,7 +2,13 @@ use atomicmin::AtomicMin;
use error::PngError; use error::PngError;
use miniz_oxide::deflate::core::*; use miniz_oxide::deflate::core::*;
pub fn compress_to_vec_oxipng(input: &[u8], level: u8, window_bits: i32, strategy: i32, max_size: &AtomicMin) -> Result<Vec<u8>, PngError> { pub fn compress_to_vec_oxipng(
input: &[u8],
level: u8,
window_bits: i32,
strategy: i32,
max_size: &AtomicMin,
) -> Result<Vec<u8>, PngError> {
// The comp flags function sets the zlib flag if the window_bits parameter is > 0. // The comp flags function sets the zlib flag if the window_bits parameter is > 0.
let flags = create_comp_flags_from_zip_params(level.into(), window_bits, strategy); let flags = create_comp_flags_from_zip_params(level.into(), window_bits, strategy);
let mut compressor = CompressorOxide::new(flags); let mut compressor = CompressorOxide::new(flags);
@ -35,7 +41,7 @@ pub fn compress_to_vec_oxipng(input: &[u8], level: u8, window_bits: i32, strateg
TDEFLStatus::Okay => { TDEFLStatus::Okay => {
if let Some(max) = max_size.get() { if let Some(max) = max_size.get() {
if output.len() > max { if output.len() > max {
return Err(PngError::DeflatedDataTooLong(output.len())) return Err(PngError::DeflatedDataTooLong(output.len()));
} }
} }
// We need more space, so extend the vector. // We need more space, so extend the vector.

View file

@ -14,21 +14,29 @@ pub fn inflate(data: &[u8]) -> Result<Vec<u8>, PngError> {
} }
/// Compress a data stream using the DEFLATE algorithm /// Compress a data stream using the DEFLATE algorithm
pub fn deflate(data: &[u8], zc: u8, zs: u8, zw: u8, max_size: &AtomicMin) -> Result<Vec<u8>, PngError> { pub fn deflate(
data: &[u8],
zc: u8,
zs: u8,
zw: u8,
max_size: &AtomicMin,
) -> Result<Vec<u8>, PngError> {
if is_cfzlib_supported() { if is_cfzlib_supported() {
return cfzlib_deflate(data, zc, zs, zw, max_size) return cfzlib_deflate(data, zc, zs, zw, max_size);
} }
miniz_stream::compress_to_vec_oxipng(data, zc, zw.into(), zs.into(), max_size) miniz_stream::compress_to_vec_oxipng(data, zc, zw.into(), zs.into(), max_size)
} }
fn is_cfzlib_supported() -> bool { fn is_cfzlib_supported() -> bool {
#[cfg(target_arch = "x86_64")] { #[cfg(target_arch = "x86_64")]
{
if is_x86_feature_detected!("sse4.2") && is_x86_feature_detected!("pclmulqdq") { if is_x86_feature_detected!("sse4.2") && is_x86_feature_detected!("pclmulqdq") {
return true; return true;
} }
} }
#[cfg(target_arch = "aarch64")] { #[cfg(target_arch = "aarch64")]
{
if is_arm_feature_detected!("neon") && is_arm_feature_detected!("crc") { if is_arm_feature_detected!("neon") && is_arm_feature_detected!("crc") {
return true; return true;
} }
@ -36,20 +44,27 @@ fn is_cfzlib_supported() -> bool {
false false
} }
pub fn cfzlib_deflate(data: &[u8], level: u8, strategy: u8, window_bits: u8, max_size: &AtomicMin) -> Result<Vec<u8>, PngError> { pub fn cfzlib_deflate(
use std::mem; data: &[u8],
level: u8,
strategy: u8,
window_bits: u8,
max_size: &AtomicMin,
) -> Result<Vec<u8>, PngError> {
use cloudflare_zlib_sys::*; use cloudflare_zlib_sys::*;
use std::mem;
assert!(data.len() < u32::max_value() as usize); assert!(data.len() < u32::max_value() as usize);
unsafe { unsafe {
let mut stream = mem::zeroed(); let mut stream = mem::zeroed();
if Z_OK != deflateInit2( if Z_OK != deflateInit2(
&mut stream, &mut stream,
level.into(), level.into(),
Z_DEFLATED, Z_DEFLATED,
window_bits.into(), window_bits.into(),
MAX_MEM_LEVEL, MAX_MEM_LEVEL,
strategy.into()) { strategy.into(),
) {
return Err(PngError::new("deflateInit2")); return Err(PngError::new("deflateInit2"));
} }
@ -65,7 +80,7 @@ pub fn cfzlib_deflate(data: &[u8], level: u8, strategy: u8, window_bits: u8, max
stream.next_out = out.as_mut_ptr(); stream.next_out = out.as_mut_ptr();
stream.avail_out = out.capacity() as uInt; stream.avail_out = out.capacity() as uInt;
match deflate(&mut stream, Z_FINISH) { match deflate(&mut stream, Z_FINISH) {
Z_STREAM_END => {}, Z_STREAM_END => {}
Z_OK => return Err(PngError::DeflatedDataTooLong(stream.total_out as usize)), Z_OK => return Err(PngError::DeflatedDataTooLong(stream.total_out as usize)),
_ => return Err(PngError::new("deflate")), _ => return Err(PngError::new("deflate")),
} }

View file

@ -30,7 +30,9 @@ impl fmt::Display for PngError {
PngError::DeflatedDataTooLong(_) => f.write_str("deflated data too long"), PngError::DeflatedDataTooLong(_) => f.write_str("deflated data too long"),
PngError::NotPNG => f.write_str("Invalid header detected; Not a PNG file"), PngError::NotPNG => f.write_str("Invalid header detected; Not a PNG file"),
PngError::InvalidData => f.write_str("Invalid data found; unable to read PNG file"), PngError::InvalidData => f.write_str("Invalid data found; unable to read PNG file"),
PngError::TruncatedData => f.write_str("Missing data in the file; the file is truncated"), PngError::TruncatedData => {
f.write_str("Missing data in the file; the file is truncated")
}
PngError::APNGNotSupported => f.write_str("APNG files are not (yet) supported"), PngError::APNGNotSupported => f.write_str("APNG files are not (yet) supported"),
PngError::ChunkMissing(s) => write!(f, "Chunk {} missing or empty", s), PngError::ChunkMissing(s) => write!(f, "Chunk {} missing or empty", s),
PngError::Other(ref s) => f.write_str(s), PngError::Other(ref s) => f.write_str(s),

View file

@ -1,8 +1,8 @@
use std::collections::HashSet;
use byteorder::{BigEndian, ReadBytesExt}; use byteorder::{BigEndian, ReadBytesExt};
use colors::{BitDepth, ColorType}; use colors::{BitDepth, ColorType};
use crc::crc32; use crc::crc32;
use error::PngError; use error::PngError;
use std::collections::HashSet;
use std::io::Cursor; use std::io::Cursor;
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
@ -50,26 +50,36 @@ pub fn parse_next_header<'a>(
byte_data: &'a [u8], byte_data: &'a [u8],
byte_offset: &mut usize, byte_offset: &mut usize,
fix_errors: bool, fix_errors: bool,
) -> Result<Option<(&'a [u8], &'a [u8])>, PngError> { ) -> Result<Option<(&'a [u8], &'a [u8])>, PngError> {
let mut rdr = Cursor::new(byte_data.get(*byte_offset..*byte_offset + 4).ok_or(PngError::TruncatedData)?); let mut rdr = Cursor::new(byte_data
.get(*byte_offset..*byte_offset + 4)
.ok_or(PngError::TruncatedData)?);
let length = rdr.read_u32::<BigEndian>().unwrap(); let length = rdr.read_u32::<BigEndian>().unwrap();
*byte_offset += 4; *byte_offset += 4;
let header_start = *byte_offset; let header_start = *byte_offset;
let chunk_name = byte_data.get(header_start..header_start + 4).ok_or(PngError::TruncatedData)?; let chunk_name = byte_data
.get(header_start..header_start + 4)
.ok_or(PngError::TruncatedData)?;
if chunk_name == b"IEND" { if chunk_name == b"IEND" {
// End of data // End of data
return Ok(None); return Ok(None);
} }
*byte_offset += 4; *byte_offset += 4;
let data = byte_data.get(*byte_offset..*byte_offset + length as usize).ok_or(PngError::TruncatedData)?; let data = byte_data
.get(*byte_offset..*byte_offset + length as usize)
.ok_or(PngError::TruncatedData)?;
*byte_offset += length as usize; *byte_offset += length as usize;
let mut rdr = Cursor::new(byte_data.get(*byte_offset..*byte_offset + 4).ok_or(PngError::TruncatedData)?); let mut rdr = Cursor::new(byte_data
.get(*byte_offset..*byte_offset + 4)
.ok_or(PngError::TruncatedData)?);
let crc = rdr.read_u32::<BigEndian>().unwrap(); let crc = rdr.read_u32::<BigEndian>().unwrap();
*byte_offset += 4; *byte_offset += 4;
let header_bytes = byte_data.get(header_start..header_start + 4 + length as usize).ok_or(PngError::TruncatedData)?; let header_bytes = byte_data
.get(header_start..header_start + 4 + length as usize)
.ok_or(PngError::TruncatedData)?;
if !fix_errors && crc32::checksum_ieee(header_bytes) != crc { if !fix_errors && crc32::checksum_ieee(header_bytes) != crc {
return Err(PngError::new(&format!( return Err(PngError::new(&format!(
"CRC Mismatch in {} header; May be recoverable by using --fix", "CRC Mismatch in {} header; May be recoverable by using --fix",

View file

@ -1,5 +1,6 @@
extern crate bit_vec; extern crate bit_vec;
extern crate byteorder; extern crate byteorder;
extern crate cloudflare_zlib_sys;
extern crate crc; extern crate crc;
extern crate image; extern crate image;
extern crate itertools; extern crate itertools;
@ -8,8 +9,8 @@ extern crate num_cpus;
#[cfg(feature = "parallel")] #[cfg(feature = "parallel")]
extern crate rayon; extern crate rayon;
extern crate zopfli; extern crate zopfli;
extern crate cloudflare_zlib_sys;
use atomicmin::AtomicMin;
use image::{DynamicImage, GenericImage, ImageFormat, Pixel}; use image::{DynamicImage, GenericImage, ImageFormat, Pixel};
use png::PngData; use png::PngData;
#[cfg(feature = "parallel")] #[cfg(feature = "parallel")]
@ -19,15 +20,15 @@ use std::fmt;
use std::fs::{copy, File}; use std::fs::{copy, File};
use std::io::{stdin, stdout, BufWriter, Read, Write}; use std::io::{stdin, stdout, BufWriter, Read, Write};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::time::{Duration, Instant};
use std::sync::Mutex; use std::sync::Mutex;
use atomicmin::AtomicMin; use std::time::{Duration, Instant};
pub use colors::AlphaOptim; pub use colors::AlphaOptim;
pub use deflate::Deflaters; pub use deflate::Deflaters;
pub use error::PngError; pub use error::PngError;
pub use headers::Headers; pub use headers::Headers;
mod atomicmin;
mod colors; mod colors;
mod deflate; mod deflate;
mod error; mod error;
@ -36,14 +37,13 @@ mod headers;
mod interlace; mod interlace;
mod png; mod png;
mod reduction; mod reduction;
mod atomicmin;
/// Private to oxipng; don't use outside tests and benches /// Private to oxipng; don't use outside tests and benches
#[doc(hidden)] #[doc(hidden)]
pub mod internal_tests { pub mod internal_tests {
pub use atomicmin::*; pub use atomicmin::*;
pub use deflate::*;
pub use colors::*; pub use colors::*;
pub use deflate::*;
pub use headers::*; pub use headers::*;
pub use png::*; pub use png::*;
} }
@ -338,7 +338,8 @@ pub fn optimize(input: &InFile, output: &OutFile, opts: &Options) -> Result<(),
InFile::Path(ref input_path) => PngData::read_file(input_path)?, InFile::Path(ref input_path) => PngData::read_file(input_path)?,
InFile::StdIn => { InFile::StdIn => {
let mut data = Vec::new(); let mut data = Vec::new();
stdin().read_to_end(&mut data) stdin()
.read_to_end(&mut data)
.map_err(|e| PngError::new(&format!("Error reading stdin: {}", e)))?; .map_err(|e| PngError::new(&format!("Error reading stdin: {}", e)))?;
data data
} }
@ -352,9 +353,11 @@ pub fn optimize(input: &InFile, output: &OutFile, opts: &Options) -> Result<(),
eprintln!("File already optimized"); eprintln!("File already optimized");
match (output, input) { match (output, input) {
// if p is None, it also means same as the input path // if p is None, it also means same as the input path
(&OutFile::Path(ref p), &InFile::Path(ref input_path)) if p.as_ref().map_or(true, |p| p == input_path) => { (&OutFile::Path(ref p), &InFile::Path(ref input_path))
if p.as_ref().map_or(true, |p| p == input_path) =>
{
return Ok(()); return Ok(());
}, }
_ => { _ => {
optimized_output = in_data; optimized_output = in_data;
} }
@ -369,21 +372,27 @@ pub fn optimize(input: &InFile, output: &OutFile, opts: &Options) -> Result<(),
} }
match (output, input) { match (output, input) {
(&OutFile::StdOut, _) | (&OutFile::StdOut, _) | (&OutFile::Path(None), &InFile::StdIn) => {
(&OutFile::Path(None), &InFile::StdIn) => {
let mut buffer = BufWriter::new(stdout()); let mut buffer = BufWriter::new(stdout());
buffer.write_all(&optimized_output) buffer
.write_all(&optimized_output)
.map_err(|e| PngError::new(&format!("Unable to write to stdout: {}", e)))?; .map_err(|e| PngError::new(&format!("Unable to write to stdout: {}", e)))?;
}, }
(&OutFile::Path(ref output_path), _) => { (&OutFile::Path(ref output_path), _) => {
let output_path = output_path.as_ref().map(|p| p.as_path()).unwrap_or_else(|| input.path().unwrap()); let output_path = output_path
.as_ref()
.map(|p| p.as_path())
.unwrap_or_else(|| input.path().unwrap());
if opts.backup { if opts.backup {
perform_backup(output_path)?; perform_backup(output_path)?;
} }
let out_file = File::create(output_path) let out_file = File::create(output_path).map_err(|err| {
.map_err(|err| PngError::new(&format!( PngError::new(&format!(
"Unable to write to file {}: {}", output_path.display(), err "Unable to write to file {}: {}",
)))?; output_path.display(),
err
))
})?;
if opts.preserve_attrs { if opts.preserve_attrs {
if let Some(input_path) = input.path() { if let Some(input_path) = input.path() {
copy_permissions(input_path, &out_file, opts.verbosity); copy_permissions(input_path, &out_file, opts.verbosity);
@ -391,12 +400,17 @@ pub fn optimize(input: &InFile, output: &OutFile, opts: &Options) -> Result<(),
} }
let mut buffer = BufWriter::new(out_file); let mut buffer = BufWriter::new(out_file);
buffer.write_all(&optimized_output) buffer.write_all(&optimized_output).map_err(|e| {
.map_err(|e| PngError::new(&format!("Unable to write to {}: {}", output_path.display(), e)))?; PngError::new(&format!(
"Unable to write to {}: {}",
output_path.display(),
e
))
})?;
if opts.verbosity.is_some() { if opts.verbosity.is_some() {
eprintln!("Output: {}", output_path.display()); eprintln!("Output: {}", output_path.display());
} }
}, }
} }
Ok(()) Ok(())
} }
@ -554,68 +568,74 @@ fn optimize_png(
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;
let best_size = AtomicMin::new(if opts.force {None} else {Some(original_len)}); let best_size = AtomicMin::new(if opts.force { None } else { Some(original_len) });
#[cfg(feature = "parallel")] #[cfg(feature = "parallel")]
let results_iter = results.into_par_iter().with_max_len(1); let results_iter = results.into_par_iter().with_max_len(1);
#[cfg(not(feature = "parallel"))] #[cfg(not(feature = "parallel"))]
let results_iter = results.into_iter(); let results_iter = results.into_iter();
let best = results_iter let best = results_iter.filter_map(|trial| {
.filter_map(|trial| { if deadline.passed() {
if deadline.passed() { return None;
}
let filtered = &filters[&trial.filter];
let new_idat = if opts.deflate == Deflaters::Zlib {
deflate::deflate(
filtered,
trial.compression,
trial.strategy,
opts.window,
&best_size,
)
} else {
deflate::zopfli_deflate(filtered)
};
let new_idat = match new_idat {
Ok(n) => n,
Err(PngError::DeflatedDataTooLong(max)) if opts.verbosity == Some(1) => {
eprintln!(
" zc = {} zs = {} f = {} >{} bytes",
trial.compression, trial.strategy, trial.filter, max,
);
return None; return None;
} }
let filtered = &filters[&trial.filter]; _ => return None,
let new_idat = if opts.deflate == Deflaters::Zlib { };
deflate::deflate(filtered, trial.compression, trial.strategy, opts.window, &best_size)
} else {
deflate::zopfli_deflate(filtered)
};
let new_idat = match new_idat {
Ok(n) => n,
Err(PngError::DeflatedDataTooLong(max)) if opts.verbosity == Some(1) => {
eprintln!(
" zc = {} zs = {} f = {} >{} bytes",
trial.compression,
trial.strategy,
trial.filter,
max,
);
return None;
},
_ => return None,
};
// update best size across all threads // update best size across all threads
let new_size = new_idat.len(); let new_size = new_idat.len();
best_size.set_min(new_size); best_size.set_min(new_size);
if opts.verbosity == Some(1) { if opts.verbosity == Some(1) {
eprintln!( eprintln!(
" zc = {} zs = {} f = {} {} bytes", " zc = {} zs = {} f = {} {} bytes",
trial.compression, trial.compression,
trial.strategy, trial.strategy,
trial.filter, trial.filter,
new_idat.len() new_idat.len()
); );
} }
if new_size < original_len || added_interlacing || opts.force { if new_size < original_len || added_interlacing || opts.force {
Some((trial, new_idat)) Some((trial, new_idat))
} else { } else {
None None
} }
}); });
#[cfg(feature = "parallel")] #[cfg(feature = "parallel")]
let best: Option<TrialWithData> = best let best: Option<TrialWithData> =
.reduce_with(|i, j| if i.1.len() <= j.1.len() { i } else { j }); best.reduce_with(|i, j| if i.1.len() <= j.1.len() { i } else { j });
#[cfg(not(feature = "parallel"))] #[cfg(not(feature = "parallel"))]
let best: Option<TrialWithData> = best.fold(None, |i, j| { let best: Option<TrialWithData> = best.fold(None, |i, j| {
if let Some(i) = i { if let Some(i) = i {
if i.1.len() <= j.1.len() { Some(i) } else { Some(j) } if i.1.len() <= j.1.len() {
Some(i)
} else { } else {
Some(j) Some(j)
} }
}); } else {
Some(j)
}
});
if let Some(better) = best { if let Some(better) = best {
png.idat_data = better.1; png.idat_data = better.1;
@ -746,7 +766,6 @@ fn perform_reductions(png: &mut PngData, opts: &Options, deadline: &Deadline) ->
reduction_occurred reduction_occurred
} }
/// Keep track of processing timeout /// Keep track of processing timeout
struct Deadline { struct Deadline {
start: Instant, start: Instant,
@ -756,7 +775,7 @@ struct Deadline {
impl Deadline { impl Deadline {
pub fn new(opts: &Options) -> Self { pub fn new(opts: &Options) -> Self {
Self { Self {
start: Instant::now(), start: Instant::now(),
timeout: opts.timeout, timeout: opts.timeout,
print_message: Mutex::new(opts.verbosity.is_some()), print_message: Mutex::new(opts.verbosity.is_some()),
@ -805,16 +824,14 @@ fn perform_strip(png: &mut PngData, opts: &Options) {
// Strip headers // Strip headers
Headers::None => (), Headers::None => (),
Headers::Keep(ref hdrs) => { Headers::Keep(ref hdrs) => {
png.aux_headers.retain(|chunk, _| { png.aux_headers.retain(|chunk, _| hdrs.contains(chunk));
hdrs.contains(chunk) }
});
},
Headers::Strip(ref hdrs) => for hdr in hdrs { Headers::Strip(ref hdrs) => for hdr in hdrs {
png.aux_headers.remove(hdr); png.aux_headers.remove(hdr);
}, },
Headers::Safe => { Headers::Safe => {
const PRESERVED_HEADERS: [&str; 9] = [ const PRESERVED_HEADERS: [&str; 9] = [
"cHRM", "gAMA", "iCCP", "sBIT", "sRGB", "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 {
@ -883,10 +900,12 @@ fn copy_permissions(input_path: &Path, out_file: &File, verbosity: Option<u8>) {
/// Compares images pixel by pixel for equivalent content /// Compares images pixel by pixel for equivalent content
fn images_equal(old_png: &DynamicImage, new_png: &DynamicImage) -> bool { fn images_equal(old_png: &DynamicImage, new_png: &DynamicImage) -> bool {
let a = old_png.pixels() let a = old_png
.pixels()
.map(|x| x.2.channels().to_owned()) .map(|x| x.2.channels().to_owned())
.filter(|p| !(p.len() == 4 && p[3] == 0)); .filter(|p| !(p.len() == 4 && p[3] == 0));
let b = new_png.pixels() let b = new_png
.pixels()
.map(|x| x.2.channels().to_owned()) .map(|x| x.2.channels().to_owned())
.filter(|p| !(p.len() == 4 && p[3] == 0)); .filter(|p| !(p.len() == 4 && p[3] == 0));
a.eq(b) a.eq(b)

View file

@ -2,10 +2,10 @@
#![deny(missing_debug_implementations, missing_copy_implementations)] #![deny(missing_debug_implementations, missing_copy_implementations)]
extern crate clap; extern crate clap;
extern crate wild;
extern crate oxipng; extern crate oxipng;
extern crate wild;
use clap::{App, Arg, ArgMatches, AppSettings}; use clap::{App, AppSettings, Arg, ArgMatches};
use oxipng::AlphaOptim; use oxipng::AlphaOptim;
use oxipng::Deflaters; use oxipng::Deflaters;
use oxipng::Headers; use oxipng::Headers;
@ -228,12 +228,22 @@ fn main() {
} }
}; };
let files = collect_files(matches.values_of("files").unwrap() let files = collect_files(
.map(PathBuf::from).collect(), &out_dir, &out_file, opts.recursive, true); matches
.values_of("files")
.unwrap()
.map(PathBuf::from)
.collect(),
&out_dir,
&out_file,
opts.recursive,
true,
);
let res: Result<(), PngError> = files.into_iter().map(|(input, output)| { let res: Result<(), PngError> = files
oxipng::optimize(&input, &output, &opts) .into_iter()
}).collect(); .map(|(input, output)| oxipng::optimize(&input, &output, &opts))
.collect();
if let Err(e) = res { if let Err(e) = res {
eprintln!("{}", e); eprintln!("{}", e);
@ -241,7 +251,13 @@ fn main() {
} }
} }
fn collect_files(files: Vec<PathBuf>, out_dir: &Option<PathBuf>, out_file: &OutFile, recursive: bool, allow_stdin: bool) -> Vec<(InFile, OutFile)> { fn collect_files(
files: Vec<PathBuf>,
out_dir: &Option<PathBuf>,
out_file: &OutFile,
recursive: bool,
allow_stdin: bool,
) -> Vec<(InFile, OutFile)> {
let mut in_out_pairs = Vec::new(); let mut in_out_pairs = Vec::new();
let allow_stdin = allow_stdin && files.len() == 1; let allow_stdin = allow_stdin && files.len() == 1;
for input in files { for input in files {
@ -275,7 +291,9 @@ fn collect_files(files: Vec<PathBuf>, out_dir: &Option<PathBuf>, out_file: &OutF
in_out_pairs in_out_pairs
} }
fn parse_opts_into_struct(matches: &ArgMatches) -> Result<(OutFile, Option<PathBuf>, Options), String> { fn parse_opts_into_struct(
matches: &ArgMatches,
) -> Result<(OutFile, Option<PathBuf>, Options), String> {
let mut opts = if let Some(x) = matches.value_of("optimization") { let mut opts = if let Some(x) = matches.value_of("optimization") {
if let Ok(opt) = x.parse::<u8>() { if let Ok(opt) = x.parse::<u8>() {
Options::from_preset(opt) Options::from_preset(opt)
@ -303,7 +321,8 @@ fn parse_opts_into_struct(matches: &ArgMatches) -> Result<(OutFile, Option<PathB
} }
if let Some(x) = matches.value_of("timeout") { if let Some(x) = matches.value_of("timeout") {
let num = x.parse().map_err(|_| "Timeout must be a number".to_owned())?; let num = x.parse()
.map_err(|_| "Timeout must be a number".to_owned())?;
opts.timeout = Some(Duration::from_secs(num)); opts.timeout = Some(Duration::from_secs(num));
} }
@ -412,9 +431,7 @@ fn parse_opts_into_struct(matches: &ArgMatches) -> Result<(OutFile, Option<PathB
} }
if let Some(hdrs) = matches.value_of("keep") { if let Some(hdrs) = matches.value_of("keep") {
opts.strip = Headers::Keep(hdrs.split(',') opts.strip = Headers::Keep(hdrs.split(',').map(|x| x.trim().to_owned()).collect())
.map(|x| x.trim().to_owned())
.collect())
} }
if let Some(hdrs) = matches.value_of("strip") { if let Some(hdrs) = matches.value_of("strip") {
@ -491,11 +508,14 @@ fn parse_numeric_range_opts(
} }
// a list ("A,B[,…]") // a list ("A,B[,…]")
let list_items = input.split(',').collect::<Vec<&str>>();; let list_items = input.split(',').collect::<Vec<&str>>();
if list_items.len() > 1 { if list_items.len() > 1 {
for value in list_items { for value in list_items {
if let Ok(value_int) = value.parse::<u8>() { if let Ok(value_int) = value.parse::<u8>() {
if (min_value <= value_int) && (value_int <= max_value) && !items.contains(&value_int) { if (min_value <= value_int)
&& (value_int <= max_value)
&& !items.contains(&value_int)
{
items.insert(value_int); items.insert(value_int);
continue; continue;
} }

View file

@ -1,3 +1,4 @@
use atomicmin::AtomicMin;
use bit_vec::BitVec; use bit_vec::BitVec;
use byteorder::{BigEndian, WriteBytesExt}; use byteorder::{BigEndian, WriteBytesExt};
use colors::{AlphaOptim, BitDepth, ColorType}; use colors::{AlphaOptim, BitDepth, ColorType};
@ -8,6 +9,8 @@ use filters::*;
use headers::*; use headers::*;
use interlace::{deinterlace_image, interlace_image}; use interlace::{deinterlace_image, interlace_image};
use itertools::{flatten, Itertools}; use itertools::{flatten, Itertools};
#[cfg(feature = "parallel")]
use rayon::prelude::*;
use reduction::bit_depth::*; use reduction::bit_depth::*;
use reduction::color::*; use reduction::color::*;
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
@ -15,9 +18,6 @@ use std::fs::File;
use std::io::{Read, Seek, SeekFrom}; use std::io::{Read, Seek, SeekFrom};
use std::iter::Iterator; use std::iter::Iterator;
use std::path::Path; use std::path::Path;
#[cfg(feature = "parallel")]
use rayon::prelude::*;
use atomicmin::AtomicMin;
const STD_COMPRESSION: u8 = 8; const STD_COMPRESSION: u8 = 8;
const STD_STRATEGY: u8 = 2; // Huffman only const STD_STRATEGY: u8 = 2; // Huffman only
@ -105,9 +105,10 @@ impl PngData {
b"IDAT" => idat_headers.extend(data), b"IDAT" => idat_headers.extend(data),
b"acTL" => return Err(PngError::APNGNotSupported), b"acTL" => return Err(PngError::APNGNotSupported),
_ => { _ => {
let name = String::from_utf8(name.to_owned()).map_err(|_| PngError::InvalidData)?; let name =
String::from_utf8(name.to_owned()).map_err(|_| PngError::InvalidData)?;
aux_headers.insert(name, data.to_owned()); aux_headers.insert(name, data.to_owned());
}, }
} }
} }
// Parse the headers into our PngData // Parse the headers into our PngData
@ -618,10 +619,11 @@ impl PngData {
STD_WINDOW, STD_WINDOW,
&best_size, &best_size,
).ok() ).ok()
.as_ref().map(|l| { .as_ref()
best_size.set_min(l.len()); .map(|l| {
l.len() best_size.set_min(l.len());
}) l.len()
})
}) })
.min() .min()
.map(|size| (size, image)) .map(|size| (size, image))

View file

@ -135,7 +135,7 @@ pub struct ScanLine<'a> {
/// The filter type used to encode the current scan line (0-4) /// The filter type used to encode the current scan line (0-4)
pub filter: u8, pub filter: u8,
/// The byte data for the current scan line, encoded with the filter specified in the `filter` field /// The byte data for the current scan line, encoded with the filter specified in the `filter` field
pub data: &'a[u8], pub data: &'a [u8],
/// The current pass if the image is interlaced /// The current pass if the image is interlaced
pub pass: Option<u8>, pub pass: Option<u8>,
} }

View file

@ -15,7 +15,10 @@ fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
filter.insert(0); filter.insert(0);
options.filter = filter; options.filter = filter;
(OutFile::Path(Some(input.with_extension("out.png").to_owned())), options) (
OutFile::Path(Some(input.with_extension("out.png").to_owned())),
options,
)
} }
fn test_it_converts( fn test_it_converts(

View file

@ -1,7 +1,7 @@
extern crate oxipng; extern crate oxipng;
use oxipng::{InFile, OutFile};
use oxipng::internal_tests::*; use oxipng::internal_tests::*;
use oxipng::{InFile, OutFile};
use std::collections::HashSet; use std::collections::HashSet;
use std::fs::remove_file; use std::fs::remove_file;
use std::path::Path; use std::path::Path;
@ -15,7 +15,10 @@ fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
filter.insert(0); filter.insert(0);
options.filter = filter; options.filter = filter;
(OutFile::Path(Some(input.with_extension("out.png").to_owned())), options) (
OutFile::Path(Some(input.with_extension("out.png").to_owned())),
options,
)
} }
fn test_it_converts( fn test_it_converts(
@ -427,7 +430,8 @@ fn zopfli_mode() {
test_it_converts( test_it_converts(
input, input,
output, opts, output,
opts,
ColorType::RGB, ColorType::RGB,
BitDepth::Eight, BitDepth::Eight,
ColorType::RGB, ColorType::RGB,

View file

@ -1,7 +1,7 @@
extern crate oxipng; extern crate oxipng;
use oxipng::{InFile, OutFile};
use oxipng::internal_tests::*; use oxipng::internal_tests::*;
use oxipng::{InFile, OutFile};
use std::collections::HashSet; use std::collections::HashSet;
use std::fs::remove_file; use std::fs::remove_file;
use std::path::Path; use std::path::Path;
@ -15,7 +15,10 @@ fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
filter.insert(0); filter.insert(0);
options.filter = filter; options.filter = filter;
(OutFile::Path(Some(input.with_extension("out.png").to_owned())), options) (
OutFile::Path(Some(input.with_extension("out.png").to_owned())),
options,
)
} }
fn test_it_converts( fn test_it_converts(

View file

@ -49,7 +49,11 @@ fn optimize() {
let mut opts: oxipng::Options = Default::default(); let mut opts: oxipng::Options = Default::default();
opts.verbosity = Some(1); opts.verbosity = Some(1);
let result = oxipng::optimize(&"tests/files/fully_optimized.png".into(), &OutFile::Path(None), &opts); let result = oxipng::optimize(
&"tests/files/fully_optimized.png".into(),
&OutFile::Path(None),
&opts,
);
assert!(result.is_ok()); assert!(result.is_ok());
} }
@ -58,7 +62,11 @@ fn optimize_corrupted() {
let mut opts: oxipng::Options = Default::default(); let mut opts: oxipng::Options = Default::default();
opts.verbosity = Some(1); opts.verbosity = Some(1);
let result = oxipng::optimize(&"tests/files/corrupted_header.png".into(), &OutFile::Path(None), &opts); let result = oxipng::optimize(
&"tests/files/corrupted_header.png".into(),
&OutFile::Path(None),
&opts,
);
assert!(result.is_err()); assert!(result.is_err());
} }
@ -67,6 +75,10 @@ fn optimize_apng() {
let mut opts: oxipng::Options = Default::default(); let mut opts: oxipng::Options = Default::default();
opts.verbosity = Some(1); opts.verbosity = Some(1);
let result = oxipng::optimize(&"tests/files/apng_file.png".into(), &OutFile::Path(None), &opts); let result = oxipng::optimize(
&"tests/files/apng_file.png".into(),
&OutFile::Path(None),
&opts,
);
assert!(result.is_err()); assert!(result.is_err());
} }

View file

@ -1,7 +1,7 @@
extern crate oxipng; extern crate oxipng;
use oxipng::{InFile, OutFile};
use oxipng::internal_tests::*; use oxipng::internal_tests::*;
use oxipng::{InFile, OutFile};
use std::collections::HashSet; use std::collections::HashSet;
use std::fs::remove_file; use std::fs::remove_file;
use std::path::Path; use std::path::Path;
@ -15,7 +15,10 @@ fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
filter.insert(0); filter.insert(0);
options.filter = filter; options.filter = filter;
(OutFile::Path(Some(input.with_extension("out.png").to_owned())), options) (
OutFile::Path(Some(input.with_extension("out.png").to_owned())),
options,
)
} }
fn test_it_converts( fn test_it_converts(

View file

@ -15,7 +15,10 @@ fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
filter.insert(0); filter.insert(0);
options.filter = filter; options.filter = filter;
(OutFile::Path(Some(input.with_extension("out.png").to_owned())), options) (
OutFile::Path(Some(input.with_extension("out.png").to_owned())),
options,
)
} }
fn test_it_converts( fn test_it_converts(
@ -273,7 +276,9 @@ fn issue_92_filter_5() {
let input = "tests/files/issue-92.png"; let input = "tests/files/issue-92.png";
let (_, mut opts) = get_opts(Path::new(input)); let (_, mut opts) = get_opts(Path::new(input));
opts.filter = [5].iter().cloned().collect(); opts.filter = [5].iter().cloned().collect();
let output = OutFile::Path(Some(Path::new(input).with_extension("-f5-out.png").to_owned())); let output = OutFile::Path(Some(
Path::new(input).with_extension("-f5-out.png").to_owned(),
));
test_it_converts( test_it_converts(
&input, &input,