Blog

<−− Hello And Welcome

The Fastest GIF Does Not Exist

2022 - February 19th tech

What's the problem with GIFs?

You're trying to create a violently-shaking GIF for comedic purposes (https://knowyourmeme.com/memes/vibrating-gifs). The GIF editor you're using lets you set a frame duration / delay, so you set it to the lowest possible value for maximum shakage. But when you view the resulting GIF, it's playing much slower than intended, and there are definitely GIFs that play faster than this one. What's going on?

If you're here because you want to fix your GIF and want the quick answer, the solution is: set your frame delay to 20ms instead of 10ms. If you want to learn a bit more about GIFs, exactly why this edge case happens, and some thoughts on how to improve things, keep reading!

(Disclaimer: If you're from a distant utopian future where this isn't a problem any more, a few of the example GIFs in this article won't make much sense. Otherwise, my condolences, and please disregard this message.)

Violently shaking dog
Me when GIFs are too slow

GIF Features

We won't be going into detail on how a GIF file is structured. For a great breakdown on the bytes that make up a GIF, check out Matthew Flickinger's "What's in a GIF" project.

In the first version of the GIF format (87a), multiple image frames (of varying size and position) would be overlaid on top of each other to create a single resulting image. Each frame could reference a different 256-colour palette, so an image could be created using more than 256 distinct colours.

In the current version of the GIF format (89a), transparency and animation features are available. Now before each image frame, an optional "delay" value can be set which determines how long that frame should be shown before moving on to the next one. Specifically, it's the number of hundredths of a second to wait before continuing to the next frame. The value can be set from 0 (no delay) to 0xffff (roughly a 10 minute delay).

Dog dancing frantically
GIF with frame delay set to 5 (50ms).
Slow dancing dog
GIF with frame delay set to 50 (500ms).

No Delay

What would a delay value of 0 look like? The spec doesn't answer the question directly, but mentions two things:

  1. When decoding a GIF file, each image frame should be processed "without delays other than those specified in the control information".
  2. Delay values are only used "if not 0".

I would take this to mean that any image frames with a delay of 0 should be combined with the previous image frame data, just like how the original 87a GIF format worked. If every image frame in the GIF had a delay of 0, the result would be a static image with the combined data of all the image frames.

Here's a couple of examples of what GIFs would look like if the spec were followed:

What a GIF with more than 256 colours would look like if possible to create
A static GIF with more than 256 colours, combining many frames.
Black squares appear in a grid with no flashes of red
An animation with red squares, set to 0-delay. These red squares aren't visible.

For context, this is what the above GIFs actually render as:

A GIF with lots of colours but slow to load
The colourful GIF only renders small portions at a time.
Black squares appear in a grid but flashes of red are visible before each one appears
The 0-delay red squares are visible when rendering.

(If you're seeing identical images here - let me know! It means there's a browser out there that is handling GIFs differently to the big browsers.)

Source of the Problem

The problem is, nobody supports 0 delay. That is, none of the programs that you'd typically be viewing GIFs with support it (Firefox, Edge, IE, Chrome, Windows Explorer, Electron apps, Qt apps...). Anything with a delay of less than 2 (20ms) will be clamped to a higher value. By looking at the various programs' source code, we can build up a picture of why.

Qt (version 6.2.2)

Qt source code
Qt source code. "IE and mozilla use a minimum delay of 10. With the minimum delay of 10 we are compatible to them and avoid huge loads on the app and xserver."

Chromium (version 98.0.4754)

Chromium source code
Chromium source code. "Many annoying ads specify a 0 duration to make an image flash as quickly as possible. We follow Firefox's behavior and use a duration of 100 ms for any frames that specify a duration of <= 10 ms."

Firefox (versions 97 and ~38)

Firefox source code
Firefox source code. "Very small timeout values are problematic for two reasons: we don't want to burn energy redrawing animated images extremely fast, and broken tools generate these values when they actually want a "default" value, so such images won't play back right without normalization... The historical behavior of IE [is] 10 – 50ms is normalized to 100ms. >50ms is used unnormalized. Opera [is] 10ms is normalized to 100ms. >10ms is used unnormalized."
Older Firefox source code
Firefox source code (from an older version, with different wording). "Ensure a minimal time between updates so we don't throttle the UI thread. Consider 0 == unspecified and make it fast but not too fast... It seems that there are broken tools out there that set a 0ms or 10ms timeout when they really want a "default" one. So munge values in that range."

Internet Explorer 5

Internet Explorer 5 is closed source, but could hypothetically look something like this:

Intenet Explorer 5 source code
Internet Explorer 5 hypothetical source code. "crude hack to cope with 'degenerate animations' whose timing is set to some small value because of the delays imposed by Netscape's animation process... assume these small values imply Netscape encoding delay... fake [a delay value] such that GIFs that rely solely on Netscape's delay to time their animations will play"

To summarise the comments above:

  • Qt does it to match IE and Firefox behaviour (and to avoid overloading the CPU)
  • Chromium does it to match Firefox behaviour (and to counteract annoying flashy ads)
  • Firefox does it to match IE and Opera behaviour (and to support non-spec-conforming GIFs, and to avoid overloading the CPU)
  • IE 5 does it because Netscape was slow (which caused lots of people to make badly formed GIFs)

One of the odd things about all these codebases: instead of e.g. clamping a value of 0 or 1 up to 2, they will instead get pushed all the way up to 10 (100ms). So there's a sweet spot – set a delay too small and you'll get a slow GIF. Set the delay juuust a bit higher and you'll get a much faster GIF.

GIF with a delay of 1. In modern browsers this plays the animations relatively slowly
10ms delay
GIF with a delay of 2. In modern browsers this is the fastest animation
20ms delay
GIF with a delay of 5
50ms delay
GIF with a delay of 10
100ms delay

If GIF decoding is incorrect where you're reading this, the above GIFs won't appear in order of speed.

Interactive Bit

You'll need to enable Javascript to see this section sorry! There's a slider that lets you see how changing the delay in a GIF file will affect the end results. It's pretty cool.

Thoughts

It seems the reason for pushing values like 10ms back up to 100ms originates from a requirement to emulate the slowness of Netscape. Qt and Firefox source code comments both want to reduce CPU usage but since a value of 20ms is supported, I think they should just be clamping the value to 20ms instead. Or, don't clamp the value at all! Modern browsers already render 20ms GIF frames just fine, and I'm not sure the "computers are too slow" argument holds up 30 years later.

On top of that, leave the 0-delay frames alone too. Combine them with prior frame content, like the original GIF specification says. If all frames have a 0-delay, show the static image that results from combining all the frames.

My use case was wanting to update two very-small-but-separate areas in a large GIF in a single frame, to reduce file size. Currently, you need to find the rectangular region that covers all changes on the screen for that frame and add that to the GIF, but if you could define multiple frames, and set all but one to a delay of 0, you could create the same effect but with less image data:

GIF with two distinct rectangular regions that could be updated in a single frame, if 0-delay frames were supported.
If one of these areas could be defined as a 0-delay frame, a more optimised GIF could be produced than with a single larger frame.

A downside to making any changes is that the badly formatted GIFs won't render any more. I reckon just be bold, like the release of Netscape 2.0:

"Netscape now conforms to GIF standards: Some GIF creation utilities produce GIF images that do not conform to standards. Such images will display empty outlines in place of the image. These outlines are much larger than the actual images. The images will not be visible at all. To repair these GIF images, content providers can read the offending GIF image into a different GIF utility that conforms to the GIF89a specifications, and save the image again."

If Netscape was fine with breaking poorly-made GIFs, we should be too!

Let's Check Out Netscape

I wanted to see what it looked like when Netscape 2.0 rendered a GIF. Here's the red-square test GIF from above, as rendered (correctly!) in Netscape 2.0 on Windows 95:

Netscape 2 running in Windows 95, rendering the red-square GIF from earlier with no red square artifacts visible.
No red squares visible!

Since we have Netscape available to us, why don't we have a look at what a 1-delay file actually looks like? Here's a GIF running at delay = 2 (20ms), for comparison. The results aren't quite as expected though. It runs very slowly, unless you're actively moving the mouse (the reason for this is discussed in this Stack Overflow page):

And this is with a delay of 0 - put before the exciting 10ms reveal, for dramatic effect. Look at the speed! I guess the original Netscape doesn't treat an "all-0-delay" GIF as a single static frame, as the spec might suggest it should. Why the mouse movement isn't required to speed this case up, I have no idea:

And finally, the 10ms delay GIF we've all been waiting for...

*drum roll*

Huh. Slow, and then crashes. I guess this was before we had 144Hz monitors to render that many frames per second. So if even Netscape 2.0 doesn't display GIFs that fast… does that mean a 10ms GIF has never been perceived in all of history? Netscape crashing is a bit of an ominous sign - who knows what would happen if someone tried too hard to see the fastest GIF. Maybe we shouldn't update the browsers after all 😅

Summary

No-one renders GIFs to spec, but they should (IMO). For now, set a GIF delay of 2 (20ms) instead of 1 (10ms) to get the fastest-running GIF. If everyone updated their code to match the spec, we get these upsides:

  • Support more than 256 colours in a single frame of GIF animation
  • Support fast GIFs (10ms delay)
  • No confusing behaviour with small delay = slow GIF
  • Better compression for GIFs with multiple small areas updated per frame

Thanks for reading!

If you have any comments or feedback, feel free to discuss on Reddit: Reddit discussion, or on Hacker News: Hacker News discussion.

Appendices

References

Source Code references

Fun Extra Things

I came across Netscape's "About GIFs" page, with a little hot air balloon lizard:

Here's Netscape trying to render a very complicated Opus Magnum GIF:

Here's Internet Explorer 5, failing to render the red-squares test GIF correctly. This shows how early it was that programs started going against the spec:

Internet Explorer 5 running in Windows 95, rendering the red-square GIF from earlier with red square artifacts visible.
Red squares visible...

Here's Windows XP picture viewer, failing to produce the colourful GIF at all... except for in the thumbnail view:

Windows XP, almost rendering a static colourful GIF correctly. The thumbnail view of the image is correct, but the full view just reports 'Drawing Failed'.
Drawing failed. Sort of?

Finally, here is Internet Explorer 5's valiant effort to render the colourful GIF:

A heavily corrupted image of the colourful GIF
Screenshot of Internet Explorer 5 attempting to render the colourful GIF (to be fair, it sorted itself out when the rendering had completed).
<−− Hello And Welcome