New filter strategies (#461)
* Refactor filters as enum * Include filter byte in filter output * Add entropy filter * Add bigrams filter * Add bigram entropy filter * Add brute filter * Replace bit-vec * Add tests and benches * Show filters in help * Use FxHasher in color to palette * Use windows function for minor improvement
This commit is contained in:
parent
420d904ba9
commit
a41d7de348
19 changed files with 938 additions and 332 deletions
54
Cargo.lock
generated
54
Cargo.lock
generated
|
|
@ -31,18 +31,24 @@ 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 = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bit-vec"
|
|
||||||
version = "0.6.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "1.3.2"
|
version = "1.3.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitvec"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c"
|
||||||
|
dependencies = [
|
||||||
|
"funty",
|
||||||
|
"radium",
|
||||||
|
"tap",
|
||||||
|
"wyz",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytemuck"
|
name = "bytemuck"
|
||||||
version = "1.12.1"
|
version = "1.12.1"
|
||||||
|
|
@ -194,6 +200,12 @@ dependencies = [
|
||||||
"miniz_oxide",
|
"miniz_oxide",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "funty"
|
||||||
|
version = "2.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "glob"
|
name = "glob"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
|
|
@ -362,7 +374,7 @@ checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff"
|
||||||
name = "oxipng"
|
name = "oxipng"
|
||||||
version = "6.0.1"
|
version = "6.0.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bit-vec",
|
"bitvec",
|
||||||
"clap",
|
"clap",
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
"filetime",
|
"filetime",
|
||||||
|
|
@ -373,6 +385,7 @@ dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"rayon",
|
"rayon",
|
||||||
"rgb",
|
"rgb",
|
||||||
|
"rustc-hash",
|
||||||
"rustc_version",
|
"rustc_version",
|
||||||
"stderrlog",
|
"stderrlog",
|
||||||
"wild",
|
"wild",
|
||||||
|
|
@ -391,6 +404,12 @@ dependencies = [
|
||||||
"miniz_oxide",
|
"miniz_oxide",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "radium"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rayon"
|
name = "rayon"
|
||||||
version = "1.5.3"
|
version = "1.5.3"
|
||||||
|
|
@ -433,6 +452,12 @@ dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustc-hash"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc_version"
|
name = "rustc_version"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
|
|
@ -472,6 +497,12 @@ 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 = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tap"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "termcolor"
|
name = "termcolor"
|
||||||
version = "1.1.3"
|
version = "1.1.3"
|
||||||
|
|
@ -585,6 +616,15 @@ version = "0.36.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
|
checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wyz"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "30b31594f29d27036c383b53b59ed3476874d518f0efb151b27a4c275141390e"
|
||||||
|
dependencies = [
|
||||||
|
"tap",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zopfli"
|
name = "zopfli"
|
||||||
version = "0.7.1"
|
version = "0.7.1"
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,6 @@ path = "src/main.rs"
|
||||||
required-features = ["binary"]
|
required-features = ["binary"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bit-vec = "0.6.3"
|
|
||||||
itertools = "0.10.3"
|
itertools = "0.10.3"
|
||||||
zopfli = { version = "0.7.1", optional = true }
|
zopfli = { version = "0.7.1", optional = true }
|
||||||
rgb = "0.8.33"
|
rgb = "0.8.33"
|
||||||
|
|
@ -32,6 +31,8 @@ libdeflater = "0.11.0"
|
||||||
log = "0.4.17"
|
log = "0.4.17"
|
||||||
stderrlog = { version = "0.5.3", optional = true, default-features = false }
|
stderrlog = { version = "0.5.3", optional = true, default-features = false }
|
||||||
crossbeam-channel = "0.5.6"
|
crossbeam-channel = "0.5.6"
|
||||||
|
bitvec = "1.0.1"
|
||||||
|
rustc-hash = "1.1.0"
|
||||||
|
|
||||||
[dependencies.filetime]
|
[dependencies.filetime]
|
||||||
optional = true
|
optional = true
|
||||||
|
|
@ -73,5 +74,5 @@ opt-level = 2
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = "thin"
|
lto = "thin"
|
||||||
|
|
||||||
[profile.dev.package.bit-vec]
|
[profile.dev.package.bitvec]
|
||||||
opt-level = 3
|
opt-level = 3
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
extern crate oxipng;
|
extern crate oxipng;
|
||||||
extern crate test;
|
extern crate test;
|
||||||
|
|
||||||
use oxipng::internal_tests::*;
|
use oxipng::{internal_tests::*, RowFilter};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use test::Bencher;
|
use test::Bencher;
|
||||||
|
|
||||||
|
|
@ -13,7 +13,7 @@ fn filters_16_bits_filter_0(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(0);
|
png.raw.filter_image(RowFilter::None);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -23,7 +23,7 @@ fn filters_8_bits_filter_0(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(0);
|
png.raw.filter_image(RowFilter::None);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -35,7 +35,7 @@ fn filters_4_bits_filter_0(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(0);
|
png.raw.filter_image(RowFilter::None);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -47,7 +47,7 @@ fn filters_2_bits_filter_0(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(0);
|
png.raw.filter_image(RowFilter::None);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -59,7 +59,7 @@ fn filters_1_bits_filter_0(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(0);
|
png.raw.filter_image(RowFilter::None);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -69,7 +69,7 @@ fn filters_16_bits_filter_1(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(1);
|
png.raw.filter_image(RowFilter::Sub);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -79,7 +79,7 @@ fn filters_8_bits_filter_1(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(1);
|
png.raw.filter_image(RowFilter::Sub);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -91,7 +91,7 @@ fn filters_4_bits_filter_1(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(1);
|
png.raw.filter_image(RowFilter::Sub);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -103,7 +103,7 @@ fn filters_2_bits_filter_1(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(1);
|
png.raw.filter_image(RowFilter::Sub);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -115,7 +115,7 @@ fn filters_1_bits_filter_1(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(1);
|
png.raw.filter_image(RowFilter::Sub);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -125,7 +125,7 @@ fn filters_16_bits_filter_2(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(2);
|
png.raw.filter_image(RowFilter::Up);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -135,7 +135,7 @@ fn filters_8_bits_filter_2(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(2);
|
png.raw.filter_image(RowFilter::Up);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -147,7 +147,7 @@ fn filters_4_bits_filter_2(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(2);
|
png.raw.filter_image(RowFilter::Up);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -159,7 +159,7 @@ fn filters_2_bits_filter_2(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(2);
|
png.raw.filter_image(RowFilter::Up);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -171,7 +171,7 @@ fn filters_1_bits_filter_2(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(2);
|
png.raw.filter_image(RowFilter::Up);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -181,7 +181,7 @@ fn filters_16_bits_filter_3(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(3);
|
png.raw.filter_image(RowFilter::Average);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -191,7 +191,7 @@ fn filters_8_bits_filter_3(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(3);
|
png.raw.filter_image(RowFilter::Average);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -203,7 +203,7 @@ fn filters_4_bits_filter_3(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(3);
|
png.raw.filter_image(RowFilter::Average);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -215,7 +215,7 @@ fn filters_2_bits_filter_3(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(3);
|
png.raw.filter_image(RowFilter::Average);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -227,7 +227,7 @@ fn filters_1_bits_filter_3(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(3);
|
png.raw.filter_image(RowFilter::Average);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -237,7 +237,7 @@ fn filters_16_bits_filter_4(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(4);
|
png.raw.filter_image(RowFilter::Paeth);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -247,7 +247,7 @@ fn filters_8_bits_filter_4(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(4);
|
png.raw.filter_image(RowFilter::Paeth);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -259,7 +259,7 @@ fn filters_4_bits_filter_4(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(4);
|
png.raw.filter_image(RowFilter::Paeth);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -271,7 +271,7 @@ fn filters_2_bits_filter_4(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(4);
|
png.raw.filter_image(RowFilter::Paeth);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -283,7 +283,7 @@ fn filters_1_bits_filter_4(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(4);
|
png.raw.filter_image(RowFilter::Paeth);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -293,7 +293,7 @@ fn filters_16_bits_filter_5(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(5);
|
png.raw.filter_image(RowFilter::MinSum);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -303,7 +303,7 @@ fn filters_8_bits_filter_5(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(5);
|
png.raw.filter_image(RowFilter::MinSum);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -315,7 +315,7 @@ fn filters_4_bits_filter_5(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(5);
|
png.raw.filter_image(RowFilter::MinSum);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -327,7 +327,7 @@ fn filters_2_bits_filter_5(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(5);
|
png.raw.filter_image(RowFilter::MinSum);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -339,6 +339,6 @@ fn filters_1_bits_filter_5(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(5);
|
png.raw.filter_image(RowFilter::MinSum);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
58
benches/strategies.rs
Normal file
58
benches/strategies.rs
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
#![feature(test)]
|
||||||
|
|
||||||
|
extern crate oxipng;
|
||||||
|
extern crate test;
|
||||||
|
|
||||||
|
use oxipng::{internal_tests::*, RowFilter};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use test::Bencher;
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn filters_minsum(b: &mut Bencher) {
|
||||||
|
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
|
||||||
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
|
b.iter(|| {
|
||||||
|
png.raw.filter_image(RowFilter::MinSum);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn filters_entropy(b: &mut Bencher) {
|
||||||
|
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
|
||||||
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
|
b.iter(|| {
|
||||||
|
png.raw.filter_image(RowFilter::Entropy);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn filters_bigrams(b: &mut Bencher) {
|
||||||
|
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
|
||||||
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
|
b.iter(|| {
|
||||||
|
png.raw.filter_image(RowFilter::Bigrams);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn filters_bigent(b: &mut Bencher) {
|
||||||
|
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
|
||||||
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
|
b.iter(|| {
|
||||||
|
png.raw.filter_image(RowFilter::BigEnt);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn filters_brute(b: &mut Bencher) {
|
||||||
|
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
|
||||||
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
|
b.iter(|| {
|
||||||
|
png.raw.filter_image(RowFilter::Brute);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -3,10 +3,9 @@
|
||||||
|
|
||||||
use crate::atomicmin::AtomicMin;
|
use crate::atomicmin::AtomicMin;
|
||||||
use crate::deflate;
|
use crate::deflate;
|
||||||
|
use crate::filters::RowFilter;
|
||||||
use crate::png::PngData;
|
use crate::png::PngData;
|
||||||
use crate::png::PngImage;
|
use crate::png::PngImage;
|
||||||
use crate::png::STD_COMPRESSION;
|
|
||||||
use crate::png::STD_FILTERS;
|
|
||||||
#[cfg(not(feature = "parallel"))]
|
#[cfg(not(feature = "parallel"))]
|
||||||
use crate::rayon;
|
use crate::rayon;
|
||||||
use crate::Deadline;
|
use crate::Deadline;
|
||||||
|
|
@ -19,9 +18,13 @@ use std::sync::atomic::AtomicUsize;
|
||||||
use std::sync::atomic::Ordering::SeqCst;
|
use std::sync::atomic::Ordering::SeqCst;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// Must use normal (lazy) compression, as faster ones (greedy) are not representative
|
||||||
|
const STD_COMPRESSION: u8 = 5;
|
||||||
|
const STD_FILTERS: [RowFilter; 2] = [RowFilter::None, RowFilter::MinSum];
|
||||||
|
|
||||||
struct Candidate {
|
struct Candidate {
|
||||||
image: PngData,
|
image: PngData,
|
||||||
filter: u8,
|
filter: RowFilter,
|
||||||
// first wins tie-breaker
|
// first wins tie-breaker
|
||||||
nth: usize,
|
nth: usize,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
291
src/filters.rs
291
src/filters.rs
|
|
@ -1,130 +1,191 @@
|
||||||
|
use std::{fmt::Display, mem::transmute};
|
||||||
|
|
||||||
use crate::error::PngError;
|
use crate::error::PngError;
|
||||||
|
|
||||||
pub fn filter_line(filter: u8, bpp: usize, data: &[u8], last_line: &[u8], buf: &mut Vec<u8>) {
|
#[repr(u8)]
|
||||||
assert!(data.len() >= bpp);
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
|
||||||
assert!(last_line.is_empty() || data.len() == last_line.len());
|
pub enum RowFilter {
|
||||||
buf.reserve(data.len());
|
// Standard filter types
|
||||||
match filter {
|
None,
|
||||||
0 => {
|
Sub,
|
||||||
buf.extend_from_slice(data);
|
Up,
|
||||||
|
Average,
|
||||||
|
Paeth,
|
||||||
|
// Heuristic strategies
|
||||||
|
MinSum,
|
||||||
|
Entropy,
|
||||||
|
Bigrams,
|
||||||
|
BigEnt,
|
||||||
|
Brute,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<u8> for RowFilter {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||||
|
if value > Self::LAST {
|
||||||
|
return Err(());
|
||||||
}
|
}
|
||||||
1 => {
|
unsafe { transmute(value as i8) }
|
||||||
buf.extend_from_slice(&data[0..bpp]);
|
|
||||||
buf.extend(
|
|
||||||
data.iter()
|
|
||||||
.skip(bpp)
|
|
||||||
.zip(data.iter())
|
|
||||||
.map(|(cur, last)| cur.wrapping_sub(*last)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
2 => {
|
|
||||||
if last_line.is_empty() {
|
|
||||||
buf.extend_from_slice(data);
|
|
||||||
} else {
|
|
||||||
assert_eq!(data.len(), last_line.len());
|
|
||||||
buf.extend(
|
|
||||||
data.iter()
|
|
||||||
.zip(last_line.iter())
|
|
||||||
.map(|(cur, last)| cur.wrapping_sub(*last)),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
3 => {
|
|
||||||
for (i, byte) in data.iter().enumerate() {
|
|
||||||
if last_line.is_empty() {
|
|
||||||
buf.push(match i.checked_sub(bpp) {
|
|
||||||
Some(x) => byte.wrapping_sub(data[x] >> 1),
|
|
||||||
None => *byte,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
buf.push(match i.checked_sub(bpp) {
|
|
||||||
Some(x) => byte.wrapping_sub(
|
|
||||||
((u16::from(data[x]) + u16::from(last_line[i])) >> 1) as u8,
|
|
||||||
),
|
|
||||||
None => byte.wrapping_sub(last_line[i] >> 1),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
4 => {
|
|
||||||
for (i, byte) in data.iter().enumerate() {
|
|
||||||
if last_line.is_empty() {
|
|
||||||
buf.push(match i.checked_sub(bpp) {
|
|
||||||
Some(x) => byte.wrapping_sub(data[x]),
|
|
||||||
None => *byte,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
buf.push(match i.checked_sub(bpp) {
|
|
||||||
Some(x) => {
|
|
||||||
byte.wrapping_sub(paeth_predictor(data[x], last_line[i], last_line[x]))
|
|
||||||
}
|
|
||||||
None => byte.wrapping_sub(last_line[i]),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unfilter_line(
|
impl Display for RowFilter {
|
||||||
filter: u8,
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
bpp: usize,
|
write!(
|
||||||
data: &[u8],
|
f,
|
||||||
last_line: &[u8],
|
"{:8}",
|
||||||
buf: &mut Vec<u8>,
|
match *self {
|
||||||
) -> Result<(), PngError> {
|
Self::None => "None",
|
||||||
buf.clear();
|
Self::Sub => "Sub",
|
||||||
buf.reserve(data.len());
|
Self::Up => "Up",
|
||||||
assert!(data.len() >= bpp);
|
Self::Average => "Average",
|
||||||
assert_eq!(data.len(), last_line.len());
|
Self::Paeth => "Paeth",
|
||||||
match filter {
|
Self::MinSum => "MinSum",
|
||||||
0 => {
|
Self::Entropy => "Entropy",
|
||||||
buf.extend_from_slice(data);
|
Self::Bigrams => "Bigrams",
|
||||||
}
|
Self::BigEnt => "BigEnt",
|
||||||
1 => {
|
Self::Brute => "Brute",
|
||||||
for (i, &cur) in data.iter().enumerate() {
|
|
||||||
let prev_byte = i.checked_sub(bpp).and_then(|x| buf.get(x).copied());
|
|
||||||
buf.push(match prev_byte {
|
|
||||||
Some(b) => cur.wrapping_add(b),
|
|
||||||
None => cur,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
2 => {
|
}
|
||||||
buf.extend(
|
}
|
||||||
data.iter()
|
|
||||||
.zip(last_line)
|
impl RowFilter {
|
||||||
.map(|(&cur, &last)| cur.wrapping_add(last)),
|
pub const LAST: u8 = Self::Brute as u8;
|
||||||
);
|
pub const STANDARD: [Self; 5] = [Self::None, Self::Sub, Self::Up, Self::Average, Self::Paeth];
|
||||||
}
|
pub const SINGLE_LINE: [Self; 2] = [Self::None, Self::Sub];
|
||||||
3 => {
|
|
||||||
for (i, (&cur, &last)) in data.iter().zip(last_line).enumerate() {
|
pub fn filter_line(self, bpp: usize, data: &[u8], last_line: &[u8], buf: &mut Vec<u8>) {
|
||||||
let prev_byte = i.checked_sub(bpp).and_then(|x| buf.get(x).copied());
|
assert!(data.len() >= bpp);
|
||||||
buf.push(match prev_byte {
|
assert!(last_line.is_empty() || data.len() == last_line.len());
|
||||||
Some(b) => cur.wrapping_add(((u16::from(b) + u16::from(last)) >> 1) as u8),
|
buf.clear();
|
||||||
None => cur.wrapping_add(last >> 1),
|
buf.reserve(data.len() + 1);
|
||||||
});
|
buf.push(self as u8);
|
||||||
|
match self {
|
||||||
|
Self::None => {
|
||||||
|
buf.extend_from_slice(data);
|
||||||
}
|
}
|
||||||
}
|
Self::Sub => {
|
||||||
4 => {
|
buf.extend_from_slice(&data[0..bpp]);
|
||||||
for (i, (&cur, &up)) in data.iter().zip(last_line).enumerate() {
|
buf.extend(
|
||||||
buf.push(
|
data.iter()
|
||||||
match i
|
.skip(bpp)
|
||||||
.checked_sub(bpp)
|
.zip(data.iter())
|
||||||
.map(|x| (buf.get(x).copied(), last_line.get(x).copied()))
|
.map(|(cur, last)| cur.wrapping_sub(*last)),
|
||||||
{
|
|
||||||
Some((Some(left), Some(left_up))) => {
|
|
||||||
cur.wrapping_add(paeth_predictor(left, up, left_up))
|
|
||||||
}
|
|
||||||
_ => cur.wrapping_add(up),
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Self::Up => {
|
||||||
|
if last_line.is_empty() {
|
||||||
|
buf.extend_from_slice(data);
|
||||||
|
} else {
|
||||||
|
assert_eq!(data.len(), last_line.len());
|
||||||
|
buf.extend(
|
||||||
|
data.iter()
|
||||||
|
.zip(last_line.iter())
|
||||||
|
.map(|(cur, last)| cur.wrapping_sub(*last)),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Self::Average => {
|
||||||
|
for (i, byte) in data.iter().enumerate() {
|
||||||
|
if last_line.is_empty() {
|
||||||
|
buf.push(match i.checked_sub(bpp) {
|
||||||
|
Some(x) => byte.wrapping_sub(data[x] >> 1),
|
||||||
|
None => *byte,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
buf.push(match i.checked_sub(bpp) {
|
||||||
|
Some(x) => byte.wrapping_sub(
|
||||||
|
((u16::from(data[x]) + u16::from(last_line[i])) >> 1) as u8,
|
||||||
|
),
|
||||||
|
None => byte.wrapping_sub(last_line[i] >> 1),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self::Paeth => {
|
||||||
|
for (i, byte) in data.iter().enumerate() {
|
||||||
|
if last_line.is_empty() {
|
||||||
|
buf.push(match i.checked_sub(bpp) {
|
||||||
|
Some(x) => byte.wrapping_sub(data[x]),
|
||||||
|
None => *byte,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
buf.push(match i.checked_sub(bpp) {
|
||||||
|
Some(x) => byte.wrapping_sub(paeth_predictor(
|
||||||
|
data[x],
|
||||||
|
last_line[i],
|
||||||
|
last_line[x],
|
||||||
|
)),
|
||||||
|
None => byte.wrapping_sub(last_line[i]),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
_ => return Err(PngError::InvalidData),
|
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
|
pub fn unfilter_line(
|
||||||
|
self,
|
||||||
|
bpp: usize,
|
||||||
|
data: &[u8],
|
||||||
|
last_line: &[u8],
|
||||||
|
buf: &mut Vec<u8>,
|
||||||
|
) -> Result<(), PngError> {
|
||||||
|
buf.clear();
|
||||||
|
buf.reserve(data.len());
|
||||||
|
assert!(data.len() >= bpp);
|
||||||
|
assert_eq!(data.len(), last_line.len());
|
||||||
|
match self {
|
||||||
|
Self::None => {
|
||||||
|
buf.extend_from_slice(data);
|
||||||
|
}
|
||||||
|
Self::Sub => {
|
||||||
|
for (i, &cur) in data.iter().enumerate() {
|
||||||
|
let prev_byte = i.checked_sub(bpp).and_then(|x| buf.get(x).copied());
|
||||||
|
buf.push(match prev_byte {
|
||||||
|
Some(b) => cur.wrapping_add(b),
|
||||||
|
None => cur,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self::Up => {
|
||||||
|
buf.extend(
|
||||||
|
data.iter()
|
||||||
|
.zip(last_line)
|
||||||
|
.map(|(&cur, &last)| cur.wrapping_add(last)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Self::Average => {
|
||||||
|
for (i, (&cur, &last)) in data.iter().zip(last_line).enumerate() {
|
||||||
|
let prev_byte = i.checked_sub(bpp).and_then(|x| buf.get(x).copied());
|
||||||
|
buf.push(match prev_byte {
|
||||||
|
Some(b) => cur.wrapping_add(((u16::from(b) + u16::from(last)) >> 1) as u8),
|
||||||
|
None => cur.wrapping_add(last >> 1),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self::Paeth => {
|
||||||
|
for (i, (&cur, &up)) in data.iter().zip(last_line).enumerate() {
|
||||||
|
buf.push(
|
||||||
|
match i
|
||||||
|
.checked_sub(bpp)
|
||||||
|
.map(|x| (buf.get(x).copied(), last_line.get(x).copied()))
|
||||||
|
{
|
||||||
|
Some((Some(left), Some(left_up))) => {
|
||||||
|
cur.wrapping_add(paeth_predictor(left, up, left_up))
|
||||||
|
}
|
||||||
|
_ => cur.wrapping_add(up),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => 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 {
|
||||||
|
|
|
||||||
|
|
@ -1,47 +1,47 @@
|
||||||
use crate::headers::IhdrData;
|
use crate::headers::IhdrData;
|
||||||
use crate::png::PngImage;
|
use crate::png::PngImage;
|
||||||
use bit_vec::BitVec;
|
use bitvec::prelude::*;
|
||||||
|
|
||||||
#[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<u8, Msb0>> = vec![BitVec::new(); 7];
|
||||||
let bits_per_pixel = png.ihdr.bpp();
|
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
|
||||||
0 => {
|
0 => {
|
||||||
passes[0].extend(BitVec::from_elem(8, false));
|
passes[0].extend_from_raw_slice(&[0]);
|
||||||
if png.ihdr.width >= 5 {
|
if png.ihdr.width >= 5 {
|
||||||
passes[1].extend(BitVec::from_elem(8, false));
|
passes[1].extend_from_raw_slice(&[0]);
|
||||||
}
|
}
|
||||||
if png.ihdr.width >= 3 {
|
if png.ihdr.width >= 3 {
|
||||||
passes[3].extend(BitVec::from_elem(8, false));
|
passes[3].extend_from_raw_slice(&[0]);
|
||||||
}
|
}
|
||||||
if png.ihdr.width >= 2 {
|
if png.ihdr.width >= 2 {
|
||||||
passes[5].extend(BitVec::from_elem(8, false));
|
passes[5].extend_from_raw_slice(&[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
4 => {
|
4 => {
|
||||||
passes[2].extend(BitVec::from_elem(8, false));
|
passes[2].extend_from_raw_slice(&[0]);
|
||||||
if png.ihdr.width >= 3 {
|
if png.ihdr.width >= 3 {
|
||||||
passes[3].extend(BitVec::from_elem(8, false));
|
passes[3].extend_from_raw_slice(&[0]);
|
||||||
}
|
}
|
||||||
if png.ihdr.width >= 2 {
|
if png.ihdr.width >= 2 {
|
||||||
passes[5].extend(BitVec::from_elem(8, false));
|
passes[5].extend_from_raw_slice(&[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
2 | 6 => {
|
2 | 6 => {
|
||||||
passes[4].extend(BitVec::from_elem(8, false));
|
passes[4].extend_from_raw_slice(&[0]);
|
||||||
if png.ihdr.width >= 2 {
|
if png.ihdr.width >= 2 {
|
||||||
passes[5].extend(BitVec::from_elem(8, false));
|
passes[5].extend_from_raw_slice(&[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
passes[6].extend(BitVec::from_elem(8, false));
|
passes[6].extend_from_raw_slice(&[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let bit_vec = BitVec::from_bytes(line.data);
|
let bit_vec = line.data.view_bits::<Msb0>();
|
||||||
for (i, bit) in bit_vec.iter().enumerate() {
|
for (i, bit) in bit_vec.iter().by_vals().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 {
|
||||||
break;
|
break;
|
||||||
|
|
@ -79,7 +79,7 @@ pub fn interlace_image(png: &PngImage) -> PngImage {
|
||||||
|
|
||||||
let mut output = Vec::with_capacity(png.data.len());
|
let mut output = Vec::with_capacity(png.data.len());
|
||||||
for pass in &passes {
|
for pass in &passes {
|
||||||
output.extend(pass.to_bytes());
|
output.extend_from_slice(pass.as_raw_slice());
|
||||||
}
|
}
|
||||||
|
|
||||||
PngImage {
|
PngImage {
|
||||||
|
|
@ -99,19 +99,19 @@ pub fn deinterlace_image(png: &PngImage) -> PngImage {
|
||||||
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
|
||||||
let mut lines: Vec<BitVec> =
|
let mut lines: Vec<BitVec<u8, Msb0>> =
|
||||||
vec![BitVec::from_elem(bits_per_line, false); png.ihdr.height as usize];
|
vec![bitvec![u8, Msb0; 0; bits_per_line]; png.ihdr.height as usize];
|
||||||
let mut current_pass = 1;
|
let mut current_pass = 1;
|
||||||
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 = line.data.view_bits::<Msb0>();
|
||||||
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)
|
||||||
/ u32::from(pass_constants.x_step)) as usize
|
/ u32::from(pass_constants.x_step)) as usize
|
||||||
* bits_per_pixel as usize;
|
* bits_per_pixel as usize;
|
||||||
for (i, bit) in bit_vec.iter().enumerate() {
|
for (i, bit) in bit_vec.iter().by_vals().enumerate() {
|
||||||
// Avoid moving padded 0's into new image
|
// Avoid moving padded 0's into new image
|
||||||
if i >= bits_in_line {
|
if i >= bits_in_line {
|
||||||
break;
|
break;
|
||||||
|
|
@ -157,7 +157,7 @@ pub fn deinterlace_image(png: &PngImage) -> PngImage {
|
||||||
while line.len() % 8 != 0 {
|
while line.len() % 8 != 0 {
|
||||||
line.push(false);
|
line.push(false);
|
||||||
}
|
}
|
||||||
output.extend(line.to_bytes());
|
output.extend_from_slice(line.as_raw_slice());
|
||||||
}
|
}
|
||||||
PngImage {
|
PngImage {
|
||||||
data: output,
|
data: output,
|
||||||
|
|
|
||||||
28
src/lib.rs
28
src/lib.rs
|
|
@ -45,6 +45,7 @@ use std::time::{Duration, Instant};
|
||||||
pub use crate::colors::AlphaOptim;
|
pub use crate::colors::AlphaOptim;
|
||||||
pub use crate::deflate::Deflaters;
|
pub use crate::deflate::Deflaters;
|
||||||
pub use crate::error::PngError;
|
pub use crate::error::PngError;
|
||||||
|
pub use crate::filters::RowFilter;
|
||||||
pub use crate::headers::Headers;
|
pub use crate::headers::Headers;
|
||||||
pub use indexmap::{IndexMap, IndexSet};
|
pub use indexmap::{IndexMap, IndexSet};
|
||||||
|
|
||||||
|
|
@ -149,7 +150,7 @@ pub struct Options {
|
||||||
/// Which filters to try on the file (0-5)
|
/// Which filters to try on the file (0-5)
|
||||||
///
|
///
|
||||||
/// Default: `0,5`
|
/// Default: `0,5`
|
||||||
pub filter: IndexSet<u8>,
|
pub filter: IndexSet<RowFilter>,
|
||||||
/// Whether to change the interlacing type of the file.
|
/// Whether to change the interlacing type of the file.
|
||||||
///
|
///
|
||||||
/// `None` will not change the current interlacing type.
|
/// `None` will not change the current interlacing type.
|
||||||
|
|
@ -253,9 +254,10 @@ impl Options {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_preset_3(mut self) -> Self {
|
fn apply_preset_3(mut self) -> Self {
|
||||||
for i in 1..=4 {
|
self.filter.insert(RowFilter::Sub);
|
||||||
self.filter.insert(i);
|
self.filter.insert(RowFilter::Up);
|
||||||
}
|
self.filter.insert(RowFilter::Average);
|
||||||
|
self.filter.insert(RowFilter::Paeth);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -292,8 +294,8 @@ impl Default for Options {
|
||||||
fn default() -> Options {
|
fn default() -> Options {
|
||||||
// Default settings based on -o 2 from the CLI interface
|
// Default settings based on -o 2 from the CLI interface
|
||||||
let mut filter = IndexSet::new();
|
let mut filter = IndexSet::new();
|
||||||
filter.insert(0);
|
filter.insert(RowFilter::None);
|
||||||
filter.insert(5);
|
filter.insert(RowFilter::MinSum);
|
||||||
let mut compression = IndexSet::new();
|
let mut compression = IndexSet::new();
|
||||||
compression.insert(11);
|
compression.insert(11);
|
||||||
// We always need NoOp to be present
|
// We always need NoOp to be present
|
||||||
|
|
@ -465,7 +467,7 @@ pub fn optimize_from_memory(data: &[u8], opts: &Options) -> PngResult<Vec<u8>> {
|
||||||
#[derive(Debug, PartialEq, PartialOrd, Clone, Copy)]
|
#[derive(Debug, PartialEq, PartialOrd, Clone, Copy)]
|
||||||
/// Defines options to be used for a single compression trial
|
/// Defines options to be used for a single compression trial
|
||||||
struct TrialOptions {
|
struct TrialOptions {
|
||||||
pub filter: u8,
|
pub filter: RowFilter,
|
||||||
pub compression: u8,
|
pub compression: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -511,9 +513,9 @@ fn optimize_png(
|
||||||
let use_filter = if png.raw.ihdr.bit_depth.as_u8() >= 8
|
let use_filter = if png.raw.ihdr.bit_depth.as_u8() >= 8
|
||||||
&& png.raw.ihdr.color_type != colors::ColorType::Indexed
|
&& png.raw.ihdr.color_type != colors::ColorType::Indexed
|
||||||
{
|
{
|
||||||
5
|
RowFilter::MinSum
|
||||||
} else {
|
} else {
|
||||||
0
|
RowFilter::None
|
||||||
};
|
};
|
||||||
if filter.is_empty() {
|
if filter.is_empty() {
|
||||||
filter.insert(use_filter);
|
filter.insert(use_filter);
|
||||||
|
|
@ -570,7 +572,7 @@ fn optimize_png(
|
||||||
|
|
||||||
info!("Trying: {} combinations", results.len());
|
info!("Trying: {} combinations", results.len());
|
||||||
|
|
||||||
let filters: IndexMap<u8, Vec<u8>> = filter
|
let filters: IndexMap<RowFilter, Vec<u8>> = filter
|
||||||
.par_iter()
|
.par_iter()
|
||||||
.with_max_len(1)
|
.with_max_len(1)
|
||||||
.map(|f| {
|
.map(|f| {
|
||||||
|
|
@ -601,7 +603,7 @@ fn optimize_png(
|
||||||
Ok(n) => n,
|
Ok(n) => n,
|
||||||
Err(PngError::DeflatedDataTooLong(max)) => {
|
Err(PngError::DeflatedDataTooLong(max)) => {
|
||||||
debug!(
|
debug!(
|
||||||
" zc = {} f = {} >{} bytes",
|
" zc = {} f = {} >{} bytes",
|
||||||
trial.compression, trial.filter, max,
|
trial.compression, trial.filter, max,
|
||||||
);
|
);
|
||||||
return None;
|
return None;
|
||||||
|
|
@ -614,7 +616,7 @@ fn optimize_png(
|
||||||
best_size.set_min(new_size);
|
best_size.set_min(new_size);
|
||||||
|
|
||||||
debug!(
|
debug!(
|
||||||
" zc = {} f = {} {} bytes",
|
" zc = {} f = {} {} bytes",
|
||||||
trial.compression,
|
trial.compression,
|
||||||
trial.filter,
|
trial.filter,
|
||||||
new_idat.len()
|
new_idat.len()
|
||||||
|
|
@ -638,7 +640,7 @@ fn optimize_png(
|
||||||
png.idat_data = idat_data;
|
png.idat_data = idat_data;
|
||||||
info!("Found better combination:");
|
info!("Found better combination:");
|
||||||
info!(
|
info!(
|
||||||
" zc = {} f = {} {} bytes",
|
" zc = {} f = {} {} bytes",
|
||||||
opts.compression,
|
opts.compression,
|
||||||
opts.filter,
|
opts.filter,
|
||||||
png.idat_data.len()
|
png.idat_data.len()
|
||||||
|
|
|
||||||
29
src/main.rs
29
src/main.rs
|
|
@ -20,6 +20,7 @@ use oxipng::AlphaOptim;
|
||||||
use oxipng::Deflaters;
|
use oxipng::Deflaters;
|
||||||
use oxipng::Headers;
|
use oxipng::Headers;
|
||||||
use oxipng::Options;
|
use oxipng::Options;
|
||||||
|
use oxipng::RowFilter;
|
||||||
use oxipng::{InFile, OutFile};
|
use oxipng::{InFile, OutFile};
|
||||||
use std::fs::DirBuilder;
|
use std::fs::DirBuilder;
|
||||||
#[cfg(feature = "zopfli")]
|
#[cfg(feature = "zopfli")]
|
||||||
|
|
@ -168,11 +169,15 @@ fn main() {
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::new("filters")
|
Arg::new("filters")
|
||||||
.help("PNG delta filters (0-5) - Default: 0,5")
|
.help(&*format!(
|
||||||
|
"PNG delta filters (0-{}) - Default: 0,{}",
|
||||||
|
RowFilter::LAST,
|
||||||
|
RowFilter::MinSum as u8
|
||||||
|
))
|
||||||
.short('f')
|
.short('f')
|
||||||
.long("filters")
|
.long("filters")
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.validator(|x| match parse_numeric_range_opts(x, 0, 5) {
|
.validator(|x| match parse_numeric_range_opts(x, 0, RowFilter::LAST) {
|
||||||
Ok(_) => Ok(()),
|
Ok(_) => Ok(()),
|
||||||
Err(_) => Err("Invalid option for filters".to_owned()),
|
Err(_) => Err("Invalid option for filters".to_owned()),
|
||||||
}),
|
}),
|
||||||
|
|
@ -264,7 +269,20 @@ fn main() {
|
||||||
-o max => (stable alias for the max compression)
|
-o max => (stable alias for the max compression)
|
||||||
|
|
||||||
Manually specifying a compression option (zc, f, etc.) will override the optimization preset,
|
Manually specifying a compression option (zc, f, etc.) will override the optimization preset,
|
||||||
regardless of the order you write the arguments.",
|
regardless of the order you write the arguments.
|
||||||
|
|
||||||
|
PNG delta filters:
|
||||||
|
0 => None
|
||||||
|
1 => Sub
|
||||||
|
2 => Up
|
||||||
|
3 => Average
|
||||||
|
4 => Paeth
|
||||||
|
Heuristic filter selection strategies:
|
||||||
|
5 => MinSum Minimum sum of absolute differences
|
||||||
|
6 => Entropy Highest Shannon entropy
|
||||||
|
7 => Bigrams Lowest count of distinct bigrams
|
||||||
|
8 => BigEnt Highest Shannon entropy of bigrams
|
||||||
|
9 => Brute Smallest compressed size (slow)",
|
||||||
)
|
)
|
||||||
.get_matches_from(wild::args());
|
.get_matches_from(wild::args());
|
||||||
|
|
||||||
|
|
@ -381,7 +399,10 @@ fn parse_opts_into_struct(
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(x) = matches.value_of("filters") {
|
if let Some(x) = matches.value_of("filters") {
|
||||||
opts.filter = parse_numeric_range_opts(x, 0, 5).unwrap();
|
opts.filter.clear();
|
||||||
|
for f in parse_numeric_range_opts(x, 0, RowFilter::LAST).unwrap() {
|
||||||
|
opts.filter.insert(RowFilter::try_from(f).unwrap());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(x) = matches.value_of("timeout") {
|
if let Some(x) = matches.value_of("timeout") {
|
||||||
|
|
|
||||||
183
src/png/mod.rs
183
src/png/mod.rs
|
|
@ -4,23 +4,27 @@ use crate::error::PngError;
|
||||||
use crate::filters::*;
|
use crate::filters::*;
|
||||||
use crate::headers::*;
|
use crate::headers::*;
|
||||||
use crate::interlace::{deinterlace_image, interlace_image};
|
use crate::interlace::{deinterlace_image, interlace_image};
|
||||||
|
use bitvec::bitarr;
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
|
use libdeflater::{CompressionLvl, Compressor};
|
||||||
use rgb::ComponentSlice;
|
use rgb::ComponentSlice;
|
||||||
use rgb::RGBA8;
|
use rgb::RGBA8;
|
||||||
|
use rustc_hash::FxHashMap;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{BufReader, Read, Write};
|
use std::io::{BufReader, Read, Write};
|
||||||
use std::iter::Iterator;
|
use std::iter::Iterator;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
/// Must use normal (lazy) compression, as faster ones (greedy) are not representative
|
|
||||||
pub(crate) const STD_COMPRESSION: u8 = 5;
|
|
||||||
pub(crate) const STD_FILTERS: [u8; 2] = [0, 5];
|
|
||||||
|
|
||||||
pub(crate) mod scan_lines;
|
pub(crate) mod scan_lines;
|
||||||
|
|
||||||
use self::scan_lines::{ScanLines, ScanLinesMut};
|
use self::scan_lines::{ScanLines, ScanLinesMut};
|
||||||
|
|
||||||
|
/// Compression level to use for the Brute filter strategy
|
||||||
|
const BRUTE_LEVEL: i32 = 1; // 1 is fastest, 2-4 are not useful, 5 is slower but more effective
|
||||||
|
/// Number of lines to compress with the Brute filter strategy
|
||||||
|
const BRUTE_LINES: usize = 4; // Values over 8 are generally not useful
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct PngImage {
|
pub struct PngImage {
|
||||||
/// The headers stored in the IHDR chunk
|
/// The headers stored in the IHDR chunk
|
||||||
|
|
@ -292,7 +296,8 @@ impl PngImage {
|
||||||
last_pass = line.pass;
|
last_pass = line.pass;
|
||||||
}
|
}
|
||||||
last_line.resize(line.data.len(), 0);
|
last_line.resize(line.data.len(), 0);
|
||||||
unfilter_line(line.filter, bpp, line.data, &last_line, &mut unfiltered_buf)?;
|
let filter = RowFilter::try_from(line.filter).map_err(|_| PngError::InvalidData)?;
|
||||||
|
filter.unfilter_line(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);
|
||||||
|
|
@ -302,61 +307,141 @@ impl PngImage {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Apply the specified filter type to all rows in the image
|
/// Apply the specified filter type to all rows in the image
|
||||||
/// 0: None
|
pub fn filter_image(&self, filter: RowFilter) -> Vec<u8> {
|
||||||
/// 1: Sub
|
|
||||||
/// 2: Up
|
|
||||||
/// 3: Average
|
|
||||||
/// 4: Paeth
|
|
||||||
/// 5: All (heuristically pick the best filter for each line)
|
|
||||||
pub fn filter_image(&self, filter: u8) -> Vec<u8> {
|
|
||||||
let mut filtered = Vec::with_capacity(self.data.len());
|
let mut filtered = 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: &[u8] = &[];
|
let mut last_line: &[u8] = &[];
|
||||||
let mut last_pass: Option<u8> = None;
|
let mut last_pass: Option<u8> = None;
|
||||||
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();
|
|
||||||
if last_pass != line.pass {
|
if last_pass != line.pass {
|
||||||
last_line = &[];
|
last_line = &[];
|
||||||
}
|
}
|
||||||
match filter {
|
|
||||||
0 | 1 | 2 | 3 | 4 => {
|
|
||||||
let filter = if last_pass == line.pass || filter <= 1 {
|
|
||||||
filter
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
};
|
|
||||||
filtered.push(filter);
|
|
||||||
filter_line(filter, bpp, line.data, last_line, &mut f_buf);
|
|
||||||
filtered.extend_from_slice(&f_buf);
|
|
||||||
}
|
|
||||||
5 => {
|
|
||||||
// Heuristically guess best filter per line
|
|
||||||
// Uses MSAD algorithm mentioned in libpng reference docs
|
|
||||||
// http://www.libpng.org/pub/png/book/chapter09.html
|
|
||||||
let mut best_filter = 0;
|
|
||||||
let mut best_line = Vec::new();
|
|
||||||
let mut best_size = u64::MAX;
|
|
||||||
|
|
||||||
// Avoid vertical filtering on first line of each interlacing pass
|
if filter <= RowFilter::Paeth {
|
||||||
for filter in if last_pass == line.pass { 0..5 } else { 0..2 } {
|
// Standard filters
|
||||||
filter_line(filter, bpp, line.data, last_line, &mut f_buf);
|
let filter = if last_pass == line.pass || filter <= RowFilter::Sub {
|
||||||
let size = f_buf.iter().fold(0_u64, |acc, &x| {
|
filter
|
||||||
let signed = x as i8;
|
} else {
|
||||||
acc + i16::from(signed).unsigned_abs() as u64
|
RowFilter::None
|
||||||
});
|
};
|
||||||
if size < best_size {
|
filter.filter_line(bpp, line.data, last_line, &mut f_buf);
|
||||||
best_size = size;
|
filtered.extend_from_slice(&f_buf);
|
||||||
best_filter = filter;
|
} else {
|
||||||
std::mem::swap(&mut best_line, &mut f_buf);
|
// Heuristic filter selection strategies
|
||||||
|
let mut best_line = Vec::new();
|
||||||
|
// Avoid vertical filtering on first line of each interlacing pass
|
||||||
|
let try_filters = if last_pass == line.pass {
|
||||||
|
RowFilter::STANDARD.iter()
|
||||||
|
} else {
|
||||||
|
RowFilter::SINGLE_LINE.iter()
|
||||||
|
};
|
||||||
|
match filter {
|
||||||
|
RowFilter::MinSum => {
|
||||||
|
// MSAD algorithm mentioned in libpng reference docs
|
||||||
|
// http://www.libpng.org/pub/png/book/chapter09.html
|
||||||
|
let mut best_size = usize::MAX;
|
||||||
|
for try_filter in try_filters {
|
||||||
|
try_filter.filter_line(bpp, line.data, last_line, &mut f_buf);
|
||||||
|
let size = f_buf.iter().fold(0, |acc, &x| {
|
||||||
|
let signed = x as i8;
|
||||||
|
acc + signed.unsigned_abs() as usize
|
||||||
|
});
|
||||||
|
if size < best_size {
|
||||||
|
best_size = size;
|
||||||
|
std::mem::swap(&mut best_line, &mut f_buf);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
f_buf.clear() //discard buffer, and start again
|
|
||||||
}
|
}
|
||||||
filtered.push(best_filter);
|
RowFilter::Entropy => {
|
||||||
filtered.extend_from_slice(&best_line);
|
// Shannon entropy algorithm, from LodePNG
|
||||||
|
// https://github.com/lvandeve/lodepng
|
||||||
|
let mut best_size = i32::MIN;
|
||||||
|
for try_filter in try_filters {
|
||||||
|
try_filter.filter_line(bpp, line.data, last_line, &mut f_buf);
|
||||||
|
let mut counts = vec![0; 0x100];
|
||||||
|
for &i in f_buf.iter() {
|
||||||
|
counts[i as usize] += 1;
|
||||||
|
}
|
||||||
|
let size = counts.into_iter().fold(0, |acc, x| {
|
||||||
|
if x == 0 {
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
acc + ilog2i(x)
|
||||||
|
}) as i32;
|
||||||
|
if size > best_size {
|
||||||
|
best_size = size;
|
||||||
|
std::mem::swap(&mut best_line, &mut f_buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RowFilter::Bigrams => {
|
||||||
|
// Count distinct bigrams, from pngwolf
|
||||||
|
// https://bjoern.hoehrmann.de/pngwolf/
|
||||||
|
let mut best_size = usize::MAX;
|
||||||
|
for try_filter in try_filters {
|
||||||
|
try_filter.filter_line(bpp, line.data, last_line, &mut f_buf);
|
||||||
|
let mut set = bitarr![0; 0x10000];
|
||||||
|
for pair in f_buf.windows(2) {
|
||||||
|
let bigram = (pair[0] as usize) << 8 | pair[1] as usize;
|
||||||
|
set.set(bigram, true);
|
||||||
|
}
|
||||||
|
let size = set.count_ones();
|
||||||
|
if size < best_size {
|
||||||
|
best_size = size;
|
||||||
|
std::mem::swap(&mut best_line, &mut f_buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RowFilter::BigEnt => {
|
||||||
|
// Bigram entropy, combined from Entropy and Bigrams filters
|
||||||
|
let mut best_size = i32::MIN;
|
||||||
|
// FxHasher is the fastest rust hasher currently available for this purpose
|
||||||
|
let mut counts = FxHashMap::<u16, u32>::default();
|
||||||
|
for try_filter in try_filters {
|
||||||
|
try_filter.filter_line(bpp, line.data, last_line, &mut f_buf);
|
||||||
|
counts.clear();
|
||||||
|
for pair in f_buf.windows(2) {
|
||||||
|
let bigram = (pair[0] as u16) << 8 | pair[1] as u16;
|
||||||
|
counts.entry(bigram).and_modify(|e| *e += 1).or_insert(1);
|
||||||
|
}
|
||||||
|
let size = counts.values().fold(0, |acc, &x| acc + ilog2i(x)) as i32;
|
||||||
|
if size > best_size {
|
||||||
|
best_size = size;
|
||||||
|
std::mem::swap(&mut best_line, &mut f_buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RowFilter::Brute => {
|
||||||
|
// Brute force by compressing each filter attempt
|
||||||
|
// Similar to that of LodePNG but includes some previous lines for context
|
||||||
|
let mut best_size = usize::MAX;
|
||||||
|
let line_start = filtered.len();
|
||||||
|
filtered.resize(filtered.len() + line.data.len() + 1, 0);
|
||||||
|
let mut compressor =
|
||||||
|
Compressor::new(CompressionLvl::new(BRUTE_LEVEL).unwrap());
|
||||||
|
let limit = filtered.len().min((line.data.len() + 1) * BRUTE_LINES);
|
||||||
|
let capacity = compressor.zlib_compress_bound(limit);
|
||||||
|
let mut dest = vec![0; capacity];
|
||||||
|
|
||||||
|
for try_filter in try_filters {
|
||||||
|
try_filter.filter_line(bpp, line.data, last_line, &mut f_buf);
|
||||||
|
filtered[line_start..].copy_from_slice(&f_buf);
|
||||||
|
let size = compressor
|
||||||
|
.zlib_compress(&filtered[filtered.len() - limit..], &mut dest)
|
||||||
|
.unwrap_or(usize::MAX);
|
||||||
|
if size < best_size {
|
||||||
|
best_size = size;
|
||||||
|
std::mem::swap(&mut best_line, &mut f_buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
filtered.resize(line_start, 0);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
filtered.extend_from_slice(&best_line);
|
||||||
}
|
}
|
||||||
|
|
||||||
last_line = line.data;
|
last_line = line.data;
|
||||||
last_pass = line.pass;
|
last_pass = line.pass;
|
||||||
}
|
}
|
||||||
|
|
@ -373,3 +458,9 @@ fn write_png_block(key: &[u8], header: &[u8], output: &mut Vec<u8>) {
|
||||||
output.append(&mut header_data);
|
output.append(&mut header_data);
|
||||||
output.extend_from_slice(&crc.to_be_bytes());
|
output.extend_from_slice(&crc.to_be_bytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Integer approximation for i * log2(i) - much faster than float calculations
|
||||||
|
fn ilog2i(i: u32) -> u32 {
|
||||||
|
let log = 32 - i.leading_zeros() - 1;
|
||||||
|
i * log + ((i - (1 << log)) << 1)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::colors::{BitDepth, ColorType};
|
use crate::colors::{BitDepth, ColorType};
|
||||||
use crate::headers::IhdrData;
|
use crate::headers::IhdrData;
|
||||||
use crate::png::PngImage;
|
use crate::png::PngImage;
|
||||||
use bit_vec::BitVec;
|
use bitvec::prelude::*;
|
||||||
|
|
||||||
const ONE_BIT_PERMUTATIONS: [u8; 2] = [0b0000_0000, 0b1111_1111];
|
const ONE_BIT_PERMUTATIONS: [u8; 2] = [0b0000_0000, 0b1111_1111];
|
||||||
const TWO_BIT_PERMUTATIONS: [u8; 4] = [0b0000_0000, 0b0101_0101, 0b1010_1010, 0b1111_1111];
|
const TWO_BIT_PERMUTATIONS: [u8; 4] = [0b0000_0000, 0b0101_0101, 0b1010_1010, 0b1111_1111];
|
||||||
|
|
@ -108,8 +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);
|
for &byte in line.data {
|
||||||
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 {
|
||||||
&ONE_BIT_PERMUTATIONS
|
&ONE_BIT_PERMUTATIONS
|
||||||
|
|
@ -129,11 +128,11 @@ pub fn reduce_bit_depth_8_or_less(png: &PngImage, mut minimum_bits: usize) -> Op
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut reduced = BitVec::with_capacity(png.data.len() * 8);
|
let mut reduced = BitVec::<u8, Msb0>::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_from_raw_slice(&[line.filter]);
|
||||||
let bit_vec = BitVec::from_bytes(line.data);
|
let bit_vec = line.data.view_bits::<Msb0>();
|
||||||
for (i, bit) in bit_vec.iter().enumerate() {
|
for (i, bit) in bit_vec.iter().by_vals().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 {
|
||||||
reduced.push(bit);
|
reduced.push(bit);
|
||||||
|
|
@ -146,7 +145,7 @@ pub fn reduce_bit_depth_8_or_less(png: &PngImage, mut minimum_bits: usize) -> Op
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(PngImage {
|
Some(PngImage {
|
||||||
data: reduced.to_bytes(),
|
data: reduced.as_raw_slice().to_vec(),
|
||||||
ihdr: IhdrData {
|
ihdr: IhdrData {
|
||||||
bit_depth: BitDepth::from_u8(minimum_bits as u8),
|
bit_depth: BitDepth::from_u8(minimum_bits as u8),
|
||||||
..png.ihdr
|
..png.ihdr
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,10 @@ use crate::png::PngImage;
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use rgb::{FromSlice, RGB8, RGBA8};
|
use rgb::{FromSlice, RGB8, RGBA8};
|
||||||
use std::hash::Hash;
|
use rustc_hash::FxHasher;
|
||||||
|
use std::hash::{BuildHasherDefault, Hash};
|
||||||
|
|
||||||
|
type FxIndexMap<K, V> = IndexMap<K, V, BuildHasherDefault<FxHasher>>;
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn reduce_rgba_to_grayscale_alpha(png: &PngImage) -> Option<PngImage> {
|
pub fn reduce_rgba_to_grayscale_alpha(png: &PngImage) -> Option<PngImage> {
|
||||||
|
|
@ -78,7 +81,7 @@ pub fn reduce_rgba_to_grayscale_alpha(png: &PngImage) -> Option<PngImage> {
|
||||||
|
|
||||||
fn reduce_scanline_to_palette<T>(
|
fn reduce_scanline_to_palette<T>(
|
||||||
iter: impl IntoIterator<Item = T>,
|
iter: impl IntoIterator<Item = T>,
|
||||||
palette: &mut IndexMap<T, u8>,
|
palette: &mut FxIndexMap<T, u8>,
|
||||||
reduced: &mut Vec<u8>,
|
reduced: &mut Vec<u8>,
|
||||||
) -> bool
|
) -> bool
|
||||||
where
|
where
|
||||||
|
|
@ -107,7 +110,8 @@ pub fn reduced_color_to_palette(png: &PngImage) -> Option<PngImage> {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let mut raw_data = Vec::with_capacity(png.data.len());
|
let mut raw_data = Vec::with_capacity(png.data.len());
|
||||||
let mut palette = IndexMap::with_capacity(257);
|
let mut palette = FxIndexMap::default();
|
||||||
|
palette.reserve(257);
|
||||||
let transparency_pixel = png
|
let transparency_pixel = png
|
||||||
.transparency_pixel
|
.transparency_pixel
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
|
|
||||||
138
tests/filters.rs
138
tests/filters.rs
|
|
@ -1,5 +1,5 @@
|
||||||
use indexmap::IndexSet;
|
use indexmap::IndexSet;
|
||||||
use oxipng::internal_tests::*;
|
use oxipng::{internal_tests::*, RowFilter};
|
||||||
use oxipng::{InFile, OutFile};
|
use oxipng::{InFile, OutFile};
|
||||||
use std::fs::remove_file;
|
use std::fs::remove_file;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
@ -11,7 +11,7 @@ fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let mut filter = IndexSet::new();
|
let mut filter = IndexSet::new();
|
||||||
filter.insert(0);
|
filter.insert(RowFilter::None);
|
||||||
options.filter = filter;
|
options.filter = filter;
|
||||||
|
|
||||||
(
|
(
|
||||||
|
|
@ -22,7 +22,7 @@ fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
|
||||||
|
|
||||||
fn test_it_converts(
|
fn test_it_converts(
|
||||||
input: &str,
|
input: &str,
|
||||||
filter: u8,
|
filter: RowFilter,
|
||||||
color_type_in: ColorType,
|
color_type_in: ColorType,
|
||||||
bit_depth_in: BitDepth,
|
bit_depth_in: BitDepth,
|
||||||
color_type_out: ColorType,
|
color_type_out: ColorType,
|
||||||
|
|
@ -67,7 +67,7 @@ fn test_it_converts(
|
||||||
fn filter_0_for_rgba_16() {
|
fn filter_0_for_rgba_16() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_0_for_rgba_16.png",
|
"tests/files/filter_0_for_rgba_16.png",
|
||||||
0,
|
RowFilter::None,
|
||||||
ColorType::RGBA,
|
ColorType::RGBA,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::RGBA,
|
ColorType::RGBA,
|
||||||
|
|
@ -79,7 +79,7 @@ fn filter_0_for_rgba_16() {
|
||||||
fn filter_1_for_rgba_16() {
|
fn filter_1_for_rgba_16() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_1_for_rgba_16.png",
|
"tests/files/filter_1_for_rgba_16.png",
|
||||||
1,
|
RowFilter::Sub,
|
||||||
ColorType::RGBA,
|
ColorType::RGBA,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::RGBA,
|
ColorType::RGBA,
|
||||||
|
|
@ -91,7 +91,7 @@ fn filter_1_for_rgba_16() {
|
||||||
fn filter_2_for_rgba_16() {
|
fn filter_2_for_rgba_16() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_2_for_rgba_16.png",
|
"tests/files/filter_2_for_rgba_16.png",
|
||||||
2,
|
RowFilter::Up,
|
||||||
ColorType::RGBA,
|
ColorType::RGBA,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::RGBA,
|
ColorType::RGBA,
|
||||||
|
|
@ -103,7 +103,7 @@ fn filter_2_for_rgba_16() {
|
||||||
fn filter_3_for_rgba_16() {
|
fn filter_3_for_rgba_16() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_3_for_rgba_16.png",
|
"tests/files/filter_3_for_rgba_16.png",
|
||||||
3,
|
RowFilter::Average,
|
||||||
ColorType::RGBA,
|
ColorType::RGBA,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::RGBA,
|
ColorType::RGBA,
|
||||||
|
|
@ -115,7 +115,7 @@ fn filter_3_for_rgba_16() {
|
||||||
fn filter_4_for_rgba_16() {
|
fn filter_4_for_rgba_16() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_4_for_rgba_16.png",
|
"tests/files/filter_4_for_rgba_16.png",
|
||||||
4,
|
RowFilter::Paeth,
|
||||||
ColorType::RGBA,
|
ColorType::RGBA,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::RGBA,
|
ColorType::RGBA,
|
||||||
|
|
@ -127,7 +127,7 @@ fn filter_4_for_rgba_16() {
|
||||||
fn filter_5_for_rgba_16() {
|
fn filter_5_for_rgba_16() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_5_for_rgba_16.png",
|
"tests/files/filter_5_for_rgba_16.png",
|
||||||
5,
|
RowFilter::MinSum,
|
||||||
ColorType::RGBA,
|
ColorType::RGBA,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::RGBA,
|
ColorType::RGBA,
|
||||||
|
|
@ -139,7 +139,7 @@ fn filter_5_for_rgba_16() {
|
||||||
fn filter_0_for_rgba_8() {
|
fn filter_0_for_rgba_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_0_for_rgba_8.png",
|
"tests/files/filter_0_for_rgba_8.png",
|
||||||
0,
|
RowFilter::None,
|
||||||
ColorType::RGBA,
|
ColorType::RGBA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::RGBA,
|
ColorType::RGBA,
|
||||||
|
|
@ -151,7 +151,7 @@ fn filter_0_for_rgba_8() {
|
||||||
fn filter_1_for_rgba_8() {
|
fn filter_1_for_rgba_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_1_for_rgba_8.png",
|
"tests/files/filter_1_for_rgba_8.png",
|
||||||
1,
|
RowFilter::Sub,
|
||||||
ColorType::RGBA,
|
ColorType::RGBA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::RGBA,
|
ColorType::RGBA,
|
||||||
|
|
@ -163,7 +163,7 @@ fn filter_1_for_rgba_8() {
|
||||||
fn filter_2_for_rgba_8() {
|
fn filter_2_for_rgba_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_2_for_rgba_8.png",
|
"tests/files/filter_2_for_rgba_8.png",
|
||||||
2,
|
RowFilter::Up,
|
||||||
ColorType::RGBA,
|
ColorType::RGBA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::RGBA,
|
ColorType::RGBA,
|
||||||
|
|
@ -175,7 +175,7 @@ fn filter_2_for_rgba_8() {
|
||||||
fn filter_3_for_rgba_8() {
|
fn filter_3_for_rgba_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_3_for_rgba_8.png",
|
"tests/files/filter_3_for_rgba_8.png",
|
||||||
3,
|
RowFilter::Average,
|
||||||
ColorType::RGBA,
|
ColorType::RGBA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::RGBA,
|
ColorType::RGBA,
|
||||||
|
|
@ -187,7 +187,7 @@ fn filter_3_for_rgba_8() {
|
||||||
fn filter_4_for_rgba_8() {
|
fn filter_4_for_rgba_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_4_for_rgba_8.png",
|
"tests/files/filter_4_for_rgba_8.png",
|
||||||
4,
|
RowFilter::Paeth,
|
||||||
ColorType::RGBA,
|
ColorType::RGBA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::RGBA,
|
ColorType::RGBA,
|
||||||
|
|
@ -199,7 +199,7 @@ fn filter_4_for_rgba_8() {
|
||||||
fn filter_5_for_rgba_8() {
|
fn filter_5_for_rgba_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_5_for_rgba_8.png",
|
"tests/files/filter_5_for_rgba_8.png",
|
||||||
5,
|
RowFilter::MinSum,
|
||||||
ColorType::RGBA,
|
ColorType::RGBA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::RGBA,
|
ColorType::RGBA,
|
||||||
|
|
@ -211,7 +211,7 @@ fn filter_5_for_rgba_8() {
|
||||||
fn filter_0_for_rgb_16() {
|
fn filter_0_for_rgb_16() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_0_for_rgb_16.png",
|
"tests/files/filter_0_for_rgb_16.png",
|
||||||
0,
|
RowFilter::None,
|
||||||
ColorType::RGB,
|
ColorType::RGB,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::RGB,
|
ColorType::RGB,
|
||||||
|
|
@ -223,7 +223,7 @@ fn filter_0_for_rgb_16() {
|
||||||
fn filter_1_for_rgb_16() {
|
fn filter_1_for_rgb_16() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_1_for_rgb_16.png",
|
"tests/files/filter_1_for_rgb_16.png",
|
||||||
1,
|
RowFilter::Sub,
|
||||||
ColorType::RGB,
|
ColorType::RGB,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::RGB,
|
ColorType::RGB,
|
||||||
|
|
@ -235,7 +235,7 @@ fn filter_1_for_rgb_16() {
|
||||||
fn filter_2_for_rgb_16() {
|
fn filter_2_for_rgb_16() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_2_for_rgb_16.png",
|
"tests/files/filter_2_for_rgb_16.png",
|
||||||
2,
|
RowFilter::Up,
|
||||||
ColorType::RGB,
|
ColorType::RGB,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::RGB,
|
ColorType::RGB,
|
||||||
|
|
@ -247,7 +247,7 @@ fn filter_2_for_rgb_16() {
|
||||||
fn filter_3_for_rgb_16() {
|
fn filter_3_for_rgb_16() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_3_for_rgb_16.png",
|
"tests/files/filter_3_for_rgb_16.png",
|
||||||
3,
|
RowFilter::Average,
|
||||||
ColorType::RGB,
|
ColorType::RGB,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::RGB,
|
ColorType::RGB,
|
||||||
|
|
@ -259,7 +259,7 @@ fn filter_3_for_rgb_16() {
|
||||||
fn filter_4_for_rgb_16() {
|
fn filter_4_for_rgb_16() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_4_for_rgb_16.png",
|
"tests/files/filter_4_for_rgb_16.png",
|
||||||
4,
|
RowFilter::Paeth,
|
||||||
ColorType::RGB,
|
ColorType::RGB,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::RGB,
|
ColorType::RGB,
|
||||||
|
|
@ -271,7 +271,7 @@ fn filter_4_for_rgb_16() {
|
||||||
fn filter_5_for_rgb_16() {
|
fn filter_5_for_rgb_16() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_5_for_rgb_16.png",
|
"tests/files/filter_5_for_rgb_16.png",
|
||||||
5,
|
RowFilter::MinSum,
|
||||||
ColorType::RGB,
|
ColorType::RGB,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::RGB,
|
ColorType::RGB,
|
||||||
|
|
@ -283,7 +283,7 @@ fn filter_5_for_rgb_16() {
|
||||||
fn filter_0_for_rgb_8() {
|
fn filter_0_for_rgb_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_0_for_rgb_8.png",
|
"tests/files/filter_0_for_rgb_8.png",
|
||||||
0,
|
RowFilter::None,
|
||||||
ColorType::RGB,
|
ColorType::RGB,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::RGB,
|
ColorType::RGB,
|
||||||
|
|
@ -295,7 +295,7 @@ fn filter_0_for_rgb_8() {
|
||||||
fn filter_1_for_rgb_8() {
|
fn filter_1_for_rgb_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_1_for_rgb_8.png",
|
"tests/files/filter_1_for_rgb_8.png",
|
||||||
1,
|
RowFilter::Sub,
|
||||||
ColorType::RGB,
|
ColorType::RGB,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::RGB,
|
ColorType::RGB,
|
||||||
|
|
@ -307,7 +307,7 @@ fn filter_1_for_rgb_8() {
|
||||||
fn filter_2_for_rgb_8() {
|
fn filter_2_for_rgb_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_2_for_rgb_8.png",
|
"tests/files/filter_2_for_rgb_8.png",
|
||||||
2,
|
RowFilter::Up,
|
||||||
ColorType::RGB,
|
ColorType::RGB,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::RGB,
|
ColorType::RGB,
|
||||||
|
|
@ -319,7 +319,7 @@ fn filter_2_for_rgb_8() {
|
||||||
fn filter_3_for_rgb_8() {
|
fn filter_3_for_rgb_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_3_for_rgb_8.png",
|
"tests/files/filter_3_for_rgb_8.png",
|
||||||
3,
|
RowFilter::Average,
|
||||||
ColorType::RGB,
|
ColorType::RGB,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::RGB,
|
ColorType::RGB,
|
||||||
|
|
@ -331,7 +331,7 @@ fn filter_3_for_rgb_8() {
|
||||||
fn filter_4_for_rgb_8() {
|
fn filter_4_for_rgb_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_4_for_rgb_8.png",
|
"tests/files/filter_4_for_rgb_8.png",
|
||||||
4,
|
RowFilter::Paeth,
|
||||||
ColorType::RGB,
|
ColorType::RGB,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::RGB,
|
ColorType::RGB,
|
||||||
|
|
@ -343,7 +343,7 @@ fn filter_4_for_rgb_8() {
|
||||||
fn filter_5_for_rgb_8() {
|
fn filter_5_for_rgb_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_5_for_rgb_8.png",
|
"tests/files/filter_5_for_rgb_8.png",
|
||||||
5,
|
RowFilter::MinSum,
|
||||||
ColorType::RGB,
|
ColorType::RGB,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::RGB,
|
ColorType::RGB,
|
||||||
|
|
@ -355,7 +355,7 @@ fn filter_5_for_rgb_8() {
|
||||||
fn filter_0_for_grayscale_alpha_16() {
|
fn filter_0_for_grayscale_alpha_16() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_0_for_grayscale_alpha_16.png",
|
"tests/files/filter_0_for_grayscale_alpha_16.png",
|
||||||
0,
|
RowFilter::None,
|
||||||
ColorType::GrayscaleAlpha,
|
ColorType::GrayscaleAlpha,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::GrayscaleAlpha,
|
ColorType::GrayscaleAlpha,
|
||||||
|
|
@ -367,7 +367,7 @@ fn filter_0_for_grayscale_alpha_16() {
|
||||||
fn filter_1_for_grayscale_alpha_16() {
|
fn filter_1_for_grayscale_alpha_16() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_1_for_grayscale_alpha_16.png",
|
"tests/files/filter_1_for_grayscale_alpha_16.png",
|
||||||
1,
|
RowFilter::Sub,
|
||||||
ColorType::GrayscaleAlpha,
|
ColorType::GrayscaleAlpha,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::GrayscaleAlpha,
|
ColorType::GrayscaleAlpha,
|
||||||
|
|
@ -379,7 +379,7 @@ fn filter_1_for_grayscale_alpha_16() {
|
||||||
fn filter_2_for_grayscale_alpha_16() {
|
fn filter_2_for_grayscale_alpha_16() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_2_for_grayscale_alpha_16.png",
|
"tests/files/filter_2_for_grayscale_alpha_16.png",
|
||||||
2,
|
RowFilter::Up,
|
||||||
ColorType::GrayscaleAlpha,
|
ColorType::GrayscaleAlpha,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::GrayscaleAlpha,
|
ColorType::GrayscaleAlpha,
|
||||||
|
|
@ -391,7 +391,7 @@ fn filter_2_for_grayscale_alpha_16() {
|
||||||
fn filter_3_for_grayscale_alpha_16() {
|
fn filter_3_for_grayscale_alpha_16() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_3_for_grayscale_alpha_16.png",
|
"tests/files/filter_3_for_grayscale_alpha_16.png",
|
||||||
3,
|
RowFilter::Average,
|
||||||
ColorType::GrayscaleAlpha,
|
ColorType::GrayscaleAlpha,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::GrayscaleAlpha,
|
ColorType::GrayscaleAlpha,
|
||||||
|
|
@ -403,7 +403,7 @@ fn filter_3_for_grayscale_alpha_16() {
|
||||||
fn filter_4_for_grayscale_alpha_16() {
|
fn filter_4_for_grayscale_alpha_16() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_4_for_grayscale_alpha_16.png",
|
"tests/files/filter_4_for_grayscale_alpha_16.png",
|
||||||
4,
|
RowFilter::Paeth,
|
||||||
ColorType::GrayscaleAlpha,
|
ColorType::GrayscaleAlpha,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::GrayscaleAlpha,
|
ColorType::GrayscaleAlpha,
|
||||||
|
|
@ -415,7 +415,7 @@ fn filter_4_for_grayscale_alpha_16() {
|
||||||
fn filter_5_for_grayscale_alpha_16() {
|
fn filter_5_for_grayscale_alpha_16() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_5_for_grayscale_alpha_16.png",
|
"tests/files/filter_5_for_grayscale_alpha_16.png",
|
||||||
5,
|
RowFilter::MinSum,
|
||||||
ColorType::GrayscaleAlpha,
|
ColorType::GrayscaleAlpha,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::GrayscaleAlpha,
|
ColorType::GrayscaleAlpha,
|
||||||
|
|
@ -427,7 +427,7 @@ fn filter_5_for_grayscale_alpha_16() {
|
||||||
fn filter_0_for_grayscale_alpha_8() {
|
fn filter_0_for_grayscale_alpha_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_0_for_grayscale_alpha_8.png",
|
"tests/files/filter_0_for_grayscale_alpha_8.png",
|
||||||
0,
|
RowFilter::None,
|
||||||
ColorType::GrayscaleAlpha,
|
ColorType::GrayscaleAlpha,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::GrayscaleAlpha,
|
ColorType::GrayscaleAlpha,
|
||||||
|
|
@ -439,7 +439,7 @@ fn filter_0_for_grayscale_alpha_8() {
|
||||||
fn filter_1_for_grayscale_alpha_8() {
|
fn filter_1_for_grayscale_alpha_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_1_for_grayscale_alpha_8.png",
|
"tests/files/filter_1_for_grayscale_alpha_8.png",
|
||||||
1,
|
RowFilter::Sub,
|
||||||
ColorType::GrayscaleAlpha,
|
ColorType::GrayscaleAlpha,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::GrayscaleAlpha,
|
ColorType::GrayscaleAlpha,
|
||||||
|
|
@ -451,7 +451,7 @@ fn filter_1_for_grayscale_alpha_8() {
|
||||||
fn filter_2_for_grayscale_alpha_8() {
|
fn filter_2_for_grayscale_alpha_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_2_for_grayscale_alpha_8.png",
|
"tests/files/filter_2_for_grayscale_alpha_8.png",
|
||||||
2,
|
RowFilter::Up,
|
||||||
ColorType::GrayscaleAlpha,
|
ColorType::GrayscaleAlpha,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::GrayscaleAlpha,
|
ColorType::GrayscaleAlpha,
|
||||||
|
|
@ -463,7 +463,7 @@ fn filter_2_for_grayscale_alpha_8() {
|
||||||
fn filter_3_for_grayscale_alpha_8() {
|
fn filter_3_for_grayscale_alpha_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_3_for_grayscale_alpha_8.png",
|
"tests/files/filter_3_for_grayscale_alpha_8.png",
|
||||||
3,
|
RowFilter::Average,
|
||||||
ColorType::GrayscaleAlpha,
|
ColorType::GrayscaleAlpha,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::GrayscaleAlpha,
|
ColorType::GrayscaleAlpha,
|
||||||
|
|
@ -475,7 +475,7 @@ fn filter_3_for_grayscale_alpha_8() {
|
||||||
fn filter_4_for_grayscale_alpha_8() {
|
fn filter_4_for_grayscale_alpha_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_4_for_grayscale_alpha_8.png",
|
"tests/files/filter_4_for_grayscale_alpha_8.png",
|
||||||
4,
|
RowFilter::Paeth,
|
||||||
ColorType::GrayscaleAlpha,
|
ColorType::GrayscaleAlpha,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::GrayscaleAlpha,
|
ColorType::GrayscaleAlpha,
|
||||||
|
|
@ -487,7 +487,7 @@ fn filter_4_for_grayscale_alpha_8() {
|
||||||
fn filter_5_for_grayscale_alpha_8() {
|
fn filter_5_for_grayscale_alpha_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_5_for_grayscale_alpha_8.png",
|
"tests/files/filter_5_for_grayscale_alpha_8.png",
|
||||||
5,
|
RowFilter::MinSum,
|
||||||
ColorType::GrayscaleAlpha,
|
ColorType::GrayscaleAlpha,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::GrayscaleAlpha,
|
ColorType::GrayscaleAlpha,
|
||||||
|
|
@ -499,7 +499,7 @@ fn filter_5_for_grayscale_alpha_8() {
|
||||||
fn filter_0_for_grayscale_16() {
|
fn filter_0_for_grayscale_16() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_0_for_grayscale_16.png",
|
"tests/files/filter_0_for_grayscale_16.png",
|
||||||
0,
|
RowFilter::None,
|
||||||
ColorType::Grayscale,
|
ColorType::Grayscale,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::Grayscale,
|
ColorType::Grayscale,
|
||||||
|
|
@ -511,7 +511,7 @@ fn filter_0_for_grayscale_16() {
|
||||||
fn filter_1_for_grayscale_16() {
|
fn filter_1_for_grayscale_16() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_1_for_grayscale_16.png",
|
"tests/files/filter_1_for_grayscale_16.png",
|
||||||
1,
|
RowFilter::Sub,
|
||||||
ColorType::Grayscale,
|
ColorType::Grayscale,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::Grayscale,
|
ColorType::Grayscale,
|
||||||
|
|
@ -523,7 +523,7 @@ fn filter_1_for_grayscale_16() {
|
||||||
fn filter_2_for_grayscale_16() {
|
fn filter_2_for_grayscale_16() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_2_for_grayscale_16.png",
|
"tests/files/filter_2_for_grayscale_16.png",
|
||||||
2,
|
RowFilter::Up,
|
||||||
ColorType::Grayscale,
|
ColorType::Grayscale,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::Grayscale,
|
ColorType::Grayscale,
|
||||||
|
|
@ -535,7 +535,7 @@ fn filter_2_for_grayscale_16() {
|
||||||
fn filter_3_for_grayscale_16() {
|
fn filter_3_for_grayscale_16() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_3_for_grayscale_16.png",
|
"tests/files/filter_3_for_grayscale_16.png",
|
||||||
3,
|
RowFilter::Average,
|
||||||
ColorType::Grayscale,
|
ColorType::Grayscale,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::Grayscale,
|
ColorType::Grayscale,
|
||||||
|
|
@ -547,7 +547,7 @@ fn filter_3_for_grayscale_16() {
|
||||||
fn filter_4_for_grayscale_16() {
|
fn filter_4_for_grayscale_16() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_4_for_grayscale_16.png",
|
"tests/files/filter_4_for_grayscale_16.png",
|
||||||
4,
|
RowFilter::Paeth,
|
||||||
ColorType::Grayscale,
|
ColorType::Grayscale,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::Grayscale,
|
ColorType::Grayscale,
|
||||||
|
|
@ -559,7 +559,7 @@ fn filter_4_for_grayscale_16() {
|
||||||
fn filter_5_for_grayscale_16() {
|
fn filter_5_for_grayscale_16() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_5_for_grayscale_16.png",
|
"tests/files/filter_5_for_grayscale_16.png",
|
||||||
5,
|
RowFilter::MinSum,
|
||||||
ColorType::Grayscale,
|
ColorType::Grayscale,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::Grayscale,
|
ColorType::Grayscale,
|
||||||
|
|
@ -571,7 +571,7 @@ fn filter_5_for_grayscale_16() {
|
||||||
fn filter_0_for_grayscale_8() {
|
fn filter_0_for_grayscale_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_0_for_grayscale_8.png",
|
"tests/files/filter_0_for_grayscale_8.png",
|
||||||
0,
|
RowFilter::None,
|
||||||
ColorType::Grayscale,
|
ColorType::Grayscale,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::Grayscale,
|
ColorType::Grayscale,
|
||||||
|
|
@ -583,7 +583,7 @@ fn filter_0_for_grayscale_8() {
|
||||||
fn filter_1_for_grayscale_8() {
|
fn filter_1_for_grayscale_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_1_for_grayscale_8.png",
|
"tests/files/filter_1_for_grayscale_8.png",
|
||||||
1,
|
RowFilter::Sub,
|
||||||
ColorType::Grayscale,
|
ColorType::Grayscale,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::Grayscale,
|
ColorType::Grayscale,
|
||||||
|
|
@ -595,7 +595,7 @@ fn filter_1_for_grayscale_8() {
|
||||||
fn filter_2_for_grayscale_8() {
|
fn filter_2_for_grayscale_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_2_for_grayscale_8.png",
|
"tests/files/filter_2_for_grayscale_8.png",
|
||||||
2,
|
RowFilter::Up,
|
||||||
ColorType::Grayscale,
|
ColorType::Grayscale,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::Grayscale,
|
ColorType::Grayscale,
|
||||||
|
|
@ -607,7 +607,7 @@ fn filter_2_for_grayscale_8() {
|
||||||
fn filter_3_for_grayscale_8() {
|
fn filter_3_for_grayscale_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_3_for_grayscale_8.png",
|
"tests/files/filter_3_for_grayscale_8.png",
|
||||||
3,
|
RowFilter::Average,
|
||||||
ColorType::Grayscale,
|
ColorType::Grayscale,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::Grayscale,
|
ColorType::Grayscale,
|
||||||
|
|
@ -619,7 +619,7 @@ fn filter_3_for_grayscale_8() {
|
||||||
fn filter_4_for_grayscale_8() {
|
fn filter_4_for_grayscale_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_4_for_grayscale_8.png",
|
"tests/files/filter_4_for_grayscale_8.png",
|
||||||
4,
|
RowFilter::Paeth,
|
||||||
ColorType::Grayscale,
|
ColorType::Grayscale,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::Grayscale,
|
ColorType::Grayscale,
|
||||||
|
|
@ -631,7 +631,7 @@ fn filter_4_for_grayscale_8() {
|
||||||
fn filter_5_for_grayscale_8() {
|
fn filter_5_for_grayscale_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_5_for_grayscale_8.png",
|
"tests/files/filter_5_for_grayscale_8.png",
|
||||||
5,
|
RowFilter::MinSum,
|
||||||
ColorType::Grayscale,
|
ColorType::Grayscale,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::Grayscale,
|
ColorType::Grayscale,
|
||||||
|
|
@ -643,7 +643,7 @@ fn filter_5_for_grayscale_8() {
|
||||||
fn filter_0_for_palette_4() {
|
fn filter_0_for_palette_4() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_0_for_palette_4.png",
|
"tests/files/filter_0_for_palette_4.png",
|
||||||
0,
|
RowFilter::None,
|
||||||
ColorType::Indexed,
|
ColorType::Indexed,
|
||||||
BitDepth::Four,
|
BitDepth::Four,
|
||||||
ColorType::Indexed,
|
ColorType::Indexed,
|
||||||
|
|
@ -655,7 +655,7 @@ fn filter_0_for_palette_4() {
|
||||||
fn filter_1_for_palette_4() {
|
fn filter_1_for_palette_4() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_1_for_palette_4.png",
|
"tests/files/filter_1_for_palette_4.png",
|
||||||
1,
|
RowFilter::Sub,
|
||||||
ColorType::Indexed,
|
ColorType::Indexed,
|
||||||
BitDepth::Four,
|
BitDepth::Four,
|
||||||
ColorType::Indexed,
|
ColorType::Indexed,
|
||||||
|
|
@ -667,7 +667,7 @@ fn filter_1_for_palette_4() {
|
||||||
fn filter_2_for_palette_4() {
|
fn filter_2_for_palette_4() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_2_for_palette_4.png",
|
"tests/files/filter_2_for_palette_4.png",
|
||||||
2,
|
RowFilter::Up,
|
||||||
ColorType::Indexed,
|
ColorType::Indexed,
|
||||||
BitDepth::Four,
|
BitDepth::Four,
|
||||||
ColorType::Indexed,
|
ColorType::Indexed,
|
||||||
|
|
@ -679,7 +679,7 @@ fn filter_2_for_palette_4() {
|
||||||
fn filter_3_for_palette_4() {
|
fn filter_3_for_palette_4() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_3_for_palette_4.png",
|
"tests/files/filter_3_for_palette_4.png",
|
||||||
3,
|
RowFilter::Average,
|
||||||
ColorType::Indexed,
|
ColorType::Indexed,
|
||||||
BitDepth::Four,
|
BitDepth::Four,
|
||||||
ColorType::Indexed,
|
ColorType::Indexed,
|
||||||
|
|
@ -691,7 +691,7 @@ fn filter_3_for_palette_4() {
|
||||||
fn filter_4_for_palette_4() {
|
fn filter_4_for_palette_4() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_4_for_palette_4.png",
|
"tests/files/filter_4_for_palette_4.png",
|
||||||
4,
|
RowFilter::Paeth,
|
||||||
ColorType::Indexed,
|
ColorType::Indexed,
|
||||||
BitDepth::Four,
|
BitDepth::Four,
|
||||||
ColorType::Indexed,
|
ColorType::Indexed,
|
||||||
|
|
@ -703,7 +703,7 @@ fn filter_4_for_palette_4() {
|
||||||
fn filter_5_for_palette_4() {
|
fn filter_5_for_palette_4() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_5_for_palette_4.png",
|
"tests/files/filter_5_for_palette_4.png",
|
||||||
5,
|
RowFilter::MinSum,
|
||||||
ColorType::Indexed,
|
ColorType::Indexed,
|
||||||
BitDepth::Four,
|
BitDepth::Four,
|
||||||
ColorType::Indexed,
|
ColorType::Indexed,
|
||||||
|
|
@ -715,7 +715,7 @@ fn filter_5_for_palette_4() {
|
||||||
fn filter_0_for_palette_2() {
|
fn filter_0_for_palette_2() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_0_for_palette_2.png",
|
"tests/files/filter_0_for_palette_2.png",
|
||||||
0,
|
RowFilter::None,
|
||||||
ColorType::Indexed,
|
ColorType::Indexed,
|
||||||
BitDepth::Two,
|
BitDepth::Two,
|
||||||
ColorType::Indexed,
|
ColorType::Indexed,
|
||||||
|
|
@ -727,7 +727,7 @@ fn filter_0_for_palette_2() {
|
||||||
fn filter_1_for_palette_2() {
|
fn filter_1_for_palette_2() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_1_for_palette_2.png",
|
"tests/files/filter_1_for_palette_2.png",
|
||||||
1,
|
RowFilter::Sub,
|
||||||
ColorType::Indexed,
|
ColorType::Indexed,
|
||||||
BitDepth::Two,
|
BitDepth::Two,
|
||||||
ColorType::Indexed,
|
ColorType::Indexed,
|
||||||
|
|
@ -739,7 +739,7 @@ fn filter_1_for_palette_2() {
|
||||||
fn filter_2_for_palette_2() {
|
fn filter_2_for_palette_2() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_2_for_palette_2.png",
|
"tests/files/filter_2_for_palette_2.png",
|
||||||
2,
|
RowFilter::Up,
|
||||||
ColorType::Indexed,
|
ColorType::Indexed,
|
||||||
BitDepth::Two,
|
BitDepth::Two,
|
||||||
ColorType::Indexed,
|
ColorType::Indexed,
|
||||||
|
|
@ -751,7 +751,7 @@ fn filter_2_for_palette_2() {
|
||||||
fn filter_3_for_palette_2() {
|
fn filter_3_for_palette_2() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_3_for_palette_2.png",
|
"tests/files/filter_3_for_palette_2.png",
|
||||||
3,
|
RowFilter::Average,
|
||||||
ColorType::Indexed,
|
ColorType::Indexed,
|
||||||
BitDepth::Two,
|
BitDepth::Two,
|
||||||
ColorType::Indexed,
|
ColorType::Indexed,
|
||||||
|
|
@ -763,7 +763,7 @@ fn filter_3_for_palette_2() {
|
||||||
fn filter_4_for_palette_2() {
|
fn filter_4_for_palette_2() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_4_for_palette_2.png",
|
"tests/files/filter_4_for_palette_2.png",
|
||||||
4,
|
RowFilter::Paeth,
|
||||||
ColorType::Indexed,
|
ColorType::Indexed,
|
||||||
BitDepth::Two,
|
BitDepth::Two,
|
||||||
ColorType::Indexed,
|
ColorType::Indexed,
|
||||||
|
|
@ -775,7 +775,7 @@ fn filter_4_for_palette_2() {
|
||||||
fn filter_5_for_palette_2() {
|
fn filter_5_for_palette_2() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_5_for_palette_2.png",
|
"tests/files/filter_5_for_palette_2.png",
|
||||||
5,
|
RowFilter::MinSum,
|
||||||
ColorType::Indexed,
|
ColorType::Indexed,
|
||||||
BitDepth::Two,
|
BitDepth::Two,
|
||||||
ColorType::Indexed,
|
ColorType::Indexed,
|
||||||
|
|
@ -787,7 +787,7 @@ fn filter_5_for_palette_2() {
|
||||||
fn filter_0_for_palette_1() {
|
fn filter_0_for_palette_1() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_0_for_palette_1.png",
|
"tests/files/filter_0_for_palette_1.png",
|
||||||
0,
|
RowFilter::None,
|
||||||
ColorType::Indexed,
|
ColorType::Indexed,
|
||||||
BitDepth::One,
|
BitDepth::One,
|
||||||
ColorType::Indexed,
|
ColorType::Indexed,
|
||||||
|
|
@ -799,7 +799,7 @@ fn filter_0_for_palette_1() {
|
||||||
fn filter_1_for_palette_1() {
|
fn filter_1_for_palette_1() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_1_for_palette_1.png",
|
"tests/files/filter_1_for_palette_1.png",
|
||||||
1,
|
RowFilter::Sub,
|
||||||
ColorType::Indexed,
|
ColorType::Indexed,
|
||||||
BitDepth::One,
|
BitDepth::One,
|
||||||
ColorType::Indexed,
|
ColorType::Indexed,
|
||||||
|
|
@ -811,7 +811,7 @@ fn filter_1_for_palette_1() {
|
||||||
fn filter_2_for_palette_1() {
|
fn filter_2_for_palette_1() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_2_for_palette_1.png",
|
"tests/files/filter_2_for_palette_1.png",
|
||||||
2,
|
RowFilter::Up,
|
||||||
ColorType::Indexed,
|
ColorType::Indexed,
|
||||||
BitDepth::One,
|
BitDepth::One,
|
||||||
ColorType::Indexed,
|
ColorType::Indexed,
|
||||||
|
|
@ -823,7 +823,7 @@ fn filter_2_for_palette_1() {
|
||||||
fn filter_3_for_palette_1() {
|
fn filter_3_for_palette_1() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_3_for_palette_1.png",
|
"tests/files/filter_3_for_palette_1.png",
|
||||||
3,
|
RowFilter::Average,
|
||||||
ColorType::Indexed,
|
ColorType::Indexed,
|
||||||
BitDepth::One,
|
BitDepth::One,
|
||||||
ColorType::Indexed,
|
ColorType::Indexed,
|
||||||
|
|
@ -835,7 +835,7 @@ fn filter_3_for_palette_1() {
|
||||||
fn filter_4_for_palette_1() {
|
fn filter_4_for_palette_1() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_4_for_palette_1.png",
|
"tests/files/filter_4_for_palette_1.png",
|
||||||
4,
|
RowFilter::Paeth,
|
||||||
ColorType::Indexed,
|
ColorType::Indexed,
|
||||||
BitDepth::One,
|
BitDepth::One,
|
||||||
ColorType::Indexed,
|
ColorType::Indexed,
|
||||||
|
|
@ -847,7 +847,7 @@ fn filter_4_for_palette_1() {
|
||||||
fn filter_5_for_palette_1() {
|
fn filter_5_for_palette_1() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_5_for_palette_1.png",
|
"tests/files/filter_5_for_palette_1.png",
|
||||||
5,
|
RowFilter::MinSum,
|
||||||
ColorType::Indexed,
|
ColorType::Indexed,
|
||||||
BitDepth::One,
|
BitDepth::One,
|
||||||
ColorType::Indexed,
|
ColorType::Indexed,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use indexmap::IndexSet;
|
use indexmap::IndexSet;
|
||||||
use oxipng::internal_tests::*;
|
use oxipng::{internal_tests::*, RowFilter};
|
||||||
use oxipng::{InFile, OutFile};
|
use oxipng::{InFile, OutFile};
|
||||||
#[cfg(feature = "filetime")]
|
#[cfg(feature = "filetime")]
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
|
@ -16,7 +16,7 @@ fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let mut filter = IndexSet::new();
|
let mut filter = IndexSet::new();
|
||||||
filter.insert(0);
|
filter.insert(RowFilter::None);
|
||||||
options.filter = filter;
|
options.filter = filter;
|
||||||
|
|
||||||
(
|
(
|
||||||
|
|
@ -173,7 +173,7 @@ fn verbose_mode() {
|
||||||
assert_eq!(logs.len(), 1);
|
assert_eq!(logs.len(), 1);
|
||||||
logs.sort();
|
logs.sort();
|
||||||
for (i, log) in logs.into_iter().enumerate() {
|
for (i, log) in logs.into_iter().enumerate() {
|
||||||
let expected_prefix = format!(" zc = 11 f = 0 ");
|
let expected_prefix = format!(" zc = 11 f = None ");
|
||||||
assert!(
|
assert!(
|
||||||
log.starts_with(&expected_prefix),
|
log.starts_with(&expected_prefix),
|
||||||
"logs[{}] = {:?} doesn't start with {:?}",
|
"logs[{}] = {:?} doesn't start with {:?}",
|
||||||
|
|
@ -454,7 +454,7 @@ fn interlaced_0_to_1_other_filter_mode() {
|
||||||
let (output, mut opts) = get_opts(&input);
|
let (output, mut opts) = get_opts(&input);
|
||||||
opts.interlace = Some(1);
|
opts.interlace = Some(1);
|
||||||
let mut filter = IndexSet::new();
|
let mut filter = IndexSet::new();
|
||||||
filter.insert(4);
|
filter.insert(RowFilter::Paeth);
|
||||||
opts.filter = filter;
|
opts.filter = filter;
|
||||||
|
|
||||||
let png = PngData::new(&input, opts.fix_errors).unwrap();
|
let png = PngData::new(&input, opts.fix_errors).unwrap();
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use indexmap::IndexSet;
|
use indexmap::IndexSet;
|
||||||
use oxipng::internal_tests::*;
|
use oxipng::{internal_tests::*, RowFilter};
|
||||||
use oxipng::{InFile, OutFile};
|
use oxipng::{InFile, OutFile};
|
||||||
use std::fs::remove_file;
|
use std::fs::remove_file;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
@ -11,7 +11,7 @@ fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let mut filter = IndexSet::new();
|
let mut filter = IndexSet::new();
|
||||||
filter.insert(0);
|
filter.insert(RowFilter::None);
|
||||||
options.filter = filter;
|
options.filter = filter;
|
||||||
|
|
||||||
(
|
(
|
||||||
|
|
|
||||||
202
tests/interlacing.rs
Normal file
202
tests/interlacing.rs
Normal file
|
|
@ -0,0 +1,202 @@
|
||||||
|
use indexmap::IndexSet;
|
||||||
|
use oxipng::{internal_tests::*, RowFilter};
|
||||||
|
use oxipng::{InFile, OutFile};
|
||||||
|
use std::fs::remove_file;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
|
||||||
|
let mut options = oxipng::Options {
|
||||||
|
force: true,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let mut filter = IndexSet::new();
|
||||||
|
filter.insert(RowFilter::None);
|
||||||
|
options.filter = filter;
|
||||||
|
|
||||||
|
(
|
||||||
|
OutFile::Path(Some(input.with_extension("out.png"))),
|
||||||
|
options,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_it_converts(
|
||||||
|
input: &str,
|
||||||
|
interlace: u8,
|
||||||
|
color_type_in: ColorType,
|
||||||
|
bit_depth_in: BitDepth,
|
||||||
|
color_type_out: ColorType,
|
||||||
|
bit_depth_out: BitDepth,
|
||||||
|
) {
|
||||||
|
let input = PathBuf::from(input);
|
||||||
|
let (output, mut opts) = get_opts(&input);
|
||||||
|
let png = PngData::new(&input, opts.fix_errors).unwrap();
|
||||||
|
opts.interlace = Some(interlace);
|
||||||
|
assert_eq!(png.raw.ihdr.color_type, color_type_in);
|
||||||
|
assert_eq!(png.raw.ihdr.bit_depth, bit_depth_in);
|
||||||
|
assert_eq!(png.raw.ihdr.interlaced, if interlace == 1 { 0 } else { 1 });
|
||||||
|
|
||||||
|
match oxipng::optimize(&InFile::Path(input), &output, &opts) {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(x) => panic!("{}", x),
|
||||||
|
};
|
||||||
|
let output = output.path().unwrap();
|
||||||
|
assert!(output.exists());
|
||||||
|
|
||||||
|
let png = match PngData::new(output, opts.fix_errors) {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(x) => {
|
||||||
|
remove_file(output).ok();
|
||||||
|
panic!("{}", x)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(png.raw.ihdr.color_type, color_type_out);
|
||||||
|
assert_eq!(png.raw.ihdr.bit_depth, bit_depth_out);
|
||||||
|
|
||||||
|
remove_file(output).ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deinterlace_rgb_16() {
|
||||||
|
test_it_converts(
|
||||||
|
"tests/files/interlaced_rgb_16_should_be_rgb_16.png",
|
||||||
|
0,
|
||||||
|
ColorType::RGB,
|
||||||
|
BitDepth::Sixteen,
|
||||||
|
ColorType::RGB,
|
||||||
|
BitDepth::Sixteen,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deinterlace_rgb_8() {
|
||||||
|
test_it_converts(
|
||||||
|
"tests/files/interlaced_rgb_8_should_be_rgb_8.png",
|
||||||
|
0,
|
||||||
|
ColorType::RGB,
|
||||||
|
BitDepth::Eight,
|
||||||
|
ColorType::RGB,
|
||||||
|
BitDepth::Eight,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deinterlace_palette_8() {
|
||||||
|
test_it_converts(
|
||||||
|
"tests/files/interlaced_palette_8_should_be_palette_8.png",
|
||||||
|
0,
|
||||||
|
ColorType::Indexed,
|
||||||
|
BitDepth::Eight,
|
||||||
|
ColorType::Indexed,
|
||||||
|
BitDepth::Eight,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deinterlace_palette_4() {
|
||||||
|
test_it_converts(
|
||||||
|
"tests/files/interlaced_palette_4_should_be_palette_4.png",
|
||||||
|
0,
|
||||||
|
ColorType::Indexed,
|
||||||
|
BitDepth::Four,
|
||||||
|
ColorType::Indexed,
|
||||||
|
BitDepth::Four,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deinterlace_palette_2() {
|
||||||
|
test_it_converts(
|
||||||
|
"tests/files/interlaced_palette_2_should_be_palette_2.png",
|
||||||
|
0,
|
||||||
|
ColorType::Indexed,
|
||||||
|
BitDepth::Two,
|
||||||
|
ColorType::Indexed,
|
||||||
|
BitDepth::Two,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deinterlace_palette_1() {
|
||||||
|
test_it_converts(
|
||||||
|
"tests/files/interlaced_palette_1_should_be_palette_1.png",
|
||||||
|
0,
|
||||||
|
ColorType::Indexed,
|
||||||
|
BitDepth::One,
|
||||||
|
ColorType::Indexed,
|
||||||
|
BitDepth::One,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn interlace_rgb_16() {
|
||||||
|
test_it_converts(
|
||||||
|
"tests/files/rgb_16_should_be_rgb_16.png",
|
||||||
|
1,
|
||||||
|
ColorType::RGB,
|
||||||
|
BitDepth::Sixteen,
|
||||||
|
ColorType::RGB,
|
||||||
|
BitDepth::Sixteen,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn interlace_rgb_8() {
|
||||||
|
test_it_converts(
|
||||||
|
"tests/files/rgb_8_should_be_rgb_8.png",
|
||||||
|
1,
|
||||||
|
ColorType::RGB,
|
||||||
|
BitDepth::Eight,
|
||||||
|
ColorType::RGB,
|
||||||
|
BitDepth::Eight,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn interlace_palette_8() {
|
||||||
|
test_it_converts(
|
||||||
|
"tests/files/palette_8_should_be_palette_8.png",
|
||||||
|
1,
|
||||||
|
ColorType::Indexed,
|
||||||
|
BitDepth::Eight,
|
||||||
|
ColorType::Indexed,
|
||||||
|
BitDepth::Eight,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn interlace_palette_4() {
|
||||||
|
test_it_converts(
|
||||||
|
"tests/files/palette_4_should_be_palette_4.png",
|
||||||
|
1,
|
||||||
|
ColorType::Indexed,
|
||||||
|
BitDepth::Four,
|
||||||
|
ColorType::Indexed,
|
||||||
|
BitDepth::Four,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn interlace_palette_2() {
|
||||||
|
test_it_converts(
|
||||||
|
"tests/files/palette_2_should_be_palette_2.png",
|
||||||
|
1,
|
||||||
|
ColorType::Indexed,
|
||||||
|
BitDepth::Two,
|
||||||
|
ColorType::Indexed,
|
||||||
|
BitDepth::Two,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn interlace_palette_1() {
|
||||||
|
test_it_converts(
|
||||||
|
"tests/files/palette_1_should_be_palette_1.png",
|
||||||
|
1,
|
||||||
|
ColorType::Indexed,
|
||||||
|
BitDepth::One,
|
||||||
|
ColorType::Indexed,
|
||||||
|
BitDepth::One,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use indexmap::IndexSet;
|
use indexmap::IndexSet;
|
||||||
use oxipng::internal_tests::*;
|
use oxipng::{internal_tests::*, RowFilter};
|
||||||
use oxipng::{InFile, OutFile};
|
use oxipng::{InFile, OutFile};
|
||||||
use std::fs::remove_file;
|
use std::fs::remove_file;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
@ -11,7 +11,7 @@ fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let mut filter = IndexSet::new();
|
let mut filter = IndexSet::new();
|
||||||
filter.insert(0);
|
filter.insert(RowFilter::None);
|
||||||
options.filter = filter;
|
options.filter = filter;
|
||||||
|
|
||||||
(
|
(
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use indexmap::IndexSet;
|
use indexmap::IndexSet;
|
||||||
use oxipng::internal_tests::*;
|
use oxipng::{internal_tests::*, RowFilter};
|
||||||
use oxipng::{InFile, OutFile};
|
use oxipng::{InFile, OutFile};
|
||||||
use std::fs::remove_file;
|
use std::fs::remove_file;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
@ -11,7 +11,7 @@ fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let mut filter = IndexSet::new();
|
let mut filter = IndexSet::new();
|
||||||
filter.insert(0);
|
filter.insert(RowFilter::None);
|
||||||
options.filter = filter;
|
options.filter = filter;
|
||||||
|
|
||||||
(
|
(
|
||||||
|
|
@ -288,7 +288,7 @@ fn issue_92_filter_0() {
|
||||||
fn issue_92_filter_5() {
|
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 = [RowFilter::MinSum].iter().cloned().collect();
|
||||||
let output = OutFile::Path(Some(Path::new(input).with_extension("-f5-out.png")));
|
let output = OutFile::Path(Some(Path::new(input).with_extension("-f5-out.png")));
|
||||||
|
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
|
|
|
||||||
124
tests/strategies.rs
Normal file
124
tests/strategies.rs
Normal file
|
|
@ -0,0 +1,124 @@
|
||||||
|
use indexmap::IndexSet;
|
||||||
|
use oxipng::{internal_tests::*, RowFilter};
|
||||||
|
use oxipng::{InFile, OutFile};
|
||||||
|
use std::fs::remove_file;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
|
||||||
|
let mut options = oxipng::Options {
|
||||||
|
force: true,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let mut filter = IndexSet::new();
|
||||||
|
filter.insert(RowFilter::None);
|
||||||
|
options.filter = filter;
|
||||||
|
|
||||||
|
(
|
||||||
|
OutFile::Path(Some(input.with_extension("out.png"))),
|
||||||
|
options,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_it_converts(
|
||||||
|
input: &str,
|
||||||
|
filter: RowFilter,
|
||||||
|
color_type_in: ColorType,
|
||||||
|
bit_depth_in: BitDepth,
|
||||||
|
color_type_out: ColorType,
|
||||||
|
bit_depth_out: BitDepth,
|
||||||
|
) {
|
||||||
|
let input = PathBuf::from(input);
|
||||||
|
|
||||||
|
let (output, mut opts) = get_opts(&input);
|
||||||
|
let png = PngData::new(&input, opts.fix_errors).unwrap();
|
||||||
|
opts.filter = IndexSet::new();
|
||||||
|
opts.filter.insert(filter);
|
||||||
|
assert_eq!(png.raw.ihdr.color_type, color_type_in);
|
||||||
|
assert_eq!(png.raw.ihdr.bit_depth, bit_depth_in);
|
||||||
|
|
||||||
|
match oxipng::optimize(&InFile::Path(input), &output, &opts) {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(x) => panic!("{}", x),
|
||||||
|
};
|
||||||
|
let output = output.path().unwrap();
|
||||||
|
assert!(output.exists());
|
||||||
|
|
||||||
|
let png = match PngData::new(output, opts.fix_errors) {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(x) => {
|
||||||
|
remove_file(output).ok();
|
||||||
|
panic!("{}", x)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(png.raw.ihdr.color_type, color_type_out);
|
||||||
|
assert_eq!(png.raw.ihdr.bit_depth, bit_depth_out);
|
||||||
|
if let Some(palette) = png.raw.palette.as_ref() {
|
||||||
|
assert!(palette.len() <= 1 << (png.raw.ihdr.bit_depth.as_u8() as usize));
|
||||||
|
} else {
|
||||||
|
assert_ne!(png.raw.ihdr.color_type, ColorType::Indexed);
|
||||||
|
}
|
||||||
|
|
||||||
|
remove_file(output).ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn filter_minsum() {
|
||||||
|
test_it_converts(
|
||||||
|
"tests/files/rgb_16_should_be_rgb_16.png",
|
||||||
|
RowFilter::MinSum,
|
||||||
|
ColorType::RGB,
|
||||||
|
BitDepth::Sixteen,
|
||||||
|
ColorType::RGB,
|
||||||
|
BitDepth::Sixteen,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn filter_entropy() {
|
||||||
|
test_it_converts(
|
||||||
|
"tests/files/rgb_8_should_be_rgb_8.png",
|
||||||
|
RowFilter::Entropy,
|
||||||
|
ColorType::RGB,
|
||||||
|
BitDepth::Eight,
|
||||||
|
ColorType::RGB,
|
||||||
|
BitDepth::Eight,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn filter_bigrams() {
|
||||||
|
test_it_converts(
|
||||||
|
"tests/files/rgba_8_should_be_rgba_8.png",
|
||||||
|
RowFilter::Bigrams,
|
||||||
|
ColorType::RGBA,
|
||||||
|
BitDepth::Eight,
|
||||||
|
ColorType::RGBA,
|
||||||
|
BitDepth::Eight,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn filter_bigent() {
|
||||||
|
test_it_converts(
|
||||||
|
"tests/files/grayscale_8_should_be_grayscale_8.png",
|
||||||
|
RowFilter::BigEnt,
|
||||||
|
ColorType::Grayscale,
|
||||||
|
BitDepth::Eight,
|
||||||
|
ColorType::Grayscale,
|
||||||
|
BitDepth::Eight,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn filter_brute() {
|
||||||
|
test_it_converts(
|
||||||
|
"tests/files/palette_8_should_be_palette_8.png",
|
||||||
|
RowFilter::Brute,
|
||||||
|
ColorType::Indexed,
|
||||||
|
BitDepth::Eight,
|
||||||
|
ColorType::Indexed,
|
||||||
|
BitDepth::Eight,
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue