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. 1418x406, 8 fps, 40 seconds (323 frames).
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:
This post outlines how I achieved this level of compression.
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 and optimizing them. This has inspired me to create a rudimentary GIF file inspector. 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 library.
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.
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.
I picked a pixel (443, 234) and graphed its RGB values over time as it goes through the animation.
I can think of two possible reasons this may happen:
- Kap uses aperture-node which in turn uses the AVFoundation 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 FFmpeg’s default dithering algorithm, which is
…so let’s fix this.
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.
Finally, I performed a lossy optimization (using Gifsicle) and reduced the palette to 48 colors, resulting in the final 986 KB image.
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 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.