Skip navigation

Hypnoglow with <canvas>

I submitted an entry to the js1k contest. You can view it here. It’s not terribly fast so I suggest using Chrome to view it.

Prior art

What I describe in this post is neither new nor advanced. It’s just my implementation. Mathieu “p01” Henri did <canvas> hypnoglow 2 years ago, and won the 20 lines “Zoom” Javascript contest. Also here. I didn’t know about other Javascript contests before js1k.


I wanted to introduce a recursive, large kernel blur with my entry. I stumbled upon the W3C page which says this:

To draw images onto the canvas, the drawImage method can be used.

  • drawImage(image, dx, dy)
  • drawImage(image, dx, dy, dw, dh)
  • drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh)

Each of those three can take either an HTMLImageElement, an HTMLCanvasElement, or an HTMLVideoElement for the image argument.

So my initial entry was doing something recursive like that:

context.drawImage(canvas, 4, 4, width-8, height-8, 0, 0, width, height);

where context is the context associated with canvas. This actually looked like crap, but worked.

Then, while testing on all 4 browsers requested by the js1k rule, I found out that only Firefox 3 would support blitting a canvas to itself. It wasn’t working at all with Chrome, Opera and Safari. It wasn’t even working with Firefox 4 beta.

Yet, Inopia/Aardbei‘s entry was using recursive blur with success. Digging into the cleverly size-optimized source, I found that it was using an additional offscreen <canvas> as a temporary buffer.

The hidden rule seems to be: you  can draw any rectangular area of a canvas A into any rectangular area of a canvas B, provided A is not B. This is actually matching how graphics cards works, where in the general case you cannot render in a texture you are using. A lot of thing remains possible : mipmap pyramids, feedback effets… I guess using Canvas 2D transformations and clipping regions would lead to even more effects.

ERRATUM: p01 proved the above paragraph wrong, drawing a canvas on itself seems to work.

Starting again

Taking some inspiration from rez’s work, I made a kaleidoscope-like visual too with a large-kernel blur.

Each frame in the entry follow this algorithm:

  • The main canvas is darkened by a half-transparent black rectangle.
  • Circles are added in various colors and size, using black <canvas> shadows.
  • The main canvas is downsampled to a 8x smaller canvas
  • …which is downsampled to a 2x smaller canvas, three times in a row (much like mipmapping). This amounts to 4 offscreen canvases.
  • The two smaller canvases (32x smaller and 64x smaller) are added to the main canvas to add glow.
  • The two larger offscreen canvas (8x smaller and 16x smaller) are added to the main canvas, but 4 times and with an offset, to add feedback.

This looked like this:

For those interested, I made an archive with the source, using stats.js from Mr Doob (use it!).

When size-optimizing, I had to change colors to save bytes and went with short HTML color names (“Red” is 1-byte shorter than “#f00”). So it’s different from the final version.


Safari, Chrome and Firefox 4 seem to take in account the sRGB color space when blitting an image.

Firefox 3 and Opera do not. Thus the entry is darker on these browsers, and doesn’t look good.



  1. Neat.

    The source width and height in the recursive drawImage you should be width-8 and height-8 respectively so that the hypnoglow does not shift toward the top left corner of the canvas.

    Strange, I just checked in all 4 browsers and could do an Hypnoglow without the need for an offscreen canvas.

  2. Indeed, corrected the -4 in -8.

    I must be incorrect about the “canvas must be a different one” rule. It was probably something else.

    Still, I will keep 4 offscreen canvases since I’m not sure they are mipmapped by themselves. In fact I don’t know which resampling is used in drawImage, so it’s a bit a safety net. 🙂

  3. The HTML5 spec does not forbid to use the same Canvas for the source and destination of drawImage(…)

    Also it leaves the resampling method up to the browser vendors. Nothing we can do here. :\

  4. Nice one!

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )


Connecting to %s

%d bloggers like this: