LVGL Image Decoder Extensions: Define Probe, Open, Read and Close Failure Ownership

A custom decoder must clean up a failed open itself. Successful probing does not transfer every temporary resource to a future close callback. After a session opens successfully, give its caller one explicit close responsibility, including when incremental decoding stops early or fails.

Applicability: this article pins LVGL v9.3.0 and a file-backed incremental decoder in the normal software image-helper path. Disable decoded-image and image-header caches for the initial fixture. Cache ownership, custom drawing callbacks and post-processing conversions need separate qualification. This is a proposed lifecycle and fault-injection procedure, not an implemented codec or executed test.

Probe, open attempt and successful session have different cleanup owners; complete and failed area sequences can share the same terminal API result.
Original source-derived lifecycle and hypothetical four-row fixture. No codec implementation, fault injection or rendering execution is claimed.

1. Draw the real callback sequence

The decoder header defines info, open, get_area and close callbacks. Use those exact signatures; an older tutorial’s source-only info callback is not interchangeable with the descriptor-based v9.3.0 interface.

In the tagged core, decoder selection searches for a successful info callback, then invokes the selected decoder’s open callback once. A failed open is returned directly. It does not automatically try the next decoder or invoke the selected decoder’s close callback. The public header’s broad “try decoders” description must be read with that implementation boundary.

A standalone information request also uses a temporary descriptor and does not create an open session that the application later closes. Design info as a bounded probe: validate recognition and report dimensions/format without leaving custom allocations or retained handles behind.

2. Separate the probe file from the session file

For an uncached file probe, the core opens the descriptor’s filesystem handle, seeks before decoder attempts and closes that handle after selection. Do not keep that probe handle for later pixel reads. A successful info result does not mean the file remains open for open_cb.

In the proposed decoder, open_cb acquires its own session resources: a context, a file handle and codec workspace. Record each acquisition in the context only after it succeeds. Keep the source pathname or source storage valid for the required use, and do not assume pointer ownership transfers merely because LVGL passes it to the callback.

Revalidate essential header and size constraints when opening. Storage contents can change between probing and opening, and source recognition alone does not validate the complete pixel stream. Reject unsupported dimensions and arithmetic overflow before allocating the requested workspace or output area.

3. Make failed open a local unwind

Injected failure Already acquired Required local unwind
Context allocation Nothing Return failure with no retained state
Session file open Context Release context
Codec workspace allocation Context and file Close file, release context
Final header validation Context, file and workspace Release all three before returning failure

Use one cleanup routine with null/validity checks for the resources that this decoder owns. Clear released fields so a diagnostic cleanup path cannot accidentally release them twice. Keep the public contract simple: when open returns failure, this attempted session owns nothing.

The normal image helper returns immediately when decoder open fails. After a successful open, it performs decoding/drawing and then closes the session. That caller behavior makes the open-time unwind necessary; allocating first and hoping close will repair a failure leaks resources.

Do not reopen the same live descriptor to reset a session. The core open operation clears the descriptor first. Close a successful session before reusing its descriptor, or use a separate descriptor for another session.

4. Distinguish area completion from an area error

The incremental callback receives a requested full area and an in/out decoded-area record. The header specifies LV_COORD_MIN initialization for the first call or reset. It uses LV_RESULT_INVALID for either failure or no remaining area. Therefore, that return value alone cannot distinguish a truncated stream from normal exhaustion.

Keep a decoder-owned diagnostic reason and progress record. For a hypothetical four-row request, successful calls might deliver rows zero, one, two and three, followed by exhaustion. A deliberate failure before row two instead records an error and incomplete coverage. The same terminal API result must not make these outcomes equivalent in the test report.

Publish dsc->decoded and the decoded area only when the returned chunk is valid. The session descriptor carries the decoded draw-buffer pointer and custom user data. Keep the buffer valid while the selected drawing path consumes it; do not return a pointer to temporary stack pixels.

In this fixture, a failed area operation leaves established session resources for the eventual close, while immediately discarding any uncommitted temporary allocation from that operation. Document that split. Freeing everything during get_area and again during close is just another ownership defect.

5. Test direct calls before integrating rendering

  1. Probe an unrelated source, a truncated header and a valid header. Check that repeated info calls leave zero custom live resources.
  2. Inject each open-stage failure from the ledger. Do not call close to conceal a failed-open leak; assert the local unwind directly.
  3. Open successfully, reset the decoded-area record as required and enumerate the complete requested region. Verify exact coverage and pixel-buffer validity.
  4. Fail an area allocation and fail a stream read after one valid chunk. Distinguish incomplete coverage from exhaustion, then close the successful session once.
  5. Stop consumption early and close. Confirm all session resources are released even though the whole image was not requested.
  6. Interleave two successful descriptors to expose accidental use of one global codec cursor or shared unowned buffer.

Use allocation IDs and handle counts, not only total heap size. A resource can be freed incorrectly and replaced by another allocation of the same size, leaving a flat graph. Keep failure reason, acquisition/release sequence and decoded-area coverage together.

6. Reintroduce the production path deliberately

The configuration template names LV_CACHE_DEF_SIZE and LV_IMAGE_HEADER_CACHE_DEF_CNT; record their effective zero values for the baseline. A warm cache can skip callbacks and hide an incomplete cleanup test. Later cache-enabled qualification must explicitly assign ownership of retained decoded data and cache eviction.

Finally, render the same valid and truncated fixtures through the selected software path. Check visible output as well as diagnostics: the normal helper’s incremental loop stops on a non-OK area result, so a partially drawn image is not proof of successful decoding. Accept the extension only when every failed stage has a terminal owner and every successful session has one matched close.

Similar Posts