* Fix bKGD conversion from gray to palette
* Allow grayscale reduction from 16 to 4 or less
* Refactor reduction evaluation sequence
* Separate palette into new file
* Refactor ColorType
Move transparency and palette data into the ColorType
* Fixup tests
* Make more use of helper functions
* Change BitDepth to u8 representation with TryFrom
* Fix clippy lints
* Don't use unstable language features
* Restore documentation on transparency/palette
* Make dependency on `image` optional
After PR https://github.com/shssoichiro/oxipng/pull/481 was merged, the
`image` dependency became unused when building with debug assertions
disabled, as it is only used to implement output sanity checks when such
assertions are enabled.
The `image` crate transitively pulls a significant amount of
dependencies, so it's useful for OxiPNG users to get rid of them when
not needed.
[Cargo does not allow specifying dependencies that are only pulled when
debug assertions are
enabled](https://github.com/rust-lang/cargo/issues/7634), so the next
best way to give users some flexibility is to gate those debug
assertions behind a feature flag.
These changes add a `sanity-checks` feature flag that controls whether
the `image` crate and the related sanity checks are compiled in. This
feature is enabled by default to keep debug builds useful to catch
problems during development.
* Fix Clippy lints
* Run tests with new sanity-checks feature enabled
* Refactor filters as enum
* Include filter byte in filter output
* Add entropy filter
* Add bigrams filter
* Add bigram entropy filter
* Add brute filter
* Replace bit-vec
* Add tests and benches
* Show filters in help
* Use FxHasher in color to palette
* Use windows function for minor improvement
* Switch main compressor to libdeflate
* Use libdeflater in evaluate
* Use libdeflater to inflate
* Use libdeflater crc
* Tidy up
* Fix benches
* Allow libdeflater/freestanding feature
* Fix building without zopfli
* 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.
* 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.
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>
* Improve performance consistency
Switch from HashMap / HashSet to IndexMap / IndexSet for consistent iteration order of various options and, as a result, more predictable performance.
libdeflater is a Rust wrapper around
[libdeflate](https://github.com/ebiggers/libdeflate) - an alternative
heavily optimised library for deflate/zlib/gzip compression and
decompression that is intended for situations where upper bounds of the
output are well-known.
In my benchmarks on test files in the repo it has shown to be usually
both slightly faster and providing better compressed output than
cloudflare-zlib, but in some cases showing the opposite, so rather
than swapping defaults, it's currently provided as another option,
similarly to zopfli.
Since it's not strictly better in all cases, I'm not providing median
numbers, but you can check distribution histograms for time and size
differences here (all using `oxipng -o 6 -t 6 -P`):
https://docs.google.com/spreadsheets/d/1WOKgeYZBhLkQvMGAC36snN4azilElzOFhx63RJu0EZY/edit?usp=sharing
* Avoid using time API when we don't need it
This avoids a syscall to the time API when the result is ignored later anyway.
This allows to use the library with default options on wasm32-unknown-unknown, where the unimplemented syscall would panic otherwise.
* eval_send doesn't need to be an Option
We can drop the value manually, thus avoiding unwrap on each access.
* Keep single `use rayon::prelude::*`
If either `rayon` is already imported, then `rayon::prelude::*` should always resolve.
* Extract comparator
* Fully enable non-parallel mode