Saturday, March 28, 2015

Semi-transparent GIF images

Are you ready to return to the 1980's? neither. But this post will take you back. And your head will hurt thinking about what I've done to the Internet.

Today, I was looking at this logo on the PHP website trying to figure out why it was so distracting:

And then, after digging around for a bit, I realized that the image was larger than it looked:

Then I realized that the browser was resizing the image to the smaller size. And then I realized that the first image looked a bit anti-aliased, which reminded me that a lot of web browsers will use bicubic scaling to resize larger images. I then realized that this behavior could be abused when applied to a 1-bit alpha channel. Such as the 1-bit alpha channels like those found in GIF images.

GIF is one of the oldest image file formats. It was invented in the late 1980's and is still used on the Internet today for some animations. One of the areas it has traditionally been weak in is transparency. PNG has largely supplanted GIF for static images. MNG (animated PNG) was supposed to be the replacement for animated GIF, but it never took off for a variety of reasons. Regardless, GIF keeps finding ways to come back again and again. Beyond natively supporting 256 colors (there are ways around the 256 color limitation via GIF animation palettes), one of the format's greatest weaknesses is the inability to store semi-transparent data. A pixel is either fully opaque or fully transparent. The end result is that "jaggies" appear along the edges of most GIF images and are particularly noticeable in transparent animated GIFs.

In recent years, browser vendors introduced smooth resizing algorithms into the image presentation core. This happened to make more websites look nicer when resizing images and because the processing power was sufficient to handle the extra load on the CPU. Depending on the browser, display of images may simply be offloaded to the GPU, which is specialized hardware for doing things like resampling image data (i.e. doing this became "free" from a programming perspective). Regardless, this effect is widespread enough that it can now be abused. Here's how:

  1. In an image editor, take a GIF image and stretch it to 11 times its size in height using Nearest Neighbor (not bicubic!).
  2. Delete the appropriate number of pixels to represent the opacity. Do transparent first and solids second on the top half of the image and solids first and transparent second on the bottom half of the image.
  3. Save the file and hardcode the original size in a HTML document.
  4. ...
  5. Profit?
The browser operates on RGBA and will do bicubic scaling on all four channels. Making the GIF 11 times larger gives us alpha options of: 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100. So if 8 vertical pixels are of one opaque color and the remaining 3 are transparent, then that will result in the final calculated pixel being 70 percent opaque.

Now some of you might be thinking, "Won't this bloat the file size?" Not necessarily. GIF under the hood uses LZW compression, which attempts to find the longest repeating sequence and compresses the file based on that information. The probability is quite high that the sequence between any two given rows will be identical, which may translate to a mere 10 to 20% increase in overall file size. It might be a good idea for anyone implementing this in software to try both horizontal and vertical scales to see which direction performs the best as far as file sizes go but I'm leaning toward vertical as being better.

Now my pretties, fly and fill the Internets once more with animated GIFs but this time with semi-transparency!