DEV Community

Cover image for Fast GIF parsing on the web with WASM + Wuffs
Sam Thorogood for Google Web Dev

Posted on • Updated on

Fast GIF parsing on the web with WASM + Wuffs

GIFs, or JIFs, are one of those strange oddities of the internet. The format is slow, inefficient, and from the 80s (!)- did we even have color screens then?

Yet today, they're everywhere. But if you want to work with this format on the web—rather than just displaying it in an <img>—the best practice is to read a GIF's raw bytes and parse it using JavaScript.

This is admittedly esoteric, but pure JavaScript is slow compared to doing it in native C, as there's just a lot of low-level byte manipulation and work to do. How do we get the best of both worlds? Web Assembly, or WASM. In this post, I'll show you how to parse directly in the browser using WASM, which gives about a 2x speed increase over JS only.

To do this, we'll use Wuffs: a library for Wrangling Untrusted File Formats Safely. It generates modern and provably safe C code for dealing with multimedia or other encoded file formats. You can read more about it here.

The Demo

Here's fastgif, a library to decode GIFs based on Wuffs and WASM; and on the right, we use a popular JS-only GIF library. The time to parse the GIF is the key, and Wuffs on a modern MacBook is typically about 2x the JS-only version.
(Of course, you might not see such amazing results, but fastgif is faster in most environments. You can also try another browser! 🤞😅)

Take It Home Today

If you'd like to use fastgif in your site, check it out here. The library and demo work in all modern, evergreen browsers: Edge, Safari, Firefox and Chrome. Neat! 🤖📸

The Numbers

Speeds of fastgif vs other approaches in a log graph

nb: this is a log graph. Smaller is better.

Some thoughts-

  • On the web, fastgif is almost exactly the same speed as a native, unoptimized binary of Wuffs—the C program running from a command line.
  • When we use -O3 to compile the native binary, it speeds up enormously—those optimizations seemingly don't apply to WASM.
  • The second example, with just a single frame, is very fast for the native case: this hints that moving lots of bytes around is costly for the web.
  • There's also a bit of startup/parse time that's encapsulated in the web-based approaches. Repeated decodes would probably be faster.

Nonetheless, fastgif is probably the fastest way to decode GIFs on the web. As an aside: this sort of work is best suited for a Worker, off the main thread—but that's out of scope of this post.

Want To Know More?

If you'd like to learn more about how fastgif takes Wuffs and makes it speedy ⚡💨 for the web, read on!


The rest of this post is for those folks who:

  • Have a good understanding of JavaScript
  • Have a basic knowledge of C and the command-line
  • Want to port any native library—not necessarily just Wuffs—to the web

Step Zero: fetch Emscripten

While you can write WASM files by hand or with other tools, the main toolchain to build Web Assembly is Emscripten. You can follow its install instructions here.

Once you're setup or you want to resume coding, be sure to source the emsdk_env.sh for your platform:

# Linux/macOS
source ~/Desktop/path/to/emsdk/emsdk_env.sh

# Windows
C:\path\to\emsdk\emsdk_env.bat  # or .ps1 for PowerShell
Enter fullscreen mode Exit fullscreen mode

Emscripten needs certain versions of Node, Clang and some other environment variables to be set. This also means you can set up Emscripten without installing it as root, as it won't replace anything on your system.

Step One: build a naĂŻve demo

Wuffs, like many other native C libraries, has some demo applications—those with an entry point of int main(). This will read input from the command line, and output it to the shell. This doesn't map to what we want to do on the web, but it's a good place to start.

In Wuffs' case, there's a few examples under example/. I started by trying to just compile the GIF player, which normally outputs ASCII art to the terminal.

git clone git@github.com:google/wuffs.git
cd wuffs/
source ~/Desktop/emsdk/emsdk_env.sh

# finally, compile:
emcc -s WASM=1 -o gifplayer.html example/gifplayer/gifplayer.c

