Second Images at Same Y-Coordinate Different X-Coordinate Overwrites / Garbles First Image

Hello LVGL community,

I’ve encountered a strange issue with image rendering in LVGL v9.3.0 (using the Zephyr RTOS). When displaying two separate image objects that share the same Y-coordinate but different X-coordinates, the second image overwrites the first one but not totally. It is a bit garbled (see screenshot) - even though they don’t overlap at all.

I am sending the image data over serial and then creating the image from the buffer.

Description of the issue:

  1. Send image 1 data over serial
  2. Call display_raw_image() with (X1, Y) (source below)
  3. Send image 2 data over serial
  4. Call display_raw_image() with (X2, Y) (source below)
  5. X2 is far enough away from X1 that the images should not overlap
  6. The second image displays correctly, but it also partially overwrites/garbles the first image

Important observation:

  • This issue ONLY occurs when both images have the same Y-coordinate
  • When I place the second image at a different Y-coordinate, the problem doesn’t happen
  • Both images display properly individually, it’s only when they’re placed at the same Y-coordinate that the overwriting occurs

I’m using a monochrome 1bpp display (Sharp LS027B7DH01) with LVGL in Zephyr. My rendering code follows this pattern:

I’ve tried pretty much everything I can think of including cache invalidation lv_draw_buf_invalidate_cache()

I am probably missing something very trivial / fundament. Has anyone else encountered this issue, or does anyone have insight into why LVGL might be overwriting content when images share the same Y-coordinate?

Screen Shots
Below a couple screenshots of what the screen looks like while observing the issue.

First image drawn and displayed correctly at X1, Y.

Second image drawn and displayed correctly at X2, Y but 1st image garbled.

Code

/**
 * @brief Display a raw 1bpp image on the screen
 *
 * @param image_data Pointer to raw 1bpp image data
 * @param width Image width in pixels
 * @param height Image height in pixels
 * @param x X position for display
 * @param y Y position for display
 * @param clear Whether to clear the display before drawing
 * @return int 0 if successful, negative error code otherwise
 */
static int display_raw_image(const uint8_t* image_data, uint16_t width,
                             uint16_t height, uint16_t x, uint16_t y,
                             bool clear) {
    LOG_INF("Displaying raw image: %ux%u pixels at (%d,%d)", width, height, x,
            y);

    /* Create LVGL image descriptor */
    lv_img_dsc_t img_dsc;
    lv_color32_t palette[2];

    /* Calculate the byte-aligned width for 1bpp data */
    uint32_t w_bytes = (width + 7) / 8;
    uint32_t buf_size = w_bytes * height;

    palette[0] = lv_color_to_32(lv_color_white(), LV_OPA_MAX);
    palette[1] = lv_color_to_32(lv_color_black(), LV_OPA_MAX);

    buf_size += sizeof(palette);
    /* Allocate buffer for LVGL format */
    uint8_t* lvgl_buf = lv_malloc(buf_size);
    if (!lvgl_buf) {
        LOG_ERR("Failed to allocate memory for LVGL image");
        return -ENOMEM;
    }

    memset(lvgl_buf, 0xff, buf_size);
    /* Copy the palette data to the LVGL buffer */
    memcpy(lvgl_buf, palette, sizeof(palette));
    /* Copy the raw 1bpp image data to the LVGL buffer */
    memcpy(lvgl_buf + sizeof(palette), image_data, buf_size - sizeof(palette));

    // memset(lvgl_buf, 0xff, buf_size / 2);
    /* Setup the image descriptor */
    img_dsc.header.cf = LV_COLOR_FORMAT_NATIVE; /* 1-bit per pixel monochrome */
    img_dsc.header.w = width;
    img_dsc.header.h = height;
    img_dsc.data_size = buf_size;
    img_dsc.header.stride =
        LV_STRIDE_AUTO; /* Set stride to byte-aligned row width */
    img_dsc.data = lvgl_buf;
    img_dsc.header.flags = LV_IMAGE_FLAGS_ALLOCATED; /* Uncompressed image */

    /* Create an LVGL image object */
    lv_obj_t* img_obj = lv_img_create(lv_scr_act());
    lv_img_set_src(img_obj, &img_dsc);

    /* Position the image */
    lv_obj_set_pos(img_obj, x, y);

    /* If clear flag is set, remove all other objects first */
    if (clear) {
        /* First, get the screen and save our image */
        lv_obj_t* scr = lv_scr_act();

        /* Find and delete all other objects */
        uint32_t i = 0; /* First child index */
        lv_obj_t* child = lv_obj_get_child(scr, i);
        while (child) {
            if (child != img_obj) {
                lv_obj_del(child);
                /* After deletion, the next child moves into this position, so
                 * don't increment i */
                child = lv_obj_get_child(scr, i);
            } else {
                /* Skip our image object */
                i++;
                child = lv_obj_get_child(scr, i);
            }
        }
    }

    /* Force screen update */
    lv_timer_handler();

    lv_free(lvgl_buf); /* Free the allocated buffer */
    return 0;
}

Thank you for any help or suggestions!

Welcome,

You do not set the width and height of the created img_obj, I doubt this will fix your issue but it might be worth setting it to the expected width and height.
When you stretch an image object with a larger size than the image itself, it might start tiling depending on style settings, it sort of looks like that’s what happening here somehow, but then it should only start tiling after the X pos, not before…

It could also have nothing to do with this piece of code at all, perhaps it is an issue with how you draw the framebuffer to the screen, have you tried any of the existing examples?

By the way, calling lv_timer_handler() for forcing a screen update like that is not really the intended purpose of it, I suggest leaving it out here and just keeping it in your main loop somewhere.

Hello Tinus,

Thank you for the reply. Really apreciate it.
If I understand correctly then the x and y position of the obj should be inferred from the lv_img_dsc_t headers x and y position. In the LVGL rendering code I see that the image is placed at the right coordinates with the right size.
As for calling lv_timer_handler(), If I don’t call it when I do, nothing gets rendered. I did find this curious given any text that I put on the screen does render correctly with lv_timer_handler() in the lcd handler thread.
Diving deeper into the rendering code I noticed that internal buffers are being reused resulting in the first image getting a pointer to the same buffer as the second image… Hence, I thought I must be missing something very fundamental here… As an experiment, I allowed the script to send 2 images at the same time and render them back to back. Even in this case the issue can be observed and I see that the buffer gets reused. Next I am going to try and put the images in source code versus getting them at run time and see if that gives any different result. If it does, there is something funny going on with LVGL’s buffering.

Thanks again and I’ll keep this thread up dated in the hope that I get to the bottom of the issue and somebody might find it useful if they run into a similar issue.

Robert