Smallest PNG

Here’s a short and pointless thing, the smallest file size png image that contains every color.

Every color that a computer can show, at any rate. This red, green and blue color channel model (RGB) stores the red, green and blue intensities as values between 0 and 255; and with three different colors, this means it can produce 2563 colors. That’s 16 million, 777 thousand, 216 different colors. And if you made each pixel in an image a different color, you’d need an image that’s 4096 by 4096 pixels in size and ~48 MB to store it.

Or do you? You could store it as a jpeg, but that storage method is lossy and so you’d lose colors.  A bitmap image (bmp) can store it losslessly, but that would take 48 MB. The png image on the other hand, is a lossless method and does some compression to save space. Is there a way to store every color in a png that also compresses very well with png’s method?

To find out, I could either read up on png’s storage method… or just write a script that generates all possible RGB permutation images, stores them and then compare the file sizes. I did the latter. And here it is, a square image with all possible colors while also being as small as possible:

It’s actually not that exciting to look at, but it contains those 16 million different colors in just 59,334 bytes. That’s 0.12% the size of the bitmap version, or 848 times smaller! I scanned through different ordering of the colors (RGB, GRB, BRG, BGR, etc.) and alternate order for the dimensions (x,y or y,x ). The ordering that resulted in the smallest file was BGR x,y; the reverse of RGB.

Could this get any smaller? Actually yes! Instead of a square, use a rectangle that’s 8192 by 2048:

This is only slightly smaller however, at 54,415 bytes. But this trend does continue a little ways! By making the image longer and longer, the byte size goes down, but only to a point:

(image  size) -> color order (byte size)
----------------------------------------
(4096,  4096) ->     bgr     (59334)
(8192,  2048) ->     bgr     (54415)
(16384, 1024) ->     bgr     (51999)
(32768,  512) ->     grb     (50832)
(16384 , 256) ->     bgr     (49916)
(131072, 128) ->     bgr     (49869)
(262144,  64) ->     bgr     (50520)
(524288,  32) ->     bgr     (52369)
(1048576, 16) ->     bgr     (56913)
(2097152,  8) ->     brg     (65715)
(4194304,  4) ->     brg     (87846)
(8388608,  2) ->     brg     (110906)
(16777216, 1) ->     grb     (37050861)

Images that long and thin however, are unwieldy, so the square and 2:1 ratio rectangle are just the ones I’m uploading. And if those images are still too big for you to download (just loading this site probably downloaded more), here’s the script I made to find the smallest:

from itertools import product, permutations
from io import BytesIO
from PIL import Image
import os, sys
import numtxt
import time

# iterates all possible ways to add in pixels, direction
# find lowest file size that stores every color

fld = 'C:/png'
if not os.path.exists(fld): os.makedirs(fld, exist_ok=True)

odr = [''.join(a) for a in permutations('rgb')]  # rgb order
cdx = {c: odr[0].index(c) for c in odr[0]}  # color index
mapp = lambda c, d: (c[cdx[d[0]]], c[cdx[d[1]]], c[cdx[d[2]]])

t0 = time.perf_counter()
sizes = {}
for o in odr:  # rgb order
    print(o)
    rgb = product(*[list(range(256))]*3)

    size = (4096, 4096)
    im1 = Image.new('RGB', size)
    im2 = Image.new('RGB', size)
    for x in range(size[0]):
        for y in range(size[1]):
            col = mapp(next(rgb), o)
            im1.putpixel((x, y), col)
            im2.putpixel((y, x), col)
    im1 = im1.convert('RGB')
    im2 = im2.convert('RGB')
    print('\tsave')
    for cl in range(11):
        if cl < 10:  # compress_level 0 to 9
            nm1 = o + ' x,y cl=' + str(cl)
            nm2 = o + ' y,x cl=' + str(cl)
            dmp1 = os.path.join(fld, nm1 + '.png')
            dmp2 = os.path.join(fld, nm2 + '.png')

            im1.save(dmp1, format='PNG', compress_level=cl)
            im2.save(dmp2, format='PNG', compress_level=cl)
        else:  # and optimize=True
            nm1 = o + ' x,y opt=T'
            nm2 = o + ' y,x opt=T'
            dmp1 = os.path.join(fld, nm1 + '.png')
            dmp2 = os.path.join(fld, nm2 + '.png')

            im1.save(dmp1, format='PNG', optimize=True)
            im2.save(dmp2, format='PNG', optimize=True)

        sizes[nm1] = os.stat(dmp1).st_size
        sizes[nm2] = os.stat(dmp2).st_size
t1 = time.perf_counter()

# find file sizes:
mn, mx = 2**31 - 1, 0
mm, xx = '', ''
for s in sorted(sizes, key=sizes.get):
    sze = sizes[s]
    if sze <= mn: mm = s; mn = sze
    if sze >= mn: xx = s; mx = sze
    print(s, '\t', sze, '\t({})'.format(numtxt.si(sze, 'B', base=1024)))

print('')
print('size:', size)
print('smallest:', mm, mn)
print('largest :', xx, mx)
print('time:', round(t1 - t0, 3), 'sec.')
Tagged with: ,
Posted in Pictures, Python

Leave a comment

In Archive
Design a site like this with WordPress.com
Get started