Animated Gifs for Text-Based UIs
Tags: go, tui, gif, tview
Published: 2020-03-03 07:44:48

## Context

In my spare time, I've been working on a simple text-based game for my 4 year old to play.  He found my 10 year old Linux laptop and has been having a blast with the terminal.

When I started writing the game, I picked GoLang as the language and researched a variety of TUI (text user interface) libraries.  After some quick prototypes, I settled with [`tview`](https://github.com/rivo/tview) for the solid primitives and ease of keyboard interaction.

## Objective

During development, I realized it could use more feedback when getting an answer right or wrong.  The way I wanted to solve that was by adding simple animations (e.g. happy vs sad cat).  Nothing in `tview` (or the other libraries) had anything readily available.

The following is the process I went through to build a new `tview` object that supported displaying animated gifs:

1. Parse the image frames and timing information
2. Display the frames on the screens
3. Scale for multiple animations

## Step 1. Parsing Gifs

GoLang has a simple built in Gif library [`image/gif`](https://golang.org/pkg/image/gif/).  After decoding the file, you are left with:

 - Set of frames as `image.Paletted`
 - Set of delays between frames as `int`

{% gist https://gist.github.com/stjohnjohnson/f39530c6ec12d44523a492f8a64da2b2 file=parse.go %}

## Step 2. Image to Text

I stumbled across [`pixelview`](https://github.com/Omnikron13/pixelview) which converts images into formatted text for `tview` by using colored half-block unicode characters (`▀`).

It also accepts the `image.Image` interface (which is what `image.Paletted` above uses).

Now I can display each of those Gif frames into a `tview` box.

{% gist https://gist.github.com/stjohnjohnson/f39530c6ec12d44523a492f8a64da2b2 file=image2text.go %}

## Step 3. Optimizing

The above prototype wouldn't _really_ work.  Besides the single-threaded iteratation through a single gif (which prevents us from adding multiple gifs), there are performance issues to take into account.  Depending on the speed and the number of frames, you would see CPU load during quick rendering and constant conversion of image objects.  Additionally, the use of `TextView` and the added features (scrolling and highlighting) slows down rendering.

To reduce that impact and support multiple images, I switched to my own `Box` class and made some important changes.

First, I switched the animation to be on-demand.  That way, whenever the view _needed_ to be re-drawn it would calculate the current frame it _should_ be on and render that.

I did that by recording the start time of the view and iterating on each frame delay until we knew where we were.

{% gist https://gist.github.com/stjohnjohnson/f39530c6ec12d44523a492f8a64da2b2 file=currentframe.go %}

Second, I removed our usage of `TextView` and made my own stripped down version of the `TextView` draw function.

{% gist https://gist.github.com/stjohnjohnson/f39530c6ec12d44523a492f8a64da2b2 file=draw.go %}

Finally, I triggered a global re-draw on a periodic basis.  That way all Gifs would be animated consistently (although not as smooth).

{% gist https://gist.github.com/stjohnjohnson/f39530c6ec12d44523a492f8a64da2b2 file=animate.go %}

## Final Product

In the end, I created a library that allowed me to add basic animated gifs into my son's game using a single interface.

[Here](https://github.com/stjohnjohnson/gifview) is the library as well as the [documentation](https://pkg.go.dev/github.com/stjohnjohnson/gifview).

And this is a simple example (dancing banana not included):

{% gist https://gist.github.com/stjohnjohnson/f39530c6ec12d44523a492f8a64da2b2 file=example.go %}

![working-demo](https://user-images.githubusercontent.com/622065/75753122-e7392b00-5cde-11ea-869b-e0ea21806ddd.gif)