Adobe

decor

Web Platform Team Blog

Making the web awesome

Blending features in Canvas

While working on adding CSS blending to the web platform, we noticed that this feature is also missing in Canvas 2D. Since blending is such a commonly used feature in our applications, it is very useful in Canvas as well.

Also since Canvas 2D lives so close to the underlying graphics libraries, it becomes easier to fix rendering inconsistencies before we throw the switch that enables blending in CSS.

Canvas 2D defines a ‘globalCompositeOperation‘ that controls how drawing commands are composited with existing pixels. After some discussion on the public mailing lists,  it seemed that this was the best place to add support for blending as well.

Editing the spec

As a first step we edited the CSS compositing and blending spec so you can specify blending in addition to compositing. The syntax of  ‘globalCompositeOperation‘ was extended to:

Value: <blend-mode> || <composite-mode> || <isolation-mode> || <knock-out-mode>
Initial: source-over

For consistency, this is identical to how you will specify blending in CSS.

‘blend-mode’ is one of the following values:

<blend-mode> = normal | multiply | screen | overlay | 
               darken | lighten | color-dodge | color-burn | hard-light | 
               soft-light | difference | exclusion | hue | saturation | 
               color | luminosity

‘composite-mode’ is one of the following values:
<composite-mode> = clear | copy | destination | source-over |
                   destination-over | source-in | destination-in | 
                   source-out | destination-out | source-atop | 
                   destination-atop | xor | lighter

‘isolation-mode’ and ‘knockout-mode’ are specific to CSS and are ignored in the Canvas 2D context.

With this new syntax, you can write the following code:

 
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');

// switch to multiply blending
ctx.globalCompositeOperation = 'multiply';
// draw magenta circle
ctx.fillStyle = 'rgb(255,0,255)';
ctx.beginPath();
ctx.arc(50, 50, 50, 0, Math.PI*2, true);
ctx.closePath();
ctx.fill();
// draw cyan circle
ctx.fillStyle = 'rgb(0,255,255)';
ctx.beginPath();
ctx.arc(100, 50, 50, 0, Math.PI*2, true);
ctx.closePath();
ctx.fill();
// draw yellow circle
ctx.fillStyle = 'rgb(255,255,0)';
ctx.beginPath();
ctx.arc(75, 100, 50, 0, Math.PI*2, true);
ctx.closePath();
ctx.fill();

which should give you the following result:

canvas

Implementing the spec

In addition to creating the spec, we also implemented it in WebKit and Firefox and are working on getting it enabled in Chromium. Browsers that don’t support this feature will ignore the setting. Blending is hardware accelerated where possible so it should not impact performance of your application.

If you want to experiment with this feature, you can download the latest nightly WebKit or Firefox. The feature is enabled by default so you don’t have to enable any flags. Here’s a jsFiddle to get you started.

For now, you can’t combine blending and compositing yet, but we will allow it in the future when there is platform support.

You can ‘feature-check’ for support by setting and blending mode and reading it back. As an example, here is a Modernizr test that checks if blending is enabled:

 
Modernizr.addTest('canvasblending', function () {
   if (Modernizr.canvas === false) return false;
   var ctx = document.createElement('canvas').getContext('2d');
   ctx.globalCompositeOperation = 'screen';
   return ctx.globalCompositeOperation == 'screen';
});

You can find documentation on how to use this API here.
Let us know what you think and if you have any ideas on how we can improve it!

17 Comments

  1. January 29, 2013 at 3:21 am, André Fiedler said:

    Here´s another one – Photoshop Blending Modes with WebGL (GLSL):

    http://mouaif.wordpress.com/2009/01/05/photoshop-math-with-glsl-shaders/

    Really nice read! Have used them, works great!
    greetings André

  2. February 01, 2013 at 2:33 pm, Kevin Gadd said:

    How do you account for the fact that many of these blending modes cannot trivially be hardware accelerated on modern GPUs (due to the fact that the blending stage of 3D rasterization isn’t customizable)? Will these blending modes force rendering to occur in software?

    • February 01, 2013 at 3:23 pm, Rik Cabanier said:

      All of them are actually accelerated on the GPU.
      Mozilla uses Direct2D and Core Graphics. Safari will use Core graphics on MacOS and iOS.
      The Skia will provide hardware support for Chrome. In addition, Win8 (and 7 through updates) offers native blending to Direct2D so IE should be able to implement this easily too.

      It is true that in order to ‘blend’ you need to push the backdrop in a texture which is expensive. However, on mobile platforms such as PowerVR and Tegra there are tricks to avoid that.

      • February 01, 2013 at 4:13 pm, Kevin Gadd said:

        Thanks Rik. Glad to hear that mobile GPUs at least provide an efficient way to do customizable blending – the cost of pushing the backdrop to a texture on desktops will at least be less dire in comparison.

  3. February 01, 2013 at 5:09 pm, kangax said:

    Looks like non-standard (?) “darker” (in Chrome) and “multiply” (in FF) — http://codepen.io/kangax/pen/Bhqtk

  4. April 22, 2013 at 10:24 am, Grant Skinner said:

    This is great to see. A couple quick thoughts:
    1. It would be nice to be able to apply both a composite operation and a blend mode simultaneously. For example, to do alpha masking as well as blending. I don’t think this warrants complicating the API though.

    2. Operations like this really drive home how nice it would be to have some kind of off-screen buffering mechanism built into Context2D, so a blend can apply to a composited draw without devs needing to manually create and maintain a separate canvas instance for the task. Further, this could also potentially provide a way to return the dimensions of draw operations (though that is complicated by the implicit clipping path on a context). As a really rough idea:

    ctx.beginBuffer();
    // draw a bunch of stuff into the buffer, utilizing the current state of the target context.
    var bounds = ctx.getBufferBounds(); // return the pixel bounds of all the buffer draw operations.
    ctx.globalCompositeOperation = “multiply”;
    ctx.closeBuffer(); // draw the pre-composited buffer into the original context

    Or maybe alternately:
    var bctx = ctx.getBuffer(); // returns a context buffer object sharing the current ctx state
    bctx.doABunchOfStuff(…etc…);
    ctx.globalCompositeOperation = “screen”;
    var bounds = bctx.getPixelBounds();
    ctx.draw(bctx);

    Just some quick ideas without a ton of thought behind them.

  5. April 22, 2013 at 11:27 am, Brian Reavis said:

    A good first start! I really like the idea of being able to use blending and compositing simultaneously.

    Awhile back I built a custom context (on top of “2d”) that supports blending modes with a “globalBlendMode” flag (https://github.com/brianreavis/canvas.hdr.js). It only supports basic drawing operations (pixel setting, images, rectangles), but it’s along the same lines as what’s outlined in this post. Obviously it’s slower than a native implementation. It’d be great to see vendors pick up native blending.

    • April 22, 2013 at 3:37 pm, Rik Cabanier said:

      It will probably be a while before we can combine any blend mode with any compositing mode. Core Graphics and Direct2D do not have support for this so we need to wait until those operating systems support this.
      Could you turn your library into a polyfill for blending?

  6. April 22, 2013 at 10:51 pm, timb said:

    Very interesting, nice work. Besides the timing attack problem, why not allow an arbitrary blending function?

    • April 22, 2013 at 11:55 pm, Rik Cabanier said:

      Core Graphics doesn’t support it and since that’s the basis for Safari’s canvas implementation, it’s not trivial to implement.

  7. April 23, 2013 at 2:35 am, Matt Bindoff said:

    I’d really also love to see hardware accelerated color matrix manipulations. Realtime color matrix calculations in JS are a massive CPU hog.

    • April 23, 2013 at 8:27 am, Rik Cabanier said:

      That’s more about filtering than blending.
      Try to lobby for it on www-style. We already have some accelerated shorthands so this could be added in the future.

  8. May 22, 2013 at 5:04 pm, Evan said:

    It isn’t working for me in chrome…
    console.log( ctx.globalCompositeOperation ); returns color-dodge for example but still draws with source-over mode.

    • August 15, 2013 at 7:00 pm, EDIGames said:

      I too am finding that Chrome ‘lies’ and returns the same named mode; but doesn’t honor it.

      • August 16, 2013 at 11:33 am, Rik Cabanier said:

        That’s odd. Can you post a jsfiddle so we can debug?
        Are none of the blend modes working?