parent
849d36ec6c
commit
c6452beb12
7 changed files with 67 additions and 34 deletions
|
|
@ -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))
|
||||
|
|
|
|||
83
src/lib.rs
83
src/lib.rs
|
|
@ -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(())
|
||||
|
|
|
|||
10
src/main.rs
10
src/main.rs
|
|
@ -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") => {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ fn get_opts(input: &Path) -> oxipng::Options {
|
|||
palette_reduction: true,
|
||||
idat_recoding: true,
|
||||
strip: false,
|
||||
use_heuristics: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ fn get_opts(input: &Path) -> oxipng::Options {
|
|||
palette_reduction: true,
|
||||
idat_recoding: true,
|
||||
strip: false,
|
||||
use_heuristics: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ fn get_opts(input: &Path) -> oxipng::Options {
|
|||
palette_reduction: true,
|
||||
idat_recoding: true,
|
||||
strip: false,
|
||||
use_heuristics: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue