Instagram-style filters in HTML5 Canvas

Instagram made us fall in love with image filters. A well-chosen filter can enhance a photo greatly. It augments the best parts and softens undesirable qualities.

On a recent project I was charged with achieving a similar effect for an HTML5 canvas powered image editor. When the user saves their image, the app uploads the canvas bitmap data as a JPEG. This meant that the pixel data had to be altered directly.

Specifically it introduces the filter and
mix-blend-mode properties. These features expose the full gamut of options one would expect from a graphical image editor.

Bringing Blend Modes to HTML5 Canvas

"The future is already here — it's just not very evenly distributed."

William Gibson

2D context canvas already supports these blend modes! However they haven't reached ubiquitous support. In order to achieve photo effects in every browser, the canvas image data must be manipulated directly. Fortunately, the browser gives us really good tools for that.

Setting things up

We need to set up a basic environment before we can begin. We'll load an image, then render the scene once it is ready.

There's a small catch here that may be unfamiliar. Assuming the proper CORS headers are set, photo.crossOrigin = "Anonymous" enables cross-origin photo painting in HTML5 canvas (another blog post in and of itself).

Not ideal. We need to blend the gradient into background. We could use context.globalCompositeOperation, however that bumps into browser support issues for modes like screen, multiply, and color-burn. We'll need to enumerate over all of the pixel data for both canvases using context.getImageData.

context.getImageData

context.getImageData grabs a box of pixels from the canvas and returns an ImageData object. ImageData provides width, height, and data properties. We only care about the data field:

Why is it so washed out?

These types of transformations are a little tricker, but the internet is a deep ocean of free information. Techniques are well established. Manipulating brightness, color, contrast, and saturation is done through a color matrix transformation. Fortunately for us, the HTML5 drawing library EaselJS has already figured this out.

Using Color Matrices

For the purposes of this blog post, I've extracted the color matrix algorithm from EaselJS. Adjusting brightness and contrast is a matter of multiplying matrices and applying the result to pixel data. I wouldn't force that on anyone. However feel free to checkout the source code if you are curious.

After we pull in a color matrix transformation library, manipulating brightness and contrast is a matter of sending the correct parameters:

We made it

A quick diff of the CSS and JS versions in Photoshop shows us that we got pretty close:

In this photo, a darker pixel means a closer match. We achieved near parity.

Implementing blend modes such as screen, multiply and color-burn is a realistic goal, it just takes a little more work. The result is beautiful photography within a 2D context canvas. View the end result for yourself or get the code.

Nate
is a senior developer in our Durham, NC office, where he focuses on client-side application development. Most days, you can find him neck-deep in JavaScript working with clients such as The Nature Conservancy and the Wildlife Conservation Society.