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:
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

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
- Includes another update to libdeflater that improves support for targets
without a C stdlib, like wasm32.

89
Cargo.lock generated
View file

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

View file

@ -16,7 +16,7 @@ license = "MIT"
name = "oxipng"
repository = "https://github.com/shssoichiro/oxipng"
build = "build.rs"
version = "4.0.2"
version = "4.0.3"
[badges]
travis-ci = { repository = "shssoichiro/oxipng", branch = "master" }
@ -30,16 +30,16 @@ required-features = ["binary"]
[dependencies]
bit-vec = "^0.6.3"
byteorder = "^1.0.0"
byteorder = "^1.4.2"
crc = "^1.2.0"
itertools = "^0.9.0"
itertools = "^0.10.0"
zopfli = { version = "^0.4.0", optional = true }
miniz_oxide = "0.4"
rgb = "0.8.25"
indexmap = "1.6.1"
libdeflater = { version = "0.7.1", optional = true }
log = "0.4.11"
stderrlog = { version = "0.5.0", optional = true }
log = "0.4.14"
stderrlog = { version = "0.5.1", optional = true }
crossbeam-channel = "0.5.0"
[dependencies.filetime]
@ -82,7 +82,12 @@ parallel = ["rayon", "indexmap/rayon"]
[lib]
name = "oxipng"
path = "src/lib.rs"
[profile.dev]
opt-level = 2
[profile.release]
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 png = PngData::new(&input, false).unwrap();
b.iter(|| reduce_color_type(&png.raw));
b.iter(|| reduce_color_type(&png.raw, true));
}
#[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 png = PngData::new(&input, false).unwrap();
b.iter(|| reduce_color_type(&png.raw));
b.iter(|| reduce_color_type(&png.raw, true));
}
#[bench]
@ -98,7 +98,7 @@ fn reductions_rgba_to_grayscale_alpha_16(b: &mut Bencher) {
));
let png = PngData::new(&input, false).unwrap();
b.iter(|| reduce_color_type(&png.raw));
b.iter(|| reduce_color_type(&png.raw, true));
}
#[bench]
@ -108,7 +108,7 @@ fn reductions_rgba_to_grayscale_alpha_8(b: &mut Bencher) {
));
let png = PngData::new(&input, false).unwrap();
b.iter(|| reduce_color_type(&png.raw));
b.iter(|| reduce_color_type(&png.raw, true));
}
#[bench]
@ -118,7 +118,7 @@ fn reductions_rgba_to_grayscale_16(b: &mut Bencher) {
));
let png = PngData::new(&input, false).unwrap();
b.iter(|| reduce_color_type(&png.raw));
b.iter(|| reduce_color_type(&png.raw, true));
}
#[bench]
@ -128,7 +128,7 @@ fn reductions_rgba_to_grayscale_8(b: &mut Bencher) {
));
let png = PngData::new(&input, false).unwrap();
b.iter(|| reduce_color_type(&png.raw));
b.iter(|| reduce_color_type(&png.raw, true));
}
#[bench]
@ -138,7 +138,7 @@ fn reductions_rgb_to_grayscale_16(b: &mut Bencher) {
));
let png = PngData::new(&input, false).unwrap();
b.iter(|| reduce_color_type(&png.raw));
b.iter(|| reduce_color_type(&png.raw, true));
}
#[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 png = PngData::new(&input, false).unwrap();
b.iter(|| reduce_color_type(&png.raw));
b.iter(|| reduce_color_type(&png.raw, true));
}
#[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 png = PngData::new(&input, false).unwrap();
b.iter(|| reduce_color_type(&png.raw));
b.iter(|| reduce_color_type(&png.raw, true));
}
#[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 png = PngData::new(&input, false).unwrap();
b.iter(|| reduce_color_type(&png.raw));
b.iter(|| reduce_color_type(&png.raw, true));
}
#[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",
"devDependencies": {
"strip-ansi-cli": "^2.0.0"
"strip-ansi-cli": "^3.0.0"
}
}

View file

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

View file

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

View file

@ -14,15 +14,11 @@ pub enum PngError {
Other(Box<str>),
}
impl Error for PngError {
// deprecated
fn description(&self) -> &str {
""
}
}
impl Error for PngError {}
impl fmt::Display for PngError {
#[inline]
#[cold]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
PngError::DeflatedDataTooLong(_) => f.write_str("deflated data too long"),
@ -40,7 +36,7 @@ impl fmt::Display for PngError {
}
impl PngError {
#[inline]
#[cold]
pub fn new(description: &str) -> PngError {
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>) {
assert!(data.len() >= bpp);
assert!(last_line.is_empty() || data.len() == last_line.len());
buf.reserve(data.len());
match filter {
0 => {
@ -17,6 +21,7 @@ pub fn filter_line(filter: u8, bpp: usize, data: &[u8], last_line: &[u8], buf: &
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())
@ -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>) {
assert_eq!(buf.len(), 0);
pub fn unfilter_line(
filter: u8,
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 filter {
0 => {
buf.extend_from_slice(data);
}
1 => {
for (i, byte) in data.iter().enumerate() {
match i.checked_sub(bpp) {
Some(x) => {
let b = buf[x];
buf.push(byte.wrapping_add(b));
}
None => {
buf.push(*byte);
}
};
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 => {
if last_line.is_empty() {
buf.extend_from_slice(data);
} else {
buf.extend(
data.iter()
.zip(last_line.iter())
.map(|(cur, last)| cur.wrapping_add(*last)),
);
};
buf.extend(
data.iter()
.zip(last_line)
.map(|(&cur, &last)| cur.wrapping_add(last)),
);
}
3 => {
for (i, byte) in data.iter().enumerate() {
if last_line.is_empty() {
match i.checked_sub(bpp) {
Some(x) => {
let b = buf[x];
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));
}
};
};
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),
});
}
}
4 => {
for (i, byte) in data.iter().enumerate() {
if last_line.is_empty() {
match i.checked_sub(bpp) {
Some(x) => {
let b = buf[x];
buf.push(byte.wrapping_add(b));
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))
}
None => {
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]));
}
};
};
_ => cur.wrapping_add(up),
},
);
}
}
_ => unreachable!(),
_ => return Err(PngError::InvalidData),
}
Ok(())
}
fn paeth_predictor(a: u8, b: u8, c: u8) -> u8 {

View file

@ -25,6 +25,45 @@ pub struct IhdrData {
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)]
/// Options to use for performing operations on headers (such as stripping)
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);
Ok(Some(RawHeader { name, data }))
}
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]);
Ok(IhdrData {
color_type: match byte_data[9] {
@ -122,10 +163,14 @@ pub fn parse_ihdr_header(byte_data: &[u8]) -> PngResult<IhdrData> {
16 => BitDepth::Sixteen,
_ => return Err(PngError::new("Unexpected bit depth in header")),
},
width: rdr.read_u32::<BigEndian>().unwrap(),
height: rdr.read_u32::<BigEndian>().unwrap(),
width: rdr
.read_u32::<BigEndian>()
.map_err(|_| PngError::TruncatedData)?,
height: rdr
.read_u32::<BigEndian>()
.map_err(|_| PngError::TruncatedData)?,
compression: byte_data[10],
filter: byte_data[11],
interlaced: byte_data[12],
interlaced,
})
}

View file

@ -5,7 +5,7 @@ use bit_vec::BitVec;
#[must_use]
pub fn interlace_image(png: &PngImage) -> PngImage {
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() {
match index % 8 {
// 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));
}
}
let bit_vec = BitVec::from_bytes(&line.data);
let bit_vec = BitVec::from_bytes(line.data);
for (i, bit) in bit_vec.iter().enumerate() {
// Avoid moving padded 0's into new image
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 {
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;
// Initialize each output line with a starting filter byte of 0
// 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 current_y: usize = pass_constants.y_shift as usize;
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)
+ u32::from(pass_constants.x_step)
- 1)

View file

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

View file

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

View file

@ -100,7 +100,7 @@ impl PngData {
let mut idat_headers: Vec<u8> = Vec::new();
while let Some(header) = parse_next_header(byte_data, &mut byte_offset, fix_errors)? {
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),
_ => {
aux_headers.insert(header.name, header.data.to_owned());
@ -118,6 +118,11 @@ impl PngData {
let ihdr_header = parse_ihdr_header(&ihdr)?;
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(
ihdr_header.color_type,
aux_headers.remove(b"PLTE"),
@ -131,7 +136,7 @@ impl PngData {
transparency_pixel,
aux_headers,
};
raw.data = raw.unfilter_image();
raw.data = raw.unfilter_image()?;
// Return the PngData
Ok(Self {
idat_data: idat_headers,
@ -203,10 +208,10 @@ impl PngData {
.fold(
0,
|prev, (index, px)| {
if px.a != 255 {
index + 1
} else {
if px.a == 255 {
prev
} else {
index + 1
}
},
);
@ -276,32 +281,25 @@ impl PngImage {
}
/// 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 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_pass = 1;
let mut last_pass = None;
let mut unfiltered_buf = Vec::new();
for line in self.scan_lines() {
if let Some(pass) = line.pass {
if pass != last_pass {
last_line.clear();
last_pass = pass;
}
if last_pass != line.pass {
last_line.clear();
last_pass = line.pass;
}
unfilter_line(
line.filter,
bpp,
&line.data,
&last_line,
&mut unfiltered_buf,
);
last_line.resize(line.data.len(), 0);
unfilter_line(line.filter, bpp, line.data, &last_line, &mut unfiltered_buf)?;
unfiltered.push(0);
unfiltered.extend_from_slice(&unfiltered_buf);
std::mem::swap(&mut last_line, &mut unfiltered_buf);
unfiltered_buf.clear();
}
unfiltered
Ok(unfiltered)
}
/// Apply the specified filter type to all rows in the image
@ -319,6 +317,9 @@ impl PngImage {
let mut f_buf = Vec::new();
for line in self.scan_lines() {
f_buf.clear();
if last_pass != line.pass {
last_line = &[];
}
match filter {
0 | 1 | 2 | 3 | 4 => {
let filter = if last_pass == line.pass || filter <= 1 {
@ -327,7 +328,7 @@ impl PngImage {
0
};
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);
}
5 => {
@ -336,12 +337,12 @@ impl PngImage {
// http://www.libpng.org/pub/png/book/chapter09.html
let mut best_filter = 0;
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
for filter in if last_pass == line.pass { 0..5 } else { 0..2 } {
filter_line(filter, bpp, &line.data, last_line, &mut f_buf);
let size = f_buf.iter().fold(0u64, |acc, &x| {
filter_line(filter, bpp, line.data, last_line, &mut f_buf);
let size = f_buf.iter().fold(0_u64, |acc, &x| {
let signed = x as i8;
acc + i16::from(signed).abs() as u64
});

View file

@ -1,7 +1,7 @@
use crate::png::PngImage;
#[derive(Debug, Clone)]
/// An iterator over the scan lines of a PNG image
#[derive(Debug, Clone)]
pub struct ScanLines<'a> {
iter: ScanLineRanges,
/// 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 bytes_per_line = ((bits_per_line + 7) / 8) as usize;
let len = bytes_per_line + 1;
assert!(self.left >= len);
self.left -= len;
self.left = self.left.checked_sub(len)?;
Some((len, current_pass))
}
}

View file

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

View file

@ -185,7 +185,9 @@ pub fn reduced_alpha_channel(png: &PngImage) -> Option<PngImage> {
let channels = png.channels_per_pixel();
let bpp = channels * byte_depth;
let bpp_mask = bpp - 1;
assert_eq!(0, bpp & bpp_mask);
if 0 != bpp & bpp_mask {
return None;
}
let colored_bytes = bpp - byte_depth;
for line in png.scan_lines() {
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]
pub fn reduce_bit_depth_8_or_less(png: &PngImage, mut minimum_bits: usize) -> Option<PngImage> {
assert!(minimum_bits >= 1 && minimum_bits < 8);
let mut reduced = BitVec::with_capacity(png.data.len() * 8);
assert!((1..8).contains(&minimum_bits));
let bit_depth: usize = png.ihdr.bit_depth.as_u8() as usize;
if minimum_bits >= bit_depth {
return None;
@ -109,7 +108,7 @@ pub fn reduce_bit_depth_8_or_less(png: &PngImage, mut minimum_bits: usize) -> Op
}
}
} else {
let bit_vec = BitVec::from_bytes(&line.data);
let bit_vec = BitVec::from_bytes(line.data);
for byte in bit_vec.to_bytes() {
while minimum_bits < bit_depth {
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) {
break;
} else {
minimum_bits <<= 1;
}
minimum_bits <<= 1;
}
}
}
}
let mut reduced = BitVec::with_capacity(png.data.len() * 8);
for line in png.scan_lines() {
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() {
let bit_index = bit_depth - (i % bit_depth);
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 bpp = 4 * byte_depth;
let bpp_mask = bpp - 1;
assert_eq!(0, bpp & bpp_mask);
if 0 != bpp & bpp_mask {
return None;
}
let colored_bytes = bpp - byte_depth;
for line in png.scan_lines() {
reduced.push(line.filter);
@ -109,6 +111,7 @@ pub fn reduced_color_to_palette(png: &PngImage) -> Option<PngImage> {
let transparency_pixel = png
.transparency_pixel
.as_ref()
.filter(|t| png.ihdr.color_type == ColorType::RGB && t.len() >= 6)
.map(|t| RGB8::new(t[1], t[3], t[5]));
for line in png.scan_lines() {
raw_data.push(line.filter);
@ -147,7 +150,7 @@ pub fn reduced_color_to_palette(png: &PngImage) -> Option<PngImage> {
}
})
.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;
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();
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
let bg = RGBA8::new(bkgd_header[1], bkgd_header[3], bkgd_header[5], 255);
let entry = if let Some(&entry) = palette.get(&bg) {

View file

@ -6,9 +6,9 @@ use rgb::RGBA8;
use std::borrow::Cow;
pub mod alpha;
use crate::alpha::*;
use crate::alpha::reduced_alpha_channel;
pub mod bit_depth;
use crate::bit_depth::*;
use crate::bit_depth::reduce_bit_depth_8_or_less;
pub mod 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
/// Returns `None` if palette hasn't changed
#[must_use]
pub fn reduced_palette(png: &PngImage) -> Option<PngImage> {
if png.ihdr.color_type != ColorType::Indexed {
// 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));
((color.a as i32) << 18)
// These are coefficients for standard sRGB to luma conversion
- (color.r as i32) * 299
- (color.g as i32) * 587
- (color.b as i32) * 114
- i32::from(color.r) * 299
- i32::from(color.g) * 587
- i32::from(color.b) * 114
};
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());
for (i, used) in used_enumerated.iter().cloned() {
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]> {
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)) {
// No reduction necessary
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
match png.ihdr.bit_depth {
BitDepth::Eight => {
for byte in 0..=255 {
byte_map[byte as usize] = palette_map[byte as usize].unwrap_or(0)
for byte in 0..=255usize {
byte_map[byte] = palette_map[byte].unwrap_or(0)
}
}
BitDepth::Four => {
for byte in 0..=255 {
byte_map[byte as usize] = palette_map[(byte & 0x0F) as usize].unwrap_or(0)
| (palette_map[(byte >> 4) as usize].unwrap_or(0) << 4);
for byte in 0..=255usize {
byte_map[byte] = palette_map[(byte & 0x0F)].unwrap_or(0)
| (palette_map[(byte >> 4)].unwrap_or(0) << 4);
}
}
BitDepth::Two => {
for byte in 0..=255 {
byte_map[byte as usize] = palette_map[(byte & 0x03) as usize].unwrap_or(0)
| (palette_map[((byte >> 2) & 0x03) as usize].unwrap_or(0) << 2)
| (palette_map[((byte >> 4) & 0x03) as usize].unwrap_or(0) << 4)
| (palette_map[(byte >> 6) as usize].unwrap_or(0) << 6);
for byte in 0..=255usize {
byte_map[byte] = palette_map[(byte & 0x03)].unwrap_or(0)
| (palette_map[((byte >> 2) & 0x03)].unwrap_or(0) << 2)
| (palette_map[((byte >> 4) & 0x03)].unwrap_or(0) << 4)
| (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> {
let max_index = palette_map
.iter()
.cloned()
.filter_map(|x| x)
.max()
.unwrap_or(0) as usize;
let max_index = palette_map.iter().cloned().flatten().max().unwrap_or(0) as usize;
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()) {
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
/// 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 reduced = Cow::Borrowed(png);
// Go down one step at a time
// Maybe not the most efficient, but it's safe
if reduced.ihdr.color_type == ColorType::RGBA {
if let Some(r) =
reduce_rgba_to_grayscale_alpha(&reduced).or_else(|| reduced_alpha_channel(&reduced))
if let Some(r) = if grayscale_reduction {
reduce_rgba_to_grayscale_alpha(&reduced)
} else {
None
}
.or_else(|| reduced_alpha_channel(&reduced))
{
reduced = Cow::Owned(r);
} 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 let Some(r) =
reduce_rgb_to_grayscale(&reduced).or_else(|| reduced_color_to_palette(&reduced))
if let Some(r) = if grayscale_reduction {
reduce_rgb_to_grayscale(&reduced)
} else {
None
}
.or_else(|| reduced_color_to_palette(&reduced))
{
reduced = Cow::Owned(r);
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;
fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
let mut options = oxipng::Options::default();
options.force = true;
let mut options = oxipng::Options {
force: true,
..Default::default()
};
let mut filter = IndexSet::new();
filter.insert(0);
options.filter = filter;
(
OutFile::Path(Some(input.with_extension("out.png").to_owned())),
OutFile::Path(Some(input.with_extension("out.png"))),
options,
)
}
@ -55,7 +57,7 @@ fn test_it_converts(
if let Some(palette) = png.raw.palette.as_ref() {
assert!(palette.len() <= 1 << (png.raw.ihdr.bit_depth.as_u8() as usize));
} else {
assert!(png.raw.ihdr.color_type != ColorType::Indexed);
assert_ne!(png.raw.ihdr.color_type, ColorType::Indexed);
}
remove_file(output).ok();

View file

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

View file

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

View file

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

View file

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

View file

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