A PNG showing differently in Firefox and Chrome

I was chatting with some friends on IM when someone posted an URL to a Psyduck image. In it said “Now open this in Firefox” when opened in Google Chrome or Safari and “Now open this in IE (or Chrome/Safari)” when opened in Firefox.

Psyduck

What.

At first, I though it would be a simple use of pattern matching against HTTP’s User-Agent on the server to send two different PNG files depending on the browser. However, both files had the same size and I couldn’t reproduce it with curl. Worse: I downloaded the file and the same behavior was present, so it should be something with the image itself.

I never really studied or read about binary file formats before, so I googled a bit and installed the chunkypng gem. After playing a bit with its documentation, I could see which blocks it had.

>> require 'chunky_png'
>> q = ChunkyPNG::Datastream.from_file "psyduck.png"
>> q.each_chunk {|chunk| p chunk.type}
"IHDR"
"acTL"
"fcTL"
"fdAT"
"fdAT"
"fdAT"
"fdAT"
"fdAT"
"fdAT"
"fdAT"
"fdAT"
"IDAT"
"IDAT"
"IDAT"
"IDAT"
"IDAT"
"IDAT"
"IDAT"
"IDAT"
"IEND"
>> q.each_chunk { |chunk| if chunk.type == "acTL" then p chunk end }
#<ChunkyPNG::Chunk::Generic:0x0000010160f570 @type="acTL",
  @content="\\x00\\x00\\x00\\x01\\x00\\x00\\x00\\x01">

Can you see the acTL block? It isn’t in the PNG specification. Why is it there? After some more searching, it was clear that this block is only available in APNG files, an extension to PNG enabling animated images similar to GIF.

The first byte @content of the acTL block is the number of frames (only 1) and the second one is how many times to loop the APNG (source). From the spec, there’s always a “default image” (described by the IDAT blocks, exactly like a normal PNG file), thus this extra frame should be the second Psyduck image.

To confirm this hypothesis, I installed pngcrush with Homebrew and removed the acTL block:

$ brew install pngcrush

# Remove the `acTL` block from psyduck-original.png.
$ pngcrush -rem acTL psyduck-original.png psyduck-no-animation.png

I ended up with an image that will look the same independent of the host browser:

Altered Psyduck

Search a bit more lead me to the cause of the discrepancy: only Firefox and Opera have support for APNG files! From this post in Google’s Products forum and this ticket in Chromium’s issue tracker, WebKit/Chrome doesn’t support the format and probably won’t for some time. Also, from the spec:

APNG is backwards-compatible with PNG; any PNG decoder should be able to ignore the APNG-specific chunks and display a single image.

Take all that and the mystery is solved: when that image is opened in Firefox (or Opera), it’s treated as an APNG and the second frame is shown. In Chrome/Safari (WebKit), the extra blocks are ignored and the default image is shown.

That was fun.

References

  1. PNG specification
  2. APNG specification