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
|
### 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
2
Cargo.lock
generated
|
|
@ -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)",
|
||||||
|
|
|
||||||
|
|
@ -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" }
|
||||||
|
|
|
||||||
26
README.md
26
README.md
|
|
@ -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'
|
||||||
|
|
|
||||||
|
|
@ -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())
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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
104
package-lock.json
generated
|
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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")),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
171
src/lib.rs
171
src/lib.rs
|
|
@ -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)
|
||||||
|
|
|
||||||
50
src/main.rs
50
src/main.rs
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
|
|
|
||||||
|
|
@ -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>,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
18
tests/lib.rs
18
tests/lib.rs
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue