A GIF optimization algorithm for screen recordings — from 5 MB to 986 KB
Tags: gif, experiment, optimization, showdev
Published: 2020-04-16 19:30:45

---
title: A GIF optimization algorithm for screen recordings — from 5 MB to 986 KB
published: true
description: When I record my screen and save it as GIF file, sometimes the file size is unsatisfyingly large. I came up with an algorithm to reduce the file size, optimized for screen recordings.
tags: gif, experiment, optimization, showdev
---

When I record my screen and save it as GIF file, sometimes the file size is unsatisfyingly large. For example, here is a GIF file recorded with [Kap](https://getkap.co/). 1418x406, 8 fps, 40 seconds (323 frames).

![Input](https://dev-to-uploads.s3.amazonaws.com/i/p75p432tuez7cef1w5a3.gif)

<figcaption>Input file: 5.00 MB</figcaption>

I know the GIF is long (40 seconds), but I’ve already reduced the frame rate to mere 8fps. *I do not expect it to be 5 MB large.*

I tried to optimize the GIF file using Gifsicle with heavy lossy compression (`gifsicle --lossy=200 --optimize=3`), the file size is still 3.1 MB large. Reduced the palette to 48 colors (`--colors 48`), but it still results in a 2.2 MB file.

So I dived a bit into the world of GIF files and was able to reduce the file size down to **986 KB**.

Optimization is performed under these conditions:

- **No resizing.** The image must look sharp on retina displays.
- **Compression artifacts are okay** as long as the result is readable.
- **Colors can be reduced.**
- No proprietary tools, but custom scripts are fine.

…and here is the resulting file, 5 times smaller:

![Output](https://dev-to-uploads.s3.amazonaws.com/i/b7idupq1jcu1lvj01i2l.gif)

<figcaption>Output: 986 KB</figcaption>

This post outlines how I achieved this level of compression.

## Studying the input file

When it comes to optimizing GIF files, what most people do is trying out a bunch of parameters until a satisfactory result is obtained. While this approach works most of the time, there is a ceiling to it. To fully exploit the characteristics of the input file, we must study the input itself, so that we know where to optimize.

ImageMagick's documentation has a chapter about [studying GIF files](https://www.imagemagick.org/Usage/anim_basics/#study) and [optimizing them](https://www.imagemagick.org/Usage/anim_opt/). This has inspired me to create a rudimentary [**GIF file inspector**](https://gif-inspector.glitch.me/). It is a web application that displays the raw data of each GIF frame. Decoding of GIF image on the web is possible thanks to the [gifuct-js](https://github.com/matt-way/gifuct-js) library.

{% glitch gif-inspector file=script.js %}

## What's so special about screen recordings?

A lot of GIFs are created from full-motion videos, and that is what most articles about GIF file optimizations are about. However, _screen recordings_ have some important characteristics different from full-motion videos:

- Most of the time, the background is stationary.
- There are usually only just a few changes from one frame to the next.

## Noisy in, noisy out.

As the GIF file inspector shows, the GIF file contains a lot of noise and dithering. Unchanged parts of the screen keeps getting repainted. While this helps make the GIF file look more pleasing, it causes the GIF file to include a lot of noise, resulting in large file size.

![Raw GIF frames before](https://dev-to-uploads.s3.amazonaws.com/i/ud504rw181cf4aj8lkhh.gif)

## Why is there this noise?

I picked a pixel (443, 234) and graphed its RGB values over time as it goes through the animation.

![Graph before](https://dev-to-uploads.s3.amazonaws.com/i/hrfnp5kcswgzt5qumrwj.png)

I can think of two possible reasons this may happen:

- Kap [uses](https://github.com/wulkano/kap/blob/f901ef4a4d85b0059f9cf5c2f7604dd6d7db1c4c/main/common/aperture.js) [aperture-node](https://www.npmjs.com/package/aperture) which in turn [uses](https://github.com/wulkano/aperture-node/blob/e09011c4e3c2dbd51c45a4c466809828de7c03d9/Sources/ApertureCLI/main.swift) the [AVFoundation](https://developer.apple.com/av-foundation/) framework to record the screen. The result is an H264 video, which is lossy. The noise you see may therefore be the H264 compression artifact.
- When a video file is converted to GIF, the color palette is reduced to 256 colors. Dithering can also cause the pixel colors to change periodically. Kap [uses](https://github.com/wulkano/kap/blob/a2e9b761cf81fa4848d5b946044ab788909dd748/main/convert.js#L213) FFmpeg’s default dithering algorithm, [which is `sierra2_4a`](https://ffmpeg.org/ffmpeg-filters.html#paletteuse).

…so let’s fix this.

![Graph after](https://dev-to-uploads.s3.amazonaws.com/i/mvq62c8610koa4s6cl39.png)

This looks much better, but how?

The basic idea is that we identify the **segments** inside the animation, and for each segment, we assign a single color to be used for the duration of that segment. A new segment will be created when the new frame’s color exceeds the threshold (RGB-Euclidean distance ≥ 18, arbitrarily chosen) compared to the color of the frame at the beginning of the segment. This is done for each pixel in the animation.

As a result, there is substantially less noise.

![Raw GIF frames after](https://dev-to-uploads.s3.amazonaws.com/i/2xo0clkk9em9z18dp4rd.gif)

You can [find the code that does this segmentation on GitHub](https://github.com/dtinth/a-gif-frame-sequence-optimizer-optimized-for-screen-recordings). It is written in JavaScript and uses the [Jimp](https://www.npmjs.com/package/jimp) library to process the animation data.

Finally, I performed a lossy optimization (using Gifsicle) and reduced the palette to 48 colors, resulting in the final 986 KB image.

## Final thoughts

This has been a fun project and I got to learn about how screen recordings are captured in Kap, how GIF files work and how they are compressed, and how to use the [Jimp](https://www.npmjs.com/package/jimp) library to work with pixel data inside image files. I also got to publish a web-based GIF file inspector.

There are many places that can be improved further. The optimization script is not optimized at all and runs very slowly (about 10x slower, compared to FFmpeg), due to its inefficiency (both time and space). There are still some noise in the final GIF image. But my curiosity has been satisfied, so I’ll leave it here.