<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: NullPointerZen</title>
    <description>The latest articles on DEV Community by NullPointerZen (@yue_shu_c621a4a637f22396f).</description>
    <link>https://dev.to/yue_shu_c621a4a637f22396f</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4091461%2F67d2ab23-740e-4763-8279-b58904d35440.png</url>
      <title>DEV Community: NullPointerZen</title>
      <link>https://dev.to/yue_shu_c621a4a637f22396f</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yue_shu_c621a4a637f22396f"/>
    <language>en</language>
    <item>
      <title>Your GIF didn't band because you compressed it too hard</title>
      <dc:creator>NullPointerZen</dc:creator>
      <pubDate>Tue, 01 Sep 2026 02:40:39 +0000</pubDate>
      <link>https://dev.to/yue_shu_c621a4a637f22396f/your-gif-didnt-band-because-you-compressed-it-too-hard-1l32</link>
      <guid>https://dev.to/yue_shu_c621a4a637f22396f/your-gif-didnt-band-because-you-compressed-it-too-hard-1l32</guid>
      <description>&lt;p&gt;A colleague sent me a gradient banner and said my compression tool had wrecked it.&lt;/p&gt;

&lt;p&gt;I pushed the quality slider to maximum and re-ran it. &lt;strong&gt;The banding was still there.&lt;/strong&gt; That's when I realised we'd both been looking at the wrong control.&lt;/p&gt;




&lt;h3&gt;
  
  
  The rule nobody reads
&lt;/h3&gt;

&lt;p&gt;Every GIF frame carries a palette of &lt;strong&gt;at most 256 colours&lt;/strong&gt;. Every pixel in that frame has to land in one of those slots.&lt;/p&gt;

&lt;p&gt;Here's the way I explain it now: &lt;strong&gt;you've been handed a box of 256 pencils and asked to copy a photograph.&lt;/strong&gt; The sky might go through a thousand shades of blue, but you own five or six blue pencils — so you pick the nearest one each time. The smooth gradient comes back as a block of dark blue, a block of mid blue, a block of light blue, &lt;strong&gt;with the steps between them clearly visible.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's banding. It isn't "compressed badly", it's "ran out of pencils".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;And here's the part that matters: how many pencils you have is unrelated to how hard you press.&lt;/strong&gt; The quality parameter controls pressure — how much detail the encoder discards. The palette controls how many pencils exist. Max out quality and you still have those same 256.&lt;/p&gt;

&lt;h3&gt;
  
  
  Four runs at default settings
&lt;/h3&gt;

&lt;p&gt;I built four clips with deliberately different visual properties and ran them all through ImgIng's animation workshop (imging.ai) on defaults. I picked that tool because the frame work runs in the browser — I had the Network tab open across six files and a dozen-plus exports and saw &lt;strong&gt;zero POST requests&lt;/strong&gt;, which you can reproduce yourself rather than taking my word for it.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Footage&lt;/th&gt;
&lt;th&gt;Size · frames&lt;/th&gt;
&lt;th&gt;Auto-selected palette&lt;/th&gt;
&lt;th&gt;Size change&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Spinner (three colours)&lt;/td&gt;
&lt;td&gt;120×120 · 8&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;16 colours&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2.0 KB → 1.5 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UI screen recording (flat palette)&lt;/td&gt;
&lt;td&gt;720×405 · 36&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;64 colours&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;79.8 KB → 21.3 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gradient banner&lt;/td&gt;
&lt;td&gt;480×270 · 20&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;224 colours&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;425.2 KB → 391.6 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Full-frame noise&lt;/td&gt;
&lt;td&gt;480×320 · 24&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;256 colours&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2.62 MB → 2.63 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The palette isn't a fixed setting — &lt;strong&gt;it's allocated against what the footage actually needs&lt;/strong&gt;. Three-colour spinner: 16 is plenty. Flat UI: 64 covers it. Gradient banner: 224 and still tight. Noise: maxed out and still short.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The banding threshold sits on row three.&lt;/strong&gt; 224 is close to the ceiling, but a continuous gradient needs far more than that, so steps are guaranteed. And look at the size column — it only dropped 7.9%. &lt;strong&gt;You paid the visual cost and got almost none of the benefit.&lt;/strong&gt; That's the worst outcome of the four, and it's the one people actually hit, because gradient banners are everywhere.&lt;/p&gt;

&lt;h3&gt;
  
  
  So why does lowering quality seem to cause it
&lt;/h3&gt;

&lt;p&gt;Because in a lot of tools those controls are wired together. Drag quality down and the tool quietly reduces the palette along with it. You observe "compressed harder → more banding" and read it as cause and effect.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The real chain is: fewer colours → banding.&lt;/strong&gt; Quality was just the thing that happened to move the palette.&lt;/p&gt;

&lt;p&gt;Easy to verify: find a tool that exposes the colour count on its own. Leave quality at maximum, take the palette from 256 to 64, and watch it band anyway.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dithering, and when to leave it off
&lt;/h3&gt;

&lt;p&gt;After reducing colours, an encoder can scatter small amounts of noise across neighbouring pixels so the steps break up visually. That's dithering.&lt;/p&gt;

&lt;p&gt;The cost is concrete: &lt;strong&gt;dithering destroys frame-to-frame consistency.&lt;/strong&gt; Large areas that were pixel-identical between frames — and therefore free, thanks to differencing — get perturbed into slightly different values. Differencing stops working and the file grows.&lt;/p&gt;

&lt;p&gt;Rule of thumb I've settled on: &lt;strong&gt;dither at low palettes, don't at high ones.&lt;/strong&gt; Low palettes have obvious banding and a lot to gain; high palettes band mildly and you'd be trading away differencing for very little.&lt;/p&gt;

&lt;h3&gt;
  
  
  What to actually do
&lt;/h3&gt;

&lt;p&gt;In this order. Don't start by touching parameters.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Classify the footage first.&lt;/strong&gt; Flat colours, UI, icons, line art → palette reduction is nearly free. If it bands, the tool set the colour count too low; raise it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gradients, live action, noise → change format.&lt;/strong&gt; This material is going to band in GIF no matter what. Animated WebP and APNG have no 256-colour ceiling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stuck with GIF anyway&lt;/strong&gt; (because it has to play somewhere that only takes GIF)? Then accept a trade: tolerate the banding, or shrink dimensions so it's less visible. Two options, pick one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stop expecting the quality slider to fix banding.&lt;/strong&gt; Covered above — no causal link.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Limits
&lt;/h3&gt;

&lt;p&gt;The four numbers come from footage I generated to sit at the extremes, so &lt;strong&gt;they won't predict what palette your clip gets&lt;/strong&gt; — same category of footage with a different colour scheme or size lands somewhere else entirely. The mechanism is the transferable part: palettes are allocated to need, and banding shows up where need exceeds allocation.&lt;/p&gt;

&lt;p&gt;Also, that automatic allocation is this tool's behaviour, not a universal one. Plenty of tools just hand every frame 256 colours. If what you see elsewhere doesn't match these numbers, check whether it's picking palettes at all.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>images</category>
    </item>
  </channel>
</rss>
