LVGL Canvas Updates: Respect Pixel Format and Row Stride in CPU-Drawn Overlays

Use the canvas buffer’s actual format and row stride for every CPU write. Width describes visible pixels; stride describes the byte distance between row starts. They are interchangeable only in a deliberately tightly packed layout. Packed index formats also need bit selection and palette handling, so a generic bytes-per-pixel loop is not sufficient.

Applicability: this guide pins LVGL v9.3.0 and begins with a CPU-owned ARGB8888 canvas using an explicit padded stride. It assumes serialized updates with no concurrent drawing into the same storage. Display DMA, hardware cache maintenance and decoder RAM optimization are outside scope. All buffer fixtures below are proposed; no canvas executable was run.

Three padded ARGB8888 rows show visible byte ranges and padding, with correct and incorrect address formulas and a separate I1 boundary test.
Original hypothetical storage map. Byte offsets are calculated for the stated explicit stride; this is not a target memory dump or DMA layout guarantee.

1. Record the storage contract

The 9.3 canvas reference describes using a buffer as the drawing surface. Record width, height, color format, stride, usable capacity, data alignment and ownership lifetime together. Include any palette or additional planes required by the selected format.

For a hypothetical overlay, choose width five pixels, height three rows, ARGB8888 format and a 32-byte row stride. Five four-byte pixels consume 20 bytes of each row; the remaining 12 bytes are padding. The three rows occupy 96 bytes. This explicit stride is a test choice, not a claim about the default alignment of every LVGL build.

Keep the buffer and its descriptor alive while the canvas references them. Allocate according to the selected renderer’s alignment requirements. A temporary local array and a correct address formula are still insufficient if the array disappears before the next redraw.

2. Select the appropriate buffer API

In the tagged canvas implementation, lv_canvas_set_buffer calculates stride from width and format. It does not accept an arbitrary caller-supplied stride. To use the explicit padded fixture, initialize an lv_draw_buf_t and attach it with lv_canvas_set_draw_buf.

The draw-buffer API accepts dimensions, format, stride, data and capacity through lv_draw_buf_init. Check its result and validate the complete application allocation before attachment. Do not assume a successful initializer proves that every later coordinate or arithmetic operation is safe.

The default stride calculation rounds pixel bits to bytes and then to the configured stride alignment. Handler customization can change that calculation. Read the descriptor’s resolved stride rather than duplicating a guessed build-time rule in the overlay producer.

3. Calculate addresses from row starts

For this non-indexed four-byte format, the byte offset is y × stride + x × 4. Validate 0 ≤ x < width and 0 ≤ y < height before converting signed coordinates to unsigned indices. Check multiplication and addition in a type wide enough for the supported dimensions and capacity.

Pixel Offset Bytes written
(0, 0) 0 0 through 3
(4, 0) 16 16 through 19
(0, 1) 32 32 through 35
(4, 2) 80 80 through 83

The tempting expression (y × width + x) × 4 puts (0, 1) at offset 20, inside row-zero padding. An all-black fill can conceal that error. Distinct row colors and a marked last pixel make the displaced writes obvious.

The tagged lv_draw_buf_goto_xy helper includes stride and format-dependent addressing, but callers still need coordinate validation. Do not treat an address helper as a bounds-checked pixel operation. When passing pointers to an external CPU drawing routine, make its row pitch and capacity explicit too.

4. Verify color representation separately

The v9.3.0 color definitions identify ARGB8888 as 32 bits per pixel and define lv_color32_t with blue, green, red and alpha byte fields. Use verified fields or explicit byte encoding instead of assuming that an RGB helper has the same memory layout as a four-byte canvas pixel.

For example, a deliberately chosen pixel with red 17, green 34, blue 51 and alpha 255 should retain those channel values when decoded. Unequal channel values reveal swaps that grayscale cannot. Compare a raw byte inspection with the canvas’s ARGB8888 pixel getter and a rendered reference patch.

Keep alpha opaque for the initial stride fixture. Introduce transparency only after addressing and channel identity pass, so a compositing difference does not disguise a row-layout defect. Premultiplied-alpha conversion is a separate representation contract.

5. Handle packed and multi-plane formats deliberately

The pinned draw-buffer address helper skips an indexed palette and advances by the packed pixel-bit position. For I1, several pixels share a byte. The canvas setter selects bit 7 minus (x modulo 8), preserving neighboring bits. Writing one whole byte per pixel would corrupt the row even if its stride were correct.

Build a separate I1 fixture around x positions seven and eight, which cross a byte boundary. Initialize the palette explicitly and verify that changing one index does not alter its neighbors. Include widths that are not multiples of eight and padding after the final partial byte.

Do not extend the ARGB8888 capacity formula to every format. Indexed buffers include palette storage, and RGB565A8 includes an additional alpha plane in the tagged allocation logic. Use the exact format’s layout and supported canvas operations; a format enum alone does not prove that every drawing helper handles it.

6. Publish the completed update and inspect canaries

Initialize row padding and guard storage with a known diagnostic pattern, then write only visible pixels. Verify padding remains unchanged and guard bytes outside the usable allocation are untouched. These checks must precede rendering: a renderer may have its own valid writes to a surface, and a screenshot cannot reveal every out-of-bounds CPU store.

After the batch, request canvas redraw with the supported invalidation path. Direct memory stores do not call object invalidation for you. If the selected image path retains a derived cached representation, invalidate that representation as well; distinguish this software image-cache issue from hardware data-cache maintenance.

  1. Draw alternating row colors, then a checkerboard and four uniquely colored corners.
  2. Compare the four address-table entries and verify every row’s padding canaries.
  3. Update only the last row and confirm earlier rows are unchanged.
  4. Reject negative and one-past-end coordinates without modifying storage.
  5. Repeat after changing width while keeping an intentionally padded stride, then repeat with the packed-index fixture.

Retain the descriptor values, arithmetic worksheet, memory checks and rendered image. Accept the overlay when addressing, representation and redraw notification agree. A correct first row is insufficient evidence that the rest of the canvas uses the right storage layout.

Similar Posts