Skip to content

Commit

Permalink
Print console output to stderr instead of stdout
Browse files Browse the repository at this point in the history
Makes it friendlier with --stdout option
See #20 for discussion
  • Loading branch information
Joshua Holmer committed Mar 4, 2016
1 parent 0ae632c commit 7d1e3a5
Show file tree
Hide file tree
Showing 8 changed files with 133 additions and 43 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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**
Expand Down
102 changes: 65 additions & 37 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
Expand All @@ -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();
Expand Down Expand Up @@ -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);
Expand All @@ -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()))
Expand Down Expand Up @@ -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();
}
}
}
Expand All @@ -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,
Expand Down Expand Up @@ -256,52 +270,66 @@ 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(())
}

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 changes: 9 additions & 3 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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 ();
}
}
Expand All @@ -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;
}
Expand All @@ -255,7 +259,9 @@ fn handle_optimization(inputs: Vec<PathBuf>, opts: oxipng::Options) {
}
match oxipng::optimize(&input, &current_opts) {
Ok(_) => (),
Err(x) => println!("{}", x),
Err(x) => {
writeln!(&mut stderr(), "{}", x).ok();
}
};
}
}
Expand Down
Binary file added tests/files/quiet_mode.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/files/verbose_mode.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion tests/filters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
57 changes: 56 additions & 1 deletion tests/flags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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");
Expand Down
2 changes: 1 addition & 1 deletion tests/reduction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit 7d1e3a5

Please sign in to comment.