# and run a quick webserver of choice:
python -m SimpleHTTPServer
serve
Enter fullscreen mode Exit fullscreen mode

This will generate gifplayer.html, gifplayer.js and gifplayer.wasm. This is Emscripten being helpful, mostly for debugging and getting started—now, if you open up http://localhost:5000/gifplayer.html in your browser, you'll see:

Assertion failed: TOTAL_MEMORY not big enough for stack

... actually, we see an error message. 💥

Some quick Googling 🔍 later, and it looks like we need to allow the program more memory. Let's recompile:

emcc -s WASM=1 -s TOTAL_MEMORY=128MB -o gifplayer.html example/gifplayer/gifplayer.c 
Enter fullscreen mode Exit fullscreen mode

Great! Open now, and we'll see this:

Waiting for input

When you ask for input using e.g. scanf or reading from the command line, Emscripten will by default use the JavaScript method prompt() to ask for data. Try pasting in some text—the browser will continue prompting until you Cancel the input, which counts an EOF.

Unfortunately, even copying and pasting raw GIF bytes into this form will do.. nothing. If you do this and read the browser's console, you'll see a message about "gif failed parsing header".

So the code is running- yay 🎉! But since we can't give it real bytes, only a JavaScript string, nothing happens 🙅.

Step Two: Send raw bytes to the GIF decoder

As gifplayer.c has an int main() method in C, it's going to run something- waiting for input- when we start it. While it's a good place to start, I want to be able to pass it raw bytes—perhaps from a window.fetch or AJAX request.

Let's modify it to do so.

a. Remove main method

I can open up the gifplayer.c file and modify it, removing the main and fail methods just by commenting them out:

/*
int fail(const char* msg) {
  ...
}

int main(int argc, char** argv) {
  ...
}
*/
Enter fullscreen mode Exit fullscreen mode

b. Replace reading from input, with accepting a passed buffer

Let's comment out the read_stdin method, and add a read_buffer method to replace it.

/*
const char* read_stdin() {
  while (src_len < SRC_BUFFER_SIZE) {
    ...
  }
  return "input is too large";
}
*/

// add this method
const char* read_buffer(uint8_t *buf, size_t len) {
  src_buffer = buf;
  src_len = len;
  return NULL;
}
Enter fullscreen mode Exit fullscreen mode

However, src_buffer used to be a fixed sized buffer based on SRC_BUFFER_SIZE. This bounded the amount of data read from the command line. Instead, we're going to accept a pointer to somewhere in memory. Let's update the declaration of src_buffer:

// don't need a fixed buffer, now just a pointer
//uint8_t src_buffer[SRC_BUFFER_SIZE] = {0};
uint8_t *src_buffer;
size_t src_len = 0;
Enter fullscreen mode Exit fullscreen mode

c. Recompile and pass data

Now, let's recompile and pass in data via the read_buffer method directly in our JavaScript console. To expose a method to JS properly, we need to indicate it in our compile pass. We'll also need the play method to display the output.

emcc -s WASM=1 -s TOTAL_MEMORY=128MB \
    -s EXPORTED_FUNCTIONS="['_read_buffer','_play']" \
    -o gifplayer.html \
    example/gifplayer/gifplayer.c
Enter fullscreen mode Exit fullscreen mode

Ok, so now let's re-open the Emscripten window. Nothing happens and no relevant error messages appear in the console—we're no longer doing anything in main().

However, we can run the Module._read_buffer method to load our buffer in. By default, the Emscripten toolchain exposes these methods on the global Module. It also exposes the C standard library—methods like malloc and free.

So to test, there's a few steps. We need to get the source of the image, we need to malloc it some memory, pass that to read_buffer, and then play it. The following code does just that—so paste it into the JavaScript console:

// the following base64 string is really long- just copy and paste this whole section
// it's the base64 encoded version of:
// https://raw.githubusercontent.com/google/wuffs/master/test/data/muybridge.gif

const testGifB64 = "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";
const buf = Uint8Array.from(atob(testGifB64), (c) => c.charCodeAt(0));
const at = Module._malloc(buf.length);
Module.HEAP8.set(buf, at);
Module._read_buffer(at, buf.length);
Module._play();
Enter fullscreen mode Exit fullscreen mode

With any luck, you should see a decoded, moving ASCII art horse 🐎💨 playing in your console:

Horse in ASCII art in console

Congrats! At this point, I rewarded myself with a donut. You should do the same. 🍩

Step Three: Don't display to the screen

Right now, we just play the ASCII art to the console. Emscripten even implements the usleep method with a busy loop: the GIF player, which pauses between frames, actually just loops to block the browser from proceeding. Yuck!

Instead of this, let's get access to the actual frames. First, inside the play() method, comment out anything to do with sleep and displaying to the screen:

#ifdef _POSIX_TIMERS
    /*
    if (started) {
      ...
    }
    */
#endif

    //ignore_return_value(write(stdout_fd, print_buffer, n));
Enter fullscreen mode Exit fullscreen mode

Now, we want to use the EM_ASM_ macro, which lets us call JavaScript inline inside C code. This is purely magic, which takes the string inside the macro and puts it Emscripten's output JS helper. So the rest of the method should now be:

    // .. continued from above

    cumulative_delay_micros +=
        (1000 * wuffs_base__image_buffer__duration(&ib)) /
        WUFFS_BASE__FLICKS_PER_MILLISECOND;

    // .. add this bit
    EM_ASM_({
      onframe($0, $1, $2);
    }, print_buffer, n, cumulative_delay_micros);

    // TODO: should a zero duration mean to show this frame forever?
  }
  return NULL;
}
Enter fullscreen mode Exit fullscreen mode

Finally, be sure to add the Emscripten header at the top of the file, as now we're using new macros:

#include <emscripten.h>
Enter fullscreen mode Exit fullscreen mode

You can now recompile using the same command as before:

emcc -s WASM=1 -s TOTAL_MEMORY=128MB \
    -s EXPORTED_FUNCTIONS="['_read_buffer','_play']" \
    -o gifplayer.html \
    example/gifplayer/gifplayer.c
Enter fullscreen mode Exit fullscreen mode

And reload the page. Now, run our helper blob again, but this time provide an onframe method:

const frames = [];
const decoder = new TextDecoder();
function onframe(buf, len, micros) {
  const s = decoder.decode(Module.HEAP8.slice(buf, buf + len));
  frames.push({s, ms: micros / 1000});
}

const testGifB64 = "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";
const buf = Uint8Array.from(atob(testGifB64), (c) => c.charCodeAt(0));
const at = Module._malloc(buf.length);
Module.HEAP8.set(buf, at);
Module._read_buffer(at, buf.length);
Module._play();
Enter fullscreen mode Exit fullscreen mode

Once you've run the above code, be sure to log the frames object, like this.

Frames in JavaScript console

Phew! Now, you could modify them, print them out, or use them at your leisure. This definitely isn't perfect, because you need to add onframe to your global scope (!). But, Emscripten's helper libraries are already pretty bad at this—they're already adding Module to your window.

Step Four: Get the image bytes

The demo we've been fixing actually just renders ASCII art to the console. Let's actually simplify the code and just get the raw bytes of the GIF.

Let's update the gifplayer.c to pass more, varying arguments to the onframe JavaScript method:

// update the forward declaration at top
extern void onframe(uint32_t *, uint32_t, uint32_t, uint32_t);

// -- removed for brevity --

    // update EM_ASM_
    EM_ASM_({
      onframe($0, $1, $2, $3);
    }, dst_buffer, width, height, cumulative_delay_micros);
Enter fullscreen mode Exit fullscreen mode

Now, recompile the code—just using the same command as before, hit ⬆️ in your terminal.

In your browser, reload and run this snippet of code to generate ImageData instances, enough to render for us:

