Move options into separate mod

This commit is contained in:
Andrew 2023-11-18 19:42:35 +13:00
parent 97af04a539
commit 8088b1d20a
2 changed files with 255 additions and 244 deletions

View file

@ -34,10 +34,9 @@ use crate::reduction::*;
use log::{debug, info, trace, warn}; use log::{debug, info, trace, warn};
use rayon::prelude::*; use rayon::prelude::*;
use std::borrow::Cow; use std::borrow::Cow;
use std::fmt;
use std::fs::{File, Metadata}; use std::fs::{File, Metadata};
use std::io::{stdin, stdout, BufWriter, Read, Write}; use std::io::{stdin, stdout, BufWriter, Read, Write};
use std::path::{Path, PathBuf}; use std::path::Path;
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc; use std::sync::Arc;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
@ -48,6 +47,7 @@ pub use crate::error::PngError;
pub use crate::filters::RowFilter; pub use crate::filters::RowFilter;
pub use crate::headers::StripChunks; pub use crate::headers::StripChunks;
pub use crate::interlace::Interlacing; pub use crate::interlace::Interlacing;
pub use crate::options::{Options, OutFile, InFile};
pub use indexmap::{indexset, IndexSet}; pub use indexmap::{indexset, IndexSet};
pub use rgb::{RGB16, RGBA8}; pub use rgb::{RGB16, RGBA8};
@ -59,6 +59,7 @@ mod evaluate;
mod filters; mod filters;
mod headers; mod headers;
mod interlace; mod interlace;
mod options;
mod png; mod png;
mod reduction; mod reduction;
#[cfg(feature = "sanity-checks")] #[cfg(feature = "sanity-checks")]
@ -75,250 +76,8 @@ pub mod internal_tests {
pub use crate::sanity_checks::*; pub use crate::sanity_checks::*;
} }
#[derive(Clone, Debug)]
pub enum OutFile {
/// 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 {
path: Some(ref p), ..
} => Some(p.as_path()),
_ => None,
}
}
}
/// Where to read images from
#[derive(Clone, Debug)]
pub enum InFile {
Path(PathBuf),
StdIn,
}
impl InFile {
pub fn path(&self) -> Option<&Path> {
match *self {
InFile::Path(ref p) => Some(p.as_path()),
InFile::StdIn => None,
}
}
}
impl fmt::Display for InFile {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
InFile::Path(ref p) => write!(f, "{}", p.display()),
InFile::StdIn => f.write_str("stdin"),
}
}
}
impl<T: Into<PathBuf>> From<T> for InFile {
fn from(s: T) -> Self {
InFile::Path(s.into())
}
}
pub type PngResult<T> = Result<T, PngError>; pub type PngResult<T> = Result<T, PngError>;
#[derive(Clone, Debug)]
/// Options controlling the output of the `optimize` function
pub struct Options {
/// Attempt to fix errors when decoding the input file rather than returning an `Err`.
///
/// Default: `false`
pub fix_errors: bool,
/// Write to output even if there was no improvement in compression.
///
/// Default: `false`
pub force: bool,
/// Which RowFilters to try on the file
///
/// Default: `None,Sub,Entropy,Bigrams`
pub filter: IndexSet<RowFilter>,
/// Whether to change the interlacing type of the file.
///
/// `None` will not change the current interlacing type.
///
/// `Some(x)` will change the file to interlacing mode `x`.
///
/// Default: `Some(Interlacing::None)`
pub interlace: Option<Interlacing>,
/// Whether to allow transparent pixels to be altered to improve compression.
pub optimize_alpha: bool,
/// Whether to attempt bit depth reduction
///
/// Default: `true`
pub bit_depth_reduction: bool,
/// Whether to attempt color type reduction
///
/// Default: `true`
pub color_type_reduction: bool,
/// Whether to attempt palette reduction
///
/// Default: `true`
pub palette_reduction: bool,
/// Whether to attempt grayscale reduction
///
/// Default: `true`
pub grayscale_reduction: bool,
/// Whether to perform recoding of IDAT and other compressed chunks
///
/// If any type of reduction is performed, IDAT recoding will be performed
/// regardless of this setting
///
/// Default: `true`
pub idat_recoding: bool,
/// Whether to forcibly reduce 16-bit to 8-bit by scaling
///
/// Default: `false`
pub scale_16: bool,
/// Which chunks to strip from the PNG file, if any
///
/// Default: `None`
pub strip: StripChunks,
/// Which DEFLATE algorithm to use
///
/// Default: `Libdeflater`
pub deflate: Deflaters,
/// Whether to use fast evaluation to pick the best filter
///
/// Default: `true`
pub fast_evaluation: bool,
/// Maximum amount of time to spend on optimizations.
/// Further potential optimizations are skipped if the timeout is exceeded.
pub timeout: Option<Duration>,
}
impl Options {
pub fn from_preset(level: u8) -> Options {
let opts = Options::default();
match level {
0 => opts.apply_preset_0(),
1 => opts.apply_preset_1(),
2 => opts.apply_preset_2(),
3 => opts.apply_preset_3(),
4 => opts.apply_preset_4(),
5 => opts.apply_preset_5(),
6 => opts.apply_preset_6(),
_ => {
warn!("Level 7 and above don't exist yet and are identical to level 6");
opts.apply_preset_6()
}
}
}
pub fn max_compression() -> Options {
Options::from_preset(6)
}
// The following methods make assumptions that they are operating
// on an `Options` struct generated by the `default` method.
fn apply_preset_0(mut self) -> Self {
self.filter.clear();
if let Deflaters::Libdeflater { compression } = &mut self.deflate {
*compression = 5;
}
self
}
fn apply_preset_1(mut self) -> Self {
self.filter.clear();
if let Deflaters::Libdeflater { compression } = &mut self.deflate {
*compression = 10;
}
self
}
fn apply_preset_2(self) -> Self {
self
}
fn apply_preset_3(mut self) -> Self {
self.fast_evaluation = false;
self.filter = indexset! {
RowFilter::None,
RowFilter::Bigrams,
RowFilter::BigEnt,
RowFilter::Brute
};
self
}
fn apply_preset_4(mut self) -> Self {
if let Deflaters::Libdeflater { compression } = &mut self.deflate {
*compression = 12;
}
self.apply_preset_3()
}
fn apply_preset_5(mut self) -> Self {
self.fast_evaluation = false;
self.filter.insert(RowFilter::Up);
self.filter.insert(RowFilter::MinSum);
self.filter.insert(RowFilter::BigEnt);
self.filter.insert(RowFilter::Brute);
if let Deflaters::Libdeflater { compression } = &mut self.deflate {
*compression = 12;
}
self
}
fn apply_preset_6(mut self) -> Self {
self.filter.insert(RowFilter::Average);
self.filter.insert(RowFilter::Paeth);
self.apply_preset_5()
}
}
impl Default for Options {
fn default() -> Options {
// Default settings based on -o 2 from the CLI interface
Options {
fix_errors: false,
force: false,
filter: indexset! {RowFilter::None, RowFilter::Sub, RowFilter::Entropy, RowFilter::Bigrams},
interlace: Some(Interlacing::None),
optimize_alpha: false,
bit_depth_reduction: true,
color_type_reduction: true,
palette_reduction: true,
grayscale_reduction: true,
idat_recoding: true,
scale_16: false,
strip: StripChunks::None,
deflate: Deflaters::Libdeflater { compression: 11 },
fast_evaluation: true,
timeout: None,
}
}
}
#[derive(Debug)] #[derive(Debug)]
/// A raw image definition which can be used to create an optimized png /// A raw image definition which can be used to create an optimized png
pub struct RawImage { pub struct RawImage {

252
src/options.rs Normal file
View file

@ -0,0 +1,252 @@
use log::warn;
use std::fmt;
use std::path::{Path, PathBuf};
use std::time::Duration;
use crate::deflate::Deflaters;
use crate::filters::RowFilter;
use crate::headers::StripChunks;
use crate::interlace::Interlacing;
use indexmap::{indexset, IndexSet};
#[derive(Clone, Debug)]
pub enum OutFile {
/// 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 {
path: Some(ref p), ..
} => Some(p.as_path()),
_ => None,
}
}
}
/// Where to read images from
#[derive(Clone, Debug)]
pub enum InFile {
Path(PathBuf),
StdIn,
}
impl InFile {
pub fn path(&self) -> Option<&Path> {
match *self {
InFile::Path(ref p) => Some(p.as_path()),
InFile::StdIn => None,
}
}
}
impl fmt::Display for InFile {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
InFile::Path(ref p) => write!(f, "{}", p.display()),
InFile::StdIn => f.write_str("stdin"),
}
}
}
impl<T: Into<PathBuf>> From<T> for InFile {
fn from(s: T) -> Self {
InFile::Path(s.into())
}
}
#[derive(Clone, Debug)]
/// Options controlling the output of the `optimize` function
pub struct Options {
/// Attempt to fix errors when decoding the input file rather than returning an `Err`.
///
/// Default: `false`
pub fix_errors: bool,
/// Write to output even if there was no improvement in compression.
///
/// Default: `false`
pub force: bool,
/// Which RowFilters to try on the file
///
/// Default: `None,Sub,Entropy,Bigrams`
pub filter: IndexSet<RowFilter>,
/// Whether to change the interlacing type of the file.
///
/// `None` will not change the current interlacing type.
///
/// `Some(x)` will change the file to interlacing mode `x`.
///
/// Default: `Some(Interlacing::None)`
pub interlace: Option<Interlacing>,
/// Whether to allow transparent pixels to be altered to improve compression.
pub optimize_alpha: bool,
/// Whether to attempt bit depth reduction
///
/// Default: `true`
pub bit_depth_reduction: bool,
/// Whether to attempt color type reduction
///
/// Default: `true`
pub color_type_reduction: bool,
/// Whether to attempt palette reduction
///
/// Default: `true`
pub palette_reduction: bool,
/// Whether to attempt grayscale reduction
///
/// Default: `true`
pub grayscale_reduction: bool,
/// Whether to perform recoding of IDAT and other compressed chunks
///
/// If any type of reduction is performed, IDAT recoding will be performed
/// regardless of this setting
///
/// Default: `true`
pub idat_recoding: bool,
/// Whether to forcibly reduce 16-bit to 8-bit by scaling
///
/// Default: `false`
pub scale_16: bool,
/// Which chunks to strip from the PNG file, if any
///
/// Default: `None`
pub strip: StripChunks,
/// Which DEFLATE algorithm to use
///
/// Default: `Libdeflater`
pub deflate: Deflaters,
/// Whether to use fast evaluation to pick the best filter
///
/// Default: `true`
pub fast_evaluation: bool,
/// Maximum amount of time to spend on optimizations.
/// Further potential optimizations are skipped if the timeout is exceeded.
pub timeout: Option<Duration>,
}
impl Options {
pub fn from_preset(level: u8) -> Options {
let opts = Options::default();
match level {
0 => opts.apply_preset_0(),
1 => opts.apply_preset_1(),
2 => opts.apply_preset_2(),
3 => opts.apply_preset_3(),
4 => opts.apply_preset_4(),
5 => opts.apply_preset_5(),
6 => opts.apply_preset_6(),
_ => {
warn!("Level 7 and above don't exist yet and are identical to level 6");
opts.apply_preset_6()
}
}
}
pub fn max_compression() -> Options {
Options::from_preset(6)
}
// The following methods make assumptions that they are operating
// on an `Options` struct generated by the `default` method.
fn apply_preset_0(mut self) -> Self {
self.filter.clear();
if let Deflaters::Libdeflater { compression } = &mut self.deflate {
*compression = 5;
}
self
}
fn apply_preset_1(mut self) -> Self {
self.filter.clear();
if let Deflaters::Libdeflater { compression } = &mut self.deflate {
*compression = 10;
}
self
}
fn apply_preset_2(self) -> Self {
self
}
fn apply_preset_3(mut self) -> Self {
self.fast_evaluation = false;
self.filter = indexset! {
RowFilter::None,
RowFilter::Bigrams,
RowFilter::BigEnt,
RowFilter::Brute
};
self
}
fn apply_preset_4(mut self) -> Self {
if let Deflaters::Libdeflater { compression } = &mut self.deflate {
*compression = 12;
}
self.apply_preset_3()
}
fn apply_preset_5(mut self) -> Self {
self.fast_evaluation = false;
self.filter.insert(RowFilter::Up);
self.filter.insert(RowFilter::MinSum);
self.filter.insert(RowFilter::BigEnt);
self.filter.insert(RowFilter::Brute);
if let Deflaters::Libdeflater { compression } = &mut self.deflate {
*compression = 12;
}
self
}
fn apply_preset_6(mut self) -> Self {
self.filter.insert(RowFilter::Average);
self.filter.insert(RowFilter::Paeth);
self.apply_preset_5()
}
}
impl Default for Options {
fn default() -> Options {
// Default settings based on -o 2 from the CLI interface
Options {
fix_errors: false,
force: false,
filter: indexset! {RowFilter::None, RowFilter::Sub, RowFilter::Entropy, RowFilter::Bigrams},
interlace: Some(Interlacing::None),
optimize_alpha: false,
bit_depth_reduction: true,
color_type_reduction: true,
palette_reduction: true,
grayscale_reduction: true,
idat_recoding: true,
scale_16: false,
strip: StripChunks::None,
deflate: Deflaters::Libdeflater { compression: 11 },
fast_evaluation: true,
timeout: None,
}
}
}