pythonpngvips

Reducing PNG file size with pyvips: matching ImgBot and ImageMagick optimizations


I'm trying to minimize file size of PNG images written with pyvips.Image.pngsave(). Original files written with just .pngsave(output) are at https://github.com/CDDA-Tilesets/UltimateCataclysm and we'll look at giant.png which is 119536 bytes.

ImgBot was able to reduce file size to 50672.

pngsave(output, compression=9, palette=True, strip=True) to 58722

But the convert command from ImageMagick is still able to reduce file size further after the latter, to 42833 with default options:

$ convert giant_pyvips_c9.png giant_pyvips_magick.png

The question is whether it's possible to fit the same image into 42833 bytes using only pyvips to avoid adding another step to our workflow?

Update: Warning

palette size is limited to 256 colors and pyvips doesn't warn you if conversion becomes lossy.


Solution

  • Try turning off filtering:

    $ vips copy giant.png x.png[palette,compression=9,strip,filter=0]
    $ ls -l x.png
    -rw-r--r-- 1 john john 41147 Feb 14 10:58 x.png
    

    Background: PNG filters put the image though a difference filter before compression. Compressing differences to neighbouring pixels rather than absolute pixel values can boost the compression ratio if there is some local pattern in values. pyvips uses an adaptive filter by default.

    Palette images encode an index into a look up table rather than anything related to luminance, so there is much less local correlation. In this case, filtering actually hurts compression.

    http://www.w3.org/TR/PNG-Filters.html

    You can see the values allowed for the filter= parameter here:

    https://github.com/libvips/libvips/blob/master/libvips/include/vips/foreign.h#L579-L598

    Update libvips 8.13 changed the default filter to none since it gives better results for most images. libvips 8.15 added support for named flags (finally), so you can use eg. paeth or up|sub instead of numbers, if you want.