2
\$\begingroup\$

In my HTML5 canvas game, I need to cross-fade two sprites which have some glow around them. (Glow is backed into sprites.)

Initially, the first sprite is visible. During the cross-fade the first sprite should vanish, and be replaced with the second one. How exactly the cross-fade is done — does not matter, as long as it is smooth and there are no visual glitches.

I've tried two techniques:

  1. During the cross-fade I simultaneously interpolate alpha of the first sprite from 1.0 to 0.0, and alpha of the second sprite — from 0.0 to 1.0.

    With this technique I can see background in the middle of the cross-fade. That's because both sprites are semi-transparent most of the time.

  2. During the cross-fade I first interpolate alpha of the second sprite from 0.0 to 1.0 (first sprite alpha is at 1.0), and then interpolate alpha of the first sprite from 1.0 to 0.0.

    With this technique background is not seen, but the glow around sprites flashes during the cross-fide — when both sprites are near the full visibility.

In non-HTML5 game I'd use shaders to do cross-fade separately in RGB and alpha channels. Is there a trick to do the cross-fade I need in HTML5 without visual glitches?

\$\endgroup\$
2
\$\begingroup\$

It seem to me what you actually want to do is create a canvas where the RGBA value of each pixel is the weighted average of the RGBA values of two pixels from two other canvases.

The Context2d has no functionality for this out-of-the-box. But you can write a function for this yourself. You can use getImageData to extract the content of a canvas as an array of pure RGBA values and you can use putImageData to write pure RGBA values to a canvas.

Here is the algorithm. It assumes that weight is a value between 0.0 and 1.0. A value of 0.25 would mean 25% sprite1 and 75% sprite2.

  1. use getImageData to get the RGBA values of source1 and source2
  2. use createImageData to create an empty target RGBA array
  3. iterate the target-array. Set each R, G, B and A value to the corresponding value of source1 * (1.0 - weight) + source2 * weight
  4. use putImageData to write the target array to the target canvas

Note that getImageData, putImageData and the pixel-based manipulation between them are quite slow because they are not hardware-accelerated. When you want to make a lot of use of this effect, or want to apply it to very large sprites, you should consider caching it.

\$\endgroup\$
2
  • \$\begingroup\$ Well, I'm afraid that I'll not even be able to afford caching it... :-( \$\endgroup\$ – Alexander Gladysh Oct 31 '13 at 18:42
  • \$\begingroup\$ You really should do the interpolation using premultiplied RGBA values, otherwise funny things can happen if you interpolate between, say, an opaque pixel with hue A and a nearly-transparent pixel with a different hue B. getImageData() returns non-premultiplied RGBA values, so the back-and-forth conversion is yet another thing you'll need to include in your interpolation routine. \$\endgroup\$ – Ilmari Karonen Nov 1 '13 at 11:36
0
\$\begingroup\$

I've just tried to implement this purely using 2D compositing operations and a secondary canvas, and I don't think it's possible. The problem is that there's no way to parameterize blending other than globalAlpha, and that's indistinguishable from the original image being more transparent, because it doesn't modify the compositing operation but rather modifies what compositing sees as the source image.

I recommend you consider switching from 2D-context to WebGL, which would allow you to use shaders to implement custom blending, as you suggest you already know how to do. You would have to rewrite your drawing code but it's still a <canvas>.

\$\endgroup\$
1
  • \$\begingroup\$ I'd also reccomnd looking into Pixi js at this point. \$\endgroup\$ – Vaughan Hilts Feb 27 '14 at 19:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.