add some code

This commit is contained in:
2025-09-05 13:25:11 +08:00
parent 9ff0a99e7a
commit 3cf1229a85
8911 changed files with 2535396 additions and 0 deletions

View File

@@ -0,0 +1,6 @@
# The following lines of boilerplate have to be in your project's CMakeLists
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
set(EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/unit-test-app/components")
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(test_esp_lvgl_port)

View File

@@ -0,0 +1 @@
idf_component_register(SRCS "test.c")

View File

@@ -0,0 +1,9 @@
## IDF Component Manager Manifest File
dependencies:
idf: ">=4.4"
esp_lcd_touch_tt21100:
version: "^1"
override_path: "../../../../lcd_touch/esp_lcd_touch_tt21100/"
esp_lvgl_port:
version: "*"
override_path: "../../../"

View File

@@ -0,0 +1,356 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#include "esp_err.h"
#include "esp_log.h"
#include "esp_check.h"
#include "driver/i2c.h"
#include "driver/gpio.h"
#include "driver/spi_master.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_vendor.h"
#include "esp_lcd_panel_ops.h"
#include "esp_lvgl_port.h"
#include "esp_lcd_touch_tt21100.h"
#include "unity.h"
/* LCD size */
#define EXAMPLE_LCD_H_RES (320)
#define EXAMPLE_LCD_V_RES (240)
/* LCD settings */
#define EXAMPLE_LCD_SPI_NUM (SPI3_HOST)
#define EXAMPLE_LCD_PIXEL_CLK_HZ (40 * 1000 * 1000)
#define EXAMPLE_LCD_CMD_BITS (8)
#define EXAMPLE_LCD_PARAM_BITS (8)
#define EXAMPLE_LCD_COLOR_SPACE (ESP_LCD_COLOR_SPACE_BGR)
#define EXAMPLE_LCD_BITS_PER_PIXEL (16)
#define EXAMPLE_LCD_DRAW_BUFF_DOUBLE (1)
#define EXAMPLE_LCD_DRAW_BUFF_HEIGHT (50)
#define EXAMPLE_LCD_BL_ON_LEVEL (1)
/* LCD pins */
#define EXAMPLE_LCD_GPIO_SCLK (GPIO_NUM_7)
#define EXAMPLE_LCD_GPIO_MOSI (GPIO_NUM_6)
#define EXAMPLE_LCD_GPIO_RST (GPIO_NUM_48)
#define EXAMPLE_LCD_GPIO_DC (GPIO_NUM_4)
#define EXAMPLE_LCD_GPIO_CS (GPIO_NUM_5)
#define EXAMPLE_LCD_GPIO_BL (GPIO_NUM_45)
/* Touch settings */
#define EXAMPLE_TOUCH_I2C_NUM (0)
#define EXAMPLE_TOUCH_I2C_CLK_HZ (400000)
/* LCD touch pins */
#define EXAMPLE_TOUCH_I2C_SCL (GPIO_NUM_18)
#define EXAMPLE_TOUCH_I2C_SDA (GPIO_NUM_8)
#define EXAMPLE_TOUCH_GPIO_INT (GPIO_NUM_3)
static char *TAG = "test";
/* LCD IO and panel */
static esp_lcd_panel_io_handle_t lcd_io = NULL;
static esp_lcd_panel_handle_t lcd_panel = NULL;
static esp_lcd_panel_io_handle_t tp_io_handle = NULL;
static esp_lcd_touch_handle_t touch_handle = NULL;
/* LVGL display and touch */
static lv_display_t *lvgl_disp = NULL;
static lv_indev_t *lvgl_touch_indev = NULL;
static esp_err_t app_lcd_init(void)
{
esp_err_t ret = ESP_OK;
/* LCD backlight */
gpio_config_t bk_gpio_config = {
.mode = GPIO_MODE_OUTPUT,
.pin_bit_mask = 1ULL << EXAMPLE_LCD_GPIO_BL
};
ESP_ERROR_CHECK(gpio_config(&bk_gpio_config));
/* LCD initialization */
ESP_LOGD(TAG, "Initialize SPI bus");
const spi_bus_config_t buscfg = {
.sclk_io_num = EXAMPLE_LCD_GPIO_SCLK,
.mosi_io_num = EXAMPLE_LCD_GPIO_MOSI,
.miso_io_num = GPIO_NUM_NC,
.quadwp_io_num = GPIO_NUM_NC,
.quadhd_io_num = GPIO_NUM_NC,
.max_transfer_sz = EXAMPLE_LCD_H_RES * EXAMPLE_LCD_DRAW_BUFF_HEIGHT * sizeof(uint16_t),
};
ESP_RETURN_ON_ERROR(spi_bus_initialize(EXAMPLE_LCD_SPI_NUM, &buscfg, SPI_DMA_CH_AUTO), TAG, "SPI init failed");
ESP_LOGD(TAG, "Install panel IO");
const esp_lcd_panel_io_spi_config_t io_config = {
.dc_gpio_num = EXAMPLE_LCD_GPIO_DC,
.cs_gpio_num = EXAMPLE_LCD_GPIO_CS,
.pclk_hz = EXAMPLE_LCD_PIXEL_CLK_HZ,
.lcd_cmd_bits = EXAMPLE_LCD_CMD_BITS,
.lcd_param_bits = EXAMPLE_LCD_PARAM_BITS,
.spi_mode = 0,
.trans_queue_depth = 10,
};
ESP_GOTO_ON_ERROR(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)EXAMPLE_LCD_SPI_NUM, &io_config, &lcd_io), err, TAG, "New panel IO failed");
ESP_LOGD(TAG, "Install LCD driver");
const esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = EXAMPLE_LCD_GPIO_RST,
.color_space = EXAMPLE_LCD_COLOR_SPACE,
.bits_per_pixel = EXAMPLE_LCD_BITS_PER_PIXEL,
};
ESP_GOTO_ON_ERROR(esp_lcd_new_panel_st7789(lcd_io, &panel_config, &lcd_panel), err, TAG, "New panel failed");
esp_lcd_panel_reset(lcd_panel);
esp_lcd_panel_init(lcd_panel);
esp_lcd_panel_mirror(lcd_panel, true, true);
esp_lcd_panel_disp_on_off(lcd_panel, true);
/* LCD backlight on */
ESP_ERROR_CHECK(gpio_set_level(EXAMPLE_LCD_GPIO_BL, EXAMPLE_LCD_BL_ON_LEVEL));
return ret;
err:
if (lcd_panel) {
esp_lcd_panel_del(lcd_panel);
}
if (lcd_io) {
esp_lcd_panel_io_del(lcd_io);
}
spi_bus_free(EXAMPLE_LCD_SPI_NUM);
return ret;
}
static esp_err_t app_lcd_deinit(void)
{
ESP_RETURN_ON_ERROR(esp_lcd_panel_del(lcd_panel), TAG, "LCD panel deinit failed");
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_del(lcd_io), TAG, "LCD IO deinit failed");
ESP_RETURN_ON_ERROR(spi_bus_free(EXAMPLE_LCD_SPI_NUM), TAG, "SPI BUS free failed");
ESP_RETURN_ON_ERROR(gpio_reset_pin(EXAMPLE_LCD_GPIO_BL), TAG, "Reset BL pin failed");
return ESP_OK;
}
static esp_err_t app_touch_init(void)
{
/* Initilize I2C */
const i2c_config_t i2c_conf = {
.mode = I2C_MODE_MASTER,
.sda_io_num = EXAMPLE_TOUCH_I2C_SDA,
.sda_pullup_en = GPIO_PULLUP_DISABLE,
.scl_io_num = EXAMPLE_TOUCH_I2C_SCL,
.scl_pullup_en = GPIO_PULLUP_DISABLE,
.master.clk_speed = EXAMPLE_TOUCH_I2C_CLK_HZ
};
ESP_RETURN_ON_ERROR(i2c_param_config(EXAMPLE_TOUCH_I2C_NUM, &i2c_conf), TAG, "I2C configuration failed");
ESP_RETURN_ON_ERROR(i2c_driver_install(EXAMPLE_TOUCH_I2C_NUM, i2c_conf.mode, 0, 0, 0), TAG, "I2C initialization failed");
/* Initialize touch HW */
const esp_lcd_touch_config_t tp_cfg = {
.x_max = EXAMPLE_LCD_H_RES,
.y_max = EXAMPLE_LCD_V_RES,
.rst_gpio_num = GPIO_NUM_NC, // Shared with LCD reset
.int_gpio_num = EXAMPLE_TOUCH_GPIO_INT,
.levels = {
.reset = 0,
.interrupt = 0,
},
.flags = {
.swap_xy = 0,
.mirror_x = 1,
.mirror_y = 0,
},
};
const esp_lcd_panel_io_i2c_config_t tp_io_config = ESP_LCD_TOUCH_IO_I2C_TT21100_CONFIG();
ESP_RETURN_ON_ERROR(esp_lcd_new_panel_io_i2c((esp_lcd_i2c_bus_handle_t)EXAMPLE_TOUCH_I2C_NUM, &tp_io_config, &tp_io_handle), TAG, "");
return esp_lcd_touch_new_i2c_tt21100(tp_io_handle, &tp_cfg, &touch_handle);
}
static esp_err_t app_touch_deinit(void)
{
ESP_RETURN_ON_ERROR(esp_lcd_touch_del(touch_handle), TAG, "Touch deinit failed");
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_del(tp_io_handle), TAG, "Touch IO deinit failed");
ESP_RETURN_ON_ERROR(i2c_driver_delete(EXAMPLE_TOUCH_I2C_NUM), TAG, "I2C deinit failed");
return ESP_OK;
}
static esp_err_t app_lvgl_init(void)
{
/* Initialize LVGL */
const lvgl_port_cfg_t lvgl_cfg = {
.task_priority = 4, /* LVGL task priority */
.task_stack = 4096, /* LVGL task stack size */
.task_affinity = -1, /* LVGL task pinned to core (-1 is no affinity) */
.task_max_sleep_ms = 500, /* Maximum sleep in LVGL task */
.timer_period_ms = 5 /* LVGL timer tick period in ms */
};
ESP_RETURN_ON_ERROR(lvgl_port_init(&lvgl_cfg), TAG, "LVGL port initialization failed");
/* Add LCD screen */
ESP_LOGD(TAG, "Add LCD screen");
const lvgl_port_display_cfg_t disp_cfg = {
.io_handle = lcd_io,
.panel_handle = lcd_panel,
.buffer_size = EXAMPLE_LCD_H_RES * EXAMPLE_LCD_DRAW_BUFF_HEIGHT * sizeof(uint16_t),
.double_buffer = EXAMPLE_LCD_DRAW_BUFF_DOUBLE,
.hres = EXAMPLE_LCD_H_RES,
.vres = EXAMPLE_LCD_V_RES,
.monochrome = false,
/* Rotation values must be same as used in esp_lcd for initial settings of the screen */
.rotation = {
.swap_xy = false,
.mirror_x = true,
.mirror_y = true,
},
.flags = {
.buff_dma = true,
#if LVGL_VERSION_MAJOR >= 9
.swap_bytes = true,
#endif
}
};
lvgl_disp = lvgl_port_add_disp(&disp_cfg);
/* Add touch input (for selected screen) */
const lvgl_port_touch_cfg_t touch_cfg = {
.disp = lvgl_disp,
.handle = touch_handle,
};
lvgl_touch_indev = lvgl_port_add_touch(&touch_cfg);
return ESP_OK;
}
static esp_err_t app_lvgl_deinit(void)
{
ESP_RETURN_ON_ERROR(lvgl_port_remove_touch(lvgl_touch_indev), TAG, "LVGL touch removing failed");
gpio_uninstall_isr_service();
ESP_RETURN_ON_ERROR(lvgl_port_remove_disp(lvgl_disp), TAG, "LVGL disp removing failed");
ESP_RETURN_ON_ERROR(lvgl_port_deinit(), TAG, "LVGL deinit failed");
return ESP_OK;
}
static void _app_button_cb(lv_event_t *e)
{
lv_disp_rotation_t rotation = lv_disp_get_rotation(lvgl_disp);
rotation++;
if (rotation > LV_DISPLAY_ROTATION_270) {
rotation = LV_DISPLAY_ROTATION_0;
}
/* LCD HW rotation */
lv_disp_set_rotation(lvgl_disp, rotation);
}
static void app_main_display(void)
{
lv_obj_t *scr = lv_scr_act();
/* Task lock */
lvgl_port_lock(0);
/* Your LVGL objects code here .... */
/* Label */
lv_obj_t *label = lv_label_create(scr);
lv_obj_set_width(label, EXAMPLE_LCD_H_RES);
lv_obj_set_style_text_align(label, LV_TEXT_ALIGN_CENTER, 0);
#if LVGL_VERSION_MAJOR == 8
lv_label_set_recolor(label, true);
lv_label_set_text(label, "#FF0000 "LV_SYMBOL_BELL" Hello world Espressif and LVGL "LV_SYMBOL_BELL"#\n#FF9400 "LV_SYMBOL_WARNING" For simplier initialization, use BSP "LV_SYMBOL_WARNING" #");
#else
lv_label_set_text(label, LV_SYMBOL_BELL" Hello world Espressif and LVGL "LV_SYMBOL_BELL"\n "LV_SYMBOL_WARNING" For simplier initialization, use BSP "LV_SYMBOL_WARNING);
#endif
lv_obj_align(label, LV_ALIGN_CENTER, 0, -30);
/* Button */
lv_obj_t *btn = lv_btn_create(scr);
label = lv_label_create(btn);
lv_label_set_text_static(label, "Rotate screen");
lv_obj_align(btn, LV_ALIGN_BOTTOM_MID, 0, -30);
lv_obj_add_event_cb(btn, _app_button_cb, LV_EVENT_CLICKED, NULL);
/* Task unlock */
lvgl_port_unlock();
}
// Some resources are lazy allocated in the LCD driver, the threadhold is left for that case
#define TEST_MEMORY_LEAK_THRESHOLD (50)
static void check_leak(size_t start_free, size_t end_free, const char *type)
{
ssize_t delta = start_free - end_free;
printf("MALLOC_CAP_%s: Before %u bytes free, After %u bytes free (delta %d)\n", type, start_free, end_free, delta);
TEST_ASSERT_GREATER_OR_EQUAL_MESSAGE (delta, TEST_MEMORY_LEAK_THRESHOLD, "memory leak");
}
TEST_CASE("Main test LVGL port", "[lvgl port]")
{
size_t start_freemem_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT);
size_t start_freemem_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT);
ESP_LOGI(TAG, "Initilize LCD.");
/* LCD HW initialization */
TEST_ASSERT_EQUAL(app_lcd_init(), ESP_OK);
/* Touch initialization */
TEST_ASSERT_EQUAL(app_touch_init(), ESP_OK);
size_t start_lvgl_freemem_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT);
size_t start_lvgl_freemem_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT);
ESP_LOGI(TAG, "Initilize LVGL.");
/* LVGL initialization */
TEST_ASSERT_EQUAL(app_lvgl_init(), ESP_OK);
/* Show LVGL objects */
app_main_display();
vTaskDelay(5000 / portTICK_PERIOD_MS);
/* LVGL deinit */
TEST_ASSERT_EQUAL(app_lvgl_deinit(), ESP_OK);
/* When using LVGL8, it takes some time to release all memory */
vTaskDelay(1000 / portTICK_PERIOD_MS);
ESP_LOGI(TAG, "LVGL deinitialized.");
size_t end_lvgl_freemem_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT);
size_t end_lvgl_freemem_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT);
check_leak(start_lvgl_freemem_8bit, end_lvgl_freemem_8bit, "8BIT LVGL");
check_leak(start_lvgl_freemem_32bit, end_lvgl_freemem_32bit, "32BIT LVGL");
/* Touch deinit */
TEST_ASSERT_EQUAL(app_touch_deinit(), ESP_OK);
/* LCD deinit */
TEST_ASSERT_EQUAL(app_lcd_deinit(), ESP_OK);
vTaskDelay(1000 / portTICK_PERIOD_MS);
ESP_LOGI(TAG, "LCD deinitilized.");
size_t end_freemem_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT);
size_t end_freemem_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT);
check_leak(start_freemem_8bit, end_freemem_8bit, "8BIT");
check_leak(start_freemem_32bit, end_freemem_32bit, "32BIT");
}
void app_main(void)
{
printf("TEST ESP LVGL port\n\r");
unity_run_menu();
}

View File

@@ -0,0 +1,6 @@
# sdkconfig to enable the SIMD in the lvgl_port
# Set custom ASM render and provide a header file with function prototypes
CONFIG_LV_DRAW_SW_ASM_CUSTOM=y
CONFIG_LV_USE_DRAW_SW_ASM=255
CONFIG_LV_DRAW_SW_ASM_CUSTOM_INCLUDE="esp_lvgl_port_lv_blend.h"

View File

@@ -0,0 +1,7 @@
CONFIG_IDF_TARGET="esp32s3"
CONFIG_ESPTOOLPY_FLASHMODE_QIO=y
CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y
CONFIG_LV_COLOR_16_SWAP=y
CONFIG_FREERTOS_HZ=1000
CONFIG_ESP_TASK_WDT_EN=n
CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y

View File

@@ -0,0 +1,7 @@
# The following lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(test_lvgl_simd)

View File

@@ -0,0 +1,135 @@
# HW Acceleration using SIMD assembly instructions
Test app accommodates two types of tests: [`functionality test`](#Functionality-test) and [`benchmark test`](#Benchmark-test). Both tests are provided per each function written in assembly (typically per each assembly file). Both test apps use a hard copy of LVGL blending API, representing an ANSI implementation of the LVGL blending functions. The hard copy is present in [`lv_blend`](main/lv_blend/) folder.
Assembly source files could be found in the [`lvgl_port`](../../src/lvgl9/simd/) component. Header file with the assembly function prototypes is provided into the LVGL using Kconfig option `LV_DRAW_SW_ASM_CUSTOM_INCLUDE` and can be found in the [`lvgl_port/include`](../../include/esp_lvgl_port_lv_blend.h)
## Benchmark results for LV Fill functions (memset)
| Color format | Matrix size | Memory alignment | ASM version | ANSI C version |
| :----------- | :---------- | :--------------- | :------------- | :------------- |
| ARGB8888 | 128x128 | 16 byte | 0.327 | 1.600 |
| | 127x127 | 1 byte | 0.488 | 1.597 |
| RGB565 | 128x128 | 16 byte | 0.196 | 1.146 |
| | 127x127 | 1 byte | 0.497 | 1.124 |
| RGB888 | 128x128 | 16 byte | 0.608 | 4.062 |
| | 127x127 | 1 byte | 0.818 | 3.969 |
* this data was obtained by running [benchmark tests](#benchmark-test) on 128x128 16 byte aligned matrix (ideal case) and 127x127 1 byte aligned matrix (worst case)
* the values represent cycles per sample to perform simple fill of the matrix on esp32s3
## Benchmark results for LV Image functions (memcpy)
| Color format | Matrix size | Memory alignment | ASM version | ANSI C version |
| :----------- | :---------- | :--------------- | :------------- | :------------- |
| RGB565 | 128x128 | 16 byte | 0.352 | 3.437 |
| | 127x128 | 1 byte | 0.866 | 5.978 |
| RGB888 | 128x128 | 16 byte | 0.744 | 4.002 |
| | 127x128 | 1 byte | 1.002 | 7.998 |
* this data was obtained by running [benchmark tests](#benchmark-test) on 128x128 16 byte aligned matrix (ideal case) and 127x128 1 byte aligned matrix (worst case)
* the values represent cycles per sample to perform memory copy between two matrices on esp32s3
## Functionality test
* Tests, whether the HW accelerated assembly version of an LVGL function provides the same results as the ANSI version
* A top-level flow of the functionality test:
* generate a test matrix with test parameters (matrix width, matrix height, memory alignment.. )
* run an ANSI version of a DUT function with the generated input parameters
* run an assembly version of a DUT function with the same input parameters
* compare the results given by the ANSI and the assembly DUTs
* the results shall be the same
* repeat all the steps for a set of different input parameters, checking different matrix heights, widths..
## Benchmark test
* Tests, whether the HW accelerated assembly version of an LVGL function provides a performance increase over the ANSI version
* A top-level flow of the functionality test:
* generate a test matrix with test parameters (matrix width, matrix height, memory alignment.. )
* run an ANSI version of a DUT function with the generated input parameters multiple times (1000 times for example), while counting CPU cycles
* run an assembly version of a DUT function with the generated input parameters multiple times (1000 times for example), while counting CPU cycles
* compare the results given by the ANSI and the assembly DUTs
* the assembly version of the DUT function shall be faster than the ANSI version of the DUT function
## Run the test app
The test app is intended to be used only with esp32 and esp32s3
idf.py build
## Example output
```
I (302) main_task: Started on CPU0
I (322) main_task: Calling app_main()
______ _____ ______ _ _
| _ \/ ___|| ___ \ | | | |
| | | |\ `--. | |_/ / | |_ ___ ___ | |_
| | | | `--. \| __/ | __| / _ \/ __|| __|
| |/ / /\__/ /| | | |_ | __/\__ \| |_
|___/ \____/ \_| \__| \___||___/ \__|
Press ENTER to see the list of tests.
Here's the test menu, pick your combo:
(1) "Test fill functionality ARGB8888" [fill][functionality][ARGB8888]
(2) "Test fill functionality RGB565" [fill][functionality][RGB565]
(3) "LV Fill benchmark ARGB8888" [fill][benchmark][ARGB8888]
(4) "LV Fill benchmark RGB565" [fill][benchmark][RGB565]
(5) "LV Image functionality RGB565 blend to RGB565" [image][functionality][RGB565]
(6) "LV Image benchmark RGB565 blend to RGB565" [image][benchmark][RGB565]
Enter test for running.
```
### Example of a functionality test run
```
Running Test fill functionality ARGB8888...
I (81512) LV Fill Functionality: running test for ARGB8888 color format
I (84732) LV Fill Functionality: test combinations: 31824
MALLOC_CAP_8BIT usage: Free memory delta: 0 Leak threshold: -800
MALLOC_CAP_32BIT usage: Free memory delta: 0 Leak threshold: -800
./main/test_lv_fill_functionality.c:102:Test fill functionality ARGB8888:PASS
Test ran in 3242ms
```
The test gives a simple FAIL/PASS result after comparison of the two DUTs results.
Also gives us an information about how many combinations (input parameters) the functionality test run with, `31824` in this case.
### Example of a benchmark test run
```
Running LV Fill benchmark ARGB8888...
I (163492) LV Fill Benchmark: running test for ARGB8888 color format
I (163522) LV Fill Benchmark: ASM ideal case: 5363.123 cycles for 128x128 matrix, 0.327 cycles per sample
I (163572) LV Fill Benchmark: ASM corner case: 7868.724 cycles for 127x127 matrix, 0.488 cycles per sample
I (163732) LV Fill Benchmark: ANSI ideal case: 26219.137 cycles for 128x128 matrix, 1.600 cycles per sample
I (163902) LV Fill Benchmark: ANSI corner case: 25762.178 cycles for 127x127 matrix, 1.597 cycles per sample
MALLOC_CAP_8BIT usage: Free memory delta: -220 Leak threshold: -800
MALLOC_CAP_8BIT potential leak: Before 393820 bytes free, After 393600 bytes free (delta 220)
MALLOC_CAP_32BIT usage: Free memory delta: -220 Leak threshold: -800
MALLOC_CAP_32BIT potential leak: Before 393820 bytes free, After 393600 bytes free (delta 220)
./main/test_lv_fill_benchmark.c:69:LV Fill benchmark ARGB8888:PASS
Test ran in 458ms
```
The test provides couple of information:
* Total number of CPU cycles for the whole DUT function
* `5363.123` cycles for the assembly DUT function
* `26219.137` cycles for the ANSI DUT function
* Number of CPU cycles per sample, which is basically the total number of CPU cycles divided by the test matrix area
* `0.327` cycles per sample for the assembly DUT
* `1.6` cycles per sample for the ANSI DUT
* In this case, the assembly implementation has achieved a performance increase in around 4.9-times, comparing to the ANSI implementation.
* Range of the CPU cycles (a best case and a corner case scenarios) into which, the DUT functions are expected to fit into
* The execution time of those function highly depends on the input parameters, thus a boundary scenarios for input parameters shall be set
* An example of such a boundaries is in a table below
* The benchmark boundary would help us to get an performance expectations of the real scenarios
Example of an best and corner case input parameters for benchmark test, for a color format `ARGB8888`
| Test matrix params | Memory alignment | Width | Height | Stride |
| :----------------- | :--------------- | :------------- | :------------- | :------------- |
| Best case | 16-byte aligned | Multiple of 8 | Multiple of 8 | Multiple of 8 |
| Corner case | 1-byte aligned | Not power of 2 | Not power of 2 | Not power of 2 |

View File

@@ -0,0 +1,31 @@
# Include SIMD assembly source code for rendering
if(CONFIG_IDF_TARGET_ESP32 OR CONFIG_IDF_TARGET_ESP32S3)
message(VERBOSE "Compiling SIMD")
set(PORT_PATH "../../../src/lvgl9")
if(CONFIG_IDF_TARGET_ESP32S3)
file(GLOB_RECURSE ASM_SOURCES ${PORT_PATH}/simd/*_esp32s3.S) # Select only esp32s3 related files
else()
file(GLOB_RECURSE ASM_SOURCES ${PORT_PATH}/simd/*_esp32.S) # Select only esp32 related files
endif()
file(GLOB_RECURSE ASM_MACROS ${PORT_PATH}/simd/lv_macro_*.S) # Explicitly add all assembler macro files
else()
message(WARNING "This test app is intended only for esp32 and esp32s3")
endif()
# Hard copy of LV files
file(GLOB_RECURSE BLEND_SRCS lv_blend/src/*.c)
idf_component_register(SRCS "test_app_main.c"
"test_lv_fill_functionality.c" # memset tests
"test_lv_fill_benchmark.c"
"test_lv_image_functionality.c" # memcpy tests
"test_lv_image_benchmark.c"
${BLEND_SRCS} # Hard copy of LVGL's blend API, to simplify testing
${ASM_SOURCES} # Assembly src files
${ASM_MACROS} # Assembly macro files
INCLUDE_DIRS "lv_blend/include" "../../../include"
REQUIRES unity
WHOLE_ARCHIVE)

View File

@@ -0,0 +1,5 @@
# Creating CONFIG_LV_DRAW_SW_ASM_CUSTOM avaliable in lvgl Kconfig to enable assembler source files by deafult
config LV_DRAW_SW_ASM_CUSTOM
bool
default y

View File

@@ -0,0 +1,60 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*
* This file is derived from the LVGL project.
* See https://github.com/lvgl/lvgl for details.
*/
/**
* @file lv_assert.h
*
*/
#ifndef LV_ASSERT_H
#define LV_ASSERT_H
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* INCLUDES
*********************/
#include "lv_log.h"
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**********************
* GLOBAL PROTOTYPES
**********************/
/**********************
* MACROS
**********************/
#define LV_ASSERT_HANDLER while(1); /*Halt by default*/
#define LV_ASSERT(expr) \
do { \
if(!(expr)) { \
LV_LOG_ERROR("Asserted at expression: %s", #expr); \
LV_ASSERT_HANDLER \
} \
} while(0)
/*-----------------
* ASSERTS
*-----------------*/
#ifdef __cplusplus
} /*extern "C"*/
#endif
#endif /*LV_ASSERT_H*/

View File

@@ -0,0 +1,272 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*
* This file is derived from the LVGL project.
* See https://github.com/lvgl/lvgl for details.
*/
/**
* @file lv_color.h
*
*/
#ifndef LV_COLOR_H
#define LV_COLOR_H
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* INCLUDES
*********************/
#include "stdint.h"
#include "stdbool.h"
#include "sdkconfig.h"
/*********************
* DEFINES
*********************/
#define LV_ATTRIBUTE_FAST_MEM
#ifndef LV_COLOR_MIX_ROUND_OFS
#ifdef CONFIG_LV_COLOR_MIX_ROUND_OFS
#define LV_COLOR_MIX_ROUND_OFS CONFIG_LV_COLOR_MIX_ROUND_OFS
#else
#define LV_COLOR_MIX_ROUND_OFS 0
#endif
#endif
/**
* Opacity percentages.
*/
typedef enum {
LV_OPA_TRANSP = 0,
LV_OPA_0 = 0,
LV_OPA_10 = 25,
LV_OPA_20 = 51,
LV_OPA_30 = 76,
LV_OPA_40 = 102,
LV_OPA_50 = 127,
LV_OPA_60 = 153,
LV_OPA_70 = 178,
LV_OPA_80 = 204,
LV_OPA_90 = 229,
LV_OPA_100 = 255,
LV_OPA_COVER = 255,
} lv_opa_t;
#define LV_OPA_MIN 2 /*Opacities below this will be transparent*/
#define LV_OPA_MAX 253 /*Opacities above this will fully cover*/
#define LV_COLOR_FORMAT_GET_BPP(cf) ( \
(cf) == LV_COLOR_FORMAT_I1 ? 1 : \
(cf) == LV_COLOR_FORMAT_A1 ? 1 : \
(cf) == LV_COLOR_FORMAT_I2 ? 2 : \
(cf) == LV_COLOR_FORMAT_A2 ? 2 : \
(cf) == LV_COLOR_FORMAT_I4 ? 4 : \
(cf) == LV_COLOR_FORMAT_A4 ? 4 : \
(cf) == LV_COLOR_FORMAT_L8 ? 8 : \
(cf) == LV_COLOR_FORMAT_A8 ? 8 : \
(cf) == LV_COLOR_FORMAT_I8 ? 8 : \
(cf) == LV_COLOR_FORMAT_AL88 ? 16 : \
(cf) == LV_COLOR_FORMAT_RGB565 ? 16 : \
(cf) == LV_COLOR_FORMAT_RGB565A8 ? 16 : \
(cf) == LV_COLOR_FORMAT_ARGB8565 ? 24 : \
(cf) == LV_COLOR_FORMAT_RGB888 ? 24 : \
(cf) == LV_COLOR_FORMAT_ARGB8888 ? 32 : \
(cf) == LV_COLOR_FORMAT_XRGB8888 ? 32 : \
0 \
)
/**********************
* TYPEDEFS
**********************/
typedef struct {
uint8_t blue;
uint8_t green;
uint8_t red;
} lv_color_t;
typedef struct {
uint16_t blue : 5;
uint16_t green : 6;
uint16_t red : 5;
} lv_color16_t;
typedef struct {
uint8_t blue;
uint8_t green;
uint8_t red;
uint8_t alpha;
} lv_color32_t;
typedef struct {
uint16_t h;
uint8_t s;
uint8_t v;
} lv_color_hsv_t;
typedef struct {
uint8_t lumi;
uint8_t alpha;
} lv_color16a_t;
typedef enum {
LV_COLOR_FORMAT_UNKNOWN = 0,
LV_COLOR_FORMAT_RAW = 0x01,
LV_COLOR_FORMAT_RAW_ALPHA = 0x02,
/*<=1 byte (+alpha) formats*/
LV_COLOR_FORMAT_L8 = 0x06,
LV_COLOR_FORMAT_I1 = 0x07,
LV_COLOR_FORMAT_I2 = 0x08,
LV_COLOR_FORMAT_I4 = 0x09,
LV_COLOR_FORMAT_I8 = 0x0A,
LV_COLOR_FORMAT_A8 = 0x0E,
/*2 byte (+alpha) formats*/
LV_COLOR_FORMAT_RGB565 = 0x12,
LV_COLOR_FORMAT_ARGB8565 = 0x13, /**< Not supported by sw renderer yet. */
LV_COLOR_FORMAT_RGB565A8 = 0x14, /**< Color array followed by Alpha array*/
LV_COLOR_FORMAT_AL88 = 0x15, /**< L8 with alpha >*/
/*3 byte (+alpha) formats*/
LV_COLOR_FORMAT_RGB888 = 0x0F,
LV_COLOR_FORMAT_ARGB8888 = 0x10,
LV_COLOR_FORMAT_XRGB8888 = 0x11,
/*Formats not supported by software renderer but kept here so GPU can use it*/
LV_COLOR_FORMAT_A1 = 0x0B,
LV_COLOR_FORMAT_A2 = 0x0C,
LV_COLOR_FORMAT_A4 = 0x0D,
/* reference to https://wiki.videolan.org/YUV/ */
/*YUV planar formats*/
LV_COLOR_FORMAT_YUV_START = 0x20,
LV_COLOR_FORMAT_I420 = LV_COLOR_FORMAT_YUV_START, /*YUV420 planar(3 plane)*/
LV_COLOR_FORMAT_I422 = 0x21, /*YUV422 planar(3 plane)*/
LV_COLOR_FORMAT_I444 = 0x22, /*YUV444 planar(3 plane)*/
LV_COLOR_FORMAT_I400 = 0x23, /*YUV400 no chroma channel*/
LV_COLOR_FORMAT_NV21 = 0x24, /*YUV420 planar(2 plane), UV plane in 'V, U, V, U'*/
LV_COLOR_FORMAT_NV12 = 0x25, /*YUV420 planar(2 plane), UV plane in 'U, V, U, V'*/
/*YUV packed formats*/
LV_COLOR_FORMAT_YUY2 = 0x26, /*YUV422 packed like 'Y U Y V'*/
LV_COLOR_FORMAT_UYVY = 0x27, /*YUV422 packed like 'U Y V Y'*/
LV_COLOR_FORMAT_YUV_END = LV_COLOR_FORMAT_UYVY,
/*Color formats in which LVGL can render*/
#if LV_COLOR_DEPTH == 8
LV_COLOR_FORMAT_NATIVE = LV_COLOR_FORMAT_L8,
LV_COLOR_FORMAT_NATIVE_WITH_ALPHA = LV_COLOR_FORMAT_AL88,
#elif LV_COLOR_DEPTH == 16
LV_COLOR_FORMAT_NATIVE = LV_COLOR_FORMAT_RGB565,
LV_COLOR_FORMAT_NATIVE_WITH_ALPHA = LV_COLOR_FORMAT_RGB565A8,
#elif LV_COLOR_DEPTH == 24
LV_COLOR_FORMAT_NATIVE = LV_COLOR_FORMAT_RGB888,
LV_COLOR_FORMAT_NATIVE_WITH_ALPHA = LV_COLOR_FORMAT_ARGB8888,
#elif LV_COLOR_DEPTH == 32
LV_COLOR_FORMAT_NATIVE = LV_COLOR_FORMAT_XRGB8888,
LV_COLOR_FORMAT_NATIVE_WITH_ALPHA = LV_COLOR_FORMAT_ARGB8888,
#endif
} lv_color_format_t;
/**********************
* MACROS
**********************/
#define LV_COLOR_MAKE(r8, g8, b8) {b8, g8, r8}
#define LV_OPA_MIX2(a1, a2) (((int32_t)(a1) * (a2)) >> 8)
#define LV_OPA_MIX3(a1, a2, a3) (((int32_t)(a1) * (a2) * (a3)) >> 16)
/**********************
* GLOBAL PROTOTYPES
**********************/
/**
* Create an ARGB8888 color from RGB888 + alpha
* @param color an RGB888 color
* @param opa the alpha value
* @return the ARGB8888 color
*/
lv_color32_t lv_color_to_32(lv_color_t color, lv_opa_t opa);
/**
* Convert am RGB888 color to RGB565 stored in `uint16_t`
* @param color and RGB888 color
* @return `color` as RGB565 on `uin16_t`
*/
uint16_t lv_color_to_u16(lv_color_t color);
/**
* Convert am RGB888 color to XRGB8888 stored in `uint32_t`
* @param color and RGB888 color
* @return `color` as XRGB8888 on `uin32_t` (the alpha channel is always set to 0xFF)
*/
uint32_t lv_color_to_u32(lv_color_t color);
/**
* Mix two RGB565 colors
* @param c1 the first color (typically the foreground color)
* @param c2 the second color (typically the background color)
* @param mix 0..255, or LV_OPA_0/10/20...
* @return mix == 0: c2
* mix == 255: c1
* mix == 128: 0.5 x c1 + 0.5 x c2
*/
static inline uint16_t LV_ATTRIBUTE_FAST_MEM lv_color_16_16_mix(uint16_t c1, uint16_t c2, uint8_t mix)
{
if (mix == 255) {
return c1;
}
if (mix == 0) {
return c2;
}
if (c1 == c2) {
return c1;
}
uint16_t ret;
/* Source: https://stackoverflow.com/a/50012418/1999969*/
mix = (uint32_t)((uint32_t)mix + 4) >> 3;
/*0x7E0F81F = 0b00000111111000001111100000011111*/
uint32_t bg = (uint32_t)(c2 | ((uint32_t)c2 << 16)) & 0x7E0F81F;
uint32_t fg = (uint32_t)(c1 | ((uint32_t)c1 << 16)) & 0x7E0F81F;
uint32_t result = ((((fg - bg) * mix) >> 5) + bg) & 0x7E0F81F;
ret = (uint16_t)(result >> 16) | result;
return ret;
}
/**
* Check if two ARGB8888 color are equal
* @param c1 the first color
* @param c2 the second color
* @return true: equal
*/
static inline bool lv_color32_eq(lv_color32_t c1, lv_color32_t c2)
{
return *((uint32_t *)&c1) == *((uint32_t *)&c2);
}
/**********************
* MACROS
**********************/
#include "lv_color_op.h"
#ifdef __cplusplus
} /*extern "C"*/
#endif
#endif /*LV_COLOR_H*/

View File

@@ -0,0 +1,93 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*
* This file is derived from the LVGL project.
* See https://github.com/lvgl/lvgl for details.
*/
/**
* @file lv_color_op.h
*
*/
#ifndef LV_COLOR_OP_H
#define LV_COLOR_OP_H
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* INCLUDES
*********************/
#include "lv_math.h"
#include "lv_color.h"
#include "lv_types.h"
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**********************
* GLOBAL PROTOTYPES
**********************/
/**
* Mix two colors with a given ratio.
* @param c1 the first color to mix (usually the foreground)
* @param c2 the second color to mix (usually the background)
* @param mix The ratio of the colors. 0: full `c2`, 255: full `c1`, 127: half `c1` and half`c2`
* @return the mixed color
*/
static inline lv_color_t LV_ATTRIBUTE_FAST_MEM lv_color_mix(lv_color_t c1, lv_color_t c2, uint8_t mix)
{
lv_color_t ret;
ret.red = LV_UDIV255((uint16_t)c1.red * mix + c2.red * (255 - mix) + LV_COLOR_MIX_ROUND_OFS);
ret.green = LV_UDIV255((uint16_t)c1.green * mix + c2.green * (255 - mix) + LV_COLOR_MIX_ROUND_OFS);
ret.blue = LV_UDIV255((uint16_t)c1.blue * mix + c2.blue * (255 - mix) + LV_COLOR_MIX_ROUND_OFS);
return ret;
}
/**
*
* @param fg
* @param bg
* @return
* @note Use bg.alpha in the return value
* @note Use fg.alpha as mix ratio
*/
static inline lv_color32_t lv_color_mix32(lv_color32_t fg, lv_color32_t bg)
{
if (fg.alpha >= LV_OPA_MAX) {
fg.alpha = bg.alpha;
return fg;
}
if (fg.alpha <= LV_OPA_MIN) {
return bg;
}
bg.red = (uint32_t)((uint32_t)fg.red * fg.alpha + (uint32_t)bg.red * (255 - fg.alpha)) >> 8;
bg.green = (uint32_t)((uint32_t)fg.green * fg.alpha + (uint32_t)bg.green * (255 - fg.alpha)) >> 8;
bg.blue = (uint32_t)((uint32_t)fg.blue * fg.alpha + (uint32_t)bg.blue * (255 - fg.alpha)) >> 8;
return bg;
}
/**********************
* PREDEFINED COLORS
**********************/
/**********************
* MACROS
**********************/
#ifdef __cplusplus
} /*extern "C"*/
#endif
#endif /*LV_COLOR_H*/

View File

@@ -0,0 +1,75 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*
* This file is derived from the LVGL project.
* See https://github.com/lvgl/lvgl for details.
*/
/**
* @file lv_draw_sw_blend.h
*
*/
#ifndef LV_DRAW_SW_BLEND_H
#define LV_DRAW_SW_BLEND_H
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* INCLUDES
*********************/
#include "lv_style.h"
#include "lv_color.h"
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
typedef struct {
void *dest_buf;
int32_t dest_w;
int32_t dest_h;
int32_t dest_stride;
const lv_opa_t *mask_buf;
int32_t mask_stride;
lv_color_t color;
lv_opa_t opa;
bool use_asm;
} _lv_draw_sw_blend_fill_dsc_t;
typedef struct {
void *dest_buf;
int32_t dest_w;
int32_t dest_h;
int32_t dest_stride;
const lv_opa_t *mask_buf;
int32_t mask_stride;
const void *src_buf;
int32_t src_stride;
lv_color_format_t src_color_format;
lv_opa_t opa;
lv_blend_mode_t blend_mode;
bool use_asm;
} _lv_draw_sw_blend_image_dsc_t;
/**********************
* GLOBAL PROTOTYPES
**********************/
/**********************
* MACROS
**********************/
#ifdef __cplusplus
} /*extern "C"*/
#endif
#endif /*LV_DRAW_SW_BLEND_H*/

View File

@@ -0,0 +1,51 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*
* This file is derived from the LVGL project.
* See https://github.com/lvgl/lvgl for details.
*/
/**
* @file lv_draw_sw_blend_argb8888.h
*
*/
#ifndef LV_DRAW_SW_BLEND_ARGB8888_H
#define LV_DRAW_SW_BLEND_ARGB8888_H
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* INCLUDES
*********************/
#include "lv_draw_sw_blend.h"
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**********************
* GLOBAL PROTOTYPES
**********************/
void /* LV_ATTRIBUTE_FAST_MEM */ lv_draw_sw_blend_color_to_argb8888(_lv_draw_sw_blend_fill_dsc_t *dsc);
void /* LV_ATTRIBUTE_FAST_MEM */ lv_draw_sw_blend_image_to_argb8888(_lv_draw_sw_blend_image_dsc_t *dsc);
/**********************
* MACROS
**********************/
#ifdef __cplusplus
} /*extern "C"*/
#endif
#endif /*LV_DRAW_SW_BLEND_ARGB8888_H*/

View File

@@ -0,0 +1,51 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*
* This file is derived from the LVGL project.
* See https://github.com/lvgl/lvgl for details.
*/
/**
* @file lv_draw_sw_blend_rgb565.h
*
*/
#ifndef LV_DRAW_SW_BLEND_RGB565_H
#define LV_DRAW_SW_BLEND_RGB565_H
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* INCLUDES
*********************/
#include "lv_draw_sw_blend.h"
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**********************
* GLOBAL PROTOTYPES
**********************/
void /* LV_ATTRIBUTE_FAST_MEM */ lv_draw_sw_blend_color_to_rgb565(_lv_draw_sw_blend_fill_dsc_t *dsc);
void /* LV_ATTRIBUTE_FAST_MEM */ lv_draw_sw_blend_image_to_rgb565(_lv_draw_sw_blend_image_dsc_t *dsc);
/**********************
* MACROS
**********************/
#ifdef __cplusplus
} /*extern "C"*/
#endif
#endif /*LV_DRAW_SW_BLEND_RGB565_H*/

View File

@@ -0,0 +1,53 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*
* This file is derived from the LVGL project.
* See https://github.com/lvgl/lvgl for details.
*/
/**
* @file lv_draw_sw_blend_rgb888.h
*
*/
#ifndef LV_DRAW_SW_BLEND_RGB888_H
#define LV_DRAW_SW_BLEND_RGB888_H
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* INCLUDES
*********************/
#include "lv_draw_sw_blend.h"
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**********************
* GLOBAL PROTOTYPES
**********************/
void /* LV_ATTRIBUTE_FAST_MEM */ lv_draw_sw_blend_color_to_rgb888(_lv_draw_sw_blend_fill_dsc_t *dsc,
uint32_t dest_px_size);
void /* LV_ATTRIBUTE_FAST_MEM */ lv_draw_sw_blend_image_to_rgb888(_lv_draw_sw_blend_image_dsc_t *dsc,
uint32_t dest_px_size);
/**********************
* MACROS
**********************/
#ifdef __cplusplus
} /*extern "C"*/
#endif
#endif /*LV_DRAW_SW_BLEND_RGB888_H*/

View File

@@ -0,0 +1,45 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*
* This file is derived from the LVGL project.
* See https://github.com/lvgl/lvgl for details.
*/
/**
* @file lv_log.h
*
*/
#ifndef LV_LOG_H
#define LV_LOG_H
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* INCLUDES
*********************/
#include "lv_types.h"
/*********************
* DEFINES
*********************/
/*Do nothing if `LV_USE_LOG 0`*/
#define _lv_log_add(level, file, line, ...)
#define LV_LOG_TRACE(...) do {}while(0)
#define LV_LOG_INFO(...) do {}while(0)
#define LV_LOG_WARN(...) do {}while(0)
#define LV_LOG_ERROR(...) do {}while(0)
#define LV_LOG_USER(...) do {}while(0)
#define LV_LOG(...) do {}while(0)
#ifdef __cplusplus
} /*extern "C"*/
#endif
#endif /*LV_LOG_H*/

View File

@@ -0,0 +1,56 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*
* This file is derived from the LVGL project.
* See https://github.com/lvgl/lvgl for details.
*/
/**
* @file lv_math.h
*
*/
#ifndef LV_MATH_H
#define LV_MATH_H
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* INCLUDES
*********************/
#include "lv_types.h"
/*********************
* DEFINES
*********************/
/**********************
* MACROS
**********************/
#define LV_MIN(a, b) ((a) < (b) ? (a) : (b))
#define LV_MIN3(a, b, c) (LV_MIN(LV_MIN(a,b), c))
#define LV_MIN4(a, b, c, d) (LV_MIN(LV_MIN(a,b), LV_MIN(c,d)))
#define LV_MAX(a, b) ((a) > (b) ? (a) : (b))
#define LV_MAX3(a, b, c) (LV_MAX(LV_MAX(a,b), c))
#define LV_MAX4(a, b, c, d) (LV_MAX(LV_MAX(a,b), LV_MAX(c,d)))
#define LV_CLAMP(min, val, max) (LV_MAX(min, (LV_MIN(val, max))))
#define LV_ABS(x) ((x) > 0 ? (x) : (-(x)))
#define LV_UDIV255(x) (((x) * 0x8081U) >> 0x17)
#define LV_IS_SIGNED(t) (((t)(-1)) < ((t)0))
#define LV_UMAX_OF(t) (((0x1ULL << ((sizeof(t) * 8ULL) - 1ULL)) - 1ULL) | (0xFULL << ((sizeof(t) * 8ULL) - 4ULL)))
#define LV_SMAX_OF(t) (((0x1ULL << ((sizeof(t) * 8ULL) - 1ULL)) - 1ULL) | (0x7ULL << ((sizeof(t) * 8ULL) - 4ULL)))
#define LV_MAX_OF(t) ((unsigned long)(LV_IS_SIGNED(t) ? LV_SMAX_OF(t) : LV_UMAX_OF(t)))
#ifdef __cplusplus
} /*extern "C"*/
#endif
#endif

View File

@@ -0,0 +1,79 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*
* This file is derived from the LVGL project.
* See https://github.com/lvgl/lvgl for details.
*/
/**
* @file lv_string.h
*
*/
#ifndef LV_STRING_H
#define LV_STRING_H
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* INCLUDES
*********************/
//#include "../lv_conf_internal.h"
#include <stdint.h>
#include <stddef.h>
#include "lv_types.h"
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**********************
* GLOBAL PROTOTYPES
**********************/
/**
* @brief Copies a block of memory from a source address to a destination address.
* @param dst Pointer to the destination array where the content is to be copied.
* @param src Pointer to the source of data to be copied.
* @param len Number of bytes to copy.
* @return Pointer to the destination array.
* @note The function does not check for any overlapping of the source and destination memory blocks.
*/
void *lv_memcpy(void *dst, const void *src, size_t len);
/**
* @brief Fills a block of memory with a specified value.
* @param dst Pointer to the destination array to fill with the specified value.
* @param v Value to be set. The value is passed as an int, but the function fills
* the block of memory using the unsigned char conversion of this value.
* @param len Number of bytes to be set to the value.
*/
void lv_memset(void *dst, uint8_t v, size_t len);
/**
* @brief Move a block of memory from source to destination
* @param dst Pointer to the destination array where the content is to be copied.
* @param src Pointer to the source of data to be copied.
* @param len Number of bytes to copy
* @return Pointer to the destination array.
*/
void *lv_memmove(void *dst, const void *src, size_t len);
/**********************
* MACROS
**********************/
#ifdef __cplusplus
} /*extern "C"*/
#endif
#endif /*LV_STRING_H*/

View File

@@ -0,0 +1,48 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*
* This file is derived from the LVGL project.
* See https://github.com/lvgl/lvgl for details.
*/
/**
* @file lv_style.h
*
*/
#ifndef LV_STYLE_H
#define LV_STYLE_H
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* INCLUDES
*********************/
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**
* Possible options how to blend opaque drawings
*/
typedef enum {
LV_BLEND_MODE_NORMAL, /**< Simply mix according to the opacity value*/
LV_BLEND_MODE_ADDITIVE, /**< Add the respective color channels*/
LV_BLEND_MODE_SUBTRACTIVE,/**< Subtract the foreground from the background*/
LV_BLEND_MODE_MULTIPLY, /**< Multiply the foreground and background*/
} lv_blend_mode_t;
#ifdef __cplusplus
} /*extern "C"*/
#endif
#endif /*LV_STYLE_H*/

View File

@@ -0,0 +1,53 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*
* This file is derived from the LVGL project.
* See https://github.com/lvgl/lvgl for details.
*/
/**
* @file lv_types.h
*
*/
#ifndef LV_TYPES_H
#define LV_TYPES_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
/**********************
* TYPEDEFS
**********************/
/**
* LVGL error codes.
*/
typedef enum {
LV_RESULT_INVALID = 0, /*Typically indicates that the object is deleted (become invalid) in the action
function or an operation was failed*/
LV_RESULT_OK, /*The object is valid (no deleted) after the action*/
} lv_result_t;
/**********************
* TYPEDEFS
**********************/
typedef uintptr_t lv_uintptr_t;
/**********************
* MACROS
**********************/
#define LV_UNUSED(x) ((void)x)
#ifdef __cplusplus
} /*extern "C"*/
#endif
#endif /*LV_TYPES_H*/

View File

@@ -0,0 +1,66 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*
* This file is derived from the LVGL project.
* See https://github.com/lvgl/lvgl for details.
*/
/**
* @file lv_color.c
*
*/
/*********************
* INCLUDES
*********************/
#include "lv_color.h"
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC PROTOTYPES
**********************/
/**********************
* GLOBAL VARIABLES
**********************/
/**********************
* STATIC VARIABLES
**********************/
/**********************
* MACROS
**********************/
/**********************
* GLOBAL FUNCTIONS
**********************/
lv_color32_t lv_color_to_32(lv_color_t color, lv_opa_t opa)
{
lv_color32_t c32;
c32.red = color.red;
c32.green = color.green;
c32.blue = color.blue;
c32.alpha = opa;
return c32;
}
uint16_t lv_color_to_u16(lv_color_t color)
{
return ((color.red & 0xF8) << 8) + ((color.green & 0xFC) << 3) + ((color.blue & 0xF8) >> 3);
}
uint32_t lv_color_to_u32(lv_color_t color)
{
return (uint32_t)((uint32_t)0xff << 24) + (color.red << 16) + (color.green << 8) + (color.blue);
}

View File

