A single-header C89 library for rasterizing immediate-mode 2D vector graphics, closely modeled on the W3C HTML5 2D canvas specification.
This is a C89 rework of Andrew Kensler's original C++ library
canvas_ity. All C++ has been
removed. The library compiles with gcc -std=c89 -Wall -Wextra -Wpedantic -Werror and produces output identical to the original.
The priorities for this library are high-quality rendering, ease of use, and compact size. Speed is important too, but secondary to the other priorities. The library takes an opinionated approach and does not provide options for trading off quality for speed.
Despite its small size, it supports nearly everything in the W3C HTML5 2D canvas specification, except for hit regions and getting certain properties. Stroke, fill, gradient, pattern, image, and font styles are specified through C function calls rather than strings. The goal is that this library could produce a conforming HTML5 2D canvas implementation if wrapped in a thin layer of JavaScript bindings.
The following program builds a star path, fills and strokes it with drop shadows, dashed lines, and a gradient shine overlay, then writes the result to a TGA file.
- Trapezoidal area antialiasing provides very smooth antialiasing, even when lines are nearly horizontal or vertical.
- Gamma-correct blending, interpolation, and resampling throughout. All colors are linearized and alpha-premultiplied on input and converted back to unpremultiplied sRGB on output. This reduces muddiness on many gradients (e.g., red to green), makes line thicknesses more perceptually uniform, and avoids dark fringes when interpolating opacity.
- Bicubic convolution resampling for patterns and images. Smoothly interpolates with less blockiness when magnifying, and antialiases well when minifying. Can simultaneously magnify and minify along different axes.
- Ordered dithering on output reduces banding on subtle gradients while remaining compression-friendly.
- High curvature is handled carefully in line joins. Thick lines are drawn correctly as though tracing with a wide pen nib, even where the lines curve sharply.
- Single-header library with no dependencies beyond the C standard library.
Nothing to link besides
-lm. Includes built-in binary parsing for TrueType font (TTF) files. Pure CPU code, no GPU required. - Compiles as strict C89 with
-Wall -Wextra -Wpedantic -Werror. - Shares no internal pointers, nor holds any external pointers.
- Uses no static or global variables. Threads may safely work with different canvas instances concurrently without locking.
- Pluggable backend abstraction (
ci_backend_t) allows swapping the CPU rasterizer for a GPU implementation via function pointers.
- The library source is roughly 3000 lines including comments.
- Object code can be less than 36 KiB on x86-64 with appropriate compiler settings for size.
- The accompanying automated test suite achieves 100% line coverage.
- Trapezoidal antialiasing overestimates coverage where paths self-intersect within a single pixel. Where inner joins are visible, this can lead to a "grittier" appearance due to the extra windings used.
- Clipping uses an antialiased sparse pixel mask rather than geometrically intersecting paths. Therefore, it is not subpixel-accurate.
- Text rendering is basic and mainly for convenience. It only supports left-to-right text and does not do hinting, ligatures, text shaping, or text layout. Basic kerning is supported.
- TrueType font parsing is not secure. It does some basic validity checking, but should only be used with known-good or sanitized fonts.
- Parameter checking does not test for non-finite floating-point values.
- Rendering is single-threaded, not explicitly vectorized, and not GPU-accelerated.
- The library does no I/O on its own. You provide it with buffers to copy into or out of.
This is a single-header library. Include it freely in any of your C source files. In exactly one file, define
#define CANVAS_ITY_IMPLEMENTATIONbefore including the header to get the implementation.
Create a canvas with ci_canvas_create(), draw into it using the
ci_canvas_* functions, retrieve pixels with ci_canvas_get_image_data(),
and clean up with ci_canvas_destroy().
See the automated test suite for examples of every public API function.
make # build the test runner
make test # build and run all 92 tests
make clean # remove build artifacts
make lint # C89 syntax check on the header
make valgrind # run under valgrind
make sanitize # build and run with ASan/UBSan/LSan
Or compile directly:
gcc -std=c89 -O2 -I src -o test_runner test/test.c -lm
The library can be compiled to WebAssembly with Emscripten for side-by-side comparison against the browser's native HTML5 Canvas 2D.
make wasm # build test/canvas_ity.js + test/canvas_ity.wasm
Then serve the project root and open test/test.html:
python3 -m http.server 8000
# open http://localhost:8000/test/test.html
Each test renders the same scene twice: once with the browser's Canvas 2D (left) and once with canvas_ity via WASM (right). Differences are expected since canvas_ity uses its own software rasterizer.
This library is a C89 rework of the original C++ canvas_ity by Andrew Kensler. The rendering algorithms, architecture, and test suite design are his work. This fork ports everything to strict C89, adds a backend abstraction layer, and replaces CMake with a plain Makefile.
-
Evenodd fill rule —
ci_canvas_set_fill_rule(ctx, CI_FILL_EVENODD)enables the alternate (even-odd) winding rule forci_canvas_fill(),ci_canvas_clip(), andci_canvas_is_point_in_path(). The original C++ library only supports the nonzero winding rule. -
Conic gradients —
ci_canvas_set_conic_gradient(ctx, type, startAngle, cx, cy)creates a gradient that sweeps around a center point, per the WHATWGcreateConicGradient()spec. Color stops are added withci_canvas_add_color_stop()as with linear and radial gradients. The original C++ library does not support conic gradients. -
Elliptical arcs —
ci_canvas_ellipse(ctx, x, y, rx, ry, rotation, startAngle, endAngle, ccw)adds an elliptical arc to the current path, per the WHATWGellipse()spec. Supports independent x/y radii and an arbitrary rotation angle. The original C++ library only provides circulararc(). -
Round rectangles —
ci_canvas_round_rectangle(ctx, x, y, w, h, radii, count)adds a rounded rectangle to the current path, per the WHATWGroundRect()spec. Accepts 1–4 radii following CSSborder-radiusshorthand (UL, UR, LR, LL). Radii are uniformly scaled when they exceed edge lengths. The original C++ library does not provideroundRect(). -
Text kerning — Kerning is automatically applied when a font is loaded via
ci_canvas_set_font(). Three kerning sources are supported: OpenType GPOS pair positioning (PairPos format 1 and format 2), Microsoftkerntable version 0, and Apple AATkerntable version 1. GPOS takes precedence over legacykernwhen both are present. Kerning adjustments are applied in bothci_canvas_fill_text()/ci_canvas_stroke_text()andci_canvas_measure_text(). The original C++ library does not apply kerning.
The examples/ directory contains standalone programs
demonstrating each component:
| Example | Description |
|---|---|
canvas_ity_example.c |
Direct canvas_ity API: star with gradients, dashed strokes, drop shadows |
nanovg_example.c |
NanoVG API backed by canvas_ity: shapes, gradients, alpha blending |
nanosvg_example.c |
NanoSVG parser + canvas_ity compositing from an inline SVG string |
tiger_example.c |
Classic Ghostscript tiger SVG rendered via NanoSVG + canvas_ity |
cd examples
make # build all examples
make clean # remove build artifacts
An optional NanoVG backend is available
in the nanovg/ directory. It bridges NanoVG's 2D vector API to
canvas_ity's rasterizer via a single-header backend (nanovg_ci.h).
NanoSVG is bundled for optional SVG
parsing. All vendored code is C89-ported. See nanovg/README.md
for usage and build instructions.
ISC for canvas_ity and all original code. Vendored libraries
in nanovg/ retain their original licenses (zlib for NanoVG/NanoSVG, MIT/public
domain for stb). See LICENSE.txt for details.


