Merge branch 'master' into miniz-deadline

This commit is contained in:
Josh Holmer 2020-03-31 21:54:37 -04:00 committed by GitHub
commit 6850d6b015
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 252 additions and 134 deletions

View file

@ -11,7 +11,7 @@ matrix:
env: TARGET=x86_64-apple-darwin
cache: cargo
- os: linux
rust: 1.36.0
rust: 1.41.0
env: TARGET=x86_64-unknown-linux-gnu
cache: cargo
- os: linux

46
Cargo.lock generated
View file

@ -154,7 +154,7 @@ dependencies = [
[[package]]
name = "deflate"
version = "0.7.20"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"adler32 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
@ -181,7 +181,7 @@ dependencies = [
[[package]]
name = "image"
version = "0.23.0"
version = "0.23.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bytemuck 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -189,7 +189,16 @@ dependencies = [
"num-iter 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"num-rational 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"png 0.15.3 (registry+https://github.com/rust-lang/crates.io-index)",
"png 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "indexmap"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rayon 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -202,7 +211,7 @@ dependencies = [
[[package]]
name = "itertools"
version = "0.8.2"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
@ -218,6 +227,14 @@ name = "libc"
version = "0.2.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "libdeflater"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "maybe-uninit"
version = "2.0.0"
@ -294,24 +311,27 @@ dependencies = [
"clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
"cloudflare-zlib 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
"crc 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
"image 0.23.0 (registry+https://github.com/rust-lang/crates.io-index)",
"itertools 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
"image 0.23.2 (registry+https://github.com/rust-lang/crates.io-index)",
"indexmap 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"itertools 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libdeflater 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"miniz_oxide 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"num_cpus 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rayon 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rgb 0.8.16 (registry+https://github.com/rust-lang/crates.io-index)",
"vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
"wild 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"zopfli 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "png"
version = "0.15.3"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"deflate 0.7.20 (registry+https://github.com/rust-lang/crates.io-index)",
"deflate 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)",
"inflate 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -455,15 +475,17 @@ dependencies = [
"checksum crossbeam-epoch 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace"
"checksum crossbeam-queue 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c695eeca1e7173472a32221542ae469b3e9aac3a4fc81f7696bcad82029493db"
"checksum crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8"
"checksum deflate 0.7.20 (registry+https://github.com/rust-lang/crates.io-index)" = "707b6a7b384888a70c8d2e8650b3e60170dfc6a67bb4aa67b6dfca57af4bedb4"
"checksum deflate 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)" = "050ef6de42a33903b30a7497b76b40d3d58691d4d3eec355348c122444a388f0"
"checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3"
"checksum glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
"checksum hermit-abi 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "1010591b26bbfe835e9faeabeb11866061cc7dcebffd56ad7d0942d0e61aefd8"
"checksum image 0.23.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ef4e336ec01a678e7ab692914c641181528e8656451e6252f8f9e33728882eaf"
"checksum image 0.23.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9062b90712d25bc6bb165d110aa59c6b47c849246e341e7b86a98daff9d49f60"
"checksum indexmap 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "076f042c5b7b98f31d205f1249267e12a6518c1481e9dae9764af19b707d2292"
"checksum inflate 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "1cdb29978cc5797bd8dcc8e5bf7de604891df2a8dc576973d71a281e916db2ff"
"checksum itertools 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f56a2d0bc861f9165be4eb3442afd3c236d8a98afd426f65d92324ae1091a484"
"checksum itertools 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b"
"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
"checksum libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)" = "eb147597cdf94ed43ab7a9038716637d2d1bf2bc571da995d0028dec06bd3018"
"checksum libdeflater 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "66dca08b13369865b2f6dca1dd05f833985cbe6c12a676b04d55f78b85e80246"
"checksum maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
"checksum memoffset 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "75189eb85871ea5c2e2c15abbdd541185f63b408415e5051f5cac122d8c774b9"
"checksum miniz_oxide 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "aa679ff6578b1cddee93d7e82e263b94a575e0bfced07284eb0c037c1d2416a5"
@ -472,7 +494,7 @@ dependencies = [
"checksum num-rational 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "da4dc79f9e6c81bef96148c8f6b8e72ad4541caa4a24373e900a36da07de03a3"
"checksum num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096"
"checksum num_cpus 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "46203554f085ff89c235cd12f7075f3233af9b11ed7c9e16dfe2560d03313ce6"
"checksum png 0.15.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ef859a23054bbfee7811284275ae522f0434a3c8e7f4b74bd4a35ae7e1c4a283"
"checksum png 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)" = "46060468187c21c00ffa2a920690b29997d7fd543f5a4d400461e4a7d4fccde8"
"checksum rayon 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "db6ce3297f9c85e16621bb8cca38a06779ffc31bb8184e1be4bed2be4678a098"
"checksum rayon-core 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "08a89b46efaf957e52b18062fb2f4660f8b8a4dde1807ca002690868ef2c85a9"
"checksum rgb 0.8.16 (registry+https://github.com/rust-lang/crates.io-index)" = "5ec4ab2cf0b27e111e266e161cf7f9efd20125a161190da1c0945c4a4408fef3"

View file

@ -31,11 +31,13 @@ required-features = ["binary"]
bit-vec = "^0.6.0"
byteorder = "^1.0.0"
crc = "^1.2.0"
itertools = "^0.8.0"
itertools = "^0.9.0"
num_cpus = "^1.0.0"
zopfli = "^0.4.0"
miniz_oxide = "0.3"
rgb = "0.8.11"
indexmap = { version = "1.3.2", features = ["rayon"] }
libdeflater = "0.2.0"
[dependencies.rayon]
optional = true

View file

@ -26,7 +26,7 @@ cargo build --release
cp target/release/oxipng /usr/local/bin
```
The current minimum supported Rust version is **1.36.0**. Oxipng may compile on earlier versions of Rust,
The current minimum supported Rust version is **1.41.0**. Oxipng may compile on earlier versions of Rust,
but there is no guarantee.
Oxipng follows Semantic Versioning.

View file

@ -26,7 +26,7 @@ cargo build --release
cp target/release/oxipng /usr/local/bin
```
The current minimum supported Rust version is **1.36.0**. Oxipng may compile on earlier versions of Rust,
The current minimum supported Rust version is **1.41.0**. Oxipng may compile on earlier versions of Rust,
but there is no guarantee.
Oxipng follows Semantic Versioning.

64
benches/libdeflater.rs Normal file
View file

@ -0,0 +1,64 @@
#![feature(test)]
extern crate oxipng;
extern crate test;
use oxipng::internal_tests::*;
use std::path::PathBuf;
use test::Bencher;
#[bench]
fn libdeflater_16_bits_strategy_0(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png"));
let png = PngData::new(&input, false).unwrap();
b.iter(|| {
libdeflater_deflate(png.raw.data.as_ref()).ok();
});
}
#[bench]
fn libdeflater_8_bits_strategy_0(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
let png = PngData::new(&input, false).unwrap();
b.iter(|| {
libdeflater_deflate(png.raw.data.as_ref()).ok();
});
}
#[bench]
fn libdeflater_4_bits_strategy_0(b: &mut Bencher) {
let input = test::black_box(PathBuf::from(
"tests/files/palette_4_should_be_palette_4.png",
));
let png = PngData::new(&input, false).unwrap();
b.iter(|| {
libdeflater_deflate(png.raw.data.as_ref()).ok();
});
}
#[bench]
fn libdeflater_2_bits_strategy_0(b: &mut Bencher) {
let input = test::black_box(PathBuf::from(
"tests/files/palette_2_should_be_palette_2.png",
));
let png = PngData::new(&input, false).unwrap();
b.iter(|| {
libdeflater_deflate(png.raw.data.as_ref()).ok();
});
}
#[bench]
fn libdeflater_1_bits_strategy_0(b: &mut Bencher) {
let input = test::black_box(PathBuf::from(
"tests/files/palette_1_should_be_palette_1.png",
));
let png = PngData::new(&input, false).unwrap();
b.iter(|| {
libdeflater_deflate(png.raw.data.as_ref()).ok();
});
}

6
package-lock.json generated
View file

@ -177,9 +177,9 @@
}
},
"minimist": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
"dev": true
},
"normalize-package-data": {

31
src/deflate/deflater.rs Normal file
View file

@ -0,0 +1,31 @@
use crate::{PngError, PngResult};
use crate::atomicmin::AtomicMin;
use libdeflater::{CompressionError, CompressionLvl, Compressor};
pub fn deflate(data: &[u8], max_size: &AtomicMin) -> PngResult<Vec<u8>> {
let mut compressor = Compressor::new(CompressionLvl::best());
let capacity = max_size.get().unwrap_or(data.len() / 2);
let mut dest = Vec::with_capacity(capacity);
unsafe {
// This is ok because the Vec contains Copy-able data (u8)
// and because libdeflater wrapper doesn't try to read
// the bytes from the target.
//
// That said, it should be able to accept MaybeUninit instead,
// so I raised an upstream issue that should make this safer:
// https://github.com/adamkewley/libdeflater/issues/1
dest.set_len(capacity);
}
let len = compressor
.zlib_compress(data, &mut dest)
.map_err(|err| match err {
CompressionError::InsufficientSpace => PngError::DeflatedDataTooLong(capacity),
})?;
if let Some(max) = max_size.get() {
if len > max {
return Err(PngError::DeflatedDataTooLong(max));
}
}
dest.truncate(len);
Ok(dest)
}

View file

@ -9,6 +9,9 @@ use zopfli;
#[doc(hidden)]
pub mod miniz_stream;
mod deflater;
pub use deflater::deflate as libdeflater_deflate;
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
pub mod cfzlib;
@ -62,4 +65,6 @@ pub enum Deflaters {
Zlib,
/// Use the better but slower Zopfli implementation
Zopfli,
/// Use libdeflater.
Libdeflater,
}

View file

@ -24,8 +24,6 @@ use std::thread;
struct Candidate {
image: PngData,
// compressed size multiplier. Fudge factor to prefer more promising formats.
bias: f32,
// if false, that's baseline file to throw away
is_reduction: bool,
filter: u8,
@ -42,30 +40,16 @@ impl Comparator {
fn evaluate(&mut self, new: Candidate) {
// a tie-breaker is required to make evaluation deterministic
let is_best = if let Some(ref old) = self.best_result {
// ordering is important - later file gets to use bias over earlier, but not the other way
// (this way bias=0 replaces, but doesn't forbid later optimizations)
let new_len = (new.image.idat_data.len() as f64
* if new.nth > old.nth {
f64::from(new.bias)
} else {
1.0
}) as usize;
let old_len = (old.image.idat_data.len() as f64
* if new.nth < old.nth {
f64::from(old.bias)
} else {
1.0
}) as usize;
// choose smallest compressed, or if compresses the same, smallest uncompressed, or cheaper filter
let new = (
new_len,
new.image.idat_data.len(),
new.image.raw.data.len(),
new.image.raw.ihdr.bit_depth,
new.filter,
new.nth,
);
let old = (
old_len,
old.image.idat_data.len(),
old.image.raw.data.len(),
old.image.raw.ihdr.bit_depth,
old.filter,
@ -141,17 +125,15 @@ impl Evaluator {
/// Set baseline image. It will be used only to measure minimum compression level required
pub fn set_baseline(&self, image: Arc<PngImage>) {
self.try_image_inner(image, 1.0, false)
self.try_image_inner(image, false)
}
/// Check if the image is smaller than others
/// Bias is a value in 0..=1 range. Compressed size is multiplied by
/// this fraction when comparing to the best, so 0.95 allows 5% larger size.
pub fn try_image(&self, image: Arc<PngImage>, bias: f32) {
self.try_image_inner(image, bias, true)
pub fn try_image(&self, image: Arc<PngImage>) {
self.try_image_inner(image, true)
}
fn try_image_inner(&self, image: Arc<PngImage>, bias: f32, is_reduction: bool) {
fn try_image_inner(&self, image: Arc<PngImage>, is_reduction: bool) {
let nth = self.nth.fetch_add(1, SeqCst);
// These clones are only cheap refcounts
let deadline = self.deadline.clone();
@ -186,7 +168,6 @@ impl Evaluator {
idat_data,
raw: Arc::clone(&image),
},
bias,
filter,
is_reduction,
nth,

View file

@ -1,9 +1,9 @@
use indexmap::IndexSet;
use crate::colors::{BitDepth, ColorType};
use crate::error::PngError;
use crate::PngResult;
use byteorder::{BigEndian, ReadBytesExt};
use crc::crc32;
use std::collections::HashSet;
use std::io::Cursor;
#[derive(Debug, Clone, Copy)]
@ -35,7 +35,7 @@ pub enum Headers {
/// Headers that won't affect rendering (all but cHRM, gAMA, iCCP, sBIT, sRGB, bKGD, hIST, pHYs, sPLT)
Safe,
/// Remove all non-critical chunks except these
Keep(HashSet<String>),
Keep(IndexSet<String>),
/// All non-critical headers
All,
}

View file

@ -28,8 +28,8 @@ use crate::png::PngImage;
use crate::reduction::*;
use crc::crc32;
use image::{DynamicImage, GenericImageView, ImageFormat, Pixel};
use indexmap::{IndexSet, IndexMap};
use rayon::prelude::*;
use std::collections::{BTreeMap, HashMap, HashSet};
use std::fmt;
use std::fs::{copy, File};
use std::io::{stdin, stdout, BufWriter, Read, Write};
@ -154,7 +154,7 @@ pub struct Options {
/// Which filters to try on the file (0-5)
///
/// Default: `0,5`
pub filter: HashSet<u8>,
pub filter: IndexSet<u8>,
/// Whether to change the interlacing type of the file.
///
/// `None` will not change the current interlacing type.
@ -166,11 +166,11 @@ pub struct Options {
/// Which zlib compression levels to try on the file (1-9)
///
/// Default: `9`
pub compression: HashSet<u8>,
pub compression: IndexSet<u8>,
/// Which zlib compression strategies to try on the file (0-3)
///
/// Default: `0-3`
pub strategies: HashSet<u8>,
pub strategies: IndexSet<u8>,
/// Window size to use when compressing the file, as `2^window` bytes.
///
/// Doesn't affect compression but may affect speed and memory usage.
@ -179,7 +179,7 @@ pub struct Options {
/// Default: `15`
pub window: u8,
/// Alpha filtering strategies to use
pub alphas: HashSet<colors::AlphaOptim>,
pub alphas: IndexSet<colors::AlphaOptim>,
/// Whether to attempt bit depth reduction
///
/// Default: `true`
@ -286,17 +286,17 @@ impl Options {
impl Default for Options {
fn default() -> Options {
// Default settings based on -o 2 from the CLI interface
let mut filter = HashSet::new();
let mut filter = IndexSet::new();
filter.insert(0);
filter.insert(5);
let mut compression = HashSet::new();
let mut compression = IndexSet::new();
compression.insert(9);
let mut strategies = HashSet::new();
let mut strategies = IndexSet::new();
for i in 0..4 {
strategies.insert(i);
}
// We always need NoOp to be present
let mut alphas = HashSet::new();
let mut alphas = IndexSet::new();
alphas.insert(AlphaOptim::NoOp);
Options {
@ -353,6 +353,7 @@ pub fn optimize(input: &InFile, output: &OutFile, opts: &Options) -> PngResult<(
data
}
};
let mut png = PngData::from_slice(&in_data, opts.fix_errors)?;
// Run the optimizer on the decoded PNG.
@ -501,7 +502,7 @@ fn optimize_png(
eprintln!(" File size = {} bytes", file_original_size);
}
let mut filter = opts.filter.iter().cloned().collect::<Vec<u8>>();
let mut filter = opts.filter.clone();
let compression = &opts.compression;
let mut strategies = opts.strategies.clone();
@ -511,14 +512,14 @@ fn optimize_png(
&& png.raw.ihdr.color_type != colors::ColorType::Indexed
{
if filter.is_empty() {
filter.push(5);
filter.insert(5);
}
if strategies.is_empty() {
strategies.insert(1);
}
} else {
if filter.is_empty() {
filter.push(0);
filter.insert(0);
}
if strategies.is_empty() {
strategies.insert(0);
@ -565,7 +566,7 @@ fn optimize_png(
}
}
} else {
// Zopfli compression has no additional options
// Zopfli and Libdeflater compression have no additional options.
results.push(TrialOptions {
filter: *f,
compression: 0,
@ -582,8 +583,10 @@ fn optimize_png(
eprintln!("Trying: {} combinations", results.len());
}
let filter_iter = filter.par_iter().with_max_len(1);
let filters: HashMap<u8, Vec<u8>> = filter_iter
let filters: IndexMap<u8, Vec<u8>> =
filter
.par_iter()
.with_max_len(1)
.map(|f| {
let png = png.clone();
(*f, png.raw.filter_image(*f))
@ -600,17 +603,17 @@ fn optimize_png(
return None;
}
let filtered = &filters[&trial.filter];
let new_idat = if opts.deflate == Deflaters::Zlib {
deflate::deflate(
let new_idat = match opts.deflate {
Deflaters::Zlib => deflate::deflate(
filtered,
trial.compression,
trial.strategy,
opts.window,
&best_size,
&deadline,
)
} else {
deflate::zopfli_deflate(filtered)
),
Deflaters::Zopfli => deflate::zopfli_deflate(filtered),
Deflaters::Libdeflater => deflate::libdeflater_deflate(filtered, &best_size),
};
let new_idat = match new_idat {
@ -740,7 +743,7 @@ fn perform_reductions(
if let Some(interlacing) = opts.interlace {
if let Some(reduced) = png.change_interlacing(interlacing) {
png = Arc::new(reduced);
eval.try_image(png.clone(), 0.);
eval.try_image(png.clone());
}
if deadline.passed() {
return;
@ -750,7 +753,7 @@ fn perform_reductions(
if opts.palette_reduction {
if let Some(reduced) = reduced_palette(&png) {
png = Arc::new(reduced);
eval.try_image(png.clone(), 0.95);
eval.try_image(png.clone());
if opts.verbosity == Some(1) {
report_reduction(&png);
}
@ -765,13 +768,13 @@ fn perform_reductions(
let previous = png.clone();
let bits = reduced.ihdr.bit_depth;
png = Arc::new(reduced);
eval.try_image(png.clone(), 1.0);
eval.try_image(png.clone());
if (bits == BitDepth::One || bits == BitDepth::Two)
&& previous.ihdr.bit_depth != BitDepth::Four
{
// Also try 16-color mode for all lower bits images, since that may compress better
if let Some(reduced) = reduce_bit_depth(&previous, 4) {
eval.try_image(Arc::new(reduced), 0.98);
eval.try_image(Arc::new(reduced));
}
}
if opts.verbosity == Some(1) {
@ -786,7 +789,7 @@ fn perform_reductions(
if opts.color_type_reduction {
if let Some(reduced) = reduce_color_type(&png) {
png = Arc::new(reduced);
eval.try_image(png.clone(), 0.96);
eval.try_image(png.clone());
if opts.verbosity == Some(1) {
report_reduction(&png);
}
@ -864,15 +867,10 @@ fn perform_strip(png: &mut PngData, opts: &Options) {
// Strip headers
Headers::None => (),
Headers::Keep(ref hdrs) => {
let keys: Vec<[u8; 4]> = raw.aux_headers.keys().cloned().collect();
for hdr in &keys {
let preserve = std::str::from_utf8(hdr)
.ok()
.map_or(false, |name| hdrs.contains(name));
if !preserve {
raw.aux_headers.remove(hdr);
}
}
raw.aux_headers.retain(|hdr, _| {
std::str::from_utf8(hdr)
.map_or(false, |name| hdrs.contains(name))
})
}
Headers::Strip(ref hdrs) => {
for hdr in hdrs {
@ -892,7 +890,7 @@ fn perform_strip(png: &mut PngData, opts: &Options) {
}
}
Headers::All => {
raw.aux_headers = BTreeMap::new();
raw.aux_headers = IndexMap::new();
}
}

View file

@ -14,13 +14,13 @@
#![allow(clippy::cognitive_complexity)]
use clap::{App, AppSettings, Arg, ArgMatches};
use indexmap::IndexSet;
use oxipng::AlphaOptim;
use oxipng::Deflaters;
use oxipng::Headers;
use oxipng::Options;
use oxipng::PngResult;
use oxipng::{InFile, OutFile};
use std::collections::HashSet;
use std::fs::DirBuilder;
use std::path::PathBuf;
use std::process::exit;
@ -193,6 +193,10 @@ fn main() {
.help("Use the slower but better compressing Zopfli algorithm, overrides zlib-specific options")
.short("Z")
.long("zopfli"))
.arg(Arg::with_name("libdeflater")
.help("Use an alternative Libdeflater algorithm, overrides zlib-specific options")
.short("D")
.long("libdeflater"))
.arg(Arg::with_name("timeout")
.help("Maximum amount of time, in seconds, to spend on optimizations")
.takes_value(true)
@ -481,6 +485,10 @@ fn parse_opts_into_struct(
opts.deflate = Deflaters::Zopfli;
}
if matches.is_present("libdeflater") {
opts.deflate = Deflaters::Libdeflater;
}
if let Some(x) = matches.value_of("threads") {
opts.threads = x.parse::<usize>().unwrap();
}
@ -492,9 +500,9 @@ fn parse_numeric_range_opts(
input: &str,
min_value: u8,
max_value: u8,
) -> Result<HashSet<u8>, String> {
) -> Result<IndexSet<u8>, String> {
const ERROR_MESSAGE: &str = "Not a valid input";
let mut items = HashSet::new();
let mut items = IndexSet::new();
// one value
if let Ok(one_value) = input.parse::<u8>() {

View file

@ -6,9 +6,9 @@ use crate::headers::*;
use crate::interlace::{deinterlace_image, interlace_image};
use byteorder::{BigEndian, WriteBytesExt};
use crc::crc32;
use indexmap::IndexMap;
use rgb::ComponentSlice;
use rgb::RGBA8;
use std::collections::BTreeMap;
use std::fs::File;
use std::io::{Read, Seek, SeekFrom};
use std::iter::Iterator;
@ -38,7 +38,7 @@ pub struct PngImage {
/// The pixel value that should be rendered as transparent
pub transparency_pixel: Option<Vec<u8>>,
/// All non-critical headers from the PNG are stored here
pub aux_headers: BTreeMap<[u8; 4], Vec<u8>>,
pub aux_headers: IndexMap<[u8; 4], Vec<u8>>,
}
/// Contains all data relevant to a PNG image
@ -97,7 +97,7 @@ impl PngData {
}
byte_offset += 8;
// Read the data headers
let mut aux_headers: BTreeMap<[u8; 4], Vec<u8>> = BTreeMap::new();
let mut aux_headers: IndexMap<[u8; 4], Vec<u8>> = IndexMap::new();
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 {

View file

@ -1,3 +1,4 @@
use indexmap::IndexSet;
use crate::colors::AlphaOptim;
use crate::colors::ColorType;
use crate::evaluate::Evaluator;
@ -8,23 +9,22 @@ use crate::png::PngImage;
use crate::rayon::prelude::*;
#[cfg(feature = "parallel")]
use rayon::prelude::*;
use std::collections::HashSet;
use std::sync::Arc;
pub(crate) fn try_alpha_reductions(
png: Arc<PngImage>,
alphas: &HashSet<AlphaOptim>,
alphas: &IndexSet<AlphaOptim>,
eval: &Evaluator,
) {
if alphas.is_empty() {
return;
}
let alphas = alphas.iter().collect::<Vec<_>>();
let alphas_iter = alphas.par_iter().with_max_len(1);
alphas_iter
.filter_map(|&alpha| filtered_alpha_channel(&png, *alpha))
.for_each(|image| eval.try_image(Arc::new(image), 0.99));
alphas
.par_iter()
.with_max_len(1)
.filter_map(|&alpha| filtered_alpha_channel(&png, alpha))
.for_each(|image| eval.try_image(Arc::new(image)));
}
pub fn filtered_alpha_channel(png: &PngImage, optim: AlphaOptim) -> Option<PngImage> {

View file

@ -1,9 +1,9 @@
use crate::colors::{BitDepth, ColorType};
use crate::headers::IhdrData;
use crate::png::PngImage;
use indexmap::IndexMap;
use itertools::Itertools;
use rgb::{FromSlice, RGB8, RGBA8};
use std::collections::HashMap;
use std::hash::Hash;
#[must_use]
@ -76,7 +76,7 @@ pub fn reduce_rgba_to_grayscale_alpha(png: &PngImage) -> Option<PngImage> {
fn reduce_scanline_to_palette<T>(
iter: impl IntoIterator<Item = T>,
palette: &mut HashMap<T, u8>,
palette: &mut IndexMap<T, u8>,
reduced: &mut Vec<u8>,
) -> bool
where
@ -105,7 +105,7 @@ pub fn reduced_color_to_palette(png: &PngImage) -> Option<PngImage> {
return None;
}
let mut raw_data = Vec::with_capacity(png.data.len());
let mut palette = HashMap::with_capacity(257);
let mut palette = IndexMap::with_capacity(257);
let transparency_pixel = png
.transparency_pixel
.as_ref()

View file

@ -1,10 +1,9 @@
use crate::colors::{BitDepth, ColorType};
use crate::headers::IhdrData;
use crate::png::PngImage;
use indexmap::map::{IndexMap, Entry::*};
use rgb::RGBA8;
use std::borrow::Cow;
use std::collections::hash_map::Entry::*;
use std::collections::HashMap;
pub mod alpha;
use crate::alpha::*;
@ -79,7 +78,7 @@ pub fn reduced_palette(png: &PngImage) -> Option<PngImage> {
});
let mut next_index = 0u16;
let mut seen = HashMap::with_capacity(palette.len());
let mut seen = IndexMap::with_capacity(palette.len());
for (i, used) in used_enumerated.iter().cloned() {
if !used {
continue;

View file

@ -1,8 +1,6 @@
use oxipng;
use indexmap::IndexSet;
use oxipng::internal_tests::*;
use oxipng::{InFile, OutFile};
use std::collections::HashSet;
use std::fs::remove_file;
use std::path::Path;
use std::path::PathBuf;
@ -11,7 +9,7 @@ fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
let mut options = oxipng::Options::default();
options.verbosity = None;
options.force = true;
let mut filter = HashSet::new();
let mut filter = IndexSet::new();
filter.insert(0);
options.filter = filter;
@ -33,7 +31,7 @@ fn test_it_converts(
let (output, mut opts) = get_opts(&input);
let png = PngData::new(&input, opts.fix_errors).unwrap();
opts.filter = HashSet::new();
opts.filter = IndexSet::new();
opts.filter.insert(filter);
assert_eq!(png.raw.ihdr.color_type, color_type_in);
assert_eq!(png.raw.ihdr.bit_depth, bit_depth_in);

View file

@ -1,8 +1,6 @@
extern crate oxipng;
use indexmap::IndexSet;
use oxipng::internal_tests::*;
use oxipng::{InFile, OutFile};
use std::collections::HashSet;
use std::fs::remove_file;
use std::path::Path;
use std::path::PathBuf;
@ -11,7 +9,7 @@ fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
let mut options = oxipng::Options::default();
options.verbosity = None;
options.force = true;
let mut filter = HashSet::new();
let mut filter = IndexSet::new();
filter.insert(0);
options.filter = filter;
@ -342,7 +340,7 @@ fn interlaced_0_to_1_other_filter_mode() {
let input = PathBuf::from("tests/files/interlaced_0_to_1_other_filter_mode.png");
let (output, mut opts) = get_opts(&input);
opts.interlace = Some(1);
let mut filter = HashSet::new();
let mut filter = IndexSet::new();
filter.insert(4);
opts.filter = filter;
@ -438,3 +436,21 @@ fn zopfli_mode() {
BitDepth::Eight,
);
}
#[test]
fn libdeflater_mode() {
let input = PathBuf::from("tests/files/zopfli_mode.png");
let (output, mut opts) = get_opts(&input);
opts.deflate = Deflaters::Libdeflater;
test_it_converts(
input,
&output,
&opts,
ColorType::RGB,
BitDepth::Eight,
ColorType::RGB,
BitDepth::Eight,
);
}

View file

@ -1,8 +1,6 @@
extern crate oxipng;
use indexmap::IndexSet;
use oxipng::internal_tests::*;
use oxipng::{InFile, OutFile};
use std::collections::HashSet;
use std::fs::remove_file;
use std::path::Path;
use std::path::PathBuf;
@ -11,7 +9,7 @@ fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
let mut options = oxipng::Options::default();
options.verbosity = None;
options.force = true;
let mut filter = HashSet::new();
let mut filter = IndexSet::new();
filter.insert(0);
options.filter = filter;

View file

@ -1,8 +1,6 @@
use oxipng;
use indexmap::IndexSet;
use oxipng::internal_tests::*;
use oxipng::{InFile, OutFile};
use std::collections::HashSet;
use std::fs::remove_file;
use std::path::Path;
use std::path::PathBuf;
@ -11,7 +9,7 @@ fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
let mut options = oxipng::Options::default();
options.verbosity = None;
options.force = true;
let mut filter = HashSet::new();
let mut filter = IndexSet::new();
filter.insert(0);
options.filter = filter;

View file

@ -1,8 +1,6 @@
use oxipng;
use indexmap::IndexSet;
use oxipng::internal_tests::*;
use oxipng::{InFile, OutFile};
use std::collections::HashSet;
use std::fs::remove_file;
use std::path::Path;
use std::path::PathBuf;
@ -11,7 +9,7 @@ fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
let mut options = oxipng::Options::default();
options.verbosity = None;
options.force = true;
let mut filter = HashSet::new();
let mut filter = IndexSet::new();
filter.insert(0);
options.filter = filter;
@ -309,7 +307,7 @@ fn issue_113_white() {
let input = "tests/files/issue-113.png";
let (_, mut opts) = get_opts(Path::new(input));
opts.interlace = Some(1);
opts.alphas = HashSet::new();
opts.alphas = IndexSet::new();
opts.alphas.insert(AlphaOptim::Black);
let output = OutFile::Path(Some(
Path::new(input).with_extension("-white-out.png").to_owned(),
@ -329,7 +327,7 @@ fn issue_113_black() {
let input = "tests/files/issue-113.png";
let (_, mut opts) = get_opts(Path::new(input));
opts.interlace = Some(1);
opts.alphas = HashSet::new();
opts.alphas = IndexSet::new();
opts.alphas.insert(AlphaOptim::Black);
let output = OutFile::Path(Some(
Path::new(input).with_extension("-black-out.png").to_owned(),
@ -349,7 +347,7 @@ fn issue_113_right() {
let input = "tests/files/issue-113.png";
let (_, mut opts) = get_opts(Path::new(input));
opts.interlace = Some(1);
opts.alphas = HashSet::new();
opts.alphas = IndexSet::new();
opts.alphas.insert(AlphaOptim::Right);
let output = OutFile::Path(Some(
Path::new(input).with_extension("-right-out.png").to_owned(),
@ -369,7 +367,7 @@ fn issue_113_left() {
let input = "tests/files/issue-113.png";
let (_, mut opts) = get_opts(Path::new(input));
opts.interlace = Some(1);
opts.alphas = HashSet::new();
opts.alphas = IndexSet::new();
opts.alphas.insert(AlphaOptim::Left);
let output = OutFile::Path(Some(
Path::new(input).with_extension("-left-out.png").to_owned(),
@ -389,7 +387,7 @@ fn issue_113_up() {
let input = "tests/files/issue-113.png";
let (_, mut opts) = get_opts(Path::new(input));
opts.interlace = Some(1);
opts.alphas = HashSet::new();
opts.alphas = IndexSet::new();
opts.alphas.insert(AlphaOptim::Up);
let output = OutFile::Path(Some(
Path::new(input).with_extension("-up-out.png").to_owned(),
@ -409,7 +407,7 @@ fn issue_113_down() {
let input = "tests/files/issue-113.png";
let (_, mut opts) = get_opts(Path::new(input));
opts.interlace = Some(1);
opts.alphas = HashSet::new();
opts.alphas = IndexSet::new();
opts.alphas.insert(AlphaOptim::Down);
let output = OutFile::Path(Some(
Path::new(input).with_extension("-down-out.png").to_owned(),
@ -441,7 +439,7 @@ fn issue_129() {
fn issue_133_black() {
let input = "tests/files/issue-133.png";
let (_, mut opts) = get_opts(Path::new(input));
opts.alphas = HashSet::new();
opts.alphas = IndexSet::new();
opts.alphas.insert(AlphaOptim::Black);
let output = OutFile::Path(Some(
Path::new(input).with_extension("-black-out.png").to_owned(),
@ -460,7 +458,7 @@ fn issue_133_black() {
fn issue_133_white() {
let input = "tests/files/issue-133.png";
let (_, mut opts) = get_opts(Path::new(input));
opts.alphas = HashSet::new();
opts.alphas = IndexSet::new();
opts.alphas.insert(AlphaOptim::White);
let output = OutFile::Path(Some(
Path::new(input).with_extension("-white-out.png").to_owned(),
@ -479,7 +477,7 @@ fn issue_133_white() {
fn issue_133_up() {
let input = "tests/files/issue-133.png";
let (_, mut opts) = get_opts(Path::new(input));
opts.alphas = HashSet::new();
opts.alphas = IndexSet::new();
opts.alphas.insert(AlphaOptim::Up);
let output = OutFile::Path(Some(
Path::new(input).with_extension("-up-out.png").to_owned(),
@ -498,7 +496,7 @@ fn issue_133_up() {
fn issue_133_down() {
let input = "tests/files/issue-133.png";
let (_, mut opts) = get_opts(Path::new(input));
opts.alphas = HashSet::new();
opts.alphas = IndexSet::new();
opts.alphas.insert(AlphaOptim::Down);
let output = OutFile::Path(Some(
Path::new(input).with_extension("-down-out.png").to_owned(),
@ -517,7 +515,7 @@ fn issue_133_down() {
fn issue_133_right() {
let input = "tests/files/issue-133.png";
let (_, mut opts) = get_opts(Path::new(input));
opts.alphas = HashSet::new();
opts.alphas = IndexSet::new();
opts.alphas.insert(AlphaOptim::Right);
let output = OutFile::Path(Some(
Path::new(input).with_extension("-right-out.png").to_owned(),
@ -536,7 +534,7 @@ fn issue_133_right() {
fn issue_133_left() {
let input = "tests/files/issue-133.png";
let (_, mut opts) = get_opts(Path::new(input));
opts.alphas = HashSet::new();
opts.alphas = IndexSet::new();
opts.alphas.insert(AlphaOptim::Left);
let output = OutFile::Path(Some(
Path::new(input).with_extension("-left-out.png").to_owned(),