@@ -0,0 +1,911 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*
* This file is derived from the LVGL project.
* See https://github.com/lvgl/lvgl for details.
*/
/**
* @file lv_draw_sw_blend.c
*
*/
/*********************
* INCLUDES
*********************/
#include "lv_draw_sw_blend_to_argb8888.h"
#include "lv_assert.h"
#include "lv_types.h"
#include "lv_log.h"
#include "lv_draw_sw_blend.h"
#include "lv_math.h"
#include "lv_color.h"
#include "lv_string.h"
#include "esp_lvgl_port_lv_blend.h"
/*********************
* DEFINES
*********************/
#define LV_ATTRIBUTE_FAST_MEM
/**********************
* TYPEDEFS
**********************/
typedef struct {
lv_color32_t fg_saved;
lv_color32_t bg_saved;
lv_color32_t res_saved;
lv_opa_t res_alpha_saved;
lv_opa_t ratio_saved;
} lv_color_mix_alpha_cache_t;
/**********************
* STATIC PROTOTYPES
**********************/
static void /* LV_ATTRIBUTE_FAST_MEM */ al88_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc);
static void /* LV_ATTRIBUTE_FAST_MEM */ l8_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc);
static void /* LV_ATTRIBUTE_FAST_MEM */ rgb565_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc);
static void /* LV_ATTRIBUTE_FAST_MEM */ rgb888_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc,
const uint8_t src_px_size);
static void /* LV_ATTRIBUTE_FAST_MEM */ argb8888_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc);
static inline void /* LV_ATTRIBUTE_FAST_MEM */ lv_color_8_32_mix(const uint8_t src, lv_color32_t *dest, uint8_t mix);
static inline lv_color32_t /* LV_ATTRIBUTE_FAST_MEM */ lv_color_32_32_mix(lv_color32_t fg, lv_color32_t bg,
lv_color_mix_alpha_cache_t *cache);
static void lv_color_mix_with_alpha_cache_init(lv_color_mix_alpha_cache_t *cache);
static inline void /* LV_ATTRIBUTE_FAST_MEM */ blend_non_normal_pixel(lv_color32_t *dest, lv_color32_t src,
lv_blend_mode_t mode, lv_color_mix_alpha_cache_t *cache);
static inline void * /* LV_ATTRIBUTE_FAST_MEM */ drawbuf_next_row(const void *buf, uint32_t stride);
/**********************
* STATIC VARIABLES
**********************/
/**********************
* MACROS
**********************/
#ifndef LV_DRAW_SW_COLOR_BLEND_TO_ARGB8888
#define LV_DRAW_SW_COLOR_BLEND_TO_ARGB8888(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_COLOR_BLEND_TO_ARGB8888_WITH_OPA
#define LV_DRAW_SW_COLOR_BLEND_TO_ARGB8888_WITH_OPA(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_COLOR_BLEND_TO_ARGB8888_WITH_MASK
#define LV_DRAW_SW_COLOR_BLEND_TO_ARGB8888_WITH_MASK(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_COLOR_BLEND_TO_ARGB8888_MIX_MASK_OPA
#define LV_DRAW_SW_COLOR_BLEND_TO_ARGB8888_MIX_MASK_OPA(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_L8_BLEND_NORMAL_TO_ARGB8888
#define LV_DRAW_SW_L8_BLEND_NORMAL_TO_ARGB8888(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_L8_BLEND_NORMAL_TO_ARGB8888_WITH_OPA
#define LV_DRAW_SW_L8_BLEND_NORMAL_TO_ARGB8888_WITH_OPA(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_L8_BLEND_NORMAL_TO_ARGB8888_WITH_MASK
#define LV_DRAW_SW_L8_BLEND_NORMAL_TO_ARGB8888_WITH_MASK(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_L8_BLEND_NORMAL_TO_ARGB8888_MIX_MASK_OPA
#define LV_DRAW_SW_L8_BLEND_NORMAL_TO_ARGB8888_MIX_MASK_OPA(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_AL88_BLEND_NORMAL_TO_ARGB8888
#define LV_DRAW_SW_AL88_BLEND_NORMAL_TO_ARGB8888(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_AL88_BLEND_NORMAL_TO_ARGB8888_WITH_OPA
#define LV_DRAW_SW_AL88_BLEND_NORMAL_TO_ARGB8888_WITH_OPA(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_AL88_BLEND_NORMAL_TO_ARGB8888_WITH_MASK
#define LV_DRAW_SW_AL88_BLEND_NORMAL_TO_ARGB8888_WITH_MASK(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_AL88_BLEND_NORMAL_TO_ARGB8888_MIX_MASK_OPA
#define LV_DRAW_SW_AL88_BLEND_NORMAL_TO_ARGB8888_MIX_MASK_OPA(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_ARGB8888
#define LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_ARGB8888(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_ARGB8888_WITH_OPA
#define LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_ARGB8888_WITH_OPA(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_ARGB8888_WITH_MASK
#define LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_ARGB8888_WITH_MASK(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_ARGB8888_MIX_MASK_OPA
#define LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_ARGB8888_MIX_MASK_OPA(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_ARGB8888
#define LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_ARGB8888(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_ARGB8888_WITH_OPA
#define LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_ARGB8888_WITH_OPA(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_ARGB8888_WITH_MASK
#define LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_ARGB8888_WITH_MASK(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_ARGB8888_MIX_MASK_OPA
#define LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_ARGB8888_MIX_MASK_OPA(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_ARGB8888
#define LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_ARGB8888(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_ARGB8888_WITH_OPA
#define LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_ARGB8888_WITH_OPA(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_ARGB8888_WITH_MASK
#define LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_ARGB8888_WITH_MASK(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_ARGB8888_MIX_MASK_OPA
#define LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_ARGB8888_MIX_MASK_OPA(...) LV_RESULT_INVALID
#endif
/**********************
* GLOBAL FUNCTIONS
**********************/
void LV_ATTRIBUTE_FAST_MEM lv_draw_sw_blend_color_to_argb8888(_lv_draw_sw_blend_fill_dsc_t *dsc)
{
int32_t w = dsc->dest_w;
int32_t h = dsc->dest_h;
lv_opa_t opa = dsc->opa;
const lv_opa_t *mask = dsc->mask_buf;
int32_t mask_stride = dsc->mask_stride;
int32_t dest_stride = dsc->dest_stride;
lv_color_mix_alpha_cache_t cache;
lv_color_mix_with_alpha_cache_init(&cache);
int32_t x;
int32_t y;
LV_UNUSED(w);
LV_UNUSED(h);
LV_UNUSED(x);
LV_UNUSED(y);
LV_UNUSED(opa);
LV_UNUSED(mask);
LV_UNUSED(mask_stride);
LV_UNUSED(dest_stride);
/*Simple fill*/
if (mask == NULL && opa >= LV_OPA_MAX) {
if (dsc->use_asm) {
LV_DRAW_SW_COLOR_BLEND_TO_ARGB8888(dsc);
} else {
uint32_t color32 = lv_color_to_u32(dsc->color);
uint32_t *dest_buf = dsc->dest_buf;
for (y = 0; y < h; y++) {
for (x = 0; x < w - 16; x += 16) {
dest_buf[x + 0] = color32;
dest_buf[x + 1] = color32;
dest_buf[x + 2] = color32;
dest_buf[x + 3] = color32;
dest_buf[x + 4] = color32;
dest_buf[x + 5] = color32;
dest_buf[x + 6] = color32;
dest_buf[x + 7] = color32;
dest_buf[x + 8] = color32;
dest_buf[x + 9] = color32;
dest_buf[x + 10] = color32;
dest_buf[x + 11] = color32;
dest_buf[x + 12] = color32;
dest_buf[x + 13] = color32;
dest_buf[x + 14] = color32;
dest_buf[x + 15] = color32;
}
for (; x < w; x ++) {
dest_buf[x] = color32;
}
dest_buf = drawbuf_next_row(dest_buf, dest_stride);
}
}
}
/*Opacity only*/
else if (mask == NULL && opa < LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_COLOR_BLEND_TO_ARGB8888_WITH_OPA(dsc)) {
lv_color32_t color_argb = lv_color_to_32(dsc->color, opa);
lv_color32_t *dest_buf = dsc->dest_buf;
for (y = 0; y < h; y++) {
for (x = 0; x < w; x++) {
dest_buf[x] = lv_color_32_32_mix(color_argb, dest_buf[x], &cache);
}
dest_buf = drawbuf_next_row(dest_buf, dest_stride);
}
}
}
/*Masked with full opacity*/
else if (mask && opa >= LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_COLOR_BLEND_TO_ARGB8888_WITH_MASK(dsc)) {
lv_color32_t color_argb = lv_color_to_32(dsc->color, 0xff);
lv_color32_t *dest_buf = dsc->dest_buf;
for (y = 0; y < h; y++) {
for (x = 0; x < w; x++) {
color_argb.alpha = mask[x];
dest_buf[x] = lv_color_32_32_mix(color_argb, dest_buf[x], &cache);
}
dest_buf = drawbuf_next_row(dest_buf, dest_stride);
mask += mask_stride;
}
}
}
/*Masked with opacity*/
else {
if (LV_RESULT_INVALID == LV_DRAW_SW_COLOR_BLEND_TO_ARGB8888_MIX_MASK_OPA(dsc)) {
lv_color32_t color_argb = lv_color_to_32(dsc->color, opa);
lv_color32_t *dest_buf = dsc->dest_buf;
for (y = 0; y < h; y++) {
for (x = 0; x < w; x++) {
color_argb.alpha = LV_OPA_MIX2(mask[x], opa);
dest_buf[x] = lv_color_32_32_mix(color_argb, dest_buf[x], &cache);
}
dest_buf = drawbuf_next_row(dest_buf, dest_stride);
mask += mask_stride;
}
}
}
}
void LV_ATTRIBUTE_FAST_MEM lv_draw_sw_blend_image_to_argb8888(_lv_draw_sw_blend_image_dsc_t *dsc)
{
switch (dsc->src_color_format) {
case LV_COLOR_FORMAT_RGB565:
rgb565_image_blend(dsc);
break;
case LV_COLOR_FORMAT_RGB888:
rgb888_image_blend(dsc, 3);
break;
case LV_COLOR_FORMAT_XRGB8888:
rgb888_image_blend(dsc, 4);
break;
case LV_COLOR_FORMAT_ARGB8888:
argb8888_image_blend(dsc);
break;
case LV_COLOR_FORMAT_L8:
l8_image_blend(dsc);
break;
case LV_COLOR_FORMAT_AL88:
al88_image_blend(dsc);
break;
default:
LV_LOG_WARN("Not supported source color format");
break;
}
}
/**********************
* STATIC FUNCTIONS
**********************/
static void LV_ATTRIBUTE_FAST_MEM al88_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc)
{
int32_t w = dsc->dest_w;
int32_t h = dsc->dest_h;
lv_opa_t opa = dsc->opa;
lv_color32_t *dest_buf_c32 = dsc->dest_buf;
int32_t dest_stride = dsc->dest_stride;
const lv_color16a_t *src_buf_al88 = dsc->src_buf;
int32_t src_stride = dsc->src_stride;
const lv_opa_t *mask_buf = dsc->mask_buf;
int32_t mask_stride = dsc->mask_stride;
int32_t dest_x;
int32_t src_x;
int32_t y;
if (dsc->blend_mode == LV_BLEND_MODE_NORMAL) {
if (mask_buf == NULL && opa >= LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_AL88_BLEND_NORMAL_TO_ARGB8888(dsc)) {
for (y = 0; y < h; y++) {
for (dest_x = 0, src_x = 0; src_x < w; dest_x++, src_x++) {
/*
dest_buf_c32[dest_x].alpha = src_buf_al88[src_x].alpha;
dest_buf_c32[dest_x].red = src_buf_al88[src_x].lumi;
dest_buf_c32[dest_x].green = src_buf_al88[src_x].lumi;
dest_buf_c32[dest_x].blue = src_buf_al88[src_x].lumi;
*/
lv_color_8_32_mix(src_buf_al88[src_x].lumi, &dest_buf_c32[dest_x], src_buf_al88[src_x].alpha);
}
dest_buf_c32 = drawbuf_next_row(dest_buf_c32, dest_stride);
src_buf_al88 = drawbuf_next_row(src_buf_al88, src_stride);
}
}
} else if (mask_buf == NULL && opa < LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_AL88_BLEND_NORMAL_TO_ARGB8888_WITH_OPA(dsc)) {
for (y = 0; y < h; y++) {
for (dest_x = 0, src_x = 0; src_x < w; dest_x++, src_x++) {
lv_color_8_32_mix(src_buf_al88[src_x].lumi, &dest_buf_c32[dest_x], LV_OPA_MIX2(src_buf_al88[src_x].alpha, opa));
}
dest_buf_c32 = drawbuf_next_row(dest_buf_c32, dest_stride);
src_buf_al88 = drawbuf_next_row(src_buf_al88, src_stride);
}
}
} else if (mask_buf && opa >= LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_AL88_BLEND_NORMAL_TO_ARGB8888_WITH_MASK(dsc)) {
for (y = 0; y < h; y++) {
for (dest_x = 0, src_x = 0; src_x < w; dest_x++, src_x++) {
lv_color_8_32_mix(src_buf_al88[src_x].lumi, &dest_buf_c32[dest_x], LV_OPA_MIX2(src_buf_al88[src_x].alpha,
mask_buf[src_x]));
}
dest_buf_c32 = drawbuf_next_row(dest_buf_c32, dest_stride);
src_buf_al88 = drawbuf_next_row(src_buf_al88, src_stride);
mask_buf += mask_stride;
}
}
} else if (mask_buf && opa < LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_AL88_BLEND_NORMAL_TO_ARGB8888_MIX_MASK_OPA(dsc)) {
for (y = 0; y < h; y++) {
for (dest_x = 0, src_x = 0; src_x < w; dest_x++, src_x++) {
lv_color_8_32_mix(src_buf_al88[src_x].lumi, &dest_buf_c32[dest_x], LV_OPA_MIX3(src_buf_al88[src_x].alpha,
mask_buf[src_x], opa));
}
dest_buf_c32 = drawbuf_next_row(dest_buf_c32, dest_stride);
src_buf_al88 = drawbuf_next_row(src_buf_al88, src_stride);
mask_buf += mask_stride;
}
}
}
} else {
lv_color32_t src_argb;
lv_color_mix_alpha_cache_t cache;
lv_color_mix_with_alpha_cache_init(&cache);
for (y = 0; y < h; y++) {
for (dest_x = 0, src_x = 0; src_x < w; dest_x++, src_x++) {
src_argb.red = src_buf_al88[src_x].lumi;
src_argb.green = src_buf_al88[src_x].lumi;
src_argb.blue = src_buf_al88[src_x].lumi;
if (mask_buf == NULL) {
src_argb.alpha = LV_OPA_MIX2(src_buf_al88[src_x].alpha, opa);
} else {
src_argb.alpha = LV_OPA_MIX3(src_buf_al88[src_x].alpha, mask_buf[dest_x], opa);
}
blend_non_normal_pixel(&dest_buf_c32[dest_x], src_argb, dsc->blend_mode, &cache);
}
if (mask_buf) {
mask_buf += mask_stride;
}
dest_buf_c32 = drawbuf_next_row(dest_buf_c32, dest_stride);
src_buf_al88 = drawbuf_next_row(src_buf_al88, src_stride);
}
}
}
static void LV_ATTRIBUTE_FAST_MEM l8_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc)
{
int32_t w = dsc->dest_w;
int32_t h = dsc->dest_h;
lv_opa_t opa = dsc->opa;
lv_color32_t *dest_buf_c32 = dsc->dest_buf;
int32_t dest_stride = dsc->dest_stride;
const uint8_t *src_buf_l8 = dsc->src_buf;
int32_t src_stride = dsc->src_stride;
const lv_opa_t *mask_buf = dsc->mask_buf;
int32_t mask_stride = dsc->mask_stride;
int32_t dest_x;
int32_t src_x;
int32_t y;
if (dsc->blend_mode == LV_BLEND_MODE_NORMAL) {
if (mask_buf == NULL && opa >= LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_L8_BLEND_NORMAL_TO_ARGB8888(dsc)) {
for (y = 0; y < h; y++) {
for (dest_x = 0, src_x = 0; src_x < w; dest_x++, src_x++) {
dest_buf_c32[dest_x].alpha = src_buf_l8[src_x];
dest_buf_c32[dest_x].red = src_buf_l8[src_x];
dest_buf_c32[dest_x].green = src_buf_l8[src_x];
dest_buf_c32[dest_x].blue = src_buf_l8[src_x];
}
dest_buf_c32 = drawbuf_next_row(dest_buf_c32, dest_stride);
src_buf_l8 = drawbuf_next_row(src_buf_l8, src_stride);
}
}
} else if (mask_buf == NULL && opa < LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_L8_BLEND_NORMAL_TO_ARGB8888_WITH_OPA(dsc)) {
for (y = 0; y < h; y++) {
for (dest_x = 0, src_x = 0; src_x < w; dest_x++, src_x++) {
lv_color_8_32_mix(src_buf_l8[src_x], &dest_buf_c32[dest_x], opa);
}
dest_buf_c32 = drawbuf_next_row(dest_buf_c32, dest_stride);
src_buf_l8 = drawbuf_next_row(src_buf_l8, src_stride);
}
}
} else if (mask_buf && opa >= LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_L8_BLEND_NORMAL_TO_ARGB8888_WITH_MASK(dsc)) {
for (y = 0; y < h; y++) {
for (dest_x = 0, src_x = 0; src_x < w; dest_x++, src_x++) {
lv_color_8_32_mix(src_buf_l8[src_x], &dest_buf_c32[dest_x], mask_buf[src_x]);
}
dest_buf_c32 = drawbuf_next_row(dest_buf_c32, dest_stride);
src_buf_l8 = drawbuf_next_row(src_buf_l8, src_stride);
mask_buf += mask_stride;
}
}
} else if (mask_buf && opa < LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_L8_BLEND_NORMAL_TO_ARGB8888_MIX_MASK_OPA(dsc)) {
for (y = 0; y < h; y++) {
for (dest_x = 0, src_x = 0; src_x < w; dest_x++, src_x++) {
lv_color_8_32_mix(src_buf_l8[src_x], &dest_buf_c32[dest_x], LV_OPA_MIX2(mask_buf[src_x], opa));
}
dest_buf_c32 = drawbuf_next_row(dest_buf_c32, dest_stride);
src_buf_l8 = drawbuf_next_row(src_buf_l8, src_stride);
mask_buf += mask_stride;
}
}
}
} else {
lv_color32_t src_argb;
lv_color_mix_alpha_cache_t cache;
lv_color_mix_with_alpha_cache_init(&cache);
for (y = 0; y < h; y++) {
for (dest_x = 0, src_x = 0; src_x < w; dest_x++, src_x++) {
src_argb.red = src_buf_l8[src_x];
src_argb.green = src_buf_l8[src_x];
src_argb.blue = src_buf_l8[src_x];
if (mask_buf == NULL) {
src_argb.alpha = opa;
} else {
src_argb.alpha = LV_OPA_MIX2(mask_buf[dest_x], opa);
}
blend_non_normal_pixel(&dest_buf_c32[dest_x], src_argb, dsc->blend_mode, &cache);
}
if (mask_buf) {
mask_buf += mask_stride;
}
dest_buf_c32 = drawbuf_next_row(dest_buf_c32, dest_stride);
src_buf_l8 = drawbuf_next_row(src_buf_l8, src_stride);
}
}
}
static void LV_ATTRIBUTE_FAST_MEM rgb565_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc)
{
int32_t w = dsc->dest_w;
int32_t h = dsc->dest_h;
lv_opa_t opa = dsc->opa;
lv_color32_t *dest_buf_c32 = dsc->dest_buf;
int32_t dest_stride = dsc->dest_stride;
const lv_color16_t *src_buf_c16 = (const lv_color16_t *) dsc->src_buf;
int32_t src_stride = dsc->src_stride;
const lv_opa_t *mask_buf = dsc->mask_buf;
int32_t mask_stride = dsc->mask_stride;
lv_color32_t color_argb;
lv_color_mix_alpha_cache_t cache;
lv_color_mix_with_alpha_cache_init(&cache);
int32_t x;
int32_t y;
LV_UNUSED(color_argb);
if (dsc->blend_mode == LV_BLEND_MODE_NORMAL) {
if (mask_buf == NULL) {
lv_result_t accelerated;
if (opa >= LV_OPA_MAX) {
accelerated = LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_ARGB8888(dsc);
} else {
accelerated = LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_ARGB8888_WITH_OPA(dsc);
}
if (LV_RESULT_INVALID == accelerated) {
color_argb.alpha = opa;
for (y = 0; y < h; y++) {
for (x = 0; x < w; x++) {
color_argb.red = (src_buf_c16[x].red * 2106) >> 8; /*To make it rounded*/
color_argb.green = (src_buf_c16[x].green * 1037) >> 8;
color_argb.blue = (src_buf_c16[x].blue * 2106) >> 8;
dest_buf_c32[x] = lv_color_32_32_mix(color_argb, dest_buf_c32[x], &cache);
}
dest_buf_c32 = drawbuf_next_row(dest_buf_c32, dest_stride);
src_buf_c16 = drawbuf_next_row(src_buf_c16, src_stride);
}
}
} else if (mask_buf && opa >= LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_ARGB8888_WITH_MASK(dsc)) {
for (y = 0; y < h; y++) {
for (x = 0; x < w; x++) {
color_argb.alpha = mask_buf[x];
color_argb.red = (src_buf_c16[x].red * 2106) >> 8; /*To make it rounded*/
color_argb.green = (src_buf_c16[x].green * 1037) >> 8;
color_argb.blue = (src_buf_c16[x].blue * 2106) >> 8;
dest_buf_c32[x] = lv_color_32_32_mix(color_argb, dest_buf_c32[x], &cache);
}
dest_buf_c32 = drawbuf_next_row(dest_buf_c32, dest_stride);
src_buf_c16 = drawbuf_next_row(src_buf_c16, src_stride);
mask_buf += mask_stride;
}
}
} else {
if (LV_RESULT_INVALID == LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_ARGB8888_MIX_MASK_OPA(dsc)) {
for (y = 0; y < h; y++) {
for (x = 0; x < w; x++) {
color_argb.alpha = LV_OPA_MIX2(mask_buf[x], opa);
color_argb.red = (src_buf_c16[x].red * 2106) >> 8; /*To make it rounded*/
color_argb.green = (src_buf_c16[x].green * 1037) >> 8;
color_argb.blue = (src_buf_c16[x].blue * 2106) >> 8;
dest_buf_c32[x] = lv_color_32_32_mix(color_argb, dest_buf_c32[x], &cache);
}
dest_buf_c32 = drawbuf_next_row(dest_buf_c32, dest_stride);
src_buf_c16 = drawbuf_next_row(src_buf_c16, src_stride);
mask_buf += mask_stride;
}
}
}
} else {
lv_color32_t src_argb;
for (y = 0; y < h; y++) {
for (x = 0; x < w; x++) {
src_argb.red = (src_buf_c16[x].red * 2106) >> 8;
src_argb.green = (src_buf_c16[x].green * 1037) >> 8;
src_argb.blue = (src_buf_c16[x].blue * 2106) >> 8;
if (mask_buf == NULL) {
src_argb.alpha = opa;
} else {
src_argb.alpha = LV_OPA_MIX2(mask_buf[x], opa);
}
blend_non_normal_pixel(&dest_buf_c32[x], src_argb, dsc->blend_mode, &cache);
}
if (mask_buf) {
mask_buf += mask_stride;
}
dest_buf_c32 = drawbuf_next_row(dest_buf_c32, dest_stride);
src_buf_c16 = drawbuf_next_row(src_buf_c16, src_stride);
}
}
}
static void LV_ATTRIBUTE_FAST_MEM rgb888_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc, const uint8_t src_px_size)
{
int32_t w = dsc->dest_w;
int32_t h = dsc->dest_h;
lv_opa_t opa = dsc->opa;
lv_color32_t *dest_buf_c32 = dsc->dest_buf;
int32_t dest_stride = dsc->dest_stride;
const uint8_t *src_buf = dsc->src_buf;
int32_t src_stride = dsc->src_stride;
const lv_opa_t *mask_buf = dsc->mask_buf;
int32_t mask_stride = dsc->mask_stride;
lv_color32_t color_argb;
lv_color_mix_alpha_cache_t cache;
lv_color_mix_with_alpha_cache_init(&cache);
int32_t dest_x;
int32_t src_x;
int32_t y;
LV_UNUSED(color_argb);
if (dsc->blend_mode == LV_BLEND_MODE_NORMAL) {
/*Special case*/
if (mask_buf == NULL && opa >= LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_ARGB8888(dsc, src_px_size)) {
if (src_px_size == 4) {
uint32_t line_in_bytes = w * 4;
for (y = 0; y < h; y++) {
lv_memcpy(dest_buf_c32, src_buf, line_in_bytes);
dest_buf_c32 = drawbuf_next_row(dest_buf_c32, dest_stride);
src_buf = drawbuf_next_row(src_buf, src_stride);
}
} else if (src_px_size == 3) {
for (y = 0; y < h; y++) {
for (dest_x = 0, src_x = 0; dest_x < w; dest_x++, src_x += 3) {
dest_buf_c32[dest_x].red = src_buf[src_x + 2];
dest_buf_c32[dest_x].green = src_buf[src_x + 1];
dest_buf_c32[dest_x].blue = src_buf[src_x + 0];
dest_buf_c32[dest_x].alpha = 0xff;
}
dest_buf_c32 = drawbuf_next_row(dest_buf_c32, dest_stride);
src_buf = drawbuf_next_row(src_buf, src_stride);
}
}
}
}
if (mask_buf == NULL && opa < LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_ARGB8888_WITH_OPA(dsc, src_px_size)) {
color_argb.alpha = opa;
for (y = 0; y < h; y++) {
for (dest_x = 0, src_x = 0; dest_x < w; dest_x++, src_x += src_px_size) {
color_argb.red = src_buf[src_x + 2];
color_argb.green = src_buf[src_x + 1];
color_argb.blue = src_buf[src_x + 0];
dest_buf_c32[dest_x] = lv_color_32_32_mix(color_argb, dest_buf_c32[dest_x], &cache);
}
dest_buf_c32 = drawbuf_next_row(dest_buf_c32, dest_stride);
src_buf = drawbuf_next_row(src_buf, src_stride);
}
}
}
if (mask_buf && opa >= LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_ARGB8888_WITH_MASK(dsc, src_px_size)) {
for (y = 0; y < h; y++) {
for (dest_x = 0, src_x = 0; dest_x < w; dest_x++, src_x += src_px_size) {
color_argb.alpha = mask_buf[dest_x];
color_argb.red = src_buf[src_x + 2];
color_argb.green = src_buf[src_x + 1];
color_argb.blue = src_buf[src_x + 0];
dest_buf_c32[dest_x] = lv_color_32_32_mix(color_argb, dest_buf_c32[dest_x], &cache);
}
dest_buf_c32 = drawbuf_next_row(dest_buf_c32, dest_stride);
src_buf = drawbuf_next_row(src_buf, src_stride);
mask_buf += mask_stride;
}
}
}
if (mask_buf && opa < LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_ARGB8888_MIX_MASK_OPA(dsc, src_px_size)) {
for (y = 0; y < h; y++) {
for (dest_x = 0, src_x = 0; dest_x < w; dest_x++, src_x += src_px_size) {
color_argb.alpha = (opa * mask_buf[dest_x]) >> 8;
color_argb.red = src_buf[src_x + 2];
color_argb.green = src_buf[src_x + 1];
color_argb.blue = src_buf[src_x + 0];
dest_buf_c32[dest_x] = lv_color_32_32_mix(color_argb, dest_buf_c32[dest_x], &cache);
}
dest_buf_c32 = drawbuf_next_row(dest_buf_c32, dest_stride);
src_buf = drawbuf_next_row(src_buf, src_stride);
mask_buf += mask_stride;
}
}
}
} else {
lv_color32_t src_argb;
for (y = 0; y < h; y++) {
for (dest_x = 0, src_x = 0; dest_x < w; dest_x++, src_x += src_px_size) {
src_argb.red = src_buf[src_x + 2];
src_argb.green = src_buf[src_x + 1];
src_argb.blue = src_buf[src_x + 0];
if (mask_buf == NULL) {
src_argb.alpha = opa;
} else {
src_argb.alpha = LV_OPA_MIX2(mask_buf[dest_x], opa);
}
blend_non_normal_pixel(&dest_buf_c32[dest_x], src_argb, dsc->blend_mode, &cache);
}
if (mask_buf) {
mask_buf += mask_stride;
}
dest_buf_c32 = drawbuf_next_row(dest_buf_c32, dest_stride);
src_buf = drawbuf_next_row(src_buf, src_stride);
}
}
}
static void LV_ATTRIBUTE_FAST_MEM argb8888_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc)
{
int32_t w = dsc->dest_w;
int32_t h = dsc->dest_h;
lv_opa_t opa = dsc->opa;
lv_color32_t *dest_buf_c32 = dsc->dest_buf;
int32_t dest_stride = dsc->dest_stride;
const lv_color32_t *src_buf_c32 = dsc->src_buf;
int32_t src_stride = dsc->src_stride;
const lv_opa_t *mask_buf = dsc->mask_buf;
int32_t mask_stride = dsc->mask_stride;
lv_color32_t color_argb;
lv_color_mix_alpha_cache_t cache;
lv_color_mix_with_alpha_cache_init(&cache);
int32_t x;
int32_t y;
if (dsc->blend_mode == LV_BLEND_MODE_NORMAL) {
if (mask_buf == NULL && opa >= LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_ARGB8888(dsc)) {
for (y = 0; y < h; y++) {
for (x = 0; x < w; x++) {
dest_buf_c32[x] = lv_color_32_32_mix(src_buf_c32[x], dest_buf_c32[x], &cache);
}
dest_buf_c32 = drawbuf_next_row(dest_buf_c32, dest_stride);
src_buf_c32 = drawbuf_next_row(src_buf_c32, src_stride);
}
}
} else if (mask_buf == NULL && opa < LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_ARGB8888_WITH_OPA(dsc)) {
for (y = 0; y < h; y++) {
for (x = 0; x < w; x++) {
color_argb = src_buf_c32[x];
color_argb.alpha = LV_OPA_MIX2(color_argb.alpha, opa);
dest_buf_c32[x] = lv_color_32_32_mix(color_argb, dest_buf_c32[x], &cache);
}
dest_buf_c32 = drawbuf_next_row(dest_buf_c32, dest_stride);
src_buf_c32 = drawbuf_next_row(src_buf_c32, src_stride);
}
}
} else if (mask_buf && opa >= LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_ARGB8888_WITH_MASK(dsc)) {
for (y = 0; y < h; y++) {
for (x = 0; x < w; x++) {
color_argb = src_buf_c32[x];
color_argb.alpha = LV_OPA_MIX2(color_argb.alpha, mask_buf[x]);
dest_buf_c32[x] = lv_color_32_32_mix(color_argb, dest_buf_c32[x], &cache);
}
dest_buf_c32 = drawbuf_next_row(dest_buf_c32, dest_stride);
src_buf_c32 = drawbuf_next_row(src_buf_c32, src_stride);
mask_buf += mask_stride;
}
}
} else if (mask_buf && opa < LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_ARGB8888_MIX_MASK_OPA(dsc)) {
for (y = 0; y < h; y++) {
for (x = 0; x < w; x++) {
color_argb = src_buf_c32[x];
color_argb.alpha = LV_OPA_MIX3(color_argb.alpha, opa, mask_buf[x]);
dest_buf_c32[x] = lv_color_32_32_mix(color_argb, dest_buf_c32[x], &cache);
}
dest_buf_c32 = drawbuf_next_row(dest_buf_c32, dest_stride);
src_buf_c32 = drawbuf_next_row(src_buf_c32, src_stride);
mask_buf += mask_stride;
}
}
}
} else {
for (y = 0; y < h; y++) {
for (x = 0; x < w; x++) {
color_argb = src_buf_c32[x];
if (mask_buf == NULL) {
color_argb.alpha = LV_OPA_MIX2(color_argb.alpha, opa);
} else {
color_argb.alpha = LV_OPA_MIX3(color_argb.alpha, mask_buf[x], opa);
}
blend_non_normal_pixel(&dest_buf_c32[x], color_argb, dsc->blend_mode, &cache);
}
if (mask_buf) {
mask_buf += mask_stride;
}
dest_buf_c32 = drawbuf_next_row(dest_buf_c32, dest_stride);
src_buf_c32 = drawbuf_next_row(src_buf_c32, src_stride);
}
}
}
static inline void LV_ATTRIBUTE_FAST_MEM lv_color_8_32_mix(const uint8_t src, lv_color32_t *dest, uint8_t mix)
{
if (mix == 0) {
return;
}
dest->alpha = 255;
if (mix >= LV_OPA_MAX) {
dest->red = src;
dest->green = src;
dest->blue = src;
} else {
lv_opa_t mix_inv = 255 - mix;
dest->red = (uint32_t)((uint32_t)src * mix + dest->red * mix_inv) >> 8;
dest->green = (uint32_t)((uint32_t)src * mix + dest->green * mix_inv) >> 8;
dest->blue = (uint32_t)((uint32_t)src * mix + dest->blue * mix_inv) >> 8;
}
}
static inline lv_color32_t LV_ATTRIBUTE_FAST_MEM lv_color_32_32_mix(lv_color32_t fg, lv_color32_t bg,
lv_color_mix_alpha_cache_t *cache)
{
/*Pick the foreground if it's fully opaque or the Background is fully transparent*/
if (fg.alpha >= LV_OPA_MAX || bg.alpha <= LV_OPA_MIN) {
return fg;
}
/*Transparent foreground: use the Background*/
else if (fg.alpha <= LV_OPA_MIN) {
return bg;
}
/*Opaque background: use simple mix*/
else if (bg.alpha == 255) {
return lv_color_mix32(fg, bg);
}
/*Both colors have alpha. Expensive calculation need to be applied*/
else {
/*Save the parameters and the result. If they will be asked again don't compute again*/
/*Update the ratio and the result alpha value if the input alpha values change*/
if (bg.alpha != cache->bg_saved.alpha || fg.alpha != cache->fg_saved.alpha) {
/*Info:
* https://en.wikipedia.org/wiki/Alpha_compositing#Analytical_derivation_of_the_over_operator*/
cache->res_alpha_saved = 255 - LV_OPA_MIX2(255 - fg.alpha, 255 - bg.alpha);
LV_ASSERT(cache->res_alpha_saved != 0);
cache->ratio_saved = (uint32_t)((uint32_t)fg.alpha * 255) / cache->res_alpha_saved;
}
if (!lv_color32_eq(bg, cache->bg_saved) || !lv_color32_eq(fg, cache->fg_saved)) {
cache->fg_saved = fg;
cache->bg_saved = bg;
fg.alpha = cache->ratio_saved;
cache->res_saved = lv_color_mix32(fg, bg);
cache->res_saved.alpha = cache->res_alpha_saved;
}
return cache->res_saved;
}
}
void lv_color_mix_with_alpha_cache_init(lv_color_mix_alpha_cache_t *cache)
{
lv_memset(&cache->fg_saved, 0x00, sizeof(lv_color32_t)); //lv_memzero
lv_memset(&cache->bg_saved, 0x00, sizeof(lv_color32_t)); //lv_memzero
lv_memset(&cache->res_saved, 0x00, sizeof(lv_color32_t)); //lv_memzero
cache->res_alpha_saved = 255;
cache->ratio_saved = 255;
}
static inline void LV_ATTRIBUTE_FAST_MEM blend_non_normal_pixel(lv_color32_t *dest, lv_color32_t src,
lv_blend_mode_t mode, lv_color_mix_alpha_cache_t *cache)
{
lv_color32_t res;
switch (mode) {
case LV_BLEND_MODE_ADDITIVE:
res.red = LV_MIN(dest->red + src.red, 255);
res.green = LV_MIN(dest->green + src.green, 255);
res.blue = LV_MIN(dest->blue + src.blue, 255);
break;
case LV_BLEND_MODE_SUBTRACTIVE:
res.red = LV_MAX(dest->red - src.red, 0);
res.green = LV_MAX(dest->green - src.green, 0);
res.blue = LV_MAX(dest->blue - src.blue, 0);
break;
case LV_BLEND_MODE_MULTIPLY:
res.red = (dest->red * src.red) >> 8;
res.green = (dest->green * src.green) >> 8;
res.blue = (dest->blue * src.blue) >> 8;
break;
default:
LV_LOG_WARN("Not supported blend mode: %d", mode);
return;
}
res.alpha = src.alpha;
*dest = lv_color_32_32_mix(res, *dest, cache);
}
static inline void *LV_ATTRIBUTE_FAST_MEM drawbuf_next_row(const void *buf, uint32_t stride)
{
return (void *)((uint8_t *)buf + stride);
}

View File

@@ -0,0 +1,962 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*
* This file is derived from the LVGL project.
* See https://github.com/lvgl/lvgl for details.
*/
/**
* @file lv_draw_sw_blend_to_rgb565.c
*
*/
/*********************
* INCLUDES
*********************/
#include "lv_draw_sw_blend_to_rgb565.h"
#include "lv_assert.h"
#include "lv_types.h"
#include "lv_log.h"
#include "lv_draw_sw_blend.h"
#include "lv_math.h"
#include "lv_color.h"
#include "lv_string.h"
#include "esp_lvgl_port_lv_blend.h"
/*********************
* DEFINES
*********************/
#define LV_ATTRIBUTE_FAST_MEM
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC PROTOTYPES
**********************/
static void /* LV_ATTRIBUTE_FAST_MEM */ al88_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc);
static void /* LV_ATTRIBUTE_FAST_MEM */ l8_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc);
static void /* LV_ATTRIBUTE_FAST_MEM */ rgb565_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc);
static void /* LV_ATTRIBUTE_FAST_MEM */ rgb888_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc,
const uint8_t src_px_size);
static void /* LV_ATTRIBUTE_FAST_MEM */ argb8888_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc);
static inline uint16_t /* LV_ATTRIBUTE_FAST_MEM */ l8_to_rgb565(const uint8_t c1);
static inline uint16_t /* LV_ATTRIBUTE_FAST_MEM */ lv_color_8_16_mix(const uint8_t c1, uint16_t c2, uint8_t mix);
static inline uint16_t /* LV_ATTRIBUTE_FAST_MEM */ lv_color_24_16_mix(const uint8_t *c1, uint16_t c2, uint8_t mix);
static inline void * /* LV_ATTRIBUTE_FAST_MEM */ drawbuf_next_row(const void *buf, uint32_t stride);
/**********************
* STATIC VARIABLES
**********************/
/**********************
* MACROS
**********************/
#ifndef LV_DRAW_SW_COLOR_BLEND_TO_RGB565
#define LV_DRAW_SW_COLOR_BLEND_TO_RGB565(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_COLOR_BLEND_TO_RGB565_WITH_OPA
#define LV_DRAW_SW_COLOR_BLEND_TO_RGB565_WITH_OPA(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_COLOR_BLEND_TO_RGB565_WITH_MASK
#define LV_DRAW_SW_COLOR_BLEND_TO_RGB565_WITH_MASK(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_COLOR_BLEND_TO_RGB565_MIX_MASK_OPA
#define LV_DRAW_SW_COLOR_BLEND_TO_RGB565_MIX_MASK_OPA(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_L8_BLEND_NORMAL_TO_RGB565
#define LV_DRAW_SW_L8_BLEND_NORMAL_TO_RGB565(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_L8_BLEND_NORMAL_TO_RGB565_WITH_OPA
#define LV_DRAW_SW_L8_BLEND_NORMAL_TO_RGB565_WITH_OPA(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_L8_BLEND_NORMAL_TO_RGB565_WITH_MASK
#define LV_DRAW_SW_L8_BLEND_NORMAL_TO_RGB565_WITH_MASK(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_L8_BLEND_NORMAL_TO_RGB565_MIX_MASK_OPA
#define LV_DRAW_SW_L8_BLEND_NORMAL_TO_RGB565_MIX_MASK_OPA(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_AL88_BLEND_NORMAL_TO_RGB565
#define LV_DRAW_SW_AL88_BLEND_NORMAL_TO_RGB565(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_AL88_BLEND_NORMAL_TO_RGB565_WITH_OPA
#define LV_DRAW_SW_AL88_BLEND_NORMAL_TO_RGB565_WITH_OPA(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_AL88_BLEND_NORMAL_TO_RGB565_WITH_MASK
#define LV_DRAW_SW_AL88_BLEND_NORMAL_TO_RGB565_WITH_MASK(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_AL88_BLEND_NORMAL_TO_RGB565_MIX_MASK_OPA
#define LV_DRAW_SW_AL88_BLEND_NORMAL_TO_RGB565_MIX_MASK_OPA(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_RGB565
#define LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_RGB565(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_RGB565_WITH_OPA
#define LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_RGB565_WITH_OPA(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_RGB565_WITH_MASK
#define LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_RGB565_WITH_MASK(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_RGB565_MIX_MASK_OPA
#define LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_RGB565_MIX_MASK_OPA(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_RGB565
#define LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_RGB565(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_RGB565_WITH_OPA
#define LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_RGB565_WITH_OPA(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_RGB565_WITH_MASK
#define LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_RGB565_WITH_MASK(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_RGB565_MIX_MASK_OPA
#define LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_RGB565_MIX_MASK_OPA(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_RGB565
#define LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_RGB565(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_RGB565_WITH_OPA
#define LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_RGB565_WITH_OPA(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_RGB565_WITH_MASK
#define LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_RGB565_WITH_MASK(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_RGB565_MIX_MASK_OPA
#define LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_RGB565_MIX_MASK_OPA(...) LV_RESULT_INVALID
#endif
/**********************
* GLOBAL FUNCTIONS
**********************/
/**
* Fill an area with a color.
* Supports normal fill, fill with opacity, fill with mask, and fill with mask and opacity.
* dest_buf and color have native color depth. (RGB565, RGB888, XRGB8888)
* The background (dest_buf) cannot have alpha channel
* @param dest_buf
* @param dest_area
* @param dest_stride
* @param color
* @param opa
* @param mask
* @param mask_stride
*/
void LV_ATTRIBUTE_FAST_MEM lv_draw_sw_blend_color_to_rgb565(_lv_draw_sw_blend_fill_dsc_t *dsc)
{
int32_t w = dsc->dest_w;
int32_t h = dsc->dest_h;
uint16_t color16 = lv_color_to_u16(dsc->color);
lv_opa_t opa = dsc->opa;
const lv_opa_t *mask = dsc->mask_buf;
int32_t mask_stride = dsc->mask_stride;
uint16_t *dest_buf_u16 = dsc->dest_buf;
int32_t dest_stride = dsc->dest_stride;
int32_t x;
int32_t y;
LV_UNUSED(w);
LV_UNUSED(h);
LV_UNUSED(x);
LV_UNUSED(y);
LV_UNUSED(opa);
LV_UNUSED(mask);
LV_UNUSED(color16);
LV_UNUSED(mask_stride);
LV_UNUSED(dest_stride);
LV_UNUSED(dest_buf_u16);
/*Simple fill*/
if (mask == NULL && opa >= LV_OPA_MAX) {
if (dsc->use_asm) {
LV_DRAW_SW_COLOR_BLEND_TO_RGB565(dsc);
} else {
for (y = 0; y < h; y++) {
uint16_t *dest_end_final = dest_buf_u16 + w;
uint32_t *dest_end_mid = (uint32_t *)((uint16_t *) dest_buf_u16 + ((w - 1) & ~(0xF)));
if ((lv_uintptr_t)&dest_buf_u16[0] & 0x3) {
dest_buf_u16[0] = color16;
dest_buf_u16++;
}
uint32_t c32 = (uint32_t)color16 + ((uint32_t)color16 << 16);
uint32_t *dest32 = (uint32_t *)dest_buf_u16;
while (dest32 < dest_end_mid) {
dest32[0] = c32;
dest32[1] = c32;
dest32[2] = c32;
dest32[3] = c32;
dest32[4] = c32;
dest32[5] = c32;
dest32[6] = c32;
dest32[7] = c32;
dest32 += 8;
}
dest_buf_u16 = (uint16_t *)dest32;
while (dest_buf_u16 < dest_end_final) {
*dest_buf_u16 = color16;
dest_buf_u16++;
}
dest_buf_u16 = drawbuf_next_row(dest_buf_u16, dest_stride);
dest_buf_u16 -= w;
}
}
}
/*Opacity only*/
else if (mask == NULL && opa < LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_COLOR_BLEND_TO_RGB565_WITH_OPA(dsc)) {
uint32_t last_dest32_color = dest_buf_u16[0] + 1; /*Set to value which is not equal to the first pixel*/
uint32_t last_res32_color = 0;
for (y = 0; y < h; y++) {
x = 0;
if ((lv_uintptr_t)&dest_buf_u16[0] & 0x3) {
dest_buf_u16[0] = lv_color_16_16_mix(color16, dest_buf_u16[0], opa);
x = 1;
}
for (; x < w - 2; x += 2) {
if (dest_buf_u16[x] != dest_buf_u16[x + 1]) {
dest_buf_u16[x + 0] = lv_color_16_16_mix(color16, dest_buf_u16[x + 0], opa);
dest_buf_u16[x + 1] = lv_color_16_16_mix(color16, dest_buf_u16[x + 1], opa);
} else {
volatile uint32_t *dest32 = (uint32_t *)&dest_buf_u16[x];
if (last_dest32_color == *dest32) {
*dest32 = last_res32_color;
} else {
last_dest32_color = *dest32;
dest_buf_u16[x] = lv_color_16_16_mix(color16, dest_buf_u16[x + 0], opa);
dest_buf_u16[x + 1] = dest_buf_u16[x];
last_res32_color = *dest32;
}
}
}
for (; x < w ; x++) {
dest_buf_u16[x] = lv_color_16_16_mix(color16, dest_buf_u16[x], opa);
}
dest_buf_u16 = drawbuf_next_row(dest_buf_u16, dest_stride);
}
}
}
/*Masked with full opacity*/
else if (mask && opa >= LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_COLOR_BLEND_TO_RGB565_WITH_MASK(dsc)) {
for (y = 0; y < h; y++) {
x = 0;
if ((lv_uintptr_t)(mask) & 0x1) {
dest_buf_u16[x] = lv_color_16_16_mix(color16, dest_buf_u16[x], mask[x]);
x++;
}
for (; x <= w - 2; x += 2) {
uint16_t mask16 = *((uint16_t *)&mask[x]);
if (mask16 == 0xFFFF) {
dest_buf_u16[x + 0] = color16;
dest_buf_u16[x + 1] = color16;
} else if (mask16 != 0) {
dest_buf_u16[x + 0] = lv_color_16_16_mix(color16, dest_buf_u16[x + 0], mask[x + 0]);
dest_buf_u16[x + 1] = lv_color_16_16_mix(color16, dest_buf_u16[x + 1], mask[x + 1]);
}
}
for (; x < w ; x++) {
dest_buf_u16[x] = lv_color_16_16_mix(color16, dest_buf_u16[x], mask[x]);
}
dest_buf_u16 = drawbuf_next_row(dest_buf_u16, dest_stride);
mask += mask_stride;
}
}
}
/*Masked with opacity*/
else if (mask && opa < LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_COLOR_BLEND_TO_RGB565_MIX_MASK_OPA(dsc)) {
for (y = 0; y < h; y++) {
for (x = 0; x < w; x++) {
dest_buf_u16[x] = lv_color_16_16_mix(color16, dest_buf_u16[x], LV_OPA_MIX2(mask[x], opa));
}
dest_buf_u16 = drawbuf_next_row(dest_buf_u16, dest_stride);
mask += mask_stride;
}
}
}
}
void LV_ATTRIBUTE_FAST_MEM lv_draw_sw_blend_image_to_rgb565(_lv_draw_sw_blend_image_dsc_t *dsc)
{
switch (dsc->src_color_format) {
case LV_COLOR_FORMAT_RGB565:
rgb565_image_blend(dsc);
break;
case LV_COLOR_FORMAT_RGB888:
rgb888_image_blend(dsc, 3);
break;
case LV_COLOR_FORMAT_XRGB8888:
rgb888_image_blend(dsc, 4);
break;
case LV_COLOR_FORMAT_ARGB8888:
argb8888_image_blend(dsc);
break;
case LV_COLOR_FORMAT_L8:
l8_image_blend(dsc);
break;
case LV_COLOR_FORMAT_AL88:
al88_image_blend(dsc);
break;
default:
LV_LOG_WARN("Not supported source color format");
break;
}
}
/**********************
* STATIC FUNCTIONS
**********************/
static void LV_ATTRIBUTE_FAST_MEM al88_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc)
{
int32_t w = dsc->dest_w;
int32_t h = dsc->dest_h;
lv_opa_t opa = dsc->opa;
uint16_t *dest_buf_u16 = dsc->dest_buf;
int32_t dest_stride = dsc->dest_stride;
const lv_color16a_t *src_buf_al88 = dsc->src_buf;
int32_t src_stride = dsc->src_stride;
const lv_opa_t *mask_buf = dsc->mask_buf;
int32_t mask_stride = dsc->mask_stride;
int32_t dest_x;
int32_t src_x;
int32_t y;
if (dsc->blend_mode == LV_BLEND_MODE_NORMAL) {
if (mask_buf == NULL && opa >= LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_AL88_BLEND_NORMAL_TO_RGB565(dsc)) {
for (y = 0; y < h; y++) {
for (dest_x = 0, src_x = 0; dest_x < w; dest_x++, src_x++) {
dest_buf_u16[dest_x] = lv_color_8_16_mix(src_buf_al88[src_x].lumi, dest_buf_u16[dest_x], src_buf_al88[src_x].alpha);
}
dest_buf_u16 = drawbuf_next_row(dest_buf_u16, dest_stride);
src_buf_al88 = drawbuf_next_row(src_buf_al88, src_stride);
}
}
} else if (mask_buf == NULL && opa < LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_AL88_BLEND_NORMAL_TO_RGB565_WITH_OPA(dsc)) {
for (y = 0; y < h; y++) {
for (dest_x = 0, src_x = 0; dest_x < w; dest_x++, src_x++) {
dest_buf_u16[dest_x] = lv_color_8_16_mix(src_buf_al88[src_x].lumi, dest_buf_u16[dest_x],
LV_OPA_MIX2(src_buf_al88[src_x].alpha, opa));
}
dest_buf_u16 = drawbuf_next_row(dest_buf_u16, dest_stride);
src_buf_al88 = drawbuf_next_row(src_buf_al88, src_stride);
}
}
} else if (mask_buf && opa >= LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_AL88_BLEND_NORMAL_TO_RGB565_WITH_MASK(dsc)) {
for (y = 0; y < h; y++) {
for (dest_x = 0, src_x = 0; dest_x < w; dest_x++, src_x++) {
dest_buf_u16[dest_x] = lv_color_8_16_mix(src_buf_al88[src_x].lumi, dest_buf_u16[dest_x],
LV_OPA_MIX2(src_buf_al88[src_x].alpha, mask_buf[dest_x]));
}
dest_buf_u16 = drawbuf_next_row(dest_buf_u16, dest_stride);
src_buf_al88 = drawbuf_next_row(src_buf_al88, src_stride);
mask_buf += mask_stride;
}
}
} else if (mask_buf && opa < LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_AL88_BLEND_NORMAL_TO_RGB565_MIX_MASK_OPA(dsc)) {
for (y = 0; y < h; y++) {
for (dest_x = 0, src_x = 0; dest_x < w; dest_x++, src_x++) {
dest_buf_u16[dest_x] = lv_color_8_16_mix(src_buf_al88[src_x].lumi, dest_buf_u16[dest_x],
LV_OPA_MIX3(src_buf_al88[src_x].alpha, mask_buf[dest_x], opa));
}
dest_buf_u16 = drawbuf_next_row(dest_buf_u16, dest_stride);
src_buf_al88 = drawbuf_next_row(src_buf_al88, src_stride);
mask_buf += mask_stride;
}
}
}
} else {
uint16_t res = 0;
for (y = 0; y < h; y++) {
lv_color16_t *dest_buf_c16 = (lv_color16_t *)dest_buf_u16;
for (dest_x = 0, src_x = 0; dest_x < w; dest_x++, src_x += 4) {
uint8_t rb = src_buf_al88[src_x].lumi >> 3;
uint8_t g = src_buf_al88[src_x].lumi >> 2;
switch (dsc->blend_mode) {
case LV_BLEND_MODE_ADDITIVE:
res = (LV_MIN(dest_buf_c16[dest_x].red + rb, 31)) << 11;
res += (LV_MIN(dest_buf_c16[dest_x].green + g, 63)) << 5;
res += LV_MIN(dest_buf_c16[dest_x].blue + rb, 31);
break;
case LV_BLEND_MODE_SUBTRACTIVE:
res = (LV_MAX(dest_buf_c16[dest_x].red - rb, 0)) << 11;
res += (LV_MAX(dest_buf_c16[dest_x].green - g, 0)) << 5;
res += LV_MAX(dest_buf_c16[dest_x].blue - rb, 0);
break;
case LV_BLEND_MODE_MULTIPLY:
res = ((dest_buf_c16[dest_x].red * rb) >> 5) << 11;
res += ((dest_buf_c16[dest_x].green * g) >> 6) << 5;
res += (dest_buf_c16[dest_x].blue * rb) >> 5;
break;
default:
LV_LOG_WARN("Not supported blend mode: %d", dsc->blend_mode);
return;
}
if (mask_buf == NULL && opa >= LV_OPA_MAX) {
dest_buf_u16[dest_x] = lv_color_16_16_mix(res, dest_buf_u16[dest_x], src_buf_al88[src_x].alpha);
} else if (mask_buf == NULL && opa < LV_OPA_MAX) {
dest_buf_u16[dest_x] = lv_color_16_16_mix(res, dest_buf_u16[dest_x], LV_OPA_MIX2(opa, src_buf_al88[src_x].alpha));
} else {
if (opa >= LV_OPA_MAX) {
dest_buf_u16[dest_x] = lv_color_16_16_mix(res, dest_buf_u16[dest_x], mask_buf[dest_x]);
} else dest_buf_u16[dest_x] = lv_color_16_16_mix(res, dest_buf_u16[dest_x], LV_OPA_MIX3(mask_buf[dest_x], opa,
src_buf_al88[src_x].alpha));
}
}
dest_buf_u16 = drawbuf_next_row(dest_buf_u16, dest_stride);
src_buf_al88 = drawbuf_next_row(src_buf_al88, src_stride);
if (mask_buf) {
mask_buf += mask_stride;
}
}
}
}
static void LV_ATTRIBUTE_FAST_MEM l8_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc)
{
int32_t w = dsc->dest_w;
int32_t h = dsc->dest_h;
lv_opa_t opa = dsc->opa;
uint16_t *dest_buf_u16 = dsc->dest_buf;
int32_t dest_stride = dsc->dest_stride;
const uint8_t *src_buf_l8 = dsc->src_buf;
int32_t src_stride = dsc->src_stride;
const lv_opa_t *mask_buf = dsc->mask_buf;
int32_t mask_stride = dsc->mask_stride;
int32_t dest_x;
int32_t src_x;
int32_t y;
if (dsc->blend_mode == LV_BLEND_MODE_NORMAL) {
if (mask_buf == NULL && opa >= LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_L8_BLEND_NORMAL_TO_RGB565(dsc)) {
for (y = 0; y < h; y++) {
for (dest_x = 0, src_x = 0; dest_x < w; dest_x++, src_x++) {
dest_buf_u16[dest_x] = l8_to_rgb565(src_buf_l8[src_x]);
}
dest_buf_u16 = drawbuf_next_row(dest_buf_u16, dest_stride);
src_buf_l8 += src_stride;
}
}
} else if (mask_buf == NULL && opa < LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_L8_BLEND_NORMAL_TO_RGB565_WITH_OPA(dsc)) {
for (y = 0; y < h; y++) {
for (dest_x = 0, src_x = 0; dest_x < w; dest_x++, src_x++) {
dest_buf_u16[dest_x] = lv_color_8_16_mix(src_buf_l8[src_x], dest_buf_u16[dest_x], opa);
}
dest_buf_u16 = drawbuf_next_row(dest_buf_u16, dest_stride);
src_buf_l8 += src_stride;
}
}
} else if (mask_buf && opa >= LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_L8_BLEND_NORMAL_TO_RGB565_WITH_MASK(dsc)) {
for (y = 0; y < h; y++) {
for (dest_x = 0, src_x = 0; dest_x < w; dest_x++, src_x++) {
dest_buf_u16[dest_x] = lv_color_8_16_mix(src_buf_l8[src_x], dest_buf_u16[dest_x], mask_buf[dest_x]);
}
dest_buf_u16 = drawbuf_next_row(dest_buf_u16, dest_stride);
src_buf_l8 += src_stride;
mask_buf += mask_stride;
}
}
} else if (mask_buf && opa < LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_L8_BLEND_NORMAL_TO_RGB565_MIX_MASK_OPA(dsc)) {
for (y = 0; y < h; y++) {
for (dest_x = 0, src_x = 0; dest_x < w; dest_x++, src_x++) {
dest_buf_u16[dest_x] = lv_color_8_16_mix(src_buf_l8[src_x], dest_buf_u16[dest_x], LV_OPA_MIX2(mask_buf[dest_x], opa));
}
dest_buf_u16 = drawbuf_next_row(dest_buf_u16, dest_stride);
src_buf_l8 += src_stride;
mask_buf += mask_stride;
}
}
}
} else {
uint16_t res = 0;
for (y = 0; y < h; y++) {
lv_color16_t *dest_buf_c16 = (lv_color16_t *)dest_buf_u16;
for (dest_x = 0, src_x = 0; dest_x < w; dest_x++, src_x += 4) {
uint8_t rb = src_buf_l8[src_x] >> 3;
uint8_t g = src_buf_l8[src_x] >> 2;
switch (dsc->blend_mode) {
case LV_BLEND_MODE_ADDITIVE:
res = (LV_MIN(dest_buf_c16[dest_x].red + rb, 31)) << 11;
res += (LV_MIN(dest_buf_c16[dest_x].green + g, 63)) << 5;
res += LV_MIN(dest_buf_c16[dest_x].blue + rb, 31);
break;
case LV_BLEND_MODE_SUBTRACTIVE:
res = (LV_MAX(dest_buf_c16[dest_x].red - rb, 0)) << 11;
res += (LV_MAX(dest_buf_c16[dest_x].green - g, 0)) << 5;
res += LV_MAX(dest_buf_c16[dest_x].blue - rb, 0);
break;
case LV_BLEND_MODE_MULTIPLY:
res = ((dest_buf_c16[dest_x].red * rb) >> 5) << 11;
res += ((dest_buf_c16[dest_x].green * g) >> 6) << 5;
res += (dest_buf_c16[dest_x].blue * rb) >> 5;
break;
default:
LV_LOG_WARN("Not supported blend mode: %d", dsc->blend_mode);
return;
}
if (mask_buf == NULL && opa >= LV_OPA_MAX) {
dest_buf_u16[dest_x] = res;
} else if (mask_buf == NULL && opa < LV_OPA_MAX) {
dest_buf_u16[dest_x] = lv_color_16_16_mix(res, dest_buf_u16[dest_x], opa);
} else {
if (opa >= LV_OPA_MAX) {
dest_buf_u16[dest_x] = lv_color_16_16_mix(res, dest_buf_u16[dest_x], mask_buf[dest_x]);
} else {
dest_buf_u16[dest_x] = lv_color_16_16_mix(res, dest_buf_u16[dest_x], LV_OPA_MIX2(mask_buf[dest_x], opa));
}
}
}
dest_buf_u16 = drawbuf_next_row(dest_buf_u16, dest_stride);
src_buf_l8 += src_stride;
if (mask_buf) {
mask_buf += mask_stride;
}
}
}
}
static void LV_ATTRIBUTE_FAST_MEM rgb565_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc)
{
int32_t w = dsc->dest_w;
int32_t h = dsc->dest_h;
lv_opa_t opa = dsc->opa;
uint16_t *dest_buf_u16 = dsc->dest_buf;
int32_t dest_stride = dsc->dest_stride;
const uint16_t *src_buf_u16 = dsc->src_buf;
int32_t src_stride = dsc->src_stride;
const lv_opa_t *mask_buf = dsc->mask_buf;
int32_t mask_stride = dsc->mask_stride;
int32_t x;
int32_t y;
if (dsc->blend_mode == LV_BLEND_MODE_NORMAL) {
if (mask_buf == NULL && opa >= LV_OPA_MAX) {
if (dsc->use_asm) {
LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_RGB565(dsc);
} else {
uint32_t line_in_bytes = w * 2;
for (y = 0; y < h; y++) {
lv_memcpy(dest_buf_u16, src_buf_u16, line_in_bytes);
dest_buf_u16 = drawbuf_next_row(dest_buf_u16, dest_stride);
src_buf_u16 = drawbuf_next_row(src_buf_u16, src_stride);
}
}
} else if (mask_buf == NULL && opa < LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_RGB565_WITH_OPA(dsc)) {
for (y = 0; y < h; y++) {
for (x = 0; x < w; x++) {
dest_buf_u16[x] = lv_color_16_16_mix(src_buf_u16[x], dest_buf_u16[x], opa);
}
dest_buf_u16 = drawbuf_next_row(dest_buf_u16, dest_stride);
src_buf_u16 = drawbuf_next_row(src_buf_u16, src_stride);
}
}
} else if (mask_buf && opa >= LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_RGB565_WITH_MASK(dsc)) {
for (y = 0; y < h; y++) {
for (x = 0; x < w; x++) {
dest_buf_u16[x] = lv_color_16_16_mix(src_buf_u16[x], dest_buf_u16[x], mask_buf[x]);
}
dest_buf_u16 = drawbuf_next_row(dest_buf_u16, dest_stride);
src_buf_u16 = drawbuf_next_row(src_buf_u16, src_stride);
mask_buf += mask_stride;
}
}
} else {
if (LV_RESULT_INVALID == LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_RGB565_MIX_MASK_OPA(dsc)) {
for (y = 0; y < h; y++) {
for (x = 0; x < w; x++) {
dest_buf_u16[x] = lv_color_16_16_mix(src_buf_u16[x], dest_buf_u16[x], LV_OPA_MIX2(mask_buf[x], opa));
}
dest_buf_u16 = drawbuf_next_row(dest_buf_u16, dest_stride);
src_buf_u16 = drawbuf_next_row(src_buf_u16, src_stride);
mask_buf += mask_stride;
}
}
}
} else {
uint16_t res = 0;
for (y = 0; y < h; y++) {
lv_color16_t *dest_buf_c16 = (lv_color16_t *) dest_buf_u16;
lv_color16_t *src_buf_c16 = (lv_color16_t *) src_buf_u16;
for (x = 0; x < w; x++) {
switch (dsc->blend_mode) {
case LV_BLEND_MODE_ADDITIVE:
if (src_buf_u16[x] == 0x0000) {
continue; /*Do not add pure black*/
}
res = (LV_MIN(dest_buf_c16[x].red + src_buf_c16[x].red, 31)) << 11;
res += (LV_MIN(dest_buf_c16[x].green + src_buf_c16[x].green, 63)) << 5;
res += LV_MIN(dest_buf_c16[x].blue + src_buf_c16[x].blue, 31);
break;
case LV_BLEND_MODE_SUBTRACTIVE:
if (src_buf_u16[x] == 0x0000) {
continue; /*Do not subtract pure black*/
}
res = (LV_MAX(dest_buf_c16[x].red - src_buf_c16[x].red, 0)) << 11;
res += (LV_MAX(dest_buf_c16[x].green - src_buf_c16[x].green, 0)) << 5;
res += LV_MAX(dest_buf_c16[x].blue - src_buf_c16[x].blue, 0);
break;
case LV_BLEND_MODE_MULTIPLY:
if (src_buf_u16[x] == 0xffff) {
continue; /*Do not multiply with pure white (considered as 1)*/
}
res = ((dest_buf_c16[x].red * src_buf_c16[x].red) >> 5) << 11;
res += ((dest_buf_c16[x].green * src_buf_c16[x].green) >> 6) << 5;
res += (dest_buf_c16[x].blue * src_buf_c16[x].blue) >> 5;
break;
default:
LV_LOG_WARN("Not supported blend mode: %d", dsc->blend_mode);
return;
}
if (mask_buf == NULL) {
dest_buf_u16[x] = lv_color_16_16_mix(res, dest_buf_u16[x], opa);
} else {
if (opa >= LV_OPA_MAX) {
dest_buf_u16[x] = lv_color_16_16_mix(res, dest_buf_u16[x], mask_buf[x]);
} else {
dest_buf_u16[x] = lv_color_16_16_mix(res, dest_buf_u16[x], LV_OPA_MIX2(mask_buf[x], opa));
}
}
}
dest_buf_u16 = drawbuf_next_row(dest_buf_u16, dest_stride);
src_buf_u16 = drawbuf_next_row(src_buf_u16, src_stride);
if (mask_buf) {
mask_buf += mask_stride;
}
}
}
}
static void LV_ATTRIBUTE_FAST_MEM rgb888_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc, const uint8_t src_px_size)
{
int32_t w = dsc->dest_w;
int32_t h = dsc->dest_h;
lv_opa_t opa = dsc->opa;
uint16_t *dest_buf_u16 = dsc->dest_buf;
int32_t dest_stride = dsc->dest_stride;
const uint8_t *src_buf_u8 = dsc->src_buf;
int32_t src_stride = dsc->src_stride;
const lv_opa_t *mask_buf = dsc->mask_buf;
int32_t mask_stride = dsc->mask_stride;
int32_t dest_x;
int32_t src_x;
int32_t y;
if (dsc->blend_mode == LV_BLEND_MODE_NORMAL) {
if (mask_buf == NULL && opa >= LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_RGB565(dsc, src_px_size)) {
for (y = 0; y < h; y++) {
for (dest_x = 0, src_x = 0; dest_x < w; dest_x++, src_x += src_px_size) {
dest_buf_u16[dest_x] = ((src_buf_u8[src_x + 2] & 0xF8) << 8) +
((src_buf_u8[src_x + 1] & 0xFC) << 3) +
((src_buf_u8[src_x + 0] & 0xF8) >> 3);
}
dest_buf_u16 = drawbuf_next_row(dest_buf_u16, dest_stride);
src_buf_u8 += src_stride;
}
}
} else if (mask_buf == NULL && opa < LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_RGB565_WITH_OPA(dsc, src_px_size)) {
for (y = 0; y < h; y++) {
for (dest_x = 0, src_x = 0; dest_x < w; dest_x++, src_x += src_px_size) {
dest_buf_u16[dest_x] = lv_color_24_16_mix(&src_buf_u8[src_x], dest_buf_u16[dest_x], opa);
}
dest_buf_u16 = drawbuf_next_row(dest_buf_u16, dest_stride);
src_buf_u8 += src_stride;
}
}
}
if (mask_buf && opa >= LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_RGB565_WITH_MASK(dsc, src_px_size)) {
for (y = 0; y < h; y++) {
for (dest_x = 0, src_x = 0; dest_x < w; dest_x++, src_x += src_px_size) {
dest_buf_u16[dest_x] = lv_color_24_16_mix(&src_buf_u8[src_x], dest_buf_u16[dest_x], mask_buf[dest_x]);
}
dest_buf_u16 = drawbuf_next_row(dest_buf_u16, dest_stride);
src_buf_u8 += src_stride;
mask_buf += mask_stride;
}
}
}
if (mask_buf && opa < LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_RGB565_MIX_MASK_OPA(dsc, src_px_size)) {
for (y = 0; y < h; y++) {
for (dest_x = 0, src_x = 0; dest_x < w; dest_x++, src_x += src_px_size) {
dest_buf_u16[dest_x] = lv_color_24_16_mix(&src_buf_u8[src_x], dest_buf_u16[dest_x], LV_OPA_MIX2(mask_buf[dest_x], opa));
}
dest_buf_u16 = drawbuf_next_row(dest_buf_u16, dest_stride);
src_buf_u8 += src_stride;
mask_buf += mask_stride;
}
}
}
} else {
uint16_t res = 0;
for (y = 0; y < h; y++) {
lv_color16_t *dest_buf_c16 = (lv_color16_t *) dest_buf_u16;
for (dest_x = 0, src_x = 0; dest_x < w; dest_x++, src_x += src_px_size) {
switch (dsc->blend_mode) {
case LV_BLEND_MODE_ADDITIVE:
res = (LV_MIN(dest_buf_c16[dest_x].red + (src_buf_u8[src_x + 2] >> 3), 31)) << 11;
res += (LV_MIN(dest_buf_c16[dest_x].green + (src_buf_u8[src_x + 1] >> 2), 63)) << 5;
res += LV_MIN(dest_buf_c16[dest_x].blue + (src_buf_u8[src_x + 0] >> 3), 31);
break;
case LV_BLEND_MODE_SUBTRACTIVE:
res = (LV_MAX(dest_buf_c16[dest_x].red - (src_buf_u8[src_x + 2] >> 3), 0)) << 11;
res += (LV_MAX(dest_buf_c16[dest_x].green - (src_buf_u8[src_x + 1] >> 2), 0)) << 5;
res += LV_MAX(dest_buf_c16[dest_x].blue - (src_buf_u8[src_x + 0] >> 3), 0);
break;
case LV_BLEND_MODE_MULTIPLY:
res = ((dest_buf_c16[dest_x].red * (src_buf_u8[src_x + 2] >> 3)) >> 5) << 11;
res += ((dest_buf_c16[dest_x].green * (src_buf_u8[src_x + 1] >> 2)) >> 6) << 5;
res += (dest_buf_c16[dest_x].blue * (src_buf_u8[src_x + 0] >> 3)) >> 5;
break;
default:
LV_LOG_WARN("Not supported blend mode: %d", dsc->blend_mode);
return;
}
if (mask_buf == NULL) {
dest_buf_u16[dest_x] = lv_color_16_16_mix(res, dest_buf_u16[dest_x], opa);
} else {
if (opa >= LV_OPA_MAX) {
dest_buf_u16[dest_x] = lv_color_16_16_mix(res, dest_buf_u16[dest_x], mask_buf[dest_x]);
} else {
dest_buf_u16[dest_x] = lv_color_16_16_mix(res, dest_buf_u16[dest_x], LV_OPA_MIX2(mask_buf[dest_x], opa));
}
}
}
dest_buf_u16 = drawbuf_next_row(dest_buf_u16, dest_stride);
src_buf_u8 += src_stride;
if (mask_buf) {
mask_buf += mask_stride;
}
}
}
}
static void LV_ATTRIBUTE_FAST_MEM argb8888_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc)
{
int32_t w = dsc->dest_w;
int32_t h = dsc->dest_h;
lv_opa_t opa = dsc->opa;
uint16_t *dest_buf_u16 = dsc->dest_buf;
int32_t dest_stride = dsc->dest_stride;
const uint8_t *src_buf_u8 = dsc->src_buf;
int32_t src_stride = dsc->src_stride;
const lv_opa_t *mask_buf = dsc->mask_buf;
int32_t mask_stride = dsc->mask_stride;
int32_t dest_x;
int32_t src_x;
int32_t y;
if (dsc->blend_mode == LV_BLEND_MODE_NORMAL) {
if (mask_buf == NULL && opa >= LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_RGB565(dsc)) {
for (y = 0; y < h; y++) {
for (dest_x = 0, src_x = 0; dest_x < w; dest_x++, src_x += 4) {
dest_buf_u16[dest_x] = lv_color_24_16_mix(&src_buf_u8[src_x], dest_buf_u16[dest_x], src_buf_u8[src_x + 3]);
}
dest_buf_u16 = drawbuf_next_row(dest_buf_u16, dest_stride);
src_buf_u8 += src_stride;
}
}
} else if (mask_buf == NULL && opa < LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_RGB565_WITH_OPA(dsc)) {
for (y = 0; y < h; y++) {
for (dest_x = 0, src_x = 0; dest_x < w; dest_x++, src_x += 4) {
dest_buf_u16[dest_x] = lv_color_24_16_mix(&src_buf_u8[src_x], dest_buf_u16[dest_x], LV_OPA_MIX2(src_buf_u8[src_x + 3],
opa));
}
dest_buf_u16 = drawbuf_next_row(dest_buf_u16, dest_stride);
src_buf_u8 += src_stride;
}
}
} else if (mask_buf && opa >= LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_RGB565_WITH_MASK(dsc)) {
for (y = 0; y < h; y++) {
for (dest_x = 0, src_x = 0; dest_x < w; dest_x++, src_x += 4) {
dest_buf_u16[dest_x] = lv_color_24_16_mix(&src_buf_u8[src_x], dest_buf_u16[dest_x],
LV_OPA_MIX2(src_buf_u8[src_x + 3], mask_buf[dest_x]));
}
dest_buf_u16 = drawbuf_next_row(dest_buf_u16, dest_stride);
src_buf_u8 += src_stride;
mask_buf += mask_stride;
}
}
} else if (mask_buf && opa < LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_RGB565_MIX_MASK_OPA(dsc)) {
for (y = 0; y < h; y++) {
for (dest_x = 0, src_x = 0; dest_x < w; dest_x++, src_x += 4) {
dest_buf_u16[dest_x] = lv_color_24_16_mix(&src_buf_u8[src_x], dest_buf_u16[dest_x],
LV_OPA_MIX3(src_buf_u8[src_x + 3], mask_buf[dest_x], opa));
}
dest_buf_u16 = drawbuf_next_row(dest_buf_u16, dest_stride);
src_buf_u8 += src_stride;
mask_buf += mask_stride;
}
}
}
} else {
uint16_t res = 0;
for (y = 0; y < h; y++) {
lv_color16_t *dest_buf_c16 = (lv_color16_t *) dest_buf_u16;
for (dest_x = 0, src_x = 0; dest_x < w; dest_x++, src_x += 4) {
switch (dsc->blend_mode) {
case LV_BLEND_MODE_ADDITIVE:
res = (LV_MIN(dest_buf_c16[dest_x].red + (src_buf_u8[src_x + 2] >> 3), 31)) << 11;
res += (LV_MIN(dest_buf_c16[dest_x].green + (src_buf_u8[src_x + 1] >> 2), 63)) << 5;
res += LV_MIN(dest_buf_c16[dest_x].blue + (src_buf_u8[src_x + 0] >> 3), 31);
break;
case LV_BLEND_MODE_SUBTRACTIVE:
res = (LV_MAX(dest_buf_c16[dest_x].red - (src_buf_u8[src_x + 2] >> 3), 0)) << 11;
res += (LV_MAX(dest_buf_c16[dest_x].green - (src_buf_u8[src_x + 1] >> 2), 0)) << 5;
res += LV_MAX(dest_buf_c16[dest_x].blue - (src_buf_u8[src_x + 0] >> 3), 0);
break;
case LV_BLEND_MODE_MULTIPLY:
res = ((dest_buf_c16[dest_x].red * (src_buf_u8[src_x + 2] >> 3)) >> 5) << 11;
res += ((dest_buf_c16[dest_x].green * (src_buf_u8[src_x + 1] >> 2)) >> 6) << 5;
res += (dest_buf_c16[dest_x].blue * (src_buf_u8[src_x + 0] >> 3)) >> 5;
break;
default:
LV_LOG_WARN("Not supported blend mode: %d", dsc->blend_mode);
return;
}
if (mask_buf == NULL && opa >= LV_OPA_MAX) {
dest_buf_u16[dest_x] = lv_color_16_16_mix(res, dest_buf_u16[dest_x], src_buf_u8[src_x + 3]);
} else if (mask_buf == NULL && opa < LV_OPA_MAX) {
dest_buf_u16[dest_x] = lv_color_16_16_mix(res, dest_buf_u16[dest_x], LV_OPA_MIX2(opa, src_buf_u8[src_x + 3]));
} else {
if (opa >= LV_OPA_MAX) {
dest_buf_u16[dest_x] = lv_color_16_16_mix(res, dest_buf_u16[dest_x], mask_buf[dest_x]);
} else dest_buf_u16[dest_x] = lv_color_16_16_mix(res, dest_buf_u16[dest_x], LV_OPA_MIX3(mask_buf[dest_x], opa,
src_buf_u8[src_x + 3]));
}
}
dest_buf_u16 = drawbuf_next_row(dest_buf_u16, dest_stride);
src_buf_u8 += src_stride;
if (mask_buf) {
mask_buf += mask_stride;
}
}
}
}
static inline uint16_t LV_ATTRIBUTE_FAST_MEM l8_to_rgb565(const uint8_t c1)
{
return ((c1 & 0xF8) << 8) + ((c1 & 0xFC) << 3) + ((c1 & 0xF8) >> 3);
}
static inline uint16_t LV_ATTRIBUTE_FAST_MEM lv_color_8_16_mix(const uint8_t c1, uint16_t c2, uint8_t mix)
{
if (mix == 0) {
return c2;
} else if (mix == 255) {
return ((c1 & 0xF8) << 8) + ((c1 & 0xFC) << 3) + ((c1 & 0xF8) >> 3);
} else {
lv_opa_t mix_inv = 255 - mix;
return ((((c1 >> 3) * mix + ((c2 >> 11) & 0x1F) * mix_inv) << 3) & 0xF800) +
((((c1 >> 2) * mix + ((c2 >> 5) & 0x3F) * mix_inv) >> 3) & 0x07E0) +
(((c1 >> 3) * mix + (c2 & 0x1F) * mix_inv) >> 8);
}
}
static inline uint16_t LV_ATTRIBUTE_FAST_MEM lv_color_24_16_mix(const uint8_t *c1, uint16_t c2, uint8_t mix)
{
if (mix == 0) {
return c2;
} else if (mix == 255) {
return ((c1[2] & 0xF8) << 8) + ((c1[1] & 0xFC) << 3) + ((c1[0] & 0xF8) >> 3);
} else {
lv_opa_t mix_inv = 255 - mix;
return ((((c1[2] >> 3) * mix + ((c2 >> 11) & 0x1F) * mix_inv) << 3) & 0xF800) +
((((c1[1] >> 2) * mix + ((c2 >> 5) & 0x3F) * mix_inv) >> 3) & 0x07E0) +
(((c1[0] >> 3) * mix + (c2 & 0x1F) * mix_inv) >> 8);
}
}
static inline void *LV_ATTRIBUTE_FAST_MEM drawbuf_next_row(const void *buf, uint32_t stride)
{
return (void *)((uint8_t *)buf + stride);
}

View File

@@ -0,0 +1,954 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*
* This file is derived from the LVGL project.
* See https://github.com/lvgl/lvgl for details.
*/
/**
* @file lv_draw_sw_blend_to_rgb888.c
*
*/
/*********************
* INCLUDES
*********************/
#include "lv_draw_sw_blend_to_rgb888.h"
#include "lv_assert.h"
#include "lv_types.h"
#include "lv_log.h"
#include "lv_draw_sw_blend.h"
#include "lv_math.h"
#include "lv_color.h"
#include "lv_string.h"
#include "esp_lvgl_port_lv_blend.h"
/*********************
* DEFINES
*********************/
#define LV_ATTRIBUTE_FAST_MEM
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC PROTOTYPES
**********************/
static void /* LV_ATTRIBUTE_FAST_MEM */ al88_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc, uint32_t dest_px_size);
static void /* LV_ATTRIBUTE_FAST_MEM */ i1_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc, uint32_t dest_px_size);
static inline uint8_t /* LV_ATTRIBUTE_FAST_MEM */ get_bit(const uint8_t *buf, int32_t bit_idx);
static void /* LV_ATTRIBUTE_FAST_MEM */ l8_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc, uint32_t dest_px_size);
static void /* LV_ATTRIBUTE_FAST_MEM */ rgb565_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc, uint32_t dest_px_size);
static void /* LV_ATTRIBUTE_FAST_MEM */ rgb888_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc,
const uint8_t dest_px_size,
uint32_t src_px_size);
static void /* LV_ATTRIBUTE_FAST_MEM */ argb8888_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc,
uint32_t dest_px_size);
static inline void /* LV_ATTRIBUTE_FAST_MEM */ lv_color_8_24_mix(const uint8_t src, uint8_t *dest, uint8_t mix);
static inline void /* LV_ATTRIBUTE_FAST_MEM */ lv_color_24_24_mix(const uint8_t *src, uint8_t *dest, uint8_t mix);
static inline void /* LV_ATTRIBUTE_FAST_MEM */ blend_non_normal_pixel(uint8_t *dest, lv_color32_t src,
lv_blend_mode_t mode);
static inline void * /* LV_ATTRIBUTE_FAST_MEM */ drawbuf_next_row(const void *buf, uint32_t stride);
/**********************
* STATIC VARIABLES
**********************/
/**********************
* MACROS
**********************/
#ifndef LV_DRAW_SW_COLOR_BLEND_TO_RGB888
#define LV_DRAW_SW_COLOR_BLEND_TO_RGB888(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_COLOR_BLEND_TO_RGB888_WITH_OPA
#define LV_DRAW_SW_COLOR_BLEND_TO_RGB888_WITH_OPA(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_COLOR_BLEND_TO_RGB888_WITH_MASK
#define LV_DRAW_SW_COLOR_BLEND_TO_RGB888_WITH_MASK(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_COLOR_BLEND_TO_RGB888_MIX_MASK_OPA
#define LV_DRAW_SW_COLOR_BLEND_TO_RGB888_MIX_MASK_OPA(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_L8_BLEND_NORMAL_TO_RGB888
#define LV_DRAW_SW_L8_BLEND_NORMAL_TO_RGB888(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_L8_BLEND_NORMAL_TO_RGB888_WITH_OPA
#define LV_DRAW_SW_L8_BLEND_NORMAL_TO_RGB888_WITH_OPA(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_L8_BLEND_NORMAL_TO_RGB888_WITH_MASK
#define LV_DRAW_SW_L8_BLEND_NORMAL_TO_RGB888_WITH_MASK(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_L8_BLEND_NORMAL_TO_RGB888_MIX_MASK_OPA
#define LV_DRAW_SW_L8_BLEND_NORMAL_TO_RGB888_MIX_MASK_OPA(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_RGB888
#define LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_RGB888(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_RGB888_WITH_OPA
#define LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_RGB888_WITH_OPA(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_RGB888_WITH_MASK
#define LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_RGB888_WITH_MASK(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_RGB888_MIX_MASK_OPA
#define LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_RGB888_MIX_MASK_OPA(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_RGB888
#define LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_RGB888(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_RGB888_WITH_OPA
#define LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_RGB888_WITH_OPA(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_RGB888_WITH_MASK
#define LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_RGB888_WITH_MASK(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_RGB888_MIX_MASK_OPA
#define LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_RGB888_MIX_MASK_OPA(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_RGB888
#define LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_RGB888(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_RGB888_WITH_OPA
#define LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_RGB888_WITH_OPA(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_RGB888_WITH_MASK
#define LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_RGB888_WITH_MASK(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_RGB888_MIX_MASK_OPA
#define LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_RGB888_MIX_MASK_OPA(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_I1_BLEND_NORMAL_TO_888
#define LV_DRAW_SW_I1_BLEND_NORMAL_TO_888(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_I1_BLEND_NORMAL_TO_888_WITH_OPA
#define LV_DRAW_SW_I1_BLEND_NORMAL_TO_888_WITH_OPA(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_I1_BLEND_NORMAL_TO_888_WITH_MASK
#define LV_DRAW_SW_I1_BLEND_NORMAL_TO_888_WITH_MASK(...) LV_RESULT_INVALID
#endif
#ifndef LV_DRAW_SW_I1_BLEND_NORMAL_TO_888_MIX_MASK_OPA
#define LV_DRAW_SW_I1_BLEND_NORMAL_TO_888_MIX_MASK_OPA(...) LV_RESULT_INVALID
#endif
/**********************
* GLOBAL FUNCTIONS
**********************/
void LV_ATTRIBUTE_FAST_MEM lv_draw_sw_blend_color_to_rgb888(_lv_draw_sw_blend_fill_dsc_t *dsc, uint32_t dest_px_size)
{
int32_t w = dsc->dest_w;
int32_t h = dsc->dest_h;
lv_opa_t opa = dsc->opa;
const lv_opa_t *mask = dsc->mask_buf;
int32_t mask_stride = dsc->mask_stride;
int32_t dest_stride = dsc->dest_stride;
int32_t x;
int32_t y;
LV_UNUSED(w);
LV_UNUSED(h);
LV_UNUSED(x);
LV_UNUSED(y);
LV_UNUSED(opa);
LV_UNUSED(mask);
LV_UNUSED(mask_stride);
LV_UNUSED(dest_stride);
/*Simple fill*/
if (mask == NULL && opa >= LV_OPA_MAX) {
if (dsc->use_asm && dest_px_size == 3) {
LV_DRAW_SW_COLOR_BLEND_TO_RGB888(dsc, dest_px_size);
} else {
if (dest_px_size == 3) {
uint8_t *dest_buf_u8 = dsc->dest_buf;
uint8_t *dest_buf_ori = dsc->dest_buf;
w *= dest_px_size;
for (x = 0; x < w; x += 3) {
dest_buf_u8[x + 0] = dsc->color.blue;
dest_buf_u8[x + 1] = dsc->color.green;
dest_buf_u8[x + 2] = dsc->color.red;
}
dest_buf_u8 += dest_stride;
for (y = 1; y < h; y++) {
lv_memcpy(dest_buf_u8, dest_buf_ori, w);
dest_buf_u8 += dest_stride;
}
}
if (dest_px_size == 4) {
uint32_t color32 = lv_color_to_u32(dsc->color);
uint32_t *dest_buf_u32 = dsc->dest_buf;
for (y = 0; y < h; y++) {
for (x = 0; x <= w - 16; x += 16) {
dest_buf_u32[x + 0] = color32;
dest_buf_u32[x + 1] = color32;
dest_buf_u32[x + 2] = color32;
dest_buf_u32[x + 3] = color32;
dest_buf_u32[x + 4] = color32;
dest_buf_u32[x + 5] = color32;
dest_buf_u32[x + 6] = color32;
dest_buf_u32[x + 7] = color32;
dest_buf_u32[x + 8] = color32;
dest_buf_u32[x + 9] = color32;
dest_buf_u32[x + 10] = color32;
dest_buf_u32[x + 11] = color32;
dest_buf_u32[x + 12] = color32;
dest_buf_u32[x + 13] = color32;
dest_buf_u32[x + 14] = color32;
dest_buf_u32[x + 15] = color32;
}
for (; x < w; x ++) {
dest_buf_u32[x] = color32;
}
dest_buf_u32 = drawbuf_next_row(dest_buf_u32, dest_stride);
}
}
}
}
/*Opacity only*/
else if (mask == NULL && opa < LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_COLOR_BLEND_TO_RGB888_WITH_OPA(dsc, dest_px_size)) {
uint32_t color32 = lv_color_to_u32(dsc->color);
uint8_t *dest_buf = dsc->dest_buf;
w *= dest_px_size;
for (y = 0; y < h; y++) {
for (x = 0; x < w; x += dest_px_size) {
lv_color_24_24_mix((const uint8_t *)&color32, &dest_buf[x], opa);
}
dest_buf = drawbuf_next_row(dest_buf, dest_stride);
}
}
}
/*Masked with full opacity*/
else if (mask && opa >= LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_COLOR_BLEND_TO_RGB888_WITH_MASK(dsc, dest_px_size)) {
uint32_t color32 = lv_color_to_u32(dsc->color);
uint8_t *dest_buf = dsc->dest_buf;
w *= dest_px_size;
for (y = 0; y < h; y++) {
uint32_t mask_x;
for (x = 0, mask_x = 0; x < w; x += dest_px_size, mask_x++) {
lv_color_24_24_mix((const uint8_t *)&color32, &dest_buf[x], mask[mask_x]);
}
dest_buf += dest_stride;
mask += mask_stride;
}
}
}
/*Masked with opacity*/
else {
if (LV_RESULT_INVALID == LV_DRAW_SW_COLOR_BLEND_TO_RGB888_MIX_MASK_OPA(dsc, dest_px_size)) {
uint32_t color32 = lv_color_to_u32(dsc->color);
uint8_t *dest_buf = dsc->dest_buf;
w *= dest_px_size;
for (y = 0; y < h; y++) {
uint32_t mask_x;
for (x = 0, mask_x = 0; x < w; x += dest_px_size, mask_x++) {
lv_color_24_24_mix((const uint8_t *) &color32, &dest_buf[x], LV_OPA_MIX2(opa, mask[mask_x]));
}
dest_buf += dest_stride;
mask += mask_stride;
}
}
}
}
void LV_ATTRIBUTE_FAST_MEM lv_draw_sw_blend_image_to_rgb888(_lv_draw_sw_blend_image_dsc_t *dsc, uint32_t dest_px_size)
{
switch (dsc->src_color_format) {
case LV_COLOR_FORMAT_RGB565:
rgb565_image_blend(dsc, dest_px_size);
break;
case LV_COLOR_FORMAT_RGB888:
rgb888_image_blend(dsc, dest_px_size, 3);
break;
case LV_COLOR_FORMAT_XRGB8888:
rgb888_image_blend(dsc, dest_px_size, 4);
break;
case LV_COLOR_FORMAT_ARGB8888:
argb8888_image_blend(dsc, dest_px_size);
break;
case LV_COLOR_FORMAT_L8:
l8_image_blend(dsc, dest_px_size);
break;
case LV_COLOR_FORMAT_AL88:
al88_image_blend(dsc, dest_px_size);
break;
case LV_COLOR_FORMAT_I1:
i1_image_blend(dsc, dest_px_size);
break;
default:
LV_LOG_WARN("Not supported source color format");
break;
}
}
/**********************
* STATIC FUNCTIONS
**********************/
static void LV_ATTRIBUTE_FAST_MEM i1_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc, uint32_t dest_px_size)
{
int32_t w = dsc->dest_w;
int32_t h = dsc->dest_h;
lv_opa_t opa = dsc->opa;
uint8_t *dest_buf_u8 = dsc->dest_buf;
int32_t dest_stride = dsc->dest_stride;
const uint8_t *src_buf_i1 = dsc->src_buf;
int32_t src_stride = dsc->src_stride;
const lv_opa_t *mask_buf = dsc->mask_buf;
int32_t mask_stride = dsc->mask_stride;
int32_t dest_x;
int32_t src_x;
int32_t y;
if (dsc->blend_mode == LV_BLEND_MODE_NORMAL) {
if (mask_buf == NULL && opa >= LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_I1_BLEND_NORMAL_TO_888(dsc)) {
for (y = 0; y < h; y++) {
for (dest_x = 0, src_x = 0; src_x < w; dest_x += dest_px_size, src_x++) {
uint8_t chan_val = get_bit(src_buf_i1, src_x) * 255;
dest_buf_u8[dest_x + 2] = chan_val;
dest_buf_u8[dest_x + 1] = chan_val;
dest_buf_u8[dest_x + 0] = chan_val;
}
dest_buf_u8 = drawbuf_next_row(dest_buf_u8, dest_stride);
src_buf_i1 = drawbuf_next_row(src_buf_i1, src_stride);
}
}
} else if (mask_buf == NULL && opa < LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_I1_BLEND_NORMAL_TO_888_WITH_OPA(dsc)) {
for (y = 0; y < h; y++) {
for (dest_x = 0, src_x = 0; src_x < w; dest_x += dest_px_size, src_x++) {
uint8_t chan_val = get_bit(src_buf_i1, src_x) * 255;
lv_color_8_24_mix(chan_val, &dest_buf_u8[dest_x], opa);
}
dest_buf_u8 = drawbuf_next_row(dest_buf_u8, dest_stride);
src_buf_i1 = drawbuf_next_row(src_buf_i1, src_stride);
}
}
} else if (mask_buf && opa >= LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_I1_BLEND_NORMAL_TO_888_WITH_MASK(dsc)) {
for (y = 0; y < h; y++) {
for (dest_x = 0, src_x = 0; src_x < w; dest_x += dest_px_size, src_x++) {
uint8_t chan_val = get_bit(src_buf_i1, src_x) * 255;
lv_color_8_24_mix(chan_val, &dest_buf_u8[dest_x], mask_buf[src_x]);
}
dest_buf_u8 = drawbuf_next_row(dest_buf_u8, dest_stride);
src_buf_i1 = drawbuf_next_row(src_buf_i1, src_stride);
mask_buf += mask_stride;
}
}
} else if (mask_buf && opa < LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_I1_BLEND_NORMAL_TO_888_MIX_MASK_OPA(dsc)) {
for (y = 0; y < h; y++) {
for (dest_x = 0, src_x = 0; src_x < w; dest_x += dest_px_size, src_x++) {
uint8_t chan_val = get_bit(src_buf_i1, src_x) * 255;
lv_color_8_24_mix(chan_val, &dest_buf_u8[dest_x], LV_OPA_MIX2(opa, mask_buf[src_x]));
}
dest_buf_u8 = drawbuf_next_row(dest_buf_u8, dest_stride);
src_buf_i1 = drawbuf_next_row(src_buf_i1, src_stride);
mask_buf += mask_stride;
}
}
}
} else {
for (y = 0; y < h; y++) {
for (dest_x = 0, src_x = 0; src_x < w; dest_x += dest_px_size, src_x++) {
lv_color32_t src_argb;
src_argb.red = get_bit(src_buf_i1, src_x) * 255;
src_argb.green = src_argb.red;
src_argb.blue = src_argb.red;
if (mask_buf == NULL) {
src_argb.alpha = opa;
} else {
src_argb.alpha = LV_OPA_MIX2(mask_buf[src_x], opa);
}
blend_non_normal_pixel(&dest_buf_u8[dest_x], src_argb, dsc->blend_mode);
}
if (mask_buf) {
mask_buf += mask_stride;
}
dest_buf_u8 = drawbuf_next_row(dest_buf_u8, dest_stride);
src_buf_i1 = drawbuf_next_row(src_buf_i1, src_stride);
}
}
}
static void LV_ATTRIBUTE_FAST_MEM al88_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc, uint32_t dest_px_size)
{
int32_t w = dsc->dest_w;
int32_t h = dsc->dest_h;
lv_opa_t opa = dsc->opa;
uint8_t *dest_buf_u8 = dsc->dest_buf;
int32_t dest_stride = dsc->dest_stride;
const lv_color16a_t *src_buf_al88 = dsc->src_buf;
int32_t src_stride = dsc->src_stride;
const lv_opa_t *mask_buf = dsc->mask_buf;
int32_t mask_stride = dsc->mask_stride;
int32_t dest_x;
int32_t src_x;
int32_t y;
if (dsc->blend_mode == LV_BLEND_MODE_NORMAL) {
if (mask_buf == NULL && opa >= LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_L8_BLEND_NORMAL_TO_RGB888(dsc, dest_px_size)) {
for (y = 0; y < h; y++) {
for (dest_x = 0, src_x = 0; src_x < w; dest_x += dest_px_size, src_x++) {
lv_color_8_24_mix(src_buf_al88[src_x].lumi, &dest_buf_u8[dest_x], src_buf_al88[src_x].alpha);
}
dest_buf_u8 += dest_stride;
src_buf_al88 = drawbuf_next_row(src_buf_al88, src_stride);
}
}
} else if (mask_buf == NULL && opa < LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_L8_BLEND_NORMAL_TO_RGB888_WITH_OPA(dsc, dest_px_size)) {
for (y = 0; y < h; y++) {
for (dest_x = 0, src_x = 0; src_x < w; dest_x += dest_px_size, src_x++) {
lv_color_8_24_mix(src_buf_al88[src_x].lumi, &dest_buf_u8[dest_x], LV_OPA_MIX2(src_buf_al88[src_x].alpha, opa));
}
dest_buf_u8 += dest_stride;
src_buf_al88 = drawbuf_next_row(src_buf_al88, src_stride);
}
}
} else if (mask_buf && opa >= LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_L8_BLEND_NORMAL_TO_RGB888_WITH_MASK(dsc, dest_px_size)) {
for (y = 0; y < h; y++) {
for (dest_x = 0, src_x = 0; src_x < w; dest_x += dest_px_size, src_x++) {
lv_color_8_24_mix(src_buf_al88[src_x].lumi, &dest_buf_u8[dest_x], LV_OPA_MIX2(src_buf_al88[src_x].alpha,
mask_buf[src_x]));
}
dest_buf_u8 += dest_stride;
src_buf_al88 = drawbuf_next_row(src_buf_al88, src_stride);
mask_buf += mask_stride;
}
}
} else if (mask_buf && opa < LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_L8_BLEND_NORMAL_TO_RGB888_MIX_MASK_OPA(dsc, dest_px_size)) {
for (y = 0; y < h; y++) {
for (dest_x = 0, src_x = 0; src_x < w; dest_x += dest_px_size, src_x++) {
lv_color_8_24_mix(src_buf_al88[src_x].lumi, &dest_buf_u8[dest_x], LV_OPA_MIX3(src_buf_al88[src_x].alpha,
mask_buf[src_x], opa));
}
dest_buf_u8 += dest_stride;
src_buf_al88 = drawbuf_next_row(src_buf_al88, src_stride);
mask_buf += mask_stride;
}
}
}
} else {
for (y = 0; y < h; y++) {
for (dest_x = 0, src_x = 0; src_x < w; dest_x += dest_px_size, src_x++) {
lv_color32_t src_argb;
src_argb.red = src_argb.green = src_argb.blue = src_buf_al88[src_x].lumi;
if (mask_buf == NULL) {
src_argb.alpha = LV_OPA_MIX2(src_buf_al88[src_x].alpha, opa);
} else {
src_argb.alpha = LV_OPA_MIX3(src_buf_al88[src_x].alpha, mask_buf[dest_x], opa);
}
blend_non_normal_pixel(&dest_buf_u8[dest_x], src_argb, dsc->blend_mode);
}
if (mask_buf) {
mask_buf += mask_stride;
}
dest_buf_u8 += dest_stride;
src_buf_al88 = drawbuf_next_row(src_buf_al88, src_stride);
}
}
}
static void LV_ATTRIBUTE_FAST_MEM l8_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc, uint32_t dest_px_size)
{
int32_t w = dsc->dest_w;
int32_t h = dsc->dest_h;
lv_opa_t opa = dsc->opa;
uint8_t *dest_buf_u8 = dsc->dest_buf;
int32_t dest_stride = dsc->dest_stride;
const uint8_t *src_buf_l8 = dsc->src_buf;
int32_t src_stride = dsc->src_stride;
const lv_opa_t *mask_buf = dsc->mask_buf;
int32_t mask_stride = dsc->mask_stride;
int32_t dest_x;
int32_t src_x;
int32_t y;
if (dsc->blend_mode == LV_BLEND_MODE_NORMAL) {
if (mask_buf == NULL && opa >= LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_L8_BLEND_NORMAL_TO_RGB888(dsc, dest_px_size)) {
for (y = 0; y < h; y++) {
for (dest_x = 0, src_x = 0; src_x < w; dest_x += dest_px_size, src_x++) {
dest_buf_u8[dest_x + 2] = src_buf_l8[src_x];
dest_buf_u8[dest_x + 1] = src_buf_l8[src_x];
dest_buf_u8[dest_x + 0] = src_buf_l8[src_x];
}
dest_buf_u8 += dest_stride;
src_buf_l8 = drawbuf_next_row(src_buf_l8, src_stride);
}
}
} else if (mask_buf == NULL && opa < LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_L8_BLEND_NORMAL_TO_RGB888_WITH_OPA(dsc, dest_px_size)) {
for (y = 0; y < h; y++) {
for (dest_x = 0, src_x = 0; src_x < w; dest_x += dest_px_size, src_x++) {
lv_color_8_24_mix(src_buf_l8[src_x], &dest_buf_u8[dest_x], opa);
}
dest_buf_u8 += dest_stride;
src_buf_l8 = drawbuf_next_row(src_buf_l8, src_stride);
}
}
} else if (mask_buf && opa >= LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_L8_BLEND_NORMAL_TO_RGB888_WITH_MASK(dsc, dest_px_size)) {
for (y = 0; y < h; y++) {
for (dest_x = 0, src_x = 0; src_x < w; dest_x += dest_px_size, src_x++) {
lv_color_8_24_mix(src_buf_l8[src_x], &dest_buf_u8[dest_x], mask_buf[src_x]);
}
dest_buf_u8 += dest_stride;
src_buf_l8 = drawbuf_next_row(src_buf_l8, src_stride);
mask_buf += mask_stride;
}
}
} else if (mask_buf && opa < LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_L8_BLEND_NORMAL_TO_RGB888_MIX_MASK_OPA(dsc, dest_px_size)) {
for (y = 0; y < h; y++) {
for (dest_x = 0, src_x = 0; src_x < w; dest_x += dest_px_size, src_x++) {
lv_color_8_24_mix(src_buf_l8[src_x], &dest_buf_u8[dest_x], LV_OPA_MIX2(opa, mask_buf[src_x]));
}
dest_buf_u8 += dest_stride;
src_buf_l8 = drawbuf_next_row(src_buf_l8, src_stride);
mask_buf += mask_stride;
}
}
}
} else {
lv_color32_t src_argb;
for (y = 0; y < h; y++) {
for (dest_x = 0, src_x = 0; src_x < w; dest_x += dest_px_size, src_x++) {
src_argb.red = src_buf_l8[src_x];
src_argb.green = src_buf_l8[src_x];
src_argb.blue = src_buf_l8[src_x];
if (mask_buf == NULL) {
src_argb.alpha = opa;
} else {
src_argb.alpha = LV_OPA_MIX2(mask_buf[dest_x], opa);
}
blend_non_normal_pixel(&dest_buf_u8[dest_x], src_argb, dsc->blend_mode);
}
if (mask_buf) {
mask_buf += mask_stride;
}
dest_buf_u8 += dest_stride;
src_buf_l8 = drawbuf_next_row(src_buf_l8, src_stride);
}
}
}
static void LV_ATTRIBUTE_FAST_MEM rgb565_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc, uint32_t dest_px_size)
{
int32_t w = dsc->dest_w;
int32_t h = dsc->dest_h;
lv_opa_t opa = dsc->opa;
uint8_t *dest_buf_u8 = dsc->dest_buf;
int32_t dest_stride = dsc->dest_stride;
const lv_color16_t *src_buf_c16 = (const lv_color16_t *) dsc->src_buf;
int32_t src_stride = dsc->src_stride;
const lv_opa_t *mask_buf = dsc->mask_buf;
int32_t mask_stride = dsc->mask_stride;
int32_t src_x;
int32_t dest_x;
int32_t y;
if (dsc->blend_mode == LV_BLEND_MODE_NORMAL) {
if (mask_buf == NULL && opa >= LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_RGB888(dsc, dest_px_size)) {
for (y = 0; y < h; y++) {
for (src_x = 0, dest_x = 0; src_x < w; dest_x += dest_px_size, src_x++) {
dest_buf_u8[dest_x + 2] = (src_buf_c16[src_x].red * 2106) >> 8; /*To make it rounded*/
dest_buf_u8[dest_x + 1] = (src_buf_c16[src_x].green * 1037) >> 8;
dest_buf_u8[dest_x + 0] = (src_buf_c16[src_x].blue * 2106) >> 8;
}
dest_buf_u8 += dest_stride;
src_buf_c16 = drawbuf_next_row(src_buf_c16, src_stride);
}
}
} else if (mask_buf == NULL && opa < LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_RGB888_WITH_OPA(dsc, dest_px_size)) {
uint8_t res[3];
for (y = 0; y < h; y++) {
for (src_x = 0, dest_x = 0; src_x < w; dest_x += dest_px_size, src_x++) {
res[2] = (src_buf_c16[src_x].red * 2106) >> 8; /*To make it rounded*/
res[1] = (src_buf_c16[src_x].green * 1037) >> 8;
res[0] = (src_buf_c16[src_x].blue * 2106) >> 8;
lv_color_24_24_mix(res, &dest_buf_u8[dest_x], opa);
}
dest_buf_u8 += dest_stride;
src_buf_c16 = drawbuf_next_row(src_buf_c16, src_stride);
}
}
} else if (mask_buf && opa >= LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_RGB888_WITH_MASK(dsc, dest_px_size)) {
uint8_t res[3];
for (y = 0; y < h; y++) {
for (src_x = 0, dest_x = 0; src_x < w; dest_x += dest_px_size, src_x++) {
res[2] = (src_buf_c16[src_x].red * 2106) >> 8; /*To make it rounded*/
res[1] = (src_buf_c16[src_x].green * 1037) >> 8;
res[0] = (src_buf_c16[src_x].blue * 2106) >> 8;
lv_color_24_24_mix(res, &dest_buf_u8[dest_x], mask_buf[src_x]);
}
dest_buf_u8 += dest_stride;
src_buf_c16 = drawbuf_next_row(src_buf_c16, src_stride);
mask_buf += mask_stride;
}
}
} else {
if (LV_RESULT_INVALID == LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_RGB888_MIX_MASK_OPA(dsc, dest_px_size)) {
uint8_t res[3];
for (y = 0; y < h; y++) {
for (src_x = 0, dest_x = 0; src_x < w; dest_x += dest_px_size, src_x++) {
res[2] = (src_buf_c16[src_x].red * 2106) >> 8; /*To make it rounded*/
res[1] = (src_buf_c16[src_x].green * 1037) >> 8;
res[0] = (src_buf_c16[src_x].blue * 2106) >> 8;
lv_color_24_24_mix(res, &dest_buf_u8[dest_x], LV_OPA_MIX2(opa, mask_buf[src_x]));
}
dest_buf_u8 += dest_stride;
src_buf_c16 = drawbuf_next_row(src_buf_c16, src_stride);
mask_buf += mask_stride;
}
}
}
} else {
lv_color32_t src_argb;
for (y = 0; y < h; y++) {
for (src_x = 0, dest_x = 0; src_x < w; src_x++, dest_x += dest_px_size) {
src_argb.red = (src_buf_c16[src_x].red * 2106) >> 8;
src_argb.green = (src_buf_c16[src_x].green * 1037) >> 8;
src_argb.blue = (src_buf_c16[src_x].blue * 2106) >> 8;
if (mask_buf == NULL) {
src_argb.alpha = opa;
} else {
src_argb.alpha = LV_OPA_MIX2(mask_buf[src_x], opa);
}
blend_non_normal_pixel(&dest_buf_u8[dest_x], src_argb, dsc->blend_mode);
}
if (mask_buf) {
mask_buf += mask_stride;
}
dest_buf_u8 += dest_stride;
src_buf_c16 = drawbuf_next_row(src_buf_c16, src_stride);
}
}
}
static void LV_ATTRIBUTE_FAST_MEM rgb888_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc, const uint8_t dest_px_size,
uint32_t src_px_size)
{
int32_t w = dsc->dest_w * dest_px_size;
int32_t h = dsc->dest_h;
lv_opa_t opa = dsc->opa;
uint8_t *dest_buf = dsc->dest_buf;
int32_t dest_stride = dsc->dest_stride;
const uint8_t *src_buf = dsc->src_buf;
int32_t src_stride = dsc->src_stride;
const lv_opa_t *mask_buf = dsc->mask_buf;
int32_t mask_stride = dsc->mask_stride;
int32_t dest_x;
int32_t src_x;
int32_t y;
if (dsc->blend_mode == LV_BLEND_MODE_NORMAL) {
/*Special case*/
if (mask_buf == NULL && opa >= LV_OPA_MAX) {
if (dsc->use_asm && dest_px_size == 3 && src_px_size == 3) {
LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_RGB888(dsc, src_px_size, dest_px_size);
} else {
if (src_px_size == dest_px_size) {
for (y = 0; y < h; y++) {
lv_memcpy(dest_buf, src_buf, w);
dest_buf += dest_stride;
src_buf += src_stride;
}
} else {
for (y = 0; y < h; y++) {
for (dest_x = 0, src_x = 0; dest_x < w; dest_x += dest_px_size, src_x += src_px_size) {
dest_buf[dest_x + 0] = src_buf[src_x + 0];
dest_buf[dest_x + 1] = src_buf[src_x + 1];
dest_buf[dest_x + 2] = src_buf[src_x + 2];
}
dest_buf += dest_stride;
src_buf += src_stride;
}
}
}
}
if (mask_buf == NULL && opa < LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_RGB888_WITH_OPA(dsc, dest_px_size, src_px_size)) {
for (y = 0; y < h; y++) {
for (dest_x = 0, src_x = 0; dest_x < w; dest_x += dest_px_size, src_x += src_px_size) {
lv_color_24_24_mix(&src_buf[src_x], &dest_buf[dest_x], opa);
}
dest_buf += dest_stride;
src_buf += src_stride;
}
}
}
if (mask_buf && opa >= LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_RGB888_WITH_MASK(dsc, dest_px_size, src_px_size)) {
uint32_t mask_x;
for (y = 0; y < h; y++) {
for (mask_x = 0, dest_x = 0, src_x = 0; dest_x < w; mask_x++, dest_x += dest_px_size, src_x += src_px_size) {
lv_color_24_24_mix(&src_buf[src_x], &dest_buf[dest_x], mask_buf[mask_x]);
}
dest_buf += dest_stride;
src_buf += src_stride;
mask_buf += mask_stride;
}
}
}
if (mask_buf && opa < LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_RGB888_MIX_MASK_OPA(dsc, dest_px_size, src_px_size)) {
uint32_t mask_x;
for (y = 0; y < h; y++) {
for (mask_x = 0, dest_x = 0, src_x = 0; dest_x < w; mask_x++, dest_x += dest_px_size, src_x += src_px_size) {
lv_color_24_24_mix(&src_buf[src_x], &dest_buf[dest_x], LV_OPA_MIX2(opa, mask_buf[mask_x]));
}
dest_buf += dest_stride;
src_buf += src_stride;
mask_buf += mask_stride;
}
}
}
} else {
lv_color32_t src_argb;
for (y = 0; y < h; y++) {
for (dest_x = 0, src_x = 0; dest_x < w; dest_x += dest_px_size, src_x += src_px_size) {
src_argb.red = src_buf[src_x + 2];
src_argb.green = src_buf[src_x + 1];
src_argb.blue = src_buf[src_x + 0];
if (mask_buf == NULL) {
src_argb.alpha = opa;
} else {
src_argb.alpha = LV_OPA_MIX2(mask_buf[dest_x], opa);
}
blend_non_normal_pixel(&dest_buf[dest_x], src_argb, dsc->blend_mode);
}
if (mask_buf) {
mask_buf += mask_stride;
}
dest_buf += dest_stride;
src_buf += src_stride;
}
}
}
static void LV_ATTRIBUTE_FAST_MEM argb8888_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc, uint32_t dest_px_size)
{
int32_t w = dsc->dest_w;
int32_t h = dsc->dest_h;
lv_opa_t opa = dsc->opa;
uint8_t *dest_buf = dsc->dest_buf;
int32_t dest_stride = dsc->dest_stride;
const lv_color32_t *src_buf_c32 = dsc->src_buf;
int32_t src_stride = dsc->src_stride;
const lv_opa_t *mask_buf = dsc->mask_buf;
int32_t mask_stride = dsc->mask_stride;
int32_t dest_x;
int32_t src_x;
int32_t y;
if (dsc->blend_mode == LV_BLEND_MODE_NORMAL) {
if (mask_buf == NULL && opa >= LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_RGB888(dsc, dest_px_size)) {
for (y = 0; y < h; y++) {
for (dest_x = 0, src_x = 0; src_x < w; dest_x += dest_px_size, src_x++) {
lv_color_24_24_mix((const uint8_t *)&src_buf_c32[src_x], &dest_buf[dest_x], src_buf_c32[src_x].alpha);
}
dest_buf += dest_stride;
src_buf_c32 = drawbuf_next_row(src_buf_c32, src_stride);
}
}
} else if (mask_buf == NULL && opa < LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_RGB888_WITH_OPA(dsc, dest_px_size)) {
for (y = 0; y < h; y++) {
for (dest_x = 0, src_x = 0; src_x < w; dest_x += dest_px_size, src_x++) {
lv_color_24_24_mix((const uint8_t *)&src_buf_c32[src_x], &dest_buf[dest_x], LV_OPA_MIX2(src_buf_c32[src_x].alpha, opa));
}
dest_buf += dest_stride;
src_buf_c32 = drawbuf_next_row(src_buf_c32, src_stride);
}
}
} else if (mask_buf && opa >= LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_RGB888_WITH_MASK(dsc, dest_px_size)) {
for (y = 0; y < h; y++) {
for (dest_x = 0, src_x = 0; src_x < w; dest_x += dest_px_size, src_x++) {
lv_color_24_24_mix((const uint8_t *)&src_buf_c32[src_x], &dest_buf[dest_x],
LV_OPA_MIX2(src_buf_c32[src_x].alpha, mask_buf[src_x]));
}
dest_buf += dest_stride;
src_buf_c32 = drawbuf_next_row(src_buf_c32, src_stride);
mask_buf += mask_stride;
}
}
} else if (mask_buf && opa < LV_OPA_MAX) {
if (LV_RESULT_INVALID == LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_RGB888_MIX_MASK_OPA(dsc, dest_px_size)) {
for (y = 0; y < h; y++) {
for (dest_x = 0, src_x = 0; src_x < w; dest_x += dest_px_size, src_x++) {
lv_color_24_24_mix((const uint8_t *)&src_buf_c32[src_x], &dest_buf[dest_x],
LV_OPA_MIX3(src_buf_c32[src_x].alpha, mask_buf[src_x], opa));
}
dest_buf += dest_stride;
src_buf_c32 = drawbuf_next_row(src_buf_c32, src_stride);
mask_buf += mask_stride;
}
}
}
} else {
lv_color32_t src_argb;
for (y = 0; y < h; y++) {
for (dest_x = 0, src_x = 0; src_x < w; dest_x += dest_px_size, src_x ++) {
src_argb = src_buf_c32[src_x];
if (mask_buf == NULL) {
src_argb.alpha = LV_OPA_MIX2(src_argb.alpha, opa);
} else {
src_argb.alpha = LV_OPA_MIX3(src_argb.alpha, mask_buf[dest_x], opa);
}
blend_non_normal_pixel(&dest_buf[dest_x], src_argb, dsc->blend_mode);
}
if (mask_buf) {
mask_buf += mask_stride;
}
dest_buf += dest_stride;
src_buf_c32 = drawbuf_next_row(src_buf_c32, src_stride);
}
}
}
static inline void LV_ATTRIBUTE_FAST_MEM blend_non_normal_pixel(uint8_t *dest, lv_color32_t src, lv_blend_mode_t mode)
{
uint8_t res[3] = {0, 0, 0};
switch (mode) {
case LV_BLEND_MODE_ADDITIVE:
res[0] = LV_MIN(dest[0] + src.blue, 255);
res[1] = LV_MIN(dest[1] + src.green, 255);
res[2] = LV_MIN(dest[2] + src.red, 255);
break;
case LV_BLEND_MODE_SUBTRACTIVE:
res[0] = LV_MAX(dest[0] - src.blue, 0);
res[1] = LV_MAX(dest[1] - src.green, 0);
res[2] = LV_MAX(dest[2] - src.red, 0);
break;
case LV_BLEND_MODE_MULTIPLY:
res[0] = (dest[0] * src.blue) >> 8;
res[1] = (dest[1] * src.green) >> 8;
res[2] = (dest[2] * src.red) >> 8;
break;
default:
LV_LOG_WARN("Not supported blend mode: %d", mode);
return;
}
lv_color_24_24_mix(res, dest, src.alpha);
}
static inline void LV_ATTRIBUTE_FAST_MEM lv_color_8_24_mix(const uint8_t src, uint8_t *dest, uint8_t mix)
{
if (mix == 0) {
return;
}
if (mix >= LV_OPA_MAX) {
dest[0] = src;
dest[1] = src;
dest[2] = src;
} else {
lv_opa_t mix_inv = 255 - mix;
dest[0] = (uint32_t)((uint32_t)src * mix + dest[0] * mix_inv) >> 8;
dest[1] = (uint32_t)((uint32_t)src * mix + dest[1] * mix_inv) >> 8;
dest[2] = (uint32_t)((uint32_t)src * mix + dest[2] * mix_inv) >> 8;
}
}
static inline void LV_ATTRIBUTE_FAST_MEM lv_color_24_24_mix(const uint8_t *src, uint8_t *dest, uint8_t mix)
{
if (mix == 0) {
return;
}
if (mix >= LV_OPA_MAX) {
dest[0] = src[0];
dest[1] = src[1];
dest[2] = src[2];
} else {
lv_opa_t mix_inv = 255 - mix;
dest[0] = (uint32_t)((uint32_t)src[0] * mix + dest[0] * mix_inv) >> 8;
dest[1] = (uint32_t)((uint32_t)src[1] * mix + dest[1] * mix_inv) >> 8;
dest[2] = (uint32_t)((uint32_t)src[2] * mix + dest[2] * mix_inv) >> 8;
}
}
static inline uint8_t LV_ATTRIBUTE_FAST_MEM get_bit(const uint8_t *buf, int32_t bit_idx)
{
return (buf[bit_idx / 8] >> (7 - (bit_idx % 8))) & 1;
}
static inline void *LV_ATTRIBUTE_FAST_MEM drawbuf_next_row(const void *buf, uint32_t stride)
{
return (void *)((uint8_t *)buf + stride);
}

View File

@@ -0,0 +1,187 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*
* This file is derived from the LVGL project.
* See https://github.com/lvgl/lvgl for details.
*/
/**
* @file lv_string.c
*/
/*********************
* INCLUDES
*********************/
//#include "../../lv_conf_internal.h"
#if LV_USE_STDLIB_STRING == LV_STDLIB_BUILTIN
#include "lv_assert.h"
#include "lv_log.h"
#include "lv_math.h"
#include "lv_string.h"
/*********************
* DEFINES
*********************/
#ifdef LV_ARCH_64
#define MEM_UNIT uint64_t
#define ALIGN_MASK 0x7
#else
#define MEM_UNIT uint32_t
#define ALIGN_MASK 0x3
#endif
#define LV_ATTRIBUTE_FAST_MEM
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC PROTOTYPES
**********************/
/**********************
* STATIC VARIABLES
**********************/
/**********************
* MACROS
**********************/
#if LV_USE_LOG && LV_LOG_TRACE_MEM
#define LV_TRACE_MEM(...) LV_LOG_TRACE(__VA_ARGS__)
#else
#define LV_TRACE_MEM(...)
#endif
#define _COPY(d, s) *d = *s; d++; s++;
#define _SET(d, v) *d = v; d++;
#define _REPEAT8(expr) expr expr expr expr expr expr expr expr
/**********************
* GLOBAL FUNCTIONS
**********************/
void *LV_ATTRIBUTE_FAST_MEM lv_memcpy(void *dst, const void *src, size_t len)
{
uint8_t *d8 = dst;
const uint8_t *s8 = src;
/*Simplify for small memories*/
if (len < 16) {
while (len) {
*d8 = *s8;
d8++;
s8++;
len--;
}
return dst;
}
lv_uintptr_t d_align = (lv_uintptr_t)d8 & ALIGN_MASK;
lv_uintptr_t s_align = (lv_uintptr_t)s8 & ALIGN_MASK;
/*Byte copy for unaligned memories*/
if (s_align != d_align) {
while (len > 32) {
_REPEAT8(_COPY(d8, s8));
_REPEAT8(_COPY(d8, s8));
_REPEAT8(_COPY(d8, s8));
_REPEAT8(_COPY(d8, s8));
len -= 32;
}
while (len) {
_COPY(d8, s8)
len--;
}
return dst;
}
/*Make the memories aligned*/
if (d_align) {
d_align = ALIGN_MASK + 1 - d_align;
while (d_align && len) {
_COPY(d8, s8);
d_align--;
len--;
}
}
uint32_t *d32 = (uint32_t *)d8;
const uint32_t *s32 = (uint32_t *)s8;
while (len > 32) {
_REPEAT8(_COPY(d32, s32))
len -= 32;
}
d8 = (uint8_t *)d32;
s8 = (const uint8_t *)s32;
while (len) {
_COPY(d8, s8)
len--;
}
return dst;
}
void LV_ATTRIBUTE_FAST_MEM lv_memset(void *dst, uint8_t v, size_t len)
{
uint8_t *d8 = (uint8_t *)dst;
uintptr_t d_align = (lv_uintptr_t) d8 & ALIGN_MASK;
/*Make the address aligned*/
if (d_align) {
d_align = ALIGN_MASK + 1 - d_align;
while (d_align && len) {
_SET(d8, v);
len--;
d_align--;
}
}
uint32_t v32 = (uint32_t)v + ((uint32_t)v << 8) + ((uint32_t)v << 16) + ((uint32_t)v << 24);
uint32_t *d32 = (uint32_t *)d8;
while (len > 32) {
_REPEAT8(_SET(d32, v32));
len -= 32;
}
d8 = (uint8_t *)d32;
while (len) {
_SET(d8, v);
len--;
}
}
void *LV_ATTRIBUTE_FAST_MEM lv_memmove(void *dst, const void *src, size_t len)
{
if (dst < src || (char *)dst > ((char *)src + len)) {
return lv_memcpy(dst, src, len);
}
if (dst > src) {
char *tmp = (char *)dst + len - 1;
char *s = (char *)src + len - 1;
while (len--) {
*tmp-- = *s--;
}
} else {
char *tmp = (char *)dst;
char *s = (char *)src;
while (len--) {
*tmp++ = *s++;
}
}
return dst;
}
/**********************
* STATIC FUNCTIONS
**********************/
#endif /*LV_STDLIB_BUILTIN*/

View File

@@ -0,0 +1,75 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "esp_err.h"
#include <stdint.h>
#include "lv_color.h"
#include "lv_draw_sw_blend.h"
#ifdef __cplusplus
extern "C" {
#endif
// ------------------------------------------------- Macros and Types --------------------------------------------------
/**
* @brief Functionality test combinations
*/
typedef struct {
unsigned int min_w; // Minimum width of the test array
unsigned int min_h; // Minimum height of the test array
unsigned int max_w; // Maximum width of the test array
unsigned int max_h; // Maximum height of the test array
unsigned int min_unalign_byte; // Minimum amount of unaligned bytes of the test array
unsigned int max_unalign_byte; // Maximum amount of unaligned bytes of the test array
unsigned int unalign_step; // Increment step in bytes unalignment of the test array
unsigned int dest_stride_step; // Increment step in destination stride of the test array
unsigned int test_combinations_count; // Count of fest combinations
} test_matrix_params_t;
/**
* @brief Functionality test case parameters
*/
typedef struct {
struct {
void *p_asm; // pointer to the working ASM test buf
void *p_ansi; // pointer to the working ANSI test buf
void *p_asm_alloc; // pointer to the beginning of the memory allocated for ASM test buf, used in free()
void *p_ansi_alloc; // pointer to the beginning of the memory allocated for ANSI test buf, used in free()
} buf;
void (*blend_api_func)(_lv_draw_sw_blend_fill_dsc_t *); // pointer to LVGL API function
void (*blend_api_px_func)(_lv_draw_sw_blend_fill_dsc_t *, uint32_t); // pointer to LVGL API function with dest_px_size argument
lv_color_format_t color_format; // LV color format
size_t data_type_size; // Used data type size, eg sizeof()
size_t active_buf_len; // Length of buffer, where the actual data are stored (not including Canary bytes)
size_t total_buf_len; // Total length of buffer (including Canary bytes)
unsigned int dest_w; // Destination buffer width
unsigned int dest_h; // Destination buffer height
unsigned int dest_stride; // Destination buffer stride
unsigned int unalign_byte; // Destination buffer memory unalignment
} func_test_case_params_t;
/**
* @brief Benchmark test case parameters
*/
typedef struct {
unsigned int height; // Test array height
unsigned int width; // Test array width
unsigned int stride; // Test array stride
unsigned int cc_height; // Corner case test array height
unsigned int cc_width; // Corner case test array width
unsigned int benchmark_cycles; // Count of benchmark cycles
void *array_align16; // test array with 16 byte alignment - testing most ideal case
void *array_align1; // test array with 1 byte alignment - testing worst case
void (*blend_api_func)(_lv_draw_sw_blend_fill_dsc_t *); // pointer to LVGL API function
void (*blend_api_px_func)(_lv_draw_sw_blend_fill_dsc_t *, uint32_t); // pointer to LVGL API function with dest_px_size argument
} bench_test_case_params_t;
#ifdef __cplusplus
} /*extern "C"*/
#endif

View File

@@ -0,0 +1,116 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "esp_err.h"
#include <stdint.h>
#include "lv_color.h"
#include "lv_draw_sw_blend.h"
#ifdef __cplusplus
extern "C" {
#endif
// ------------------------------------------------- Macros and Types --------------------------------------------------
/**
* @brief Type of blend DUT function
*/
typedef enum {
OPERATION_FILL,
OPERATION_FILL_WITH_OPA,
} blend_operation_t;
/**
* @brief Canary pixels amount depending on data type
* @note
* - We should use at least 16 bytes of memory for canary pixels because of esp32s3 TIE 16-bytes wide Q registers
* - Canary pixels are multiplied by sizeof(used_data_type) to get the memory length occupied by the canary pixels
* - The memory occupied by canary pixels should be in 16-byte multiples, to achieve 16-byte memory alignment in functionality test
* - For example, ideally, for RGB565 we would need 8 canary pixels -> 8 * sizeof(uint16_t) = 16
*/
typedef enum {
CANARY_PIXELS_ARGB8888 = 4, /*!< Canary pixels: 4 * sizeof(uint32_t) = 16 */
CANARY_PIXELS_RGB565 = 8, /*!< Canary pixels: 8 * sizeof(uint16_t) = 16 */
CANARY_PIXELS_RGB888 = 6, /*!< Canary pixels: 6 * sizeof(uint24_t) = 18 */
} canary_pixels_t;
/**
* @brief Functionality test combinations for LV Image
*/
typedef struct {
unsigned int min_w; /*!< Minimum width of the test array */
unsigned int min_h; /*!< Minimum height of the test array */
unsigned int max_w; /*!< Maximum width of the test array */
unsigned int max_h; /*!< Maximum height of the test array */
unsigned int src_min_unalign_byte; /*!< Minimum amount of unaligned bytes of the source test array */
unsigned int dest_min_unalign_byte; /*!< Minimum amount of unaligned bytes of the destination test array */
unsigned int src_max_unalign_byte; /*!< Maximum amount of unaligned bytes of the source test array */
unsigned int dest_max_unalign_byte; /*!< Maximum amount of unaligned bytes of the destination test array */
unsigned int src_unalign_step; /*!< Increment step in bytes unalignment of the source test array */
unsigned int dest_unalign_step; /*!< Increment step in bytes unalignment of the destination test array */
unsigned int src_stride_step; /*!< Increment step in destination stride of the source test array */
unsigned int dest_stride_step; /*!< Increment step in destination stride of the destination test array */
unsigned int test_combinations_count; /*!< Count of fest combinations */
} test_matrix_lv_image_params_t;
/**
* @brief Functionality test case parameters for LV Image
*/
typedef struct {
struct {
void *p_src; /*!< pointer to the source test buff (common src buffer for both the ANSI and ASM) */
void *p_src_alloc; /*!< pointer to the beginning of the memory allocated for the source ASM test buf, used in free() */
void *p_dest_asm; /*!< pointer to the destination ASM test buf */
void *p_dest_ansi; /*!< pointer to the destination ANSI test buf */
void *p_dest_asm_alloc; /*!< pointer to the beginning of the memory allocated for the destination ASM test buf, used in free() */
void *p_dest_ansi_alloc; /*!< pointer to the beginning of the memory allocated for the destination ANSI test buf, used in free() */
} buf;
void (*blend_api_func)(_lv_draw_sw_blend_image_dsc_t *); /*!< pointer to LVGL API function */
void (*blend_api_func_px_size)(_lv_draw_sw_blend_image_dsc_t *, uint32_t); /*!< pointer to LVGL API function, with additional parameter: pixel size */
lv_color_format_t color_format; /*!< LV color format */
size_t src_data_type_size; /*!< Used data type size in the source buffer, eg sizeof(src_buff[0]) */
size_t dest_data_type_size; /*!< Used data type size in the destination buffer, eg sizeof(dest_buff[0]) */
size_t src_buf_len; /*!< Length of the source buffer, including matrix padding (no Canary pixels are used for source buffer) */
size_t active_dest_buf_len; /*!< Length of the destination buffer, where the actual data are stored, including matrix padding, not including Canary pixels */
size_t total_dest_buf_len; /*!< Total length of the destination buffer (including Canary pixels and matrix padding) */
size_t canary_pixels; /*!< Canary pixels must be adjusted according to the used color type, to achieve aligned memory effect */
size_t memory_alignment_offset; /*!< Memory offset, to correct canary pixels memory shift for RGB888 */
unsigned int dest_w; /*!< Destination buffer width */
unsigned int dest_h; /*!< Destination buffer height */
unsigned int src_stride; /*!< Source buffer stride */
unsigned int dest_stride; /*!< Destination buffer stride */
unsigned int src_unalign_byte; /*!< Source buffer memory unalignment */
unsigned int dest_unalign_byte; /*!< Destination buffer memory unalignment */
blend_operation_t operation_type; /*!< Type of fundamental blend operation */
} func_test_case_lv_image_params_t;
/**
* @brief Benchmark test case parameters for LV Image
*/
typedef struct {
unsigned int height; /*!< Test array height */
unsigned int width; /*!< Test array width */
unsigned int dest_stride; /*!< Destination test array stride */
unsigned int src_stride; /*!< Source test array stride */
unsigned int cc_height; /*!< Corner case test array height */
unsigned int cc_width; /*!< Corner case test array width */
unsigned int benchmark_cycles; /*!< Count of benchmark cycles */
void *src_array_align16; /*!< Source test array with 16 byte alignment - testing most ideal case */
void *src_array_align1; /*!< Source test array with 1 byte alignment - testing worst case */
void *dest_array_align16; /*!< Destination test array with 16 byte alignment - testing most ideal case */
void *dest_array_align1; /*!< Destination test array with 1 byte alignment - testing worst case */
void (*blend_api_func)(_lv_draw_sw_blend_image_dsc_t *); /*!< pointer to LVGL API function */
void (*blend_api_func_px_size)(_lv_draw_sw_blend_image_dsc_t *, uint32_t); /*!< pointer to LVGL API function, with additional parameter: pixel size */
lv_color_format_t color_format; /*!< LV color format */
} bench_test_case_lv_image_params_t;
#ifdef __cplusplus
} /*extern "C"*/
#endif

View File

@@ -0,0 +1,50 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include "unity.h"
#include "unity_test_utils.h"
#include "lv_fill_common.h"
#define TEST_MEMORY_LEAK_THRESHOLD (300)
void app_main(void)
{
// ______ _____ ______ _ _
// | _ \/ ___|| ___ \ | | | |
// | | | |\ `--. | |_/ / | |_ ___ ___ | |_
// | | | | `--. \| __/ | __| / _ \/ __|| __|
// | |/ / /\__/ /| | | |_ | __/\__ \| |_
// |___/ \____/ \_| \__| \___||___/ \__|
printf("______ _____ ______ _ _ \r\n");
printf("| _ \\/ ___|| ___ \\ | | | | \r\n");
printf("| | | |\\ `--. | |_/ / | |_ ___ ___ | |_ \r\n");
printf("| | | | `--. \\| __/ | __| / _ \\/ __|| __|\r\n");
printf("| |/ / /\\__/ /| | | |_ | __/\\__ \\| |_ \r\n");
printf("|___/ \\____/ \\_| \\__| \\___||___/ \\__|\r\n");
UNITY_BEGIN();
unity_run_menu();
UNITY_END();
}
/* setUp runs before every test */
void setUp(void)
{
// Check for memory leaks
unity_utils_set_leak_level(TEST_MEMORY_LEAK_THRESHOLD);
unity_utils_record_free_mem();
}
/* tearDown runs after every test */
void tearDown(void)
{
// Evaluate memory leaks
unity_utils_evaluate_leaks();
}

View File

@@ -0,0 +1,212 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include <malloc.h>
#include <sdkconfig.h>
#include "unity.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h" // for xthal_get_ccount()
#include "lv_fill_common.h"
#include "lv_draw_sw_blend.h"
#include "lv_draw_sw_blend_to_argb8888.h"
#include "lv_draw_sw_blend_to_rgb565.h"
#include "lv_draw_sw_blend_to_rgb888.h"
#define WIDTH 128
#define HEIGHT 128
#define STRIDE WIDTH
#define UNALIGN_BYTES 1
#define BENCHMARK_CYCLES 1000
// ------------------------------------------------- Macros and Types --------------------------------------------------
static const char *TAG_LV_FILL_BENCH = "LV Fill Benchmark";
static const char *asm_ansi_func[] = {"ASM", "ANSI"};
static lv_color_t test_color = {
.blue = 0x56,
.green = 0x34,
.red = 0x12,
};
// ------------------------------------------------ Static function headers --------------------------------------------
/**
* @brief Initialize the benchmark test
*/
static void lv_fill_benchmark_init(bench_test_case_params_t *test_params);
/**
* @brief Run the benchmark test
*/
static float lv_fill_benchmark_run(bench_test_case_params_t *test_params, _lv_draw_sw_blend_fill_dsc_t *dsc);
// ------------------------------------------------ Test cases ---------------------------------------------------------
/*
Benchmark tests
Requires:
- To pass functionality tests first
Purpose:
- Test that an acceleration is achieved by an assembly implementation of LVGL blending API
Procedure:
- Initialize input parameters (test array length, width, allocate array...) of the benchmark test
- Run assembly version of LVGL blending API multiple times (1000-times or so)
- Firstly use an input test parameters for the most ideal case (16-byte aligned array, array width and height divisible by 4 for ARGB8888 color format)
- Then use worst-case input test parameters (1-byte aligned array, array width and height NOT divisible by 4 for ARGB8888 color format)
- Count how many CPU cycles does it take to run a function from the LVGL blending API for each case (ideal and worst case)
- Run ansi version of LVGL blending API multiple times (1000-times or so) and repeat the 2 above steps for the ansi version
- Free test arrays and structures needed for LVGL blending API
*/
// ------------------------------------------------ Test cases stages --------------------------------------------------
TEST_CASE("LV Fill benchmark ARGB8888", "[fill][benchmark][ARGB8888]")
{
uint32_t *dest_array_align16 = (uint32_t *)memalign(16, STRIDE * HEIGHT * sizeof(uint32_t) + UNALIGN_BYTES);
TEST_ASSERT_NOT_EQUAL(NULL, dest_array_align16);
// Apply byte unalignment for the worst-case test scenario
uint32_t *dest_array_align1 = dest_array_align16 + UNALIGN_BYTES;
bench_test_case_params_t test_params = {
.height = HEIGHT,
.width = WIDTH,
.stride = STRIDE * sizeof(uint32_t),
.cc_height = HEIGHT - 1,
.cc_width = WIDTH - 1,
.benchmark_cycles = BENCHMARK_CYCLES,
.array_align16 = (void *)dest_array_align16,
.array_align1 = (void *)dest_array_align1,
.blend_api_func = &lv_draw_sw_blend_color_to_argb8888,
};
ESP_LOGI(TAG_LV_FILL_BENCH, "running test for ARGB8888 color format");
lv_fill_benchmark_init(&test_params);
free(dest_array_align16);
}
TEST_CASE("LV Fill benchmark RGB565", "[fill][benchmark][RGB565]")
{
uint16_t *dest_array_align16 = (uint16_t *)memalign(16, STRIDE * HEIGHT * sizeof(uint16_t) + UNALIGN_BYTES);
TEST_ASSERT_NOT_EQUAL(NULL, dest_array_align16);
// Apply byte unalignment for the worst-case test scenario
uint16_t *dest_array_align1 = dest_array_align16 + UNALIGN_BYTES;
bench_test_case_params_t test_params = {
.height = HEIGHT,
.width = WIDTH,
.stride = STRIDE * sizeof(uint16_t),
.cc_height = HEIGHT - 1,
.cc_width = WIDTH - 1,
.benchmark_cycles = BENCHMARK_CYCLES,
.array_align16 = (void *)dest_array_align16,
.array_align1 = (void *)dest_array_align1,
.blend_api_func = &lv_draw_sw_blend_color_to_rgb565,
};
ESP_LOGI(TAG_LV_FILL_BENCH, "running test for RGB565 color format");
lv_fill_benchmark_init(&test_params);
free(dest_array_align16);
}
TEST_CASE("LV Fill benchmark RGB888", "[fill][benchmark][RGB888]")
{
uint8_t *dest_array_align16 = (uint8_t *)memalign(16, STRIDE * HEIGHT * sizeof(uint8_t) * 3 + UNALIGN_BYTES);
TEST_ASSERT_NOT_EQUAL(NULL, dest_array_align16);
// Apply byte unalignment for the worst-case test scenario
uint8_t *dest_array_align1 = dest_array_align16 + UNALIGN_BYTES;
bench_test_case_params_t test_params = {
.height = HEIGHT,
.width = WIDTH,
.stride = STRIDE * 3,
.cc_height = HEIGHT - 1,
.cc_width = WIDTH - 1,
.benchmark_cycles = BENCHMARK_CYCLES,
.array_align16 = (void *)dest_array_align16,
.array_align1 = (void *)dest_array_align1,
.blend_api_px_func = &lv_draw_sw_blend_color_to_rgb888,
};
ESP_LOGI(TAG_LV_FILL_BENCH, "running test for RGB888 color format");
lv_fill_benchmark_init(&test_params);
free(dest_array_align16);
}
// ------------------------------------------------ Static test functions ----------------------------------------------
static void lv_fill_benchmark_init(bench_test_case_params_t *test_params)
{
// Init structure for LVGL blend API, to call the Assembly API
_lv_draw_sw_blend_fill_dsc_t dsc = {
.dest_buf = test_params->array_align16,
.dest_w = test_params->width,
.dest_h = test_params->height,
.dest_stride = test_params->stride, // stride * sizeof()
.mask_buf = NULL,
.color = test_color,
.opa = LV_OPA_MAX,
.use_asm = true,
};
// Init structure for LVGL blend API, to call the ANSI API
_lv_draw_sw_blend_fill_dsc_t dsc_cc = dsc;
dsc_cc.dest_buf = test_params->array_align1;
dsc_cc.dest_w = test_params->cc_width;
dsc_cc.dest_h = test_params->cc_height;
// Run benchmark 2 times:
// First run using assembly, second run using ANSI
for (int i = 0; i < 2; i++) {
// Run benchmark with the most ideal input parameters
// Dest array is 16 byte aligned, dest_w and dest_h are dividable by 4
float cycles = lv_fill_benchmark_run(test_params, &dsc); // Call Benchmark cycle
float per_sample = cycles / ((float)(dsc.dest_w * dsc.dest_h));
ESP_LOGI(TAG_LV_FILL_BENCH, " %s ideal case: %.3f cycles for %"PRIi32"x%"PRIi32" matrix, %.3f cycles per sample", asm_ansi_func[i], cycles, dsc.dest_w, dsc.dest_h, per_sample);
// Run benchmark with the corner case input parameters
// Dest array is 1 byte aligned, dest_w and dest_h are not dividable by 4
cycles = lv_fill_benchmark_run(test_params, &dsc_cc); // Call Benchmark cycle
per_sample = cycles / ((float)(dsc_cc.dest_w * dsc_cc.dest_h));
ESP_LOGI(TAG_LV_FILL_BENCH, " %s corner case: %.3f cycles for %"PRIi32"x%"PRIi32" matrix, %.3f cycles per sample\n", asm_ansi_func[i], cycles, dsc_cc.dest_w, dsc_cc.dest_h, per_sample);
// change to ANSI
dsc.use_asm = false;
dsc_cc.use_asm = false;
}
}
static float lv_fill_benchmark_run(bench_test_case_params_t *test_params, _lv_draw_sw_blend_fill_dsc_t *dsc)
{
// Call the DUT function for the first time to init the benchmark test
if (test_params->blend_api_func != NULL) {
test_params->blend_api_func(dsc);
} else if (test_params->blend_api_px_func != NULL) {
test_params->blend_api_px_func(dsc, 3);
}
const unsigned int start_b = xthal_get_ccount();
if (test_params->blend_api_func != NULL) {
for (int i = 0; i < test_params->benchmark_cycles; i++) {
test_params->blend_api_func(dsc);
}
} else if (test_params->blend_api_px_func != NULL) {
for (int i = 0; i < test_params->benchmark_cycles; i++) {
test_params->blend_api_px_func(dsc, 3);
}
}
const unsigned int end_b = xthal_get_ccount();
const float total_b = end_b - start_b;
const float cycles = total_b / (test_params->benchmark_cycles);
return cycles;
}

View File

@@ -0,0 +1,383 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include <malloc.h>
#include <inttypes.h>
#include "unity.h"
#include "esp_log.h"
#include "lv_fill_common.h"
#include "lv_draw_sw_blend.h"
#include "lv_draw_sw_blend_to_argb8888.h"
#include "lv_draw_sw_blend_to_rgb565.h"
#include "lv_draw_sw_blend_to_rgb888.h"
// ------------------------------------------------- Defines -----------------------------------------------------------
#define DBG_PRINT_OUTPUT false
#define CANARY_BYTES 4
// ------------------------------------------------- Macros and Types --------------------------------------------------
#define UPDATE_TEST_CASE(test_case_ptr, dest_w, dest_h, dest_stride, unalign_byte) ({ \
(test_case_ptr)->active_buf_len = (size_t)(dest_h * dest_stride); \
(test_case_ptr)->total_buf_len = (size_t)((dest_h * dest_stride) + (CANARY_BYTES * 2)); \
(test_case_ptr)->dest_w = (dest_w); \
(test_case_ptr)->dest_h = (dest_h); \
(test_case_ptr)->dest_stride = (dest_stride); \
(test_case_ptr)->unalign_byte = (unalign_byte); \
})
static const char *TAG_LV_FILL_FUNC = "LV Fill Functionality";
static char test_msg_buf[128];
static lv_color_t test_color = {
.blue = 0x56,
.green = 0x34,
.red = 0x12,
};
// ------------------------------------------------ Static function headers --------------------------------------------
/**
* @brief Generate all the functionality test combinations
*
* - generate functionality test combinations, based on the provided test_matrix struct
*
* @param[in] test_matrix Pointer to structure defining test matrix - all the test combinations
* @param[in] test_case Pointer to structure defining functionality test case
*/
static void functionality_test_matrix(test_matrix_params_t *test_matrix, func_test_case_params_t *test_case);
/**
* @brief Fill test buffers for functionality test
*
* @param[in] test_case Pointer to structure defining functionality test case
*/
static void fill_test_bufs(func_test_case_params_t *test_case);
/**
* @brief The actual functionality test
*
* - function prepares structures for functionality testing and runs the LVGL API
*
* @param[in] test_case Pointer to structure defining functionality test case
*/
static void lv_fill_functionality(func_test_case_params_t *test_case);
/**
* @brief Evaluate results for 32bit data length
*
* @param[in] test_case Pointer to structure defining functionality test case
*/
static void test_eval_32bit_data(func_test_case_params_t *test_case);
/**
* @brief Evaluate results for 16bit data length
*
* @param[in] test_case Pointer to structure defining functionality test case
*/
static void test_eval_16bit_data(func_test_case_params_t *test_case);
/**
* @brief Evaluate results for 24bit data length
*
* @param[in] test_case Pointer to structure defining functionality test case
*/
static void test_eval_24bit_data(func_test_case_params_t *test_case);
// ------------------------------------------------ Test cases ---------------------------------------------------------
/*
Functionality tests
Purpose:
- Test that an assembly version of LVGL blending API achieves the same results as the ANSI version
Procedure:
- Prepare testing matrix, to cover all the possible combinations of destination array widths, lengths, memory alignment...
- Run assembly version of the LVGL blending API
- Run ANSI C version of the LVGL blending API
- Compare the results
- Repeat above 3 steps for each test matrix setup
*/
// ------------------------------------------------ Test cases stages --------------------------------------------------
TEST_CASE("Test fill functionality ARGB8888", "[fill][functionality][ARGB8888]")
{
test_matrix_params_t test_matrix = {
.min_w = 8, // 8 is the lower limit for the esp32s3 asm implementation, otherwise esp32 is executed
.min_h = 1,
.max_w = 16,
.max_h = 16,
.min_unalign_byte = 0,
.max_unalign_byte = 16,
.unalign_step = 1,
.dest_stride_step = 1,
.test_combinations_count = 0,
};
func_test_case_params_t test_case = {
.blend_api_func = &lv_draw_sw_blend_color_to_argb8888,
.color_format = LV_COLOR_FORMAT_ARGB8888,
.data_type_size = sizeof(uint32_t),
};
ESP_LOGI(TAG_LV_FILL_FUNC, "running test for ARGB8888 color format");
functionality_test_matrix(&test_matrix, &test_case);
}
TEST_CASE("Test fill functionality RGB565", "[fill][functionality][RGB565]")
{
test_matrix_params_t test_matrix = {
.min_w = 16, // 16 is the lower limit for the esp32s3 asm implementation, otherwise esp32 is executed
.min_h = 1,
.max_w = 32,
.max_h = 16,
.min_unalign_byte = 0,
.max_unalign_byte = 16,
.unalign_step = 1,
.dest_stride_step = 1,
.test_combinations_count = 0,
};
func_test_case_params_t test_case = {
.blend_api_func = &lv_draw_sw_blend_color_to_rgb565,
.color_format = LV_COLOR_FORMAT_RGB565,
.data_type_size = sizeof(uint16_t),
};
ESP_LOGI(TAG_LV_FILL_FUNC, "running test for RGB565 color format");
functionality_test_matrix(&test_matrix, &test_case);
}
TEST_CASE("Test fill functionality RGB888", "[fill][functionality][RGB888]")
{
test_matrix_params_t test_matrix = {
.min_w = 12, // 12 is the lower limit for the esp32s3 asm implementation, otherwise esp32 is executed
.min_h = 1,
.max_w = 32,
.max_h = 3,
.min_unalign_byte = 0,
.max_unalign_byte = 16,
.unalign_step = 1,
.dest_stride_step = 1,
.test_combinations_count = 0,
};
func_test_case_params_t test_case = {
.blend_api_px_func = &lv_draw_sw_blend_color_to_rgb888,
.color_format = LV_COLOR_FORMAT_RGB888,
.data_type_size = sizeof(uint8_t) * 3, // 24-bit data length
};
ESP_LOGI(TAG_LV_FILL_FUNC, "running test for RGB888 color format");
functionality_test_matrix(&test_matrix, &test_case);
}
// ------------------------------------------------ Static test functions ----------------------------------------------
static void functionality_test_matrix(test_matrix_params_t *test_matrix, func_test_case_params_t *test_case)
{
// Step destination array width
for (int dest_w = test_matrix->min_w; dest_w <= test_matrix->max_w; dest_w++) {
// Step destination array height
for (int dest_h = test_matrix->min_h; dest_h <= test_matrix->max_h; dest_h++) {
// Step destination array stride
for (int dest_stride = dest_w; dest_stride <= dest_w * 2; dest_stride += test_matrix->dest_stride_step) {
// Step destination array unalignment
for (int unalign_byte = test_matrix->min_unalign_byte; unalign_byte <= test_matrix->max_unalign_byte; unalign_byte += test_matrix->unalign_step) {
// Call functionality test
UPDATE_TEST_CASE(test_case, dest_w, dest_h, dest_stride, unalign_byte);
lv_fill_functionality(test_case);
test_matrix->test_combinations_count++;
}
}
}
}
ESP_LOGI(TAG_LV_FILL_FUNC, "test combinations: %d\n", test_matrix->test_combinations_count);
}
static void lv_fill_functionality(func_test_case_params_t *test_case)
{
fill_test_bufs(test_case);
// Init structure for LVGL blend API, to call the Assembly API
_lv_draw_sw_blend_fill_dsc_t dsc_asm = {
.dest_buf = test_case->buf.p_asm,
.dest_w = test_case->dest_w,
.dest_h = test_case->dest_h,
.dest_stride = test_case->dest_stride * test_case->data_type_size, // stride * sizeof()
.mask_buf = NULL,
.color = test_color,
.opa = LV_OPA_MAX,
.use_asm = true,
};
// Init structure for LVGL blend API, to call the ANSI API
_lv_draw_sw_blend_fill_dsc_t dsc_ansi = dsc_asm;
dsc_ansi.dest_buf = test_case->buf.p_ansi;
dsc_ansi.use_asm = false;
if (test_case->blend_api_func != NULL) {
test_case->blend_api_func(&dsc_asm); // Call the LVGL API with Assembly code
test_case->blend_api_func(&dsc_ansi); // Call the LVGL API with ANSI code
} else if (test_case->blend_api_px_func != NULL) {
test_case->blend_api_px_func(&dsc_asm, 3); // Call the LVGL API with Assembly code with set pixel size
test_case->blend_api_px_func(&dsc_ansi, 3); // Call the LVGL API with ANSI code with set pixel size
}
// Shift array pointers by Canary Bytes amount back
test_case->buf.p_asm -= CANARY_BYTES * test_case->data_type_size;
test_case->buf.p_ansi -= CANARY_BYTES * test_case->data_type_size;
// Evaluate the results
sprintf(test_msg_buf, "Test case: dest_w = %d, dest_h = %d, dest_stride = %d, unalign_byte = %d\n", test_case->dest_w, test_case->dest_h, test_case->dest_stride, test_case->unalign_byte);
switch (test_case->color_format) {
case LV_COLOR_FORMAT_ARGB8888: {
test_eval_32bit_data(test_case);
break;
}
case LV_COLOR_FORMAT_RGB565: {
test_eval_16bit_data(test_case);
break;
}
case LV_COLOR_FORMAT_RGB888: {
test_eval_24bit_data(test_case);
break;
}
default:
TEST_ASSERT_MESSAGE(false, "LV Color format not found");
}
free(test_case->buf.p_asm_alloc);
free(test_case->buf.p_ansi_alloc);
}
static void fill_test_bufs(func_test_case_params_t *test_case)
{
const size_t data_type_size = test_case->data_type_size; // sizeof() of used data type
const size_t total_buf_len = test_case->total_buf_len; // Total buffer length, data part of the buffer including the Canary bytes
const size_t active_buf_len = test_case->active_buf_len; // Length of buffer
const unsigned int unalign_byte = test_case->unalign_byte;
// Allocate destination arrays for Assembly and ANSI LVGL Blend API
void *mem_asm = memalign(16, (total_buf_len * data_type_size) + unalign_byte);
void *mem_ansi = memalign(16, (total_buf_len * data_type_size) + unalign_byte);
TEST_ASSERT_NOT_NULL_MESSAGE(mem_asm, "Lack of memory");
TEST_ASSERT_NOT_NULL_MESSAGE(mem_ansi, "Lack of memory");
// Save a pointer to the beginning of the allocated memory which will be used to free()
test_case->buf.p_asm_alloc = mem_asm;
test_case->buf.p_ansi_alloc = mem_ansi;
// Apply destination array unalignment
uint8_t *dest_buf_asm = (uint8_t *)mem_asm + unalign_byte;
uint8_t *dest_buf_ansi = (uint8_t *)mem_ansi + unalign_byte;
// Set the whole buffer to 0, including the Canary bytes part
memset(dest_buf_asm, 0, total_buf_len * data_type_size);
memset(dest_buf_ansi, 0, total_buf_len * data_type_size);
// Fill the actual part of the destination buffers with known values,
// Values must be same, because of the stride
for (int i = CANARY_BYTES; i < active_buf_len + CANARY_BYTES; i++) {
dest_buf_asm[i * data_type_size] = (uint8_t)(i % 255);
dest_buf_ansi[i * data_type_size] = (uint8_t)(i % 255);
}
// Shift array pointers by Canary Bytes amount
dest_buf_asm += CANARY_BYTES * data_type_size;
dest_buf_ansi += CANARY_BYTES * data_type_size;
// Save a pointer to the working part of the memory, where the test data are stored
test_case->buf.p_asm = (void *)dest_buf_asm;
test_case->buf.p_ansi = (void *)dest_buf_ansi;
}
static void test_eval_32bit_data(func_test_case_params_t *test_case)
{
// Print results 32bit data
#if DBG_PRINT_OUTPUT
for (uint32_t i = 0; i < test_case->total_buf_len; i++) {
printf("dest_buf[%"PRIi32"] %s ansi = %8"PRIx32" \t asm = %8"PRIx32" \n", i, ((i < 10) ? (" ") : ("")), ((uint32_t *)test_case->buf.p_ansi)[i], ((uint32_t *)test_case->buf.p_asm)[i]);
}
printf("\n");
#endif
// Canary bytes area must stay 0
TEST_ASSERT_EACH_EQUAL_UINT32_MESSAGE(0, (uint32_t *)test_case->buf.p_ansi, CANARY_BYTES, test_msg_buf);
TEST_ASSERT_EACH_EQUAL_UINT32_MESSAGE(0, (uint32_t *)test_case->buf.p_asm, CANARY_BYTES, test_msg_buf);
// dest_buf_asm and dest_buf_ansi must be equal
TEST_ASSERT_EQUAL_UINT32_ARRAY_MESSAGE((uint32_t *)test_case->buf.p_asm + CANARY_BYTES, (uint32_t *)test_case->buf.p_ansi + CANARY_BYTES, test_case->active_buf_len, test_msg_buf);
// Canary bytes area must stay 0
TEST_ASSERT_EACH_EQUAL_UINT32_MESSAGE(0, (uint32_t *)test_case->buf.p_ansi + (test_case->total_buf_len - CANARY_BYTES), CANARY_BYTES, test_msg_buf);
TEST_ASSERT_EACH_EQUAL_UINT32_MESSAGE(0, (uint32_t *)test_case->buf.p_asm + (test_case->total_buf_len - CANARY_BYTES), CANARY_BYTES, test_msg_buf);
}
static void test_eval_16bit_data(func_test_case_params_t *test_case)
{
// Print results, 16bit data
#if DBG_PRINT_OUTPUT
for (uint32_t i = 0; i < test_case->total_buf_len; i++) {
printf("dest_buf[%"PRIi32"] %s ansi = %8"PRIx16" \t asm = %8"PRIx16" \n", i, ((i < 10) ? (" ") : ("")), ((uint16_t *)test_case->buf.p_ansi)[i], ((uint16_t *)test_case->buf.p_asm)[i]);
}
printf("\n");
#endif
// Canary bytes area must stay 0
TEST_ASSERT_EACH_EQUAL_UINT16_MESSAGE(0, (uint16_t *)test_case->buf.p_ansi, CANARY_BYTES, test_msg_buf);
TEST_ASSERT_EACH_EQUAL_UINT16_MESSAGE(0, (uint16_t *)test_case->buf.p_asm, CANARY_BYTES, test_msg_buf);
// dest_buf_asm and dest_buf_ansi must be equal
TEST_ASSERT_EQUAL_UINT16_ARRAY_MESSAGE((uint16_t *)test_case->buf.p_asm + CANARY_BYTES, (uint16_t *)test_case->buf.p_ansi + CANARY_BYTES, test_case->active_buf_len, test_msg_buf);
// Canary bytes area must stay 0
TEST_ASSERT_EACH_EQUAL_UINT16_MESSAGE(0, (uint16_t *)test_case->buf.p_ansi + (test_case->total_buf_len - CANARY_BYTES), CANARY_BYTES, test_msg_buf);
TEST_ASSERT_EACH_EQUAL_UINT16_MESSAGE(0, (uint16_t *)test_case->buf.p_asm + (test_case->total_buf_len - CANARY_BYTES), CANARY_BYTES, test_msg_buf);
}
static void test_eval_24bit_data(func_test_case_params_t *test_case)
{
// Print results, 24bit data
#if DBG_PRINT_OUTPUT
size_t data_type_size = test_case->data_type_size;
for (uint32_t i = 0; i < test_case->total_buf_len; i++) {
uint32_t ansi_value = ((uint8_t *)test_case->buf.p_ansi)[i * data_type_size]
| (((uint8_t *)test_case->buf.p_ansi)[i * data_type_size + 1] << 8)
| (((uint8_t *)test_case->buf.p_ansi)[i * data_type_size + 2] << 16);
uint32_t asm_value = ((uint8_t *)test_case->buf.p_asm)[i * data_type_size]
| (((uint8_t *)test_case->buf.p_asm)[i * data_type_size + 1] << 8)
| (((uint8_t *)test_case->buf.p_asm)[i * data_type_size + 2] << 16);
printf("dest_buf[%"PRIi32"] %s ansi = %8"PRIx32" \t asm = %8"PRIx32" \n", i, ((i < 10) ? (" ") : ("")), ansi_value, asm_value);
}
printf("\n");
#endif
const int canary_bytes_area = CANARY_BYTES * test_case->data_type_size;
// Canary bytes area must stay 0
TEST_ASSERT_EACH_EQUAL_UINT8_MESSAGE(0, (uint8_t *)test_case->buf.p_ansi, canary_bytes_area, test_msg_buf);
TEST_ASSERT_EACH_EQUAL_UINT8_MESSAGE(0, (uint8_t *)test_case->buf.p_asm, canary_bytes_area, test_msg_buf);
// dest_buf_asm and dest_buf_ansi must be equal
TEST_ASSERT_EQUAL_UINT8_ARRAY_MESSAGE((uint8_t *)test_case->buf.p_asm + canary_bytes_area, (uint8_t *)test_case->buf.p_ansi + canary_bytes_area, test_case->active_buf_len * test_case->data_type_size, test_msg_buf);
// Canary bytes area must stay 0
TEST_ASSERT_EACH_EQUAL_UINT8_MESSAGE(0, (uint8_t *)test_case->buf.p_ansi + (test_case->total_buf_len - CANARY_BYTES) * test_case->data_type_size, canary_bytes_area, test_msg_buf);
TEST_ASSERT_EACH_EQUAL_UINT8_MESSAGE(0, (uint8_t *)test_case->buf.p_asm + (test_case->total_buf_len - CANARY_BYTES) * test_case->data_type_size, canary_bytes_area, test_msg_buf);
}

View File

