Merge branch 'master' into libdeflate
This commit is contained in:
commit
7c6b6152b5
10 changed files with 91 additions and 117 deletions
96
src/lib.rs
96
src/lib.rs
|
|
@ -34,7 +34,7 @@ use log::{debug, info, trace, warn};
|
|||
use rayon::prelude::*;
|
||||
use std::borrow::Cow;
|
||||
use std::fmt;
|
||||
use std::fs::{copy, File, Metadata};
|
||||
use std::fs::{File, Metadata};
|
||||
use std::io::{stdin, stdout, BufWriter, Read, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
|
@ -76,15 +76,36 @@ pub mod internal_tests {
|
|||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum OutFile {
|
||||
/// Path(None) means same as input
|
||||
Path(Option<PathBuf>),
|
||||
/// Don't actually write any output, just calculate the best results.
|
||||
None,
|
||||
/// Write output to a file.
|
||||
///
|
||||
/// * `path`: Path to write the output file. `None` means same as input.
|
||||
/// * `preserve_attrs`: Ensure the output file has the same permissions & timestamps as the input file.
|
||||
Path {
|
||||
path: Option<PathBuf>,
|
||||
preserve_attrs: bool,
|
||||
},
|
||||
/// Write to standard output.
|
||||
StdOut,
|
||||
}
|
||||
|
||||
impl OutFile {
|
||||
/// Construct a new `OutFile` with the given path.
|
||||
///
|
||||
/// This is a convenience method for `OutFile::Path { path: Some(path), preserve_attrs: false }`.
|
||||
pub fn from_path(path: PathBuf) -> Self {
|
||||
OutFile::Path {
|
||||
path: Some(path),
|
||||
preserve_attrs: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn path(&self) -> Option<&Path> {
|
||||
match *self {
|
||||
OutFile::Path(Some(ref p)) => Some(p.as_path()),
|
||||
OutFile::Path {
|
||||
path: Some(ref p), ..
|
||||
} => Some(p.as_path()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
|
@ -126,30 +147,14 @@ pub type PngResult<T> = Result<T, PngError>;
|
|||
#[derive(Clone, Debug)]
|
||||
/// Options controlling the output of the `optimize` function
|
||||
pub struct Options {
|
||||
/// Whether the input file should be backed up before writing the output.
|
||||
///
|
||||
/// Default: `false`
|
||||
pub backup: bool,
|
||||
/// Attempt to fix errors when decoding the input file rather than returning an `Err`.
|
||||
///
|
||||
/// Default: `false`
|
||||
pub fix_errors: bool,
|
||||
/// Don't actually run any optimizations, just parse the PNG file.
|
||||
///
|
||||
/// Default: `false`
|
||||
pub check: bool,
|
||||
/// Don't actually write any output, just calculate the best results.
|
||||
///
|
||||
/// Default: `false`
|
||||
pub pretend: bool,
|
||||
/// Write to output even if there was no improvement in compression.
|
||||
///
|
||||
/// Default: `false`
|
||||
pub force: bool,
|
||||
/// Ensure the output file has the same permissions as the input file.
|
||||
///
|
||||
/// Default: `false`
|
||||
pub preserve_attrs: bool,
|
||||
/// Which RowFilters to try on the file
|
||||
///
|
||||
/// Default: `None,Sub,Entropy,Bigrams`
|
||||
|
|
@ -294,12 +299,8 @@ impl Default for Options {
|
|||
fn default() -> Options {
|
||||
// Default settings based on -o 2 from the CLI interface
|
||||
Options {
|
||||
backup: false,
|
||||
check: false,
|
||||
pretend: false,
|
||||
fix_errors: false,
|
||||
force: false,
|
||||
preserve_attrs: false,
|
||||
filter: indexset! {RowFilter::None, RowFilter::Sub, RowFilter::Entropy, RowFilter::Bigrams},
|
||||
interlace: Some(Interlacing::None),
|
||||
optimize_alpha: false,
|
||||
|
|
@ -416,7 +417,13 @@ pub fn optimize(input: &InFile, output: &OutFile, opts: &Options) -> PngResult<(
|
|||
let opt_metadata_preserved;
|
||||
let in_data = match *input {
|
||||
InFile::Path(ref input_path) => {
|
||||
if opts.preserve_attrs {
|
||||
if matches!(
|
||||
output,
|
||||
OutFile::Path {
|
||||
preserve_attrs: true,
|
||||
..
|
||||
}
|
||||
) {
|
||||
opt_metadata_preserved = input_path
|
||||
.metadata()
|
||||
.map_err(|err| {
|
||||
|
|
@ -445,11 +452,6 @@ pub fn optimize(input: &InFile, output: &OutFile, opts: &Options) -> PngResult<(
|
|||
|
||||
let mut png = PngData::from_slice(&in_data, opts)?;
|
||||
|
||||
if opts.check {
|
||||
info!("Running in check mode, not optimizing");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Run the optimizer on the decoded PNG.
|
||||
let mut optimized_output = optimize_png(&mut png, &in_data, opts, deadline)?;
|
||||
|
||||
|
|
@ -458,8 +460,8 @@ pub fn optimize(input: &InFile, output: &OutFile, opts: &Options) -> PngResult<(
|
|||
if is_fully_optimized(in_data.len(), optimized_output.len(), opts) {
|
||||
match (output, input) {
|
||||
// if p is None, it also means same as the input path
|
||||
(OutFile::Path(ref p), InFile::Path(ref input_path))
|
||||
if p.as_ref().map_or(true, |p| p == input_path) =>
|
||||
(OutFile::Path { path, .. }, InFile::Path(ref input_path))
|
||||
if path.as_ref().map_or(true, |p| p == input_path) =>
|
||||
{
|
||||
info!("{}: Could not optimize further, no change written", input);
|
||||
return Ok(());
|
||||
|
|
@ -484,26 +486,21 @@ pub fn optimize(input: &InFile, output: &OutFile, opts: &Options) -> PngResult<(
|
|||
)
|
||||
};
|
||||
|
||||
if opts.pretend {
|
||||
info!("{}: Running in pretend mode, no output", savings);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
match (output, input) {
|
||||
(&OutFile::StdOut, _) | (&OutFile::Path(None), &InFile::StdIn) => {
|
||||
(OutFile::None, _) => {
|
||||
info!("{}: Running in pretend mode, no output", savings);
|
||||
}
|
||||
(&OutFile::StdOut, _) | (&OutFile::Path { path: None, .. }, &InFile::StdIn) => {
|
||||
let mut buffer = BufWriter::new(stdout());
|
||||
buffer
|
||||
.write_all(&optimized_output)
|
||||
.map_err(|e| PngError::new(&format!("Unable to write to stdout: {}", e)))?;
|
||||
}
|
||||
(OutFile::Path(ref output_path), _) => {
|
||||
let output_path = output_path
|
||||
(OutFile::Path { path, .. }, _) => {
|
||||
let output_path = path
|
||||
.as_ref()
|
||||
.map(|p| p.as_path())
|
||||
.unwrap_or_else(|| input.path().unwrap());
|
||||
if opts.backup {
|
||||
perform_backup(output_path)?;
|
||||
}
|
||||
let out_file = File::create(output_path).map_err(|err| {
|
||||
PngError::new(&format!(
|
||||
"Unable to write to file {}: {}",
|
||||
|
|
@ -983,19 +980,6 @@ fn is_fully_optimized(original_size: usize, optimized_size: usize, opts: &Option
|
|||
original_size <= optimized_size && !opts.force
|
||||
}
|
||||
|
||||
fn perform_backup(input_path: &Path) -> PngResult<()> {
|
||||
let backup_file = input_path.with_extension(format!(
|
||||
"bak.{}",
|
||||
input_path.extension().unwrap().to_str().unwrap()
|
||||
));
|
||||
copy(input_path, &backup_file).map(|_| ()).map_err(|_| {
|
||||
PngError::new(&format!(
|
||||
"Unable to write to backup file at {}",
|
||||
backup_file.display()
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
fn copy_permissions(metadata_input: &Metadata, out_file: &File) -> PngResult<()> {
|
||||
let readonly_input = metadata_input.permissions().readonly();
|
||||
|
|
|
|||
63
src/main.rs
63
src/main.rs
|
|
@ -25,6 +25,7 @@ use oxipng::RowFilter;
|
|||
use oxipng::StripChunks;
|
||||
use oxipng::{InFile, OutFile};
|
||||
use rayon::prelude::*;
|
||||
use std::ffi::OsString;
|
||||
use std::fs::DirBuilder;
|
||||
use std::io::Write;
|
||||
#[cfg(feature = "zopfli")]
|
||||
|
|
@ -60,11 +61,12 @@ fn main() {
|
|||
.help("Back up modified files")
|
||||
.short('b')
|
||||
.long("backup")
|
||||
.hide(true)
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("recursive")
|
||||
.help("Recurse into subdirectories")
|
||||
.help("Recurse into subdirectories and optimize all *.png/*.apng files")
|
||||
.short('r')
|
||||
.long("recursive")
|
||||
.action(ArgAction::SetTrue),
|
||||
|
|
@ -102,13 +104,6 @@ fn main() {
|
|||
.long("preserve")
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("check")
|
||||
.help("Do not run any optimization passes")
|
||||
.short('c')
|
||||
.long("check")
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("pretend")
|
||||
.help("Do not write any files, only calculate compression gains")
|
||||
|
|
@ -256,7 +251,7 @@ fn main() {
|
|||
)
|
||||
.arg(
|
||||
Arg::new("timeout")
|
||||
.help("Maximum amount of time, in seconds, to spend on optimizations")
|
||||
.help("Maximum amount of time, in seconds, to spend on optimizations (currently of limited use due to the shift away from zlib)")
|
||||
.value_name("secs")
|
||||
.long("timeout")
|
||||
.value_parser(value_parser!(u64)),
|
||||
|
|
@ -298,6 +293,11 @@ Heuristic filter selection strategies:
|
|||
)
|
||||
.get_matches_from(std::env::args());
|
||||
|
||||
if matches.get_flag("backup") {
|
||||
eprintln!("The --backup flag is no longer supported. Please use --out or --dir to preserve your existing files.");
|
||||
exit(1)
|
||||
}
|
||||
|
||||
let (out_file, out_dir, opts) = match parse_opts_into_struct(&matches) {
|
||||
Ok(x) => x,
|
||||
Err(x) => {
|
||||
|
|
@ -353,10 +353,10 @@ fn collect_files(
|
|||
out_dir: &Option<PathBuf>,
|
||||
out_file: &OutFile,
|
||||
recursive: bool,
|
||||
allow_stdin: bool,
|
||||
top_level: bool, //explicitly specify files
|
||||
) -> Vec<(InFile, OutFile)> {
|
||||
let mut in_out_pairs = Vec::new();
|
||||
let allow_stdin = allow_stdin && files.len() == 1;
|
||||
let allow_stdin = top_level && files.len() == 1;
|
||||
for input in files {
|
||||
let using_stdin = allow_stdin && input.to_str().map_or(false, |p| p == "-");
|
||||
if !using_stdin && input.is_dir() {
|
||||
|
|
@ -376,15 +376,27 @@ fn collect_files(
|
|||
}
|
||||
continue;
|
||||
};
|
||||
let out_file = if let Some(ref out_dir) = *out_dir {
|
||||
let out_path = Some(out_dir.join(input.file_name().unwrap()));
|
||||
OutFile::Path(out_path)
|
||||
} else {
|
||||
(*out_file).clone()
|
||||
};
|
||||
let out_file =
|
||||
if let (Some(out_dir), &OutFile::Path { preserve_attrs, .. }) = (out_dir, out_file) {
|
||||
let path = Some(out_dir.join(input.file_name().unwrap()));
|
||||
OutFile::Path {
|
||||
path,
|
||||
preserve_attrs,
|
||||
}
|
||||
} else {
|
||||
(*out_file).clone()
|
||||
};
|
||||
let in_file = if using_stdin {
|
||||
InFile::StdIn
|
||||
} else {
|
||||
// Skip non png files if not given on top level
|
||||
if !top_level && {
|
||||
let extension = input.extension().map(|f| f.to_ascii_lowercase());
|
||||
extension != Some(OsString::from("png"))
|
||||
&& extension != Some(OsString::from("apng"))
|
||||
} {
|
||||
continue;
|
||||
}
|
||||
InFile::Path(input)
|
||||
};
|
||||
in_out_pairs.push((in_file, out_file));
|
||||
|
|
@ -459,10 +471,15 @@ fn parse_opts_into_struct(
|
|||
None
|
||||
};
|
||||
|
||||
let out_file = if matches.get_flag("stdout") {
|
||||
let out_file = if matches.get_flag("pretend") {
|
||||
OutFile::None
|
||||
} else if matches.get_flag("stdout") {
|
||||
OutFile::StdOut
|
||||
} else {
|
||||
OutFile::Path(matches.get_one::<PathBuf>("output_file").cloned())
|
||||
OutFile::Path {
|
||||
path: matches.get_one::<PathBuf>("output_file").cloned(),
|
||||
preserve_attrs: matches.get_flag("preserve"),
|
||||
}
|
||||
};
|
||||
|
||||
opts.optimize_alpha = matches.get_flag("alpha");
|
||||
|
|
@ -474,18 +491,10 @@ fn parse_opts_into_struct(
|
|||
opts.fast_evaluation = matches.get_flag("fast");
|
||||
}
|
||||
|
||||
opts.backup = matches.get_flag("backup");
|
||||
|
||||
opts.force = matches.get_flag("force");
|
||||
|
||||
opts.fix_errors = matches.get_flag("fix");
|
||||
|
||||
opts.check = matches.get_flag("check");
|
||||
|
||||
opts.pretend = matches.get_flag("pretend");
|
||||
|
||||
opts.preserve_attrs = matches.get_flag("preserve");
|
||||
|
||||
opts.bit_depth_reduction = !matches.get_flag("no-bit-reduction");
|
||||
|
||||
opts.color_type_reduction = !matches.get_flag("no-color-reduction");
|
||||
|
|
|
|||
|
|
@ -20,10 +20,7 @@ fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
|
|||
filter.insert(RowFilter::None);
|
||||
options.filter = filter;
|
||||
|
||||
(
|
||||
OutFile::Path(Some(input.with_extension("out.png"))),
|
||||
options,
|
||||
)
|
||||
(OutFile::from_path(input.with_extension("out.png")), options)
|
||||
}
|
||||
|
||||
fn test_it_converts(
|
||||
|
|
|
|||
|
|
@ -26,10 +26,7 @@ fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
|
|||
filter.insert(RowFilter::None);
|
||||
options.filter = filter;
|
||||
|
||||
(
|
||||
OutFile::Path(Some(input.with_extension("out.png"))),
|
||||
options,
|
||||
)
|
||||
(OutFile::from_path(input.with_extension("out.png")), options)
|
||||
}
|
||||
|
||||
/// Add callback to allow checks before the output file is deleted again
|
||||
|
|
@ -518,8 +515,10 @@ fn preserve_attrs() {
|
|||
#[cfg(feature = "filetime")]
|
||||
let mtime_canon = RefCell::new(filetime::FileTime::from_unix_time(0, 0));
|
||||
|
||||
let (output, mut opts) = get_opts(&input);
|
||||
opts.preserve_attrs = true;
|
||||
let (mut output, opts) = get_opts(&input);
|
||||
if let OutFile::Path { preserve_attrs, .. } = &mut output {
|
||||
*preserve_attrs = true;
|
||||
}
|
||||
|
||||
#[cfg(feature = "filetime")]
|
||||
let callback_pre = |path_in: &Path| {
|
||||
|
|
|
|||
|
|
@ -22,10 +22,7 @@ fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
|
|||
filter.insert(RowFilter::None);
|
||||
options.filter = filter;
|
||||
|
||||
(
|
||||
OutFile::Path(Some(input.with_extension("out.png"))),
|
||||
options,
|
||||
)
|
||||
(OutFile::from_path(input.with_extension("out.png")), options)
|
||||
}
|
||||
|
||||
fn test_it_converts(
|
||||
|
|
|
|||
|
|
@ -17,10 +17,7 @@ fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
|
|||
filter.insert(RowFilter::None);
|
||||
options.filter = filter;
|
||||
|
||||
(
|
||||
OutFile::Path(Some(input.with_extension("out.png"))),
|
||||
options,
|
||||
)
|
||||
(OutFile::from_path(input.with_extension("out.png")), options)
|
||||
}
|
||||
|
||||
fn test_it_converts(
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ fn optimize_from_memory_apng() {
|
|||
fn optimize() {
|
||||
let result = oxipng::optimize(
|
||||
&"tests/files/fully_optimized.png".into(),
|
||||
&OutFile::Path(None),
|
||||
&OutFile::None,
|
||||
&Options::default(),
|
||||
);
|
||||
assert!(result.is_ok());
|
||||
|
|
@ -47,7 +47,7 @@ fn optimize() {
|
|||
fn optimize_corrupted() {
|
||||
let result = oxipng::optimize(
|
||||
&"tests/files/corrupted_header.png".into(),
|
||||
&OutFile::Path(None),
|
||||
&OutFile::None,
|
||||
&Options::default(),
|
||||
);
|
||||
assert!(result.is_err());
|
||||
|
|
@ -57,7 +57,7 @@ fn optimize_corrupted() {
|
|||
fn optimize_apng() {
|
||||
let result = oxipng::optimize(
|
||||
&"tests/files/apng_file.png".into(),
|
||||
&OutFile::Path(None),
|
||||
&OutFile::None,
|
||||
&Options::from_preset(0),
|
||||
);
|
||||
assert!(result.is_ok());
|
||||
|
|
|
|||
|
|
@ -21,10 +21,7 @@ fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
|
|||
filter.insert(RowFilter::None);
|
||||
options.filter = filter;
|
||||
|
||||
(
|
||||
OutFile::Path(Some(input.with_extension("out.png"))),
|
||||
options,
|
||||
)
|
||||
(OutFile::from_path(input.with_extension("out.png")), options)
|
||||
}
|
||||
|
||||
fn test_it_converts(
|
||||
|
|
|
|||
|
|
@ -20,10 +20,7 @@ fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
|
|||
filter.insert(RowFilter::None);
|
||||
options.filter = filter;
|
||||
|
||||
(
|
||||
OutFile::Path(Some(input.with_extension("out.png"))),
|
||||
options,
|
||||
)
|
||||
(OutFile::from_path(input.with_extension("out.png")), options)
|
||||
}
|
||||
|
||||
fn test_it_converts(
|
||||
|
|
@ -295,7 +292,7 @@ fn issue_92_filter_5() {
|
|||
let input = "tests/files/issue-92.png";
|
||||
let (_, mut opts) = get_opts(Path::new(input));
|
||||
opts.filter = [RowFilter::MinSum].iter().cloned().collect();
|
||||
let output = OutFile::Path(Some(Path::new(input).with_extension("-f5-out.png")));
|
||||
let output = OutFile::from_path(Path::new(input).with_extension("-f5-out.png"));
|
||||
|
||||
test_it_converts(
|
||||
input,
|
||||
|
|
|
|||
|
|
@ -19,10 +19,7 @@ fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
|
|||
filter.insert(RowFilter::None);
|
||||
options.filter = filter;
|
||||
|
||||
(
|
||||
OutFile::Path(Some(input.with_extension("out.png"))),
|
||||
options,
|
||||
)
|
||||
(OutFile::from_path(input.with_extension("out.png")), options)
|
||||
}
|
||||
|
||||
fn test_it_converts(
|
||||
|
|
|
|||
Loading…
Reference in a new issue