Alex Rivera | Logout

HTML5 Canvas globalCompositeOperation for overlaying gradients not adding up to higher intensity?

Asked 2012-04-08T03:49:08.667
14

I'm currently working on a heatmap.js fix and I was wondering whether anyone knows if it's possible to achive the following effect with <canvas>'s 2d rendering context.

  • I have a radial gradient from black (alpha 0.5) to transparent 40pixel radius. center of the radial gradient is at x=50, y=50
  • I have another radial gradient from black (alpha 0.5) to transparent, 40pixel radius. center of the radial gradient is at x=80, y=50

The two gradients are overlapping. My problem now is: the overlapping area gets added up together resulting in a higher alpha value than the radial gradients centers and thus showing wrong data (e.g. hotter areas in a heatmap because of those additions between the gradients)

Have a look at the following gist, by executing it in your console you can see the problem.

Expected behaviour would be: Darkest areas are the gradients centers, the overlapping area of the two gradients merges but doesn't add up.

After seeing that none of the globalCompositeOperations resulted in the expected behaviour I tried combinations of those operations. A way I thought it maybe would be possible was the following:

  • draw first gradient
  • use compositeOperation 'destination-out'
  • draw second gradient -> substracts overlapping area from the first gradient
  • use compositeOperation 'source-over'
  • draw second gradient again

But unfortunately I didn't find a combination that worked. I'd love to hear your feedback, thanks in advance!

PS: I know this could be done by manipulating the pixels manually, but I was wondering whether there's an easier, more elegant and faster solution for that.

Edit
Report

2 Answers

26

This is really wacky but it does what you want without getting imageData involved.

The thing that came to mind was that you want the exact functionality that paths themselves have on the canvas when you stroke them. To quote the spec:

As a result of how the algorithm to trace a path is defined, overlapping parts of the paths in one stroke operation are treated as if their union was what was painted.

You can read more about that here.

Anyway, what you'd want, essentially, is a blurry path of nothing but arcs that you can stroke once and you'd perfectly get the effect you were looking for.

The only problem is that there is no way to make a blurry path in canvas. Or almost no way.

Instead of using the path itself we can use the shadow of a path in order to simulate blurry circles that obey the same rules that paths do.

The only problem there, then, is that you don't want to see the actual path, you just want to see the shadow of it. Making the stroke transparent won't work: A shadow will only draw will not draw at a higher opacity than the thing it is shadowing.

But shadows do have the properties shadowOffsetX and shadowOffsetY, which are typically used to shift the shadow by a pixel or two to make the illusion of a light source.

But what if we draw the shadows so far away that you can't see them? Or rather, what if we draw the paths so far away that you can't see them, all you can see are the shadows?

Well that happens to do the trick. Here is a quick result, your original code is on the top and the shadows are the second canvas:

enter image description here

It's not exactly what you had before in terms of gradients and size

answered 2012-04-15T23:12:16.973
0

The composite modes are as you find them. You can't composite any better than this as far as I am aware, without manually setting pixels. If you update your question with an explicit description of how you want to blend the pixels, I will update my answer accordingly.

So what's are the per-pixel solutions?

There are 2 main pixel-based approaches I can see that would begin to solve this problem

  1. Draw onto a hidden context and blend with a manual function

  2. Write a function that calculates the gradient manually and applies a custom blending function.

Your first option is more versatile than the second in the sense that you can draw whatever you like using the normal canvas methods, and then blend this canvas onto your visible canvas. See @Phrogz context-blender project for a good idea of how to blend one context onto another

The second option is necessary when you need to draw in a way that the canvas doesn't facilitate by default.

The largest difficulty to contend with is the alpha transparency due to the fact you could have contents behind the radial gradient. Once you have drawn on top of a background image, it's near impossible to see what that was before you drew over the top of it, unless you keep a copy of that background. Even on a per-pixel basis you have the difficulty: blending an image over the top of another image would not work.

Essentially that means that compositing your image of multiple translucent gradients on top of your visible canvas, regardless of your choice of generic compositing function, isn't going to work unless that c

answered 2012-04-12T12:46:30.607

Your Answer