Skip to content

fix(lua): address Bitmap image review feedback - #2

Merged
thomaskistler merged 7 commits into
thomaskistler/lvgl-bitmap-experimentfrom
thomaskistler/bitmap-review-fixes
Oct 26, 2026
Merged

thomaskistler merged 7 commits into
thomaskistler/lvgl-bitmap-experimentfrom
thomaskistler/bitmap-review-fixes

Conversation

@thomaskistler

@thomaskistler thomaskistler commented Oct 26, 2026 •

Copy link
Copy Markdown
Owner

Fixes: N/A (review follow-up; no linked issue).

Summary of changes:

Review follow-up to the Bitmap source support for lvgl.image (#1 / EdgeTX#7947). This PR targets the Bitmap feature branch, so the diff contains only the fixes. Commits 1–5 address the five points in pfeerick's review comment. Fixes for two regressions found later in commits 1 and 2 are folded into those commits. Commits 6 and 7 fix further problems found in a later combined review of #1 and this PR. Each commit has its own regression tests.

Behavior note: image:set({file=...}) now actually changes the displayed image and replaces any previous filename callback. Previously the new path was stored but never applied, and an existing callback kept overriding it.

1. Native window deletion freed the image's Lua handle early (047256d0dc, review point 1)

Issue: The image's LV_EVENT_DELETE handler released the image's registry ref and deleted the wrapper, but the parent's child list still held that ref number. Lua reuses freed registry slots, so after box:clear() a newly created object could get the same slot. The parent's next cleanup then cleared that unrelated object instead. The same happened after native page or dialog close. The handler also stored the caller's lua_State, which could be a coroutine that has since been garbage-collected.

Fix: The delete handler now only releases the Bitmap (and any conversion buffer) and forgets the window. The handle and its registry slot stay valid until the parent's normal cleanup, which then deletes the wrapper exactly once. Image methods, callbacks and visibility checks are guarded while the window is gone. set() on such an image raises "Image window has been deleted". The handler uses the main Lua thread rather than the creating coroutine.

The new deleted-parent check for lvgl.image(parent, ...) and lvgl.build(parent, ...) asks the object whether its window was deleted. Only images track that state, so labels, lines and triangles, which have no window, can still be used as parents and fall back to the script window as before.

2. A failed set() could leave the image blank (16a8e27cf8, review point 2)

Issue: image:set({file = {}}) cleared the displayed Bitmap before the file value was validated. image:set({bitmap = b2, bogus = 1}) could swap the source before the invalid key raised, depending on table iteration order. Either way the image stayed blank until the next successful set().

Fix: The file/bitmap source is parsed after all other parameters, so an invalid key raises before the source is touched. The new source is fully validated first: its type, unloaded or oversized Bitmaps, the pixel format and the memory budget. Its ref and conversion buffer are acquired next, and only then is the old source released and replaced. Any error therefore leaves the current image intact.

file and bitmap are read with raw lookups, so an __index metamethod on the params table can't run Lua during the update.

Other parameters in a rejected set() behave as for every other widget: keys processed before the error stay applied. Only the image source is guaranteed to stay unchanged.

3. Failed constructors leaked the Bitmap and its memory-budget charge (864507a22e, review point 3)

Issue: lvgl.image({bitmap = b, badkey = 1}) raised after taking the Bitmap ref, allocating the ARGB4444 conversion buffer and charging the Lua bitmap memory budget. Nothing released them, so each failure permanently shrank the budget available to Bitmap.open().

Fix: Image construction runs in a protected call. On failure the image's refs, conversion buffer, budget charge, native window and wrapper are all released. Failures while creating children release the partially built object, and the previous temporary parent is restored.

4. Remove lvgl.IMAGE_BITMAP_SUPPORTED (717342fbde, review point 4)

Issue: Scripts should detect the feature with a firmware version check, so the extra capability constant and its @notice aren't needed. The @status line also needed updating.

Fix: Removed the constant and its documentation. @status now says Bitmap support was added in 3.0.0, main's current version. If this is backported to 2.12, the backport needs the matching 2.12.x version.

5. {file = ..., bitmap = false} was rejected (13a1c6b127, review point 5)

Issue: The conflict check treated bitmap = false as a Bitmap, so image:set({file = "/IMAGES/a.png", bitmap = false}) failed with "Specify either file or bitmap, not both". The result also depended on table iteration order.

Fix: file and bitmap are read once, after the other parameters, so field order doesn't matter. false means "no Bitmap": with file it selects the file, alone it clears the image. A real Bitmap together with file is still rejected.

6. lvgl.clear(image) left a dangling image object (46c307b32f)

Issue: lvgl.clear(image) cleans the StaticImage window, which deletes its internal LVGL image object, but StaticImage kept the pointer. The next source change, image:set({bitmap=false}) or teardown called lv_obj_del() on freed memory and crashed.

Fix: StaticImage now overrides clear() and resets its image pointer after the window is cleaned. Later source changes create a new image object.

7. A failed child build left the wrong parent active (4ffde2356b)

Issue: lvgl.build() and the children of page/dialog constructors set a temporary parent while building nested objects but never restored it when a child raised. Objects created later were attached to that parent. With deferred image deletion, that parent could already be a deleted image wrapper.

Fix: Nested objects are built in a protected call, and the previous parent is restored before the error is re-raised.

Validation

Native TX16S Debug build on macOS with AppleClang:

  • cmake --build native --target gtests-radio: passed for each of the 7 commits.
  • The targeted filter below passed for each commit, from 45 tests after commit 1 to 57 after commit 7.
  • ./gtests-radio --gtest_filter='LuaBitmapImage.*:Lua.*:MainWindow.*:color.*:Lcd_colorlcd.bitmap:Keyboard.*' at head (4ffde2356b): passed, 57 tests.
  • ./gtests-radio (full suite) at head: passed, 274 tests.
  • MallocScribble=1 MallocGuardEdges=1 ./gtests-radio --gtest_filter='LuaBitmapImage.*' --gtest_repeat=20 at head: passed, all 32 image tests in each of the 20 runs. These macOS malloc debugging settings are not sanitizer coverage.
  • The regression tests for the windowless-parent and __index fixes (now in commits 1 and 2) and for commits 6 and 7 were also run against the code without each fix; each one failed or crashed there.

Not run:

  • ASan: disabled by the repository's AppleClang guard on this macOS setup.
  • Simulator UI and hardware testing of these commits.

@thomaskistler
thomaskistler force-pushed the thomaskistler/bitmap-review-fixes branch 4 times, most recently from bed01eb to b056ca1 Compare October 26, 2026 05:04
thomaskistler and others added 7 commits October 25, 2026 22:16
Address review point 1. Release native Bitmap resources immediately but keep the parent-owned registry slot and handle until deferred cleanup. Delete image wrappers exactly once, guard deleted windows, and retain the main Lua thread. Reject only explicit parents whose image window was deleted, so windowless objects such as labels still fall back to the script window. Include lifecycle, ref-slot reuse, coroutine and windowless-parent regressions.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Address review point 2. Parse the file/bitmap source after all other image parameters, so a rejected property never replaces it. Validate the new source and acquire its ref and conversion buffer before releasing the old source, so validation and allocation errors leave the current image intact. Read source fields with raw lookups so params table __index metamethods cannot run during the update. Include rejected-update, metamethod and decoded JPEG zero-copy regressions.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Address review point 3. Protect image construction and explicitly clean failed objects, native windows, registry refs and conversion charges. Restore parent context on failure, including child construction. Include repeated constructor failure and resource-baseline regressions.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Address review point 4. Remove the experimental capability constant and document main's Bitmap addition in 3.0.0 without guessing a 2.12 backport release.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Address review point 5. Allow file plus bitmap=false to select the file source regardless of table traversal order, while rejecting file plus an actual Bitmap. Document and test both source-field orders.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
lvgl.clear(image) cleans the StaticImage window, which deletes its
internal LVGL image object without updating StaticImage::image. A later
source change or image teardown then deleted the stale pointer again.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
lvgl.build() and children of page/dialog constructors set a temporary
parent while building nested objects but never restored it when a child
raised. Later constructors were attached to that parent, which might
already have been released and deleted.

Build nested objects in a protected call and restore the previous parent
before rethrowing.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@thomaskistler
thomaskistler force-pushed the thomaskistler/bitmap-review-fixes branch from b056ca1 to 4ffde23 Compare October 26, 2026 05:18
@thomaskistler
thomaskistler merged commit 4ffde23 into thomaskistler/lvgl-bitmap-experiment Oct 26, 2026
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.

1 participant