@@ -0,0 +1,223 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include <malloc.h>
#include <sdkconfig.h>
#include "unity.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h" // for xthal_get_ccount()
#include "lv_image_common.h"
#include "lv_draw_sw_blend.h"
#include "lv_draw_sw_blend_to_rgb565.h"
#include "lv_draw_sw_blend_to_rgb888.h"
#define COMMON_DIM 128 // Common matrix dimension 128x128 pixels
#define WIDTH COMMON_DIM
#define HEIGHT COMMON_DIM
#define STRIDE WIDTH
#define UNALIGN_BYTES 3
#define BENCHMARK_CYCLES 1000
// ------------------------------------------------ Static variables ---------------------------------------------------
static const char *TAG_LV_IMAGE_BENCH = "LV Image Benchmark";
static const char *asm_ansi_func[] = {"ASM", "ANSI"};
// ------------------------------------------------ Static function headers --------------------------------------------
/**
* @brief Initialize the benchmark test
*/
static void lv_image_benchmark_init(bench_test_case_lv_image_params_t *test_params);
/**
* @brief Run the benchmark test
*/
static float lv_image_benchmark_run(bench_test_case_lv_image_params_t *test_params, _lv_draw_sw_blend_image_dsc_t *dsc);
// ------------------------------------------------ Test cases ---------------------------------------------------------
/*
Benchmark tests
Requires:
- To pass functionality tests first
Purpose:
- Test that an acceleration is achieved by an assembly implementation of LVGL blending API
Procedure:
- Initialize input parameters (test array length, width, allocate array...) of the benchmark test
- Run assembly version of LVGL blending API multiple times (1000-times or so)
- Firstly use an input test parameters for the most ideal case (16-byte aligned arrays, arrays widths divisible by 2 for RGB565 color format)
- Then use worst-case input test parameters (1-byte aligned arrays, arrays width NOT divisible by 2 for RGB565 color format)
- Count how many CPU cycles does it take to run a function from the LVGL blending API for each case (ideal and worst case)
- Run ansi version of LVGL blending API multiple times (1000-times or so) and repeat the 2 above steps for the ansi version
- Compare the results
- Free test arrays and structures needed for LVGL blending API
Inducing Most ideal and worst case scenarios:
- Most ideal:
- Both, the source and the destination buffers should be aligned by 16-byte (Xtensa PIE), or 4-byte (Xtensa base) boundaries
- Matrix width (in pixels) should be equal to the main loop length in the assembly src code
typically multiples of 16 bytes (for RGB565 it's either 32 bytes - 16 pixels or 48 bytes - 24 pixels)
- Matrix height does not have any effect on benchmark unit tests, unit the matrix is too large that cache limitations start to affect the performance
- Matrix strides, should be equal to the matrix widths (0 matrix padding), or their multiples (matrix width = matrix padding)
- Worst case:
- Both, hte source and the destination buffers should NOT be aligned by 16-byte (Xtensa PIE), or 4-byte (Xtensa base) boundaries,
Source buffer unalignment should be different from the destination unalignment, with one unalignment being even, the other being odd
The unalignments shall be small numbers (preferably 1 or 2 bytes)
- Matrix width should be one pixels smaller, than the matrix width for the most ideal case
- Matrix height does not have any effect on benchmark unit tests, unit the matrix is too large that cache limitations start to affect the performance
- Matrix strides, should NOT be equal to the matrix widths (non 0 matrix padding)
*/
// ------------------------------------------------ Test cases stages --------------------------------------------------
TEST_CASE("LV Image benchmark RGB565 blend to RGB565", "[image][benchmark][RGB565]")
{
uint16_t *dest_array_align16 = (uint16_t *)memalign(16, STRIDE * HEIGHT * sizeof(uint16_t) + UNALIGN_BYTES);
uint16_t *src_array_align16 = (uint16_t *)memalign(16, STRIDE * HEIGHT * sizeof(uint16_t) + UNALIGN_BYTES);
TEST_ASSERT_NOT_EQUAL_MESSAGE(NULL, dest_array_align16, "Lack of memory");
TEST_ASSERT_NOT_EQUAL_MESSAGE(NULL, src_array_align16, "Lack of memory");
// Apply byte unalignment (different for each array) for the worst-case test scenario
uint16_t *dest_array_align1 = (uint16_t *)((uint8_t *)dest_array_align16 + UNALIGN_BYTES - 1);
uint16_t *src_array_align1 = (uint16_t *)((uint8_t *)src_array_align16 + UNALIGN_BYTES);
bench_test_case_lv_image_params_t test_params = {
.height = HEIGHT,
.width = WIDTH,
.dest_stride = STRIDE * sizeof(uint16_t),
.src_stride = STRIDE * sizeof(uint16_t),
.cc_height = HEIGHT,
.cc_width = WIDTH - 1,
.benchmark_cycles = BENCHMARK_CYCLES,
.src_array_align16 = (void *)src_array_align16,
.src_array_align1 = (void *)src_array_align1,
.dest_array_align16 = (void *)dest_array_align16,
.dest_array_align1 = (void *)dest_array_align1,
.blend_api_func = &lv_draw_sw_blend_image_to_rgb565,
.color_format = LV_COLOR_FORMAT_RGB565,
};
ESP_LOGI(TAG_LV_IMAGE_BENCH, "running test for RGB565 color format");
lv_image_benchmark_init(&test_params);
free(dest_array_align16);
free(src_array_align16);
}
TEST_CASE("LV Image benchmark RGB888 blend to RGB888", "[image][benchmark][RGB888]")
{
uint8_t *dest_array_align16 = (uint8_t *)memalign(16, (STRIDE * HEIGHT * sizeof(uint8_t) * 3) + UNALIGN_BYTES);
uint8_t *src_array_align16 = (uint8_t *)memalign(16, (STRIDE * HEIGHT * sizeof(uint8_t) * 3) + UNALIGN_BYTES);
TEST_ASSERT_NOT_EQUAL_MESSAGE(NULL, dest_array_align16, "Lack of memory");
TEST_ASSERT_NOT_EQUAL_MESSAGE(NULL, src_array_align16, "Lack of memory");
// Apply byte unalignment (different for each array) for the worst-case test scenario
uint8_t *dest_array_align1 = dest_array_align16 + UNALIGN_BYTES - 1;
uint8_t *src_array_align1 = src_array_align16 + UNALIGN_BYTES;
bench_test_case_lv_image_params_t test_params = {
.height = HEIGHT,
.width = WIDTH,
.dest_stride = STRIDE * sizeof(uint8_t) * 3,
.src_stride = STRIDE * sizeof(uint8_t) * 3,
.cc_height = HEIGHT,
.cc_width = WIDTH - 1,
.benchmark_cycles = BENCHMARK_CYCLES,
.src_array_align16 = (void *)src_array_align16,
.src_array_align1 = (void *)src_array_align1,
.dest_array_align16 = (void *)dest_array_align16,
.dest_array_align1 = (void *)dest_array_align1,
.blend_api_func_px_size = &lv_draw_sw_blend_image_to_rgb888,
.color_format = LV_COLOR_FORMAT_RGB888,
};
ESP_LOGI(TAG_LV_IMAGE_BENCH, "running test for RGB888 color format");
lv_image_benchmark_init(&test_params);
free(dest_array_align16);
free(src_array_align16);
}
// ------------------------------------------------ Static test functions ----------------------------------------------
static void lv_image_benchmark_init(bench_test_case_lv_image_params_t *test_params)
{
// Init structure for LVGL blend API, to call the Assembly API
_lv_draw_sw_blend_image_dsc_t dsc = {
.dest_buf = test_params->dest_array_align16,
.dest_w = test_params->width,
.dest_h = test_params->height,
.dest_stride = test_params->dest_stride, // stride * sizeof()
.mask_buf = NULL,
.src_buf = test_params->src_array_align16,
.src_stride = test_params->src_stride,
.src_color_format = test_params->color_format,
.opa = LV_OPA_MAX,
.blend_mode = LV_BLEND_MODE_NORMAL,
.use_asm = true,
};
// Init structure for LVGL blend API, to call the ANSI API
_lv_draw_sw_blend_image_dsc_t dsc_cc = dsc;
dsc_cc.dest_buf = test_params->dest_array_align1;
dsc_cc.dest_w = test_params->cc_width;
dsc_cc.dest_h = test_params->cc_height;
dsc_cc.src_buf = test_params->src_array_align1;
// Run benchmark 2 times:
// First run using assembly, second run using ANSI
for (int i = 0; i < 2; i++) {
// Run benchmark with the most ideal input parameters
float cycles = lv_image_benchmark_run(test_params, &dsc); // Call Benchmark cycle
float per_sample = cycles / ((float)(dsc.dest_w * dsc.dest_h));
ESP_LOGI(TAG_LV_IMAGE_BENCH, " %s ideal case: %.3f cycles for %"PRIi32"x%"PRIi32" matrix, %.3f cycles per sample", asm_ansi_func[i], cycles, dsc.dest_w, dsc.dest_h, per_sample);
// Run benchmark with the corner case input parameters
cycles = lv_image_benchmark_run(test_params, &dsc_cc); // Call Benchmark cycle
per_sample = cycles / ((float)(dsc_cc.dest_w * dsc_cc.dest_h));
ESP_LOGI(TAG_LV_IMAGE_BENCH, " %s corner case: %.3f cycles for %"PRIi32"x%"PRIi32" matrix, %.3f cycles per sample\n", asm_ansi_func[i], cycles, dsc_cc.dest_w, dsc_cc.dest_h, per_sample);
// change to ANSI
dsc.use_asm = false;
dsc_cc.use_asm = false;
}
}
static float lv_image_benchmark_run(bench_test_case_lv_image_params_t *test_params, _lv_draw_sw_blend_image_dsc_t *dsc)
{
// Call the DUT function for the first time to init the benchmark test
if (test_params->blend_api_func != NULL) {
test_params->blend_api_func(dsc); // Call the LVGL API
} else if (test_params->blend_api_func_px_size != NULL) {
test_params->blend_api_func_px_size(dsc, 3); // Call the LVGL API with set pixel size
} else {
TEST_ASSERT_MESSAGE(false, "Not supported: Both API pointers can't be NULL");
}
// Run the benchmark
const unsigned int start_b = xthal_get_ccount();
if (test_params->blend_api_func != NULL) {
for (int i = 0; i < test_params->benchmark_cycles; i++) {
test_params->blend_api_func(dsc);
}
} else if (test_params->blend_api_func_px_size != NULL) {
for (int i = 0; i < test_params->benchmark_cycles; i++) {
test_params->blend_api_func_px_size(dsc, 3);
}
}
const unsigned int end_b = xthal_get_ccount();
const float total_b = end_b - start_b;
const float cycles = total_b / (test_params->benchmark_cycles);
return cycles;
}

View File

@@ -0,0 +1,459 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include <malloc.h>
#include <inttypes.h>
#include "sdkconfig.h"
#include "unity.h"
#include "esp_log.h"
#include "lv_image_common.h"
#include "lv_draw_sw_blend.h"
#include "lv_draw_sw_blend_to_rgb565.h"
#include "lv_draw_sw_blend_to_rgb888.h"
// ------------------------------------------------- Defines -----------------------------------------------------------
#define DBG_PRINT_OUTPUT false
// ------------------------------------------------- Macros and Types --------------------------------------------------
#define UPDATE_TEST_CASE(test_case_ptr, dest_w, dest_h, src_stride, dest_stride, src_unalign_byte, dest_unalign_byte) ({ \
(test_case_ptr)->src_buf_len = (size_t)(dest_h * src_stride); \
(test_case_ptr)->active_dest_buf_len = (size_t)(dest_h * dest_stride); \
(test_case_ptr)->total_dest_buf_len = (size_t)((dest_h * dest_stride) + (test_case_ptr->canary_pixels * 2)); \
(test_case_ptr)->dest_w = (dest_w); \
(test_case_ptr)->dest_h = (dest_h); \
(test_case_ptr)->src_stride = (src_stride); \
(test_case_ptr)->dest_stride = (dest_stride); \
(test_case_ptr)->src_unalign_byte = (src_unalign_byte); \
(test_case_ptr)->dest_unalign_byte = (dest_unalign_byte); \
})
// ------------------------------------------------ Static variables ---------------------------------------------------
static const char *TAG_LV_IMAGE_FUNC = "LV Image Functionality";
static char test_msg_buf[200];
static const test_matrix_lv_image_params_t default_test_matrix_image_blend = {
#if CONFIG_IDF_TARGET_ESP32S3
.min_w = 8, // 8 is the lower limit for the esp32s3 asm implementation, otherwise esp32 is executed
.min_h = 1,
.max_w = 24,
.max_h = 2,
.src_max_unalign_byte = 16, // Use 16-byte boundary check for Xtensa PIE
.dest_max_unalign_byte = 16,
.dest_unalign_step = 1, // Step 1 as the destination array is being aligned in the assembly code all the time
.src_unalign_step = 3, // Step 3 (more relaxed) as source array is used unaligned in the assembly code
.src_stride_step = 3,
.dest_stride_step = 3,
#else
.min_w = 1,
.min_h = 1,
.max_w = 16,
.max_h = 2,
.src_max_unalign_byte = 4, // Use 4-byte boundary check for Xtensa base
.dest_max_unalign_byte = 4,
.dest_unalign_step = 1,
.src_unalign_step = 1,
.src_stride_step = 1,
.dest_stride_step = 1,
#endif
.src_min_unalign_byte = 0,
.dest_min_unalign_byte = 0,
.test_combinations_count = 0,
};
// ------------------------------------------------ Static function headers --------------------------------------------
/**
* @brief Generate all the functionality test combinations
*
* - generate functionality test combinations, based on the provided test_matrix struct
*
* @param[in] test_matrix Pointer to structure defining test matrix - all the test combinations
* @param[in] test_case Pointer ot structure defining functionality test case
*/
static void functionality_test_matrix(test_matrix_lv_image_params_t *test_matrix, func_test_case_lv_image_params_t *test_case);
/**
* @brief Fill test buffers for image functionality test
*
* @param[in] test_case Pointer ot structure defining functionality test case
*/
static void fill_test_bufs(func_test_case_lv_image_params_t *test_case);
/**
* @brief The actual functionality test
*
* - function prepares structures for functionality testing and runs the LVGL API
*
* @param[in] test_case Pointer ot structure defining functionality test case
*/
static void lv_image_functionality(func_test_case_lv_image_params_t *test_case);
/**
* @brief Evaluate results of LV Image functionality for 16bit data length
*
* @param[in] test_case Pointer ot structure defining functionality test case
*/
static void test_eval_image_16bit_data(func_test_case_lv_image_params_t *test_case);
/**
* @brief Evaluate results of LV Image functionality for 24bit data length
*
* @param[in] test_case Pointer ot structure defining functionality test case
*/
static void test_eval_image_24bit_data(func_test_case_lv_image_params_t *test_case);
// ------------------------------------------------ Test cases ---------------------------------------------------------
/*
Functionality tests
Purpose:
- Test that an assembly version of LVGL blending API achieves the same results as the ANSI version
Procedure:
- Prepare testing matrix, to cover all the possible combinations of destination and source arrays widths,
lengths, strides and memory alignments
- Run assembly version of the LVGL blending API
- Run ANSI C version of the LVGL blending API
- Compare the results
- Repeat above 3 steps for each test matrix setup
*/
// ------------------------------------------------ Test cases stages --------------------------------------------------
TEST_CASE("LV Image functionality RGB565 blend to RGB565", "[image][functionality][RGB565]")
{
test_matrix_lv_image_params_t test_matrix = default_test_matrix_image_blend;
func_test_case_lv_image_params_t test_case = {
.blend_api_func = &lv_draw_sw_blend_image_to_rgb565,
.color_format = LV_COLOR_FORMAT_RGB565,
.canary_pixels = CANARY_PIXELS_RGB565,
.memory_alignment_offset = 0,
.src_data_type_size = sizeof(uint16_t),
.dest_data_type_size = sizeof(uint16_t),
.operation_type = OPERATION_FILL,
};
ESP_LOGI(TAG_LV_IMAGE_FUNC, "running test for RGB565 color format");
functionality_test_matrix(&test_matrix, &test_case);
}
TEST_CASE("LV Image functionality RGB888 blend to RGB888", "[image][functionality][RGB888]")
{
test_matrix_lv_image_params_t test_matrix = default_test_matrix_image_blend;
func_test_case_lv_image_params_t test_case = {
.blend_api_func_px_size = &lv_draw_sw_blend_image_to_rgb888, // The blending API function takes additional parameter, pixel size
.color_format = LV_COLOR_FORMAT_RGB888,
.canary_pixels = CANARY_PIXELS_RGB888,
.memory_alignment_offset = 32 - (CANARY_PIXELS_RGB888 * 3), // Closes 16-byte boundary (32) - RGB888 canary pixels
.src_data_type_size = sizeof(uint8_t) * 3,
.dest_data_type_size = sizeof(uint8_t) * 3,
.operation_type = OPERATION_FILL,
};
ESP_LOGI(TAG_LV_IMAGE_FUNC, "running test for RGB888 color format");
functionality_test_matrix(&test_matrix, &test_case);
}
// ------------------------------------------------ Static test functions ----------------------------------------------
static void functionality_test_matrix(test_matrix_lv_image_params_t *test_matrix, func_test_case_lv_image_params_t *test_case)
{
// Step destination array width
for (int dest_w = test_matrix->min_w; dest_w <= test_matrix->max_w; dest_w++) {
// Step destination array height
for (int dest_h = test_matrix->min_h; dest_h <= test_matrix->max_h; dest_h++) {
// Step source array stride
for (int src_stride = dest_w; src_stride <= dest_w * 2; src_stride += test_matrix->src_stride_step) {
// Step destination array stride
for (int dest_stride = dest_w; dest_stride <= dest_w * 2; dest_stride += test_matrix->dest_stride_step) {
// Step source array unalignment
for (int src_unalign_byte = test_matrix->src_min_unalign_byte; src_unalign_byte <= test_matrix->src_max_unalign_byte; src_unalign_byte += test_matrix->src_unalign_step) {
// Step destination array unalignment
for (int dest_unalign_byte = test_matrix->dest_min_unalign_byte; dest_unalign_byte <= test_matrix->dest_max_unalign_byte; dest_unalign_byte += test_matrix->dest_unalign_step) {
// Call functionality test
UPDATE_TEST_CASE(test_case, dest_w, dest_h, src_stride, dest_stride, src_unalign_byte, dest_unalign_byte);
lv_image_functionality(test_case);
test_matrix->test_combinations_count++;
}
}
}
}
}
}
ESP_LOGI(TAG_LV_IMAGE_FUNC, "test combinations: %d\n", test_matrix->test_combinations_count);
}
static void lv_image_functionality(func_test_case_lv_image_params_t *test_case)
{
fill_test_bufs(test_case);
_lv_draw_sw_blend_image_dsc_t dsc_asm = {
.dest_buf = test_case->buf.p_dest_asm,
.dest_w = test_case->dest_w,
.dest_h = test_case->dest_h,
.dest_stride = test_case->dest_stride * test_case->dest_data_type_size, // dest_stride * sizeof(data_type)
.mask_buf = NULL,
.mask_stride = 0,
.src_buf = test_case->buf.p_src,
.src_stride = test_case->src_stride * test_case->src_data_type_size, // src_stride * sizeof(data_type)
.src_color_format = test_case->color_format,
.opa = LV_OPA_MAX,
.blend_mode = LV_BLEND_MODE_NORMAL,
.use_asm = true,
};
// Init structure for LVGL blend API, to call the ANSI API
_lv_draw_sw_blend_image_dsc_t dsc_ansi = dsc_asm;
dsc_ansi.dest_buf = test_case->buf.p_dest_ansi;
dsc_ansi.use_asm = false;
if (test_case->blend_api_func != NULL) {
test_case->blend_api_func(&dsc_asm); // Call the LVGL API with Assembly code
test_case->blend_api_func(&dsc_ansi); // Call the LVGL API with ANSI code
} else if (test_case->blend_api_func_px_size != NULL) {
test_case->blend_api_func_px_size(&dsc_asm, 3); // Call the LVGL API with Assembly code with set pixel size
test_case->blend_api_func_px_size(&dsc_ansi, 3); // Call the LVGL API with ANSI code with set pixel size
} else {
TEST_ASSERT_MESSAGE(false, "Not supported: Both API pointers can't be NULL");
}
// Shift array pointers by (Canary pixels amount * data type length) back
test_case->buf.p_dest_asm -= test_case->canary_pixels * test_case->dest_data_type_size;
test_case->buf.p_dest_ansi -= test_case->canary_pixels * test_case->dest_data_type_size;
// Evaluate the results
sprintf(test_msg_buf, "Test case: dest_w = %d, dest_h = %d, dest_stride = %d, src_stride = %d, dest_unalign_byte = %d, src_unalign_byte = %d\n",
test_case->dest_w, test_case->dest_h, test_case->dest_stride, test_case->src_stride, test_case->dest_unalign_byte, test_case->src_unalign_byte);
#if DBG_PRINT_OUTPUT
printf("%s\n", test_msg_buf);
#endif
switch (test_case->color_format) {
case LV_COLOR_FORMAT_RGB565:
test_eval_image_16bit_data(test_case);
break;
case LV_COLOR_FORMAT_RGB888:
test_eval_image_24bit_data(test_case);
break;
default:
TEST_ASSERT_MESSAGE(false, "LV Color format not found");
break;
}
// Free memory allocated for test buffers
free(test_case->buf.p_dest_asm_alloc);
free(test_case->buf.p_dest_ansi_alloc);
free(test_case->buf.p_src_alloc);
}
static void fill_test_bufs(func_test_case_lv_image_params_t *test_case)
{
const size_t src_data_type_size = test_case->src_data_type_size; // sizeof() of used data type in the source buffer
const size_t dest_data_type_size = test_case->dest_data_type_size; // sizeof() of used data type in the destination buffer
const size_t src_buf_len = test_case->src_buf_len; // Total source buffer length, data part of the source buffer including matrix padding (no Canary pixels are used for source buffer)
const size_t total_dest_buf_len = test_case->total_dest_buf_len; // Total destination buffer length, data part of the destination buffer including the Canary pixels and matrix padding
const size_t active_dest_buf_len = test_case->active_dest_buf_len; // Length of the data part of the destination buffer including matrix padding
const size_t canary_pixels = test_case->canary_pixels; // Canary pixels, according to the data type
const unsigned int src_unalign_byte = test_case->src_unalign_byte; // Unalignment bytes for source buffer
const unsigned int dest_unalign_byte = test_case->dest_unalign_byte; // Unalignment bytes for destination buffer
const unsigned int memory_offset = test_case->memory_alignment_offset; // Memory alignment offset for 16-byte boundary
// Allocate destination arrays and source array for Assembly and ANSI LVGL Blend API
void *src_mem_common = memalign(16, (src_buf_len * src_data_type_size) + src_unalign_byte);
void *dest_mem_asm = memalign(16, (total_dest_buf_len * dest_data_type_size) + dest_unalign_byte + memory_offset);
void *dest_mem_ansi = memalign(16, (total_dest_buf_len * dest_data_type_size) + dest_unalign_byte + memory_offset);
TEST_ASSERT_NOT_NULL_MESSAGE(src_mem_common, "Lack of memory");
TEST_ASSERT_NOT_NULL_MESSAGE(dest_mem_asm, "Lack of memory");
TEST_ASSERT_NOT_NULL_MESSAGE(dest_mem_ansi, "Lack of memory");
// Save a pointer to the beginning of the allocated memory which will be used to free()
test_case->buf.p_src_alloc = src_mem_common;
test_case->buf.p_dest_asm_alloc = dest_mem_asm;
test_case->buf.p_dest_ansi_alloc = dest_mem_ansi;
// Apply destination and source array unalignment
uint8_t *src_buf_common = (uint8_t *)src_mem_common + src_unalign_byte;
uint8_t *dest_buf_asm = (uint8_t *)dest_mem_asm + dest_unalign_byte + memory_offset;
uint8_t *dest_buf_ansi = (uint8_t *)dest_mem_ansi + dest_unalign_byte + memory_offset;
// Set the whole buffer to 0, including the Canary pixels part
memset(src_buf_common, 0, src_buf_len * src_data_type_size);
memset(dest_buf_asm, 0, total_dest_buf_len * src_data_type_size);
memset(dest_buf_ansi, 0, total_dest_buf_len * src_data_type_size);
switch (test_case->operation_type) {
case OPERATION_FILL:
// Fill the actual part of the destination buffers with known values,
// Values must be same, because of the stride
if (test_case->color_format == LV_COLOR_FORMAT_RGB565) {
uint16_t *dest_buf_asm_uint16 = (uint16_t *)dest_buf_asm;
uint16_t *dest_buf_ansi_uint16 = (uint16_t *)dest_buf_ansi;
uint16_t *src_buf_uint16 = (uint16_t *)src_buf_common;
// Fill destination buffers
for (int i = 0; i < active_dest_buf_len; i++) {
dest_buf_asm_uint16[canary_pixels + i] = i + ((i & 1) ? 0x6699 : 0x9966);
dest_buf_ansi_uint16[canary_pixels + i] = dest_buf_asm_uint16[canary_pixels + i];
}
// Fill source buffer
for (int i = 0; i < src_buf_len; i++) {
src_buf_uint16[i] = i + ((i & 1) ? 0x55AA : 0xAA55);
}
}
if (test_case->color_format == LV_COLOR_FORMAT_RGB888) {
uint8_t *dest_buf_asm_uint8 = dest_buf_asm;
uint8_t *dest_buf_ansi_uint8 = dest_buf_ansi;
uint8_t *src_buf_uint8 = src_buf_common;
// Fill destination buffers
for (int i = 0; i < active_dest_buf_len * 3; i++) {
dest_buf_asm_uint8[(canary_pixels * 3) + i] = i + ((i & 1) ? 0x66 : 0x99);
dest_buf_ansi_uint8[(canary_pixels * 3) + i] = dest_buf_asm_uint8[(canary_pixels * 3) + i];
}
// Fill source buffer
for (int i = 0; i < src_buf_len * 3; i++) {
src_buf_uint8[i] = i + ((i & 1) ? 0x55 : 0xAA);
}
}
break;
default:
TEST_ASSERT_MESSAGE(false, "LV Operation not found");
break;
}
// Shift array pointers by (Canary pixels amount * data type length) forward
dest_buf_asm += canary_pixels * dest_data_type_size;
dest_buf_ansi += canary_pixels * dest_data_type_size;
// Save a pointer to the working part of the memory, where the test data are stored
test_case->buf.p_src = (void *)src_buf_common;
test_case->buf.p_dest_asm = (void *)dest_buf_asm;
test_case->buf.p_dest_ansi = (void *)dest_buf_ansi;
#if DBG_PRINT_OUTPUT
printf("Destination buffers fill:\n");
for (uint32_t i = 0; i < test_case->active_dest_buf_len; i++) {
printf("dest_buf[%"PRIi32"] %s ansi = %8"PRIx16" \t asm = %8"PRIx16" \n", i, ((i < 10) ? (" ") : ("")), ((uint16_t *)test_case->buf.p_dest_ansi)[i], ((uint16_t *)test_case->buf.p_dest_asm)[i]);
}
printf("\n");
printf("Source buffer fill:\n");
for (uint32_t i = 0; i < test_case->src_buf_len; i++) {
printf("src_buf[%"PRIi32"] %s = %8"PRIx16" \n", i, ((i < 10) ? (" ") : ("")), ((uint16_t *)test_case->buf.p_src)[i]);
}
printf("\n");
#endif
}
static void test_eval_image_16bit_data(func_test_case_lv_image_params_t *test_case)
{
// Print results, 16bit data
#if DBG_PRINT_OUTPUT
printf("\nEval\nDestination buffers fill:\n");
for (uint32_t i = 0; i < test_case->total_dest_buf_len; i++) {
printf("dest_buf[%"PRIi32"] %s ansi = %8"PRIx16" \t asm = %8"PRIx16" %s \n", i, ((i < 10) ? (" ") : ("")), ((uint16_t *)test_case->buf.p_dest_ansi)[i], ((uint16_t *)test_case->buf.p_dest_asm)[i], (((uint16_t *)test_case->buf.p_dest_ansi)[i] == ((uint16_t *)test_case->buf.p_dest_asm)[i]) ? ("OK") : ("FAIL"));
}
printf("\n");
printf("Source buffer fill:\n");
for (uint32_t i = 0; i < test_case->src_buf_len; i++) {
printf("src_buf[%"PRIi32"] %s = %8"PRIx16" \n", i, ((i < 10) ? (" ") : ("")), ((uint16_t *)test_case->buf.p_src)[i]);
}
printf("\n");
#endif
// Canary pixels area must stay 0
const size_t canary_pixels = test_case->canary_pixels;
TEST_ASSERT_EACH_EQUAL_UINT16_MESSAGE(0, (uint16_t *)test_case->buf.p_dest_ansi, canary_pixels, test_msg_buf);
TEST_ASSERT_EACH_EQUAL_UINT16_MESSAGE(0, (uint16_t *)test_case->buf.p_dest_asm, canary_pixels, test_msg_buf);
// dest_buf_asm and dest_buf_ansi must be equal
TEST_ASSERT_EQUAL_UINT16_ARRAY_MESSAGE((uint16_t *)test_case->buf.p_dest_ansi + canary_pixels, (uint16_t *)test_case->buf.p_dest_asm + canary_pixels, test_case->active_dest_buf_len, test_msg_buf);
// Data part of the destination buffer and source buffer (not considering matrix padding) must be equal
uint16_t *dest_row_begin = (uint16_t *)test_case->buf.p_dest_asm + canary_pixels;
uint16_t *src_row_begin = (uint16_t *)test_case->buf.p_src;
for (int row = 0; row < test_case->dest_h; row++) {
TEST_ASSERT_EQUAL_UINT16_ARRAY_MESSAGE(dest_row_begin, src_row_begin, test_case->dest_w, test_msg_buf);
dest_row_begin += test_case->dest_stride; // Move pointer of the destination buffer to the next row
src_row_begin += test_case->src_stride; // Move pointer of the source buffer to the next row
}
// Canary pixels area must stay 0
TEST_ASSERT_EACH_EQUAL_UINT16_MESSAGE(0, (uint16_t *)test_case->buf.p_dest_ansi + (test_case->total_dest_buf_len - canary_pixels), canary_pixels, test_msg_buf);
TEST_ASSERT_EACH_EQUAL_UINT16_MESSAGE(0, (uint16_t *)test_case->buf.p_dest_asm + (test_case->total_dest_buf_len - canary_pixels), canary_pixels, test_msg_buf);
}
static void test_eval_image_24bit_data(func_test_case_lv_image_params_t *test_case)
{
// Print results, 24bit data
#if DBG_PRINT_OUTPUT
printf("\nEval\nDestination buffers fill:\n");
size_t dest_data_type_size = test_case->dest_data_type_size;
for (uint32_t i = 0; i < test_case->total_dest_buf_len; i++) {
uint32_t ansi_value = ((uint8_t *)test_case->buf.p_dest_ansi)[i * dest_data_type_size]
| (((uint8_t *)test_case->buf.p_dest_ansi)[i * dest_data_type_size + 1] << 8)
| (((uint8_t *)test_case->buf.p_dest_ansi)[i * dest_data_type_size + 2] << 16);
uint32_t asm_value = ((uint8_t *)test_case->buf.p_dest_asm)[i * dest_data_type_size]
| (((uint8_t *)test_case->buf.p_dest_asm)[i * dest_data_type_size + 1] << 8)
| (((uint8_t *)test_case->buf.p_dest_asm)[i * dest_data_type_size + 2] << 16);
printf("dest_buf[%"PRIi32"] %s ansi = %8"PRIx32" \t asm = %8"PRIx32" \n", i, ((i < 10) ? (" ") : ("")), ansi_value, asm_value);
}
printf("\n");
printf("Source buffer fill:\n");
printf("src_buf_len = %d\n", test_case->src_buf_len);
size_t src_data_type_size = test_case->src_data_type_size;
for (uint32_t i = 0; i < test_case->src_buf_len; i++) {
uint32_t value = ((uint8_t *)test_case->buf.p_src)[i * src_data_type_size]
| (((uint8_t *)test_case->buf.p_src)[i * src_data_type_size + 1] << 8)
| (((uint8_t *)test_case->buf.p_src)[i * src_data_type_size + 2] << 16);
printf("dest_buf[%"PRIi32"] %s = %8"PRIx32" \n", i, ((i < 10) ? (" ") : ("")), value);
}
printf("\n");
#endif
// Canary pixels area must stay 0
const size_t canary_pixels = test_case->canary_pixels;
TEST_ASSERT_EACH_EQUAL_UINT8_MESSAGE(0, (uint8_t *)test_case->buf.p_dest_ansi, canary_pixels * 3, test_msg_buf);
TEST_ASSERT_EACH_EQUAL_UINT8_MESSAGE(0, (uint8_t *)test_case->buf.p_dest_asm, canary_pixels * 3, test_msg_buf);
// dest_buf_asm and dest_buf_ansi must be equal
TEST_ASSERT_EQUAL_UINT8_ARRAY_MESSAGE((uint8_t *)test_case->buf.p_dest_ansi + (canary_pixels * 3), (uint8_t *)test_case->buf.p_dest_asm + (canary_pixels * 3), test_case->active_dest_buf_len, test_msg_buf);
// Data part of the destination buffer and source buffer (not considering matrix padding) must be equal
uint8_t *dest_row_begin = (uint8_t *)test_case->buf.p_dest_asm + (canary_pixels * 3);
uint8_t *src_row_begin = (uint8_t *)test_case->buf.p_src;
for (int row = 0; row < test_case->dest_h; row++) {
TEST_ASSERT_EQUAL_UINT8_ARRAY_MESSAGE(dest_row_begin, src_row_begin, test_case->dest_w * 3, test_msg_buf);
dest_row_begin += (test_case->dest_stride * 3); // Move pointer of the destination buffer to the next row
src_row_begin += (test_case->src_stride * 3); // Move pointer of the source buffer to the next row
}
// Canary pixels area must stay 0
TEST_ASSERT_EACH_EQUAL_UINT8_MESSAGE(0, (uint8_t *)test_case->buf.p_dest_ansi + ((test_case->total_dest_buf_len * 3) - (canary_pixels * 3)), canary_pixels * 3, test_msg_buf);
TEST_ASSERT_EACH_EQUAL_UINT8_MESSAGE(0, (uint8_t *)test_case->buf.p_dest_asm + ((test_case->total_dest_buf_len * 3) - (canary_pixels * 3)), canary_pixels * 3, test_msg_buf);
}

View File

@@ -0,0 +1,3 @@
CONFIG_ESP_TASK_WDT=n
CONFIG_OPTIMIZATION_LEVEL_RELEASE=y
CONFIG_COMPILER_OPTIMIZATION_PERF=y