Bedtime. /Graphics API

Graphics API

Drawing primitives

Every call takes the gfx_t* passed to the redraw callback. There is no cursor and no transform stack: each call takes absolute screen coordinates and paints in the current draw colour.

Color model

The panel is 1-bit and the frame starts cleared to black, so drawing means turning pixels on. gfx_set_color() selects what a draw call does to the pixels it touches:

Every draw callback (redraw, a hint painter, a menu painter...) starts from the same default state: colour 1, transparent bitmaps, the default font, and the full drawing area (the screen, or the tapp's window). Any change lasts until that callback returns - nothing needs to be restored.

Coordinate system

Origin (0,0) is the top-left pixel, x grows right, y grows down, and the screen is SCREEN_WIDTH x SCREEN_HEIGHT = 400x240. Coordinates are gfx_coord_t, SIGNED: a shape may start left of or above the panel, and every primitive clips it against the display and the clip window - partly off-screen draws the visible part.

Dithering - grey on a 1-bit panel

The _dithered calls paint an ordered 8x8 Bayer pattern in the current colour instead of a solid fill, which the eye reads as grey. shade is 0-7 and picks how many pixels get painted:

shade01234567
pixels set6%12%25%37%50%62%75%87%

In the usual colour 1 that runs from nearly black to nearly white. The pattern is anchored to screen coordinates rather than to the shape, so neighbouring fills of the same shade line up seamlessly.

A minimal redraw

static void my_redraw(gfx_t* gfx, const os_app_t* app) {
    my_model_t* m = os_app_get_model(app);

    gfx_set_font(gfx, gfx_nunito_bold_18);
    gfx_draw_str(gfx, 20, 40, "hello");

    gfx_fill_rect_dithered(gfx, 20, 60, 360, 20, 3);  // grey bar
    gfx_draw_rect_r(gfx, 20, 60, 360, 20, 4);         // outline over it

    gfx_set_color(gfx, GFX_COLOR_XOR);                // XOR the playhead in
    gfx_draw_vline(gfx, 20 + m->cursor, 60, 20);
}
void gfx_draw_pixel(gfx_t* gfx, gfx_coord_t x, gfx_coord_t y)

Paint a single pixel in the current colour

gfx_t*gfxGraphics context
gfx_coord_txX coordinate
gfx_coord_tyY coordinate
void gfx_draw_hline(gfx_t* gfx, gfx_coord_t x, gfx_coord_t y, gfx_coord_t w)

Horizontal line running right from (x, y)

gfx_t*gfxGraphics context
gfx_coord_txLeft end
gfx_coord_tyRow
gfx_coord_twLength in pixels

Note — This is the fastest primitive - it writes whole framebuffer bytes at a time. A fill built from hlines is substantially faster than one built from pixels.

void gfx_draw_vline(gfx_t* gfx, gfx_coord_t x, gfx_coord_t y, gfx_coord_t h)

Vertical line running down from (x, y)

gfx_t*gfxGraphics context
gfx_coord_txColumn
gfx_coord_tyTop end
gfx_coord_thLength in pixels
void gfx_draw_line(gfx_t* gfx, gfx_coord_t x1, gfx_coord_t y1, gfx_coord_t x2, gfx_coord_t y2)

Line between two arbitrary points (Bresenham)

gfx_t*gfxGraphics context
gfx_coord_tx1First point X
gfx_coord_ty1First point Y
gfx_coord_tx2Second point X
gfx_coord_ty2Second point Y
void gfx_draw_line_thick(gfx_t* gfx, gfx_coord_t x1, gfx_coord_t y1, gfx_coord_t x2, gfx_coord_t y2, uint16_t thickness)

Line with a thickness, drawn as a stack of parallel lines

gfx_t*gfxGraphics context
gfx_coord_tx1First point X
gfx_coord_ty1First point Y
gfx_coord_tx2Second point X
gfx_coord_ty2Second point Y
uint16_tthicknessWidth in pixels; 1 is gfx_draw_line()
void gfx_draw_bezier(gfx_t* gfx, gfx_coord_t x0, gfx_coord_t y0, gfx_coord_t x1, gfx_coord_t y1, gfx_coord_t x2, gfx_coord_t y2)

Quadratic Bezier curve through 16 straight segments

gfx_t*gfxGraphics context
gfx_coord_tx0Start X
gfx_coord_ty0Start Y
gfx_coord_tx1Control point X - the curve is pulled toward it but does not touch it
gfx_coord_ty1Control point Y
gfx_coord_tx2End X
gfx_coord_ty2End Y
void gfx_draw_rect(gfx_t* gfx, gfx_coord_t x, gfx_coord_t y, gfx_coord_t w, gfx_coord_t h)

Rectangle outline

gfx_t*gfxGraphics context
gfx_coord_txLeft edge
gfx_coord_tyTop edge
gfx_coord_twWidth in pixels
gfx_coord_thHeight in pixels
void gfx_draw_rect_r(gfx_t* gfx, gfx_coord_t x, gfx_coord_t y, gfx_coord_t w, gfx_coord_t h, uint16_t r)

Rectangle outline with rounded corners

gfx_t*gfxGraphics context
gfx_coord_txLeft edge
gfx_coord_tyTop edge
gfx_coord_twWidth in pixels
gfx_coord_thHeight in pixels
uint16_trCorner radius; must not exceed half the shorter side
void gfx_draw_rect_fill(gfx_t* gfx, gfx_coord_t x, gfx_coord_t y, gfx_coord_t w, gfx_coord_t h)

Solid rectangle

gfx_t*gfxGraphics context
gfx_coord_txLeft edge
gfx_coord_tyTop edge
gfx_coord_twWidth in pixels
gfx_coord_thHeight in pixels

Note — Filling with colour 0 erases a region before it is redrawn.

void gfx_draw_rect_fill_r(gfx_t* gfx, gfx_coord_t x, gfx_coord_t y, gfx_coord_t w, gfx_coord_t h, uint16_t r)

Solid rectangle with rounded corners

gfx_t*gfxGraphics context
gfx_coord_txLeft edge
gfx_coord_tyTop edge
gfx_coord_twWidth in pixels
gfx_coord_thHeight in pixels
uint16_trCorner radius; must not exceed half the shorter side
void gfx_fill_rect_dithered(gfx_t* gfx, gfx_coord_t x, gfx_coord_t y, gfx_coord_t w, gfx_coord_t h, uint8_t shade)

Rectangle filled with a dither pattern instead of solid colour

gfx_t*gfxGraphics context
gfx_coord_txLeft edge
gfx_coord_tyTop edge
gfx_coord_twWidth in pixels
gfx_coord_thHeight in pixels
uint8_tshade0-7, see gfx_shades - 0 is the sparsest, 7 the densest
void gfx_draw_hline_dithered(gfx_t* gfx, gfx_coord_t x, gfx_coord_t y, gfx_coord_t w, uint8_t shade)

Dithered horizontal line

gfx_t*gfxGraphics context
gfx_coord_txLeft end
gfx_coord_tyRow
gfx_coord_twLength in pixels
uint8_tshade0-7, see gfx_shades
void gfx_draw_vline_dithered(gfx_t* gfx, gfx_coord_t x, gfx_coord_t y, gfx_coord_t h, uint8_t shade)

Dithered vertical line

gfx_t*gfxGraphics context
gfx_coord_txColumn
gfx_coord_tyTop end
gfx_coord_thLength in pixels
uint8_tshade0-7, see gfx_shades
void gfx_fill_circle_dithered(gfx_t* gfx, gfx_coord_t cx, gfx_coord_t cy, gfx_coord_t r, uint8_t shade)

Dithered filled circle

gfx_t*gfxGraphics context
gfx_coord_tcxCentre X
gfx_coord_tcyCentre Y
gfx_coord_trRadius in pixels
uint8_tshade0-7, see gfx_shades
void gfx_fill_rect_r_dithered(gfx_t* gfx, gfx_coord_t x, gfx_coord_t y, gfx_coord_t w, gfx_coord_t h, gfx_coord_t r, uint8_t shade)

Dithered rounded rectangle

gfx_t*gfxGraphics context
gfx_coord_txLeft edge
gfx_coord_tyTop edge
gfx_coord_twWidth in pixels
gfx_coord_thHeight in pixels
gfx_coord_trCorner radius
uint8_tshade0-7, see gfx_shades
void gfx_fill_rect_gradient_v_dithered_bayer(gfx_t* gfx, gfx_coord_t x, gfx_coord_t y, gfx_coord_t w, gfx_coord_t h, uint8_t shade_top, uint8_t shade_bottom)

Rectangle with a vertical dither gradient

gfx_t*gfxGraphics context
gfx_coord_txLeft edge
gfx_coord_tyTop edge
gfx_coord_twWidth in pixels
gfx_coord_thHeight in pixels
uint8_tshade_topShade at the top row, 0-7
uint8_tshade_bottomShade at the bottom row, 0-7

Note — Equal top and bottom shades give a flat fill, so this doubles as gfx_fill_rect_dithered() when a gradient is animated in and out.

void gfx_fill_rect_gradient_h_dithered_bayer(gfx_t* gfx, gfx_coord_t x, gfx_coord_t y, gfx_coord_t w, gfx_coord_t h, uint8_t shade_left, uint8_t shade_right)

Rectangle with a horizontal dither gradient

gfx_t*gfxGraphics context
gfx_coord_txLeft edge
gfx_coord_tyTop edge
gfx_coord_twWidth in pixels
gfx_coord_thHeight in pixels
uint8_tshade_leftShade at the left column, 0-7
uint8_tshade_rightShade at the right column, 0-7
void gfx_draw_circle(gfx_t* gfx, gfx_coord_t x0, gfx_coord_t y0, uint16_t r, uint8_t opt)

Circle outline, whole or by quadrant

gfx_t*gfxGraphics context
gfx_coord_tx0Centre X
gfx_coord_ty0Centre Y
uint16_trRadius in pixels
uint8_toptQuadrant mask - GFX_DRAW_ALL, or an OR of the GFX_DRAW_* family
void gfx_draw_disc(gfx_t* gfx, gfx_coord_t x0, gfx_coord_t y0, uint16_t r, uint8_t opt)

Filled circle, whole or by quadrant

gfx_t*gfxGraphics context
gfx_coord_tx0Centre X
gfx_coord_ty0Centre Y
uint16_trRadius in pixels
uint8_toptQuadrant mask - GFX_DRAW_ALL, or an OR of the GFX_DRAW_UPPER_RIGHT family
void gfx_set_color(gfx_t* gfx, uint8_t color)

Select what subsequent draw calls do to the pixels they touch

gfx_t*gfxGraphics context
uint8_tcolorGFX_COLOR_CLEAR, GFX_COLOR_SET or GFX_COLOR_XOR

Note — Lasts until the draw callback returns; the next one starts at GFX_COLOR_SET.

uint16_t gfx_draw_str(gfx_t* gfx, gfx_coord_t x, gfx_coord_t y, const char* str)

Draw a string in the current font and colour

gfx_t*gfxGraphics context
gfx_coord_txLeft edge of the first glyph
gfx_coord_tyText baseline - glyphs sit *above* this row, so y is the bottom of the line
const char*strNUL-terminated ASCII/UTF-8 string

Returns — Width drawn, in pixels - add it to x to continue on the same line

uint16_t gfx_draw_strf(gfx_t* gfx, gfx_coord_t x, gfx_coord_t y, const char* fmt, ...)

printf-style gfx_draw_str()

gfx_t*gfxGraphics context
gfx_coord_txLeft edge of the first glyph
gfx_coord_tyText baseline
const char*fmtprintf format string
...

Returns — Width drawn, in pixels

Note — `%f` costs far more than the integer conversions. For a value redrawn every frame, scale to an int explicitly: `gfx_draw_strf(gfx, x, y, "%d%%", (int)(v * 100))`.

Warning — The formatted result is built in a shared 128-byte buffer and truncated to fit. Do not call it from an audio or timer callback while the UI thread may also be drawing.

void gfx_set_font(gfx_t* gfx, const uint8_t* font)

Select the font for subsequent text calls

gfx_t*gfxGraphics context
const uint8_t*fontOne of the exported font blobs - gfx_nunito_semibold_14, gfx_nunito_bold_18 or DepartureMono_Regular_10

Note — Sticky, like the colour. Setting a font is cheap; measuring text is not, so hoist gfx_get_str_width() out of loops rather than the gfx_set_font() call.

uint16_t gfx_get_str_width(gfx_t* gfx, const char* str)

Measure a string in the current font without drawing it

gfx_t*gfxGraphics context
const char*strNUL-terminated string

Returns — Width in pixels

// centre a label
const uint16_t w = gfx_get_str_width(gfx, label);
gfx_draw_str(gfx, SCREEN_CENTER_X - (w >> 1), 120, label);
void gfx_draw_xbm(gfx_t* gfx, gfx_coord_t x, gfx_coord_t y, gfx_coord_t w, gfx_coord_t h, const uint8_t* bitmap)

Blit a 1-bit XBM bitmap

gfx_t*gfxGraphics context
gfx_coord_txLeft edge
gfx_coord_tyTop edge
gfx_coord_twBitmap width in pixels
gfx_coord_thBitmap height in pixels
const uint8_t*bitmapXBM data: one bit per pixel, LSB first, each row padded to a whole byte

Note — TRANSPARENT by default: 1 bits are painted in the current colour, 0 bits are left alone. gfx_set_bitmap_mode(gfx, false) makes blits opaque (0 bits in the opposite colour) for the rest of the callback. For assets, prefer ui_draw_img(), which takes a gfx_img_t from the asset pipeline and knows its own dimensions; this is the blit for a bitmap built at runtime.

void gfx_set_bitmap_mode(gfx_t* gfx, uint8_t is_transparent)

Transparent (default) or opaque bitmap blits, until the draw callback returns

gfx_t*gfxGraphics context
uint8_tis_transparenttrue: 0 bits are left alone; false: they are painted in the opposite colour, overwriting the bitmap's whole bounding box
void gfx_set_clip(gfx_t* gfx, gfx_coord_t x0, gfx_coord_t y0, gfx_coord_t x1, gfx_coord_t y1)

Restrict drawing to a rectangular window

gfx_t*gfxGraphics context
gfx_coord_tx0Left edge of the window
gfx_coord_ty0Top edge of the window
gfx_coord_tx1Right edge of the window (a coordinate, not a width)
gfx_coord_ty1Bottom edge of the window (a coordinate, not a height)
gfx_set_clip(gfx, 10, 40, 390, 200);   // scrolling list body
draw_rows(gfx, m);
gfx_clip_reset(gfx);

Note — Corner-to-corner, not x/y/w/h. It narrows the callback's drawing area (the screen, or the tapp's window/cell - it can never widen it) and lasts until gfx_clip_reset() or the callback's return.

void gfx_clip_reset(gfx_t* gfx)

Remove the clip window: restore the whole area this callback draws into (the screen, or the tapp's window / hint cell - never beyond it)

gfx_t*gfxGraphics context
uint32_t ui_get_frame(gfx_t* gfx)

How many frames have been PAINTED

gfx_t*gfxGraphics context

Returns — Frame counter, incremented once per frame actually pushed to the display

extern const uint8_t gfx_nunito_semibold_14[]

Default UI body font

extern const uint8_t gfx_nunito_bold_18[]

Headings / emphasis

extern const uint8_t DepartureMono_Regular_10[]

Monospace - counters, numeric readouts