Skip to content

Commit

Permalink
Merge branch 'feature/rgb_lcd_yuv_converter' into 'master'
Browse files Browse the repository at this point in the history
RGB-YUV converter

Closes IDF-4598

See merge request espressif/esp-idf!19094
  • Loading branch information
suda-morris committed Sep 1, 2022
2 parents 86c4094 + fc1aa28 commit e2634b5
Show file tree
Hide file tree
Showing 15 changed files with 345 additions and 11 deletions.
3 changes: 2 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ repos:
.+test_idf_monitor\/tests\/.+|
.*_pb2.py|
.*.pb-c.h|
.*.pb-c.c
.*.pb-c.c|
.*.yuv
)$
- id: end-of-file-fixer
exclude: *whitespace_excludes
Expand Down
35 changes: 34 additions & 1 deletion components/esp_lcd/include/esp_lcd_panel_rgb.h
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ typedef struct {
lcd_clock_source_t clk_src; /*!< Clock source for the RGB LCD peripheral */
esp_lcd_rgb_timing_t timings; /*!< RGB timing parameters, including the screen resolution */
size_t data_width; /*!< Number of data lines */
size_t bits_per_pixel; /*!< Color depth, in bpp, specially, if set to zero, it will default to `data_width`.
size_t bits_per_pixel; /*!< Frame buffer color depth, in bpp, specially, if set to zero, it will default to `data_width`.
When using a Serial RGB interface, this value could be different from `data_width` */
size_t bounce_buffer_size_px; /*!< If it's non-zero, the driver allocates two DRAM bounce buffers for DMA use.
DMA fetching from DRAM bounce buffer is much faster than PSRAM frame buffer. */
Expand Down Expand Up @@ -209,6 +209,39 @@ esp_err_t esp_lcd_rgb_panel_get_frame_buffer(esp_lcd_panel_handle_t panel, uint3
*/
esp_err_t esp_lcd_rgb_panel_refresh(esp_lcd_panel_handle_t panel);

/**
* @brief LCD color conversion profile
*/
typedef struct {
lcd_color_space_t color_space; /*!< Color space of the image */
lcd_color_range_t color_range; /*!< Color range of the image */
lcd_yuv_sample_t yuv_sample; /*!< YUV sample format of the image */
} esp_lcd_color_conv_profile_t;

/**
* @brief Configuration of YUG-RGB conversion
*/
typedef struct {
lcd_yuv_conv_std_t std; /*!< YUV conversion standard: BT601, BT709 */
esp_lcd_color_conv_profile_t src; /*!< Color conversion profile of the input image */
esp_lcd_color_conv_profile_t dst; /*!< Color conversion profile of the output image */
} esp_lcd_yuv_conv_config_t;

/**
* @brief Configure how to convert the color format between RGB and YUV
*
* @note Pass in `config` as NULL will disable the RGB-YUV converter.
* @note The hardware converter can only parse a "packed" storage format, while "planar" and "semi-planar" format is not supported.
*
* @param[in] panel LCD panel handle, returned from `esp_lcd_new_rgb_panel`
* @param[in] config Configuration of RGB-YUV conversion
* @return
* - ESP_ERR_INVALID_ARG: Configure RGB-YUV conversion failed because of invalid argument
* - ESP_ERR_NOT_SUPPORTED: Configure RGB-YUV conversion failed because the conversion mode is not supported by the hardware
* - ESP_OK: Configure RGB-YUV conversion successfully
*/
esp_err_t esp_lcd_rgb_panel_set_yuv_conversion(esp_lcd_panel_handle_t panel, const esp_lcd_yuv_conv_config_t *config);

#endif // SOC_LCD_RGB_SUPPORTED

#ifdef __cplusplus
Expand Down
71 changes: 62 additions & 9 deletions components/esp_lcd/src/esp_lcd_rgb_panel.c
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ struct esp_rgb_panel_t {
int panel_id; // LCD panel ID
lcd_hal_context_t hal; // Hal layer object
size_t data_width; // Number of data lines
size_t bits_per_pixel; // Color depth, in bpp
size_t fb_bits_per_pixel; // Frame buffer color depth, in bpp
size_t output_bits_per_pixel; // Color depth seen from the output data line. Default to fb_bits_per_pixel, but can be changed by YUV-RGB conversion
size_t sram_trans_align; // Alignment for framebuffer that allocated in SRAM
size_t psram_trans_align; // Alignment for framebuffer that allocated in PSRAM
int disp_gpio_num; // Display control GPIO, which is used to perform action like "disp_off"
Expand Down Expand Up @@ -220,13 +221,13 @@ esp_err_t esp_lcd_new_rgb_panel(const esp_lcd_rgb_panel_config_t *rgb_panel_conf
#endif

// bpp defaults to the number of data lines, but for serial RGB interface, they're not equal
size_t bits_per_pixel = rgb_panel_config->data_width;
size_t fb_bits_per_pixel = rgb_panel_config->data_width;
if (rgb_panel_config->bits_per_pixel) { // override bpp if it's set
bits_per_pixel = rgb_panel_config->bits_per_pixel;
fb_bits_per_pixel = rgb_panel_config->bits_per_pixel;
}
// calculate buffer size
size_t fb_size = rgb_panel_config->timings.h_res * rgb_panel_config->timings.v_res * bits_per_pixel / 8;
size_t bb_size = rgb_panel_config->bounce_buffer_size_px * bits_per_pixel / 8;
size_t fb_size = rgb_panel_config->timings.h_res * rgb_panel_config->timings.v_res * fb_bits_per_pixel / 8;
size_t bb_size = rgb_panel_config->bounce_buffer_size_px * fb_bits_per_pixel / 8;
if (bb_size) {
// we want the bounce can always end in the second buffer
ESP_GOTO_ON_FALSE(fb_size % (2 * bb_size) == 0, ESP_ERR_INVALID_ARG, err, TAG,
Expand Down Expand Up @@ -297,7 +298,8 @@ esp_err_t esp_lcd_new_rgb_panel(const esp_lcd_rgb_panel_config_t *rgb_panel_conf
memcpy(rgb_panel->data_gpio_nums, rgb_panel_config->data_gpio_nums, SOC_LCD_RGB_DATA_WIDTH);
rgb_panel->timings = rgb_panel_config->timings;
rgb_panel->data_width = rgb_panel_config->data_width;
rgb_panel->bits_per_pixel = bits_per_pixel;
rgb_panel->fb_bits_per_pixel = fb_bits_per_pixel;
rgb_panel->output_bits_per_pixel = fb_bits_per_pixel; // by default, the output bpp is the same as the frame buffer bpp
rgb_panel->disp_gpio_num = rgb_panel_config->disp_gpio_num;
rgb_panel->flags.disp_en_level = !rgb_panel_config->flags.disp_active_low;
rgb_panel->flags.no_fb = rgb_panel_config->flags.no_fb;
Expand Down Expand Up @@ -387,6 +389,57 @@ esp_err_t esp_lcd_rgb_panel_refresh(esp_lcd_panel_handle_t panel)
return ESP_OK;
}

esp_err_t esp_lcd_rgb_panel_set_yuv_conversion(esp_lcd_panel_handle_t panel, const esp_lcd_yuv_conv_config_t *config)
{
ESP_RETURN_ON_FALSE(panel, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base);
lcd_hal_context_t *hal = &rgb_panel->hal;
bool en_conversion = config != NULL;

// bits per pixel for different YUV sample
const uint8_t bpp_yuv[] = {
[LCD_YUV_SAMPLE_422] = 16,
[LCD_YUV_SAMPLE_420] = 12,
[LCD_YUV_SAMPLE_411] = 12,
};

if (en_conversion) {
if (memcmp(&config->src, &config->dst, sizeof(config->src)) == 0) {
ESP_RETURN_ON_FALSE(false, ESP_ERR_INVALID_ARG, TAG, "conversion source and destination are the same");
}

if (config->src.color_space == LCD_COLOR_SPACE_YUV && config->dst.color_space == LCD_COLOR_SPACE_RGB) { // YUV->RGB
lcd_ll_set_convert_mode_yuv_to_rgb(hal->dev, config->src.yuv_sample);
// Note, the RGB->YUV conversion only support RGB565
rgb_panel->output_bits_per_pixel = 16;
} else if (config->src.color_space == LCD_COLOR_SPACE_RGB && config->dst.color_space == LCD_COLOR_SPACE_YUV) { // RGB->YUV
lcd_ll_set_convert_mode_rgb_to_yuv(hal->dev, config->dst.yuv_sample);
rgb_panel->output_bits_per_pixel = bpp_yuv[config->dst.yuv_sample];
} else if (config->src.color_space == LCD_COLOR_SPACE_YUV && config->dst.color_space == LCD_COLOR_SPACE_YUV) { // YUV->YUV
lcd_ll_set_convert_mode_yuv_to_yuv(hal->dev, config->src.yuv_sample, config->dst.yuv_sample);
rgb_panel->output_bits_per_pixel = bpp_yuv[config->dst.yuv_sample];
} else {
ESP_RETURN_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, TAG, "unsupported conversion mode");
}

// set conversion standard
lcd_ll_set_yuv_convert_std(hal->dev, config->std);
// set conversion data width
lcd_ll_set_convert_data_width(hal->dev, rgb_panel->data_width);
// set color range
lcd_ll_set_input_color_range(hal->dev, config->src.color_range);
lcd_ll_set_output_color_range(hal->dev, config->dst.color_range);
} else {
// output bpp equals to frame buffer bpp
rgb_panel->output_bits_per_pixel = rgb_panel->fb_bits_per_pixel;
}

// enable or disable RGB-YUV conversion
lcd_ll_enable_rgb_yuv_convert(hal->dev, en_conversion);

return ESP_OK;
}

static esp_err_t rgb_panel_del(esp_lcd_panel_t *panel)
{
esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base);
Expand Down Expand Up @@ -425,7 +478,7 @@ static esp_err_t rgb_panel_init(esp_lcd_panel_t *panel)
// configure blank region timing
lcd_ll_set_blank_cycles(rgb_panel->hal.dev, 1, 1); // RGB panel always has a front and back blank (porch region)
lcd_ll_set_horizontal_timing(rgb_panel->hal.dev, rgb_panel->timings.hsync_pulse_width,
rgb_panel->timings.hsync_back_porch, rgb_panel->timings.h_res * rgb_panel->bits_per_pixel / rgb_panel->data_width,
rgb_panel->timings.hsync_back_porch, rgb_panel->timings.h_res * rgb_panel->output_bits_per_pixel / rgb_panel->data_width,
rgb_panel->timings.hsync_front_porch);
lcd_ll_set_vertical_timing(rgb_panel->hal.dev, rgb_panel->timings.vsync_pulse_width,
rgb_panel->timings.vsync_back_porch, rgb_panel->timings.v_res,
Expand Down Expand Up @@ -500,7 +553,7 @@ static esp_err_t rgb_panel_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int
y_end = MIN(y_end, v_res);
}

int bytes_per_pixel = rgb_panel->bits_per_pixel / 8;
int bytes_per_pixel = rgb_panel->fb_bits_per_pixel / 8;
int pixels_per_line = rgb_panel->timings.h_res;
uint32_t bytes_per_line = bytes_per_pixel * pixels_per_line;
uint8_t *fb = rgb_panel->fbs[rgb_panel->cur_fb_index];
Expand Down Expand Up @@ -862,7 +915,7 @@ static esp_err_t lcd_rgb_panel_select_clock_src(esp_rgb_panel_t *panel, lcd_cloc
static IRAM_ATTR bool lcd_rgb_panel_fill_bounce_buffer(esp_rgb_panel_t *panel, uint8_t *buffer)
{
bool need_yield = false;
int bytes_per_pixel = panel->bits_per_pixel / 8;
int bytes_per_pixel = panel->fb_bits_per_pixel / 8;
if (panel->flags.no_fb) {
if (panel->on_bounce_empty) {
// We don't have a frame buffer here; we need to call a callback to refill the bounce buffer
Expand Down
3 changes: 3 additions & 0 deletions components/esp_lcd/test_apps/rgb_lcd/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(rgb_lcd_panel_test)

target_add_binary_data(rgb_lcd_panel_test.elf "resources/pictures/hello.yuv" BINARY)
target_add_binary_data(rgb_lcd_panel_test.elf "resources/pictures/world.yuv" BINARY)

if(CONFIG_COMPILER_DUMP_RTL_FILES)
add_custom_target(check_test_app_sections ALL
COMMAND ${PYTHON} $ENV{IDF_PATH}/tools/ci/check_callgraph.py
Expand Down
4 changes: 4 additions & 0 deletions components/esp_lcd/test_apps/rgb_lcd/main/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
set(srcs "test_app_main.c"
"test_rgb_panel.c")

if(CONFIG_SOC_LCD_SUPPORT_RGB_YUV_CONV)
list(APPEND srcs "test_yuv_rgb_conv.c")
endif()

# In order for the cases defined by `TEST_CASE` to be linked into the final elf,
# the component can be registered as WHOLE_ARCHIVE
idf_component_register(SRCS ${srcs}
Expand Down
105 changes: 105 additions & 0 deletions components/esp_lcd/test_apps/rgb_lcd/main/test_yuv_rgb_conv.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "unity.h"
#include "esp_lcd_panel_rgb.h"
#include "esp_lcd_panel_ops.h"
#include "esp_random.h"
#include "esp_timer.h"
#include "esp_attr.h"
#include "spi_flash_mmap.h"
#include "test_rgb_board.h"

#define TEST_IMG_SIZE (320 * 320 * sizeof(uint16_t))

// YUV images are embedded in the firmware binary
extern const uint8_t image_hello_yuv_start[] asm("_binary_hello_yuv_start");
extern const uint8_t image_hello_yuv_end[] asm("_binary_hello_yuv_end");
extern const uint8_t image_world_yuv_start[] asm("_binary_world_yuv_start");
extern const uint8_t image_world_yuv_end[] asm("_binary_world_yuv_end");

TEST_CASE("lcd_rgb_panel_yuv422_conversion", "[lcd]")
{
esp_lcd_panel_handle_t panel_handle = NULL;
esp_lcd_rgb_panel_config_t panel_config = {
.data_width = 16,
.psram_trans_align = 64,
.bits_per_pixel = 16, // YUV422: 16bits per pixel
.clk_src = LCD_CLK_SRC_DEFAULT,
.disp_gpio_num = TEST_LCD_DISP_EN_GPIO,
.pclk_gpio_num = TEST_LCD_PCLK_GPIO,
.vsync_gpio_num = TEST_LCD_VSYNC_GPIO,
.hsync_gpio_num = TEST_LCD_HSYNC_GPIO,
.de_gpio_num = TEST_LCD_DE_GPIO,
.data_gpio_nums = {
TEST_LCD_DATA0_GPIO,
TEST_LCD_DATA1_GPIO,
TEST_LCD_DATA2_GPIO,
TEST_LCD_DATA3_GPIO,
TEST_LCD_DATA4_GPIO,
TEST_LCD_DATA5_GPIO,
TEST_LCD_DATA6_GPIO,
TEST_LCD_DATA7_GPIO,
TEST_LCD_DATA8_GPIO,
TEST_LCD_DATA9_GPIO,
TEST_LCD_DATA10_GPIO,
TEST_LCD_DATA11_GPIO,
TEST_LCD_DATA12_GPIO,
TEST_LCD_DATA13_GPIO,
TEST_LCD_DATA14_GPIO,
TEST_LCD_DATA15_GPIO,
},
.timings = {
.pclk_hz = TEST_LCD_PIXEL_CLOCK_HZ,
.h_res = TEST_LCD_H_RES,
.v_res = TEST_LCD_V_RES,
.hsync_back_porch = 68,
.hsync_front_porch = 20,
.hsync_pulse_width = 5,
.vsync_back_porch = 18,
.vsync_front_porch = 4,
.vsync_pulse_width = 1,
},
.flags.fb_in_psram = 1, // allocate frame buffer in PSRAM
};

printf("Create RGB LCD panel\r\n");
TEST_ESP_OK(esp_lcd_new_rgb_panel(&panel_config, &panel_handle));
TEST_ESP_OK(esp_lcd_panel_reset(panel_handle));

printf("Set YUV-RGB conversion profile\r\n");
esp_lcd_yuv_conv_config_t conv_config = {
.std = LCD_YUV_CONV_STD_BT601,
.src = {
.color_range = LCD_COLOR_RANGE_FULL,
.color_space = LCD_COLOR_SPACE_RGB,
},
.dst = {
.color_range = LCD_COLOR_RANGE_FULL,
.color_space = LCD_COLOR_SPACE_RGB,
},
};
TEST_ESP_ERR(ESP_ERR_INVALID_ARG, esp_lcd_rgb_panel_set_yuv_conversion(panel_handle, &conv_config));

conv_config.src.color_space = LCD_COLOR_SPACE_YUV;
conv_config.src.yuv_sample = LCD_YUV_SAMPLE_422;
TEST_ESP_OK(esp_lcd_rgb_panel_set_yuv_conversion(panel_handle, &conv_config));

TEST_ESP_OK(esp_lcd_panel_init(panel_handle));

printf("Draw YUV images\r\n");
for (int i = 0; i < 4; i++) {
TEST_ESP_OK(esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, 320, 320, image_hello_yuv_start));
vTaskDelay(pdMS_TO_TICKS(1000));
TEST_ESP_OK(esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, 320, 320, image_world_yuv_start));
vTaskDelay(pdMS_TO_TICKS(1000));
}

TEST_ESP_OK(esp_lcd_panel_del(panel_handle));
}
5 changes: 5 additions & 0 deletions components/esp_lcd/test_apps/rgb_lcd/resources/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# How to generate the YUV image from the PNG image

```bash
ffmpeg -i hello.png -pix_fmt uyvy422 hello.yuv
```
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

Large diffs are not rendered by default.

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

Large diffs are not rendered by default.

Loading

0 comments on commit e2634b5

Please sign in to comment.