2016-12-29

Transparency in SZLib

One of the neat features in early Woopsi was screen dimming. The class responsible read each overlapped pixel from the framebuffer, adjusted its brightness, and wrote it back. I eventually had to kill the feature when I changed how the rendering system worked and broke any support for transparency.

Woopsi’s drawing algorithm (essentially a reversed painter’s algorithm) looks like this:

  • Pass in a rect that describes the area to be redrawn;
  • For each layer in the layer hierarchy, from the topmost down:
    • Draw the parts of the layer that intersect with the rect;
    • Subtract the drawn parts from the rect.

Each layer is expected to own all pixels within its rectangular region. To support transparency, there would need to be a way for layers to opt out of subtracting themselves from the redraw rect so that lower layers could draw into the same area. Even if there were a way to achieve that, we’re traversing the hierarchy the wrong way. Transparent views need to render over the top of existing framebuffer data. We need to draw from the bottom up.

The CanvasLayers JavaScript library has a workaround but it’s really inefficient. When setting the library up, it includes the option to support transparency. With that enabled, this is the algorithhm:

  • Pass in a rect that describes the area to be redrawn;
  • For each layer in the layer hierarchy, from the bottommost up:
    • Draw the parts of the layer that intersect the rect.

It’s just the painter’s algorithm. Its awfulness is slightly mitigated by all of the dirty rectangle work that the library does, but we’re still pointlessly drawing a bunch of stuff below opaque layers. We’re also forced to maintain two algorithms to do the same work.

SZLib needed to support transparency in order for overlapping objects in Hanky Alien and Chuckie Egg to work, so I put some thought into the problem and arrived at the obvious (with hindsight) solution: a two-pass algorithm. In pass 1, we iterate over the layers from top-down to figure out which layer will be responsible for drawing what. In pass 2, we iterate over pass 1’s results from bottom-up and perform the drawing.

Pass 1 looks like this:

  • Pass in a rect that describes the area to be redrawn;
  • Create an array of layer/rect tuples;
  • For each layer in the layer hierarchy, from the topmost down:
    • Find the intersection of the layer with the rect;
    • Create a tuple containing the intersection and the layer;
    • If the layer is opaque, remove the intersection from the rect.

Note that we don’t consider an intersection to be “used” if the layer is transparent, which allows lower layers to create their own tuple in subsequent iterations.

In pass 2, we iterate backwards over the tuple array and redraw each tuple. Pass 1 gave us an array ordered by z-index from high to low; by iterating backwards we therefore redraw from the bottom up. Lower opaque layers are drawn before higher transparent layers.

This new algorithm supports transparency with the minimum amount of redundant drawing.

2010-11-30

CanvasLayers - A Layer Library for the HTML5 Canvas Tag

Wouldn’t it be neat if the canvas tag supported layers? Now it does!

This uses the layering engine from CanvasUI, modified to support transparency and transparent sections (if required). Unlike other layer libraries for javascript, this uses a single canvas rather than a stack of several. Something this library gives you that a stack of canvases can’t is nested layers.

I changed the CanvasUI library so that it uses the latest Woopsi layout technique (damaged rects) instead of the old method (BSP trees). It doesn’t appear to have had any impact on performance, but it does make the code simpler. The new CanvasLayers library uses the latest version of the CanvasUI as its basis. I’m hoping to strip the duplicate functionality out of CanvasUI and use the CanvasLayers library as the canvas engine.

The CanvasLayers library sourcecode can be downloaded from BitBucket: