A PNG showing differently in Firefox and Chrome
Update (2026-02): The support for APNGs has improved considerably since this post was written. It was still an interesting investigation into PNGs so I’m going to leave it here.
I was chatting with some friends on IM when someone posted a link to a Psyduck. The image had the following instructions: “Now open this in Firefox”. And so I did, and it now said “Now open this in IE (or Chrome/Safari)”, presented with a different background color.
What.
Pattern matching against the User-Agent of the HTTP request to send two different files was my first guess. However, the PNGs downloaded through each browser were identical. Worse: the same behavior was present with the file copied locally, so it should be something with the image itself.
Since I never read about image file formats in depth, I had no clue what could be going on. So after finding out about the chunkypng gem, I started poking around the PNG and found out how to look at its chunks.
>> 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.
After a few minutes of Googling, it was clear that this block is only available in a related file format called APNG, 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 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:
Searching led 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.
The mystery is solved: the image is correctly treated as an APNG when opened on Firefox, the animation runs for 1 frame, and you see the second frame. On the other hand, in Chrome/Safari, it is read as a normal PNG, the extra block is ignored, and the default image is shown.
That was fun.