p5.js
p5.js copied to clipboard
Implement `saveGif` as a native p5 function
🚧 This branch will be the work in progress for the implementation of the public saveGif function for the '22 GSoC edition. 🚧
Save Gif
This PR resolves issue #5118, in which a thorough discussion is had about whether p5.js should include a native GIF encoding solution or not.
How it works
In the previously mentioned issue some proposals were discussed about how the interface for this functionality should be. The easiest I found to both implement and use was the following. The saveGif function admits at most 3 arguments for now:
saveGif(filename, duration, delay)
filenamefor the name of the file downloaded to your computerdurationduration in seconds that you wish to download from your animationdelaydelay in seconds that you wish to wait before recording the animation. The animation will nevertheless have a duration of what you specified on thedurationargument.
As an example, I think it is best to use it within the mouse/keyboard events:
function mousePressed() {
if (mouseButton === RIGHT) {
saveGif('mySketch', 3, 1);
}
}
This function will take all the current frames of the present animation, save them in a buffer and then proceed to generate a palette, apply this palette to every frame, optimize for transparency and encode it in a GIF blob, that will then force to download.
Functionality can be extended in lots of places but I believe that the current state works very conveniently by adding just 3 extra lines of familiar code. I plan on keep contributing to this functionality even after the GSoC to make it as convenient as possible.
Changes
- Most important change is in src/image/loading_displaying.js, which is where the main
saveGiffunction lives. Alongside this function, the functions_generateGlobalPaletteand_pixelEqualswere also added. - Second important change is in src/image/image.js, where the old
saveGiffunction was renamed to a more obscure and explicitencodeAndDownloadGif.- Alongside this change, the file src/image/p5.Image.js was also changed to consider this new function name.
- Same goes with the test test/unit/image/downloading.js, which was updated to evaluate the
encodeAndDownloadGiffunction.
⚠ Some other changes
Some other changes were made for the sake of either speed or convenience. These changes are completely opinionated and may include breaking changes that I may not be aware of, so please consider them carefully.
- The
package.jsonfile is modified to include the package gifenc, created by Matt DesLauriers. He personally reached out to me regarding this topic and it turns out that the package includes two key functions that make the GIF rendering not only much easier, but also much faster.- Naturally, the
package-lock.jsonfile also changes.
- Naturally, the
- The
Gruntfile.jsis also changed. In order to include Matt's package, we needed to first compile the javascript code to babel code, before uglifying it. The uglify module wouldn't process the ES6 notation for some reason, although I've seen in some places that the project is already accounting for that. That is thebabelmodule that appears in lines likegrunt.loadNpmTasks('grunt-babel');- Also, the
ecmaVersionwouldn't pass thenpm run buildnornpm run lintnor any other command of this kind altogether. So I bumped up the version to 8 and it seems to be working fine.
- Also, the
Again, all these changes are considered from a rather ignorant and novel standpoint, so maybe I am making very questionable decisions here. I ask the community to pay special attention here, though it does not need to be said that I will try my best for this to be as close to perfect as it can be.
Screenshots of the change:
These are some screenshots of the tool in use. We wrote the function keyPressed and we are tracking the 's' key. When pressed, a gif will be rendered and downloaded. A beautiful sketch by @TomasMiskov is presented.

During the saving and rendering, a little paragraph appears indicating the progress. I feel like this is very important since, even though it usually only takes a couple of seconds, a couple of seconds in web time is an almost infinite amount of time.

The resulting gif is presented here, just shy over 1MB in size:

PR Checklist
- [x]
npm run lintpasses - [x] Inline documentation is included / updated
- [ ] Unit tests are included / updated
@limzykenneth @Qianqianye don't know exactly who I have to ping for this but this is review-ready! let me know if I have to proceed further before the review or do something else. hope you have a great day :)
Thanks so much for the review, @davepagurek!
Great work @jesi-rgb! Thanks @davepagurek for the review. As @jesi-rgb 's mentor @endurance21, please feel free to merge the PR when it's ready. Thank you!
@endurance21 Well! The example errors we talked about are fixed! To be honest, there was nothing to fix on the first place.
Either grunt or firefox or something was caching the old code and preveting the new code to be used in the example. I just removed every p5.js file under /lib and re-built again.
Unless something else can be added, I think we are ready!
Also, I notice that gifs turn out all black if I call saveGif inside setup(). I'm not sure exactly why this is happening yet, since this is all that happens between setup() and draw(). I suspect it might have something to do with the first draw is getting called after setup finishes regardless of noLoop() being called, so there could be some double-drawing? e.g. adding this to the top of saveGif fixes it:
if (this._setupDone) {
await new Promise((res) => window.requestAnimationFrame(res));
}
That's a bit of a hack though, so in any case, I think it's also fine to add something to the documentation saying it has to be called outside of setup for now.
@davepagurek yes, I knew that calling within setup causes problems and wanted to make it possible for those that may unintentionally put it there to just work.
Unfortunately, the snippet of code you shared didn't work for me! I'll just state that in the documentation for now, while we manage some ways to make this work :)
Unfortunately, the snippet of code you shared didn't work for me! I'll just state that in the documentation for now, while we manage some ways to make this work :)
Sounds good! Also, that's interesting that it doesn't work, I guess there's more to it than I thought 🤔 Definitely out of scope for this PR then! If you feel like sending the sketch it doesn't work for, I can poke around in it and see if I can debug some more!
@jesi-rgb great work! I have reviewed the PR and tested it locally, and happy to say that the animation to gif feature is working just fine and is in the stage to be made available to the public and thus merging now.
Disclaimer : This PR contains the one basic discussion regarding the "babel parsing the es6-es5 code and the role of uglifyJs ", which is still a matter of discussion within the group, however for now the solution provided by this pr seems reasonable to that discussion and if a better solution is discovered in near future, it shall definitely be incorporated.