#33 Found a PNG smaller than optipng's


I generated a PNG using Java, then I tried a third-party PNG encoder and also tried spawning an optipng process.
The third-party encoder is written in Java and is of course faster than spawning a process, but what astonished me is that, in some cases, the third-party encoder produces PNG smaller than optipng can. I attach one such example so that you can analyze it better that I can do and maybe improve optipng.
(there seem to be more zeroes between the header and the content)

test-1-java.png - Java ImageIO.write
test-2-pngencoder.png - objectplanet.com/pngencoder/
test-3-optipng.png - optipng -o7 test-1-java.png
test-4-optipng-zn19.png - optipng -o7 -zm1-9 test-1-java.png


  • Lapo Luchini

    Lapo Luchini - 2012-09-13
  • Matthew

    Matthew - 2012-09-13

    The unnamed PNG encoder has used a palette, while OptiPNG has used greyscale. A greyscale image is like a palette image, but with an implicit, fixed palette (i.e. it doesn't need to be stored in the image). Therefore, comparing the images, the former has a reordered, explicit palette, while the latter has a fixed, implicit palette.

    PLTE: 192+12=204 bytes
    IDAT: 563+12=575 bytes
    Sum: 779 bytes

    PLTE: none=0 bytes
    IDAT: 812+12=824 bytes (249 bytes larger)
    sum: 824 bytes

    Surprisingly, reordering the palette improves the compressability of the image data so much that the reduced size of the IDAT outweighs the cost of adding a palette.

    The crucial difference is that white has been moved from 255 to 0, so that most of the rows then contain 0 0 0 0 0 ... instead of 0 255 255 255 255 ...
    So now instead of encoding a bunch of 257*250 zeros, the greyscale image is encoding 250 lots of 0 255 255 255...
    The maximum runlength in zlib is 258 so it has to incorporate about 250 length/distance pairs to encode.

    [geeky deflate speculation]
    I guess that the palette image is encoding:
    0, 250ish*[-1 distance, 258 length],
    while the greyscale image is either encoding:
    (0, 255, [-1 distance, 255 length])*250 or:
    (0, 255, [-1 distance, 255 length]) followed by 250ish runs of [-257 distance, 258 length]

    Either way, it's either encoding two literals per row plus an inefficient lenth/distance pair, or it's encoding 250ish inefficient length/distance pairs. (-1 distance and 258 length both carry very little overhead.)
    [/geeky deflate speculation]

    From the IDAT size difference, whatever method it's using is obviously costing it about 8 bits per row.
    Long story short: converting greyscale to palette can, in cases like this, give an unexpected level of improvement. Thanks partly to the massive amounts of white and partly to the relatively small palette (64 entries of a possible 256).


Log in to post a comment.

Get latest updates about Open Source Projects, Conferences and News.

Sign up for the SourceForge newsletter:

No, thanks