* Update and optimize dependencies
These changes update the dependencies to their latest versions, fixing
some known issues that prevented doing so in the first place.
In addition, the direct dependency on byteorder was dropped in favor
of stdlib functions that have been stabilized for some time in Rust, and
the transitive dependency on chrono, pulled by stderrlog, was also
dropped, which had been affected by security issues and improperly
maintained in the past:
- https://github.com/cardoe/stderrlog-rs/issues/31
- https://www.reddit.com/r/rust/comments/ts84n4/chrono_or_time_03/
* Run rustfmt
* Bump MSRV to 1.56.1
Updating to this patch version should not be cumbersome for end-users,
and it is required by a transitive dependency.
* Bump MSRV to 1.57.0
os_str_bytes requires it.
* Add initial support for changing Zopfli iterations
PR https://github.com/shssoichiro/oxipng/pull/445 did some dependency
updates, which included using the latest zopfli version. The latest
version of this crate exposes new options in its API that allow users to
choose the desired number of Zopfli compression iterations, which
may greatly affect execution time. In fact, other optimizers such as
zopflipng dynamically select this number depending on the input file
size (see: https://github.com/shssoichiro/oxipng/issues/414).
As a first step towards making OxiPNG deal with Zopfli better, let's add
the necessary options for libraries to be able to choose the number of
iterations. This number is still fixed to 15 as before when using the
CLI.
* Fix Clippy lint
Co-authored-by: Josh Holmer <jholmer.in@gmail.com>
* Update and optimize dependencies
These changes update the dependencies to their latest versions, fixing
some known issues that prevented doing so in the first place.
In addition, the direct dependency on byteorder was dropped in favor
of stdlib functions that have been stabilized for some time in Rust, and
the transitive dependency on chrono, pulled by stderrlog, was also
dropped, which had been affected by security issues and improperly
maintained in the past:
- https://github.com/cardoe/stderrlog-rs/issues/31
- https://www.reddit.com/r/rust/comments/ts84n4/chrono_or_time_03/
* Run rustfmt
* Bump MSRV to 1.56.1
Updating to this patch version should not be cumbersome for end-users,
and it is required by a transitive dependency.
* Bump MSRV to 1.57.0
os_str_bytes requires it.
The for loop in apply_preset_5 was not including 9.
apply_preset_6 now builds now calls apply_preset_4 instead of
apply_preset_5, and adds all compression levels from 1 to 9.
Co-authored-by: Nino Burini <nburini@jabra.com>
For RGB(A) images that contain gray colors, this reduction can achieve
significant space savings. However, in the absence of gamma correction
data, some PNG decoders assume more exotic color spaces for grayscale
images instead of the ubiquitous sRGB. This results in gamma
miscorrection, and for the end user this means that colors will look
wrong, like "washed-out". Java's ImageIO class, which is popular in the
JVM world to read PNG files, uses rather unconventional defaults, as
explained in this StackOverflow question: https://stackoverflow.com/questions/31312645/java-imageio-grayscale-png-issue
Gamma miscorrection problems aside, OxiPNG currently tries hard to
reduce RGB(A) images to grayscale, because it expects that reduction to
be quite effective. However, in some cases, OxiPNG generates smaller
PNG files when reducing grasycale RGB(A) images to paletted color than
actual grayscale color. For example, let's say that "~/gray.png" is a
256x256 RGBA image entirely filled with (119, 119, 119, 255) pixels.
OxiPNG, by default, reduces this image to grayscale and achieves a
68.23% decrease:
$ cargo build --release && target/release/oxipng -omax --out ~/out.png ~/gray.png
Processing: /home/user/gray.png
256x256 pixels, PNG format
4x8 bits/pixel, RGBA
IDAT size = 604 bytes
File size = 661 bytes
Reducing image to 1x4 bits/pixel, Grayscale
Trying: 144 combinations
Found better combination:
zc = 6 zs = 0 f = 0 153 bytes
IDAT size = 153 bytes (451 bytes decrease)
file size = 210 bytes (451 bytes = 68.23% decrease)
Output: /home/user/out.png
However, if the --ng option that this commit adds is used to skip the
grayscale reduction step, OxiPNG reduces to a single color palette
instead, which is much more efficient, achieving a 84.42% decrease:
$ cargo build --release && target/release/oxipng -omax --ng --out ~/out.png ~/gray.png
Processing: /home/alejandro/gray.png
256x256 pixels, PNG format
4x8 bits/pixel, RGBA
IDAT size = 604 bytes
File size = 661 bytes
Reducing image to 1 bits/pixel, 1 colors in palette
Trying: 144 combinations
Found better combination:
zc = 3 zs = 3 f = 0 31 bytes
IDAT size = 31 bytes (573 bytes decrease)
file size = 103 bytes (558 bytes = 84.42% decrease)
Output: /home/alejandro/out.png
While OxiPNG should arguably be made smarter to better handle these
cases, in the meantime, adding an option to manually skip that grayscale
reduction can't hurt. In fact, it may even help users achieving the most
out of current versions of OxiPNG, and developers reasoning about what
makes a grayscale-like RGB(A) image compress better with a color
palette.
Due to the reasons stated above, this adds a simple "grayscale_reduction"
option to the Options struct, and a "no-grayscale-reduction" command
line switch, that makes OxiPNG not try this problematic grayscale
reduction on RGB(A) images.
* Switch to crossbeam-channel + rayon::spawn
* Remove thread_spawn for evaluation altogether
This allows to avoid a deadlock when there is only one Rayon thread, and doesn't sacrifice performance, since the caller of .get_result() had to always block on the iterator to be finished anyway, and all the messages are already sent from separate threads.
* Fix `verbose_mode` test
This one is easier to "fix", since we're in control of the rayon pool - just adding one extra thread to the default number to make sure we always can one root "spawn" call without stealing from the actual pool.
* Update --help to exclude -a
* Add a deprecation warning to level 4 constructor
* Initialise logger earlier
* Add warning for level > 3 for non-zlib
It's not obvious immediately that these levels don't have any effect on libdeflater and Zopfli, since they don't iterate over zlib-specific fine-tuned options.
Hence, show warning so that user knows they're getting "downgraded" to level 3.
* Add "max" level alias; more level warnings
* Update --help trial numbers for non-zlib
* Fix incorrect trial numbers
This allows to configure or compile away logging in the library from a single place in Rust apps.
For the CLI side, the usage and output remained the same, except it's now colour-coded.
Fixes#217.
Follow-up to #210.
I haven't noticed / forgotten that clap has own mechanism for conflicts between arguments, and it's probably best to use it instead of custom checks.
Rayon uses a singleton global pool.
By default it's set to a regular spawn handler with number of logical CPU cores, but it can be overridden by Rust applications to customize number of threads, spawn handlers, exit handlers and other options.
Such customization should be usually done at the app level, because if a single library initialises the global pool, then Rayon will prevent any further overrides and they will error out. This can cause conflicts between libraries or library and user code and make them impossible to use together.
Hence, I've removed the `threads` option from the `Options` struct and instead moved initialisation to the CLI part of the codebase (main.rs).
Users of the library that didn't depend on custom `threads` number can keep using it as before - they'll still get same number of threads as number of logical CPU cores, while users who need fine-tuning, can do that by customizing rayon pool themselves at the top level of the app.
Note: another alternative to keep the option could've been to use `ThreadPoolBuilder::build` + `ThreadPool::install` to use a local pool just within OxiPNG, but that would ignore any customizations made by users in top-level pool and would prevent usage on targets that require custom spawn handlers like WebAssembly. As such, I've decided to avoid it.
Co-authored-by: Josh Holmer <jholmer.in@gmail.com>
In order to avoid problems with mutable references,
the approach is to use the eval sender for non-parallel.
Computations will still be done synchronously
with the fake synchronous rayon module.
* Skip baselines earlier in the evaluator
We know that we'll throw them away anyway, so there is no point in even sending them to Sender and comparing them with others once we used them for the best candidate size.
* Simplify comparison logic