Print console output to stderr instead of stdout
Makes it friendlier with --stdout option See #20 for discussion
This commit is contained in:
parent
0ae632c7f4
commit
7d1e3a5bb9
8 changed files with 133 additions and 43 deletions
|
|
@ -1,6 +1,7 @@
|
|||
**Version 0.1.2** (unreleased)
|
||||
- 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))
|
||||
- Write status/debug information to stderr instead of stdout
|
||||
- Use heuristics to determine best combination for `-o1` ([#21](https://github.com/shssoichiro/oxipng/issues/21))
|
||||
|
||||
**Version 0.1.1**
|
||||
|
|
|
|||
102
src/lib.rs
102
src/lib.rs
|
|
@ -7,7 +7,7 @@ extern crate libz_sys;
|
|||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::fs::{File, copy};
|
||||
use std::io::{BufWriter, Write, stdout};
|
||||
use std::io::{BufWriter, Write, stderr, stdout};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub mod deflate {
|
||||
|
|
@ -47,8 +47,8 @@ pub struct Options {
|
|||
pub fn optimize(filepath: &Path, opts: &Options) -> Result<(), String> {
|
||||
// Decode PNG from file
|
||||
if opts.verbosity.is_some() {
|
||||
println!("Processing: {}", filepath.to_str().unwrap())
|
||||
};
|
||||
writeln!(&mut stderr(), "Processing: {}", filepath.to_str().unwrap()).ok();
|
||||
}
|
||||
let in_file = Path::new(filepath);
|
||||
let mut png = match png::PngData::new(&in_file) {
|
||||
Ok(x) => x,
|
||||
|
|
@ -59,21 +59,33 @@ pub fn optimize(filepath: &Path, opts: &Options) -> Result<(), String> {
|
|||
let idat_original_size = png.idat_data.len();
|
||||
let file_original_size = filepath.metadata().unwrap().len() as usize;
|
||||
if opts.verbosity.is_some() {
|
||||
println!(" {}x{} pixels, PNG format",
|
||||
writeln!(&mut stderr(),
|
||||
" {}x{} pixels, PNG format",
|
||||
png.ihdr_data.width,
|
||||
png.ihdr_data.height);
|
||||
png.ihdr_data.height)
|
||||
.ok();
|
||||
if let Some(palette) = png.palette.clone() {
|
||||
println!(" {} bits/pixel, {} colors in palette",
|
||||
writeln!(&mut stderr(),
|
||||
" {} bits/pixel, {} colors in palette",
|
||||
png.ihdr_data.bit_depth,
|
||||
palette.len() / 3);
|
||||
palette.len() / 3)
|
||||
.ok();
|
||||
} else {
|
||||
println!(" {}x{} bits/pixel, {:?}",
|
||||
writeln!(&mut stderr(),
|
||||
" {}x{} bits/pixel, {:?}",
|
||||
png.channels_per_pixel(),
|
||||
png.ihdr_data.bit_depth,
|
||||
png.ihdr_data.color_type);
|
||||
png.ihdr_data.color_type)
|
||||
.ok();
|
||||
}
|
||||
println!(" IDAT size = {} bytes", idat_original_size);
|
||||
println!(" File size = {} bytes", file_original_size);
|
||||
writeln!(&mut stderr(),
|
||||
" IDAT size = {} bytes",
|
||||
idat_original_size)
|
||||
.ok();
|
||||
writeln!(&mut stderr(),
|
||||
" File size = {} bytes",
|
||||
file_original_size)
|
||||
.ok();
|
||||
}
|
||||
|
||||
let mut filter = opts.filter.clone();
|
||||
|
|
@ -149,8 +161,8 @@ pub fn optimize(filepath: &Path, opts: &Options) -> Result<(), String> {
|
|||
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)
|
||||
};
|
||||
writeln!(&mut stderr(), "Trying: {} combinations", combinations).ok();
|
||||
}
|
||||
crossbeam::scope(|scope| {
|
||||
for f in &filter {
|
||||
let filtered = png.filter_image(*f);
|
||||
|
|
@ -169,12 +181,12 @@ pub fn optimize(filepath: &Path, opts: &Options) -> Result<(), String> {
|
|||
};
|
||||
|
||||
if opts.verbosity == Some(1) {
|
||||
println!(" zc = {} zm = {} zs = {} f = {} {} bytes",
|
||||
writeln!(&mut stderr(), " zc = {} zm = {} zs = {} f = {} {} bytes",
|
||||
*zc,
|
||||
*zm,
|
||||
*zs,
|
||||
*f,
|
||||
new_idat.len());
|
||||
new_idat.len()).ok();
|
||||
}
|
||||
|
||||
Ok((*f, *zc, *zm, *zs, new_idat.clone()))
|
||||
|
|
@ -202,13 +214,15 @@ 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",
|
||||
writeln!(&mut stderr(), "Found better combination:").ok();
|
||||
writeln!(&mut stderr(),
|
||||
" zc = {} zm = {} zs = {} f = {} {} bytes",
|
||||
better.1,
|
||||
better.2,
|
||||
better.3,
|
||||
better.0,
|
||||
png.idat_data.len());
|
||||
png.idat_data.len())
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -220,12 +234,12 @@ pub fn optimize(filepath: &Path, opts: &Options) -> Result<(), String> {
|
|||
|
||||
let output_data = png.output();
|
||||
if file_original_size <= output_data.len() && !opts.force && opts.interlace.is_none() {
|
||||
println!("File already optimized");
|
||||
writeln!(&mut stderr(), "File already optimized").ok();
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if opts.pretend {
|
||||
println!("Running in pretend mode, no output");
|
||||
writeln!(&mut stderr(), "Running in pretend mode, no output").ok();
|
||||
} else {
|
||||
if opts.backup {
|
||||
match copy(in_file,
|
||||
|
|
@ -256,38 +270,48 @@ pub fn optimize(filepath: &Path, opts: &Options) -> Result<(), String> {
|
|||
}
|
||||
};
|
||||
let mut buffer = BufWriter::new(out_file);
|
||||
if opts.verbosity.is_some() {
|
||||
match buffer.write_all(&output_data) {
|
||||
Ok(_) => println!("Output: {}", opts.out_file.display()),
|
||||
Err(_) => {
|
||||
return Err(format!("Unable to write to file {}", opts.out_file.display()))
|
||||
match buffer.write_all(&output_data) {
|
||||
Ok(_) => {
|
||||
if opts.verbosity.is_some() {
|
||||
writeln!(&mut stderr(), "Output: {}", opts.out_file.display()).ok();
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
return Err(format!("Unable to write to file {}", opts.out_file.display()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if opts.verbosity.is_some() {
|
||||
if idat_original_size >= png.idat_data.len() {
|
||||
println!(" IDAT size = {} bytes ({} bytes decrease)",
|
||||
writeln!(&mut stderr(),
|
||||
" IDAT size = {} bytes ({} bytes decrease)",
|
||||
png.idat_data.len(),
|
||||
idat_original_size - png.idat_data.len());
|
||||
idat_original_size - png.idat_data.len())
|
||||
.ok();
|
||||
} else {
|
||||
println!(" IDAT size = {} bytes ({} bytes increase)",
|
||||
writeln!(&mut stderr(),
|
||||
" IDAT size = {} bytes ({} bytes increase)",
|
||||
png.idat_data.len(),
|
||||
png.idat_data.len() - idat_original_size);
|
||||
png.idat_data.len() - idat_original_size)
|
||||
.ok();
|
||||
}
|
||||
if file_original_size >= output_data.len() {
|
||||
println!(" file size = {} bytes ({} bytes = {:.2}% decrease)",
|
||||
writeln!(&mut stderr(),
|
||||
" 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);
|
||||
100f64)
|
||||
.ok();
|
||||
} else {
|
||||
println!(" file size = {} bytes ({} bytes = {:.2}% increase)",
|
||||
writeln!(&mut stderr(),
|
||||
" 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);
|
||||
100f64)
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
|
@ -295,13 +319,17 @@ pub fn optimize(filepath: &Path, opts: &Options) -> Result<(), String> {
|
|||
|
||||
fn report_reduction(png: &png::PngData) {
|
||||
if let Some(palette) = png.palette.clone() {
|
||||
println!("Reducing image to {} bits/pixel, {} colors in palette",
|
||||
writeln!(&mut stderr(),
|
||||
"Reducing image to {} bits/pixel, {} colors in palette",
|
||||
png.ihdr_data.bit_depth,
|
||||
palette.len() / 3);
|
||||
palette.len() / 3)
|
||||
.ok();
|
||||
} else {
|
||||
println!("Reducing image to {}x{} bits/pixel, {}",
|
||||
writeln!(&mut stderr(),
|
||||
"Reducing image to {}x{} bits/pixel, {}",
|
||||
png.channels_per_pixel(),
|
||||
png.ihdr_data.bit_depth,
|
||||
png.ihdr_data.color_type);
|
||||
png.ihdr_data.color_type)
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
12
src/main.rs
12
src/main.rs
|
|
@ -5,6 +5,7 @@ extern crate regex;
|
|||
use clap::{App, Arg, ArgMatches};
|
||||
use regex::Regex;
|
||||
use std::collections::HashSet;
|
||||
use std::io::{Write, stderr};
|
||||
use std::path::PathBuf;
|
||||
|
||||
const VERSION_STRING: &'static str = "0.1.1";
|
||||
|
|
@ -223,7 +224,7 @@ fn main() {
|
|||
match parse_opts_into_struct(&matches, &mut opts) {
|
||||
Ok(_) => (),
|
||||
Err(x) => {
|
||||
println!("{}", x);
|
||||
writeln!(&mut stderr(), "{}", x).ok();
|
||||
return ();
|
||||
}
|
||||
}
|
||||
|
|
@ -244,7 +245,10 @@ fn handle_optimization(inputs: Vec<PathBuf>, opts: oxipng::Options) {
|
|||
handle_optimization(input.read_dir().unwrap().map(|x| x.unwrap().path()).collect(),
|
||||
current_opts)
|
||||
} else {
|
||||
println!("{} is a directory, skipping", input.display());
|
||||
writeln!(&mut stderr(),
|
||||
"{} is a directory, skipping",
|
||||
input.display())
|
||||
.ok();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
|
@ -255,7 +259,9 @@ fn handle_optimization(inputs: Vec<PathBuf>, opts: oxipng::Options) {
|
|||
}
|
||||
match oxipng::optimize(&input, ¤t_opts) {
|
||||
Ok(_) => (),
|
||||
Err(x) => println!("{}", x),
|
||||
Err(x) => {
|
||||
writeln!(&mut stderr(), "{}", x).ok();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
BIN
tests/files/quiet_mode.png
Normal file
BIN
tests/files/quiet_mode.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 112 KiB |
BIN
tests/files/verbose_mode.png
Normal file
BIN
tests/files/verbose_mode.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 112 KiB |
|
|
@ -33,7 +33,7 @@ fn get_opts(input: &Path) -> oxipng::Options {
|
|||
clobber: true,
|
||||
create: true,
|
||||
preserve_attrs: false,
|
||||
verbosity: Some(0),
|
||||
verbosity: None,
|
||||
filter: filter,
|
||||
interlace: None,
|
||||
compression: compression,
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ fn get_opts(input: &Path) -> oxipng::Options {
|
|||
clobber: true,
|
||||
create: true,
|
||||
preserve_attrs: false,
|
||||
verbosity: Some(0),
|
||||
verbosity: None,
|
||||
filter: filter,
|
||||
interlace: None,
|
||||
compression: compression,
|
||||
|
|
@ -49,6 +49,61 @@ fn get_opts(input: &Path) -> oxipng::Options {
|
|||
}
|
||||
}
|
||||
|
||||
fn test_it_converts(input: &Path,
|
||||
output: &Path,
|
||||
opts: &oxipng::Options,
|
||||
color_type_in: png::ColorType,
|
||||
bit_depth_in: png::BitDepth,
|
||||
color_type_out: png::ColorType,
|
||||
bit_depth_out: png::BitDepth) {
|
||||
let png = png::PngData::new(input).unwrap();
|
||||
|
||||
assert!(png.ihdr_data.color_type == color_type_in);
|
||||
assert!(png.ihdr_data.bit_depth == bit_depth_in);
|
||||
|
||||
match oxipng::optimize(input, opts) {
|
||||
Ok(_) => (),
|
||||
Err(x) => panic!(x.to_owned()),
|
||||
};
|
||||
assert!(output.exists());
|
||||
|
||||
let png = match png::PngData::new(output) {
|
||||
Ok(x) => x,
|
||||
Err(x) => {
|
||||
remove_file(output).ok();
|
||||
panic!(x.to_owned())
|
||||
}
|
||||
};
|
||||
|
||||
assert!(png.ihdr_data.color_type == color_type_out);
|
||||
assert!(png.ihdr_data.bit_depth == bit_depth_out);
|
||||
|
||||
let old_png = image::open(input).unwrap();
|
||||
let new_png = image::open(output).unwrap();
|
||||
|
||||
// Conversion should be lossless
|
||||
assert!(old_png.pixels().map(|x| x.2.channels().to_owned()).collect::<Vec<Vec<u8>>>() ==
|
||||
new_png.pixels().map(|x| x.2.channels().to_owned()).collect::<Vec<Vec<u8>>>());
|
||||
|
||||
remove_file(output).ok();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verbose_mode() {
|
||||
let input = PathBuf::from("tests/files/verbose_mode.png");
|
||||
let mut opts = get_opts(&input);
|
||||
opts.verbosity = Some(1);
|
||||
let output = opts.out_file.clone();
|
||||
|
||||
test_it_converts(&input,
|
||||
&output,
|
||||
&opts,
|
||||
png::ColorType::RGB,
|
||||
png::BitDepth::Eight,
|
||||
png::ColorType::RGB,
|
||||
png::BitDepth::Eight);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strip_headers() {
|
||||
let input = PathBuf::from("tests/files/strip_headers.png");
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ fn get_opts(input: &Path) -> oxipng::Options {
|
|||
clobber: true,
|
||||
create: true,
|
||||
preserve_attrs: false,
|
||||
verbosity: Some(0),
|
||||
verbosity: None,
|
||||
filter: filter,
|
||||
interlace: None,
|
||||
compression: compression,
|
||||
|
|
|
|||
Loading…
Reference in a new issue