Rate-Distortion Optimized Lossy PNG, QOI, and LZ4 image (LZ4I) Encoding Tool
rdopng is a command line tool which uses LZ match optimization, Lagrangian multiplier rate distortion optimization (RDO), a simple perceptual error tolerance model, and Oklab-based colorspace error metrics to encode lossy 24/32bpp PNG/QOI/LZ4I files. The encoded lossy PNG files are typically 30-80% smaller relative to lodepng/libpng. The tool defaults to reasonably fast near-lossless settings which writes PNG's around 30-40% smaller than lossless PNG encoders.
Unlike pngquant, rdopng does not use 256-color palettes or dithering. PNG files encoded by rdopng typically range between roughly 2.5-7bpp, depending on the options used (and how much time and patience you have).
Some example encodes and command lines are here.
You can download a pre-built Windows binary for an older version of rdopng here. (The latest version is in the repo.) You may need to install the VS 2022 runtime redistributable from Microsoft.
You'll need cmake. There are no other dependencies.
Linux (gcc/clang):
cmake .
make
Windows (tested with Visual Studio 2022):
cmake .
rdopng.sln
Encodes a .PNG/.BMP/.TGA/.JPG file to "./file_rdo.png":
rdopng file.png
Encodes a .PNG/.BMP/.TGA/.JPG file to "./file_rdo.qoi" (and also unpacks the coded image and saves it as .PNG):
rdopng -qoi -unpack_qoi_to_png file.png
Encodes a file to "./file_rdo.qoi" at higher quality per bit, but much slower (also try -better which is in between the default/uber settings):
rdopng -qoi -uber -unpack_qoi_to_png file.png
Encodes smaller PNG files but will be 2x slower:
rdopng -two_pass file.png
Encodes at lower than default quality (which is 300), but writes smaller files:
rdopng -lambda 500 file.png
Significantly lower PNG quality (which increases artifacts), using a higher than default parsing level to compensate for artifacts:
rdopng -level 3 -lambda 1000 file.png
Enable debug output and write output to z.png:
rdopng -debug file.png -output z.png
Load a normal map, normalize it, pack it using angular normal map metrics, decoded/encode texels using GPU SNORM unpacking (instead of the default UNORM):
rdopng -normalize -normal_map -snorm file.png
Level ranges from 0-29. Levels 0-9 use up to 4 pixel long matches, levels 10-17 use up to 6 pixel long matches, and 18-23 use up to 6 or 12 pixel long matches. Levels 24-29 use exhaustive matching and are beyond impractical except on tiny images.
The higher the level within a match length category, the slower the encoder. Higher match length categories are needed for the higher lambdas/lower bitrates. At near-lossless settings (lower than approximately lambda 300), the smaller/less aggressive parsing levels are usually fine. At higher lambdas/lower bitrates the higher levels are needed to avoid artifacts. To get below roughly 3-4bpp you'll need to use high lambdas, two pass mode, and very slow parsing levels.
-lambda is the quality slider. Useful lambda values are roughly 1-20000, but values beyond approximately 500-1000 (depending on the image) will require fiddling with the level to compensate for artifacts. Higher levels are extremely slow because the current tool is single threaded.
Most options work with both QOI, LZ4I and PNG. The -level option is only for PNG, and the -uber/-better options are only for QOI/LZ4I.
rdopng -lz4i -lambda 5000 -debug -better file.png
Unpacking .LZ4I images to PNG:
rdopng -unpack file.lz4i
LZ4I image files contain a simple header followed by the RGB(A) pixels compressed using LZ4. Here's the header (it's like QOI's but with a different sig):
#pragma pack(push, 1)
struct lz4i_header
{
char sig[4]; // signature bytes "lz4i"
uint32_t width; // image width in pixels (BE)
uint32_t height; // image height in pixels (BE)
uint8_t channels; // 3 = RGB, 4 = RGBA
uint8_t colorspace; // 0 = sRGB with linear alpha 1 = all channels linear
};
#pragma pack(pop)
rdopng has only been tested on little endian platforms, under Windows using MSVC and Ubuntu Linux using clang/gcc. There are a few known endian issues in there, which I'll eventually fix. It has not been compiled or tested on OSX.
Thanks to Paul Hughes for encouraging me to continue working on this on Twitter. Also, thanks to Jyrki Alakuijala for suggesting to drop YCbCr for an alternative such as Oklab.