Invert the colors of a QR code canvas


A QR code is a LV_CANVAS_BUF_SIZE_INDEXED_1BIT canvas, and each pixel of its buffer can be color inverted. I’m asking how to best invert a canvas?

MCU and board type

STM32F205RET (128K SRAM, 512K Flash) in a custom board, with gcc-arm(1), GNU make(1), and STM32 HAL.

LVGL version


What do you want to achieve?

I want to modify a canvas that is being displayed, to have its 1-bit colours inverted.

What have you tried so far?

In the callback of a clickable canvas, I tried lv_obj_set_style_local_image_recolor(3), lv_img_buf_set_palette(3), lv_canvas_set_palette(3), and lv_canvas_set_px(3). All had no effect in the appearance.

I tried modifying the internal image buffer as well:

lv_img_dsc_t * img = lv_canvas_get_img(qrcode);
uint8_t * buf_u8 = (uint8_t *)img->data + 8;
// loop over the buffer and flip the bits (1-bit monochrome)

This kind of works outside the canvas click callback set with:

lv_obj_set_click(qrcanvas, true);

…but it seems to not work inside the callback.


It may be that some of the above tried method work outside of a click event handler, because the object referenced passed to the callback is a copy of the pointer?

I’m not using lv_img_cache_invalidate_src(3), so maybe this would help?


Pointing a smartphone camera at a LVGL QR code on a small display is error prone. I’m trying to invert the QR code colours on a button key press (to help the phone detect the QR code.)

Possible solution A

This seems to properly invert all the colours of a canvas (actually it’s internal image data.)

static lv_res_t img_inv(lv_img_dsc_t *pImg)
  //lv_img_dsc_t *pImg = lv_canvas_get_img(qrcode);
  uint8_t *pBuf = (uint8_t *)pImg->data + 8;    /*+8 skip the palette*/
  uint32_t nSiz = pImg->header.h * pImg->header.w / 8;

  // Portable but suboptimal inversion method
  for (int nIter = 0; nIter < nSiz; nIter++)
    pBuf[nIter] = ~pBuf[nIter];

  //lv_img_cache_invalidate_src(pImg);  // Is this needed?

  return LV_RES_OK;

…which works if called in main like this:

int main(void)
  lv_obj_t *pCanv = create_a_canvas_please();
  // Draw on the canvas, 1-bit mode

…but it seems to fail if called inside a click callback like this:

lv_obj_set_click(pCanv, true);
lv_obj_set_event_cb(pCanv, my_event_cb);

static void my_event_cb(lv_obj_t * obj, lv_event_t event)
    switch(event) {
        case LV_EVENT_CLICKED:
        img_inv(lv_canvas_get_img(obj));  // Fails here, works outside callback!

New suspicion

It seems that the lv_obj_t obj inside of callbacks are a shallow copy of the actual pointer. It’s possibly my mistake is a programming language one rather than problem understanding LVGL?

Solution to callback scope failure

Modifying internal data (like a image or canvas buffer) is not enough, because LVGL only redraws invalid portions of the display (and doesn’t know that you modified the internal data.) Use lv_obj_invalidate(lv_scr_act()); to inform LVGL.

static void my_event_cb(lv_obj_t * obj, lv_event_t event)
    switch(event) {
        case LV_EVENT_CLICKED:
            img_inv(lv_canvas_get_img(obj));  // Fails inside, works outside cb!
            //lv_refr_now(lv_disp_t * disp);  // Not sure where to get the *disp
            //lv_img_cache_invalidate_src(lv_canvas_get_img(obj));  // Broken
            //lv_obj_invalidate_area(lv_scr_act(), const lv_area_t *pArea);  // Huh?
            //lv_event_send_refresh_recursive(lv_scr_act());  // Broken
            //lv_event_send_refresh(lv_scr_act());  // Broken
            lv_obj_invalidate(lv_scr_act());  // Works correctly!

Eight hours lost

Did you hear the joke, how many hours does a skilled computer scientist need to invalidate a screen area?

A: It takes a skilled scientist about eight hours, or a junior scientist who knows how many days, to wade through LVGL sources seeking the various ways to redraw, refresh, invalidate, and otherwise hack a drawing construct to work.

I wish I had a good suggestion on how to improve this situation but I don’t.