Merge branch 'master' into master

This commit is contained in:
Josh Holmer 2021-07-11 23:24:53 -04:00 committed by GitHub
commit 577018cc80
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 1529 additions and 551 deletions

View file

@ -11,7 +11,10 @@ jobs:
strategy: strategy:
matrix: matrix:
conf: [x86_64, i686] conf: [x86_64]
# Temporarily disable i686 binaries, they are failing on linking libdeflate
# and I don't have a Windows machine set up to experiment with fixing it.
# conf: [x86_64, i686]
runs-on: windows-latest runs-on: windows-latest

View file

@ -1,3 +1,9 @@
### Version 4.0.3
- Bump itertools to 0.10.x
- Temporarily disable i686 releases, which were failing due to an odd linker issue,
so that at least amd64 builds will publish successfully
- This only relates to the releases published on Github. You can still manually compile oxipng for any platform.
### Version 4.0.2 ### Version 4.0.2
- Includes another update to libdeflater that improves support for targets - Includes another update to libdeflater that improves support for targets
without a C stdlib, like wasm32. without a C stdlib, like wasm32.

89
Cargo.lock generated
View file

@ -2,9 +2,9 @@
# It is not intended for manual editing. # It is not intended for manual editing.
[[package]] [[package]]
name = "adler" name = "adler"
version = "0.2.3" version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]] [[package]]
name = "adler32" name = "adler32"
@ -58,27 +58,21 @@ checksum = "39092a32794787acd8525ee150305ff051b0aa6cc2abaf193924f5ab05425f39"
[[package]] [[package]]
name = "bytemuck" name = "bytemuck"
version = "1.4.1" version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41aa2ec95ca3b5c54cf73c91acf06d24f4495d5f1b1c12506ae3483d646177ac" checksum = "bed57e2090563b83ba8f83366628ce535a7584c9afa4c9fc0612a03925c6df58"
[[package]] [[package]]
name = "byteorder" name = "byteorder"
version = "1.3.4" version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.66" version = "1.0.67"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48" checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd"
[[package]]
name = "cfg-if"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
@ -138,12 +132,6 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
[[package]]
name = "const_fn"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd51eab21ab4fd6a3bf889e2d0958c0a6e3a61ad04260325e919e652a2a62826"
[[package]] [[package]]
name = "crc" name = "crc"
version = "1.8.1" version = "1.8.1"
@ -159,7 +147,7 @@ version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a"
dependencies = [ dependencies = [
"cfg-if 1.0.0", "cfg-if",
] ]
[[package]] [[package]]
@ -168,7 +156,7 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775" checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775"
dependencies = [ dependencies = [
"cfg-if 1.0.0", "cfg-if",
"crossbeam-utils", "crossbeam-utils",
] ]
@ -178,19 +166,18 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9" checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9"
dependencies = [ dependencies = [
"cfg-if 1.0.0", "cfg-if",
"crossbeam-epoch", "crossbeam-epoch",
"crossbeam-utils", "crossbeam-utils",
] ]
[[package]] [[package]]
name = "crossbeam-epoch" name = "crossbeam-epoch"
version = "0.9.1" version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1aaa739f95311c2c7887a76863f500026092fb1dce0161dab577e559ef3569d" checksum = "2584f639eb95fea8c798496315b297cf81b9b58b6d30ab066a75455333cf4b12"
dependencies = [ dependencies = [
"cfg-if 1.0.0", "cfg-if",
"const_fn",
"crossbeam-utils", "crossbeam-utils",
"lazy_static", "lazy_static",
"memoffset", "memoffset",
@ -199,12 +186,12 @@ dependencies = [
[[package]] [[package]]
name = "crossbeam-utils" name = "crossbeam-utils"
version = "0.8.1" version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d" checksum = "e7e9d99fa91428effe99c5c6d4634cdeba32b8cf784fc428a2a687f61a952c49"
dependencies = [ dependencies = [
"autocfg", "autocfg",
"cfg-if 1.0.0", "cfg-if",
"lazy_static", "lazy_static",
] ]
@ -250,18 +237,18 @@ checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
[[package]] [[package]]
name = "hermit-abi" name = "hermit-abi"
version = "0.1.17" version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8" checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c"
dependencies = [ dependencies = [
"libc", "libc",
] ]
[[package]] [[package]]
name = "image" name = "image"
version = "0.23.12" version = "0.23.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ce04077ead78e39ae8610ad26216aed811996b043d47beed5090db674f9e9b5" checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1"
dependencies = [ dependencies = [
"bytemuck", "bytemuck",
"byteorder", "byteorder",
@ -285,9 +272,9 @@ dependencies = [
[[package]] [[package]]
name = "itertools" name = "itertools"
version = "0.9.0" version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" checksum = "37d572918e350e82412fe766d24b15e6682fb2ed2bbe018280caa810397cb319"
dependencies = [ dependencies = [
"either", "either",
] ]
@ -300,9 +287,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.81" version = "0.2.87"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb" checksum = "265d751d31d6780a3f956bb5b8022feba2d94eeee5a84ba64f4212eedca42213"
[[package]] [[package]]
name = "libdeflate-sys" name = "libdeflate-sys"
@ -324,11 +311,11 @@ dependencies = [
[[package]] [[package]]
name = "log" name = "log"
version = "0.4.11" version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
dependencies = [ dependencies = [
"cfg-if 0.1.10", "cfg-if",
] ]
[[package]] [[package]]
@ -351,9 +338,9 @@ dependencies = [
[[package]] [[package]]
name = "miniz_oxide" name = "miniz_oxide"
version = "0.4.3" version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f2d26ec3309788e423cfbf68ad1800f061638098d76a83681af979dc4eda19d" checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b"
dependencies = [ dependencies = [
"adler", "adler",
"autocfg", "autocfg",
@ -412,7 +399,7 @@ dependencies = [
[[package]] [[package]]
name = "oxipng" name = "oxipng"
version = "4.0.2" version = "4.0.3"
dependencies = [ dependencies = [
"bit-vec", "bit-vec",
"byteorder", "byteorder",
@ -426,7 +413,7 @@ dependencies = [
"itertools", "itertools",
"libdeflater", "libdeflater",
"log", "log",
"miniz_oxide 0.4.3", "miniz_oxide 0.4.4",
"rayon", "rayon",
"rgb", "rgb",
"rustc_version", "rustc_version",
@ -498,9 +485,9 @@ dependencies = [
[[package]] [[package]]
name = "rustc_version" name = "rustc_version"
version = "0.3.0" version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65c94201b44764d6d1f7e37c15a8289ed55e546c1762c7f1d57f616966e0c181" checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee"
dependencies = [ dependencies = [
"semver", "semver",
] ]
@ -522,18 +509,18 @@ dependencies = [
[[package]] [[package]]
name = "semver-parser" name = "semver-parser"
version = "0.10.1" version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ef146c2ad5e5f4b037cd6ce2ebb775401729b19a82040c1beac9d36c7d1428" checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7"
dependencies = [ dependencies = [
"pest", "pest",
] ]
[[package]] [[package]]
name = "stderrlog" name = "stderrlog"
version = "0.5.0" version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b02f316286ae558d83acc93dd81eaba096e746987a7961d4a9ae026842bae67f" checksum = "45a53e2eff3e94a019afa6265e8ee04cb05b9d33fe9f5078b14e4e391d155a38"
dependencies = [ dependencies = [
"atty", "atty",
"chrono", "chrono",

View file

@ -16,7 +16,7 @@ license = "MIT"
name = "oxipng" name = "oxipng"
repository = "https://github.com/shssoichiro/oxipng" repository = "https://github.com/shssoichiro/oxipng"
build = "build.rs" build = "build.rs"
version = "4.0.2" version = "4.0.3"
[badges] [badges]
travis-ci = { repository = "shssoichiro/oxipng", branch = "master" } travis-ci = { repository = "shssoichiro/oxipng", branch = "master" }
@ -30,16 +30,16 @@ required-features = ["binary"]
[dependencies] [dependencies]
bit-vec = "^0.6.3" bit-vec = "^0.6.3"
byteorder = "^1.0.0" byteorder = "^1.4.2"
crc = "^1.2.0" crc = "^1.2.0"
itertools = "^0.9.0" itertools = "^0.10.0"
zopfli = { version = "^0.4.0", optional = true } zopfli = { version = "^0.4.0", optional = true }
miniz_oxide = "0.4" miniz_oxide = "0.4"
rgb = "0.8.25" rgb = "0.8.25"
indexmap = "1.6.1" indexmap = "1.6.1"
libdeflater = { version = "0.7.1", optional = true } libdeflater = { version = "0.7.1", optional = true }
log = "0.4.11" log = "0.4.14"
stderrlog = { version = "0.5.0", optional = true } stderrlog = { version = "0.5.1", optional = true }
crossbeam-channel = "0.5.0" crossbeam-channel = "0.5.0"
[dependencies.filetime] [dependencies.filetime]
@ -82,7 +82,12 @@ parallel = ["rayon", "indexmap/rayon"]
[lib] [lib]
name = "oxipng" name = "oxipng"
path = "src/lib.rs" path = "src/lib.rs"
[profile.dev] [profile.dev]
opt-level = 2 opt-level = 2
[profile.release] [profile.release]
lto = "thin" lto = "thin"
[profile.dev.package.bit-vec]
opt-level = 3

View file

@ -80,7 +80,7 @@ fn reductions_rgba_to_rgb_16(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/rgba_16_should_be_rgb_16.png")); let input = test::black_box(PathBuf::from("tests/files/rgba_16_should_be_rgb_16.png"));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, false).unwrap();
b.iter(|| reduce_color_type(&png.raw)); b.iter(|| reduce_color_type(&png.raw, true));
} }
#[bench] #[bench]
@ -88,7 +88,7 @@ fn reductions_rgba_to_rgb_8(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/rgba_8_should_be_rgb_8.png")); let input = test::black_box(PathBuf::from("tests/files/rgba_8_should_be_rgb_8.png"));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, false).unwrap();
b.iter(|| reduce_color_type(&png.raw)); b.iter(|| reduce_color_type(&png.raw, true));
} }
#[bench] #[bench]
@ -98,7 +98,7 @@ fn reductions_rgba_to_grayscale_alpha_16(b: &mut Bencher) {
)); ));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, false).unwrap();
b.iter(|| reduce_color_type(&png.raw)); b.iter(|| reduce_color_type(&png.raw, true));
} }
#[bench] #[bench]
@ -108,7 +108,7 @@ fn reductions_rgba_to_grayscale_alpha_8(b: &mut Bencher) {
)); ));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, false).unwrap();
b.iter(|| reduce_color_type(&png.raw)); b.iter(|| reduce_color_type(&png.raw, true));
} }
#[bench] #[bench]
@ -118,7 +118,7 @@ fn reductions_rgba_to_grayscale_16(b: &mut Bencher) {
)); ));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, false).unwrap();
b.iter(|| reduce_color_type(&png.raw)); b.iter(|| reduce_color_type(&png.raw, true));
} }
#[bench] #[bench]
@ -128,7 +128,7 @@ fn reductions_rgba_to_grayscale_8(b: &mut Bencher) {
)); ));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, false).unwrap();
b.iter(|| reduce_color_type(&png.raw)); b.iter(|| reduce_color_type(&png.raw, true));
} }
#[bench] #[bench]
@ -138,7 +138,7 @@ fn reductions_rgb_to_grayscale_16(b: &mut Bencher) {
)); ));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, false).unwrap();
b.iter(|| reduce_color_type(&png.raw)); b.iter(|| reduce_color_type(&png.raw, true));
} }
#[bench] #[bench]
@ -146,7 +146,7 @@ fn reductions_rgb_to_grayscale_8(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_grayscale_8.png")); let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_grayscale_8.png"));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, false).unwrap();
b.iter(|| reduce_color_type(&png.raw)); b.iter(|| reduce_color_type(&png.raw, true));
} }
#[bench] #[bench]
@ -154,7 +154,7 @@ fn reductions_rgba_to_palette_8(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/rgba_8_should_be_palette_8.png")); let input = test::black_box(PathBuf::from("tests/files/rgba_8_should_be_palette_8.png"));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, false).unwrap();
b.iter(|| reduce_color_type(&png.raw)); b.iter(|| reduce_color_type(&png.raw, true));
} }
#[bench] #[bench]
@ -162,7 +162,7 @@ fn reductions_rgb_to_palette_8(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_palette_8.png")); let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_palette_8.png"));
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, false).unwrap();
b.iter(|| reduce_color_type(&png.raw)); b.iter(|| reduce_color_type(&png.raw, true));
} }
#[bench] #[bench]

