Merge branch 'master' into master
This commit is contained in:
commit
577018cc80
29 changed files with 1529 additions and 551 deletions
5
.github/workflows/deploy.yml
vendored
5
.github/workflows/deploy.yml
vendored
|
|
@ -11,7 +11,10 @@ jobs:
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
conf: [x86_64, i686]
|
conf: [x86_64]
|
||||||
|
# Temporarily disable i686 binaries, they are failing on linking libdeflate
|
||||||
|
# and I don't have a Windows machine set up to experiment with fixing it.
|
||||||
|
# conf: [x86_64, i686]
|
||||||
|
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,9 @@
|
||||||
|
### Version 4.0.3
|
||||||
|
- Bump itertools to 0.10.x
|
||||||
|
- Temporarily disable i686 releases, which were failing due to an odd linker issue,
|
||||||
|
so that at least amd64 builds will publish successfully
|
||||||
|
- This only relates to the releases published on Github. You can still manually compile oxipng for any platform.
|
||||||
|
|
||||||
### Version 4.0.2
|
### Version 4.0.2
|
||||||
- Includes another update to libdeflater that improves support for targets
|
- Includes another update to libdeflater that improves support for targets
|
||||||
without a C stdlib, like wasm32.
|
without a C stdlib, like wasm32.
|
||||||
|
|
|
||||||
89
Cargo.lock
generated
89
Cargo.lock
generated
|
|
@ -2,9 +2,9 @@
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "adler"
|
name = "adler"
|
||||||
version = "0.2.3"
|
version = "1.0.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e"
|
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "adler32"
|
name = "adler32"
|
||||||
|
|
@ -58,27 +58,21 @@ checksum = "39092a32794787acd8525ee150305ff051b0aa6cc2abaf193924f5ab05425f39"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytemuck"
|
name = "bytemuck"
|
||||||
version = "1.4.1"
|
version = "1.5.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "41aa2ec95ca3b5c54cf73c91acf06d24f4495d5f1b1c12506ae3483d646177ac"
|
checksum = "bed57e2090563b83ba8f83366628ce535a7584c9afa4c9fc0612a03925c6df58"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "byteorder"
|
name = "byteorder"
|
||||||
version = "1.3.4"
|
version = "1.4.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
|
checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.0.66"
|
version = "1.0.67"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48"
|
checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cfg-if"
|
|
||||||
version = "0.1.10"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
|
|
@ -138,12 +132,6 @@ version = "1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "const_fn"
|
|
||||||
version = "0.4.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "cd51eab21ab4fd6a3bf889e2d0958c0a6e3a61ad04260325e919e652a2a62826"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crc"
|
name = "crc"
|
||||||
version = "1.8.1"
|
version = "1.8.1"
|
||||||
|
|
@ -159,7 +147,7 @@ version = "1.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a"
|
checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if 1.0.0",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -168,7 +156,7 @@ version = "0.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775"
|
checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if 1.0.0",
|
"cfg-if",
|
||||||
"crossbeam-utils",
|
"crossbeam-utils",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -178,19 +166,18 @@ version = "0.8.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9"
|
checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if 1.0.0",
|
"cfg-if",
|
||||||
"crossbeam-epoch",
|
"crossbeam-epoch",
|
||||||
"crossbeam-utils",
|
"crossbeam-utils",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-epoch"
|
name = "crossbeam-epoch"
|
||||||
version = "0.9.1"
|
version = "0.9.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a1aaa739f95311c2c7887a76863f500026092fb1dce0161dab577e559ef3569d"
|
checksum = "2584f639eb95fea8c798496315b297cf81b9b58b6d30ab066a75455333cf4b12"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if 1.0.0",
|
"cfg-if",
|
||||||
"const_fn",
|
|
||||||
"crossbeam-utils",
|
"crossbeam-utils",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"memoffset",
|
"memoffset",
|
||||||
|
|
@ -199,12 +186,12 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-utils"
|
name = "crossbeam-utils"
|
||||||
version = "0.8.1"
|
version = "0.8.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d"
|
checksum = "e7e9d99fa91428effe99c5c6d4634cdeba32b8cf784fc428a2a687f61a952c49"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
"cfg-if 1.0.0",
|
"cfg-if",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -250,18 +237,18 @@ checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hermit-abi"
|
name = "hermit-abi"
|
||||||
version = "0.1.17"
|
version = "0.1.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8"
|
checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "image"
|
name = "image"
|
||||||
version = "0.23.12"
|
version = "0.23.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7ce04077ead78e39ae8610ad26216aed811996b043d47beed5090db674f9e9b5"
|
checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
|
|
@ -285,9 +272,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itertools"
|
name = "itertools"
|
||||||
version = "0.9.0"
|
version = "0.10.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b"
|
checksum = "37d572918e350e82412fe766d24b15e6682fb2ed2bbe018280caa810397cb319"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"either",
|
"either",
|
||||||
]
|
]
|
||||||
|
|
@ -300,9 +287,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.81"
|
version = "0.2.87"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb"
|
checksum = "265d751d31d6780a3f956bb5b8022feba2d94eeee5a84ba64f4212eedca42213"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libdeflate-sys"
|
name = "libdeflate-sys"
|
||||||
|
|
@ -324,11 +311,11 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.11"
|
version = "0.4.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b"
|
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if 0.1.10",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -351,9 +338,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "miniz_oxide"
|
name = "miniz_oxide"
|
||||||
version = "0.4.3"
|
version = "0.4.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0f2d26ec3309788e423cfbf68ad1800f061638098d76a83681af979dc4eda19d"
|
checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"adler",
|
"adler",
|
||||||
"autocfg",
|
"autocfg",
|
||||||
|
|
@ -412,7 +399,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxipng"
|
name = "oxipng"
|
||||||
version = "4.0.2"
|
version = "4.0.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bit-vec",
|
"bit-vec",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
|
|
@ -426,7 +413,7 @@ dependencies = [
|
||||||
"itertools",
|
"itertools",
|
||||||
"libdeflater",
|
"libdeflater",
|
||||||
"log",
|
"log",
|
||||||
"miniz_oxide 0.4.3",
|
"miniz_oxide 0.4.4",
|
||||||
"rayon",
|
"rayon",
|
||||||
"rgb",
|
"rgb",
|
||||||
"rustc_version",
|
"rustc_version",
|
||||||
|
|
@ -498,9 +485,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc_version"
|
name = "rustc_version"
|
||||||
version = "0.3.0"
|
version = "0.3.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "65c94201b44764d6d1f7e37c15a8289ed55e546c1762c7f1d57f616966e0c181"
|
checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"semver",
|
"semver",
|
||||||
]
|
]
|
||||||
|
|
@ -522,18 +509,18 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "semver-parser"
|
name = "semver-parser"
|
||||||
version = "0.10.1"
|
version = "0.10.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "42ef146c2ad5e5f4b037cd6ce2ebb775401729b19a82040c1beac9d36c7d1428"
|
checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"pest",
|
"pest",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "stderrlog"
|
name = "stderrlog"
|
||||||
version = "0.5.0"
|
version = "0.5.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b02f316286ae558d83acc93dd81eaba096e746987a7961d4a9ae026842bae67f"
|
checksum = "45a53e2eff3e94a019afa6265e8ee04cb05b9d33fe9f5078b14e4e391d155a38"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"atty",
|
"atty",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
|
|
||||||
15
Cargo.toml
15
Cargo.toml
|
|
@ -16,7 +16,7 @@ license = "MIT"
|
||||||
name = "oxipng"
|
name = "oxipng"
|
||||||
repository = "https://github.com/shssoichiro/oxipng"
|
repository = "https://github.com/shssoichiro/oxipng"
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
version = "4.0.2"
|
version = "4.0.3"
|
||||||
|
|
||||||
[badges]
|
[badges]
|
||||||
travis-ci = { repository = "shssoichiro/oxipng", branch = "master" }
|
travis-ci = { repository = "shssoichiro/oxipng", branch = "master" }
|
||||||
|
|
@ -30,16 +30,16 @@ required-features = ["binary"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bit-vec = "^0.6.3"
|
bit-vec = "^0.6.3"
|
||||||
byteorder = "^1.0.0"
|
byteorder = "^1.4.2"
|
||||||
crc = "^1.2.0"
|
crc = "^1.2.0"
|
||||||
itertools = "^0.9.0"
|
itertools = "^0.10.0"
|
||||||
zopfli = { version = "^0.4.0", optional = true }
|
zopfli = { version = "^0.4.0", optional = true }
|
||||||
miniz_oxide = "0.4"
|
miniz_oxide = "0.4"
|
||||||
rgb = "0.8.25"
|
rgb = "0.8.25"
|
||||||
indexmap = "1.6.1"
|
indexmap = "1.6.1"
|
||||||
libdeflater = { version = "0.7.1", optional = true }
|
libdeflater = { version = "0.7.1", optional = true }
|
||||||
log = "0.4.11"
|
log = "0.4.14"
|
||||||
stderrlog = { version = "0.5.0", optional = true }
|
stderrlog = { version = "0.5.1", optional = true }
|
||||||
crossbeam-channel = "0.5.0"
|
crossbeam-channel = "0.5.0"
|
||||||
|
|
||||||
[dependencies.filetime]
|
[dependencies.filetime]
|
||||||
|
|
@ -82,7 +82,12 @@ parallel = ["rayon", "indexmap/rayon"]
|
||||||
[lib]
|
[lib]
|
||||||
name = "oxipng"
|
name = "oxipng"
|
||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
opt-level = 2
|
opt-level = 2
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = "thin"
|
lto = "thin"
|
||||||
|
|
||||||
|
[profile.dev.package.bit-vec]
|
||||||
|
opt-level = 3
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,7 @@ fn reductions_rgba_to_rgb_16(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgba_16_should_be_rgb_16.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgba_16_should_be_rgb_16.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| reduce_color_type(&png.raw));
|
b.iter(|| reduce_color_type(&png.raw, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
|
|
@ -88,7 +88,7 @@ fn reductions_rgba_to_rgb_8(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgba_8_should_be_rgb_8.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgba_8_should_be_rgb_8.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| reduce_color_type(&png.raw));
|
b.iter(|| reduce_color_type(&png.raw, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
|
|
@ -98,7 +98,7 @@ fn reductions_rgba_to_grayscale_alpha_16(b: &mut Bencher) {
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| reduce_color_type(&png.raw));
|
b.iter(|| reduce_color_type(&png.raw, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
|
|
@ -108,7 +108,7 @@ fn reductions_rgba_to_grayscale_alpha_8(b: &mut Bencher) {
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| reduce_color_type(&png.raw));
|
b.iter(|| reduce_color_type(&png.raw, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
|
|
@ -118,7 +118,7 @@ fn reductions_rgba_to_grayscale_16(b: &mut Bencher) {
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| reduce_color_type(&png.raw));
|
b.iter(|| reduce_color_type(&png.raw, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
|
|
@ -128,7 +128,7 @@ fn reductions_rgba_to_grayscale_8(b: &mut Bencher) {
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| reduce_color_type(&png.raw));
|
b.iter(|| reduce_color_type(&png.raw, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
|
|
@ -138,7 +138,7 @@ fn reductions_rgb_to_grayscale_16(b: &mut Bencher) {
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| reduce_color_type(&png.raw));
|
b.iter(|| reduce_color_type(&png.raw, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
|
|
@ -146,7 +146,7 @@ fn reductions_rgb_to_grayscale_8(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_grayscale_8.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_grayscale_8.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| reduce_color_type(&png.raw));
|
b.iter(|| reduce_color_type(&png.raw, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
|
|
@ -154,7 +154,7 @@ fn reductions_rgba_to_palette_8(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgba_8_should_be_palette_8.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgba_8_should_be_palette_8.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| reduce_color_type(&png.raw));
|
b.iter(|| reduce_color_type(&png.raw, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
|
|
@ -162,7 +162,7 @@ fn reductions_rgb_to_palette_8(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_palette_8.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_palette_8.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| reduce_color_type(&png.raw));
|
b.iter(|| reduce_color_type(&png.raw, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
|
|
|
||||||
1447
package-lock.json
generated
1447
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -21,6 +21,6 @@
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/shssoichiro/oxipng#readme",
|
"homepage": "https://github.com/shssoichiro/oxipng#readme",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"strip-ansi-cli": "^2.0.0"
|
"strip-ansi-cli": "^3.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,13 +9,13 @@ 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)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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() {
|
if val == usize::MAX {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(val)
|
Some(val)
|
||||||
|
|
|
||||||
|
|
@ -101,6 +101,10 @@ impl BitDepth {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Parse a number of bits per channel per pixel into a `BitDepth`
|
/// Parse a number of bits per channel per pixel into a `BitDepth`
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// If depth is unsupported
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn from_u8(depth: u8) -> BitDepth {
|
pub fn from_u8(depth: u8) -> BitDepth {
|
||||||
match depth {
|
match depth {
|
||||||
|
|
|
||||||
10
src/error.rs
10
src/error.rs
|
|
@ -14,15 +14,11 @@ pub enum PngError {
|
||||||
Other(Box<str>),
|
Other(Box<str>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Error for PngError {
|
impl Error for PngError {}
|
||||||
// deprecated
|
|
||||||
fn description(&self) -> &str {
|
|
||||||
""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for PngError {
|
impl fmt::Display for PngError {
|
||||||
#[inline]
|
#[inline]
|
||||||
|
#[cold]
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match *self {
|
match *self {
|
||||||
PngError::DeflatedDataTooLong(_) => f.write_str("deflated data too long"),
|
PngError::DeflatedDataTooLong(_) => f.write_str("deflated data too long"),
|
||||||
|
|
@ -40,7 +36,7 @@ impl fmt::Display for PngError {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PngError {
|
impl PngError {
|
||||||
#[inline]
|
#[cold]
|
||||||
pub fn new(description: &str) -> PngError {
|
pub fn new(description: &str) -> PngError {
|
||||||
PngError::Other(description.into())
|
PngError::Other(description.into())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
110
src/filters.rs
110
src/filters.rs
|
|
@ -1,4 +1,8 @@
|
||||||
|
use crate::error::PngError;
|
||||||
|
|
||||||
pub fn filter_line(filter: u8, bpp: usize, data: &[u8], last_line: &[u8], buf: &mut Vec<u8>) {
|
pub fn filter_line(filter: u8, bpp: usize, data: &[u8], last_line: &[u8], buf: &mut Vec<u8>) {
|
||||||
|
assert!(data.len() >= bpp);
|
||||||
|
assert!(last_line.is_empty() || data.len() == last_line.len());
|
||||||
buf.reserve(data.len());
|
buf.reserve(data.len());
|
||||||
match filter {
|
match filter {
|
||||||
0 => {
|
0 => {
|
||||||
|
|
@ -17,6 +21,7 @@ pub fn filter_line(filter: u8, bpp: usize, data: &[u8], last_line: &[u8], buf: &
|
||||||
if last_line.is_empty() {
|
if last_line.is_empty() {
|
||||||
buf.extend_from_slice(data);
|
buf.extend_from_slice(data);
|
||||||
} else {
|
} else {
|
||||||
|
assert_eq!(data.len(), last_line.len());
|
||||||
buf.extend(
|
buf.extend(
|
||||||
data.iter()
|
data.iter()
|
||||||
.zip(last_line.iter())
|
.zip(last_line.iter())
|
||||||
|
|
@ -62,95 +67,64 @@ pub fn filter_line(filter: u8, bpp: usize, data: &[u8], last_line: &[u8], buf: &
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unfilter_line(filter: u8, bpp: usize, data: &[u8], last_line: &[u8], buf: &mut Vec<u8>) {
|
pub fn unfilter_line(
|
||||||
assert_eq!(buf.len(), 0);
|
filter: u8,
|
||||||
|
bpp: usize,
|
||||||
|
data: &[u8],
|
||||||
|
last_line: &[u8],
|
||||||
|
buf: &mut Vec<u8>,
|
||||||
|
) -> Result<(), PngError> {
|
||||||
|
buf.clear();
|
||||||
buf.reserve(data.len());
|
buf.reserve(data.len());
|
||||||
|
assert!(data.len() >= bpp);
|
||||||
|
assert_eq!(data.len(), last_line.len());
|
||||||
match filter {
|
match filter {
|
||||||
0 => {
|
0 => {
|
||||||
buf.extend_from_slice(data);
|
buf.extend_from_slice(data);
|
||||||
}
|
}
|
||||||
1 => {
|
1 => {
|
||||||
for (i, byte) in data.iter().enumerate() {
|
for (i, &cur) in data.iter().enumerate() {
|
||||||
match i.checked_sub(bpp) {
|
let prev_byte = i.checked_sub(bpp).and_then(|x| buf.get(x).copied());
|
||||||
Some(x) => {
|
buf.push(match prev_byte {
|
||||||
let b = buf[x];
|
Some(b) => cur.wrapping_add(b),
|
||||||
buf.push(byte.wrapping_add(b));
|
None => cur,
|
||||||
}
|
});
|
||||||
None => {
|
|
||||||
buf.push(*byte);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
2 => {
|
2 => {
|
||||||
if last_line.is_empty() {
|
|
||||||
buf.extend_from_slice(data);
|
|
||||||
} else {
|
|
||||||
buf.extend(
|
buf.extend(
|
||||||
data.iter()
|
data.iter()
|
||||||
.zip(last_line.iter())
|
.zip(last_line)
|
||||||
.map(|(cur, last)| cur.wrapping_add(*last)),
|
.map(|(&cur, &last)| cur.wrapping_add(last)),
|
||||||
);
|
);
|
||||||
};
|
|
||||||
}
|
}
|
||||||
3 => {
|
3 => {
|
||||||
for (i, byte) in data.iter().enumerate() {
|
for (i, (&cur, &last)) in data.iter().zip(last_line).enumerate() {
|
||||||
if last_line.is_empty() {
|
let prev_byte = i.checked_sub(bpp).and_then(|x| buf.get(x).copied());
|
||||||
match i.checked_sub(bpp) {
|
buf.push(match prev_byte {
|
||||||
Some(x) => {
|
Some(b) => cur.wrapping_add(((u16::from(b) + u16::from(last)) >> 1) as u8),
|
||||||
let b = buf[x];
|
None => cur.wrapping_add(last >> 1),
|
||||||
buf.push(byte.wrapping_add(b >> 1));
|
});
|
||||||
}
|
|
||||||
None => {
|
|
||||||
buf.push(*byte);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
match i.checked_sub(bpp) {
|
|
||||||
Some(x) => {
|
|
||||||
let b = buf[x];
|
|
||||||
buf.push(byte.wrapping_add(
|
|
||||||
((u16::from(b) + u16::from(last_line[i])) >> 1) as u8,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
buf.push(byte.wrapping_add(last_line[i] >> 1));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
4 => {
|
4 => {
|
||||||
for (i, byte) in data.iter().enumerate() {
|
for (i, (&cur, &up)) in data.iter().zip(last_line).enumerate() {
|
||||||
if last_line.is_empty() {
|
buf.push(
|
||||||
match i.checked_sub(bpp) {
|
match i
|
||||||
Some(x) => {
|
.checked_sub(bpp)
|
||||||
let b = buf[x];
|
.map(|x| (buf.get(x).copied(), last_line.get(x).copied()))
|
||||||
buf.push(byte.wrapping_add(b));
|
{
|
||||||
|
Some((Some(left), Some(left_up))) => {
|
||||||
|
cur.wrapping_add(paeth_predictor(left, up, left_up))
|
||||||
}
|
}
|
||||||
None => {
|
_ => cur.wrapping_add(up),
|
||||||
buf.push(*byte);
|
},
|
||||||
}
|
);
|
||||||
};
|
|
||||||
} else {
|
|
||||||
match i.checked_sub(bpp) {
|
|
||||||
Some(x) => {
|
|
||||||
let b = buf[x];
|
|
||||||
buf.push(byte.wrapping_add(paeth_predictor(
|
|
||||||
b,
|
|
||||||
last_line[i],
|
|
||||||
last_line[x],
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
buf.push(byte.wrapping_add(last_line[i]));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => return Err(PngError::InvalidData),
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paeth_predictor(a: u8, b: u8, c: u8) -> u8 {
|
fn paeth_predictor(a: u8, b: u8, c: u8) -> u8 {
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,45 @@ pub struct IhdrData {
|
||||||
pub interlaced: u8,
|
pub interlaced: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl IhdrData {
|
||||||
|
/// Bits per pixel
|
||||||
|
#[must_use]
|
||||||
|
#[inline]
|
||||||
|
pub fn bpp(&self) -> u8 {
|
||||||
|
self.bit_depth.as_u8() * self.color_type.channels_per_pixel()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Byte length of IDAT that is correct for this IHDR
|
||||||
|
#[must_use]
|
||||||
|
pub fn raw_data_size(&self) -> usize {
|
||||||
|
let w = self.width as usize;
|
||||||
|
let h = self.height as usize;
|
||||||
|
let bpp = self.bpp();
|
||||||
|
|
||||||
|
fn bitmap_size(bpp: u8, w: usize, h: usize) -> usize {
|
||||||
|
(((w / 8) * bpp as usize) + ((w & 7) * bpp as usize + 7) / 8) * h
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.interlaced == 0 {
|
||||||
|
bitmap_size(bpp, w, h) + h
|
||||||
|
} else {
|
||||||
|
let mut size = bitmap_size(bpp, (w + 7) >> 3, (h + 7) >> 3) + ((h + 7) >> 3);
|
||||||
|
if w > 4 {
|
||||||
|
size += bitmap_size(bpp, (w + 3) >> 3, (h + 7) >> 3) + ((h + 7) >> 3);
|
||||||
|
}
|
||||||
|
size += bitmap_size(bpp, (w + 3) >> 2, (h + 3) >> 3) + ((h + 3) >> 3);
|
||||||
|
if w > 2 {
|
||||||
|
size += bitmap_size(bpp, (w + 1) >> 2, (h + 3) >> 2) + ((h + 3) >> 2);
|
||||||
|
}
|
||||||
|
size += bitmap_size(bpp, (w + 1) >> 1, (h + 1) >> 2) + ((h + 1) >> 2);
|
||||||
|
if w > 1 {
|
||||||
|
size += bitmap_size(bpp, w >> 1, (h + 1) >> 1) + ((h + 1) >> 1);
|
||||||
|
}
|
||||||
|
size + bitmap_size(bpp, w, h >> 1) + (h >> 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
/// Options to use for performing operations on headers (such as stripping)
|
/// Options to use for performing operations on headers (such as stripping)
|
||||||
pub enum Headers {
|
pub enum Headers {
|
||||||
|
|
@ -98,12 +137,14 @@ pub fn parse_next_header<'a>(
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut name = [0u8; 4];
|
let mut name = [0_u8; 4];
|
||||||
name.copy_from_slice(chunk_name);
|
name.copy_from_slice(chunk_name);
|
||||||
Ok(Some(RawHeader { name, data }))
|
Ok(Some(RawHeader { name, data }))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_ihdr_header(byte_data: &[u8]) -> PngResult<IhdrData> {
|
pub fn parse_ihdr_header(byte_data: &[u8]) -> PngResult<IhdrData> {
|
||||||
|
// This eliminates bounds checks for the rest of the function
|
||||||
|
let interlaced = byte_data.get(12).copied().ok_or(PngError::TruncatedData)?;
|
||||||
let mut rdr = Cursor::new(&byte_data[0..8]);
|
let mut rdr = Cursor::new(&byte_data[0..8]);
|
||||||
Ok(IhdrData {
|
Ok(IhdrData {
|
||||||
color_type: match byte_data[9] {
|
color_type: match byte_data[9] {
|
||||||
|
|
@ -122,10 +163,14 @@ pub fn parse_ihdr_header(byte_data: &[u8]) -> PngResult<IhdrData> {
|
||||||
16 => BitDepth::Sixteen,
|
16 => BitDepth::Sixteen,
|
||||||
_ => return Err(PngError::new("Unexpected bit depth in header")),
|
_ => return Err(PngError::new("Unexpected bit depth in header")),
|
||||||
},
|
},
|
||||||
width: rdr.read_u32::<BigEndian>().unwrap(),
|
width: rdr
|
||||||
height: rdr.read_u32::<BigEndian>().unwrap(),
|
.read_u32::<BigEndian>()
|
||||||
|
.map_err(|_| PngError::TruncatedData)?,
|
||||||
|
height: rdr
|
||||||
|
.read_u32::<BigEndian>()
|
||||||
|
.map_err(|_| PngError::TruncatedData)?,
|
||||||
compression: byte_data[10],
|
compression: byte_data[10],
|
||||||
filter: byte_data[11],
|
filter: byte_data[11],
|
||||||
interlaced: byte_data[12],
|
interlaced,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use bit_vec::BitVec;
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn interlace_image(png: &PngImage) -> PngImage {
|
pub fn interlace_image(png: &PngImage) -> PngImage {
|
||||||
let mut passes: Vec<BitVec> = vec![BitVec::new(); 7];
|
let mut passes: Vec<BitVec> = vec![BitVec::new(); 7];
|
||||||
let bits_per_pixel = png.ihdr.bit_depth.as_u8() * png.channels_per_pixel();
|
let bits_per_pixel = png.ihdr.bpp();
|
||||||
for (index, line) in png.scan_lines().enumerate() {
|
for (index, line) in png.scan_lines().enumerate() {
|
||||||
match index % 8 {
|
match index % 8 {
|
||||||
// Add filter bytes to passes that will be in the output image
|
// Add filter bytes to passes that will be in the output image
|
||||||
|
|
@ -40,7 +40,7 @@ pub fn interlace_image(png: &PngImage) -> PngImage {
|
||||||
passes[6].extend(BitVec::from_elem(8, false));
|
passes[6].extend(BitVec::from_elem(8, false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let bit_vec = BitVec::from_bytes(&line.data);
|
let bit_vec = BitVec::from_bytes(line.data);
|
||||||
for (i, bit) in bit_vec.iter().enumerate() {
|
for (i, bit) in bit_vec.iter().enumerate() {
|
||||||
// Avoid moving padded 0's into new image
|
// Avoid moving padded 0's into new image
|
||||||
if i >= (png.ihdr.width * u32::from(bits_per_pixel)) as usize {
|
if i >= (png.ihdr.width * u32::from(bits_per_pixel)) as usize {
|
||||||
|
|
@ -95,7 +95,7 @@ pub fn interlace_image(png: &PngImage) -> PngImage {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinterlace_image(png: &PngImage) -> PngImage {
|
pub fn deinterlace_image(png: &PngImage) -> PngImage {
|
||||||
let bits_per_pixel = png.ihdr.bit_depth.as_u8() * png.channels_per_pixel();
|
let bits_per_pixel = png.ihdr.bpp();
|
||||||
let bits_per_line = 8 + bits_per_pixel as usize * png.ihdr.width as usize;
|
let bits_per_line = 8 + bits_per_pixel as usize * png.ihdr.width as usize;
|
||||||
// Initialize each output line with a starting filter byte of 0
|
// Initialize each output line with a starting filter byte of 0
|
||||||
// as well as some blank data
|
// as well as some blank data
|
||||||
|
|
@ -105,7 +105,7 @@ pub fn deinterlace_image(png: &PngImage) -> PngImage {
|
||||||
let mut pass_constants = interlaced_constants(current_pass);
|
let mut pass_constants = interlaced_constants(current_pass);
|
||||||
let mut current_y: usize = pass_constants.y_shift as usize;
|
let mut current_y: usize = pass_constants.y_shift as usize;
|
||||||
for line in png.scan_lines() {
|
for line in png.scan_lines() {
|
||||||
let bit_vec = BitVec::from_bytes(&line.data);
|
let bit_vec = BitVec::from_bytes(line.data);
|
||||||
let bits_in_line = ((png.ihdr.width - u32::from(pass_constants.x_shift)
|
let bits_in_line = ((png.ihdr.width - u32::from(pass_constants.x_shift)
|
||||||
+ u32::from(pass_constants.x_step)
|
+ u32::from(pass_constants.x_step)
|
||||||
- 1)
|
- 1)
|
||||||
|
|
|
||||||
28
src/lib.rs
28
src/lib.rs
|
|
@ -12,6 +12,7 @@
|
||||||
#![warn(clippy::path_buf_push_overwrite)]
|
#![warn(clippy::path_buf_push_overwrite)]
|
||||||
#![warn(clippy::range_plus_one)]
|
#![warn(clippy::range_plus_one)]
|
||||||
#![allow(clippy::cognitive_complexity)]
|
#![allow(clippy::cognitive_complexity)]
|
||||||
|
#![allow(clippy::upper_case_acronyms)]
|
||||||
#![cfg_attr(
|
#![cfg_attr(
|
||||||
not(any(feature = "libdeflater", feature = "zopfli")),
|
not(any(feature = "libdeflater", feature = "zopfli")),
|
||||||
allow(irrefutable_let_patterns),
|
allow(irrefutable_let_patterns),
|
||||||
|
|
@ -97,7 +98,7 @@ impl InFile {
|
||||||
pub fn path(&self) -> Option<&Path> {
|
pub fn path(&self) -> Option<&Path> {
|
||||||
match *self {
|
match *self {
|
||||||
InFile::Path(ref p) => Some(p.as_path()),
|
InFile::Path(ref p) => Some(p.as_path()),
|
||||||
_ => None,
|
InFile::StdIn => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -168,6 +169,10 @@ pub struct Options {
|
||||||
///
|
///
|
||||||
/// Default: `true`
|
/// Default: `true`
|
||||||
pub palette_reduction: bool,
|
pub palette_reduction: bool,
|
||||||
|
/// Whether to attempt grayscale reduction
|
||||||
|
///
|
||||||
|
/// Default: `true`
|
||||||
|
pub grayscale_reduction: bool,
|
||||||
/// Whether to perform IDAT recoding
|
/// Whether to perform IDAT recoding
|
||||||
///
|
///
|
||||||
/// If any type of reduction is performed, IDAT recoding will be performed
|
/// If any type of reduction is performed, IDAT recoding will be performed
|
||||||
|
|
@ -304,6 +309,7 @@ impl Default for Options {
|
||||||
bit_depth_reduction: true,
|
bit_depth_reduction: true,
|
||||||
color_type_reduction: true,
|
color_type_reduction: true,
|
||||||
palette_reduction: true,
|
palette_reduction: true,
|
||||||
|
grayscale_reduction: true,
|
||||||
idat_recoding: true,
|
idat_recoding: true,
|
||||||
strip: Headers::None,
|
strip: Headers::None,
|
||||||
deflate: Deflaters::Zlib {
|
deflate: Deflaters::Zlib {
|
||||||
|
|
@ -616,7 +622,7 @@ fn optimize_png(
|
||||||
);
|
);
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
_ => return None,
|
Err(_) => return None,
|
||||||
};
|
};
|
||||||
|
|
||||||
// update best size across all threads
|
// update best size across all threads
|
||||||
|
|
@ -682,14 +688,14 @@ fn optimize_png(
|
||||||
" file size = {} bytes ({} bytes = {:.2}% decrease)",
|
" file size = {} bytes ({} bytes = {:.2}% decrease)",
|
||||||
output.len(),
|
output.len(),
|
||||||
file_original_size - output.len(),
|
file_original_size - output.len(),
|
||||||
(file_original_size - output.len()) as f64 / file_original_size as f64 * 100f64
|
(file_original_size - output.len()) as f64 / file_original_size as f64 * 100_f64
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
info!(
|
info!(
|
||||||
" file size = {} bytes ({} bytes = {:.2}% increase)",
|
" file size = {} bytes ({} bytes = {:.2}% increase)",
|
||||||
output.len(),
|
output.len(),
|
||||||
output.len() - file_original_size,
|
output.len() - file_original_size,
|
||||||
(output.len() - file_original_size) as f64 / file_original_size as f64 * 100f64
|
(output.len() - file_original_size) as f64 / file_original_size as f64 * 100_f64
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -767,7 +773,7 @@ fn perform_reductions(
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.color_type_reduction {
|
if opts.color_type_reduction {
|
||||||
if let Some(reduced) = reduce_color_type(&png) {
|
if let Some(reduced) = reduce_color_type(&png, opts.grayscale_reduction) {
|
||||||
png = Arc::new(reduced);
|
png = Arc::new(reduced);
|
||||||
eval.try_image(png.clone());
|
eval.try_image(png.clone());
|
||||||
report_reduction(&png);
|
report_reduction(&png);
|
||||||
|
|
@ -812,10 +818,14 @@ impl Deadline {
|
||||||
if let Some(imp) = &self.imp {
|
if let Some(imp) = &self.imp {
|
||||||
let elapsed = imp.start.elapsed();
|
let elapsed = imp.start.elapsed();
|
||||||
if elapsed > imp.timeout {
|
if elapsed > imp.timeout {
|
||||||
if imp
|
if match imp.print_message.compare_exchange(
|
||||||
.print_message
|
true,
|
||||||
.compare_and_swap(true, false, Ordering::SeqCst)
|
false,
|
||||||
{
|
Ordering::SeqCst,
|
||||||
|
Ordering::SeqCst,
|
||||||
|
) {
|
||||||
|
Ok(x) | Err(x) => x,
|
||||||
|
} {
|
||||||
warn!("Timed out after {} second(s)", elapsed.as_secs());
|
warn!("Timed out after {} second(s)", elapsed.as_secs());
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -184,6 +184,9 @@ fn main() {
|
||||||
.arg(Arg::with_name("no-palette-reduction")
|
.arg(Arg::with_name("no-palette-reduction")
|
||||||
.help("No palette reduction")
|
.help("No palette reduction")
|
||||||
.long("np"))
|
.long("np"))
|
||||||
|
.arg(Arg::with_name("no-grayscale-reduction")
|
||||||
|
.help("No grayscale reduction")
|
||||||
|
.long("ng"))
|
||||||
.arg(Arg::with_name("no-reductions")
|
.arg(Arg::with_name("no-reductions")
|
||||||
.help("No reductions")
|
.help("No reductions")
|
||||||
.long("nx"))
|
.long("nx"))
|
||||||
|
|
@ -433,10 +436,15 @@ fn parse_opts_into_struct(
|
||||||
opts.palette_reduction = false;
|
opts.palette_reduction = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if matches.is_present("no-grayscale-reduction") {
|
||||||
|
opts.grayscale_reduction = false;
|
||||||
|
}
|
||||||
|
|
||||||
if matches.is_present("no-reductions") {
|
if matches.is_present("no-reductions") {
|
||||||
opts.bit_depth_reduction = false;
|
opts.bit_depth_reduction = false;
|
||||||
opts.color_type_reduction = false;
|
opts.color_type_reduction = false;
|
||||||
opts.palette_reduction = false;
|
opts.palette_reduction = false;
|
||||||
|
opts.grayscale_reduction = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if matches.is_present("no-recoding") {
|
if matches.is_present("no-recoding") {
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,7 @@ impl PngData {
|
||||||
let mut idat_headers: Vec<u8> = Vec::new();
|
let mut idat_headers: Vec<u8> = Vec::new();
|
||||||
while let Some(header) = parse_next_header(byte_data, &mut byte_offset, fix_errors)? {
|
while let Some(header) = parse_next_header(byte_data, &mut byte_offset, fix_errors)? {
|
||||||
match &header.name {
|
match &header.name {
|
||||||
b"IDAT" => idat_headers.extend(header.data),
|
b"IDAT" => idat_headers.extend_from_slice(header.data),
|
||||||
b"acTL" => return Err(PngError::APNGNotSupported),
|
b"acTL" => return Err(PngError::APNGNotSupported),
|
||||||
_ => {
|
_ => {
|
||||||
aux_headers.insert(header.name, header.data.to_owned());
|
aux_headers.insert(header.name, header.data.to_owned());
|
||||||
|
|
@ -118,6 +118,11 @@ impl PngData {
|
||||||
let ihdr_header = parse_ihdr_header(&ihdr)?;
|
let ihdr_header = parse_ihdr_header(&ihdr)?;
|
||||||
let raw_data = deflate::inflate(idat_headers.as_ref())?;
|
let raw_data = deflate::inflate(idat_headers.as_ref())?;
|
||||||
|
|
||||||
|
// Reject files with incorrect width/height or truncated data
|
||||||
|
if raw_data.len() != ihdr_header.raw_data_size() {
|
||||||
|
return Err(PngError::TruncatedData);
|
||||||
|
}
|
||||||
|
|
||||||
let (palette, transparency_pixel) = Self::palette_to_rgba(
|
let (palette, transparency_pixel) = Self::palette_to_rgba(
|
||||||
ihdr_header.color_type,
|
ihdr_header.color_type,
|
||||||
aux_headers.remove(b"PLTE"),
|
aux_headers.remove(b"PLTE"),
|
||||||
|
|
@ -131,7 +136,7 @@ impl PngData {
|
||||||
transparency_pixel,
|
transparency_pixel,
|
||||||
aux_headers,
|
aux_headers,
|
||||||
};
|
};
|
||||||
raw.data = raw.unfilter_image();
|
raw.data = raw.unfilter_image()?;
|
||||||
// Return the PngData
|
// Return the PngData
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
idat_data: idat_headers,
|
idat_data: idat_headers,
|
||||||
|
|
@ -203,10 +208,10 @@ impl PngData {
|
||||||
.fold(
|
.fold(
|
||||||
0,
|
0,
|
||||||
|prev, (index, px)| {
|
|prev, (index, px)| {
|
||||||
if px.a != 255 {
|
if px.a == 255 {
|
||||||
index + 1
|
|
||||||
} else {
|
|
||||||
prev
|
prev
|
||||||
|
} else {
|
||||||
|
index + 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -276,32 +281,25 @@ impl PngImage {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reverse all filters applied on the image, returning an unfiltered IDAT bytestream
|
/// Reverse all filters applied on the image, returning an unfiltered IDAT bytestream
|
||||||
fn unfilter_image(&self) -> Vec<u8> {
|
fn unfilter_image(&self) -> Result<Vec<u8>, PngError> {
|
||||||
let mut unfiltered = Vec::with_capacity(self.data.len());
|
let mut unfiltered = Vec::with_capacity(self.data.len());
|
||||||
let bpp = ((self.ihdr.bit_depth.as_u8() * self.channels_per_pixel() + 7) / 8) as usize;
|
let bpp = ((self.ihdr.bit_depth.as_u8() * self.channels_per_pixel() + 7) / 8) as usize;
|
||||||
let mut last_line: Vec<u8> = Vec::new();
|
let mut last_line: Vec<u8> = Vec::new();
|
||||||
let mut last_pass = 1;
|
let mut last_pass = None;
|
||||||
let mut unfiltered_buf = Vec::new();
|
let mut unfiltered_buf = Vec::new();
|
||||||
for line in self.scan_lines() {
|
for line in self.scan_lines() {
|
||||||
if let Some(pass) = line.pass {
|
if last_pass != line.pass {
|
||||||
if pass != last_pass {
|
|
||||||
last_line.clear();
|
last_line.clear();
|
||||||
last_pass = pass;
|
last_pass = line.pass;
|
||||||
}
|
}
|
||||||
}
|
last_line.resize(line.data.len(), 0);
|
||||||
unfilter_line(
|
unfilter_line(line.filter, bpp, line.data, &last_line, &mut unfiltered_buf)?;
|
||||||
line.filter,
|
|
||||||
bpp,
|
|
||||||
&line.data,
|
|
||||||
&last_line,
|
|
||||||
&mut unfiltered_buf,
|
|
||||||
);
|
|
||||||
unfiltered.push(0);
|
unfiltered.push(0);
|
||||||
unfiltered.extend_from_slice(&unfiltered_buf);
|
unfiltered.extend_from_slice(&unfiltered_buf);
|
||||||
std::mem::swap(&mut last_line, &mut unfiltered_buf);
|
std::mem::swap(&mut last_line, &mut unfiltered_buf);
|
||||||
unfiltered_buf.clear();
|
unfiltered_buf.clear();
|
||||||
}
|
}
|
||||||
unfiltered
|
Ok(unfiltered)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Apply the specified filter type to all rows in the image
|
/// Apply the specified filter type to all rows in the image
|
||||||
|
|
@ -319,6 +317,9 @@ impl PngImage {
|
||||||
let mut f_buf = Vec::new();
|
let mut f_buf = Vec::new();
|
||||||
for line in self.scan_lines() {
|
for line in self.scan_lines() {
|
||||||
f_buf.clear();
|
f_buf.clear();
|
||||||
|
if last_pass != line.pass {
|
||||||
|
last_line = &[];
|
||||||
|
}
|
||||||
match filter {
|
match filter {
|
||||||
0 | 1 | 2 | 3 | 4 => {
|
0 | 1 | 2 | 3 | 4 => {
|
||||||
let filter = if last_pass == line.pass || filter <= 1 {
|
let filter = if last_pass == line.pass || filter <= 1 {
|
||||||
|
|
@ -327,7 +328,7 @@ impl PngImage {
|
||||||
0
|
0
|
||||||
};
|
};
|
||||||
filtered.push(filter);
|
filtered.push(filter);
|
||||||
filter_line(filter, bpp, &line.data, last_line, &mut f_buf);
|
filter_line(filter, bpp, line.data, last_line, &mut f_buf);
|
||||||
filtered.extend_from_slice(&f_buf);
|
filtered.extend_from_slice(&f_buf);
|
||||||
}
|
}
|
||||||
5 => {
|
5 => {
|
||||||
|
|
@ -336,12 +337,12 @@ impl PngImage {
|
||||||
// http://www.libpng.org/pub/png/book/chapter09.html
|
// http://www.libpng.org/pub/png/book/chapter09.html
|
||||||
let mut best_filter = 0;
|
let mut best_filter = 0;
|
||||||
let mut best_line = Vec::new();
|
let mut best_line = Vec::new();
|
||||||
let mut best_size = std::u64::MAX;
|
let mut best_size = u64::MAX;
|
||||||
|
|
||||||
// Avoid vertical filtering on first line of each interlacing pass
|
// Avoid vertical filtering on first line of each interlacing pass
|
||||||
for filter in if last_pass == line.pass { 0..5 } else { 0..2 } {
|
for filter in if last_pass == line.pass { 0..5 } else { 0..2 } {
|
||||||
filter_line(filter, bpp, &line.data, last_line, &mut f_buf);
|
filter_line(filter, bpp, line.data, last_line, &mut f_buf);
|
||||||
let size = f_buf.iter().fold(0u64, |acc, &x| {
|
let size = f_buf.iter().fold(0_u64, |acc, &x| {
|
||||||
let signed = x as i8;
|
let signed = x as i8;
|
||||||
acc + i16::from(signed).abs() as u64
|
acc + i16::from(signed).abs() as u64
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::png::PngImage;
|
use crate::png::PngImage;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
/// An iterator over the scan lines of a PNG image
|
/// An iterator over the scan lines of a PNG image
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct ScanLines<'a> {
|
pub struct ScanLines<'a> {
|
||||||
iter: ScanLineRanges,
|
iter: ScanLineRanges,
|
||||||
/// A reference to the PNG image being iterated upon
|
/// A reference to the PNG image being iterated upon
|
||||||
|
|
@ -167,8 +167,7 @@ impl Iterator for ScanLineRanges {
|
||||||
let bits_per_line = pixels_per_line * u32::from(self.bits_per_pixel);
|
let bits_per_line = pixels_per_line * u32::from(self.bits_per_pixel);
|
||||||
let bytes_per_line = ((bits_per_line + 7) / 8) as usize;
|
let bytes_per_line = ((bits_per_line + 7) / 8) as usize;
|
||||||
let len = bytes_per_line + 1;
|
let len = bytes_per_line + 1;
|
||||||
assert!(self.left >= len);
|
self.left = self.left.checked_sub(len)?;
|
||||||
self.left -= len;
|
|
||||||
Some((len, current_pass))
|
Some((len, current_pass))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,11 +10,7 @@ pub trait ParallelIterator: Iterator + Sized {
|
||||||
where
|
where
|
||||||
OP: Fn(Self::Item, Self::Item) -> Self::Item + Sync,
|
OP: Fn(Self::Item, Self::Item) -> Self::Item + Sync,
|
||||||
{
|
{
|
||||||
if let Some(a) = self.next() {
|
self.next().map(|a| self.fold(a, op))
|
||||||
Some(self.fold(a, op))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -185,7 +185,9 @@ pub fn reduced_alpha_channel(png: &PngImage) -> Option<PngImage> {
|
||||||
let channels = png.channels_per_pixel();
|
let channels = png.channels_per_pixel();
|
||||||
let bpp = channels * byte_depth;
|
let bpp = channels * byte_depth;
|
||||||
let bpp_mask = bpp - 1;
|
let bpp_mask = bpp - 1;
|
||||||
assert_eq!(0, bpp & bpp_mask);
|
if 0 != bpp & bpp_mask {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
let colored_bytes = bpp - byte_depth;
|
let colored_bytes = bpp - byte_depth;
|
||||||
for line in png.scan_lines() {
|
for line in png.scan_lines() {
|
||||||
for (i, &byte) in line.data.iter().enumerate() {
|
for (i, &byte) in line.data.iter().enumerate() {
|
||||||
|
|
|
||||||
|
|
@ -74,8 +74,7 @@ pub fn reduce_bit_depth(png: &PngImage, minimum_bits: usize) -> Option<PngImage>
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn reduce_bit_depth_8_or_less(png: &PngImage, mut minimum_bits: usize) -> Option<PngImage> {
|
pub fn reduce_bit_depth_8_or_less(png: &PngImage, mut minimum_bits: usize) -> Option<PngImage> {
|
||||||
assert!(minimum_bits >= 1 && minimum_bits < 8);
|
assert!((1..8).contains(&minimum_bits));
|
||||||
let mut reduced = BitVec::with_capacity(png.data.len() * 8);
|
|
||||||
let bit_depth: usize = png.ihdr.bit_depth.as_u8() as usize;
|
let bit_depth: usize = png.ihdr.bit_depth.as_u8() as usize;
|
||||||
if minimum_bits >= bit_depth {
|
if minimum_bits >= bit_depth {
|
||||||
return None;
|
return None;
|
||||||
|
|
@ -109,7 +108,7 @@ pub fn reduce_bit_depth_8_or_less(png: &PngImage, mut minimum_bits: usize) -> Op
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let bit_vec = BitVec::from_bytes(&line.data);
|
let bit_vec = BitVec::from_bytes(line.data);
|
||||||
for byte in bit_vec.to_bytes() {
|
for byte in bit_vec.to_bytes() {
|
||||||
while minimum_bits < bit_depth {
|
while minimum_bits < bit_depth {
|
||||||
let permutations: &[u8] = if minimum_bits == 1 {
|
let permutations: &[u8] = if minimum_bits == 1 {
|
||||||
|
|
@ -123,17 +122,17 @@ pub fn reduce_bit_depth_8_or_less(png: &PngImage, mut minimum_bits: usize) -> Op
|
||||||
};
|
};
|
||||||
if permutations.iter().any(|perm| *perm == byte) {
|
if permutations.iter().any(|perm| *perm == byte) {
|
||||||
break;
|
break;
|
||||||
} else {
|
}
|
||||||
minimum_bits <<= 1;
|
minimum_bits <<= 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
let mut reduced = BitVec::with_capacity(png.data.len() * 8);
|
||||||
for line in png.scan_lines() {
|
for line in png.scan_lines() {
|
||||||
reduced.extend(BitVec::from_bytes(&[line.filter]));
|
reduced.extend(BitVec::from_bytes(&[line.filter]));
|
||||||
let bit_vec = BitVec::from_bytes(&line.data);
|
let bit_vec = BitVec::from_bytes(line.data);
|
||||||
for (i, bit) in bit_vec.iter().enumerate() {
|
for (i, bit) in bit_vec.iter().enumerate() {
|
||||||
let bit_index = bit_depth - (i % bit_depth);
|
let bit_index = bit_depth - (i % bit_depth);
|
||||||
if bit_index <= minimum_bits {
|
if bit_index <= minimum_bits {
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,9 @@ pub fn reduce_rgba_to_grayscale_alpha(png: &PngImage) -> Option<PngImage> {
|
||||||
let byte_depth = png.ihdr.bit_depth.as_u8() >> 3;
|
let byte_depth = png.ihdr.bit_depth.as_u8() >> 3;
|
||||||
let bpp = 4 * byte_depth;
|
let bpp = 4 * byte_depth;
|
||||||
let bpp_mask = bpp - 1;
|
let bpp_mask = bpp - 1;
|
||||||
assert_eq!(0, bpp & bpp_mask);
|
if 0 != bpp & bpp_mask {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
let colored_bytes = bpp - byte_depth;
|
let colored_bytes = bpp - byte_depth;
|
||||||
for line in png.scan_lines() {
|
for line in png.scan_lines() {
|
||||||
reduced.push(line.filter);
|
reduced.push(line.filter);
|
||||||
|
|
@ -109,6 +111,7 @@ pub fn reduced_color_to_palette(png: &PngImage) -> Option<PngImage> {
|
||||||
let transparency_pixel = png
|
let transparency_pixel = png
|
||||||
.transparency_pixel
|
.transparency_pixel
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
.filter(|t| png.ihdr.color_type == ColorType::RGB && t.len() >= 6)
|
||||||
.map(|t| RGB8::new(t[1], t[3], t[5]));
|
.map(|t| RGB8::new(t[1], t[3], t[5]));
|
||||||
for line in png.scan_lines() {
|
for line in png.scan_lines() {
|
||||||
raw_data.push(line.filter);
|
raw_data.push(line.filter);
|
||||||
|
|
@ -147,7 +150,7 @@ pub fn reduced_color_to_palette(png: &PngImage) -> Option<PngImage> {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.max();
|
.max();
|
||||||
let trns_size = num_transparent.map(|n| n + 8).unwrap_or(0);
|
let trns_size = num_transparent.map_or(0, |n| n + 8);
|
||||||
|
|
||||||
let headers_size = palette.len() * 3 + 8 + trns_size;
|
let headers_size = palette.len() * 3 + 8 + trns_size;
|
||||||
if raw_data.len() + headers_size > png.data.len() {
|
if raw_data.len() + headers_size > png.data.len() {
|
||||||
|
|
@ -157,7 +160,10 @@ pub fn reduced_color_to_palette(png: &PngImage) -> Option<PngImage> {
|
||||||
|
|
||||||
let mut aux_headers = png.aux_headers.clone();
|
let mut aux_headers = png.aux_headers.clone();
|
||||||
if let Some(bkgd_header) = png.aux_headers.get(b"bKGD") {
|
if let Some(bkgd_header) = png.aux_headers.get(b"bKGD") {
|
||||||
assert_eq!(bkgd_header.len(), 6);
|
if bkgd_header.len() != 6 {
|
||||||
|
// malformed chunk?
|
||||||
|
return None;
|
||||||
|
}
|
||||||
// In bKGD 16-bit values are used even for 8-bit images
|
// In bKGD 16-bit values are used even for 8-bit images
|
||||||
let bg = RGBA8::new(bkgd_header[1], bkgd_header[3], bkgd_header[5], 255);
|
let bg = RGBA8::new(bkgd_header[1], bkgd_header[3], bkgd_header[5], 255);
|
||||||
let entry = if let Some(&entry) = palette.get(&bg) {
|
let entry = if let Some(&entry) = palette.get(&bg) {
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,9 @@ use rgb::RGBA8;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
pub mod alpha;
|
pub mod alpha;
|
||||||
use crate::alpha::*;
|
use crate::alpha::reduced_alpha_channel;
|
||||||
pub mod bit_depth;
|
pub mod bit_depth;
|
||||||
use crate::bit_depth::*;
|
use crate::bit_depth::reduce_bit_depth_8_or_less;
|
||||||
pub mod color;
|
pub mod color;
|
||||||
use crate::color::*;
|
use crate::color::*;
|
||||||
|
|
||||||
|
|
@ -17,7 +17,6 @@ pub(crate) use crate::bit_depth::reduce_bit_depth;
|
||||||
|
|
||||||
/// Attempt to reduce the number of colors in the palette
|
/// Attempt to reduce the number of colors in the palette
|
||||||
/// Returns `None` if palette hasn't changed
|
/// Returns `None` if palette hasn't changed
|
||||||
#[must_use]
|
|
||||||
pub fn reduced_palette(png: &PngImage) -> Option<PngImage> {
|
pub fn reduced_palette(png: &PngImage) -> Option<PngImage> {
|
||||||
if png.ihdr.color_type != ColorType::Indexed {
|
if png.ihdr.color_type != ColorType::Indexed {
|
||||||
// Can't reduce if there is no palette
|
// Can't reduce if there is no palette
|
||||||
|
|
@ -70,14 +69,14 @@ pub fn reduced_palette(png: &PngImage) -> Option<PngImage> {
|
||||||
.unwrap_or_else(|| RGBA8::new(0, 0, 0, 255));
|
.unwrap_or_else(|| RGBA8::new(0, 0, 0, 255));
|
||||||
((color.a as i32) << 18)
|
((color.a as i32) << 18)
|
||||||
// These are coefficients for standard sRGB to luma conversion
|
// These are coefficients for standard sRGB to luma conversion
|
||||||
- (color.r as i32) * 299
|
- i32::from(color.r) * 299
|
||||||
- (color.g as i32) * 587
|
- i32::from(color.g) * 587
|
||||||
- (color.b as i32) * 114
|
- i32::from(color.b) * 114
|
||||||
};
|
};
|
||||||
color_val(a.0).cmp(&color_val(b.0))
|
color_val(a.0).cmp(&color_val(b.0))
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut next_index = 0u16;
|
let mut next_index = 0_u16;
|
||||||
let mut seen = IndexMap::with_capacity(palette.len());
|
let mut seen = IndexMap::with_capacity(palette.len());
|
||||||
for (i, used) in used_enumerated.iter().cloned() {
|
for (i, used) in used_enumerated.iter().cloned() {
|
||||||
if !used {
|
if !used {
|
||||||
|
|
@ -138,33 +137,33 @@ fn do_palette_reduction(png: &PngImage, palette_map: &[Option<u8>; 256]) -> Opti
|
||||||
}
|
}
|
||||||
|
|
||||||
fn palette_map_to_byte_map(png: &PngImage, palette_map: &[Option<u8>; 256]) -> Option<[u8; 256]> {
|
fn palette_map_to_byte_map(png: &PngImage, palette_map: &[Option<u8>; 256]) -> Option<[u8; 256]> {
|
||||||
let len = png.palette.as_ref().map(|p| p.len()).unwrap_or(0);
|
let len = png.palette.as_ref().map_or(0, |p| p.len());
|
||||||
if (0..len).all(|i| palette_map[i].map_or(true, |to| to == i as u8)) {
|
if (0..len).all(|i| palette_map[i].map_or(true, |to| to == i as u8)) {
|
||||||
// No reduction necessary
|
// No reduction necessary
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut byte_map = [0u8; 256];
|
let mut byte_map = [0_u8; 256];
|
||||||
|
|
||||||
// low bit-depths can be pre-computed for every byte value
|
// low bit-depths can be pre-computed for every byte value
|
||||||
match png.ihdr.bit_depth {
|
match png.ihdr.bit_depth {
|
||||||
BitDepth::Eight => {
|
BitDepth::Eight => {
|
||||||
for byte in 0..=255 {
|
for byte in 0..=255usize {
|
||||||
byte_map[byte as usize] = palette_map[byte as usize].unwrap_or(0)
|
byte_map[byte] = palette_map[byte].unwrap_or(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BitDepth::Four => {
|
BitDepth::Four => {
|
||||||
for byte in 0..=255 {
|
for byte in 0..=255usize {
|
||||||
byte_map[byte as usize] = palette_map[(byte & 0x0F) as usize].unwrap_or(0)
|
byte_map[byte] = palette_map[(byte & 0x0F)].unwrap_or(0)
|
||||||
| (palette_map[(byte >> 4) as usize].unwrap_or(0) << 4);
|
| (palette_map[(byte >> 4)].unwrap_or(0) << 4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BitDepth::Two => {
|
BitDepth::Two => {
|
||||||
for byte in 0..=255 {
|
for byte in 0..=255usize {
|
||||||
byte_map[byte as usize] = palette_map[(byte & 0x03) as usize].unwrap_or(0)
|
byte_map[byte] = palette_map[(byte & 0x03)].unwrap_or(0)
|
||||||
| (palette_map[((byte >> 2) & 0x03) as usize].unwrap_or(0) << 2)
|
| (palette_map[((byte >> 2) & 0x03)].unwrap_or(0) << 2)
|
||||||
| (palette_map[((byte >> 4) & 0x03) as usize].unwrap_or(0) << 4)
|
| (palette_map[((byte >> 4) & 0x03)].unwrap_or(0) << 4)
|
||||||
| (palette_map[(byte >> 6) as usize].unwrap_or(0) << 6);
|
| (palette_map[(byte >> 6)].unwrap_or(0) << 6);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
|
@ -174,12 +173,7 @@ fn palette_map_to_byte_map(png: &PngImage, palette_map: &[Option<u8>; 256]) -> O
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reordered_palette(palette: &[RGBA8], palette_map: &[Option<u8>; 256]) -> Vec<RGBA8> {
|
fn reordered_palette(palette: &[RGBA8], palette_map: &[Option<u8>; 256]) -> Vec<RGBA8> {
|
||||||
let max_index = palette_map
|
let max_index = palette_map.iter().cloned().flatten().max().unwrap_or(0) as usize;
|
||||||
.iter()
|
|
||||||
.cloned()
|
|
||||||
.filter_map(|x| x)
|
|
||||||
.max()
|
|
||||||
.unwrap_or(0) as usize;
|
|
||||||
let mut new_palette = vec![RGBA8::new(0, 0, 0, 255); max_index + 1];
|
let mut new_palette = vec![RGBA8::new(0, 0, 0, 255); max_index + 1];
|
||||||
for (&color, &map_to) in palette.iter().zip(palette_map.iter()) {
|
for (&color, &map_to) in palette.iter().zip(palette_map.iter()) {
|
||||||
if let Some(map_to) = map_to {
|
if let Some(map_to) = map_to {
|
||||||
|
|
@ -191,15 +185,19 @@ fn reordered_palette(palette: &[RGBA8], palette_map: &[Option<u8>; 256]) -> Vec<
|
||||||
|
|
||||||
/// Attempt to reduce the color type of the image
|
/// Attempt to reduce the color type of the image
|
||||||
/// Returns true if the color type was reduced, false otherwise
|
/// Returns true if the color type was reduced, false otherwise
|
||||||
pub fn reduce_color_type(png: &PngImage) -> Option<PngImage> {
|
pub fn reduce_color_type(png: &PngImage, grayscale_reduction: bool) -> Option<PngImage> {
|
||||||
let mut should_reduce_bit_depth = false;
|
let mut should_reduce_bit_depth = false;
|
||||||
let mut reduced = Cow::Borrowed(png);
|
let mut reduced = Cow::Borrowed(png);
|
||||||
|
|
||||||
// Go down one step at a time
|
// Go down one step at a time
|
||||||
// Maybe not the most efficient, but it's safe
|
// Maybe not the most efficient, but it's safe
|
||||||
if reduced.ihdr.color_type == ColorType::RGBA {
|
if reduced.ihdr.color_type == ColorType::RGBA {
|
||||||
if let Some(r) =
|
if let Some(r) = if grayscale_reduction {
|
||||||
reduce_rgba_to_grayscale_alpha(&reduced).or_else(|| reduced_alpha_channel(&reduced))
|
reduce_rgba_to_grayscale_alpha(&reduced)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
.or_else(|| reduced_alpha_channel(&reduced))
|
||||||
{
|
{
|
||||||
reduced = Cow::Owned(r);
|
reduced = Cow::Owned(r);
|
||||||
} else if let Some(r) = reduced_color_to_palette(&reduced) {
|
} else if let Some(r) = reduced_color_to_palette(&reduced) {
|
||||||
|
|
@ -216,8 +214,12 @@ pub fn reduce_color_type(png: &PngImage) -> Option<PngImage> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if reduced.ihdr.color_type == ColorType::RGB {
|
if reduced.ihdr.color_type == ColorType::RGB {
|
||||||
if let Some(r) =
|
if let Some(r) = if grayscale_reduction {
|
||||||
reduce_rgb_to_grayscale(&reduced).or_else(|| reduced_color_to_palette(&reduced))
|
reduce_rgb_to_grayscale(&reduced)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
.or_else(|| reduced_color_to_palette(&reduced))
|
||||||
{
|
{
|
||||||
reduced = Cow::Owned(r);
|
reduced = Cow::Owned(r);
|
||||||
should_reduce_bit_depth = true;
|
should_reduce_bit_depth = true;
|
||||||
|
|
|
||||||
BIN
tests/files/issue-182.png
Normal file
BIN
tests/files/issue-182.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 71 B |
|
|
@ -6,14 +6,16 @@ use std::path::Path;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
|
fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
|
||||||
let mut options = oxipng::Options::default();
|
let mut options = oxipng::Options {
|
||||||
options.force = true;
|
force: true,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
let mut filter = IndexSet::new();
|
let mut filter = IndexSet::new();
|
||||||
filter.insert(0);
|
filter.insert(0);
|
||||||
options.filter = filter;
|
options.filter = filter;
|
||||||
|
|
||||||
(
|
(
|
||||||
OutFile::Path(Some(input.with_extension("out.png").to_owned())),
|
OutFile::Path(Some(input.with_extension("out.png"))),
|
||||||
options,
|
options,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -55,7 +57,7 @@ fn test_it_converts(
|
||||||
if let Some(palette) = png.raw.palette.as_ref() {
|
if let Some(palette) = png.raw.palette.as_ref() {
|
||||||
assert!(palette.len() <= 1 << (png.raw.ihdr.bit_depth.as_u8() as usize));
|
assert!(palette.len() <= 1 << (png.raw.ihdr.bit_depth.as_u8() as usize));
|
||||||
} else {
|
} else {
|
||||||
assert!(png.raw.ihdr.color_type != ColorType::Indexed);
|
assert_ne!(png.raw.ihdr.color_type, ColorType::Indexed);
|
||||||
}
|
}
|
||||||
|
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
|
|
|
||||||
|
|
@ -10,14 +10,16 @@ use std::path::Path;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
|
fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
|
||||||
let mut options = oxipng::Options::default();
|
let mut options = oxipng::Options {
|
||||||
options.force = true;
|
force: true,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
let mut filter = IndexSet::new();
|
let mut filter = IndexSet::new();
|
||||||
filter.insert(0);
|
filter.insert(0);
|
||||||
options.filter = filter;
|
options.filter = filter;
|
||||||
|
|
||||||
(
|
(
|
||||||
OutFile::Path(Some(input.with_extension("out.png").to_owned())),
|
OutFile::Path(Some(input.with_extension("out.png"))),
|
||||||
options,
|
options,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -200,7 +202,7 @@ fn strip_headers_list() {
|
||||||
let output = output.path().unwrap();
|
let output = output.path().unwrap();
|
||||||
assert!(output.exists());
|
assert!(output.exists());
|
||||||
|
|
||||||
let png = match PngData::new(&output, opts.fix_errors) {
|
let png = match PngData::new(output, opts.fix_errors) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(x) => {
|
Err(x) => {
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
|
|
@ -234,7 +236,7 @@ fn strip_headers_safe() {
|
||||||
let output = output.path().unwrap();
|
let output = output.path().unwrap();
|
||||||
assert!(output.exists());
|
assert!(output.exists());
|
||||||
|
|
||||||
let png = match PngData::new(&output, opts.fix_errors) {
|
let png = match PngData::new(output, opts.fix_errors) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(x) => {
|
Err(x) => {
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
|
|
@ -268,7 +270,7 @@ fn strip_headers_all() {
|
||||||
let output = output.path().unwrap();
|
let output = output.path().unwrap();
|
||||||
assert!(output.exists());
|
assert!(output.exists());
|
||||||
|
|
||||||
let png = match PngData::new(&output, opts.fix_errors) {
|
let png = match PngData::new(output, opts.fix_errors) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(x) => {
|
Err(x) => {
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
|
|
@ -302,7 +304,7 @@ fn strip_headers_none() {
|
||||||
let output = output.path().unwrap();
|
let output = output.path().unwrap();
|
||||||
assert!(output.exists());
|
assert!(output.exists());
|
||||||
|
|
||||||
let png = match PngData::new(&output, opts.fix_errors) {
|
let png = match PngData::new(output, opts.fix_errors) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(x) => {
|
Err(x) => {
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
|
|
@ -334,7 +336,7 @@ fn interlacing_0_to_1() {
|
||||||
let output = output.path().unwrap();
|
let output = output.path().unwrap();
|
||||||
assert!(output.exists());
|
assert!(output.exists());
|
||||||
|
|
||||||
let png = match PngData::new(&output, opts.fix_errors) {
|
let png = match PngData::new(output, opts.fix_errors) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(x) => {
|
Err(x) => {
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
|
|
@ -364,7 +366,7 @@ fn interlacing_1_to_0() {
|
||||||
let output = output.path().unwrap();
|
let output = output.path().unwrap();
|
||||||
assert!(output.exists());
|
assert!(output.exists());
|
||||||
|
|
||||||
let png = match PngData::new(&output, opts.fix_errors) {
|
let png = match PngData::new(output, opts.fix_errors) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(x) => {
|
Err(x) => {
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
|
|
@ -396,7 +398,7 @@ fn interlacing_0_to_1_small_files() {
|
||||||
let output = output.path().unwrap();
|
let output = output.path().unwrap();
|
||||||
assert!(output.exists());
|
assert!(output.exists());
|
||||||
|
|
||||||
let png = match PngData::new(&output, opts.fix_errors) {
|
let png = match PngData::new(output, opts.fix_errors) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(x) => {
|
Err(x) => {
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
|
|
@ -430,7 +432,7 @@ fn interlacing_1_to_0_small_files() {
|
||||||
let output = output.path().unwrap();
|
let output = output.path().unwrap();
|
||||||
assert!(output.exists());
|
assert!(output.exists());
|
||||||
|
|
||||||
let png = match PngData::new(&output, opts.fix_errors) {
|
let png = match PngData::new(output, opts.fix_errors) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(x) => {
|
Err(x) => {
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
|
|
@ -465,7 +467,7 @@ fn interlaced_0_to_1_other_filter_mode() {
|
||||||
let output = output.path().unwrap();
|
let output = output.path().unwrap();
|
||||||
assert!(output.exists());
|
assert!(output.exists());
|
||||||
|
|
||||||
let png = match PngData::new(&output, opts.fix_errors) {
|
let png = match PngData::new(output, opts.fix_errors) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(x) => {
|
Err(x) => {
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
|
|
@ -560,7 +562,7 @@ fn fix_errors() {
|
||||||
let output = output.path().unwrap();
|
let output = output.path().unwrap();
|
||||||
assert!(output.exists());
|
assert!(output.exists());
|
||||||
|
|
||||||
let png = match PngData::new(&output, false) {
|
let png = match PngData::new(output, false) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(x) => {
|
Err(x) => {
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
|
|
@ -576,6 +578,7 @@ fn fix_errors() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(feature = "zopfli")]
|
||||||
fn zopfli_mode() {
|
fn zopfli_mode() {
|
||||||
let input = PathBuf::from("tests/files/zopfli_mode.png");
|
let input = PathBuf::from("tests/files/zopfli_mode.png");
|
||||||
let (output, mut opts) = get_opts(&input);
|
let (output, mut opts) = get_opts(&input);
|
||||||
|
|
@ -593,6 +596,7 @@ fn zopfli_mode() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(feature = "libdeflater")]
|
||||||
fn libdeflater_mode() {
|
fn libdeflater_mode() {
|
||||||
let input = PathBuf::from("tests/files/zopfli_mode.png");
|
let input = PathBuf::from("tests/files/zopfli_mode.png");
|
||||||
let (output, mut opts) = get_opts(&input);
|
let (output, mut opts) = get_opts(&input);
|
||||||
|
|
|
||||||
|
|
@ -6,14 +6,16 @@ use std::path::Path;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
|
fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
|
||||||
let mut options = oxipng::Options::default();
|
let mut options = oxipng::Options {
|
||||||
options.force = true;
|
force: true,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
let mut filter = IndexSet::new();
|
let mut filter = IndexSet::new();
|
||||||
filter.insert(0);
|
filter.insert(0);
|
||||||
options.filter = filter;
|
options.filter = filter;
|
||||||
|
|
||||||
(
|
(
|
||||||
OutFile::Path(Some(input.with_extension("out.png").to_owned())),
|
OutFile::Path(Some(input.with_extension("out.png"))),
|
||||||
options,
|
options,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
use oxipng;
|
|
||||||
|
|
||||||
use oxipng::Headers;
|
use oxipng::Headers;
|
||||||
use oxipng::OutFile;
|
use oxipng::OutFile;
|
||||||
use std::default::Default;
|
use std::default::Default;
|
||||||
|
|
|
||||||
|
|
@ -6,14 +6,16 @@ use std::path::Path;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
|
fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
|
||||||
let mut options = oxipng::Options::default();
|
let mut options = oxipng::Options {
|
||||||
options.force = true;
|
force: true,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
let mut filter = IndexSet::new();
|
let mut filter = IndexSet::new();
|
||||||
filter.insert(0);
|
filter.insert(0);
|
||||||
options.filter = filter;
|
options.filter = filter;
|
||||||
|
|
||||||
(
|
(
|
||||||
OutFile::Path(Some(input.with_extension("out.png").to_owned())),
|
OutFile::Path(Some(input.with_extension("out.png"))),
|
||||||
options,
|
options,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -711,7 +713,7 @@ fn small_files() {
|
||||||
let output = output.path().unwrap();
|
let output = output.path().unwrap();
|
||||||
assert!(output.exists());
|
assert!(output.exists());
|
||||||
|
|
||||||
let png = match PngData::new(&output, opts.fix_errors) {
|
let png = match PngData::new(output, opts.fix_errors) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(x) => {
|
Err(x) => {
|
||||||
remove_file(&output).ok();
|
remove_file(&output).ok();
|
||||||
|
|
@ -743,7 +745,7 @@ fn palette_should_be_reduced_with_dupes() {
|
||||||
let output = output.path().unwrap();
|
let output = output.path().unwrap();
|
||||||
assert!(output.exists());
|
assert!(output.exists());
|
||||||
|
|
||||||
let png = match PngData::new(&output, opts.fix_errors) {
|
let png = match PngData::new(output, opts.fix_errors) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(x) => {
|
Err(x) => {
|
||||||
remove_file(&output).ok();
|
remove_file(&output).ok();
|
||||||
|
|
@ -776,7 +778,7 @@ fn palette_should_be_reduced_with_unused() {
|
||||||
let output = output.path().unwrap();
|
let output = output.path().unwrap();
|
||||||
assert!(output.exists());
|
assert!(output.exists());
|
||||||
|
|
||||||
let png = match PngData::new(&output, opts.fix_errors) {
|
let png = match PngData::new(output, opts.fix_errors) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(x) => {
|
Err(x) => {
|
||||||
remove_file(&output).ok();
|
remove_file(&output).ok();
|
||||||
|
|
@ -809,7 +811,7 @@ fn palette_should_be_reduced_with_both() {
|
||||||
let output = output.path().unwrap();
|
let output = output.path().unwrap();
|
||||||
assert!(output.exists());
|
assert!(output.exists());
|
||||||
|
|
||||||
let png = match PngData::new(&output, opts.fix_errors) {
|
let png = match PngData::new(output, opts.fix_errors) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(x) => {
|
Err(x) => {
|
||||||
remove_file(&output).ok();
|
remove_file(&output).ok();
|
||||||
|
|
|
||||||
|
|
@ -6,14 +6,16 @@ use std::path::Path;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
|
fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
|
||||||
let mut options = oxipng::Options::default();
|
let mut options = oxipng::Options {
|
||||||
options.force = true;
|
force: true,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
let mut filter = IndexSet::new();
|
let mut filter = IndexSet::new();
|
||||||
filter.insert(0);
|
filter.insert(0);
|
||||||
options.filter = filter;
|
options.filter = filter;
|
||||||
|
|
||||||
(
|
(
|
||||||
OutFile::Path(Some(input.with_extension("out.png").to_owned())),
|
OutFile::Path(Some(input.with_extension("out.png"))),
|
||||||
options,
|
options,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -62,7 +64,7 @@ fn test_it_converts(
|
||||||
if let Some(palette) = png.raw.palette.as_ref() {
|
if let Some(palette) = png.raw.palette.as_ref() {
|
||||||
assert!(palette.len() <= 1 << (png.raw.ihdr.bit_depth.as_u8() as usize));
|
assert!(palette.len() <= 1 << (png.raw.ihdr.bit_depth.as_u8() as usize));
|
||||||
} else {
|
} else {
|
||||||
assert!(png.raw.ihdr.color_type != ColorType::Indexed);
|
assert_ne!(png.raw.ihdr.color_type, ColorType::Indexed);
|
||||||
}
|
}
|
||||||
|
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
|
|
@ -99,7 +101,7 @@ fn issue_42() {
|
||||||
let output = output.path().unwrap();
|
let output = output.path().unwrap();
|
||||||
assert!(output.exists());
|
assert!(output.exists());
|
||||||
|
|
||||||
let png = match PngData::new(&output, opts.fix_errors) {
|
let png = match PngData::new(output, opts.fix_errors) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(x) => {
|
Err(x) => {
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
|
|
@ -287,12 +289,10 @@ 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(
|
let output = OutFile::Path(Some(Path::new(input).with_extension("-f5-out.png")));
|
||||||
Path::new(input).with_extension("-f5-out.png").to_owned(),
|
|
||||||
));
|
|
||||||
|
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
&input,
|
input,
|
||||||
Some((output, opts)),
|
Some((output, opts)),
|
||||||
ColorType::Grayscale,
|
ColorType::Grayscale,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
|
|
@ -308,9 +308,7 @@ fn issue_113_white() {
|
||||||
opts.interlace = Some(1);
|
opts.interlace = Some(1);
|
||||||
opts.alphas = IndexSet::new();
|
opts.alphas = IndexSet::new();
|
||||||
opts.alphas.insert(AlphaOptim::Black);
|
opts.alphas.insert(AlphaOptim::Black);
|
||||||
let output = OutFile::Path(Some(
|
let output = OutFile::Path(Some(Path::new(input).with_extension("-white-out.png")));
|
||||||
Path::new(input).with_extension("-white-out.png").to_owned(),
|
|
||||||
));
|
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
input,
|
input,
|
||||||
Some((output, opts)),
|
Some((output, opts)),
|
||||||
|
|
@ -328,9 +326,7 @@ fn issue_113_black() {
|
||||||
opts.interlace = Some(1);
|
opts.interlace = Some(1);
|
||||||
opts.alphas = IndexSet::new();
|
opts.alphas = IndexSet::new();
|
||||||
opts.alphas.insert(AlphaOptim::Black);
|
opts.alphas.insert(AlphaOptim::Black);
|
||||||
let output = OutFile::Path(Some(
|
let output = OutFile::Path(Some(Path::new(input).with_extension("-black-out.png")));
|
||||||
Path::new(input).with_extension("-black-out.png").to_owned(),
|
|
||||||
));
|
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
input,
|
input,
|
||||||
Some((output, opts)),
|
Some((output, opts)),
|
||||||
|
|
@ -348,9 +344,7 @@ fn issue_113_right() {
|
||||||
opts.interlace = Some(1);
|
opts.interlace = Some(1);
|
||||||
opts.alphas = IndexSet::new();
|
opts.alphas = IndexSet::new();
|
||||||
opts.alphas.insert(AlphaOptim::Right);
|
opts.alphas.insert(AlphaOptim::Right);
|
||||||
let output = OutFile::Path(Some(
|
let output = OutFile::Path(Some(Path::new(input).with_extension("-right-out.png")));
|
||||||
Path::new(input).with_extension("-right-out.png").to_owned(),
|
|
||||||
));
|
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
input,
|
input,
|
||||||
Some((output, opts)),
|
Some((output, opts)),
|
||||||
|
|
@ -368,9 +362,7 @@ fn issue_113_left() {
|
||||||
opts.interlace = Some(1);
|
opts.interlace = Some(1);
|
||||||
opts.alphas = IndexSet::new();
|
opts.alphas = IndexSet::new();
|
||||||
opts.alphas.insert(AlphaOptim::Left);
|
opts.alphas.insert(AlphaOptim::Left);
|
||||||
let output = OutFile::Path(Some(
|
let output = OutFile::Path(Some(Path::new(input).with_extension("-left-out.png")));
|
||||||
Path::new(input).with_extension("-left-out.png").to_owned(),
|
|
||||||
));
|
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
input,
|
input,
|
||||||
Some((output, opts)),
|
Some((output, opts)),
|
||||||
|
|
@ -388,9 +380,7 @@ fn issue_113_up() {
|
||||||
opts.interlace = Some(1);
|
opts.interlace = Some(1);
|
||||||
opts.alphas = IndexSet::new();
|
opts.alphas = IndexSet::new();
|
||||||
opts.alphas.insert(AlphaOptim::Up);
|
opts.alphas.insert(AlphaOptim::Up);
|
||||||
let output = OutFile::Path(Some(
|
let output = OutFile::Path(Some(Path::new(input).with_extension("-up-out.png")));
|
||||||
Path::new(input).with_extension("-up-out.png").to_owned(),
|
|
||||||
));
|
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
input,
|
input,
|
||||||
Some((output, opts)),
|
Some((output, opts)),
|
||||||
|
|
@ -408,9 +398,7 @@ fn issue_113_down() {
|
||||||
opts.interlace = Some(1);
|
opts.interlace = Some(1);
|
||||||
opts.alphas = IndexSet::new();
|
opts.alphas = IndexSet::new();
|
||||||
opts.alphas.insert(AlphaOptim::Down);
|
opts.alphas.insert(AlphaOptim::Down);
|
||||||
let output = OutFile::Path(Some(
|
let output = OutFile::Path(Some(Path::new(input).with_extension("-down-out.png")));
|
||||||
Path::new(input).with_extension("-down-out.png").to_owned(),
|
|
||||||
));
|
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
input,
|
input,
|
||||||
Some((output, opts)),
|
Some((output, opts)),
|
||||||
|
|
@ -440,9 +428,7 @@ fn issue_133_black() {
|
||||||
let (_, mut opts) = get_opts(Path::new(input));
|
let (_, mut opts) = get_opts(Path::new(input));
|
||||||
opts.alphas = IndexSet::new();
|
opts.alphas = IndexSet::new();
|
||||||
opts.alphas.insert(AlphaOptim::Black);
|
opts.alphas.insert(AlphaOptim::Black);
|
||||||
let output = OutFile::Path(Some(
|
let output = OutFile::Path(Some(Path::new(input).with_extension("-black-out.png")));
|
||||||
Path::new(input).with_extension("-black-out.png").to_owned(),
|
|
||||||
));
|
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
input,
|
input,
|
||||||
Some((output, opts)),
|
Some((output, opts)),
|
||||||
|
|
@ -459,9 +445,7 @@ fn issue_133_white() {
|
||||||
let (_, mut opts) = get_opts(Path::new(input));
|
let (_, mut opts) = get_opts(Path::new(input));
|
||||||
opts.alphas = IndexSet::new();
|
opts.alphas = IndexSet::new();
|
||||||
opts.alphas.insert(AlphaOptim::White);
|
opts.alphas.insert(AlphaOptim::White);
|
||||||
let output = OutFile::Path(Some(
|
let output = OutFile::Path(Some(Path::new(input).with_extension("-white-out.png")));
|
||||||
Path::new(input).with_extension("-white-out.png").to_owned(),
|
|
||||||
));
|
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
input,
|
input,
|
||||||
Some((output, opts)),
|
Some((output, opts)),
|
||||||
|
|
@ -478,9 +462,7 @@ fn issue_133_up() {
|
||||||
let (_, mut opts) = get_opts(Path::new(input));
|
let (_, mut opts) = get_opts(Path::new(input));
|
||||||
opts.alphas = IndexSet::new();
|
opts.alphas = IndexSet::new();
|
||||||
opts.alphas.insert(AlphaOptim::Up);
|
opts.alphas.insert(AlphaOptim::Up);
|
||||||
let output = OutFile::Path(Some(
|
let output = OutFile::Path(Some(Path::new(input).with_extension("-up-out.png")));
|
||||||
Path::new(input).with_extension("-up-out.png").to_owned(),
|
|
||||||
));
|
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
input,
|
input,
|
||||||
Some((output, opts)),
|
Some((output, opts)),
|
||||||
|
|
@ -497,9 +479,7 @@ fn issue_133_down() {
|
||||||
let (_, mut opts) = get_opts(Path::new(input));
|
let (_, mut opts) = get_opts(Path::new(input));
|
||||||
opts.alphas = IndexSet::new();
|
opts.alphas = IndexSet::new();
|
||||||
opts.alphas.insert(AlphaOptim::Down);
|
opts.alphas.insert(AlphaOptim::Down);
|
||||||
let output = OutFile::Path(Some(
|
let output = OutFile::Path(Some(Path::new(input).with_extension("-down-out.png")));
|
||||||
Path::new(input).with_extension("-down-out.png").to_owned(),
|
|
||||||
));
|
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
input,
|
input,
|
||||||
Some((output, opts)),
|
Some((output, opts)),
|
||||||
|
|
@ -516,9 +496,7 @@ fn issue_133_right() {
|
||||||
let (_, mut opts) = get_opts(Path::new(input));
|
let (_, mut opts) = get_opts(Path::new(input));
|
||||||
opts.alphas = IndexSet::new();
|
opts.alphas = IndexSet::new();
|
||||||
opts.alphas.insert(AlphaOptim::Right);
|
opts.alphas.insert(AlphaOptim::Right);
|
||||||
let output = OutFile::Path(Some(
|
let output = OutFile::Path(Some(Path::new(input).with_extension("-right-out.png")));
|
||||||
Path::new(input).with_extension("-right-out.png").to_owned(),
|
|
||||||
));
|
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
input,
|
input,
|
||||||
Some((output, opts)),
|
Some((output, opts)),
|
||||||
|
|
@ -535,9 +513,7 @@ fn issue_133_left() {
|
||||||
let (_, mut opts) = get_opts(Path::new(input));
|
let (_, mut opts) = get_opts(Path::new(input));
|
||||||
opts.alphas = IndexSet::new();
|
opts.alphas = IndexSet::new();
|
||||||
opts.alphas.insert(AlphaOptim::Left);
|
opts.alphas.insert(AlphaOptim::Left);
|
||||||
let output = OutFile::Path(Some(
|
let output = OutFile::Path(Some(Path::new(input).with_extension("-left-out.png")));
|
||||||
Path::new(input).with_extension("-left-out.png").to_owned(),
|
|
||||||
));
|
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
input,
|
input,
|
||||||
Some((output, opts)),
|
Some((output, opts)),
|
||||||
|
|
@ -635,7 +611,7 @@ fn issue_175() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn issue_182() {
|
fn issue_182() {
|
||||||
let input = "tests/files/issue-175.png";
|
let input = "tests/files/issue-182.png";
|
||||||
let (output, mut opts) = get_opts(Path::new(input));
|
let (output, mut opts) = get_opts(Path::new(input));
|
||||||
opts.interlace = Some(0);
|
opts.interlace = Some(0);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue