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?
palette
size is limited to 256 colors and pyvips
doesn't warn you if conversion becomes lossy.
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.