diff --git a/src/main.rs b/src/main.rs index 94c0cfc6..18b03baa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -35,13 +35,16 @@ use std::process::exit; use std::time::Duration; fn main() { + // Note: Long help descriptions should wrap at 70 characters (column 71) + // Indentation is 10 spaces, so this allows it to fit on an 80 character terminal + // Short help descriptions should ideally be no more than 54 characters let matches = Command::new("oxipng") .version(env!("CARGO_PKG_VERSION")) .author("Joshua Holmer ") - .about("Losslessly improves compression of PNG files") + .about("Losslessly improve compression of PNG files") .arg( Arg::new("files") - .help("File(s) to compress (use \"-\" for stdin)") + .help("File(s) to compress (use '-' for stdin)") .index(1) .num_args(1..) .use_value_delimiter(false) @@ -50,11 +53,31 @@ fn main() { ) .arg( Arg::new("optimization") - .help("Optimization level - Default: 2") + .help("Optimization level (0-6, or max)") + .long_help("\ +Set the optimization level preset. The default level 2 is quite fast +and provides good compression. Lower levels are faster, higher levels +provide better compression, though with increasingly diminishing +returns. + +0 => --zc 5 --fast (1 trial, determined heuristically) +1 => --zc 10 --fast (1 trial, determined heuristically) +2 => --zc 11 -f 0,1,6,7 --fast (4 fast trials, 1 main trial) +3 => --zc 11 -f 0,7,8,9 (4 trials) +4 => --zc 12 -f 0,7,8,9 (4 trials) +5 => --zc 12 -f 0,1,2,5,6,7,8,9 (8 trials) +6 => --zc 12 -f 0-9 (10 trials) +max => (stable alias for the max level) + +Manually specifying a compression option (zc, f, etc.) will override +the optimization preset, regardless of the order you write the +arguments.") .short('o') .long("opt") .value_name("level") - .value_parser(["0", "1", "2", "3", "4", "5", "6", "max"]), + .default_value("2") + .value_parser(["0", "1", "2", "3", "4", "5", "6", "max"]) + .hide_possible_values(true), ) .arg( Arg::new("backup") @@ -66,7 +89,10 @@ fn main() { ) .arg( Arg::new("recursive") - .help("Recurse into subdirectories and optimize all *.png/*.apng files") + .help("Recurse input directories, optimizing all PNG files") + .long_help("\ +When directories are given as input, traverse the directory trees and +optimize all PNG files found (files with “.png” or “.apng” extension).") .short('r') .long("recursive") .action(ArgAction::SetTrue), @@ -74,6 +100,10 @@ fn main() { .arg( Arg::new("output_dir") .help("Write output file(s) to ") + .long_help("\ +Write output file(s) to . If the directory does not exist, +it will be created. Note that this will not preserve the directory +structure of the input files when used with '--recursive'.") .long("dir") .value_name("directory") .value_parser(value_parser!(PathBuf)) @@ -99,35 +129,54 @@ fn main() { ) .arg( Arg::new("preserve") - .help("Preserve file attributes if possible") + .help("Preserve file permissions and timestamps if possible") + .long_help("\ +Preserve file permissions and timestamps if possible. + +Timestamps can only be preserved if oxipng was compiled with the +`filetime` feature.") .short('p') .long("preserve") .action(ArgAction::SetTrue), ) .arg( Arg::new("pretend") - .help("Do not write any files, only calculate compression gains") + .help("Do not write any files, only show compression results") .short('P') .long("pretend") .action(ArgAction::SetTrue), ) .arg( Arg::new("strip-safe") - .help("Strip safely-removable metadata objects") + .help("Strip safely-removable chunks, same as '--strip safe'") .short('s') .action(ArgAction::SetTrue) .conflicts_with("strip"), ) .arg( Arg::new("strip") - .help("Strip metadata objects ['safe', 'all', or comma-separated list]\nCAUTION: stripping 'all' will convert APNGs to standard PNGs") + .help("Strip metadata (safe, all, or comma-separated list)\nCAUTION: 'all' will convert APNGs to standard PNGs") + .long_help(format!("\ +Strip metadata chunks, where is one of: + +safe => Strip all non-critical chunks, except for the following: + {} +all => Strip all non-critical chunks + => Strip chunks in the comma-separated list, e.g. 'bKGD,cHRM' + +CAUTION: 'all' will convert APNGs to standard PNGs.", + StripChunks::KEEP_SAFE + .iter() + .map(|c| String::from_utf8_lossy(c)) + .collect::>() + .join(", "))) .long("strip") .value_name("mode") .conflicts_with("strip-safe"), ) .arg( Arg::new("keep") - .help("Strip all optional metadata except objects in the comma-separated list") + .help("Strip all metadata except in the comma-separated list") .long("keep") .value_name("list") .conflicts_with("strip") @@ -135,28 +184,50 @@ fn main() { ) .arg( Arg::new("alpha") - .help("Perform additional alpha optimizations") + .help("Perform additional alpha channel optimization") + .long_help("\ +Perform additional optimization on images with an alpha channel, by +altering the color values of fully transparent pixels. This is +generally recommended for better compression, but take care as this is +technically a lossy transformation and may be unsuitable for some +applications.") .short('a') .long("alpha") .action(ArgAction::SetTrue), ) .arg( Arg::new("interlace") - .help("PNG interlace type - Default: 0") + .help("Set PNG interlacing type (0, 1, keep)") + .long_help("\ +Set the PNG interlacing type, where is one of: + +0 => Remove interlacing from all images that are processed +1 => Apply Adam7 interlacing on all images that are processed +keep => Keep the existing interlacing type of each image + +Note that interlacing can add 25-50% to the size of an optimized +image. Only use it if you believe the benefits outweigh the costs for +your use case.") .short('i') .long("interlace") .value_name("type") - .value_parser(["0", "1", "keep"]), + .default_value("0") + .value_parser(["0", "1", "keep"]) + .hide_possible_values(true), ) .arg( Arg::new("scale16") .help("Forcibly reduce 16-bit images to 8-bit") + .long_help("\ +Forcibly reduce 16-bit images to 8-bit. Reduction is performed by +scaling the values, such that e.g. 0x00FF is reduced to 0x01 rather +than 0x00.") .long("scale16") .action(ArgAction::SetTrue), ) .arg( Arg::new("verbose") - .help("Run in verbose mode (use multiple times to increase verbosity)") + .help("Run in verbose mode (use twice to increase verbosity)") .short('v') .long("verbose") .action(ArgAction::Count) @@ -172,9 +243,29 @@ fn main() { ) .arg( Arg::new("filters") - .help(format!("PNG delta filters (0-{})", RowFilter::LAST)) + .help(format!("Filters to try (0-{}; see '--help' for details)", RowFilter::LAST)) + .long_help("\ +Peform compression trials with each of the given filter types. You can +specify a comma-separated list, or a range of values. E.g. '-f 0-3' is +the same as '-f 0,1,2,3'. + +PNG delta filters (apply the same filter to every line) + 0 => None (recommended to always include this filter) + 1 => Sub + 2 => Up + 3 => Average + 4 => Paeth +Heuristic strategies (try to find the best delta filter for each line) + 5 => MinSum Minimum sum of absolute differences + 6 => Entropy Highest Shannon entropy + 7 => Bigrams Lowest count of distinct bigrams + 8 => BigEnt Highest Shannon entropy of bigrams + 9 => Brute Smallest compressed size (slow) + +The default value depends on the optimization level preset.") .short('f') .long("filters") + .value_name("list") .value_parser(|x: &str| { parse_numeric_range_opts(x, 0, RowFilter::LAST) .map_err(|_| "Invalid option for filters") @@ -182,7 +273,11 @@ fn main() { ) .arg( Arg::new("fast") - .help("Use fast filter evaluation (helpful when you have more filters enabled than CPU cores)") + .help("Use fast filter evaluation") + .long_help("\ +Perform a fast compression evaluation of each enabled filter, followed +by a single main compression trial of the best result. Recommended +if you have more filters enabled than CPU cores.") .long("fast") .action(ArgAction::SetTrue), ) @@ -226,13 +321,21 @@ fn main() { ) .arg( Arg::new("no-recoding") - .help("No recoding of IDAT or other compressed chunks unless necessary") + .help("No recompression unless reductions occur") + .long_help("\ +No recompression of IDAT unless reductions occur. Recompression of +other compressed chunks (such as iCCP) will also be disabled. Note +that the combination of '--nx' and '--nz' will fully disable all +optimization.") .long("nz") .action(ArgAction::SetTrue), ) .arg( Arg::new("fix") - .help("Enable error recovery") + .help("Disable checksum validation") + .long_help("\ +Do not perform checksum validation of PNG chunks. This may allow some +files with errors to be processed successfully.") .long("fix") .action(ArgAction::SetTrue), ) @@ -244,53 +347,46 @@ fn main() { ) .arg( Arg::new("zopfli") - .help("Use the slow but stronger Zopfli compressor (recommended use is with all filters and `--fast` enabled)") + .help("Use the slow but stronger Zopfli compressor") + .long_help("\ +Use the slow but stronger Zopfli compressor. Recommended use is with +'-o max' and '--fast'. + +This option is only available if oxipng was compiled with the `zopfli` +feature.") .short('Z') .long("zopfli") .action(ArgAction::SetTrue), ) .arg( Arg::new("timeout") - .help("Maximum amount of time, in seconds, to spend on optimizations (currently of limited use due to the shift away from zlib)") + .help("Maximum amount of time to spend on optimizations") + .long_help("\ +Maximum amount of time, in seconds, to spend on optimizations. Oxipng +will check the timeout before each reduction or compression trial, and +will stop trying to optimize the file if the timeout is exceeded. Note +that this does not cut short any compression trials that are already +in progress, so it is currently of limited effectiveness for large +files or high compression levels.") .value_name("secs") .long("timeout") .value_parser(value_parser!(u64)), ) .arg( Arg::new("threads") - .help("Set number of threads to use - Default: num CPU cores") + .help("Set number of threads to use [default: num CPU cores]") + .long_help("\ +Set number of threads to use [default: num CPU cores] + +This option is only available if oxipng was compiled with the +`parallel` feature.") .long("threads") .short('t') .value_name("num") .value_parser(value_parser!(usize)), ) - .after_help( - "Optimization levels: - -o 0 => --zc 5 --fast (1 trial, determined heuristically) - -o 1 => --zc 10 --fast (1 trial, determined heuristically) - -o 2 => --zc 11 -f 0,1,6,7 --fast (1 trial, determined by fast evaluation) - -o 3 => --zc 11 -f 0,7,8,9 (4 trials) - -o 4 => --zc 12 -f 0,7,8,9 (4 trials; same as `-o 3` for zopfli) - -o 5 => --zc 12 -f 0,1,2,5,6,7,8,9 (8 trials) - -o 6 => --zc 12 -f 0-9 (10 trials) - -o max => (stable alias for the max compression) - - Manually specifying a compression option (zc, f, etc.) will override the optimization preset, - regardless of the order you write the arguments. - -PNG delta filters: - 0 => None - 1 => Sub - 2 => Up - 3 => Average - 4 => Paeth -Heuristic filter selection strategies: - 5 => MinSum Minimum sum of absolute differences - 6 => Entropy Highest Shannon entropy - 7 => Bigrams Lowest count of distinct bigrams - 8 => BigEnt Highest Shannon entropy of bigrams - 9 => Brute Smallest compressed size (slow)", - ) + .after_help("Run `oxipng --help` to see full details of all options") + .after_long_help("") .get_matches_from(std::env::args()); if matches.get_flag("backup") {