Tristan Hume

Github Resume + Project List Blog

Magic PNG Thumbnails

I was shown trick by a friend where an image was posted on a website that displayed one thing in the thumbnail and another in the lightbox.

This post contains an explanation of how these images work and how I was able to replicate their behaviour.

The Behaviour

Certain renderers of the png files would display one image and other renderers would display a completely different one. One image is always dark and one is light.


Difference example

Things that display the light image:

Things that display the dark image:

This can lead to interesting combos:

The Challenge and Victory

I started on a long journey to figure out how this effect works so that I could replicate it. The path to enlightenment involved many wrong turns including believing that the image was being interpreted as a GIF but I eventually discovered the truth.

After I discovered the secret I wrote a command line tool in Ruby called doubleVision so that anybody could generate magic thumbnail images.

doubleVision is available on Github and as an executable Ruby gem.

The output images look like this:

Sample Image

Try downloading it to your computer and then viewing it. Cool eh?

How it works

The PNG specification contains a metadata attribute that allows you to specify the gamma to render the image with. This attribute is intended to be used to ensure that images look identical on all computers. This is a very normal image processing process called Gamma Correction

The PNG specification defines the gAMA chunk (the chunk that stores the gamma value) to change the image output like so:

light_out = image_sample^(1 / gamma)

This scales the image values exponentially based on the reciprocal of the gamma value. If the gamma value is around 1 like it normally is this function has little noticeable effect. During this process, the lowest brightness value for a pixel is 0 and the highest is 1.

If we set the PNG gamma attribute to a very low value, making the exponent value very high (since it is the reciprocal), all darker pixels will be made black and all lighter pixels will be mapped to the normal spectrum.

Exponential Gamma Mapping

Gamma mapping

We can reverse this mapping for a very low value of the gamma attribute (I use 0.023) to get a PNG image where all the pixels of the image are mapped to very light colors. If we then set the gamma value of the PNG to 0.023 the image will look somewhat normal, except for the rounding errors introduced by crunching the image into high values.

The thing is, not all renderers support the gamma attribute. If we try and view this image in a renderer that does not support the gamma attribute it will show too bright to make out.

We can abuse this to create a magic thumbnail by taking two images of the same size and creating a new image twice their dimensions. One image is run through the previously mentioned reverse gamma filter that makes all pixels very bright and the other is darkened so that it has no very bright pixels. The images are then spaced out in grids around each other (see image). The resulting image is saved as a PNG file with a gAMA of 0.023.

Pixel Grid Pattern

Grid Pattern

When the image is displayed in a renderer that supports gamma (Like Firefox/Chrome) the light pixels become fairly dark but visible colors and the normal pixels become a grid of dark pixels. When the image is displayed in a renderer that does not support gamma (like Apple/Microsoft rendering) The untransformed image is shown surrounded by a grid of seemingly white pixels.

Installation and Usage

You can install the doubleVision gem and command using:

$ gem install doubleVision

Next, run the program like this:

doubleVision withgamma.png withoutgamma.png out.png

obviously replacing the filenames with your own.

It will combine the images into one image (out.png) that will display withgamma.png when viewed with gamma support (e.g. in Firefox) and withoutgamma.png when displayed without gamma support (e.g. As a thumbnail)

For more detailed instructions read the README on Github

Other Example

Day and Night

Was generated from: Night and Day