Canvas

A picture the size of the widget that your own code draws on: points, lines, shapes, text, a graph nothing else draws. It is LVGL's lv_canvas. Its pixels are a buffer of its own, taken when its screen is built - from PSRAM where the board has it switched on, otherwise from the chip's own RAM - and freed when the screen is deleted. The designer gives it the colour it starts as; what is drawn on it after that is your code's.

Group: Visualiser · Default size: 120 × 80 · Style parts: Main · Exports: Arduino, ESP-IDF, MicroPython

SettingDefaultWhat it does
PixelsColour - 2 bytes a pixelWhat each pixel holds. Colour and transparency is 4 bytes a pixel (3 on LVGL 8 with a 16-bit display), and wherever nothing is drawn what is behind the canvas shows through.
Starts aswhiteThe colour every pixel is when the screen is built, and what Clear canvas puts back unless it is given a colour of its own.
Starting opacity255With transparency only: 0 is clear, 255 solid.

What it costs

The buffer is the widget's width × height × the bytes a pixel, so the size of the box is the memory it takes: 120 × 80 in colour is 18.8 KB, a whole 320 × 240 screen is 150 KB, and with transparency twice that. On a board with PSRAM switched on that is easily had. On one without it comes out of the chip's own RAM, which LVGL, its draw buffer, Wi-Fi and everything else share - the Problems tab says how much the design's canvases take, warns past 32 KB and says it is an error when they cannot fit. A canvas whose buffer cannot be had is drawn empty and says so on LVGL's log; nothing stops. A screen that is not Temporary keeps its canvases' buffers once it has been shown.

Drawing on it

Each canvas is declared in ui.h like any widget (ui_Canvas1), and the export's lw_canvas.h adds three calls: ui_canvas_buffer(canvas) (its pixels, or NULL when there was not the memory), ui_canvas_clear(canvas, colour, opacity) and ui_canvas_attach(), which the screen calls. The README of the export has an example for the design's own LVGL version. In short, on LVGL 9 a pixel is lv_canvas_set_px() and anything else is drawn through a layer (lv_canvas_init_layer(), lv_draw_rect(), lv_draw_line(), lv_draw_label(), lv_canvas_finish_layer()); on LVGL 8 there are lv_canvas_set_px_color(), lv_canvas_draw_rect(), lv_canvas_draw_line() and the rest. Draw once the screen is built (a Screen loaded event calling a function of yours is the place) and only while ui_canvas_buffer() is not NULL.

On MicroPython the buffer is a bytearray the screen module keeps, and the canvas is ui.ui_Canvas1: ui.ui_Canvas1.set_px(10, 10, lv.color_hex(0xFF0000), 255). ESPHome has a canvas of its own that starts empty and is drawn on by ESPHome's actions; this export does not write it yet, so the widget is greyed there.

The Clear canvas action wipes it to the colour it starts as, or to one of the event's own, from any event or automation.

Example: a sketch pad that is cleared by a button

  1. Drag a Canvas onto the screen, name it Pad, and make it 200 × 120.
  2. Under Canvas settings set Starts as to a light grey, #ECEFF1.
  3. Drag a Button under it, caption Clear, and give it a Clicked event with the action Clear canvas, Canvas Pad, To The colour it starts as.
  4. Give the canvas a Pressed event that calls a function of yours, pad_draw, and in ui_events.c set the pixel under the finger, so each tap leaves a dot: on LVGL 9 lv_indev_get_point(lv_indev_active(), &p), take the canvas's lv_obj_get_coords() corner off it, and lv_canvas_set_px(ui_Pad, x, y, lv_color_hex(0x263238), LV_OPA_COVER). Tick Clickable under Flags so it takes a press.