Saturday, December 23, 2006

Two dimensions for a change

Haven't got much of anything interesting added to the synthesizer thingie during the last few days. Instead, I've actually played around a bit in two dimensions. For a long time I've wanted a simple vector graphics library. A few days ago I started playing with the math. The result is rather boring system based on a couple of simple principles:

  1. There is canvas, masks, and paint; paint is applied to canvas through a mask.
  2. Geometric primitives (= line paths) work with masks.
  3. Colors, gradients, bitmaps, whatever, work with paint.
  4. To draw a white triangle, cut a triangle shaped hole in a mask, apply white paint.
The result looks like this (smaller one is screencap, bigger is the same thing magnified):


Now, at this point there isn't even a datatype for a path, so the individual lines in the mask must be drawn separately, but fixing that isn't a huge amount of work (actually probably less than writing this blog post). And I'd expect stuff like beziergons to work before christmas.

Now, what is nice about the design, is that as you see it antializes rather beautifully. It actually draws what you'd get by doing 16x16 naive supersampling and averaging down. Ofcourse all the supersampling is internal to the line drawing function. I'll probably make it 256x256 when I have the extra minute to tweak the algorithm a bit more.

The whole thing is a very basic scanline rasterizer, so the trick really is in the scanlines. Now a scanline rasterizer works by incrementing a counter every time it crosses a line going up, and decrements a counter every time it crosses a line going down. Then you typically fill when you've got either non-zero or odd number in your counter, depending a bit on what you want.

The antialising then is actually quite easy, just a question of putting fractional values in the scanline buffer, and drawing an anti-aliased line. The only important thing then is that the line drawing function makes sure that when one starts from left side of the line, and sums all the pixel values until one has crossed the whole line, the result is exactly one. In a sense, what is being drawn is the finite difference of the edge of an antialised half-plane. And I actually got the idea originally from anti-aliasing audio with BLEPs. Don't ask.

And the best thing? Since integration (and differentiation) are linear operations, one can draw each line independently, and just integrate the scanlines at the end. There aren't really any special cases whatsoever.

Ps. I'm not sure if the above makes any sense. I might consider a better explanation some other day, but I now have to add those beziergons. :)

No comments: