Øyvind Kolås    pippin@gimp·org twitter github bitcoin patreon  

ctx a 2d vector graphics protocol and rasterizer

New website http://ctx.graphics/

ctx is a vector graphics rendering protocol and renderer, for resolution independent graphics and user interfaces; from code and RAM constrained embedded/BIOS use to networked user interfaces.


The whole engine including a subsetted font with a only RGBA8 rasterizer fits in less than 64kb, the raw numbers on x86_64 (i686 doesn't get much smaller either). Different optimization settings of the compiler give a wide range for the performance/binary size tradeoff.

font data size:    15291 bytes (A sans font subsetted to only ASCII)
RGBA8 rasterizer:  38869 bytes (-Os ~38kb
                                -O0  90-631kb
                                -O2  57-114kb
                                -Ofast/-O3  76-181kb)

text data parser:  22232 bytes (not needed for direct use from C, but also
                                on embedded this can be useful for ease of
                                integration with other languages or directly
                                using ctx+mictrocontroller+display as a serial

The total RAM requirements are modest, with the most slim configuration <16kb of RAM is required when directly rendering using the default limits, if the maximum number of vertices in a path is reduced this can be below 10kb.

Rendering tests

A gallery of test cases; mostly written in ctx' own syntax, some use SVG/HTML as input and an in-progress port of Microraptor GUIs SVG/HTML parser.

microcontroller tests

In this video both microcontrollers, the arm-cortex-m4 at 97mhz in the card10 badge and on the TTGO ESP32 both with immediate rendering, the card10 has a 180x80 display with really high visual quality, while the TFT LCD on the ESP32 has slightly higher pixel resolution at 160x128.

january 2020, a demo running on an esp with 320x240 16bpp display, here the framebuffer is larger than the biggest single allocation we can afford and the deferred rendering is rendering the display in 4 chunks. With little geometry this is fine; with more the displays starts having very visible tearing on large content changes.

Development funding

The library is still under development in terms of performance and capabilities. ctx is possible due to my financial supporters. If you want to encourage continued development of the concepts involved in ctx – do consider becoming one.


  • ctx renderstream a compact ASCII and binary serialization of the vector drawing API.
  • Pluggable font engines.
    through stb_truetype - optional
    uses binary ctx renderstream as format - optional
  • subsetted scalable outline fonts
  • optional use of stb_truetype for runtime parsing TTF/OTF fonts.
  • Self contained math, does not need linking with -lm
  • linear and radial gradients, with opacity, and dithering
  • SVG/CSS compositing and blending modes
  • drop shadow
  • size of caches and level of antialiasing is configurable through defines set before including the single header library.
  • Pixel-formats:
    like html5 canvas, a todo item is doing compositing in linear light temporarily in 16bit instead.
    8bit RGB, 256 colors, 3bit red 3bit green 2bit blue, similar to RGB565 with much worse banding but each pixel is a single byte, gradients get dithered other colors are quantized.
    16bit RGB, 65536 colors - gradients get dithered other colors are quantized.
    same as RGB565 but with the bytes swapped.
    The floating point code is less squeezed for performance than the 8bit code, SIMD and other forms of vectorization are also more readily available to the compiler on platforms where this is enabled.
    Format with red and blue components swapped around, compatible with cairo (pending endianness).
    black and white, gradients get dithered, other colors are thresholded.
    4 level grayscale, gradients get dithered, other colors are quantized
    16 level grayscale, gradients get dithered, ...
    256 level grayscale
    floating point, grayscale
    256 level grayscale, with alpha component

    ctx syntax

    ctx is also able to parse and serialize a text-based version of the renderstream.

    The language consists of a stream of words, numbers and strings, optionally separated by white space. The characters ,=(); are also treated as white-space. Words are substrings consisting of 1 or more characters in the ranges a-z and a-Z. Numbers are sequenes of 0-9. Numbers can be followed by the suffixes % or @, indicating that units of % canvas width/height should be used or a global em / cell-size. Strings are UTF8 contained in ' or " pairs. "\n\r" etc from C are available for escaping newlines, though they can also be directly included in the string. #introduces a comment and the rest of the line is ignored.

    The parser has a list of predefined words, see table below. When a word is parsed the parser expects a series of commands of the same type, that will consume arguments until a command change. There are also short forms of the commands available, where applicable these match the SVG path data syntax thus making ctx a superset of SVG path data.

    graygraysets gray source, and color model
    grayagray alphasets gray alpha source, and color model
    rgbr g bsets rgb source
    rgbar g b argb alpha color source
    cmykc m y kcmyk color source
    cmykac m y k acmyk alpha color source
    drgbr g bsets rgb source, in device space
    drgbar g b argb alpha color source, in device space
    dcmykc m y kcmyk color source, in device space
    dcmykac m y k acmyk alpha color source, in device space
    A arcTox1 y1 x2 y2 radius
    B arcx y radius angle1 angle2 direction
    C curveTocx1 cy1 cx2 cy2 x y
    E stroke
    F fill
    G restore
    H horLineTox
    J rotateradians
    L lineTox y
    M moveTox y
    N beginPath
    O scalescale_x scale_y
    Q quadTocx cy x y
    S smoothTocx cy x y
    T smoothQuadTox y
    U reset
    V verLineToy
    X exit
    X done
    Z closePath
    W transforma b c d e fapplies the transformation matrix
    a relArcTo5
    b clip
    c relCurveTo6
    g save
    e translatex y
    f linearGradientx0 y0 x1 y1
    h relHorLinex
    l relLineTox y
    m relMoveTox y
    n font"font name"
    o radialGradient6
    p gradientAddStop addStoppos R G B Aarguments depend on current color model, you can change the color model without setting a color by specifying a color without arguments.
    q relQuadTocx cy x y
    r rectanglex y width height
    rectx y width height
    s relSmoothTocx cy x y
    t relSmoothQuadTox y
    u strokeText"utf8-string"
    v relVerLiney
    w glyphunichardraws a single unicode character, the character no is currently passed in as decimal. (might be made internal), since a text string duplicates the API.
    x text"utf8-string" | kerningInterspersed with utf8 sequences of text numbers can appear that shift the horizontal position.
    y identity
    z closePath
    kw lineWidthline_width
    kj lineJoin joinbevel | round | miter0 1 or 2 are also valid values
    kc lineCap capnone | round | square
    kf fontSizefont_size
    kt textAlignstart | end | center | left | right
    kt textAlignstart | end | center | left | right
    kb textBaselinealphabetic | top | bottom | middle | hanging | ideographic
    ka globalAlphaalpha
    ka compositingModesourceOver | copy | clear | sourceIn | sourceOut | sourceAtop | destinationOver | destination | destinationIn | destination_out | destinationAtop | xor
    kB blendModenormal | multiply | screen | overlay | darken | lighten | colorDodge | colorBurn | hardLight | softLight | difference | exclusion | hue | saturation | color | luminosity | divide | addition | subtract
    ks shadowBlurblur radius
    kC shadowColorcolor componentsworks like gradientAddStop
    kx shadowOffsetXhorizontal_offset
    ky shadowOffsetYvertical_offset

    ctx renderstream

    ctx can render directly to a framebuffer or batch drawing commands in a list for later playback, in immediate-mode the rasterizer is handed commands as they are generated, and when recording to renderstream the same data is built up in a compact binary representation.

    During rendering the renderstream memory is not touched by the rasterizer, this permits multiple threads to render different tiles/parts of a framebuffer without locking.

    On one of the micro-controller tests this is instead done in serial, generating the output framebuffer in chunks when the microcontroller would be unable to fit a full copy in RAM.

    The renderer uses by a ctx context can be overriden, for now ctx comes with backend for driving cairo - opening up for SVG and PDF output, as well as backends for generating ctx syntax to a FILE * and a backend that computes a grid of hashes, permitting improved re-rendering speed for mostly static UIs.


    It is early stages for ctx, more instructions on integration and use read the header file itself. A git repository with tests and examples will eventually appear.

    Library header: ctx.h, ctx-font-regular.h font used in examples, card10 test l0dable: ctx.elf

    semi-ordered roadmap

    • reduce size and improve performance
    • rasterizer glitches
    • clipping path (currently the boundng box of specified path is used)
    • point in fill/stroke, (current in_fill API uses path boundingbox)
    • texture upload as part of ctx syntax
    • ICC color management (provisions have been made in the API for this, but RGBtoRGB and CMYKtoRGB RGBtoCMYK conversions are currently done naively, instead of using babl.)
    • compositing groups
    • glyph fallback
    • faster shadow blur
    • gradients: use both circle centers
    • stroking
      • miter limit (unless round joins are used, all corners are mitered now)
      • dash pattern
      • reimplement as a stroke_to_path
    • optional 32bit float implementation of rasterizer, continuing to produce 8bit alpha coverage.
    • 32bit float covarge computation in rasterizer
    • add manual SIMD (when compiled with -Ofast gcc already vectorizes quite a bit of the code, even better alignment hints/constraints could speed up the code generation for gcc on existing code).

    test-size.c compiled size test-case

    #include <stdint.h>
    #include "ctx-font-regular.h"
    #include "ctx.h"
    #define WIDTH    72
    #define HEIGHT   24
    uint8_t pixels[WIDTH*HEIGHT*4];
    int main (int argc, char **argv)
      Ctx *ctx = ctx_new_for_framebuffer (
        pixels, WIDTH, HEIGHT, WIDTH*4,
      ctx_set_rgba (ctx, 0.5, 0.5, 0.5, 1);
      ctx_rectangle (ctx, 0, 0, 80, 24);
      ctx_fill (ctx);
      char *utf8 = "tinytest\necho foobaz\n";
      ctx_move_to (ctx, 10, 9);
      ctx_set_font_size (ctx, 12);
      ctx_set_line_width (ctx, 2);
      ctx_set_rgba (ctx, 0, 0, 0, 1);
      ctx_text_stroke (ctx, utf8);
      ctx_set_rgba (ctx, 1,0, 1.0, 1.0, 1.0);
      ctx_move_to (ctx, 10, 9);
      ctx_text (ctx, utf8);
      static char *utf8_gray_scale[]={" ","░","▒","▓","█","█", NULL};
      int no=0;
      for (int y= 0; y < HEIGHT; y++)
        for (int x = 0; x < WIDTH; x++, no+=4)
          printf ("%s", utf8_gray_scale[5-(int)CTX_CLAMP(pixels[no+1]/255.0*6.0, 0, 5)]);
        printf ("\n");
      ctx_free (ctx);
      return 0;
    which outputs (the rightmost column of pixels is a bug):
    ~/src/ctx/tests$ ./test-size 
    ▓▓▓▓▓▓▓▓▓▓██▓▓█ █▓▓▓▓▓▓▓▓▓▓▓▓███▓▓▓▓▓▓▓▓▓▓▓▓▓███▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓█
    ▓▓▓▓▓▓▓▓▓██ ██████████▓██▓▓▓██▓▓██▓████▓▓█████▒███▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓█
    ▓▓▓▓▓▓▓▓▓█░  ░█ ██░░ ░██ █▓█ █   ▓█▒  ███   ▓▓   █▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓█
    ▓▓▓▓▓▓▓▓▓██ ███ ██ ▓█░██░███░█▓▓██▓░██░██░████▒███▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓█
    ▓▓▓▓▓▓▓▓▓▓█ █▓█ ██░██▓▓█▓▒█▒▓█▓▒██░▓██░▓█ ▓███▒██▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓█
    ▓▓▓▓▓▓▓▓▓▓█ █▓█ ██░██▓▓██ █ ██▓▓██░▒▓▓▓███▒░▒█▒█▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓█
    ▓▓▓▓▓▓▓▓▓▓█ ███ ██░██▓▓██▒░▒██▓▓██▒▒████████░█▒███▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓█
    ▓▓▓▓▓▓▓▓▓▓█▒░▒█ ██░██▓▓██▓ █▓██░░██░░▒░█▓░▒░▓██░░█▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓█
    ▓▓▓▓▓▓▓▓▓▓▓█████▓██▓▓██▓██ █▓▓████▓██████████▓████▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓█
    ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓█▓ █▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓█
    ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓█ █▓▓▓▓▓▓▓▓▓▓▓▓▓██░░█▓▓▓▓▓▓▓▓▓▓▓█▒▓█▓▓▓▓▓▓▓▓▓▓▓▓▓█
    ▓▓▓▓▓▓▓▓▓▓▓████▓▓▓█████ ███▓▓▓████▓▓▓▓█░███████▓▓████▓█▒▓███▓▓████▓▓████
    ▓▓▓▓▓▓▓▓▓▓██▒▒▓███   ▓█ ▒ ░█▓█▒  ██▓▓█▒  ░█▓  ████  ▒██▒░  ███   ▓██   █
    ▓▓▓▓▓▓▓▓▓██░██▒██▒▓████ ▓█▒▓█▓▒██░█▓▓██░███░██░██░██▒▓█▒░██░█████░██████
    ▓▓▓▓▓▓▓▓▓█▓▒██▓▒█ █▓▓▓█ ███▒█░███░█▓▓▓█░██▓▓██▓▓█ ███░█▒▓██▒███▓▓░▓███░█
    ▓▓▓▓▓▓▓▓▓█▓▒█████ █▓▓▓█ ███▒█░█▓█░█▓▓▓█░██▒▓██▓▓█░███░█▒▓██▒▓█ ██▒▓██░▓█
    ▓▓▓▓▓▓▓▓▓█▓░█████░█████ ███▒█░▓██ █▓▓▓█░██▓░██░██ ██▓░█▒▒██░██░██░▓█▒▒██
    ▓▓▓▓▓▓▓▓▓▓█▓░▒░▓██ ▒░██ ███▒██░░ ▓█▓▓▓█░█▓█▒  ▒██▓ ░░██▒░▒░▒██░░░▒▓█ ▒▒█