White outlines around buttons

Description

I’m trying to lay a button on top of an already displayed image om am LCD-display but the button has a white outline and is smaller than the desired size (the outline has the correct size). In the flush-Callback the coordinates in area differ from the defined button size and position (macros at the beginning of the code snippet). The background image was not drawn with LVGL but is an image in the external RAM read out by the LTDC-peripheral of the STM32.

What MCU/Processor/Board and compiler are you using?

STM32F439 witth STM32CubeIDE

What LVGL version are you using?

8.3.8

What do you want to achieve?

Draw a button to an already displayed image without it having a white outline (the green border is desired)

What have you tried so far?

set outline to 0, set desired button size, set desired button position, align to top left (doesn’t change the position of the button inside the outline), different background opacities and colors

Code to reproduce

/* Size and position of button in pixels */
#define DISP_WIDTH	1920
#define DISP_HEIGHT	1200
#define BOOTBUTTON_WIDTH        510
#define BOOTBUTTON_HEIGHT       53
#define BOOTBUTTON_LEFT_X0     	246
#define BOOTBUTTON_LEFT_Y0  	650

static lv_color_t __attribute__((__section__(".lvgldrawbuffsection1"))) drawBuffer[1300480]; /* in extrnal RAM */
static lv_disp_drv_t disp_drv;
static lv_disp_draw_buf_t displayDrawBuffer;
static lv_disp_t *disp;
static lv_style_t style_btn;
static lv_style_t style_label;

void init_graphic_lib()
{
	lv_init();
	lv_disp_draw_buf_init(&displayDrawBuffer, drawBuffer, NULL, sizeof(drawBuffer)/2);
	lv_disp_drv_init(&disp_drv);            /*Basic initialization*/
	disp_drv.draw_buf = &displayDrawBuffer;          /*Set an initialized buffer*/
	disp_drv.flush_cb = flush_cb;        /*Set a flush callback to draw to the display*/
	disp_drv.hor_res = DISP_WIDTH;                 /*Set the horizontal resolution in pixels*/
	disp_drv.ver_res = DISP_HEIGHT;                 /*Set the vertical resolution in pixels*/
	disp = lv_disp_drv_register(&disp_drv); /*Register the driver and save the created display objects*/
}

static void btn_style_init(void)
{
    /*Create a simple button style*/
    lv_style_init(&style_btn);
    lv_style_set_width(&style_btn, BOOTBUTTON_WIDTH);
    lv_style_set_height(&style_btn, BOOTBUTTON_HEIGHT);
    lv_style_set_radius(&style_btn, 0);
    lv_style_set_bg_opa(&style_btn, LV_OPA_COVER);
    lv_style_set_bg_color(&style_btn, lv_palette_main(LV_PALETTE_NONE));

    lv_style_set_pad_all(&style_btn, 0);
    lv_style_set_outline_pad(&style_btn, 0);
    lv_style_set_outline_width(&style_btn, 0);

    lv_style_set_border_color(&style_btn, lv_color_make(144, 165, 107));
    lv_style_set_border_opa(&style_btn, LV_OPA_COVER);
    lv_style_set_border_width(&style_btn, 3);

    lv_style_set_text_color(&style_btn, lv_color_white());
}

void lvgl_test()
{
    btn_style_init();
    lv_obj_t* btn = lv_btn_create(lv_scr_act());
    lv_obj_remove_style_all(btn);
    lv_obj_set_pos(btn, BOOTBUTTON_LEFT_X0, BOOTBUTTON_LEFT_Y0);
    lv_obj_set_size(btn, BOOTBUTTON_WIDTH, BOOTBUTTON_HEIGHT);
    lv_obj_add_style(btn, &style_btn, LV_PART_MAIN | LV_STATE_DEFAULT);

    lv_style_init(&style_label);
    lv_obj_t* label = lv_label_create(btn);
    lv_obj_remove_style_all(label);
    lv_label_set_text(label, NULL);
    lv_label_set_text(label, "HELLO WORLD");
    lv_obj_center(label);
    lv_obj_add_style(label, &style_label, LV_PART_MAIN | LV_STATE_DEFAULT);
}

void flush_cb(lv_disp_drv_t * disp, const lv_area_t * area, lv_color_t * buf)
{
	if (lv_disp_flush_is_last(&disp_drv))
	{
               int32_t x, y;

                /* Write button into already displayed image */
		for(y = area->y1; y <= area->y2; y++)
		{
			uint32_t row = y * DISP_WIDTH;

			for(x = area->x1; x <= area->x2; x++)
			{
				u16_alreadyDisplayedImage[row+x] = buf->full;
				buf++;
			}
		}
        }

        lv_disp_flush_ready(&disp_drv);
}

int main()
{
       /* ...display image to lay button on top */

        init_graphic_lib();
        lvgl_test();
}

/* lv_tick_inc(1) is called every ms, lv_timer_handler() every 10 ms */

Screenshot and/or video

Your idea place image other way is fail. Use LVGL img. And try simulate your code, i mean too shadow part of object is aplied to button flush

Hi @Aardvark ,

I pasted your example to the simulator to test with a slight modification to make the screen black as follows:

#define BOOTBUTTON_WIDTH        510
#define BOOTBUTTON_HEIGHT       53
#define BOOTBUTTON_LEFT_X0     	246
#define BOOTBUTTON_LEFT_Y0  	650

static lv_style_t	style_btn, style_label;

static void btn_style_init(void)
{
    /*Create a simple button style*/
    lv_style_init(&style_btn);
    lv_style_set_width(&style_btn, BOOTBUTTON_WIDTH);
    lv_style_set_height(&style_btn, BOOTBUTTON_HEIGHT);
    lv_style_set_radius(&style_btn, 0);
    lv_style_set_bg_opa(&style_btn, LV_OPA_COVER);
    lv_style_set_bg_color(&style_btn, lv_palette_main(LV_PALETTE_NONE));

    lv_style_set_pad_all(&style_btn, 0);
    lv_style_set_outline_pad(&style_btn, 0);
    lv_style_set_outline_width(&style_btn, 0);

    lv_style_set_border_color(&style_btn, lv_color_make(144, 165, 107));
    lv_style_set_border_opa(&style_btn, LV_OPA_COVER);
    lv_style_set_border_width(&style_btn, 3);

    lv_style_set_text_color(&style_btn, lv_color_white());
}

void test( lv_obj_t *parent)
{
	lv_obj_set_style_bg_color(parent, lv_color_black(), LV_PART_MAIN);	// Make the default white screen black so we can see what is happening...
    btn_style_init();
    lv_obj_t* btn = lv_btn_create(parent);
    lv_obj_remove_style_all(btn);
    lv_obj_set_pos(btn, BOOTBUTTON_LEFT_X0, BOOTBUTTON_LEFT_Y0);
    lv_obj_set_size(btn, BOOTBUTTON_WIDTH, BOOTBUTTON_HEIGHT);
    lv_obj_add_style(btn, &style_btn, LV_PART_MAIN | LV_STATE_DEFAULT);

    lv_style_init(&style_label);
    lv_obj_t* label = lv_label_create(btn);
    lv_obj_remove_style_all(label);
     lv_label_set_text(label, "HELLO WORLD");
    lv_obj_center(label);
    lv_obj_add_style(label, &style_label, LV_PART_MAIN | LV_STATE_DEFAULT);

}

// In main function
test(lv_scr_act());

The result was as expected no white border was present on the button:

Can I ask why you might need to write an image to the LVGL buffer from another source?
I expect this is going to cause undefined issues as the LVGL rendering engine will not be aware of the presence of the image in the buffer.

Would it perhaps be better to use the lv_img object to place you image as @Marian_M has intimated above or maybe you could use the lv_imgbtn object?

I apologise if I have misunderstood your requirement.

Kind Regards,

Pete

1 Like

Hello pete-pjb, thanks a lot! I will look into these objects and try the exmple in the simulator. The thing is, I don’t want to write an image to the LVGL-buffer but the other way around: I want to write the LVGL-buffer (containing the created button) to a background image that’s already showing on the display simply by replacing the correspnding pixels of the background image. But I don’t know if that’s the proper way to go, I’m fairly new to LVGL. Sorry if my explanation wasn’t clear

Hi @Aardvark ,

I must admit I am struggling to understand your full requirement here :slight_smile:

Is there an example of what you are trying to achieve you can liken it to somewhere else perhaps? Can you explain the concept and context of your requirement somehow?

Is it like you are setting a wallpaper for you ‘LVGL Desktop’ as it were, or something of that nature?

Kind Regards,

Pete

Actually it’s quite a simple task: there is a static background image filling the whole LCD-screen (it’s streaming from an external SDRAM to the display via the STM32 LTDC-peripheral). When a certain event occurs, a button should appear on this background image, when another event occurs it should disappear again

Hi @Aardvark ,

Take a look at this post it may help?

And also this post is probably more specific to your scenario:

Kind Regards,

Pete

1 Like

Your idea is waste of energy. Define sdram as framebuffer and lvgl handle img on background. Real efective will be use LTDC 2 layers, but this need manage alpha channel on top layer…

1 Like

Ok thanks; the framebuffer is already in the external SDRAM. I tried it with 2 layers but the memory bus or LTDC peripheral can’t handle the the high amount of data (it’s a high resolution of 1920x1200). Intentionally I didn’t want to touch the background image with LVGL, but just present it with LTDC and draw or remove small widgets to it with LVGL in realtime and without reloading the whole screen. Do you have a suggestion how I should proceed? Should I define the already displayed background image as parent and then the button as child? Do you have any idea why the button is created smaller than intended and has the white frame?

Hi pete-pjb,

Thank you, I’ll take a look at it. I think my use of the word “streaming” was misleading, the background is no video but a normal image (stream was in the sense of data stream).

Parent and child isnt required only right order. And size and frame is based on shadow reserved area i mean.

1 Like

SOLVED: LVGL seems to draw a fairly larger area than the object itself and if the screen-color is white, there is a white frame around the object. The appropriate way for my application was to create a LVGL-screen with the same image-object as the background-image on the physical display. Then, when e.g. a button is created, it can be drawn to the physical display after the last flush-callback and will have the correct outline color