Select compression and filter modes heuristically for -o1

Closes #21
This commit is contained in:
Joshua Holmer 2016-03-04 15:24:58 -05:00
parent 849d36ec6c
commit c6452beb12
7 changed files with 67 additions and 34 deletions

View file

@ -2,6 +2,7 @@
- Fix program version that is displayed when running `oxipng -V`
- Ensure `--quiet` mode is actually quiet (@SethDusek [#20](https://github.com/shssoichiro/oxipng/pull/20))
- Imply `--quiet` when `--stdout` is enabled
- Use heuristics to determine best combination for `-o1` ([#21](https://github.com/shssoichiro/oxipng/issues/21))
**Version 0.1.1**
- Fix `oxipng *` writing all input files to one output file ([#15](https://github.com/shssoichiro/oxipng/issues/15))

View file

@ -41,11 +41,14 @@ pub struct Options {
pub palette_reduction: bool,
pub idat_recoding: bool,
pub strip: bool,
pub use_heuristics: bool,
}
pub fn optimize(filepath: &Path, opts: &Options) -> Result<(), String> {
// Decode PNG from file
if opts.verbosity.is_some() { println!("Processing: {}", filepath.to_str().unwrap()) };
if opts.verbosity.is_some() {
println!("Processing: {}", filepath.to_str().unwrap())
};
let in_file = Path::new(filepath);
let mut png = match png::PngData::new(&in_file) {
Ok(x) => x,
@ -73,6 +76,31 @@ pub fn optimize(filepath: &Path, opts: &Options) -> Result<(), String> {
println!(" File size = {} bytes", file_original_size);
}
let mut filter = opts.filter.clone();
let compression = opts.compression.clone();
let memory = opts.memory.clone();
let mut strategies = opts.strategies.clone();
if opts.use_heuristics {
// Heuristically determine which set of options to use
if png.ihdr_data.bit_depth.as_u8() >= 8 &&
png.ihdr_data.color_type != png::ColorType::Indexed {
if filter.is_empty() {
filter.insert(5);
}
if strategies.is_empty() {
strategies.insert(1);
}
} else {
if filter.is_empty() {
filter.insert(0);
}
if strategies.is_empty() {
strategies.insert(0);
}
}
}
let mut something_changed = false;
if opts.bit_depth_reduction {
@ -118,16 +146,17 @@ pub fn optimize(filepath: &Path, opts: &Options) -> Result<(), String> {
if opts.idat_recoding || something_changed {
// Go through selected permutations and determine the best
let mut best: Option<(u8, u8, u8, u8, Vec<u8>)> = None;
let combinations = opts.filter.len() * opts.compression.len() * opts.memory.len() *
opts.strategies.len();
let combinations = filter.len() * compression.len() * memory.len() * strategies.len();
let mut results = Vec::with_capacity(combinations);
if opts.verbosity.is_some() { println!("Trying: {} combinations", combinations) };
if opts.verbosity.is_some() {
println!("Trying: {} combinations", combinations)
};
crossbeam::scope(|scope| {
for f in &opts.filter {
for f in &filter {
let filtered = png.filter_image(*f);
for zc in &opts.compression {
for zm in &opts.memory {
for zs in &opts.strategies {
for zc in &compression {
for zm in &memory {
for zs in &strategies {
let moved_filtered = filtered.clone();
results.push(scope.spawn(move || {
let new_idat = match deflate::deflate::deflate(&moved_filtered,
@ -173,13 +202,13 @@ pub fn optimize(filepath: &Path, opts: &Options) -> Result<(), String> {
if let Some(better) = best {
png.idat_data = better.4.clone();
if opts.verbosity.is_some() {
println!("Found better combination:");
println!(" zc = {} zm = {} zs = {} f = {} {} bytes",
better.1,
better.2,
better.3,
better.0,
png.idat_data.len());
println!("Found better combination:");
println!(" zc = {} zm = {} zs = {} f = {} {} bytes",
better.1,
better.2,
better.3,
better.0,
png.idat_data.len());
}
}
}
@ -240,25 +269,25 @@ pub fn optimize(filepath: &Path, opts: &Options) -> Result<(), String> {
if opts.verbosity.is_some() {
if idat_original_size >= png.idat_data.len() {
println!(" IDAT size = {} bytes ({} bytes decrease)",
png.idat_data.len(),
idat_original_size - png.idat_data.len());
png.idat_data.len(),
idat_original_size - png.idat_data.len());
} else {
println!(" IDAT size = {} bytes ({} bytes increase)",
png.idat_data.len(),
png.idat_data.len() - idat_original_size);
png.idat_data.len(),
png.idat_data.len() - idat_original_size);
}
if file_original_size >= output_data.len() {
println!(" file size = {} bytes ({} bytes = {:.2}% decrease)",
output_data.len(),
file_original_size - output_data.len(),
(file_original_size - output_data.len()) as f64 / file_original_size as f64 *
100f64);
output_data.len(),
file_original_size - output_data.len(),
(file_original_size - output_data.len()) as f64 / file_original_size as f64 *
100f64);
} else {
println!(" file size = {} bytes ({} bytes = {:.2}% increase)",
output_data.len(),
output_data.len() - file_original_size,
(output_data.len() - file_original_size) as f64 / file_original_size as f64 *
100f64);
output_data.len(),
output_data.len() - file_original_size,
(output_data.len() - file_original_size) as f64 / file_original_size as f64 *
100f64);
}
}
Ok(())

View file

@ -46,6 +46,7 @@ fn main() {
palette_reduction: true,
idat_recoding: true,
strip: false,
use_heuristics: false,
};
let matches = App::new("oxipng")
@ -202,7 +203,7 @@ fn main() {
.long("strip"))
.after_help("Optimization levels:
-o 0 => --zc 3 --nz (0 or 1 trials)
-o 1 => --zc 9 (1 trial)
-o 1 => --zc 9 (1 trial, determined heuristically)
-o 2 => --zc 9 --zs 0-3 --f 0,5 (8 trials)
-o 3 => --zc 9 --zm 8-9 --zs 0-3 --f 0,5 (16 trials)
-o 4 => --zc 9 --zm 8-9 --zs 0-3 --f 0-5 (48 trials)
@ -268,12 +269,11 @@ fn parse_opts_into_struct(matches: &ArgMatches, opts: &mut oxipng::Options) -> R
opts.compression = compression;
}
Some("1") => {
let mut filter = HashSet::new();
filter.insert(0);
let filter = HashSet::new();
opts.filter = filter;
let mut strategies = HashSet::new();
strategies.insert(0);
let strategies = HashSet::new();
opts.strategies = strategies;
opts.use_heuristics = true;
}
// 2 is the default
Some("3") => {

View file

@ -68,7 +68,7 @@ impl fmt::Display for BitDepth {
}
impl BitDepth {
fn as_u8(&self) -> u8 {
pub fn as_u8(&self) -> u8 {
match *self {
BitDepth::One => 1,
BitDepth::Two => 2,
@ -77,7 +77,7 @@ impl BitDepth {
BitDepth::Sixteen => 16,
}
}
fn from_u8(depth: u8) -> BitDepth {
pub fn from_u8(depth: u8) -> BitDepth {
match depth {
1 => BitDepth::One,
2 => BitDepth::Two,

View file

@ -45,6 +45,7 @@ fn get_opts(input: &Path) -> oxipng::Options {
palette_reduction: true,
idat_recoding: true,
strip: false,
use_heuristics: false,
}
}

View file

@ -45,6 +45,7 @@ fn get_opts(input: &Path) -> oxipng::Options {
palette_reduction: true,
idat_recoding: true,
strip: false,
use_heuristics: false,
}
}

View file

@ -45,6 +45,7 @@ fn get_opts(input: &Path) -> oxipng::Options {
palette_reduction: true,
idat_recoding: true,
strip: false,
use_heuristics: false,
}
}