{
  "version": "https://jsonfeed.org/version/1",
  "title": "Gif on LLBBL Blog",
  "icon": "https://avatars.micro.blog/avatars/2023/40/125738.jpg",
  "home_page_url": "https://llbbl.blog/",
  "feed_url": "https://llbbl.blog/feed.json",
  "items": [
      {
        "id": "http://llbbl.micro.blog/2026/08/25/looping-is-not-in-the.html",
        "title": "Looping Is Not in the GIF Spec",
        "content_html": "<p>The GIF89a specification defines the signature, the color tables, the LZW encoder, the four interlacing passes, transparency, and the per-frame delay. It does not define animation looping. Download it and search: the word &ldquo;loop&rdquo; does not appear in the document once.</p>\n<p>Every GIF you have ever watched repeat did so because of a 19-byte block that Netscape made up in 1995 and nobody ever standardized.</p>\n<p>I saved the same eight frames twice, once with a loop count and once without:</p>\n<pre tabindex=\"0\"><code>loop.gif      1969 bytes\nnoloop.gif    1950 bytes\n</code></pre><p>Nineteen bytes. That is the entire difference between an animation and a slideshow that stops.</p>\n<hr>\n<h2 id=\"the-nineteen-bytes\">The Nineteen Bytes</h2>\n<pre tabindex=\"0\"><code>$ grep -abo NETSCAPE loop.gif\n28:NETSCAPE\n$ grep -abo NETSCAPE noloop.gif\n$\n</code></pre><p>At byte 25, three bytes before that match, the block starts:</p>\n<pre tabindex=\"0\"><code>0x21        extension introducer\n0xFF        application extension label\n0x0B        block size: 11\n&#34;NETSCAPE&#34;  8 bytes\n&#34;2.0&#34;       3 bytes\n0x03        sub-block size: 3\n0x01        sub-block ID: loop count\n0x0000      loop count, little-endian; zero means forever\n0x00        block terminator\n</code></pre><p>The GIF89a spec does define a generic Application Extension, in section 26: a container where a vendor can stash eleven bytes of identifier and whatever payload they like, which decoders that don&rsquo;t recognize the identifier must skip. That was the extension point Netscape used, and the feature won so completely that it is now the only reason the format still exists.</p>\n<p>Section 14 of the same document, well before it gets there, tells you not to do this:</p>\n<blockquote>\n<p>This approach is recommended in favor of using Application Extensions, which become overhead for all other applications that do not process them.</p>\n</blockquote>\n<p>The spec authors saw the vendor-extension move coming, wrote down that it was a bad idea, and then shipped the mechanism anyway. Six years later a browser vendor used it to define the format&rsquo;s most famous feature.</p>\n<p>There is no RFC for this. There is no W3C note. There is no erratum to GIF89a. The <a href=\"https://wiki.whatwg.org/wiki/GIF\">WHATWG&rsquo;s own wiki page for GIF</a> does not document the block either; it links out to a third-party page that reverse-engineered it. Every GIF encoder in the world implements a format feature whose only description is somebody else&rsquo;s notes.</p>\n<hr>\n<h2 id=\"the-header-and-the-ghost-of-1995\">The Header, and the Ghost of 1995</h2>\n<p>The first sixteen bytes tell you most of what the file is:</p>\n<pre tabindex=\"0\"><code>$ xxd -g 1 -l 32 loop.gif\n00000000: 47 49 46 38 39 61 c8 00 64 00 81 00 00 ff 5a 28  GIF89a..d.....Z(\n00000010: 14 14 1e 00 00 00 00 00 00 21 ff 0b 4e 45 54 53  .........!..NETS\n</code></pre><p><code>GIF89a</code>, then <code>c8 00</code> and <code>64 00</code>: 200 by 100, little-endian, which is the opposite of what JPEG, PNG, and MP4 all chose. Then a packed byte, <code>0x81</code>, whose low three bits say the global color table holds 2^(1+1) colors. Four colors, twelve bytes, and then at byte 25 the Netscape block starts immediately.</p>\n<p>The per-frame timing lives in a different block, the Graphic Control Extension, and it has a problem:</p>\n<pre tabindex=\"0\"><code>GCE at byte 44:  delay field = 8 (hundredths of a second)\nGCE at byte 386: delay field = 8 (hundredths of a second)\n8 Graphic Control Extensions total\n</code></pre><p>Hundredths of a second. Two bytes, unsigned, little-endian. The finest interval the format can express is 10 milliseconds, so the theoretical ceiling is 100fps and there is no way to say 60fps evenly.</p>\n<p>It gets worse in practice. Browsers treat a delay of 0 or 1 as 10, so asking for 100fps gets you 10fps. <a href=\"https://bugzilla.mozilla.org/show_bug.cgi?id=232822\">Firefox has done this since at least 2004</a>, and the reason in the source comments is compatibility with how Netscape behaved. A delay of 2 is the practical floor, which puts the real maximum at 50fps.</p>\n<p>So the animation feature was defined by Netscape, and the animation timing is still bounded by an emulation of Netscape&rsquo;s bugs, thirty years after Netscape.</p>\n<hr>\n<h2 id=\"lzw-is-just-worse\">LZW Is Just Worse</h2>\n<p>Here is the part that makes GIF&rsquo;s survival strange.</p>\n<p>I took a 512x512 image with 151,112 distinct colors and reduced it to a 256-color adaptive palette. Then I saved those exact same quantized pixels two ways:</p>\n<pre tabindex=\"0\"><code>pal.gif      59198 bytes    (LZW)\npal.png      29154 bytes    (DEFLATE)\ntrue.png     64106 bytes    (DEFLATE, all 151,112 colors, lossless)\n</code></pre><p>Identical pixels, identical palette. PNG is less than half the size. And full-color lossless PNG, with every one of those 151,112 colors intact, is only 8% bigger than the GIF that threw 150,856 of them away.</p>\n<p>LZW builds a dictionary of repeated index sequences with codes that grow from 2 bits to a hard ceiling of 12. DEFLATE does LZ77 plus Huffman coding and has no such ceiling. On anything that isn&rsquo;t flat-color line art, it is not close.</p>\n<p>To be fair to the format, GIF is not always the loser. My eight-frame animation of a flat orange circle on a flat dark background came out:</p>\n<pre tabindex=\"0\"><code>anim.gif     1969 bytes\nanim.png     2170 bytes   (APNG)\nanim.webp    4392 bytes\n</code></pre><p>On a handful of frames of solid color, GIF&rsquo;s per-frame overhead is small and LZW does fine. That is the shape of content GIF was designed for in 1987, when it was moving graphics over a 2400-baud modem, and it is still competitive there. It is just that nobody uses GIF for that anymore. They use it for compressed video of a person reacting to something, which is close to the worst possible input for a 256-color palette and a 12-bit dictionary.</p>\n<hr>\n<h2 id=\"the-format-png-was-built-to-replace\">The Format PNG Was Built To Replace</h2>\n<p>At the end of December 1994, CompuServe and Unisys jointly announced that software reading or writing GIF would need a license for <a href=\"https://patents.google.com/patent/US4558302A/en\">US Patent 4,558,302</a>, Terry Welch&rsquo;s LZW patent, granted in 1985.</p>\n<p>The response was immediate. PNG went from nothing to a frozen <a href=\"https://www.w3.org/TR/PNG-History.html\">ninth draft on 7 March 1995</a>, roughly ten weeks later. It is a better format on essentially every axis: better compression, 24-bit color, an alpha channel instead of one transparent index, and no patent. The League for Programming Freedom ran a &ldquo;Burn All GIFs&rdquo; campaign. Everyone agreed GIF should die.</p>\n<p>The patent expired on 20 June 2003, and by then the argument had been over for years. PNG had won the still-image half completely.</p>\n<p>It lost the other half because it did not have animation, and by the time APNG existed the web had already decided that a looping image was a GIF. The format that PNG was created to replace survives entirely on the one capability PNG shipped without, which the GIF specification also does not have, and which exists only because a browser vendor stuffed it into a generic extension slot and shipped it.</p>\n<hr>\n<h2 id=\"what-to-do-about-it\">What To Do About It</h2>\n<ul>\n<li><strong>Stop using GIF for video.</strong> A short MP4 or a WebM is smaller by an order of magnitude, plays with hardware decoding, and does not have a 256-color palette. Every major platform silently transcodes your GIF uploads to video already.</li>\n<li><strong>Use PNG for anything still.</strong> There is no case in 2026 where a static GIF is the right answer.</li>\n<li><strong>If you must ship GIF, quantize deliberately.</strong> Pick the palette yourself rather than letting the encoder guess, and dither on purpose. The default adaptive palette is rarely the best 256 colors for your image.</li>\n<li><strong>Never set a frame delay below 2.</strong> You will get 10fps instead of the 50 or 100 you asked for, and it will look like a bug in your code.</li>\n<li><strong>Check for the loop block if animations mysteriously play once.</strong> <code>grep -abo NETSCAPE file.gif</code>. Plenty of encoders omit it by default, including Pillow unless you pass <code>loop=0</code>.</li>\n</ul>\n<p>Every format in this series has had a gap between what the specification says and what implementations do. GIF is the case where the gap swallowed the format. The spec describes a still-image format with optional frame timing. What the world actually uses is that spec plus one undocumented vendor block, and the vendor has been gone since 2003.</p>\n<h2 id=\"sources\">Sources</h2>\n<ul>\n<li><a href=\"https://www.w3.org/Graphics/GIF/spec-gif89a.txt\">GIF89a specification</a> — CompuServe, 31 July 1989; the Application Extension block is section 26</li>\n<li><a href=\"https://wiki.whatwg.org/wiki/GIF\">WHATWG Wiki: GIF</a> — where the web platform&rsquo;s own wiki sends you for the looping extension, which is off-site</li>\n<li><a href=\"https://patents.google.com/patent/US4558302A/en\">US Patent 4,558,302</a> — Welch, granted 1985, expired 20 June 2003</li>\n<li><a href=\"https://www.w3.org/TR/PNG-History.html\">PNG specification history</a> — the ninth draft froze on 7 March 1995</li>\n<li><a href=\"https://bugzilla.mozilla.org/show_bug.cgi?id=232822\">Mozilla bug 232822</a> — animated GIF frame delays of 10ms are slowed to 100ms</li>\n<li><a href=\"http://www.matthewflickinger.com/lab/whatsinagif/lzw_image_data.asp\">What&rsquo;s In A GIF</a> — the clearest walkthrough of GIF&rsquo;s LZW bit packing anywhere</li>\n</ul>\n<blockquote>\n<p>I&rsquo;d appreciate a follow. You can subscribe with your email below. The emails go out once a week, or you can find me on Mastodon at <a href=\"https://micro.blog/llbbl?remote_follow=1\">@logan@llbbl.blog</a>.</p>\n</blockquote>\n",
        "date_published": "2026-08-25T10:00:00-05:00",
        "url": "https://llbbl.blog/2026/08/25/looping-is-not-in-the.html",
        "tags": ["Programming","web","File-formats","Compression","Gif"]
      }
  ]
}
