Color mixing for a color palette

We are using LVGL with LV_COLOR_DEPTH 8 on a STM32H7 in CLUT Mode.
Since the color mixer (lv_color_mix) does not work with a color palette, we have implemented a simple mechanism that works quite well:
We need to display a simple GUI (without images), because the required number of basic colours is quite small (we use up to 32 colours). As a result, we can use the rest of the palette colours (up to 224) for the pre-calculated colors of the gradients. We use another LUT (with 1024 entries of 1 byte each) to find the index of the colour gradients in the CLUT.

Thats for short. We would like to contribute our code to the LVGL project if there is any interest. Is there any? We could create a merge request.

Hi,

Isn’t is slow to search for the best match in every call of lv_color_mix()?

No, thats why we have the additional MixLUT: We combine the two colors we want to mix (each one of 32, so we have 5 bits each) to a single 10 bit word. This is the index to the MixLUT, having 1024 entries of 1 byte each. Here we’ll find the index to the first entry of the pre-calculated gradient’s colors in the CLUT.

Example: Currently we represent each color gradient by 6 colors, lets call it the color resolution: So a color gradient from color c1 to color c2 is as follows:

100%c1+0%c2  | 80%c1+20%c2 | 60%c1+40%c2 | 40%c1+60%c2 | 20%c1+80%c2 | 0%c1+100%c2
      c1     |      c3      |     c4     |      c5     |      c6     |     c2

The colors c1 and c2 are found already in the first 32 indices of the CLUT, because these are basic colors. We store the mixed colors c3 to c6 in the indices 32 and upwards. Remember that the colors are only the indices of the RGB888 colors stored in the CLUT.

The mixing algorithm works as follows (0<=alpha<=255):

int lv_color_mix(int c1, int c2, int alpha) {
  if (alpha==255)
    return (c1);
  if (alpha==0)
    return (c2);
  idx=MixCLUT[c1<<5|c2];
  return (idx+((alpha*6)>>8)-1);
}

I got it, thank for the explanation. But one thing is still not clear. How could you craft the LUT?

The first 32 colors are the colors of your UI. It’s clear. The rest there should be grouped to 4 colors

  1. 80%c1+20%c2
  2. 60%c1+40%c2
  3. 40%c1+60%c2
  4. 20%c1+80%c2

So there are (256-32) / 4 = 56 “entry points” (where the 4 colors start for the 4 alpha value) to which MixCLUT can point. So you need to map 32x32 = 1024 color combinations to 56 indices. (I see that there are 20%c1 + 80%c2 is the same as 80%c2 + 20%c1 but it’s still very far).

What’s the trick? :slight_smile:

Yes, the MixLUT does not store all the theoretically possible color gradients. But at least in our case, the design does also not need it - a round corner of a button e.g. is always drawn in the same color, but on 3 different background colors. So we need 3 color gradients for it, or 12 colors. We need also other color gradients, but of course we need to keep it below the maximum of 56 color gradients. This limitation is the price we have to pay when we want to manage only half of the frame buffer’s size…

I see. It seems like a quite custom solution to me and I can hardly imagine a generic way to support it. I think the simplest and more versatile would be to allow overwriting lv_color_mix() e.g. by adding
#if LV_COLOR_MIX_EXTERNAL or so.

What do you think?

Yes, color gradients with a CLUT could be a bit specific. We have already thought about many options depending on our specific UI design… Currently it looks like limiting to max 57 gradients is easier to handle than limiting to max 256 colors in the CLUT.
New microcontrollers are coming out that have enough internal memory to store a full 800x480 pixel framebuffer at 8bpp, like the STM32H7. This lowers system cost and might increase interest in a more “general purpose” solution. But for now, I think your idea of having a custom color mixer is the best - we would appreciate it. As a suggestion, we have submitted a pull request that covers our requirements. Please take a look:

We need also to overwrite lv_color_premult and lv_color_mix_premult.

Another question: In our case the initial configuration of the base object done in lv_obj_constructor() is not very suitable, so we have to unmask some of the flags like LV_OBJ_FLAG_CLICKABLE or LV_OBJ_FLAG_SNAPPABLE, because it should not be possible to drag a label around.

Wouldn`t it be better to initialize the base object without setting all the flags, and to set them individually in each of the widgets? This would also be a bit more efficient, since the bits are not set by default and have to be reset in the widgets, as we see in some cases.

If have used a clean-all-flags approach all the users needed to set a lot of flags. Now only a few flags needs to be changed in some cases. I think it’s more user firendly.

Note that specific widget (e.g. the label) can set/clear its own flags:

You can also create a function like my_label_create(lv_obj_t * parent) which create a normal label and changes some flags.

Thank you, I think I can follow your approach.
Thank you also for merging our lv_color_mix pull request :slight_smile: