Skip to content


JavaScript <canvas> to (Animated) GIF

This is the GIF which was generated from the canvas.

This is the raw canvas element saved as a non-animated PNG

I’ve tried this before but it didn’t work. <canvas> can’t do toDataURL(‘image/gif’), and the primitive GLIF library couldn’t do much so I never had the opportunity to test my gif-merging code that I had. But I’m at it again, this time, porting it from the AS3GIF library, an awesomely comprehensive bitmap to binary gif encoder that even supports LZW compression (and the patent has luckily expired. Yay!). AS3Gif is supposed to “play and encode animated GIFs”, but since web pages can usually natively play GIFs fine, it’s only a port of the GIFEncoder portions of the library. And it works really well. The rest of this post is copied from the Github readme. Interesting how the w2_embed/anonybot embed post was a blog post turned into readme, this is a readme turned into blogpost. I’ll start with a link to the Github repo anyway:

http://github.com/antimatter15/jsgif

Basic Usage

Since it pretty much is GIFEncoder, you could consult the as3gif how-to page

But there are some differences so I’ll cover it here anyway.This is the GIF which was generated from the canvas.

You first need to include the JS files. It’s probably best if you include it in this order, but it shouldnt’ matter too much.

<script type="text/javascript" src="LZWEncoder.js"></script> <script type="text/javascript" src="NeuQuant.js"></script> <script type="text/javascript" src="GIFEncoder.js"></script> 

If you want to render the gif through an inline <img> tag or try to save to disk or send to server or anything that requires
conversion into a non-binary string form, you should probably include b64.js too.

<script type="text/javascript" src="b64.js"></script> 

Simple enough right? Now to convert stuff to GIF, you need to have a working or at least some imageData-esque array.

<canvas id="bitmap"></canvas> <script> var canvas = document.getElementById('bitmap'); var context = canvas.getContext('2d'); context.fillStyle = 'rgb(255,255,255)'; context.fillRect(0,0,canvas.width, canvas.height); //GIF can't do transparent so do white context.fillStyle = "rgb(200,0,0)"; context.fillRect (10, 10, 75, 50); //draw a little red box 

Now we need to init the GIFEncoder.

 var encoder = new GIFEncoder(); 

If you are making an animated gif, you need to add the following

 encoder.setRepeat(0); //0 -> loop forever //1+ -> loop n times then stop encoder.setDelay(500); //go to next frame every n milliseconds 

Now, you need to tell the magical thing that you’re gonna start inserting frames (even if it’s only one).

 encoder.start(); 

And for the part that took the longest to port: adding a real frame.

 encoder.addFrame(context); 

In the GIFEncoder version, it accepts a Bitmap. Well, that doesn’t exist in Javascript (natively, anyway) so instead, I use what I feel is a decent analogue: the canvas context. However, if you’re in a situation where you don’t have a real <canvas> element. That’s okay. You can set the second parameter to true and pass a imageData.data-esque array as your first argument. So in other words, you can do encoder.addFrame(fake_imageData, true) as an alternative. However, you must do an encoder.setSize(width, height); before you do any of the addFrames if you pass a imageData.data-like array. If you pass a canvas context, then that’s all okay, because it will automagically do a setSize with the canvas width/height stuff.

Now the last part is to finalize the animation and get it for display.

 encoder.finish(); var binary_gif = encoder.stream().getData() //notice this is different from the as3gif package! var data_url = 'data:image/gif;base64,'+encode64(binary_gif); 

Docs

Each of the files exposes a single global (see, at least it’s considerate!). But since there’s three files, that means that there’s three globals. But two of them are more of supporting libraries that I don’t totally understand or care about enough to document. So I’m just gonna document GIFEncoder.

new GIFEncoder() This is super parent function. You really don’t need the new keyword because It’s not really even using any special inheritance pattern. It’s a closure that does some var blah = exports.blah = function blah(){ for no good reason. Anyway, it returns an object with a bunch of methods that the section will be devoted to documenting. Note that I’ve never tested more than half of these, so good luck.

Boolean start() This writes the GIF Header and returns false if it fails.

Boolean addFrame(CanvasRenderingContext2D context) This is the magical magic behind everything. This adds a frame.

Boolean addFrame(CanvasPixelArray image, true) This is the magical magic behind everything. This adds a frame. This time you need
you pass true as the second argument and then magic strikes and it loads your canvas pixel array (which can be a real array, I dont care and I think the program has learned from my constant apathy to also not care). But note that if you do, you must first manually call
setSize which is happily defined just below this one.

void setSize(width, height) Sets the canvas size. It’s supposed to be private, but I’m exposing it anyway. Gets called automagically as the size of the first frame if you don’t do that crappy hacky imageData.data hack.

void setDelay(int milliseconds) the number of milliseconds to wait on each frame

void setDispose(int code) Sets the GIF frame disposal code for the last added frame and any subsequent frames. Default is 0 if no transparent color has been set, otherwise 2. I have no clue what this means so I just copypasted
it from the actionscript docs.

void setFrameRate(Number fps) Sets frame rate in frames per second. Equivalent to setDelay(1000/fps). I think that’s stupid.

void setQuality(int quality) Sets quality of color quantization (conversion of images to the maximum 256 colors allowed by the GIF specification). Lower values (minimum = 1) produce better colors, but slow processing significantly. 10 is the default, and produces good color mapping at reasonable speeds. Values greater than 20 do not yield significant improvements in speed. BLAH BLAH BLAH. Whatever

void setRepeat(int iter) Sets the number of times the set of GIF frames should be played. Default is 1; 0 means play indefinitely. Must be invoked before the first image is added.

void setTransparent(Number color) Sets the transparent color for the last added frame and any subsequent frames. Since all colors are subject to modification in the quantization process, the color in the final palette for each frame closest to the given color becomes the transparent color for that frame. May be set to null to indicate no transparent color.

ByteArray finish() Adds final trailer to the GIF stream, if you don’t call the finish method the GIF stream will not be valid.

String stream() Yay the only function that returns a non void/boolean. It’s the magical stream function which should have been a getter which JS does support but I didnt’ feel like making it a getter because getters are so weird and inconsistent. Like sure there’s the nice pretty get thing but I think IE9/8 doesn’t implement it because it’s non standard or something and replaced it with a hideously ugly blah blah. So Anyway, it’s a function. It returns a byteArray with three writeByte functions that you wouldn’t care about and a getData() function which returns a binary string with the GIF. There’s also a .bin attribute which contains an array with the binary stuff that I don’t care about.

WebWorkers

The process isn’t really the fastest thing ever, so you should use WebWorkers for piecing together animations more than a few frames long. You can find the rest of the WebWorkers section on the actual readme, because the rest is just a huge block of code with comments.

http://github.com/antimatter15/jsgif

Posted in Ajax Animator, Animated GIF Encoder, Multimedia Codecs, Stick Figures.

Tagged with , , , , , , , , .


21 Responses

Stay in touch with the conversation, subscribe to the RSS feed for comments on this post.

  1. Howard Yeend says

    Very nice. Do you know of a way to split an animated gif into individual frames using js and canvas? Or a way to step through gifs in JS?

  2. admin says

    You should look at the spec. I think you would just split by blocks and add the header to each one.

  3. Robin Hayes says

    This works really well, but I’ve come across a problem.
    I’m trying to export a two-frame gif, which has 50 colours total; no colours change between the two frames. However, even with setQuality(1), the colours are reduced to 34, and are different on each frame. Adding more setQuality()s, between frames, has no effect on the second frame. I’m working with pixel art, so getting the colours right matters a lot.

    Is there a way to bypass the quality filter entirely? Thanks a lot.

  4. Forrest O. says

    Has anybody figured out how to get transparency working? Every 10th frame or so it seems to work. There might be something that didn’t translate right in findClosest()

    My use: http://meemoo.org/iframework/#/example/flipbook

  5. pingu7 says

    Hi. Thanks for sharing this. I used it on a little project here: http://loadersuite.com/ and I meant to come back and offer a fix.

    In NewQuant.js, try commenting out transIndex = findClosest(transparent); and replace with:

    transIndex=nq.map(transparent[0]&255,transparent[1]&255,transparent[2]&255)}

    Set the transparency colour with something like: encoder.setTransparent([0xEF,0xEF,0xEF]). That seemed to do the trick for me.

    Thanks again

  6. pingu7 says

    *Sorry, in GIFEncoder.js, I meant to say

  7. Forrest Oliphant says

    This patch should fix the color/size issue: https://github.com/antimatter15/jsgif/pull/8/files

  8. Anonymous says

    fdsdsfdfd

  9. ptk says

    hi,
    I’ve been researching the options available for client side animated GIFs and I must say that I overlooked this one because of how terribly complicated you made it sound on this page and I was under the impression that this library was not able to control the speed of the frames (both by your animated GIF examples and it not being mentioned anywhere)

    as it turns out, I was pleasently surprised that this was one of the easier solutions I tried. I think it might benefit your library if you gave the readme a slight overhaul with an overview of functions so that people may be more inclined to adopt this useful utility. just a thought. thanks.

  10. George says

    Hi there, I’m trying to get this image in my canvas to export to the gif, Please take a look at my code at http://www.anythoughts.net/gifgo/jsgif-master/Demos/test.html and see what the problem is. Thanks.

  11. Jesse Chisholm says

    George, I believe the problem is that your script calls context.finish() before the imageObj.onload gets called. So it is too late to inject the pattern frame.

    Try putting all of the finish stuff in the imageObj.onload method. I.e., onload adds the pattern, then adds the “Zibri” then finishes, then sets the display image.src.

    CAVEAT: I have not tried this, just looking at timing of code execution.

  12. Jesse Chisholm says

    PS: I’ve tried it now, and it works.

    Sorta.

    The “Zibri” shows up in imageObj, which I didn’t expect.
    You will need to do more code juggling to avoid actually displaying imageObj.

  13. shopping online says

    Wonderful conquer! I have to newbie as you fix your internet site, the best way could possibly my spouse and i sign up to for any blog site site? The particular bank account taught me to be a new appropriate package. I had been a small amount knowledgeable of your your over the air supplied sparkly see-through idea

  14. Garage Remodeling says

    This is certainly a smart way to remain organized.
    I understand my garage and also basement are really a mess.
    I in the morning certainly devoloping on making anything similar to this.

  15. http://pinterest.com/ says

    Water with a Homemade ND Filter

  16. https://plus.google.com/ says

    Which things occurs all of the the time. Those are really things that go bad real swiftly
    when it isn’t performing properly.

  17. free project tv says

    Online television is definitely the wave of the future.
    As broadband speeds get faster, people will be watching
    their shows on sites like this.

  18. Fredericka says

    Away door i similar to this tool. However around time it’s launched
    to wear down. I did discover a way to shock the cells backside,
    good since new.

  19. Noel Whitemore says

    An easy way to grab frames from canvas animations is to use the JavaScript console built into your browser. There is an article on Stack Overflow titled “grabbing-each-frame-of-an-html5-canvas” that demonstrates how to do this, but the basic principle is that the toDataURL() method allows you to access a single instance of the canvas image and this can then be written back to the browser as an image. To capture a complete sequence, the SetInterval() method can be used.

  20. Shonda says

    First of all I would like to say excellent
    blog! I had a quick question that I’d like to ask if you do not mind.
    I was interested to find out how you center yourself and clear your head
    prior to writing. I’ve had difficulty clearing my thoughts in getting my thoughts out.
    I do take pleasure in writing but it just seems like the
    first 10 to 15 minutes are generally lost just trying to figure
    out how to begin. Any ideas or hints? Thanks!

  21. http://www.bowtiesneckties.com says

    I’m truly enjoying the design and layout of your site. It’s a very easy on the eyes which makes it much
    more pleasant for me to come here and visit more often.
    Did you hire out a developer to create your theme?
    Exceptional work!



Some HTML is OK

or, reply to this post via trackback.