NodeJS image processing with node-gd

Written by Vincent Bruijn

Since a couple of years, I am the maintainer of the NodeJS library called node-gd, which are JavaScript bindings for NodeJS to the libgd graphics library. During the past years, many major changes have taken place in the code base of node-gd to keep it up to date with the pace of JavaScript evolution. In this article I will go deeper into the technical changes that I implemented in the past years. It is not an explanation of how node-gd works or what you can do with it. For that I would suggest to directly visit its documentation page

Probably Monet

Somewhere in June 2014, I was working on a hobby project in which I wanted to manipulate JPEG images via code. I used to use PHP for these kind of projects mostly, but with the rise of serverside JavaScript, I also wanted to use JavaScript for this purpose. This way, I thought, I would learn more about serverside JavaScript, especially NodeJS and the Express web-framework. At the time there were some other image manipulation libraries for NodeJS but, as far as I recall, they either did not fulfill my wishes or were just too hard to get installed. Besides, I was aquainted with PHP’s image processing functions, like imagecreatefromjpeg and the others of the GD API.

LibGD

LibGD, not necessarily related to PHP, is a library written in C for creating and manipulating different kinds of images. The library exists already quite a while: the earliest copyright notice I could find in the code dates back to 1994. Within PHP, the earliest changelog message I could find with regard to libgd dates back to PHP version 4.3.0 stating:

Bundled GD library 2.0.1 with php (ext/gd/libgd) (Rasmus, Jani, Markus, Edin)

In the years I was writing PHP mostly, around 2008-2010, I had also written some hobby projects with the GD functions, so I knew quite well what was possible with it. One of these projects being Maleglitch, which partly consists of a PHP script that does a trial-and-error bending of image data. The code injects arbitrary strings into Jpeg image data multiple times, with the purpose of getting a nice glitched end result.

Maleglitch Website

node-gd

The origins of node-gd lay with Taegon Kim, who started to develop node-gd in 2010. That means that at the time I got involved, it existed for about 4 years already. Taegon Kim wrote the initial C++ code that worked with the NodeJS 0.4.8 add on API. And indeed, it’s C++ code that makes the binding between JavaScript and LibGD. This early version contains only a few functions available in libgd, the main functions for opening different types of images. By the time others got involved, the API of node-gd was extended with more and more functions which are available in libgd, for example functions to apply filters to images.

What I like about libgd, apart from the API that I already knew, was the extremes to which for example JPEG lossy compression could be bent. When saving a JPEG file via libgd with a compression level of zero (losing most image data), you will get a cubist-like image consisting of large pixel blocks of faint colors.

Saving an image with JPEG compression 0, you will get a cubist-like image consisting of large pixel blocks of faint colors.

NAN

When I took the maintainance of node-gd upon me, I first fixed that it was able to build on MacOS. Intrigued by C++, I set off to see whether I was able to implement NAN, the “Native Abstractions for Node.js”. I have no experience and no schooling in C++, so all I did was set up the build tools, dig into NAN’s github page and documentation, modify some code, build it, and analyze what happened. It took me a while, but at some point I got the hang of how NAN, v8 and libgd could work together (more or less).

More than half a year after getting node-gd to work on MacOS, I did my first commit for the port to using NAN. After getting the hang of it, I decided to expose more of the libgd API via node-gd. Also, since one of the earlier maintainers had added tests, I decided to add more tests. After that, I ditched a dependency on CoffeeScript because I wanted to have it depend on as few packages as possible.

Time passed, in which I did some additional private projects with node-gd and I kept maintaining the package. Not many changes were needed, nor were many issues opened or bugs found. This does not mean that node-gd is production ready, my C++ skills are not advanced enough in order for me to ensure all code works according best practices. I did invest additional spare time in extending the documentation: I read the libgd code to understand how certain things work and tried to add as many code examples as possible.

N-API

Last year I noticed v8 had evolved, just like NAN, and deprecation warnings were thrown during build time. I considered it the right moment to fix these issues. The I came to know about the Native Addons API, in short N-API, available in NodeJS since verison 6.x.x. And since I was up to something new, I decided to see if I could port node-gd to use N-API instead of NAN. But of course, via trial and error, I had not been working with C++ for a while. After a couple of evenings cursing, searching and writing, I had a base version of node-gd running with N-API ready! Wow, that went faster than I had expected.

I made major changes to the code base, not only did I move to N-API, I also:

  • Added about 40 additional tests
  • Simplified the JavaScript bridge between the bindings and the package’s API
  • Fixed multiple issues that caused NodeJS to exit due to uncaught exceptions in the C++ code
  • Extended the test coverage from NodeJS version 6 up to 12
  • Simplified the creation of Animated GIFs
  • Added about 20 async operations using the AsyncWorker API
  • Let all async tasks return Promises
  • Added extra checks to reduce errors thrown after image destroy
  • Aligned the directory structure with NPMs preferred layout

These changes lead me to decide to make a new major release, which became v2.0: node-gd is quite future proof again. I hope some developers out there will start using it, although some much more actively maintained image manipulation packages are currently available for NodeJS too. Feel free to give node-gd a try, and do not hesitate to open issues on GitHub. I cannot give any guarantee in when those bugs will be fixed, since I do all this in my spare time, but I will do my best.

Daniel Spoerri