1429
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -21,6 +21,6 @@
}, },
"homepage": "https://github.com/shssoichiro/oxipng#readme", "homepage": "https://github.com/shssoichiro/oxipng#readme",
"devDependencies": { "devDependencies": {
"strip-ansi-cli": "^2.0.0" "strip-ansi-cli": "^3.0.0"
} }
} }

View file

@ -9,13 +9,13 @@ pub struct AtomicMin {
impl AtomicMin { impl AtomicMin {
pub fn new(init: Option<usize>) -> Self { pub fn new(init: Option<usize>) -> Self {
Self { Self {
val: AtomicUsize::new(init.unwrap_or(usize::max_value())), val: AtomicUsize::new(init.unwrap_or(usize::MAX)),
} }
} }
pub fn get(&self) -> Option<usize> { pub fn get(&self) -> Option<usize> {
let val = self.val.load(SeqCst); let val = self.val.load(SeqCst);
if val == usize::max_value() { if val == usize::MAX {
None None
} else { } else {
Some(val) Some(val)

View file

@ -101,6 +101,10 @@ impl BitDepth {
} }
} }
/// Parse a number of bits per channel per pixel into a `BitDepth` /// Parse a number of bits per channel per pixel into a `BitDepth`
///
/// # Panics
///
/// If depth is unsupported
#[inline] #[inline]
pub fn from_u8(depth: u8) -> BitDepth { pub fn from_u8(depth: u8) -> BitDepth {
match depth { match depth {

View file

@ -14,15 +14,11 @@ pub enum PngError {
Other(Box<str>), Other(Box<str>),
} }
impl Error for PngError { impl Error for PngError {}
// deprecated
fn description(&self) -> &str {
""
}
}
impl fmt::Display for PngError { impl fmt::Display for PngError {
#[inline] #[inline]
#[cold]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self { match *self {
PngError::DeflatedDataTooLong(_) => f.write_str("deflated data too long"), PngError::DeflatedDataTooLong(_) => f.write_str("deflated data too long"),
@ -40,7 +36,7 @@ impl fmt::Display for PngError {
} }
impl PngError { impl PngError {
#[inline] #[cold]
pub fn new(description: &str) -> PngError { pub fn new(description: &str) -> PngError {
PngError::Other(description.into()) PngError::Other(description.into())
} }

View file

@ -1,4 +1,8 @@
use crate::error::PngError;
pub fn filter_line(filter: u8, bpp: usize, data: &[u8], last_line: &[u8], buf: &mut Vec<u8>) { pub fn filter_line(filter: u8, bpp: usize, data: &[u8], last_line: &[u8], buf: &mut Vec<u8>) {
assert!(data.len() >= bpp);
assert!(last_line.is_empty() || data.len() == last_line.len());
buf.reserve(data.len()); buf.reserve(data.len());
match filter { match filter {
0 => { 0 => {
@ -17,6 +21,7 @@ pub fn filter_line(filter: u8, bpp: usize, data: &[u8], last_line: &[u8], buf: &
if last_line.is_empty() { if last_line.is_empty() {
buf.extend_from_slice(data); buf.extend_from_slice(data);
} else { } else {
assert_eq!(data.len(), last_line.len());
buf.extend( buf.extend(
data.iter() data.iter()
.zip(last_line.iter()) .zip(last_line.iter())
@ -62,95 +67,64 @@ pub fn filter_line(filter: u8, bpp: usize, data: &[u8], last_line: &[u8], buf: &
} }
} }
pub fn unfilter_line(filter: u8, bpp: usize, data: &[u8], last_line: &[u8], buf: &mut Vec<u8>) { pub fn unfilter_line(
assert_eq!(buf.len(), 0); filter: u8,
bpp: usize,
data: &[u8],
last_line: &[u8],
buf: &mut Vec<u8>,
) -> Result<(), PngError> {
buf.clear();
buf.reserve(data.len()); buf.reserve(data.len());
assert!(data.len() >= bpp);
assert_eq!(data.len(), last_line.len());
match filter { match filter {
0 => { 0 => {
buf.extend_from_slice(data); buf.extend_from_slice(data);
} }
1 => { 1 => {
for (i, byte) in data.iter().enumerate() { for (i, &cur) in data.iter().enumerate() {
match i.checked_sub(bpp) { let prev_byte = i.checked_sub(bpp).and_then(|x| buf.get(x).copied());
Some(x) => { buf.push(match prev_byte {
let b = buf[x]; Some(b) => cur.wrapping_add(b),
buf.push(byte.wrapping_add(b)); None => cur,
} });
None => {
buf.push(*byte);
}
};
} }
} }
2 => { 2 => {
if last_line.is_empty() { buf.extend(
buf.extend_from_slice(data); data.iter()
} else { .zip(last_line)
buf.extend( .map(|(&cur, &last)| cur.wrapping_add(last)),
data.iter() );
.zip(last_line.iter())
.map(|(cur, last)| cur.wrapping_add(*last)),
);
};
} }
3 => { 3 => {
for (i, byte) in data.iter().enumerate() { for (i, (&cur, &last)) in data.iter().zip(last_line).enumerate() {
if last_line.is_empty() { let prev_byte = i.checked_sub(bpp).and_then(|x| buf.get(x).copied());
match i.checked_sub(bpp) { buf.push(match prev_byte {
Some(x) => { Some(b) => cur.wrapping_add(((u16::from(b) + u16::from(last)) >> 1) as u8),
let b = buf[x]; None => cur.wrapping_add(last >> 1),
buf.push(byte.wrapping_add(b >> 1)); });
}
None => {
buf.push(*byte);
}
};
} else {
match i.checked_sub(bpp) {
Some(x) => {
let b = buf[x];
buf.push(byte.wrapping_add(
((u16::from(b) + u16::from(last_line[i])) >> 1) as u8,
));
}
None => {
buf.push(byte.wrapping_add(last_line[i] >> 1));
}
};
};
} }
} }
4 => { 4 => {
for (i, byte) in data.iter().enumerate() { for (i, (&cur, &up)) in data.iter().zip(last_line).enumerate() {
if last_line.is_empty() { buf.push(
match i.checked_sub(bpp) { match i
Some(x) => { .checked_sub(bpp)
let b = buf[x]; .map(|x| (buf.get(x).copied(), last_line.get(x).copied()))
buf.push(byte.wrapping_add(b)); {
Some((Some(left), Some(left_up))) => {
cur.wrapping_add(paeth_predictor(left, up, left_up))
} }
None => { _ => cur.wrapping_add(up),
buf.push(*byte); },
} );
};
} else {
match i.checked_sub(bpp) {
Some(x) => {
let b = buf[x];
buf.push(byte.wrapping_add(paeth_predictor(
b,
last_line[i],
last_line[x],
)));
}
None => {
buf.push(byte.wrapping_add(last_line[i]));
}
};
};
} }
} }
_ => unreachable!(), _ => return Err(PngError::InvalidData),
} }
Ok(())
} }
fn paeth_predictor(a: u8, b: u8, c: u8) -> u8 { fn paeth_predictor(a: u8, b: u8, c: u8) -> u8 {

View file

@ -25,6 +25,45 @@ pub struct IhdrData {
pub interlaced: u8, pub interlaced: u8,
} }
impl IhdrData {
/// Bits per pixel
#[must_use]
#[inline]
pub fn bpp(&self) -> u8 {
self.bit_depth.as_u8() * self.color_type.channels_per_pixel()
}
/// Byte length of IDAT that is correct for this IHDR
#[must_use]
pub fn raw_data_size(&self) -> usize {
let w = self.width as usize;
let h = self.height as usize;
let bpp = self.bpp();
fn bitmap_size(bpp: u8, w: usize, h: usize) -> usize {
(((w / 8) * bpp as usize) + ((w & 7) * bpp as usize + 7) / 8) * h
}
if self.interlaced == 0 {
bitmap_size(bpp, w, h) + h
} else {
let mut size = bitmap_size(bpp, (w + 7) >> 3, (h + 7) >> 3) + ((h + 7) >> 3);
if w > 4 {
size += bitmap_size(bpp, (w + 3) >> 3, (h + 7) >> 3) + ((h + 7) >> 3);
}
size += bitmap_size(bpp, (w + 3) >> 2, (h + 3) >> 3) + ((h + 3) >> 3);
if w > 2 {
size += bitmap_size(bpp, (w + 1) >> 2, (h + 3) >> 2) + ((h + 3) >> 2);
}
size += bitmap_size(bpp, (w + 1) >> 1, (h + 1) >> 2) + ((h + 1) >> 2);
if w > 1 {
size += bitmap_size(bpp, w >> 1, (h + 1) >> 1) + ((h + 1) >> 1);
}
size + bitmap_size(bpp, w, h >> 1) + (h >> 1)
}
}
}
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
/// Options to use for performing operations on headers (such as stripping) /// Options to use for performing operations on headers (such as stripping)
pub enum Headers { pub enum Headers {
@ -98,12 +137,14 @@ pub fn parse_next_header<'a>(
))); )));
} }
let mut name = [0u8; 4]; let mut name = [0_u8; 4];
name.copy_from_slice(chunk_name); name.copy_from_slice(chunk_name);
Ok(Some(RawHeader { name, data })) Ok(Some(RawHeader { name, data }))
} }
pub fn parse_ihdr_header(byte_data: &[u8]) -> PngResult<IhdrData> { pub fn parse_ihdr_header(byte_data: &[u8]) -> PngResult<IhdrData> {
// This eliminates bounds checks for the rest of the function
let interlaced = byte_data.get(12).copied().ok_or(PngError::TruncatedData)?;
let mut rdr = Cursor::new(&byte_data[0..8]); let mut rdr = Cursor::new(&byte_data[0..8]);
Ok(IhdrData { Ok(IhdrData {
color_type: match byte_data[9] { color_type: match byte_data[9] {
@ -122,10 +163,14 @@ pub fn parse_ihdr_header(byte_data: &[u8]) -> PngResult<IhdrData> {
16 => BitDepth::Sixteen, 16 => BitDepth::Sixteen,
_ => return Err(PngError::new("Unexpected bit depth in header")), _ => return Err(PngError::new("Unexpected bit depth in header")),
}, },
width: rdr.read_u32::<BigEndian>().unwrap(), width: rdr
height: rdr.read_u32::<BigEndian>().unwrap(), .read_u32::<BigEndian>()
.map_err(|_| PngError::TruncatedData)?,
height: rdr
.read_u32::<BigEndian>()
.map_err(|_| PngError::TruncatedData)?,
compression: byte_data[10], compression: byte_data[10],
filter: byte_data[11], filter: byte_data[11],
interlaced: byte_data[12], interlaced,
}) })
} }

View file

@ -5,7 +5,7 @@ use bit_vec::BitVec;
#[must_use] #[must_use]
pub fn interlace_image(png: &PngImage) -> PngImage { pub fn interlace_image(png: &PngImage) -> PngImage {
let mut passes: Vec<BitVec> = vec![BitVec::new(); 7]; let mut passes: Vec<BitVec> = vec![BitVec::new(); 7];
let bits_per_pixel = png.ihdr.bit_depth.as_u8() * png.channels_per_pixel(); let bits_per_pixel = png.ihdr.bpp();
for (index, line) in png.scan_lines().enumerate() { for (index, line) in png.scan_lines().enumerate() {
match index % 8 { match index % 8 {
// Add filter bytes to passes that will be in the output image // Add filter bytes to passes that will be in the output image
@ -40,7 +40,7 @@ pub fn interlace_image(png: &PngImage) -> PngImage {
passes[6].extend(BitVec::from_elem(8, false)); passes[6].extend(BitVec::from_elem(8, false));
} }
} }
let bit_vec = BitVec::from_bytes(&line.data); let bit_vec = BitVec::from_bytes(line.data);
for (i, bit) in bit_vec.iter().enumerate() { for (i, bit) in bit_vec.iter().enumerate() {
// Avoid moving padded 0's into new image // Avoid moving padded 0's into new image
if i >= (png.ihdr.width * u32::from(bits_per_pixel)) as usize { if i >= (png.ihdr.width * u32::from(bits_per_pixel)) as usize {
@ -95,7 +95,7 @@ pub fn interlace_image(png: &PngImage) -> PngImage {
} }
pub fn deinterlace_image(png: &PngImage) -> PngImage { pub fn deinterlace_image(png: &PngImage) -> PngImage {
let bits_per_pixel = png.ihdr.bit_depth.as_u8() * png.channels_per_pixel(); let bits_per_pixel = png.ihdr.bpp();
let bits_per_line = 8 + bits_per_pixel as usize * png.ihdr.width as usize; let bits_per_line = 8 + bits_per_pixel as usize * png.ihdr.width as usize;
// Initialize each output line with a starting filter byte of 0 // Initialize each output line with a starting filter byte of 0
// as well as some blank data // as well as some blank data
@ -105,7 +105,7 @@ pub fn deinterlace_image(png: &PngImage) -> PngImage {
let mut pass_constants = interlaced_constants(current_pass); let mut pass_constants = interlaced_constants(current_pass);
let mut current_y: usize = pass_constants.y_shift as usize; let mut current_y: usize = pass_constants.y_shift as usize;
for line in png.scan_lines() { for line in png.scan_lines() {
let bit_vec = BitVec::from_bytes(&line.data); let bit_vec = BitVec::from_bytes(line.data);
let bits_in_line = ((png.ihdr.width - u32::from(pass_constants.x_shift) let bits_in_line = ((png.ihdr.width - u32::from(pass_constants.x_shift)
+ u32::from(pass_constants.x_step) + u32::from(pass_constants.x_step)
- 1) - 1)

View file

@ -12,6 +12,7 @@
#![warn(clippy::path_buf_push_overwrite)] #![warn(clippy::path_buf_push_overwrite)]
#![warn(clippy::range_plus_one)] #![warn(clippy::range_plus_one)]
#![allow(clippy::cognitive_complexity)] #![allow(clippy::cognitive_complexity)]
#![allow(clippy::upper_case_acronyms)]
#![cfg_attr( #![cfg_attr(
not(any(feature = "libdeflater", feature = "zopfli")), not(any(feature = "libdeflater", feature = "zopfli")),
allow(irrefutable_let_patterns), allow(irrefutable_let_patterns),
@ -97,7 +98,7 @@ impl InFile {
pub fn path(&self) -> Option<&Path> { pub fn path(&self) -> Option<&Path> {
match *self { match *self {
InFile::Path(ref p) => Some(p.as_path()), InFile::Path(ref p) => Some(p.as_path()),
_ => None, InFile::StdIn => None,
} }
} }
} }
@ -168,6 +169,10 @@ pub struct Options {
/// ///
/// Default: `true` /// Default: `true`
pub palette_reduction: bool, pub palette_reduction: bool,
/// Whether to attempt grayscale reduction
///
/// Default: `true`
pub grayscale_reduction: bool,
/// Whether to perform IDAT recoding /// Whether to perform IDAT recoding
/// ///
/// If any type of reduction is performed, IDAT recoding will be performed /// If any type of reduction is performed, IDAT recoding will be performed
@ -304,6 +309,7 @@ impl Default for Options {
bit_depth_reduction: true, bit_depth_reduction: true,
color_type_reduction: true, color_type_reduction: true,
palette_reduction: true, palette_reduction: true,
grayscale_reduction: true,
idat_recoding: true, idat_recoding: true,
strip: Headers::None, strip: Headers::None,
deflate: Deflaters::Zlib { deflate: Deflaters::Zlib {
@ -616,7 +622,7 @@ fn optimize_png(
); );
return None; return None;
} }
_ => return None, Err(_) => return None,
}; };
// update best size across all threads // update best size across all threads
@ -682,14 +688,14 @@ fn optimize_png(
" file size = {} bytes ({} bytes = {:.2}% decrease)", " file size = {} bytes ({} bytes = {:.2}% decrease)",
output.len(), output.len(),
file_original_size - output.len(), file_original_size - output.len(),
(file_original_size - output.len()) as f64 / file_original_size as f64 * 100f64 (file_original_size - output.len()) as f64 / file_original_size as f64 * 100_f64
); );
} else { } else {
info!( info!(
" file size = {} bytes ({} bytes = {:.2}% increase)", " file size = {} bytes ({} bytes = {:.2}% increase)",
output.len(), output.len(),
output.len() - file_original_size, output.len() - file_original_size,
(output.len() - file_original_size) as f64 / file_original_size as f64 * 100f64 (output.len() - file_original_size) as f64 / file_original_size as f64 * 100_f64
); );
} }
@ -767,7 +773,7 @@ fn perform_reductions(
} }
if opts.color_type_reduction { if opts.color_type_reduction {
if let Some(reduced) = reduce_color_type(&png) { if let Some(reduced) = reduce_color_type(&png, opts.grayscale_reduction) {
png = Arc::new(reduced); png = Arc::new(reduced);
eval.try_image(png.clone()); eval.try_image(png.clone());
report_reduction(&png); report_reduction(&png);
@ -812,10 +818,14 @@ impl Deadline {
if let Some(imp) = &self.imp { if let Some(imp) = &self.imp {
let elapsed = imp.start.elapsed(); let elapsed = imp.start.elapsed();
if elapsed > imp.timeout { if elapsed > imp.timeout {
if imp if match imp.print_message.compare_exchange(
.print_message true,
.compare_and_swap(true, false, Ordering::SeqCst) false,
{ Ordering::SeqCst,
Ordering::SeqCst,
) {
Ok(x) | Err(x) => x,
} {
warn!("Timed out after {} second(s)", elapsed.as_secs()); warn!("Timed out after {} second(s)", elapsed.as_secs());
} }
return true; return true;

View file

@ -184,6 +184,9 @@ fn main() {
.arg(Arg::with_name("no-palette-reduction") .arg(Arg::with_name("no-palette-reduction")
.help("No palette reduction") .help("No palette reduction")
.long("np")) .long("np"))
.arg(Arg::with_name("no-grayscale-reduction")
.help("No grayscale reduction")
.long("ng"))
.arg(Arg::with_name("no-reductions") .arg(Arg::with_name("no-reductions")
.help("No reductions") .help("No reductions")
.long("nx")) .long("nx"))
@ -433,10 +436,15 @@ fn parse_opts_into_struct(
opts.palette_reduction = false; opts.palette_reduction = false;
} }
if matches.is_present("no-grayscale-reduction") {
opts.grayscale_reduction = false;
}
if matches.is_present("no-reductions") { if matches.is_present("no-reductions") {
opts.bit_depth_reduction = false; opts.bit_depth_reduction = false;
opts.color_type_reduction = false; opts.color_type_reduction = false;
opts.palette_reduction = false; opts.palette_reduction = false;
opts.grayscale_reduction = false;
} }
if matches.is_present("no-recoding") { if matches.is_present("no-recoding") {

View file

@ -100,7 +100,7 @@ impl PngData {
let mut idat_headers: Vec<u8> = Vec::new(); let mut idat_headers: Vec<u8> = Vec::new();
while let Some(header) = parse_next_header(byte_data, &mut byte_offset, fix_errors)? { while let Some(header) = parse_next_header(byte_data, &mut byte_offset, fix_errors)? {
match &header.name { match &header.name {
b"IDAT" => idat_headers.extend(header.data), b"IDAT" => idat_headers.extend_from_slice(header.data),
b"acTL" => return Err(PngError::APNGNotSupported), b"acTL" => return Err(PngError::APNGNotSupported),
_ => { _ => {
aux_headers.insert(header.name, header.data.to_owned()); aux_headers.insert(header.name, header.data.to_owned());
@ -118,6 +118,11 @@ impl PngData {
let ihdr_header = parse_ihdr_header(&ihdr)?; let ihdr_header = parse_ihdr_header(&ihdr)?;
let raw_data = deflate::inflate(idat_headers.as_ref())?; let raw_data = deflate::inflate(idat_headers.as_ref())?;
// Reject files with incorrect width/height or truncated data
if raw_data.len() != ihdr_header.raw_data_size() {
return Err(PngError::TruncatedData);
}
let (palette, transparency_pixel) = Self::palette_to_rgba( let (palette, transparency_pixel) = Self::palette_to_rgba(
ihdr_header.color_type, ihdr_header.color_type,
aux_headers.remove(b"PLTE"), aux_headers.remove(b"PLTE"),
@ -131,7 +136,7 @@ impl PngData {
transparency_pixel, transparency_pixel,
aux_headers, aux_headers,
}; };
raw.data = raw.unfilter_image(); raw.data = raw.unfilter_image()?;
// Return the PngData // Return the PngData
Ok(Self { Ok(Self {
idat_data: idat_headers, idat_data: idat_headers,
@ -203,10 +208,10 @@ impl PngData {
.fold( .fold(
0, 0,
|prev, (index, px)| { |prev, (index, px)| {
if px.a != 255 { if px.a == 255 {
index + 1
} else {
prev prev
} else {
index + 1
} }
}, },
); );
@ -276,32 +281,25 @@ impl PngImage {
} }
/// Reverse all filters applied on the image, returning an unfiltered IDAT bytestream /// Reverse all filters applied on the image, returning an unfiltered IDAT bytestream
fn unfilter_image(&self) -> Vec<u8> { fn unfilter_image(&self) -> Result<Vec<u8>, PngError> {
let mut unfiltered = Vec::with_capacity(self.data.len()); let mut unfiltered = Vec::with_capacity(self.data.len());
let bpp = ((self.ihdr.bit_depth.as_u8() * self.channels_per_pixel() + 7) / 8) as usize; let bpp = ((self.ihdr.bit_depth.as_u8() * self.channels_per_pixel() + 7) / 8) as usize;
let mut last_line: Vec<u8> = Vec::new(); let mut last_line: Vec<u8> = Vec::new();
let mut last_pass = 1; let mut last_pass = None;
let mut unfiltered_buf = Vec::new(); let mut unfiltered_buf = Vec::new();
for line in self.scan_lines() { for line in self.scan_lines() {
if let Some(pass) = line.pass { if last_pass != line.pass {
if pass != last_pass { last_line.clear();
last_line.clear(); last_pass = line.pass;
last_pass = pass;
}
} }
unfilter_line( last_line.resize(line.data.len(), 0);
line.filter, unfilter_line(line.filter, bpp, line.data, &last_line, &mut unfiltered_buf)?;
bpp,
&line.data,
&last_line,
&mut unfiltered_buf,
);
unfiltered.push(0); unfiltered.push(0);
unfiltered.extend_from_slice(&unfiltered_buf); unfiltered.extend_from_slice(&unfiltered_buf);
std::mem::swap(&mut last_line, &mut unfiltered_buf); std::mem::swap(&mut last_line, &mut unfiltered_buf);
unfiltered_buf.clear(); unfiltered_buf.clear();
} }
unfiltered Ok(unfiltered)
} }
/// Apply the specified filter type to all rows in the image /// Apply the specified filter type to all rows in the image
@ -319,6 +317,9 @@ impl PngImage {
let mut f_buf = Vec::new(); let mut f_buf = Vec::new();
for line in self.scan_lines() { for line in self.scan_lines() {
f_buf.clear(); f_buf.clear();
if last_pass != line.pass {
last_line = &[];
}
match filter { match filter {
0 | 1 | 2 | 3 | 4 => { 0 | 1 | 2 | 3 | 4 => {
let filter = if last_pass == line.pass || filter <= 1 { let filter = if last_pass == line.pass || filter <= 1 {
@ -327,7 +328,7 @@ impl PngImage {
0 0
}; };
filtered.push(filter); filtered.push(filter);
filter_line(filter, bpp, &line.data, last_line, &mut f_buf); filter_line(filter, bpp, line.data, last_line, &mut f_buf);
filtered.extend_from_slice(&f_buf); filtered.extend_from_slice(&f_buf);
} }
5 => { 5 => {
@ -336,12 +337,12 @@ impl PngImage {
// http://www.libpng.org/pub/png/book/chapter09.html // http://www.libpng.org/pub/png/book/chapter09.html
let mut best_filter = 0; let mut best_filter = 0;
let mut best_line = Vec::new(); let mut best_line = Vec::new();
let mut best_size = std::u64::MAX; let mut best_size = u64::MAX;
// Avoid vertical filtering on first line of each interlacing pass // Avoid vertical filtering on first line of each interlacing pass
for filter in if last_pass == line.pass { 0..5 } else { 0..2 } { for filter in if last_pass == line.pass { 0..5 } else { 0..2 } {
filter_line(filter, bpp, &line.data, last_line, &mut f_buf); filter_line(filter, bpp, line.data, last_line, &mut f_buf);
let size = f_buf.iter().fold(0u64, |acc, &x| { let size = f_buf.iter().fold(0_u64, |acc, &x| {
let signed = x as i8; let signed = x as i8;
acc + i16::from(signed).abs() as u64 acc + i16::from(signed).abs() as u64
}); });

View file

@ -1,7 +1,7 @@
use crate::png::PngImage; use crate::png::PngImage;
#[derive(Debug, Clone)]
/// An iterator over the scan lines of a PNG image /// An iterator over the scan lines of a PNG image
#[derive(Debug, Clone)]
pub struct ScanLines<'a> { pub struct ScanLines<'a> {
iter: ScanLineRanges, iter: ScanLineRanges,
/// A reference to the PNG image being iterated upon /// A reference to the PNG image being iterated upon
@ -167,8 +167,7 @@ impl Iterator for ScanLineRanges {
let bits_per_line = pixels_per_line * u32::from(self.bits_per_pixel); let bits_per_line = pixels_per_line * u32::from(self.bits_per_pixel);
let bytes_per_line = ((bits_per_line + 7) / 8) as usize; let bytes_per_line = ((bits_per_line + 7) / 8) as usize;
let len = bytes_per_line + 1; let len = bytes_per_line + 1;
assert!(self.left >= len); self.left = self.left.checked_sub(len)?;
self.left -= len;
Some((len, current_pass)) Some((len, current_pass))
} }
} }

View file

@ -10,11 +10,7 @@ pub trait ParallelIterator: Iterator + Sized {
where where
OP: Fn(Self::Item, Self::Item) -> Self::Item + Sync, OP: Fn(Self::Item, Self::Item) -> Self::Item + Sync,
{ {
if let Some(a) = self.next() { self.next().map(|a| self.fold(a, op))
Some(self.fold(a, op))
} else {
None
}
} }
} }

View file

@ -185,7 +185,9 @@ pub fn reduced_alpha_channel(png: &PngImage) -> Option<PngImage> {
let channels = png.channels_per_pixel(); let channels = png.channels_per_pixel();
let bpp = channels * byte_depth; let bpp = channels * byte_depth;
let bpp_mask = bpp - 1; let bpp_mask = bpp - 1;
assert_eq!(0, bpp & bpp_mask); if 0 != bpp & bpp_mask {
return None;
}
let colored_bytes = bpp - byte_depth; let colored_bytes = bpp - byte_depth;
for line in png.scan_lines() { for line in png.scan_lines() {
for (i, &byte) in line.data.iter().enumerate() { for (i, &byte) in line.data.iter().enumerate() {

View file

@ -74,8 +74,7 @@ pub fn reduce_bit_depth(png: &PngImage, minimum_bits: usize) -> Option<PngImage>
#[must_use] #[must_use]
pub fn reduce_bit_depth_8_or_less(png: &PngImage, mut minimum_bits: usize) -> Option<PngImage> { pub fn reduce_bit_depth_8_or_less(png: &PngImage, mut minimum_bits: usize) -> Option<PngImage> {
assert!(minimum_bits >= 1 && minimum_bits < 8); assert!((1..8).contains(&minimum_bits));
let mut reduced = BitVec::with_capacity(png.data.len() * 8);
let bit_depth: usize = png.ihdr.bit_depth.as_u8() as usize; let bit_depth: usize = png.ihdr.bit_depth.as_u8() as usize;
if minimum_bits >= bit_depth { if minimum_bits >= bit_depth {
return None; return None;
@ -109,7 +108,7 @@ pub fn reduce_bit_depth_8_or_less(png: &PngImage, mut minimum_bits: usize) -> Op
} }
} }
} else { } else {
let bit_vec = BitVec::from_bytes(&line.data); let bit_vec = BitVec::from_bytes(line.data);
for byte in bit_vec.to_bytes() { for byte in bit_vec.to_bytes() {
while minimum_bits < bit_depth { while minimum_bits < bit_depth {
let permutations: &[u8] = if minimum_bits == 1 { let permutations: &[u8] = if minimum_bits == 1 {
@ -123,17 +122,17 @@ pub fn reduce_bit_depth_8_or_less(png: &PngImage, mut minimum_bits: usize) -> Op
}; };
if permutations.iter().any(|perm| *perm == byte) { if permutations.iter().any(|perm| *perm == byte) {
break; break;
} else {
minimum_bits <<= 1;
} }
minimum_bits <<= 1;
} }
} }
} }
} }
let mut reduced = BitVec::with_capacity(png.data.len() * 8);
for line in png.scan_lines() { for line in png.scan_lines() {
reduced.extend(BitVec::from_bytes(&[line.filter])); reduced.extend(BitVec::from_bytes(&[line.filter]));
let bit_vec = BitVec::from_bytes(&line.data); let bit_vec = BitVec::from_bytes(line.data);
for (i, bit) in bit_vec.iter().enumerate() { for (i, bit) in bit_vec.iter().enumerate() {
let bit_index = bit_depth - (i % bit_depth); let bit_index = bit_depth - (i % bit_depth);
if bit_index <= minimum_bits { if bit_index <= minimum_bits {

View file

@ -12,7 +12,9 @@ pub fn reduce_rgba_to_grayscale_alpha(png: &PngImage) -> Option<PngImage> {
let byte_depth = png.ihdr.bit_depth.as_u8() >> 3; let byte_depth = png.ihdr.bit_depth.as_u8() >> 3;
let bpp = 4 * byte_depth; let bpp = 4 * byte_depth;
let bpp_mask = bpp - 1; let bpp_mask = bpp - 1;
assert_eq!(0, bpp & bpp_mask); if 0 != bpp & bpp_mask {
return None;
}
let colored_bytes = bpp - byte_depth; let colored_bytes = bpp - byte_depth;
for line in png.scan_lines() { for line in png.scan_lines() {
reduced.push(line.filter); reduced.push(line.filter);
@ -109,6 +111,7 @@ pub fn reduced_color_to_palette(png: &PngImage) -> Option<PngImage> {
let transparency_pixel = png let transparency_pixel = png
.transparency_pixel .transparency_pixel
.as_ref() .as_ref()
.filter(|t| png.ihdr.color_type == ColorType::RGB && t.len() >= 6)
.map(|t| RGB8::new(t[1], t[3], t[5])); .map(|t| RGB8::new(t[1], t[3], t[5]));
for line in png.scan_lines() { for line in png.scan_lines() {
raw_data.push(line.filter); raw_data.push(line.filter);
@ -147,7 +150,7 @@ pub fn reduced_color_to_palette(png: &PngImage) -> Option<PngImage> {
} }
}) })
.max(); .max();
let trns_size = num_transparent.map(|n| n + 8).unwrap_or(0); let trns_size = num_transparent.map_or(0, |n| n + 8);
let headers_size = palette.len() * 3 + 8 + trns_size; let headers_size = palette.len() * 3 + 8 + trns_size;
if raw_data.len() + headers_size > png.data.len() { if raw_data.len() + headers_size > png.data.len() {
@ -157,7 +160,10 @@ pub fn reduced_color_to_palette(png: &PngImage) -> Option<PngImage> {
let mut aux_headers = png.aux_headers.clone(); let mut aux_headers = png.aux_headers.clone();
if let Some(bkgd_header) = png.aux_headers.get(b"bKGD") { if let Some(bkgd_header) = png.aux_headers.get(b"bKGD") {
assert_eq!(bkgd_header.len(), 6); if bkgd_header.len() != 6 {
// malformed chunk?
return None;
}
// In bKGD 16-bit values are used even for 8-bit images // In bKGD 16-bit values are used even for 8-bit images
let bg = RGBA8::new(bkgd_header[1], bkgd_header[3], bkgd_header[5], 255); let bg = RGBA8::new(bkgd_header[1], bkgd_header[3], bkgd_header[5], 255);
let entry = if let Some(&entry) = palette.get(&bg) { let entry = if let Some(&entry) = palette.get(&bg) {

View file

@ -6,9 +6,9 @@ use rgb::RGBA8;
use std::borrow::Cow; use std::borrow::Cow;
pub mod alpha; pub mod alpha;
use crate::alpha::*; use crate::alpha::reduced_alpha_channel;
pub mod bit_depth; pub mod bit_depth;
use crate::bit_depth::*; use crate::bit_depth::reduce_bit_depth_8_or_less;
pub mod color; pub mod color;
use crate::color::*; use crate::color::*;
@ -17,7 +17,6 @@ pub(crate) use crate::bit_depth::reduce_bit_depth;
/// Attempt to reduce the number of colors in the palette /// Attempt to reduce the number of colors in the palette
/// Returns `None` if palette hasn't changed /// Returns `None` if palette hasn't changed
#[must_use]
pub fn reduced_palette(png: &PngImage) -> Option<PngImage> { pub fn reduced_palette(png: &PngImage) -> Option<PngImage> {
if png.ihdr.color_type != ColorType::Indexed { if png.ihdr.color_type != ColorType::Indexed {
// Can't reduce if there is no palette // Can't reduce if there is no palette
@ -70,14 +69,14 @@ pub fn reduced_palette(png: &PngImage) -> Option<PngImage> {
.unwrap_or_else(|| RGBA8::new(0, 0, 0, 255)); .unwrap_or_else(|| RGBA8::new(0, 0, 0, 255));
((color.a as i32) << 18) ((color.a as i32) << 18)
// These are coefficients for standard sRGB to luma conversion // These are coefficients for standard sRGB to luma conversion
- (color.r as i32) * 299 - i32::from(color.r) * 299
- (color.g as i32) * 587 - i32::from(color.g) * 587
- (color.b as i32) * 114 - i32::from(color.b) * 114
}; };
color_val(a.0).cmp(&color_val(b.0)) color_val(a.0).cmp(&color_val(b.0))
}); });
let mut next_index = 0u16; let mut next_index = 0_u16;
let mut seen = IndexMap::with_capacity(palette.len()); let mut seen = IndexMap::with_capacity(palette.len());
for (i, used) in used_enumerated.iter().cloned() { for (i, used) in used_enumerated.iter().cloned() {
if !used { if !used {
@ -138,33 +137,33 @@ fn do_palette_reduction(png: &PngImage, palette_map: &[Option<u8>; 256]) -> Opti
} }
fn palette_map_to_byte_map(png: &PngImage, palette_map: &[Option<u8>; 256]) -> Option<[u8; 256]> { fn palette_map_to_byte_map(png: &PngImage, palette_map: &[Option<u8>; 256]) -> Option<[u8; 256]> {
let len = png.palette.as_ref().map(|p| p.len()).unwrap_or(0); let len = png.palette.as_ref().map_or(0, |p| p.len());
if (0..len).all(|i| palette_map[i].map_or(true, |to| to == i as u8)) { if (0..len).all(|i| palette_map[i].map_or(true, |to| to == i as u8)) {
// No reduction necessary // No reduction necessary
return None; return None;
} }
let mut byte_map = [0u8; 256]; let mut byte_map = [0_u8; 256];
// low bit-depths can be pre-computed for every byte value // low bit-depths can be pre-computed for every byte value
match png.ihdr.bit_depth { match png.ihdr.bit_depth {
BitDepth::Eight => { BitDepth::Eight => {
for byte in 0..=255 { for byte in 0..=255usize {
byte_map[byte as usize] = palette_map[byte as usize].unwrap_or(0) byte_map[byte] = palette_map[byte].unwrap_or(0)
} }
} }
BitDepth::Four => { BitDepth::Four => {
for byte in 0..=255 { for byte in 0..=255usize {
byte_map[byte as usize] = palette_map[(byte & 0x0F) as usize].unwrap_or(0) byte_map[byte] = palette_map[(byte & 0x0F)].unwrap_or(0)
| (palette_map[(byte >> 4) as usize].unwrap_or(0) << 4); | (palette_map[(byte >> 4)].unwrap_or(0) << 4);
} }
} }
BitDepth::Two => { BitDepth::Two => {
for byte in 0..=255 { for byte in 0..=255usize {
byte_map[byte as usize] = palette_map[(byte & 0x03) as usize].unwrap_or(0) byte_map[byte] = palette_map[(byte & 0x03)].unwrap_or(0)
| (palette_map[((byte >> 2) & 0x03) as usize].unwrap_or(0) << 2) | (palette_map[((byte >> 2) & 0x03)].unwrap_or(0) << 2)
| (palette_map[((byte >> 4) & 0x03) as usize].unwrap_or(0) << 4) | (palette_map[((byte >> 4) & 0x03)].unwrap_or(0) << 4)
| (palette_map[(byte >> 6) as usize].unwrap_or(0) << 6); | (palette_map[(byte >> 6)].unwrap_or(0) << 6);
} }
} }
_ => {} _ => {}
@ -174,12 +173,7 @@ fn palette_map_to_byte_map(png: &PngImage, palette_map: &[Option<u8>; 256]) -> O
} }
fn reordered_palette(palette: &[RGBA8], palette_map: &[Option<u8>; 256]) -> Vec<RGBA8> { fn reordered_palette(palette: &[RGBA8], palette_map: &[Option<u8>; 256]) -> Vec<RGBA8> {
let max_index = palette_map let max_index = palette_map.iter().cloned().flatten().max().unwrap_or(0) as usize;
.iter()
.cloned()
.filter_map(|x| x)
.max()
.unwrap_or(0) as usize;
let mut new_palette = vec![RGBA8::new(0, 0, 0, 255); max_index + 1]; let mut new_palette = vec![RGBA8::new(0, 0, 0, 255); max_index + 1];
for (&color, &map_to) in palette.iter().zip(palette_map.iter()) { for (&color, &map_to) in palette.iter().zip(palette_map.iter()) {
if let Some(map_to) = map_to { if let Some(map_to) = map_to {
@ -191,15 +185,19 @@ fn reordered_palette(palette: &[RGBA8], palette_map: &[Option<u8>; 256]) -> Vec<
/// Attempt to reduce the color type of the image /// Attempt to reduce the color type of the image
/// Returns true if the color type was reduced, false otherwise /// Returns true if the color type was reduced, false otherwise
pub fn reduce_color_type(png: &PngImage) -> Option<PngImage> { pub fn reduce_color_type(png: &PngImage, grayscale_reduction: bool) -> Option<PngImage> {
let mut should_reduce_bit_depth = false; let mut should_reduce_bit_depth = false;
let mut reduced = Cow::Borrowed(png); let mut reduced = Cow::Borrowed(png);
// Go down one step at a time // Go down one step at a time
// Maybe not the most efficient, but it's safe // Maybe not the most efficient, but it's safe
if reduced.ihdr.color_type == ColorType::RGBA { if reduced.ihdr.color_type == ColorType::RGBA {
if let Some(r) = if let Some(r) = if grayscale_reduction {
reduce_rgba_to_grayscale_alpha(&reduced).or_else(|| reduced_alpha_channel(&reduced)) reduce_rgba_to_grayscale_alpha(&reduced)
} else {
None
}
.or_else(|| reduced_alpha_channel(&reduced))
{ {
reduced = Cow::Owned(r); reduced = Cow::Owned(r);
} else if let Some(r) = reduced_color_to_palette(&reduced) { } else if let Some(r) = reduced_color_to_palette(&reduced) {
@ -216,8 +214,12 @@ pub fn reduce_color_type(png: &PngImage) -> Option<PngImage> {
} }
if reduced.ihdr.color_type == ColorType::RGB { if reduced.ihdr.color_type == ColorType::RGB {
if let Some(r) = if let Some(r) = if grayscale_reduction {
reduce_rgb_to_grayscale(&reduced).or_else(|| reduced_color_to_palette(&reduced)) reduce_rgb_to_grayscale(&reduced)
} else {
None
}
.or_else(|| reduced_color_to_palette(&reduced))
{ {
reduced = Cow::Owned(r); reduced = Cow::Owned(r);
should_reduce_bit_depth = true; should_reduce_bit_depth = true;

BIN
tests/files/issue-182.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 B

View file

@ -6,14 +6,16 @@ use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
fn get_opts(input: &Path) -> (OutFile, oxipng::Options) { fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
let mut options = oxipng::Options::default(); let mut options = oxipng::Options {
options.force = true; force: true,
..Default::default()
};
let mut filter = IndexSet::new(); let mut filter = IndexSet::new();
filter.insert(0); filter.insert(0);
options.filter = filter; options.filter = filter;
( (
OutFile::Path(Some(input.with_extension("out.png").to_owned())), OutFile::Path(Some(input.with_extension("out.png"))),
options, options,
) )
} }
@ -55,7 +57,7 @@ fn test_it_converts(
if let Some(palette) = png.raw.palette.as_ref() { if let Some(palette) = png.raw.palette.as_ref() {
assert!(palette.len() <= 1 << (png.raw.ihdr.bit_depth.as_u8() as usize)); assert!(palette.len() <= 1 << (png.raw.ihdr.bit_depth.as_u8() as usize));
} else { } else {
assert!(png.raw.ihdr.color_type != ColorType::Indexed); assert_ne!(png.raw.ihdr.color_type, ColorType::Indexed);
} }
remove_file(output).ok(); remove_file(output).ok();

View file

@ -10,14 +10,16 @@ use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
fn get_opts(input: &Path) -> (OutFile, oxipng::Options) { fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
let mut options = oxipng::Options::default(); let mut options = oxipng::Options {
options.force = true; force: true,
..Default::default()
};
let mut filter = IndexSet::new(); let mut filter = IndexSet::new();
filter.insert(0); filter.insert(0);
options.filter = filter; options.filter = filter;
( (
OutFile::Path(Some(input.with_extension("out.png").to_owned())), OutFile::Path(Some(input.with_extension("out.png"))),
options, options,
) )
} }
@ -200,7 +202,7 @@ fn strip_headers_list() {
let output = output.path().unwrap(); let output = output.path().unwrap();
assert!(output.exists()); assert!(output.exists());
let png = match PngData::new(&output, opts.fix_errors) { let png = match PngData::new(output, opts.fix_errors) {
Ok(x) => x, Ok(x) => x,
Err(x) => { Err(x) => {
remove_file(output).ok(); remove_file(output).ok();
@ -234,7 +236,7 @@ fn strip_headers_safe() {
let output = output.path().unwrap(); let output = output.path().unwrap();
assert!(output.exists()); assert!(output.exists());
let png = match PngData::new(&output, opts.fix_errors) { let png = match PngData::new(output, opts.fix_errors) {
Ok(x) => x, Ok(x) => x,
Err(x) => { Err(x) => {
remove_file(output).ok(); remove_file(output).ok();
@ -268,7 +270,7 @@ fn strip_headers_all() {
let output = output.path().unwrap(); let output = output.path().unwrap();
assert!(output.exists()); assert!(output.exists());
let png = match PngData::new(&output, opts.fix_errors) { let png = match PngData::new(output, opts.fix_errors) {
Ok(x) => x, Ok(x) => x,
Err(x) => { Err(x) => {
remove_file(output).ok(); remove_file(output).ok();
@ -302,7 +304,7 @@ fn strip_headers_none() {
let output = output.path().unwrap(); let output = output.path().unwrap();
assert!(output.exists()); assert!(output.exists());
let png = match PngData::new(&output, opts.fix_errors) { let png = match PngData::new(output, opts.fix_errors) {
Ok(x) => x, Ok(x) => x,
Err(x) => { Err(x) => {
remove_file(output).ok(); remove_file(output).ok();
@ -334,7 +336,7 @@ fn interlacing_0_to_1() {
let output = output.path().unwrap(); let output = output.path().unwrap();
assert!(output.exists()); assert!(output.exists());
let png = match PngData::new(&output, opts.fix_errors) { let png = match PngData::new(output, opts.fix_errors) {
Ok(x) => x, Ok(x) => x,
Err(x) => { Err(x) => {
remove_file(output).ok(); remove_file(output).ok();
@ -364,7 +366,7 @@ fn interlacing_1_to_0() {
let output = output.path().unwrap(); let output = output.path().unwrap();
assert!(output.exists()); assert!(output.exists());
let png = match PngData::new(&output, opts.fix_errors) { let png = match PngData::new(output, opts.fix_errors) {
Ok(x) => x, Ok(x) => x,
Err(x) => { Err(x) => {
remove_file(output).ok(); remove_file(output).ok();
@ -396,7 +398,7 @@ fn interlacing_0_to_1_small_files() {
let output = output.path().unwrap(); let output = output.path().unwrap();
assert!(output.exists()); assert!(output.exists());
let png = match PngData::new(&output, opts.fix_errors) { let png = match PngData::new(output, opts.fix_errors) {
Ok(x) => x, Ok(x) => x,
Err(x) => { Err(x) => {
remove_file(output).ok(); remove_file(output).ok();
@ -430,7 +432,7 @@ fn interlacing_1_to_0_small_files() {
let output = output.path().unwrap(); let output = output.path().unwrap();
assert!(output.exists()); assert!(output.exists());
let png = match PngData::new(&output, opts.fix_errors) { let png = match PngData::new(output, opts.fix_errors) {
Ok(x) => x, Ok(x) => x,
Err(x) => { Err(x) => {
remove_file(output).ok(); remove_file(output).ok();
@ -465,7 +467,7 @@ fn interlaced_0_to_1_other_filter_mode() {
let output = output.path().unwrap(); let output = output.path().unwrap();
assert!(output.exists()); assert!(output.exists());
let png = match PngData::new(&output, opts.fix_errors) { let png = match PngData::new(output, opts.fix_errors) {
Ok(x) => x, Ok(x) => x,
Err(x) => { Err(x) => {
remove_file(output).ok(); remove_file(output).ok();
@ -560,7 +562,7 @@ fn fix_errors() {
let output = output.path().unwrap(); let output = output.path().unwrap();
assert!(output.exists()); assert!(output.exists());
let png = match PngData::new(&output, false) { let png = match PngData::new(output, false) {
Ok(x) => x, Ok(x) => x,
Err(x) => { Err(x) => {
remove_file(output).ok(); remove_file(output).ok();
@ -576,6 +578,7 @@ fn fix_errors() {
} }
#[test] #[test]
#[cfg(feature = "zopfli")]
fn zopfli_mode() { fn zopfli_mode() {
let input = PathBuf::from("tests/files/zopfli_mode.png"); let input = PathBuf::from("tests/files/zopfli_mode.png");
let (output, mut opts) = get_opts(&input); let (output, mut opts) = get_opts(&input);
@ -593,6 +596,7 @@ fn zopfli_mode() {
} }
#[test] #[test]
#[cfg(feature = "libdeflater")]
fn libdeflater_mode() { fn libdeflater_mode() {
let input = PathBuf::from("tests/files/zopfli_mode.png"); let input = PathBuf::from("tests/files/zopfli_mode.png");
let (output, mut opts) = get_opts(&input); let (output, mut opts) = get_opts(&input);

View file

@ -6,14 +6,16 @@ use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
fn get_opts(input: &Path) -> (OutFile, oxipng::Options) { fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
let mut options = oxipng::Options::default(); let mut options = oxipng::Options {
options.force = true; force: true,
..Default::default()
};
let mut filter = IndexSet::new(); let mut filter = IndexSet::new();
filter.insert(0); filter.insert(0);
options.filter = filter; options.filter = filter;
( (
OutFile::Path(Some(input.with_extension("out.png").to_owned())), OutFile::Path(Some(input.with_extension("out.png"))),
options, options,
) )
} }

View file

@ -1,5 +1,3 @@
use oxipng;
use oxipng::Headers; use oxipng::Headers;
use oxipng::OutFile; use oxipng::OutFile;
use std::default::Default; use std::default::Default;

View file

@ -6,14 +6,16 @@ use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
fn get_opts(input: &Path) -> (OutFile, oxipng::Options) { fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
let mut options = oxipng::Options::default(); let mut options = oxipng::Options {
options.force = true; force: true,
..Default::default()
};
let mut filter = IndexSet::new(); let mut filter = IndexSet::new();
filter.insert(0); filter.insert(0);
options.filter = filter; options.filter = filter;
( (
OutFile::Path(Some(input.with_extension("out.png").to_owned())), OutFile::Path(Some(input.with_extension("out.png"))),
options, options,
) )
} }
@ -711,7 +713,7 @@ fn small_files() {
let output = output.path().unwrap(); let output = output.path().unwrap();
assert!(output.exists()); assert!(output.exists());
let png = match PngData::new(&output, opts.fix_errors) { let png = match PngData::new(output, opts.fix_errors) {
Ok(x) => x, Ok(x) => x,
Err(x) => { Err(x) => {
remove_file(&output).ok(); remove_file(&output).ok();
@ -743,7 +745,7 @@ fn palette_should_be_reduced_with_dupes() {
let output = output.path().unwrap(); let output = output.path().unwrap();
assert!(output.exists()); assert!(output.exists());
let png = match PngData::new(&output, opts.fix_errors) { let png = match PngData::new(output, opts.fix_errors) {
Ok(x) => x, Ok(x) => x,
Err(x) => { Err(x) => {
remove_file(&output).ok(); remove_file(&output).ok();
@ -776,7 +778,7 @@ fn palette_should_be_reduced_with_unused() {
let output = output.path().unwrap(); let output = output.path().unwrap();
assert!(output.exists()); assert!(output.exists());
let png = match PngData::new(&output, opts.fix_errors) { let png = match PngData::new(output, opts.fix_errors) {
Ok(x) => x, Ok(x) => x,
Err(x) => { Err(x) => {
remove_file(&output).ok(); remove_file(&output).ok();
@ -809,7 +811,7 @@ fn palette_should_be_reduced_with_both() {
let output = output.path().unwrap(); let output = output.path().unwrap();
assert!(output.exists()); assert!(output.exists());
let png = match PngData::new(&output, opts.fix_errors) { let png = match PngData::new(output, opts.fix_errors) {
Ok(x) => x, Ok(x) => x,
Err(x) => { Err(x) => {
remove_file(&output).ok(); remove_file(&output).ok();

View file

@ -6,14 +6,16 @@ use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
fn get_opts(input: &Path) -> (OutFile, oxipng::Options) { fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
let mut options = oxipng::Options::default(); let mut options = oxipng::Options {
options.force = true; force: true,
..Default::default()
};
let mut filter = IndexSet::new(); let mut filter = IndexSet::new();
filter.insert(0); filter.insert(0);
options.filter = filter; options.filter = filter;
( (
OutFile::Path(Some(input.with_extension("out.png").to_owned())), OutFile::Path(Some(input.with_extension("out.png"))),
options, options,
) )
} }
@ -62,7 +64,7 @@ fn test_it_converts(
if let Some(palette) = png.raw.palette.as_ref() { if let Some(palette) = png.raw.palette.as_ref() {
assert!(palette.len() <= 1 << (png.raw.ihdr.bit_depth.as_u8() as usize)); assert!(palette.len() <= 1 << (png.raw.ihdr.bit_depth.as_u8() as usize));
} else { } else {
assert!(png.raw.ihdr.color_type != ColorType::Indexed); assert_ne!(png.raw.ihdr.color_type, ColorType::Indexed);
} }
remove_file(output).ok(); remove_file(output).ok();
@ -99,7 +101,7 @@ fn issue_42() {
let output = output.path().unwrap(); let output = output.path().unwrap();
assert!(output.exists()); assert!(output.exists());
let png = match PngData::new(&output, opts.fix_errors) { let png = match PngData::new(output, opts.fix_errors) {
Ok(x) => x, Ok(x) => x,
Err(x) => { Err(x) => {
remove_file(output).ok(); remove_file(output).ok();
@ -287,12 +289,10 @@ fn issue_92_filter_5() {
let input = "tests/files/issue-92.png"; let input = "tests/files/issue-92.png";
let (_, mut opts) = get_opts(Path::new(input)); let (_, mut opts) = get_opts(Path::new(input));
opts.filter = [5].iter().cloned().collect(); opts.filter = [5].iter().cloned().collect();
let output = OutFile::Path(Some( let output = OutFile::Path(Some(Path::new(input).with_extension("-f5-out.png")));
Path::new(input).with_extension("-f5-out.png").to_owned(),
));
test_it_converts( test_it_converts(
&input, input,
Some((output, opts)), Some((output, opts)),
ColorType::Grayscale, ColorType::Grayscale,
BitDepth::Eight, BitDepth::Eight,
@ -308,9 +308,7 @@ fn issue_113_white() {
opts.interlace = Some(1); opts.interlace = Some(1);
opts.alphas = IndexSet::new(); opts.alphas = IndexSet::new();
opts.alphas.insert(AlphaOptim::Black); opts.alphas.insert(AlphaOptim::Black);
let output = OutFile::Path(Some( let output = OutFile::Path(Some(Path::new(input).with_extension("-white-out.png")));
Path::new(input).with_extension("-white-out.png").to_owned(),
));
test_it_converts( test_it_converts(
input, input,
Some((output, opts)), Some((output, opts)),
@ -328,9 +326,7 @@ fn issue_113_black() {
opts.interlace = Some(1); opts.interlace = Some(1);
opts.alphas = IndexSet::new(); opts.alphas = IndexSet::new();
opts.alphas.insert(AlphaOptim::Black); opts.alphas.insert(AlphaOptim::Black);
let output = OutFile::Path(Some( let output = OutFile::Path(Some(Path::new(input).with_extension("-black-out.png")));
Path::new(input).with_extension("-black-out.png").to_owned(),
));
test_it_converts( test_it_converts(
input, input,
Some((output, opts)), Some((output, opts)),
@ -348,9 +344,7 @@ fn issue_113_right() {
opts.interlace = Some(1); opts.interlace = Some(1);
opts.alphas = IndexSet::new(); opts.alphas = IndexSet::new();
opts.alphas.insert(AlphaOptim::Right); opts.alphas.insert(AlphaOptim::Right);
let output = OutFile::Path(Some( let output = OutFile::Path(Some(Path::new(input).with_extension("-right-out.png")));
Path::new(input).with_extension("-right-out.png").to_owned(),
));
test_it_converts( test_it_converts(
input, input,
Some((output, opts)), Some((output, opts)),
@ -368,9 +362,7 @@ fn issue_113_left() {
opts.interlace = Some(1); opts.interlace = Some(1);
opts.alphas = IndexSet::new(); opts.alphas = IndexSet::new();
opts.alphas.insert(AlphaOptim::Left); opts.alphas.insert(AlphaOptim::Left);
let output = OutFile::Path(Some( let output = OutFile::Path(Some(Path::new(input).with_extension("-left-out.png")));
Path::new(input).with_extension("-left-out.png").to_owned(),
));
test_it_converts( test_it_converts(
input, input,
Some((output, opts)), Some((output, opts)),
@ -388,9 +380,7 @@ fn issue_113_up() {
opts.interlace = Some(1); opts.interlace = Some(1);
opts.alphas = IndexSet::new(); opts.alphas = IndexSet::new();
opts.alphas.insert(AlphaOptim::Up); opts.alphas.insert(AlphaOptim::Up);
let output = OutFile::Path(Some( let output = OutFile::Path(Some(Path::new(input).with_extension("-up-out.png")));
Path::new(input).with_extension("-up-out.png").to_owned(),
));
test_it_converts( test_it_converts(
input, input,
Some((output, opts)), Some((output, opts)),
@ -408,9 +398,7 @@ fn issue_113_down() {
opts.interlace = Some(1); opts.interlace = Some(1);
opts.alphas = IndexSet::new(); opts.alphas = IndexSet::new();
opts.alphas.insert(AlphaOptim::Down); opts.alphas.insert(AlphaOptim::Down);
let output = OutFile::Path(Some( let output = OutFile::Path(Some(Path::new(input).with_extension("-down-out.png")));
Path::new(input).with_extension("-down-out.png").to_owned(),
));
test_it_converts( test_it_converts(
input, input,
Some((output, opts)), Some((output, opts)),
@ -440,9 +428,7 @@ fn issue_133_black() {
let (_, mut opts) = get_opts(Path::new(input)); let (_, mut opts) = get_opts(Path::new(input));
opts.alphas = IndexSet::new(); opts.alphas = IndexSet::new();
opts.alphas.insert(AlphaOptim::Black); opts.alphas.insert(AlphaOptim::Black);
let output = OutFile::Path(Some( let output = OutFile::Path(Some(Path::new(input).with_extension("-black-out.png")));
Path::new(input).with_extension("-black-out.png").to_owned(),
));
test_it_converts( test_it_converts(
input, input,
Some((output, opts)), Some((output, opts)),
@ -459,9 +445,7 @@ fn issue_133_white() {
let (_, mut opts) = get_opts(Path::new(input)); let (_, mut opts) = get_opts(Path::new(input));
opts.alphas = IndexSet::new(); opts.alphas = IndexSet::new();
opts.alphas.insert(AlphaOptim::White); opts.alphas.insert(AlphaOptim::White);
let output = OutFile::Path(Some( let output = OutFile::Path(Some(Path::new(input).with_extension("-white-out.png")));
Path::new(input).with_extension("-white-out.png").to_owned(),
));
test_it_converts( test_it_converts(
input, input,
Some((output, opts)), Some((output, opts)),
@ -478,9 +462,7 @@ fn issue_133_up() {
let (_, mut opts) = get_opts(Path::new(input)); let (_, mut opts) = get_opts(Path::new(input));
opts.alphas = IndexSet::new(); opts.alphas = IndexSet::new();
opts.alphas.insert(AlphaOptim::Up); opts.alphas.insert(AlphaOptim::Up);
let output = OutFile::Path(Some( let output = OutFile::Path(Some(Path::new(input).with_extension("-up-out.png")));
Path::new(input).with_extension("-up-out.png").to_owned(),
));
test_it_converts( test_it_converts(
input, input,
Some((output, opts)), Some((output, opts)),
@ -497,9 +479,7 @@ fn issue_133_down() {
let (_, mut opts) = get_opts(Path::new(input)); let (_, mut opts) = get_opts(Path::new(input));
opts.alphas = IndexSet::new(); opts.alphas = IndexSet::new();
opts.alphas.insert(AlphaOptim::Down); opts.alphas.insert(AlphaOptim::Down);
let output = OutFile::Path(Some( let output = OutFile::Path(Some(Path::new(input).with_extension("-down-out.png")));
Path::new(input).with_extension("-down-out.png").to_owned(),
));
test_it_converts( test_it_converts(
input, input,
Some((output, opts)), Some((output, opts)),
@ -516,9 +496,7 @@ fn issue_133_right() {
let (_, mut opts) = get_opts(Path::new(input)); let (_, mut opts) = get_opts(Path::new(input));
opts.alphas = IndexSet::new(); opts.alphas = IndexSet::new();
opts.alphas.insert(AlphaOptim::Right); opts.alphas.insert(AlphaOptim::Right);
let output = OutFile::Path(Some( let output = OutFile::Path(Some(Path::new(input).with_extension("-right-out.png")));
Path::new(input).with_extension("-right-out.png").to_owned(),
));
test_it_converts( test_it_converts(
input, input,
Some((output, opts)), Some((output, opts)),
@ -535,9 +513,7 @@ fn issue_133_left() {
let (_, mut opts) = get_opts(Path::new(input)); let (_, mut opts) = get_opts(Path::new(input));
opts.alphas = IndexSet::new(); opts.alphas = IndexSet::new();
opts.alphas.insert(AlphaOptim::Left); opts.alphas.insert(AlphaOptim::Left);
let output = OutFile::Path(Some( let output = OutFile::Path(Some(Path::new(input).with_extension("-left-out.png")));
Path::new(input).with_extension("-left-out.png").to_owned(),
));
test_it_converts( test_it_converts(
input, input,
Some((output, opts)), Some((output, opts)),
@ -635,7 +611,7 @@ fn issue_175() {
#[test] #[test]
fn issue_182() { fn issue_182() {
let input = "tests/files/issue-175.png"; let input = "tests/files/issue-182.png";
let (output, mut opts) = get_opts(Path::new(input)); let (output, mut opts) = get_opts(Path::new(input));
opts.interlace = Some(0); opts.interlace = Some(0);