Version 2.0.0
This commit is contained in:
parent
879ca01042
commit
d110949d2a
25 changed files with 337 additions and 216 deletions
13
CHANGELOG.md
13
CHANGELOG.md
|
|
@ -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
|
||||
- Bump `image` to 0.19.0
|
||||
- Bump `bit-vec` to 0.5.0
|
||||
|
|
|
|||
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -265,7 +265,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "oxipng"
|
||||
version = "1.0.4"
|
||||
version = "2.0.0"
|
||||
dependencies = [
|
||||
"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)",
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ homepage = "https://github.com/shssoichiro/oxipng"
|
|||
license = "MIT"
|
||||
name = "oxipng"
|
||||
repository = "https://github.com/shssoichiro/oxipng"
|
||||
version = "1.0.4"
|
||||
version = "2.0.0"
|
||||
|
||||
[badges]
|
||||
travis-ci = { repository = "shssoichiro/oxipng", branch = "master" }
|
||||
|
|
|
|||
26
README.md
26
README.md
|
|
@ -94,39 +94,39 @@ Oxipng is open-source software, distributed under the MIT license.
|
|||
|
||||
## 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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
'./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'
|
||||
'./target/release/oxipng -P ./tests/files/rgb_16_should_be_grayscale_8.png' ran
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
'./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'
|
||||
'./target/release/oxipng -o4 -P ./tests/files/rgb_16_should_be_grayscale_8.png' ran
|
||||
8.09x faster than 'optipng -o 4 -simulate ./tests/files/rgb_16_should_be_grayscale_8.png'
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ extern crate oxipng;
|
|||
extern crate test;
|
||||
|
||||
use oxipng::internal_tests::*;
|
||||
use test::Bencher;
|
||||
use std::path::PathBuf;
|
||||
use test::Bencher;
|
||||
|
||||
#[bench]
|
||||
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 png = PngData::new(&input, false).unwrap();
|
||||
|
||||
b.iter(|| {
|
||||
inflate(png.idat_data.as_ref())
|
||||
});
|
||||
b.iter(|| inflate(png.idat_data.as_ref()));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ extern crate oxipng;
|
|||
extern crate test;
|
||||
|
||||
use oxipng::internal_tests::*;
|
||||
use test::Bencher;
|
||||
use std::path::PathBuf;
|
||||
use test::Bencher;
|
||||
|
||||
#[bench]
|
||||
fn filters_16_bits_filter_0(b: &mut Bencher) {
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ extern crate oxipng;
|
|||
extern crate test;
|
||||
|
||||
use oxipng::internal_tests::*;
|
||||
use test::Bencher;
|
||||
use std::path::PathBuf;
|
||||
use test::Bencher;
|
||||
|
||||
#[bench]
|
||||
fn interlacing_16_bits(b: &mut Bencher) {
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ extern crate oxipng;
|
|||
extern crate test;
|
||||
|
||||
use oxipng::internal_tests::*;
|
||||
use test::Bencher;
|
||||
use std::path::PathBuf;
|
||||
use test::Bencher;
|
||||
|
||||
#[bench]
|
||||
fn reductions_16_to_8_bits(b: &mut Bencher) {
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ extern crate oxipng;
|
|||
extern crate test;
|
||||
|
||||
use oxipng::internal_tests::*;
|
||||
use test::Bencher;
|
||||
use std::path::PathBuf;
|
||||
use test::Bencher;
|
||||
|
||||
#[bench]
|
||||
fn zopfli_16_bits_strategy_0(b: &mut Bencher) {
|
||||
|
|
|
|||
104
package-lock.json
generated
104
package-lock.json
generated
|
|
@ -34,8 +34,8 @@
|
|||
"integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"camelcase": "2.1.1",
|
||||
"map-obj": "1.0.1"
|
||||
"camelcase": "^2.0.0",
|
||||
"map-obj": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"currently-unhandled": {
|
||||
|
|
@ -44,7 +44,7 @@
|
|||
"integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"array-find-index": "1.0.2"
|
||||
"array-find-index": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"decamelize": {
|
||||
|
|
@ -59,7 +59,7 @@
|
|||
"integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-arrayish": "0.2.1"
|
||||
"is-arrayish": "^0.2.1"
|
||||
}
|
||||
},
|
||||
"find-up": {
|
||||
|
|
@ -68,8 +68,8 @@
|
|||
"integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"path-exists": "2.1.0",
|
||||
"pinkie-promise": "2.0.1"
|
||||
"path-exists": "^2.0.0",
|
||||
"pinkie-promise": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"get-stdin": {
|
||||
|
|
@ -96,7 +96,7 @@
|
|||
"integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"repeating": "2.0.1"
|
||||
"repeating": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"is-arrayish": {
|
||||
|
|
@ -111,7 +111,7 @@
|
|||
"integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"builtin-modules": "1.1.1"
|
||||
"builtin-modules": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"is-finite": {
|
||||
|
|
@ -120,7 +120,7 @@
|
|||
"integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"number-is-nan": "1.0.1"
|
||||
"number-is-nan": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"is-utf8": {
|
||||
|
|
@ -135,11 +135,11 @@
|
|||
"integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "4.1.11",
|
||||
"parse-json": "2.2.0",
|
||||
"pify": "2.3.0",
|
||||
"pinkie-promise": "2.0.1",
|
||||
"strip-bom": "2.0.0"
|
||||
"graceful-fs": "^4.1.2",
|
||||
"parse-json": "^2.2.0",
|
||||
"pify": "^2.0.0",
|
||||
"pinkie-promise": "^2.0.0",
|
||||
"strip-bom": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"loud-rejection": {
|
||||
|
|
@ -148,8 +148,8 @@
|
|||
"integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"currently-unhandled": "0.4.1",
|
||||
"signal-exit": "3.0.2"
|
||||
"currently-unhandled": "^0.4.1",
|
||||
"signal-exit": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"map-obj": {
|
||||
|
|
@ -164,16 +164,16 @@
|
|||
"integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"camelcase-keys": "2.1.0",
|
||||
"decamelize": "1.2.0",
|
||||
"loud-rejection": "1.6.0",
|
||||
"map-obj": "1.0.1",
|
||||
"minimist": "1.2.0",
|
||||
"normalize-package-data": "2.4.0",
|
||||
"object-assign": "4.1.1",
|
||||
"read-pkg-up": "1.0.1",
|
||||
"redent": "1.0.0",
|
||||
"trim-newlines": "1.0.0"
|
||||
"camelcase-keys": "^2.0.0",
|
||||
"decamelize": "^1.1.2",
|
||||
"loud-rejection": "^1.0.0",
|
||||
"map-obj": "^1.0.1",
|
||||
"minimist": "^1.1.3",
|
||||
"normalize-package-data": "^2.3.4",
|
||||
"object-assign": "^4.0.1",
|
||||
"read-pkg-up": "^1.0.1",
|
||||
"redent": "^1.0.0",
|
||||
"trim-newlines": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"minimist": {
|
||||
|
|
@ -188,10 +188,10 @@
|
|||
"integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"hosted-git-info": "2.5.0",
|
||||
"is-builtin-module": "1.0.0",
|
||||
"semver": "5.5.0",
|
||||
"validate-npm-package-license": "3.0.1"
|
||||
"hosted-git-info": "^2.1.4",
|
||||
"is-builtin-module": "^1.0.0",
|
||||
"semver": "2 || 3 || 4 || 5",
|
||||
"validate-npm-package-license": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"number-is-nan": {
|
||||
|
|
@ -212,7 +212,7 @@
|
|||
"integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"error-ex": "1.3.1"
|
||||
"error-ex": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"path-exists": {
|
||||
|
|
@ -221,7 +221,7 @@
|
|||
"integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"pinkie-promise": "2.0.1"
|
||||
"pinkie-promise": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"path-type": {
|
||||
|
|
@ -230,9 +230,9 @@
|
|||
"integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "4.1.11",
|
||||
"pify": "2.3.0",
|
||||
"pinkie-promise": "2.0.1"
|
||||
"graceful-fs": "^4.1.2",
|
||||
"pify": "^2.0.0",
|
||||
"pinkie-promise": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"pify": {
|
||||
|
|
@ -253,7 +253,7 @@
|
|||
"integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"pinkie": "2.0.4"
|
||||
"pinkie": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"read-pkg": {
|
||||
|
|
@ -262,9 +262,9 @@
|
|||
"integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"load-json-file": "1.1.0",
|
||||
"normalize-package-data": "2.4.0",
|
||||
"path-type": "1.1.0"
|
||||
"load-json-file": "^1.0.0",
|
||||
"normalize-package-data": "^2.3.2",
|
||||
"path-type": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"read-pkg-up": {
|
||||
|
|
@ -273,8 +273,8 @@
|
|||
"integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"find-up": "1.1.2",
|
||||
"read-pkg": "1.1.0"
|
||||
"find-up": "^1.0.0",
|
||||
"read-pkg": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"redent": {
|
||||
|
|
@ -283,8 +283,8 @@
|
|||
"integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"indent-string": "2.1.0",
|
||||
"strip-indent": "1.0.1"
|
||||
"indent-string": "^2.1.0",
|
||||
"strip-indent": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"repeating": {
|
||||
|
|
@ -293,7 +293,7 @@
|
|||
"integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-finite": "1.0.2"
|
||||
"is-finite": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"semver": {
|
||||
|
|
@ -314,7 +314,7 @@
|
|||
"integrity": "sha1-SzBz2TP/UfORLwOsVRlJikFQ20A=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"spdx-license-ids": "1.2.2"
|
||||
"spdx-license-ids": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"spdx-expression-parse": {
|
||||
|
|
@ -335,7 +335,7 @@
|
|||
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-regex": "3.0.0"
|
||||
"ansi-regex": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"strip-ansi-cli": {
|
||||
|
|
@ -344,8 +344,8 @@
|
|||
"integrity": "sha1-D91Cq86DJTm/cv+ROSpxqzVGIIw=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"meow": "3.7.0",
|
||||
"strip-ansi": "4.0.0"
|
||||
"meow": "^3.7.0",
|
||||
"strip-ansi": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"strip-bom": {
|
||||
|
|
@ -354,7 +354,7 @@
|
|||
"integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-utf8": "0.2.1"
|
||||
"is-utf8": "^0.2.0"
|
||||
}
|
||||
},
|
||||
"strip-indent": {
|
||||
|
|
@ -363,7 +363,7 @@
|
|||
"integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"get-stdin": "4.0.1"
|
||||
"get-stdin": "^4.0.1"
|
||||
}
|
||||
},
|
||||
"trim-newlines": {
|
||||
|
|
@ -378,8 +378,8 @@
|
|||
"integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"spdx-correct": "1.0.2",
|
||||
"spdx-expression-parse": "1.0.4"
|
||||
"spdx-correct": "~1.0.0",
|
||||
"spdx-expression-parse": "~1.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use std::sync::atomic::AtomicUsize;
|
||||
use std::sync::atomic::Ordering::{SeqCst, Relaxed};
|
||||
use std::sync::atomic::Ordering::{Relaxed, SeqCst};
|
||||
|
||||
pub struct AtomicMin {
|
||||
val: AtomicUsize,
|
||||
|
|
@ -8,20 +8,26 @@ pub struct AtomicMin {
|
|||
impl AtomicMin {
|
||||
pub fn new(init: Option<usize>) -> 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> {
|
||||
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) {
|
||||
let mut current_val = self.val.load(Relaxed);
|
||||
loop {
|
||||
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;
|
||||
continue;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,13 @@ use atomicmin::AtomicMin;
|
|||
use error::PngError;
|
||||
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.
|
||||
let flags = create_comp_flags_from_zip_params(level.into(), window_bits, strategy);
|
||||
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 => {
|
||||
if let Some(max) = max_size.get() {
|
||||
if output.len() > max {
|
||||
return Err(PngError::DeflatedDataTooLong(output.len()))
|
||||
return Err(PngError::DeflatedDataTooLong(output.len()));
|
||||
}
|
||||
}
|
||||
// We need more space, so extend the vector.
|
||||
|
|
|
|||
|
|
@ -14,21 +14,29 @@ pub fn inflate(data: &[u8]) -> Result<Vec<u8>, PngError> {
|
|||
}
|
||||
|
||||
/// 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() {
|
||||
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)
|
||||
}
|
||||
|
||||
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") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#[cfg(target_arch = "aarch64")] {
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
{
|
||||
if is_arm_feature_detected!("neon") && is_arm_feature_detected!("crc") {
|
||||
return true;
|
||||
}
|
||||
|
|
@ -36,20 +44,27 @@ fn is_cfzlib_supported() -> bool {
|
|||
false
|
||||
}
|
||||
|
||||
pub fn cfzlib_deflate(data: &[u8], level: u8, strategy: u8, window_bits: u8, max_size: &AtomicMin) -> Result<Vec<u8>, PngError> {
|
||||
use std::mem;
|
||||
pub fn cfzlib_deflate(
|
||||
data: &[u8],
|
||||
level: u8,
|
||||
strategy: u8,
|
||||
window_bits: u8,
|
||||
max_size: &AtomicMin,
|
||||
) -> Result<Vec<u8>, PngError> {
|
||||
use cloudflare_zlib_sys::*;
|
||||
use std::mem;
|
||||
|
||||
assert!(data.len() < u32::max_value() as usize);
|
||||
unsafe {
|
||||
let mut stream = mem::zeroed();
|
||||
if Z_OK != deflateInit2(
|
||||
&mut stream,
|
||||
level.into(),
|
||||
Z_DEFLATED,
|
||||
window_bits.into(),
|
||||
MAX_MEM_LEVEL,
|
||||
strategy.into()) {
|
||||
&mut stream,
|
||||
level.into(),
|
||||
Z_DEFLATED,
|
||||
window_bits.into(),
|
||||
MAX_MEM_LEVEL,
|
||||
strategy.into(),
|
||||
) {
|
||||
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.avail_out = out.capacity() as uInt;
|
||||
match deflate(&mut stream, Z_FINISH) {
|
||||
Z_STREAM_END => {},
|
||||
Z_STREAM_END => {}
|
||||
Z_OK => return Err(PngError::DeflatedDataTooLong(stream.total_out as usize)),
|
||||
_ => return Err(PngError::new("deflate")),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,9 @@ impl fmt::Display for PngError {
|
|||
PngError::DeflatedDataTooLong(_) => f.write_str("deflated data too long"),
|
||||
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::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::ChunkMissing(s) => write!(f, "Chunk {} missing or empty", s),
|
||||
PngError::Other(ref s) => f.write_str(s),
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
use std::collections::HashSet;
|
||||
use byteorder::{BigEndian, ReadBytesExt};
|
||||
use colors::{BitDepth, ColorType};
|
||||
use crc::crc32;
|
||||
use error::PngError;
|
||||
use std::collections::HashSet;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
|
|
@ -50,26 +50,36 @@ pub fn parse_next_header<'a>(
|
|||
byte_data: &'a [u8],
|
||||
byte_offset: &mut usize,
|
||||
fix_errors: bool,
|
||||
) -> Result<Option<(&'a [u8], &'a [u8])>, PngError> {
|
||||
let mut rdr = Cursor::new(byte_data.get(*byte_offset..*byte_offset + 4).ok_or(PngError::TruncatedData)?);
|
||||
) -> 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 length = rdr.read_u32::<BigEndian>().unwrap();
|
||||
*byte_offset += 4;
|
||||
|
||||
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" {
|
||||
// End of data
|
||||
return Ok(None);
|
||||
}
|
||||
*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;
|
||||
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();
|
||||
*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 {
|
||||
return Err(PngError::new(&format!(
|
||||
"CRC Mismatch in {} header; May be recoverable by using --fix",
|
||||
|
|
|
|||
171
src/lib.rs
171
src/lib.rs
|
|
@ -1,5 +1,6 @@
|
|||
extern crate bit_vec;
|
||||
extern crate byteorder;
|
||||
extern crate cloudflare_zlib_sys;
|
||||
extern crate crc;
|
||||
extern crate image;
|
||||
extern crate itertools;
|
||||
|
|
@ -8,8 +9,8 @@ extern crate num_cpus;
|
|||
#[cfg(feature = "parallel")]
|
||||
extern crate rayon;
|
||||
extern crate zopfli;
|
||||
extern crate cloudflare_zlib_sys;
|
||||
|
||||
use atomicmin::AtomicMin;
|
||||
use image::{DynamicImage, GenericImage, ImageFormat, Pixel};
|
||||
use png::PngData;
|
||||
#[cfg(feature = "parallel")]
|
||||
|
|
@ -19,15 +20,15 @@ use std::fmt;
|
|||
use std::fs::{copy, File};
|
||||
use std::io::{stdin, stdout, BufWriter, Read, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::{Duration, Instant};
|
||||
use std::sync::Mutex;
|
||||
use atomicmin::AtomicMin;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
pub use colors::AlphaOptim;
|
||||
pub use deflate::Deflaters;
|
||||
pub use error::PngError;
|
||||
pub use headers::Headers;
|
||||
|
||||
mod atomicmin;
|
||||
mod colors;
|
||||
mod deflate;
|
||||
mod error;
|
||||
|
|
@ -36,14 +37,13 @@ mod headers;
|
|||
mod interlace;
|
||||
mod png;
|
||||
mod reduction;
|
||||
mod atomicmin;
|
||||
|
||||
/// Private to oxipng; don't use outside tests and benches
|
||||
#[doc(hidden)]
|
||||
pub mod internal_tests {
|
||||
pub use atomicmin::*;
|
||||
pub use deflate::*;
|
||||
pub use colors::*;
|
||||
pub use deflate::*;
|
||||
pub use headers::*;
|
||||
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::StdIn => {
|
||||
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)))?;
|
||||
data
|
||||
}
|
||||
|
|
@ -352,9 +353,11 @@ pub fn optimize(input: &InFile, output: &OutFile, opts: &Options) -> Result<(),
|
|||
eprintln!("File already optimized");
|
||||
match (output, input) {
|
||||
// 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(());
|
||||
},
|
||||
}
|
||||
_ => {
|
||||
optimized_output = in_data;
|
||||
}
|
||||
|
|
@ -369,21 +372,27 @@ pub fn optimize(input: &InFile, output: &OutFile, opts: &Options) -> Result<(),
|
|||
}
|
||||
|
||||
match (output, input) {
|
||||
(&OutFile::StdOut, _) |
|
||||
(&OutFile::Path(None), &InFile::StdIn) => {
|
||||
(&OutFile::StdOut, _) | (&OutFile::Path(None), &InFile::StdIn) => {
|
||||
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)))?;
|
||||
},
|
||||
}
|
||||
(&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 {
|
||||
perform_backup(output_path)?;
|
||||
}
|
||||
let out_file = File::create(output_path)
|
||||
.map_err(|err| PngError::new(&format!(
|
||||
"Unable to write to file {}: {}", output_path.display(), err
|
||||
)))?;
|
||||
let out_file = File::create(output_path).map_err(|err| {
|
||||
PngError::new(&format!(
|
||||
"Unable to write to file {}: {}",
|
||||
output_path.display(),
|
||||
err
|
||||
))
|
||||
})?;
|
||||
if opts.preserve_attrs {
|
||||
if let Some(input_path) = input.path() {
|
||||
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);
|
||||
buffer.write_all(&optimized_output)
|
||||
.map_err(|e| PngError::new(&format!("Unable to write to {}: {}", output_path.display(), e)))?;
|
||||
buffer.write_all(&optimized_output).map_err(|e| {
|
||||
PngError::new(&format!(
|
||||
"Unable to write to {}: {}",
|
||||
output_path.display(),
|
||||
e
|
||||
))
|
||||
})?;
|
||||
if opts.verbosity.is_some() {
|
||||
eprintln!("Output: {}", output_path.display());
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -554,68 +568,74 @@ fn optimize_png(
|
|||
let original_len = original_png.idat_data.len();
|
||||
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")]
|
||||
let results_iter = results.into_par_iter().with_max_len(1);
|
||||
#[cfg(not(feature = "parallel"))]
|
||||
let results_iter = results.into_iter();
|
||||
let best = results_iter
|
||||
.filter_map(|trial| {
|
||||
if deadline.passed() {
|
||||
let best = results_iter.filter_map(|trial| {
|
||||
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;
|
||||
}
|
||||
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,
|
||||
};
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
// update best size across all threads
|
||||
let new_size = new_idat.len();
|
||||
best_size.set_min(new_size);
|
||||
// update best size across all threads
|
||||
let new_size = new_idat.len();
|
||||
best_size.set_min(new_size);
|
||||
|
||||
if opts.verbosity == Some(1) {
|
||||
eprintln!(
|
||||
" zc = {} zs = {} f = {} {} bytes",
|
||||
trial.compression,
|
||||
trial.strategy,
|
||||
trial.filter,
|
||||
new_idat.len()
|
||||
);
|
||||
}
|
||||
if opts.verbosity == Some(1) {
|
||||
eprintln!(
|
||||
" zc = {} zs = {} f = {} {} bytes",
|
||||
trial.compression,
|
||||
trial.strategy,
|
||||
trial.filter,
|
||||
new_idat.len()
|
||||
);
|
||||
}
|
||||
|
||||
if new_size < original_len || added_interlacing || opts.force {
|
||||
Some((trial, new_idat))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
if new_size < original_len || added_interlacing || opts.force {
|
||||
Some((trial, new_idat))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
#[cfg(feature = "parallel")]
|
||||
let best: Option<TrialWithData> = best
|
||||
.reduce_with(|i, j| if i.1.len() <= j.1.len() { i } else { j });
|
||||
let best: Option<TrialWithData> =
|
||||
best.reduce_with(|i, j| if i.1.len() <= j.1.len() { i } else { j });
|
||||
#[cfg(not(feature = "parallel"))]
|
||||
let best: Option<TrialWithData> = best.fold(None, |i, j| {
|
||||
if let Some(i) = i {
|
||||
if i.1.len() <= j.1.len() { Some(i) } else { Some(j) }
|
||||
if let Some(i) = i {
|
||||
if i.1.len() <= j.1.len() {
|
||||
Some(i)
|
||||
} else {
|
||||
Some(j)
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Some(j)
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(better) = best {
|
||||
png.idat_data = better.1;
|
||||
|
|
@ -746,7 +766,6 @@ fn perform_reductions(png: &mut PngData, opts: &Options, deadline: &Deadline) ->
|
|||
reduction_occurred
|
||||
}
|
||||
|
||||
|
||||
/// Keep track of processing timeout
|
||||
struct Deadline {
|
||||
start: Instant,
|
||||
|
|
@ -756,7 +775,7 @@ struct Deadline {
|
|||
|
||||
impl Deadline {
|
||||
pub fn new(opts: &Options) -> Self {
|
||||
Self {
|
||||
Self {
|
||||
start: Instant::now(),
|
||||
timeout: opts.timeout,
|
||||
print_message: Mutex::new(opts.verbosity.is_some()),
|
||||
|
|
@ -805,16 +824,14 @@ fn perform_strip(png: &mut PngData, opts: &Options) {
|
|||
// Strip headers
|
||||
Headers::None => (),
|
||||
Headers::Keep(ref hdrs) => {
|
||||
png.aux_headers.retain(|chunk, _| {
|
||||
hdrs.contains(chunk)
|
||||
});
|
||||
},
|
||||
png.aux_headers.retain(|chunk, _| hdrs.contains(chunk));
|
||||
}
|
||||
Headers::Strip(ref hdrs) => for hdr in hdrs {
|
||||
png.aux_headers.remove(hdr);
|
||||
},
|
||||
Headers::Safe => {
|
||||
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>>();
|
||||
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
|
||||
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())
|
||||
.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())
|
||||
.filter(|p| !(p.len() == 4 && p[3] == 0));
|
||||
a.eq(b)
|
||||
|
|
|
|||
50
src/main.rs
50
src/main.rs
|
|
@ -2,10 +2,10 @@
|
|||
#![deny(missing_debug_implementations, missing_copy_implementations)]
|
||||
|
||||
extern crate clap;
|
||||
extern crate wild;
|
||||
extern crate oxipng;
|
||||
extern crate wild;
|
||||
|
||||
use clap::{App, Arg, ArgMatches, AppSettings};
|
||||
use clap::{App, AppSettings, Arg, ArgMatches};
|
||||
use oxipng::AlphaOptim;
|
||||
use oxipng::Deflaters;
|
||||
use oxipng::Headers;
|
||||
|
|
@ -228,12 +228,22 @@ fn main() {
|
|||
}
|
||||
};
|
||||
|
||||
let files = collect_files(matches.values_of("files").unwrap()
|
||||
.map(PathBuf::from).collect(), &out_dir, &out_file, opts.recursive, true);
|
||||
let files = collect_files(
|
||||
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)| {
|
||||
oxipng::optimize(&input, &output, &opts)
|
||||
}).collect();
|
||||
let res: Result<(), PngError> = files
|
||||
.into_iter()
|
||||
.map(|(input, output)| oxipng::optimize(&input, &output, &opts))
|
||||
.collect();
|
||||
|
||||
if let Err(e) = res {
|
||||
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 allow_stdin = allow_stdin && files.len() == 1;
|
||||
for input in files {
|
||||
|
|
@ -275,7 +291,9 @@ fn collect_files(files: Vec<PathBuf>, out_dir: &Option<PathBuf>, out_file: &OutF
|
|||
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") {
|
||||
if let Ok(opt) = x.parse::<u8>() {
|
||||
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") {
|
||||
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));
|
||||
}
|
||||
|
||||
|
|
@ -412,9 +431,7 @@ fn parse_opts_into_struct(matches: &ArgMatches) -> Result<(OutFile, Option<PathB
|
|||
}
|
||||
|
||||
if let Some(hdrs) = matches.value_of("keep") {
|
||||
opts.strip = Headers::Keep(hdrs.split(',')
|
||||
.map(|x| x.trim().to_owned())
|
||||
.collect())
|
||||
opts.strip = Headers::Keep(hdrs.split(',').map(|x| x.trim().to_owned()).collect())
|
||||
}
|
||||
|
||||
if let Some(hdrs) = matches.value_of("strip") {
|
||||
|
|
@ -491,11 +508,14 @@ fn parse_numeric_range_opts(
|
|||
}
|
||||
|
||||
// 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 {
|
||||
for value in list_items {
|
||||
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);
|
||||
continue;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
use atomicmin::AtomicMin;
|
||||
use bit_vec::BitVec;
|
||||
use byteorder::{BigEndian, WriteBytesExt};
|
||||
use colors::{AlphaOptim, BitDepth, ColorType};
|
||||
|
|
@ -8,6 +9,8 @@ use filters::*;
|
|||
use headers::*;
|
||||
use interlace::{deinterlace_image, interlace_image};
|
||||
use itertools::{flatten, Itertools};
|
||||
#[cfg(feature = "parallel")]
|
||||
use rayon::prelude::*;
|
||||
use reduction::bit_depth::*;
|
||||
use reduction::color::*;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
|
@ -15,9 +18,6 @@ use std::fs::File;
|
|||
use std::io::{Read, Seek, SeekFrom};
|
||||
use std::iter::Iterator;
|
||||
use std::path::Path;
|
||||
#[cfg(feature = "parallel")]
|
||||
use rayon::prelude::*;
|
||||
use atomicmin::AtomicMin;
|
||||
|
||||
const STD_COMPRESSION: u8 = 8;
|
||||
const STD_STRATEGY: u8 = 2; // Huffman only
|
||||
|
|
@ -105,9 +105,10 @@ impl PngData {
|
|||
b"IDAT" => idat_headers.extend(data),
|
||||
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());
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
// Parse the headers into our PngData
|
||||
|
|
@ -618,10 +619,11 @@ impl PngData {
|
|||
STD_WINDOW,
|
||||
&best_size,
|
||||
).ok()
|
||||
.as_ref().map(|l| {
|
||||
best_size.set_min(l.len());
|
||||
l.len()
|
||||
})
|
||||
.as_ref()
|
||||
.map(|l| {
|
||||
best_size.set_min(l.len());
|
||||
l.len()
|
||||
})
|
||||
})
|
||||
.min()
|
||||
.map(|size| (size, image))
|
||||
|
|
|
|||
|
|
@ -135,7 +135,7 @@ pub struct ScanLine<'a> {
|
|||
/// The filter type used to encode the current scan line (0-4)
|
||||
pub filter: u8,
|
||||
/// 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
|
||||
pub pass: Option<u8>,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,10 @@ fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
|
|||
filter.insert(0);
|
||||
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(
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
extern crate oxipng;
|
||||
|
||||
use oxipng::{InFile, OutFile};
|
||||
use oxipng::internal_tests::*;
|
||||
use oxipng::{InFile, OutFile};
|
||||
use std::collections::HashSet;
|
||||
use std::fs::remove_file;
|
||||
use std::path::Path;
|
||||
|
|
@ -15,7 +15,10 @@ fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
|
|||
filter.insert(0);
|
||||
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(
|
||||
|
|
@ -427,7 +430,8 @@ fn zopfli_mode() {
|
|||
|
||||
test_it_converts(
|
||||
input,
|
||||
output, opts,
|
||||
output,
|
||||
opts,
|
||||
ColorType::RGB,
|
||||
BitDepth::Eight,
|
||||
ColorType::RGB,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
extern crate oxipng;
|
||||
|
||||
use oxipng::{InFile, OutFile};
|
||||
use oxipng::internal_tests::*;
|
||||
use oxipng::{InFile, OutFile};
|
||||
use std::collections::HashSet;
|
||||
use std::fs::remove_file;
|
||||
use std::path::Path;
|
||||
|
|
@ -15,7 +15,10 @@ fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
|
|||
filter.insert(0);
|
||||
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(
|
||||
|
|
|
|||
18
tests/lib.rs
18
tests/lib.rs
|
|
@ -49,7 +49,11 @@ fn optimize() {
|
|||
let mut opts: oxipng::Options = Default::default();
|
||||
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());
|
||||
}
|
||||
|
||||
|
|
@ -58,7 +62,11 @@ fn optimize_corrupted() {
|
|||
let mut opts: oxipng::Options = Default::default();
|
||||
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());
|
||||
}
|
||||
|
||||
|
|
@ -67,6 +75,10 @@ fn optimize_apng() {
|
|||
let mut opts: oxipng::Options = Default::default();
|
||||
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());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
extern crate oxipng;
|
||||
|
||||
use oxipng::{InFile, OutFile};
|
||||
use oxipng::internal_tests::*;
|
||||
use oxipng::{InFile, OutFile};
|
||||
use std::collections::HashSet;
|
||||
use std::fs::remove_file;
|
||||
use std::path::Path;
|
||||
|
|
@ -15,7 +15,10 @@ fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
|
|||
filter.insert(0);
|
||||
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(
|
||||
|
|
|
|||
|
|
@ -15,7 +15,10 @@ fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
|
|||
filter.insert(0);
|
||||
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(
|
||||
|
|
@ -273,7 +276,9 @@ fn issue_92_filter_5() {
|
|||
let input = "tests/files/issue-92.png";
|
||||
let (_, mut opts) = get_opts(Path::new(input));
|
||||
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(
|
||||
&input,
|
||||
|
|
|
|||
Loading…
Reference in a new issue