const frames = [];
const decoder = new TextDecoder();
function onframe(buf, width, height, micros) {
  const len = width * height;
  const buf = new Uint32Array(len);
  buf.set(Module.HEAPU32.subarray(buf, buf + len))
  const clamped = new Uint8ClampedArray(buf.buffer);
  const imageData = new ImageData(clamped, width, height);
  frames.push({imageData, ms: micros / 1000});
}

const testGifB64 = "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";
const buf = Uint8Array.from(atob(testGifB64), (c) => c.charCodeAt(0));
const at = Module._malloc(buf.length);
Module.HEAP8.set(buf, at);
Module._read_buffer(at, buf.length);
Module._play();
Enter fullscreen mode Exit fullscreen mode

Finally, run this code to display frames[0] to the screen:

const canvas = document.createElement('canvas');
document.body.appendChild(canvas);
const context = canvas.getContext('2d');
context.putImageData(frames[0].imageData, 0, 0);
Enter fullscreen mode Exit fullscreen mode

Congratulations! You made an image appear! 🖼️🎉

There's still, obviously, a few stepping stones from what we've just done to creating a usable library like you saw in fastgif, before.

One challenge is that Emscripten's helper layer is quite enormous (~100kb of JS, more HTML), and is really designed for monolithic programs—where all your code is in C, rather than just trying to wrap up a single library.

If you'd like to read more about Emscripten and how to avoid using its helper layer, its "runtime", check out my follow-up post here. It's way more technical than this post, and that's saying something—some knowledge of C required.


Done

I hope you've learned either:

a) that WASM is cool, and can speed up traditionally complex computational tasks like GIF decoding: and/or
b) that it's not too hard to port a native C library—albeit in in a very basic way—to the Web.

Thanks for reading! ✨

Top comments (12)

Collapse
 
adambankin profile image
Adam Bankin

Great read Sam. I do find that gifuct is faster on Safari Mobile (iOS 11.4.1, iPhone 7 plus) but I expect this is due to lack of WASM support…?

Collapse
 
samthor profile image
Sam Thorogood

So WASM actually is supported there. Safari is sometimes slower (my friend commented this too), but we're not really sure why—my hunch is that Safari's JS implementation is very good for this sort of stuff, making gifuct quite fast.

Chrome and Firefox have the more expected pattern.

Collapse
 
weswedding profile image
Weston Wedding

Wow, nice!

I was working on a gifuct-based meme assembler as a side project but maybe I ought to switch to WASM.

Collapse
 
samthor profile image
Sam Thorogood

Thanks, Weston! WASM isn't supported everywhere, but it's pretty amazing how quickly it came to all four modern evergreen browsers.

Collapse
 
alexbender profile image
Alex Bender

Hi Sam!
Thank you for such a good explanation of wasm: now I feel that I can start doing something with it, really intriguing.
One note:
seems that you have two Three steps.
Thanks!

Collapse
 
samthor profile image
Sam Thorogood

Whoops, fixed! 🤣

Collapse
 
alexbender profile image
Alex Bender

so fast, haha. Thanks!

Collapse
 
kristobaljunta profile image
Kristobal Junta • Edited

Somehow, I observe an 8x speed increase on the latest Chrome on Android

Collapse
 
samthor profile image
Sam Thorogood

That's great! We see a huge spread—some of it, I suspect, is that we are hitting initial startup work in either case. Running the code 100 times on user's browsers is not considered friendly, since I'd size their devices up for minutes, but that would be the way to really determine what the time is.

Collapse
 
ben profile image
Ben Halpern

Wow, so cool!

Collapse
 
nektro profile image
Meghan (she/her)

Great article! I'm very excited about the future of WASM :D

Collapse
 
onefabis profile image
Alexander Smirnov • Edited

Hello, at step 3 I was faced with the next error:
EM_ASM should not receive i64s as inputs, they are not valid in JS