Merge branch 'master' into libdeflate

This commit is contained in:
andrews05 2023-09-27 08:53:44 +13:00 committed by GitHub
commit 7c6b6152b5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 91 additions and 117 deletions

View file

@ -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();

View file

@ -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");

View file

@ -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(

View file

@ -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| {

View file

@ -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(

View file

@ -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(

View file

@ -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());

View file

@ -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(

View file

@ -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,

View file

@ -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(