Skip to content

feat(lua): add lvgl.bitmap object with owned image pixels - #7972

Open
thomaskistler wants to merge 1 commit into
EdgeTX:mainfrom
thomaskistler:thomaskistler/lvgl-bitmap-widget
Open

thomaskistler wants to merge 1 commit into
EdgeTX:mainfrom
thomaskistler:thomaskistler/lvgl-bitmap-widget

Conversation

@thomaskistler

@thomaskistler thomaskistler commented Nov 1, 2026 •

Copy link
Copy Markdown

Fixes: N/A (feature; no linked issue).

Summary of changes:

Adds lvgl.bitmap([parent, ]params) and lvgl.BITMAP for lvgl.build(): an image object that owns its decoded, scaled pixels rather than relying on LVGL's shared image cache. This is an alternative to #7947, keeping bitmap ownership in a separate object instead of adding another source type to lvgl.image.

local tile = lvgl.bitmap({
  file = "/AEROGRID/maps/tiles/set/16/15080/27863.jpg",
  x = 0, y = 0, w = 256, h = 256,
})
tile:set({ file = "/AEROGRID/maps/tiles/set/16/15081/27863.jpg" })

Motivation

lvgl.image decodes through LVGL's shared image cache, which has 8 entries (LV_IMG_CACHE_DEF_SIZE). A Lua widget that shows more images than that, for example a map made of 256x256 JPEG tiles, keeps evicting images that are still visible and decodes them again on redraw. lvgl.bitmap decodes once when its file changes and the object owns the pixels, so a widget can keep one object per image and decide for itself which ones stay decoded.

Details

  • file is a string or a function, like lvgl.image. A function is only called while the object is visible. Setting a string replaces a previous function. Repeating the same file and frame size does not decode again.
  • The image is scaled to fit the frame and centered when decoded. Changing w or h reloads the file. Hidden objects keep their pixels.
  • StaticBitmap::setSource(filename) chooses RGB565 for opaque images and ARGB4444 for images with alpha. setSource(filename, fmt) forces a format, and the constructor accepts the same optional format. Existing callers in model selection, file preview, and the model bitmap widget explicitly retain ARGB4444 rendering.
  • Opaque images retain RGB565 color and use two bytes per scaled pixel instead of being reduced to four-bit color channels and expanded to a three-byte color-plus-alpha format. Images with transparency use ARGB4444 converted to LVGL color-plus-alpha, at three bytes per scaled pixel. Grayscale-alpha PNGs also retain their transparency.
  • Pixel memory is not counted against the Lua bitmap memory limit (LUA_MEM_EXTRA_MAX). The per-object frame limit is not a total-memory budget; scripts control how many decoded images they retain.
  • A new file is only applied after all parameters of an update have been accepted. A rejected set() preserves the source and releases its staged callback. Changing file and size together decodes once, after any dynamic filename and size callbacks have supplied their final values. Other parameters retain the existing partially applied behavior of set().
  • A file that cannot be loaded, does not fit in memory, or produces an unsupported scaled size leaves the object blank. It is not retried until the file or frame size changes. set({file=''}) clears the image immediately, including while hidden.
  • The frame must have positive pixel dimensions, or come from a size function, and cover at most four display areas. Frames larger than the display are allowed. The scaled image must fit LVGL's 2047-pixel-per-axis descriptor limit; bitmap source and scaled-allocation dimensions are checked before narrowing or scaling.
  • The image stays behind child objects. lvgl.clear(bitmap) removes all children except the object's own image canvas. Its pixels are freed when the source changes, the source is cleared, or the native window closes. RGB565 scaling fills the complete destination, and conversion failures leave no incorrectly formatted canvas.
  • Uses the existing Lua/LVGL construction, callback, and handle lifecycle. Shared Lua/LVGL hardening and existing filename-decoder cleanup are outside this PR.
  • ETX_BITMAP is inserted after ETX_QRCODE, shifting subsequent numeric type constants by one. Scripts should use the named lvgl.* constants. The new API is documented in its luadoc block and marked as introduced in 3.0.0.

Validation

Native test build succeeded. All 292 tests across 39 suites passed. Under GuardMalloc, all 77 bitmap/Lua/LCD/window tests passed, plus ten shuffled passes of the 49 bitmap tests.

Not performed: ARM firmware or Companion builds, hardware performance measurements, or radio heap measurements.

Add the bitmap object and Lua registration on upstream main without shared Lua/LVGL hardening. Keep RGB565 and alpha conversion, source/frame decode coalescing, protected bitmap-only setter staging, image-canvas preservation, and source/scaling bounds.

Make update virtual for bitmap-specific setters. Retain the existing constructor, callback and manager behavior and cover the feature with bitmap-focused regressions.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@thomaskistler
thomaskistler force-pushed the thomaskistler/lvgl-bitmap-widget branch from 8a43230 to 2c63158 Compare November 3, 2026 06:00

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants