From be85ad7369f71280ba6920c956ebf2dcc7f159ae Mon Sep 17 00:00:00 2001 From: Fabian Blatz Date: Wed, 30 Oct 2024 09:18:28 +0100 Subject: [PATCH] feat(sw): Add method to convert a htiled I1 buffer to vtiled (#7129) --- docs/details/main-components/display.rst | 25 ++++ src/draw/sw/lv_draw_sw.c | 29 ++++ src/draw/sw/lv_draw_sw.h | 17 +++ .../draw/test_draw_sw_post_process.c | 129 ++++++++++++++++++ 4 files changed, 200 insertions(+) diff --git a/docs/details/main-components/display.rst b/docs/details/main-components/display.rst index 8b2b9f68c..b14500c83 100644 --- a/docs/details/main-components/display.rst +++ b/docs/details/main-components/display.rst @@ -488,6 +488,31 @@ meaning ``(horizontal_resolution x vertical_resolution / 8) + 8`` bytes. As LVGL can not handle fractional width make sure to round the horizontal resolution to 8 bits (for example 90 to 96). +The :cpp:func:`lv_draw_sw_i1_convert_to_vtiled` function is used to convert a draw buffer in I1 color format from a row-wise (htiled) to a column-wise (vtiled) buffer layout. +This conversion is necessary for certain display controllers that require a different draw buffer mapping. The function assumes that the buffer width and height are rounded to a multiple of 8. +The bit order of the resulting vtiled buffer can be specified using the `bit_order_lsb` parameter. +For more details, refer to the implementation in :cpp:func:`lv_draw_sw_i1_convert_to_vtiled` in :file:`src/draw/sw/lv_draw_sw.c`. + +To ensure that the redrawn areas start and end on byte boundaries, you can add a rounder callback to your display driver. This callback will round the width and height to the nearest multiple of 8. + +Here is an example of how to implement and set a rounder callback: + +.. code:: c + + static void my_rounder_cb(lv_event_t *e) + { + lv_area_t *area = lv_event_get_param(e); + + /* Round the height to the nearest multiple of 8 */ + area->y1 = (area->y1 & ~0x7); + area->y2 = (area->y2 | 0x7); + } + + lv_display_add_event_cb(display, my_rounder_cb, LV_EVENT_INVALIDATE_AREA, display); + +In this example, the `my_rounder_cb` function rounds the coordinates of the redrawn area to the nearest multiple of 8. +The `x1` and `y1` coordinates are rounded down, while the `x2` and `y2` coordinates are rounded up. This ensures that the +width and height of the redrawn area are always multiples of 8. Constraints on Redrawn Area --------------------------- diff --git a/src/draw/sw/lv_draw_sw.c b/src/draw/sw/lv_draw_sw.c index 5e656c4bd..01fae648a 100644 --- a/src/draw/sw/lv_draw_sw.c +++ b/src/draw/sw/lv_draw_sw.c @@ -282,6 +282,35 @@ void lv_draw_sw_i1_invert(void * buf, uint32_t buf_size) } } +void lv_draw_sw_i1_convert_to_vtiled(const void * buf, uint32_t buf_size, uint32_t width, uint32_t height, + void * out_buf, + uint32_t out_buf_size, bool bit_order_lsb) +{ + LV_ASSERT(buf && out_buf); + LV_ASSERT(width % 8 == 0 && height % 8 == 0); + LV_ASSERT(buf_size == (width / 8) * height); + LV_ASSERT(out_buf_size >= buf_size); + + lv_memset(out_buf, 0, out_buf_size); + + const uint8_t * src_buf = (uint8_t *)buf; + uint8_t * dst_buf = (uint8_t *)out_buf; + + for(uint32_t y = 0; y < height; y++) { + for(uint32_t x = 0; x < width; x++) { + uint32_t src_index = y * width + x; + uint32_t dst_index = x * height + y; + uint8_t bit = (src_buf[src_index / 8] >> (7 - (src_index % 8))) & 0x01; + if(bit_order_lsb) { + dst_buf[dst_index / 8] |= (bit << (dst_index % 8)); + } + else { + dst_buf[dst_index / 8] |= (bit << (7 - (dst_index % 8))); + } + } + } +} + void lv_draw_sw_rotate(const void * src, void * dest, int32_t src_width, int32_t src_height, int32_t src_stride, int32_t dest_stride, lv_display_rotation_t rotation, lv_color_format_t color_format) { diff --git a/src/draw/sw/lv_draw_sw.h b/src/draw/sw/lv_draw_sw.h index 1152fe588..48b239371 100644 --- a/src/draw/sw/lv_draw_sw.h +++ b/src/draw/sw/lv_draw_sw.h @@ -171,6 +171,23 @@ void lv_draw_sw_rgb565_swap(void * buf, uint32_t buf_size_px); */ void lv_draw_sw_i1_invert(void * buf, uint32_t buf_size); + +/** + * Convert a draw buffer in I1 color format from htiled (row-wise) + * to vtiled (column-wise) buffer layout. The conversion assumes that the buffer width + * and height is rounded to a multiple of 8. + * @param buf pointer to the buffer to be converted + * @param buf_size size of the buffer in bytes + * @param width width of the buffer + * @param height height of the buffer + * @param out_buf pointer to the output buffer + * @param out_buf_size size of the output buffer in bytes + * @param bit_order_lsb bit order of the resulting vtiled buffer + */ +void lv_draw_sw_i1_convert_to_vtiled(const void * buf, uint32_t buf_size, uint32_t width, uint32_t height, + void * out_buf, + uint32_t out_buf_size, bool bit_order_lsb); + /** * Rotate a buffer into another buffer * @param src the source buffer diff --git a/tests/src/test_cases/draw/test_draw_sw_post_process.c b/tests/src/test_cases/draw/test_draw_sw_post_process.c index 4d8397ac0..06973d155 100644 --- a/tests/src/test_cases/draw/test_draw_sw_post_process.c +++ b/tests/src/test_cases/draw/test_draw_sw_post_process.c @@ -315,4 +315,133 @@ void test_invert(void) TEST_ASSERT_EQUAL_UINT8_ARRAY(&expected_buf[3], &buf3[3], 2); } +void test_vtile_small(void) +{ + uint8_t src_buf[8] = {0x3C, 0x42, 0x81, 0xA5, 0x81, 0x81, 0x42, 0x3C}; + uint8_t dst_buf[8] = {0}; + + uint8_t expected_buf_msb[8] = {0x3C, 0x42, 0x91, 0x81, 0x81, 0x91, 0x42, 0x3C}; + uint8_t expected_buf_lsb[8] = {0x3C, 0x42, 0x89, 0x81, 0x81, 0x89, 0x42, 0x3C}; + + lv_draw_sw_i1_convert_to_vtiled(src_buf, 8, 8, 8, dst_buf, 8, false); + TEST_ASSERT_EQUAL_UINT8_ARRAY(expected_buf_msb, dst_buf, 8); + + lv_draw_sw_i1_convert_to_vtiled(src_buf, 8, 8, 8, dst_buf, 8, true); + TEST_ASSERT_EQUAL_UINT8_ARRAY(expected_buf_lsb, dst_buf, 8); +} + +void test_vtile_rectangular(void) +{ + uint8_t src_buf[80] = { + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x40, 0x80, 0x83, 0xE1, 0x00, + 0x40, 0x80, 0x84, 0x01, 0x00, + 0x40, 0x41, 0x08, 0x01, 0x00, + 0x40, 0x41, 0x10, 0x01, 0x00, + 0x40, 0x41, 0x20, 0x01, 0x00, + 0x40, 0x41, 0x20, 0x01, 0x00, + 0x40, 0x41, 0x20, 0x01, 0x00, + 0x40, 0x41, 0x20, 0x01, 0x00, + 0x40, 0x23, 0x20, 0x01, 0x00, + 0x40, 0x22, 0x20, 0xF1, 0x00, + 0x40, 0x14, 0x20, 0x11, 0x00, + 0x40, 0x14, 0x10, 0x11, 0x00, + 0x40, 0x08, 0x08, 0x11, 0x00, + 0x7E, 0x08, 0x07, 0xF1, 0xF8, + 0x00, 0x00, 0x00, 0x00, 0x00, + }; + uint8_t dst_buf[80] = {0}; + + uint8_t expected_buf_msb[80] = { + 0x00, 0x00, + 0x7F, 0xFE, + 0x00, 0x02, + 0x00, 0x02, + 0x00, 0x02, + 0x00, 0x02, + 0x00, 0x02, + 0x00, 0x00, + 0x60, 0x00, + 0x1F, 0x80, + 0x00, 0x60, + 0x00, 0x18, + 0x00, 0x06, + 0x00, 0x18, + 0x00, 0x60, + 0x3F, 0x80, + 0xC0, 0x00, + 0x00, 0x00, + 0x07, 0xF0, + 0x08, 0x08, + 0x10, 0x04, + 0x20, 0x02, + 0x80, 0x02, + 0x80, 0x02, + 0x80, 0x22, + 0x80, 0x22, + 0x80, 0x22, + 0x00, 0x3E, + 0x00, 0x00, + 0x00, 0x00, + 0x00, 0x00, + 0xFF, 0xFE, + 0x00, 0x02, + 0x00, 0x02, + 0x00, 0x02, + 0x00, 0x02, + 0x00, 0x02, + 0x00, 0x00, + 0x00, 0x00, + 0x00, 0x00, + }; + uint8_t expected_buf_lsb[80] = { + 0x00, 0x00, + 0xFE, 0x7F, + 0x00, 0x40, + 0x00, 0x40, + 0x00, 0x40, + 0x00, 0x40, + 0x00, 0x40, + 0x00, 0x00, + 0x06, 0x00, + 0xF8, 0x01, + 0x00, 0x06, + 0x00, 0x18, + 0x00, 0x60, + 0x00, 0x18, + 0x00, 0x06, + 0xF8, 0x03, + 0x06, 0x00, + 0x00, 0x00, + 0xE0, 0x0F, + 0x10, 0x10, + 0x08, 0x20, + 0x04, 0x40, + 0x02, 0x40, + 0x02, 0x40, + 0x02, 0x44, + 0x02, 0x44, + 0x02, 0x44, + 0x00, 0x7C, + 0x00, 0x00, + 0x00, 0x00, + 0x00, 0x00, + 0xFE, 0x7F, + 0x00, 0x40, + 0x00, 0x40, + 0x00, 0x40, + 0x00, 0x40, + 0x00, 0x40, + 0x00, 0x00, + 0x00, 0x00, + 0x00, 0x00, + }; + + lv_draw_sw_i1_convert_to_vtiled(src_buf, 80, 40, 16, dst_buf, 80, false); + TEST_ASSERT_EQUAL_UINT8_ARRAY(expected_buf_msb, dst_buf, 8); + + lv_draw_sw_i1_convert_to_vtiled(src_buf, 80, 40, 16, dst_buf, 80, true); + TEST_ASSERT_EQUAL_UINT8_ARRAY(expected_buf_lsb, dst_buf, 8); +} + #endif