Lightweight C library for HTML5 websockets

lws_display_list is a modernized 1970s-style Display List of graphic primitives held in an lws_dll2 list of Display List Objects (DLOs).

Provided DLO primitives are:

  • filled rectangle (with individually controllable rounded corners)
  • PNG (1:1 and original orientation only, transparency supported)
  • JPEG (1:1 and original orientation only)
  • utf-8 text areas (using compressed, antialiased mcufonts)

The aim of it is to process some other representation to describe the logical scene completely using DLOs in memory, discard the earlier representation and then rasterize the Display List a single line at a time from top to bottom, so no backing framebuffer is required at all. DLOs are destroyed as they go out of scope during rasterization.

Although the memory required does scale with scene complexity in terms of number of DLOs, it hardly scales at all with output resolution, allowing modern 32-bpp rendering on very constrained devices, if slowly.

DLO capabilities

DLOs are quite capable

  • no floats (lws_fx integer fixed-point)
  • 16-bit signed coordinate space with off-surface clipping handled
  • Internal 32-bpp RGBA colourspace (8-bit opacity)
  • correct Z-order opacity resolution
  • Supports arbitrary paletteization (down to 1bpp) and error diffusion
  • DLO-private error diffusion for clean opaque overlaid objects
  • Antialised bitmap text using compressed fonts (eg 7 font sizes 10- 32px in both regular and bold < ~100KB)
  • Very lightweight stateful PNG and JPEG decode

All DLOs in a Display List are consumed as they are rasterized, individual DLOs are destroyed as soon as they go out of scope during top - bottom rendering, freeing any related resources as soon as possible.


DLOs may point to a compressed PNG, which is decompressed on the fly and the decompression context destroyed as the rasterization goes beyond its bounding box. Using the lws stateful rewrite of upng, the memory cost of 32-bpp PNG decode of any dimensions is 40K + 16 x width bytes, including error diffusion line buffers. Decoding of the compressed PNG data is done statefully on demand as needed to fill an output line, so no memory is needed to hold excess decode production.

Multiple PNG DLOs including PNG-over-PNG (with alpha mixing) are allowed. PNGs only take heap memory while the current rasterization line intersects them, so any number of PNGs that don't intersect vertically do not cost any more peak memory allocation than decoding one, since the decoding contexts and DLOs of the earlier ones have been destroyed before the next one's decoding context is allocated.


DLOs can also represent JPEGs using a stream parsing rewite of picojpeg. No framebuffer is required to hold the output, it produces one line of pixels at a time. JPEGs use either 8- or 16- line deep MCUs, necessitating an 8 or 16 line RGB (or Y if grayscale) pixel buffer during decode.

Heap requirements while a JPG is being rasterized is 2.5KB plus the MCU buffer dependent on the chroma coding:

Image type Fixed alloc MCU buffer
grayscale 2.5KB image width x 8 bytes
YUV 4:4:4 2.5KB image width x 24 bytes
YUV 4:4:2v 2.5KB image width x 24 bytes
YUV 4:4:2h 2.5KB image width x 48 bytes
YUV 4:4:0 2.5KB image width x 48 bytes

DLO text

Text DLOs are predicated around unicode utf-8 and a stream parsing rewrite of mcufont decoder.

mcufont includes a ttf renderer app which is ported into lws as well, this allows production of antialised (16 alpha level) compressed bitmaped fonts from any ttf font at a selected size and including specified subsets of unicode code points.

Font glyphs are decompressed statefully as part of the DLO line rasterization process, so there are no glyph buffers or caching. The decompression is very fast and allows fitting over a dozen font sizes and weights into 100KB.

Wrapping inside a bounding box is supported as is "run-on", where text DLOs follow one another inline, used for example to use a bold font in part of a text using a different DLO with a different font before continuing with another DLO using the non-bold font cleanly. DLOs are marked as running-on or not.

Centering and right-justification is possible by summing run-ons on the current line by walking the display list backwards until a non- run-on DLO is seen, and adjusting the affected DLOs x position.

Display List lifecycle

Create empty display list

Create the display state (the dynamic counterpart of the const, static lws_display definition) and the empty display list.

lws_display_state_t lds;
lws_displaylist_t dl;

Instantiate the lws_display and bind the display list to it

lws_display_state_init(&lds, cx, 30000, 10000, lls, &disp.disp);
lws_display_dl_init(&dl, &lds);
### Create DLOs into the list
Eg, create a png from `data` / `len`, return NULL if failed.

if (!lws_display_dlo_png_new(&dl, &box, data, len)) {

Eg, create a white background rectange the size of the `lws_display`

lws_dlo_rect_t *dr; lws_box_t box = { 0, 0, dl.ds->disp->ic.wh_px[0].whole, dl.ds->disp->ic.wh_px[1].whole };

dr = lws_display_dlo_rect_new(&dl, &box, 0, LWSDC_RGBA(255, 255, 255, 255)); if (!dr) return 1;

### Rendering into an lws_display

lds->disp->blit(lds, (uint8_t *)&dl, &box); ```