shssoichiro / oxipng

Multithreaded PNG optimizer written in Rust
MIT License
2.89k stars 124 forks source link

optipng generates much smaller files than oxipng even at lower levels #195

Closed desbma closed 1 year ago

desbma commented 4 years ago
$ cd /tmp
$ wget -q 'https://raw.githubusercontent.com/petvas/i3lock-blur/master/lock.png'
$ cp lock.png{,.orig}

$ optipng -v
OptiPNG version 0.7.7
Copyright (C) 2001-2017 Cosmin Truta and the Contributing Authors.

This program is open-source software. See LICENSE for more details.

Portions of this software are based in part on the work of:
  Jean-loup Gailly and Mark Adler (zlib)
  Glenn Randers-Pehrson and the PNG Development Group (libpng)
  Miyasaka Masaru (BMP support)
  David Koblas (GIF support)

Using libpng version 1.6.37 and zlib version 1.2.11

$ optipng -o4 lock.png
** Processing: lock.png
512x512 pixels, 4x8 bits/pixel, RGB+alpha
Reducing image to 8 bits/pixel, 233 colors (232 transparent) in palette
Input IDAT size = 13216 bytes
Input file size = 13359 bytes

Trying:
  zc = 9  zm = 8  zs = 0  f = 0     IDAT size = 6592
  zc = 9  zm = 8  zs = 3  f = 0     IDAT size = 6251

Selecting parameters:
  zc = 9  zm = 8  zs = 3  f = 0     IDAT size = 6251

Output IDAT size = 6251 bytes (6965 bytes decrease)
Output file size = 7336 bytes (6023 bytes = 45.09% decrease)

$ oxipng --version
oxipng 2.3.0

$ cp lock.png{.orig,}
$ oxipng -o6 lock.png
Processing: lock.png
    512x512 pixels, PNG format
    4x8 bits/pixel, RGBA
    IDAT size = 13216 bytes
    File size = 13359 bytes
Trying: 216 combinations
Found better combination:
    zc = 9  zs = 0  f = 0        8927 bytes
    IDAT size = 8927 bytes (4289 bytes decrease)
    file size = 9055 bytes (4304 bytes = 32.22% decrease)
Output: lock.png
TPS commented 4 years ago

Looks like oxipng missed the 32-bit → palletted conversion that optipng did. Is that choice by default, or isn't that implemented (yet)?

RReverser commented 4 years ago

@TPS AFAIK oxipng does perform that conversion. You might want to add -v flag to see more output from oxipng.

desbma commented 4 years ago

Output of oxipng -v -o6 lock.png: https://pastebin.com/raw/U2yskz4f

TPS commented 4 years ago

optipng:

512x512 pixels, 4x8 bits/pixel, RGB+alpha Reducing image to 8 bits/pixel, 233 colors (232 transparent) in palette

oxipng:

512x512 pixels, PNG format 4x8 bits/pixel, RGBA Reducing image to 2x8 bits/pixel, Grayscale + Alpha

I think, instead, that the different conversion (4-byte color+ɑ → 2-byte grayscale+ɑ, instead of → paletted+tRNS) is probably the cause of different result.

TPS commented 4 years ago

Would https://github.com/shssoichiro/oxipng/pull/199 affect this positively?

RReverser commented 4 years ago

@TPS No, it's just providing more deterministic output, but not adding any new optimisations.

P.S. By the way, out of curiousity - why are you leaving reactions to your own messages? 😀

RReverser commented 4 years ago

I think, instead, that the different conversion (4-byte color+ɑ → 2-byte grayscale+ɑ, instead of → paletted+tRNS) is probably the cause of different result.

This actually sounds reasonable and something we've seen in one of examples in Squoosh PR as well.

@shssoichiro It seems that OxiPNG currently assumes that grayscale is the most efficient representaton for grayscale-ish images, but paletted representation often compresses even better (e.g. in example above Grayscale+Alpha requires more space than 233 colours in a palette).

shssoichiro commented 4 years ago

So reduction should try both grayscale and palette (or grayscale+alpha and palette). Seems like a reasonable approach. Color reduction is pretty fast so this shouldn't hurt speed much either.

RReverser commented 4 years ago

I think even adding conversion for Grayscale+Alpha -> palette would already go a long way, as currently it's not attempted (it should be similar to existing RGBA -> palette - maybe possible to make the function generic somehow?).

TPS commented 4 years ago

I think even adding conversion for Grayscale+Alpha -> palette

@RReverser To be clear, do you mean Grayscale+ɑ → Pallette+tRNS, Greyscale → Pallette, or both? I've seen that 2nd pairing come out better, as well as trying different palletted bit-depths. (I.E., sometimes deflate's compression somehow works better w/ 8-bit than ≤4-bit pallettes.)

RReverser commented 4 years ago

I was mostly referring to Grayscale alpha -> Palette as it's an easy win (and it will practically always be a win as in the worst case it's reducing 2 bytes -> 1 byte uncompressed), but other combinations might be worth looking into too.