How You Can Do Cool Image Effects Using HTML5 Canvas

You need to know how image filters work!  You’re writing an HTML5 application that works with images and you’d like to let your users dynamically alter the brightness of their images. Or maybe you’d like them to be able to give their image a vintage look…. or convert it to black and white to give that sense of drama…

Wouldn’t it be great if you knew how to apply cool image effects such as these from JavaScript dynamically? Would you like to learn the code behind how these image filters work? You totally can… This article will give you the fundamentals for basic image processing using the HTML5 Canvas API. You’ll learn how to write code that accesses an image’s pixel data and transforms this pixel data programmatically to perform cool image effects. Click here to try the sample app.

The HTML 5 Canvas API is used here to access and manipulate the pixel data of an image. Once we have loaded an image, we can draw that image into the 2d context of a canvas object as the code snippet below shows.

    
    var image = new Image();
    image.onload = function () {
      if (image.width != canvas.width)
        canvas.width = image.width;
      if (image.height != canvas.height)
        canvas.height = image.height;
      context.clearRect(0, 0, canvas.width, canvas.height);
      context.drawImage(image, 0, 0, canvas.width, canvas.height);
      filterCanvas(imageFilter);
    }
    image.src = imageURL;
  }

Once the image has been drawn into the context, we can access the pixel data of the context by invoking the context’s getImageData method. This method will return an object that has information about every pixel in the image.


  var imageData = context.getImageData(0, 0, canvas.width, canvas.height);

The returned imageData object will contain a property called data which will reference a single dimension array of numbers. This array of numbers is a packed array containing a red, a green, a blue and an alpha value for each pixel in the image. Therefore the number of values in this array will be equal to W x H x 4. Where W is the width of the canvas, H is the height of the canvas and 4 is the number of color components (red, green, blue, alpha). The range for a color component value is from 0–255. If you try to assign a value to a color component outside of this range the value will be clamped. Using this array you can write code that inspects and manipulates the pixel data. To update the canvas’s context with any modifications to the imageData object the context’s putImageData method can be used to write the pixel data back.

       
  context.putImageData(imageData, 0, 0);

What you do with the imageData between calling these two methods is really what this article is all about and where you can apply a filter to transform the appearance of the image. So putting it all together as follows:

  
  // apply a filter to the image data contained in the canvas object
  function filterCanvas(filter) {
    if (canvas.width > 0 && canvas.height > 0) {
      var imageData = context.getImageData(0, 0, canvas.width, canvas.height);
      filter(imageData);
      context.putImageData(imageData, 0, 0);
    }
  }

The first filter that I’ll describe is often referred to as a grayscale filter. This filter sets the intensity of the red, green and blue color components to the arithmetic average of those values. This has the effect of changing a color image into a black and white or grayscale image. If you look at the snippet below you’ll see that the code loops through 1 pixel each time thru the loop. The value referenced by d[i] will always be the red component for the current pixel. The value referenced by d[i+1] will always be the green component. The value referenced by d[i+2] will always be the blue component. The value referenced by d[i+3] will always be the alpha component for the current pixel. Note: The alpha value is used to specify the transparency of the pixel. In all of the filters shown in this article the alpha component is not referenced or altered; therefore leaving it intact. The grayscale filter adds the red, green and blue values together and divides by three yielding an average value. This average value is assigned back to all 3 color components. Give this a try by clicking the grayscale link in the sample app.

  
  // grayscale filter using an arithmetic average of the color 
  // components
  grayscale = function (pixels, args) {
    var d = pixels.data;
    for (var i = 0; i < d.length; i += 4) {
      var r = d[i];
      var g = d[i + 1];
      var b = d[i + 2];
      d[i] = d[i + 1] = d[i + 2] = (r+g+b)/3;
    }
    return pixels;
  };

The next effect I’ll talk about is another popular one often referred to as a sepia filter. A sepia filter imparts a warm, antique look to an image. The math behind a sepia filter leverages a number of weighted formulas for each color component that factor in a contribution from each color component to arrive at a new value. Looking at the formulas in the code snippet below you’ll see a number of “magic” weights that will give us the desired effect. Each time through the loop the red, green and blue components for a single pixel will be updated. An interesting exercise for the reader is to experiment with these weights to come with your own  unique filter.

  
  // sepia-style filter that gives a warm antique feel to an image
  sepia = function (pixels, args) {
    var d = pixels.data;
    for (var i = 0; i < d.length; i += 4) {
      var r = d[i];
      var g = d[i + 1];
      var b = d[i + 2];
      d[i]     = (r * 0.393)+(g * 0.769)+(b * 0.189); // red
      d[i + 1] = (r * 0.349)+(g * 0.686)+(b * 0.168); // green
      d[i + 2] = (r * 0.272)+(g * 0.534)+(b * 0.131); // blue
    }
    return pixels;
  };

The next filter has a lot of similarities to the grayscale filter. But rather than applying the average back to all color components it only applies it to the red component thereby shifting all of the color information to red. The green and blue channels are zeroed out. Another excercise for the reader is to make a blue or a green filter.

  
  // filter that shifts all color information to red
  red = function (pixels, args) {
    var d = pixels.data;
    for (var i = 0; i < d.length; i += 4) {
      var r = d[i];
      var g = d[i + 1];
      var b = d[i + 2];
      d[i] = (r+g+b)/3;        // apply average to red channel
      d[i + 1] = d[i + 2] = 0; // zero out green and blue channel
    }
    return pixels;
  };

The brightness filter is the last one we’ll cover in this article. In this filter a positive or negative value is added to each red, green and blue component. This has the effect of either brightening the image given a positive value or darkening the image with a negative value. The function for this filter is slightly different than the others in that we use a JavaScript closure to create a parameterized function. By invoking the brightness function with a numeric parameter a new function instance is returned that will bound to the supplied delta parameter. This newly returned function object is the actual filter function.

  
  // filter that brightens an image by adding a fixed value 
  // to each color component
  // a javascript closure is used to parameterize the filter 
  // with the delta value
  brightness = function(delta) {
      return function (pixels, args) {
        var d = pixels.data;
        for (var i = 0; i < d.length; i += 4) {
          d[i] += delta;     // red
          d[i + 1] += delta; // green
          d[i + 2] += delta; // blue   
        }
        return pixels;
      };
  };

Want to learn more about image filters…

Get My Free Bonus Article on Color Matrix Filters!  I’ll send you a link to a bonus article for my data-driven Color Matrix Filter and sample application when you join my mailing list.  A Color Matrix Filter can do almost an infinite number of different color filter effects.  You won’t want to miss adding this to your toolchest!  Just drop your email in the form below:

 

I hope you’ve enjoyed this brief tour on image filters.  You can get the full source code for the article here and try out the sample app here.

 

 

 

11 Comments.

  1. I’m trying to create an inverse color I can’t manage to get it right. Do you have any tips for that ?

    • “Inverse color” could be interpreted a couple of different ways. A given color component has a range from 0-255 so given a value for a given color component, c… we can calculate its inverse i with the function i = 255-c. Giving us this filter.


      // invert filter inverts the scalar value for each color component
      invert = function (pixels, args) {
      var d = pixels.data;
      for (var i = 0; i < d.length; i += 4) { d[i] = 255 - d[i]; d[i+1] = 255 - d[i + 1]; d[i+2] = 255 - d[i + 2]; } return pixels; };

  2. Why not apply css3 filters instead ?

    • True CSS3 filters will add another valuable tool in the toolchest especially for canned filters. But the techniques shown here allow for interop with the canvas API so that you can have full procedural control of your drawing. So still relevant IMO.

  3. too bad css filters are soon to be adopted by major browsers which make this much easier.

    • True CSS3 filters will add another valuable tool in the toolchest especially for canned filters. But the techniques shown here allow for interop with the canvas API so that you can have full procedural control of your drawing. So still very relevant IMO.

  4. If you want to experiment with some filters like this in real time, I made a simple page to work with your webcam like this: http://mizzouacm.github.io/javascript-workshop/
    You can fill in the filter function to see the effects in real time. You can also get screenshots by clicking the filtered video. It’s pretty fun to see what interesting filters you can come up with, and I’m hoping to host an intro to javascript workshop utilizing the demo.
    I can’t take credit for the idea; I got it from the wonderful Hackers At Berkeley workshop[1]. I am starting a similar club at my university and wanted to do the same kind of workshop. I rewrote the demo because I wanted it to be real time, and I also just wanted the fun of implementing it myself :)
    [1] Blog about workshop: http://blog.hackersatberkeley.com/?p=97 Demo: http://introjs.hackersatberkeley.com/

  5. Thanks for this super helpful tutorial. Quick question: how would you make a filter for yellow?

    Also, is there a way to adjust the brightness of the red filter?

    Thanks!

    • Thanks for the feedback Paul. Signup for my mailing list as I’ll be delving deeper into more advanced image processing and other HTML5 topics.

      I’ll give you some hints. Yellow is equal parts of red and green. Take a look at how the brightness filter works and think about how it might be combined with the red filter to affect brightness of that filter.

      Have fun exploring!

  6. Thanks for the article. Will try to apply this
    to a webcam stream using webrtc.

  7. I can’t believe I googled for “Image Effects” using the Canvas Element and wasn’t directed to any book. Does anybody know of one?