MeltingIce Blog

Back in Action

Computer Science major from WPI and Software/Systems Engineer at TwitPic. Obsessed with numbers and programming languages.

Implementing Layers in CamanJS

Quick disclaimer: if you haven’t heard of my project CamanJS, I recommend checking out the official website to see what it’s all about before reading this post.

Ever since the start of the project, I’ve had the concept of layers for CamanJS in the back of my mind. While working on some preset filters, I found myself restricted by the fact that I could only edit the base image directly.  I debated the addition of layers for awhile because I felt it would either make CamanJS too complex or too slow. Eventually, I decided that layers had the potential to increase the power and flexibility of CamanJS by magnitudes if implemented properly.

Also from the beginning, I had an ideal goal in mind that was akin to Photoshop, but without the complexity. I wanted to make an image manipulation library that was simple and made sense to the average user or novice developer. These goals needed to extend to the layering system as well, or else the beauty of the library would be tarnished.

I’ve decided to write this blog post starting with the end result of the implementation and finishing with the gritty details. Lets get started :)

Layers from a User’s Perspective

This will be much easier to explain with some code on the table first, so here’s how the layer implementation is used:

// We invoke CamanJS on an image in the DOM with id 'test'
Caman("#test", function () {

  // here, we can use any filters we want
  this.brightness(10);

  // Lets create a new layer
  this.newLayer(function () {
    // Everything inside of this callback will be applied to this layer

    this.setBlendingMode('multiply');
    this.opacity(10);

    // This will copy the contents of the parent layer to this layer
    this.copyParent();

    // We can use any CamanJS filter on this layer via the filter object
    this.filter.gamma(0.8);
    this.filter.contrast(50);

    // Layers can be nested as well. The deeper a layer is nested, the higher
    // it is on the layer stack
    this.newLayer(function () {
      this.setBlendingMode('softLight');
      this.opacity(10);

      // Here we simply fill the layer with a solid color
      this.fillColor('#f49600');
    });

    // We can do some more editing here on this layer if needed
    this.filter.exposure(10);
  });

  // Some final filters to apply to the image
  this.exposure(20);
  this.gamma(0.8);
  this.vignette(250, 20);

  // Begins the image rendering process
  this.render();
});

The awesome part about this implementation is that it gives CamanJS not only layers, but also grouped layers. Layers have full access to all of the filters normally available to the base image in addition to a few special layer-only functions.  If you’ve ever used Photoshop or GIMP, setBlendingMode() and opacity() should be very familiar to you. They are identical to changing the blending mode and opacity in Photoshop as shown in the image here.  CamanJS doesn’t currently have all of the blending modes that Photoshop has available, but they’re very simple to create and can be added by extending the Caman.manip.blenders object:

(function (Caman) {

  Caman.extend( Caman.manip.blenders, {
    /*
     * This function will be iterated over in a pixel-by-pixel fashion.
     * Both arguments are rgba objects, with the current pixel's color
     * channels:
     *     rgbaLayer.r, rgbaLayer.g, rgbaLayer.b
     *
     * The first argument is the pixel from the layer, and the second
     * argument is the current pixel from the parent canvas that we're
     * blending into.
     */
    newBlender: function (rgbaLayer, rgbaParent) {
      rgbaParent.r = rgbaLayer.r * 2 - rgbaParent.r;
      rgbaParent.g = rgbaLayer.g * 1.5 - rgbaParent.g;
      rgbaParent.b = rgbaLayer.b * 2.5 + rgbaParent.b;

      // We must return the new pixel value for this to work properly
      return rgbaParent;
    }
  });

}(Caman));

You can also set the layer opacity which affects how much the current layer will be blended with the layer below it. This is implemented outside of the blender functions, so it’s something you don’t have to worry about when adding your own blending techniques.

You can also fill a layer with a solid color, which can be very useful for tinting images in many different ways.  The ability to fill a layer with a new image via URL or DOM node to create an overlay is currently in the works and should be implemented soon.

Finally, layers have the option to copy the contents of the parent layer via the copyParent() function. This is extremely useful if you want to do something such as adding glow to the image. For example, you could make a new layer, copy the parent layer, apply a heavy blur, and blend it back with the parent layer with a low opacity.

The In-Depth Implementation Details

Now that you know how to use layers, lets take a look at how they’re implemented in the core library.

Implementation Overview

A million dollars is cool… but you know what’s cooler? Stacks and queues. The trick to managing all of these layers lies in simple queue and stack management.

The problem with the HTML canvas is that, while it offers a layering system for drawn shapes and paths, it doesn’t support layers for pixel-based manipulation. Thus, in order to simulate layering, CamanJS creates a new canvas element per layer. Each of these canvases are stored in a queue aptly named canvasQueue so that they can be retrieved and processed in the same order they are created. One important thing to remember is that CamanJS doesn’t do any manipulation or rendering until the final call to render() happens for speed purposes, hence the need for these queues.

Layer information is stored in the new canvasLayer object. The callback passed to newLayer() is invoked with this new canvasLayer object as the context for ‘this’. That’s how you are able to call this.setBlendingMode() or this.opacity().

Pre-Render System State

The queues are used when layers are created and filter functions are called. They store all of the actions that need to happen when rendering begins. Once rendering begins, then stacks come into play.

So, lets say you’ve invoked CamanJS on an image then called some filter functions and created a new layer.  Before the call to render happens, this is what the system looks like:

The renderQueue is used to store all directives that need to be processed at render time and the canvasQueue stores all layers that have been created. On a side note, layer canvases are created and injected into the DOM immediately when newLayer() is called as opposed to at render time.

Now, notice the LAYER_DEQUEUE and LAYER_FINISHED directives in the renderQueue. If you happen to be familiar with OpenGL, the concept is similar. In OpenGL, you have glPushMatrix() and glPopMatrix() that are used for doing complex transformations.  These directives tell CamanJS when to start processing a layer and when to finish processing it.

The system state before render is called is pretty straight-forward. Once rendering begins is when the real magic happens.

Post-Render System State

At this point in time, all of the queues have been filled with all of the processing directives and layers, and the system is ready to start rendering the new image.

The first thing CamanJS does is shift an item off the renderQueue and gives it a quick inspection. Every item in the queue has a ‘type’, which can be either SINGLE, KERNEL, LAYER_DEQUEUE, or LAYER_FINISHED. If the type is SINGLE or KERNEL, CamanJS executes the appropriate render type with the given filter and its options.  This has always been in CamanJS and is how rendering has worked even before the layer system.

When CamanJS encounters LAYER_DEQUEUE, it then modifies the system so that it can begin working on the  next layer contents.  All pixel data is stored in the (aptly named) pixel_data array. When a new layer is encountered, the current pixel_data is pushed into the pixelStack and the new layer’s pixel data is set to the pixel_data array, overwriting it.  This ensures that render speed is kept to a maximum because it can directly access the current pixel data instead of indexing into the canvasQueue array directly.  It also stores a direct reference to the current layer for speed purposes. Immediately after LAYER_DEQUEUE is processed, the system looks like this:

The pixelStack now contains the pixel data from the base image and the pixel_data array has been replaced with the layer1 pixel data. Layer1 has also been placed on the layerStack.  The purpose of the layerStack is so that we can easily destroy the created canvases when they’re done processing. It simply pops the stack to get the layer that just finished processing, then calls the destroy() method from that layer.

When the LAYER_FINISHED type is shifted off of the renderQueue, the system invokes the blender that was set by setBlendingMode(). This copies the current layer’s data onto the parent layer via the blending function. Once the copy is done, the layer is popped off of the layerStack and the current pixel_data array is replaced with the parent layer’s pixel data that was just modified.

While this example only has a single layer for simplicity purposes, it should be simple to see how this works exactly the same for multiple layers and layer groups. The simplicity of the system comes from the fact that there is only a single pixel_data object whose content is managed by the queues and stacks.

Example Image Modified with Layers

Believe it or not, the example code from the beginning of this blog post is an actual preset filter called Glowing Sun. It uses two stacked layers to achieve its effect, which looks like this:

Before

After

I’m quite happy with the result and I think its a filter that people will enjoy using.

Whats Next for CamanJS?

I thought I would finish up this blog post with a short list of ideas in my head that I would like to see implemented sometime in the future.

  • Image overlays (currently in-progress)
  • More blending modes
  • Layer masking (either by another image or primitive shapes/paths)
  • Ability to write text on the canvas

If you have any other ideas and suggestions, I would love to hear them! The source code is open and available at Github.

Share this Post:
  • Twitter
  • Facebook
  • Reddit
  • HackerNews
  • Digg
  • Slashdot
  • Identi.ca
  • StumbleUpon
  • del.icio.us
  • Google Bookmarks
  • Google Buzz
  • Tumblr
  • Posterous
  • Add to favorites
  • email

6 Responses to Implementing Layers in CamanJS

  1. This is simply awesome! Congrats dude!
    CamanJS has lots of potential, when am I going to see it integrated to TwitPic? ;)

    About the this.render() calls, are they really necessary? Couldn’t you call it implicitly after running the callback?

    • That’s true, I probably could. I’ll have to look into that to see if there’s something I’m missing or if thats feasible.

      As for implementing it with TwitPic… all in due time ;)

  2. The benchmark link at camanjs.com/test/benchmark.html no longer works. Pending update?

  3. Just in an old blog post, but it is an old post so that is expected.

  4. Pingback: HTML5 Canvas Tutorials and Resources - WebsitesMadeRight.com

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>