Polyfill
While working on my physics engine I realized that I would need a 2D graphics solution to draw the wide variety of shapes my engine would support. I also noticed that as I've moved between languages and platforms I needed something portable as well. Ideally the graphics library would be simple, capable of scaling to high resolutions, and support embedded fonts. The requirement for embedded fonts is important as most font libraries are fairly large - stb_truetype.h for instance is a single-file library of 194kb.
All of these requirements have led me to create drawing.js: a polygon based library modelled after javascript canvases. Polygons, circles, and even fonts are composed of just 2 things: lines and bezier curves.
SVG Paths
Defining even simple polygons in source can be exhausting, so most SVG engines allow for a shorthand notation: M move to, L line, C curve, Z close the path. Using this notation we can draw a simple cat:
Left shows the control points, right shows the final result.
Characters
In addition to geometric shapes we can represent letters, numbers, and symbols as SVG paths.
At 5 lines and 22 curves, this is the most complex character in drawing.js. As expected of a real g.
Curve Segmentation
The cubic Bezier curves we use are represented as a cubic polynomials. This makes determining what pixels they overlap computationally expensive. Instead, the curves are decomposed into lines immediately before rasterization.
A good segmentation algorithm will create the fewest lines possible while closely approximating the curve.
Pixel Blending
Pixels on the edge of the polygon might not be entirely covered by the polygon. When this happens, we don't want to completely replace the background pixel's color. Instead we want to blend it with the polygon's color.
There are several algorithms to blend pixels on the edge of the polygon with the background - some faster than others.
Algorithm | Firefox (ms) | Chrome (ms) | Accurate |
Naive RGB #1 | 128 | 88 | true |
Naive RGB #2 | 133 | 94 | true |
32-bit #1 | 48 | 47 | true |
32-bit #2 | 48 | 38 | true |
32-bit #3 | 47 | 33 | false |
imul #1 | 53 | 36 | false |
imul #2 | 43 | 34 | true |
The winning algorithm, imul #2, splits the pixel into red/blue and green/alpha bytes and uses some integer tricks: