From 17f6478a32043db28d29e23d01bdb189cb87030e Mon Sep 17 00:00:00 2001 From: schneider <schneider@blinkenlichts.net> Date: Sun, 14 Feb 2021 15:19:13 +0100 Subject: [PATCH] change(blit): Allow to blit images with alpha channel --- bootloader/bootloader-display.c | 3 +- epicardium/epicardium.h | 24 +++-- epicardium/main.c | 9 +- epicardium/modules/display.c | 108 +++++++++++++--------- epicardium/modules/panic.c | 3 +- hw-tests/dual-core/main.c | 3 +- hw-tests/hello-world/main.c | 3 +- hw-tests/ips/main.c | 6 +- lib/gfx/framebuffer.c | 18 ++++ lib/gfx/framebuffer.h | 1 + lib/gfx/gfx.c | 153 ++++++++++++++++++-------------- lib/gfx/gfx.h | 14 +-- pycardium/modules/py/display.py | 38 ++++---- pycardium/modules/sys_display.c | 56 ++---------- 14 files changed, 228 insertions(+), 211 deletions(-) diff --git a/bootloader/bootloader-display.c b/bootloader/bootloader-display.c index d04db551d..9b4b66fb4 100644 --- a/bootloader/bootloader-display.c +++ b/bootloader/bootloader-display.c @@ -8,13 +8,12 @@ static void bootloader_display_splash(void) { - gfx_copy_region( + gfx_copy_region_rle_mono( &display_screen, 0, 0, 160, 80, - GFX_RLE_MONO, sizeof(splash), (const void *)(splash) ); diff --git a/epicardium/epicardium.h b/epicardium/epicardium.h index 2cbe812e4..33edc9e61 100644 --- a/epicardium/epicardium.h +++ b/epicardium/epicardium.h @@ -1652,6 +1652,16 @@ enum disp_font_name { DISP_FONT24 = 4, }; +/* + * Image data type + */ +enum epic_rgb_format { + EPIC_RGB8 = 0, + EPIC_RGBA8 = 1, + EPIC_RGB565 = 2, + EPIC_RGBA5551 = 3, + }; + /** * Prints a string into the display framebuffer with font type selectable * @@ -1699,22 +1709,22 @@ API(API_DISP_PIXEL, int epic_disp_pixel( )); /** - * Blit an image buffer to display + * Blits an image buffer to the display * * :param x: x position * :param y: y position - * :param w: image width - * :param h: image height - * :param img: image data (rgb565) - * :param alpha: 8 bit alpha channel. Currently unused. + * :param w: Image width + * :param h: Image height + * :param img: Image data + * :param format: Format of the image data. One of :c:type:`epic_rgb_format`. */ API(API_DISP_BLIT, int epic_disp_blit( int16_t x, int16_t y, int16_t w, int16_t h, - uint16_t *img, - uint8_t *alpha + void *img, + enum epic_rgb_format format )); /** diff --git a/epicardium/main.c b/epicardium/main.c index 270f2b0a8..f4c7d9d94 100644 --- a/epicardium/main.c +++ b/epicardium/main.c @@ -48,14 +48,7 @@ int main(void) epic_disp_clear(0x0000); gfx_copy_region( - &display_screen, - 0, - 0, - 160, - 80, - GFX_RAW, - sizeof(version_splash), - version_splash + &display_screen, 0, 0, 160, 80, GFX_RGB565, version_splash ); if (strcmp(CARD10_VERSION, "v1.16") != 0) { diff --git a/epicardium/modules/display.c b/epicardium/modules/display.c index d7607a202..05b0eaec1 100644 --- a/epicardium/modules/display.c +++ b/epicardium/modules/display.c @@ -92,61 +92,81 @@ int epic_disp_blit( int16_t pos_y, int16_t width, int16_t height, - uint16_t *img, - uint8_t *alpha + void *img, + enum epic_rgb_format format ) { - /* TODO: alpha is not supported yet */ int cl = check_lock(); if (cl < 0) { return cl; - } else { - int16_t offset_x = (pos_x < 0) ? -pos_x : 0; - int16_t count_x = width - offset_x; - int16_t offset_y = (pos_y < 0) ? -pos_y : 0; - int16_t count_y = height - offset_y; + } - if (pos_x + width >= 160) { - count_x -= (pos_x + width) % 160; - } - if (pos_y + height >= 80) { - count_y -= (pos_y + height) % 80; - } + int16_t offset_x = (pos_x < 0) ? -pos_x : 0; + int16_t count_x = width - offset_x; + int16_t offset_y = (pos_y < 0) ? -pos_y : 0; + int16_t count_y = height - offset_y; + + if (pos_x + width >= 160) { + count_x -= (pos_x + width) % 160; + } + if (pos_y + height >= 80) { + count_y -= (pos_y + height) % 80; + } + + size_t bpp; + enum gfx_encoding encoding; + + switch (format) { + case EPIC_RGB565: + bpp = 2; + encoding = GFX_RGB565; + break; + case EPIC_RGBA5551: + bpp = 2; + encoding = GFX_RGBA5551; + break; + case EPIC_RGB8: + bpp = 3; + encoding = GFX_RGB8; + break; + case EPIC_RGBA8: + bpp = 4; + encoding = GFX_RGBA8; + break; + default: + return -1; + break; + } - if (offset_x == 0 && offset_y == 0 && count_x == width && - count_y == height) { - /* Simply copy full image, no cropping or alpha blending */ + if (offset_x == 0 && offset_y == 0 && count_x == width && + count_y == height) { + /* Copy full image. No cropping.*/ + gfx_copy_region( + &display_screen, + pos_x, + pos_y, + width, + height, + encoding, + img + ); + } else { + /* Copy cropped image line by line. */ + int16_t curr_y; + for (curr_y = offset_y; curr_y < offset_y + count_y; curr_y++) { + uint8_t *line = img + (curr_y * width + offset_x) * bpp; gfx_copy_region( &display_screen, - pos_x, - pos_y, - width, - height, - GFX_RAW, - width * height * 2, - img + pos_x + offset_x, + pos_y + curr_y, + count_x, + 1, + encoding, + line ); - } else { - /* Copy cropped image line by line */ - for (int16_t curr_y = offset_y; - curr_y < offset_y + count_y; - curr_y++) { - uint16_t *curr_img = - img + (curr_y * width + offset_x); - gfx_copy_region( - &display_screen, - pos_x + offset_x, - pos_y + curr_y, - count_x, - 1, - GFX_RAW, - count_x * 2, - curr_img - ); - } } - - return 0; } + + return 0; } int epic_disp_line( diff --git a/epicardium/modules/panic.c b/epicardium/modules/panic.c index ed4575a56..b365b223d 100644 --- a/epicardium/modules/panic.c +++ b/epicardium/modules/panic.c @@ -120,13 +120,12 @@ static void faultsplash(const char *msg) { LCD_SetBacklight(100); - gfx_copy_region( + gfx_copy_region_rle_mono( &display_screen, 0, 0, 160, 80, - GFX_RLE_MONO, sizeof(faultsplash_rle), faultsplash_rle ); diff --git a/hw-tests/dual-core/main.c b/hw-tests/dual-core/main.c index 30026b805..3de7f4715 100644 --- a/hw-tests/dual-core/main.c +++ b/hw-tests/dual-core/main.c @@ -28,8 +28,7 @@ int main(void) 0, 160, 80, - GFX_RAW, - sizeof(Heart), + GFX_RGB565, (const void *)(Heart) ); gfx_update(&display_screen); diff --git a/hw-tests/hello-world/main.c b/hw-tests/hello-world/main.c index 5f2a82aad..39a75e35f 100644 --- a/hw-tests/hello-world/main.c +++ b/hw-tests/hello-world/main.c @@ -36,8 +36,7 @@ int main(void) 0, 160, 80, - GFX_RAW, - sizeof(Heart), + GFX_RGB565, (const void *)(Heart) ); gfx_update(&display_screen); diff --git a/hw-tests/ips/main.c b/hw-tests/ips/main.c index b638a7832..e1ff5fa16 100644 --- a/hw-tests/ips/main.c +++ b/hw-tests/ips/main.c @@ -43,8 +43,7 @@ int main(void) 0, 40, 40, - GFX_RAW, - 40 * 40 * 2, + GFX_RGB565, (const void *)(gImage_40X40) ); gfx_copy_region( @@ -53,8 +52,7 @@ int main(void) 0, 160, 80, - GFX_RAW, - 160 * 80 * 2, + GFX_RGB565, (const void *)(gImage_160X80) ); gfx_update(&display_screen); diff --git a/lib/gfx/framebuffer.c b/lib/gfx/framebuffer.c index 6c8dc3aac..314f3e2f0 100644 --- a/lib/gfx/framebuffer.c +++ b/lib/gfx/framebuffer.c @@ -99,3 +99,21 @@ void fb_setpixel(struct framebuffer *fb, int x, int y, Color c) pixel[0] = color[1]; } } + +Color fb_getpixel(struct framebuffer *fb, int x, int y) +{ + uint8_t *pixel = fb_pixel(fb, x, y); + if (pixel == NULL) + return 0; + + Color c; + uint8_t *color = (uint8_t *)(&c); + const size_t bpp = fb_bytes_per_pixel(fb); + switch (bpp) { + default: + case 2: + color[0] = pixel[1]; + color[1] = pixel[0]; + } + return c; +} diff --git a/lib/gfx/framebuffer.h b/lib/gfx/framebuffer.h index e2358c734..0bd0a593a 100644 --- a/lib/gfx/framebuffer.h +++ b/lib/gfx/framebuffer.h @@ -27,6 +27,7 @@ struct framebuffer { size_t fb_bytes_per_pixel(const struct framebuffer *fb); void *fb_pixel(struct framebuffer *fb, int x, int y); void fb_setpixel(struct framebuffer *fb, int x, int y, Color c); +Color fb_getpixel(struct framebuffer *fb, int x, int y); void fb_clear_to_color(struct framebuffer *fb, Color c); void fb_clear(struct framebuffer *fb); Color fb_encode_color_rgb(struct framebuffer *fb, int r, int g, int b); diff --git a/lib/gfx/gfx.c b/lib/gfx/gfx.c index 886ecdeeb..172dc9079 100644 --- a/lib/gfx/gfx.c +++ b/lib/gfx/gfx.c @@ -13,6 +13,42 @@ const struct gfx_color_rgb gfx_colors_rgb[COLORS] = { { 255, 255, 0 } /* YELLOW */ }; +static inline uint16_t rgb8_to_rgb565(const uint8_t *bytes) +{ + return ((bytes[0] & 0b11111000) << 8) | ((bytes[1] & 0b11111100) << 3) | + (bytes[2] >> 3); +} + +static inline void rgb565_to_rgb8(uint16_t c, uint8_t *bytes) +{ + bytes[0] = (c & 0b1111100000000000) >> 8; + bytes[1] = (c & 0b0000011111100000) >> 3; + bytes[2] = (c & 0b0000000000011111) << 3; +} + +static inline uint16_t rgba5551_to_rgb565(const uint8_t *bytes) +{ + return (bytes[1] << 8) | (bytes[0] & 0b11000000) | + ((bytes[0] & 0b00111110) >> 1); +} + +static inline uint8_t apply_alpha(uint8_t in, uint8_t bg, uint8_t alpha) +{ + /* Not sure if it is worth (or even a good idea) to have + * the special cases here. */ + if (bg == 0) { + return (in * alpha) / 255; + } + + uint8_t beta = 255 - alpha; + + if (bg == 255) { + return ((in * alpha) / 255) + beta; + } + + return (in * alpha + bg * beta) / 255; +} + void gfx_setpixel(struct gfx_region *r, int x, int y, Color c) { if (x < 0 || y < 0) @@ -23,6 +59,25 @@ void gfx_setpixel(struct gfx_region *r, int x, int y, Color c) fb_setpixel(r->fb, r->x + x, r->y + y, c); } +void gfx_setpixel_rgba(struct gfx_region *r, int x, int y, const uint8_t *c) +{ + if (x < 0 || y < 0) + return; + if ((size_t)x >= r->width || (size_t)y >= r->height) + return; + + uint8_t pixel[3]; + Color p = fb_getpixel(r->fb, r->x + x, r->y + y); + rgb565_to_rgb8(p, pixel); + + uint8_t alpha = c[3]; + pixel[0] = apply_alpha(c[0], pixel[0], alpha); + pixel[1] = apply_alpha(c[1], pixel[1], alpha); + pixel[2] = apply_alpha(c[2], pixel[2], alpha); + + fb_setpixel(r->fb, r->x + x, r->y + y, rgb8_to_rgb565(pixel)); +} + struct gfx_region gfx_screen(struct framebuffer *fb) { struct gfx_region r = { .fb = fb, @@ -270,61 +325,48 @@ Color gfx_color(struct gfx_region *reg, enum gfx_color color) return gfx_color_rgb(reg, c->r, c->g, c->b); } -static void gfx_copy_region_raw( +void gfx_copy_region( struct gfx_region *reg, int x, int y, int w, int h, - size_t size, - const void *p + enum gfx_encoding encoding, + const uint8_t *p ) { - size_t bpp = size / (w * h); - for (int y_ = 0; y_ < h; y_++) { for (int x_ = 0; x_ < w; x_++) { Color c; + uint8_t alpha; - switch (bpp) { - default: - case 2: + switch (encoding) { + case GFX_RGB565: + /* Assuming alignment here */ c = *(const uint16_t *)(p); + gfx_setpixel(reg, x + x_, y + y_, c); + p += 2; + break; + case GFX_RGB8: + c = rgb8_to_rgb565(p); + gfx_setpixel(reg, x + x_, y + y_, c); + p += 3; + break; + case GFX_RGBA5551: + c = rgba5551_to_rgb565(p); + alpha = (*p) & 1; + if (alpha) { + gfx_setpixel(reg, x + x_, y + y_, c); + } + p += 2; + break; + case GFX_RGBA8: + gfx_setpixel_rgba(reg, x + x_, y + y_, p); + p += 4; + break; + default: + return; break; } - - gfx_setpixel(reg, x + x_, y + y_, c); - p += bpp; - } - } -} - -static void gfx_copy_region_mono( - struct gfx_region *reg, - int x, - int y, - int w, - int h, - size_t size, - const void *p -) { - const char *bp = p; - int bit = 0; - Color white = gfx_color(reg, WHITE); - Color black = gfx_color(reg, BLACK); - - for (int y_ = 0; y_ < h; y_++) { - for (int x_ = 0; x_ < w; x_++) { - int value = *bp & (1 << bit); - if (++bit >= 8) { - bp++; - bit %= 8; - - if ((const void *)(bp) >= (p + size)) - return; - } - - Color c = value ? white : black; - gfx_setpixel(reg, x + x_, y + y_, c); } } } @@ -336,7 +378,7 @@ static void gfx_copy_region_mono( * significant bit determines the color, the remaining 7 bits determine the * amount. */ -static void gfx_copy_region_rle_mono( +void gfx_copy_region_rle_mono( struct gfx_region *reg, int x, int y, @@ -363,31 +405,6 @@ static void gfx_copy_region_rle_mono( } } -void gfx_copy_region( - struct gfx_region *reg, - int x, - int y, - int w, - int h, - enum gfx_encoding encoding, - size_t size, - const void *p -) { - switch (encoding) { - case GFX_RAW: - gfx_copy_region_raw(reg, x, y, w, h, size, p); - break; - case GFX_MONO: - gfx_copy_region_mono(reg, x, y, w, h, size, p); - break; - case GFX_RLE_MONO: - gfx_copy_region_rle_mono(reg, x, y, w, h, size, p); - break; - default: - break; - } -} - void gfx_copy_raw(struct gfx_region *reg, const void *p, size_t size) { fb_copy_raw(reg->fb, p, size); diff --git a/lib/gfx/gfx.h b/lib/gfx/gfx.h index 4c9a16e50..c785b73d8 100644 --- a/lib/gfx/gfx.h +++ b/lib/gfx/gfx.h @@ -44,9 +44,12 @@ enum gfx_color { }; enum gfx_encoding { - GFX_RAW, + GFX_RGB565, GFX_MONO, - GFX_RLE_MONO + GFX_RLE_MONO, + GFX_RGB8, + GFX_RGBA8, + GFX_RGBA5551 }; struct gfx_color_rgb { @@ -57,9 +60,10 @@ struct gfx_color_rgb { Color gfx_color(struct gfx_region *reg, enum gfx_color color); -void gfx_copy_region(struct gfx_region *reg, int x, int y, int w, int h, - enum gfx_encoding encoding, size_t size, - const void *p); +void gfx_copy_region_rle_mono(struct gfx_region *reg, int x, int y, int w, int h, + size_t size, const void *p); +void gfx_copy_region( struct gfx_region *reg, int x, int y, int w, int h, + enum gfx_encoding encoding, const uint8_t *p); void gfx_copy_raw(struct gfx_region *reg, const void *p, size_t size); #endif diff --git a/pycardium/modules/py/display.py b/pycardium/modules/py/display.py index 1c94d47b3..4884b1e7d 100644 --- a/pycardium/modules/py/display.py +++ b/pycardium/modules/py/display.py @@ -8,6 +8,11 @@ FONT16 = 2 FONT20 = 3 FONT24 = 4 +RGB8 = 0 +RGBA8 = 1 +RGB565 = 2 +RGBA5551 = 3 + class Display: """ @@ -120,7 +125,7 @@ class Display: sys_display.pixel(x, y, col) return self - def blit(self, x, y, w, h, img, rgb565=False, alpha=None): + def blit(self, x, y, w, h, img, format=RGB8): """ Draws an image on the display. @@ -128,16 +133,20 @@ class Display: :param y: Y coordinate :param w: Image width :param h: Image height - :param img: Buffer with pixel data. Default format is RGB with 8 bits per channel. - :param alpha: Alpha mask for `img` - :param rgb565: Set to `True` if the data supplied is in rgb565 format instead of 8 bit RGB. + :param img: Buffer with pixel data + :param format: Format of the RGB data. One of ``display.RGB8``, `` + + - ``display.RGB8``: 24 bit RGB. + - ``display.RGBA8``: 24 bit RGB + 8 bit alpha. + - ``display.RGB565``: 16 bit RGB. This consumes 1 byte less RAM per pixel than ``display.RGB8``. + - ``display.RGBA5551``: 15 bit RGB + 1 bit alpha. + + Default is ``display.RGB8``. - .. note:: - Alpha mask support is not yet implemented. .. versionadded:: 1.17 - **Example with RGB data:** + **Example with RGB8 data:** .. code-block:: python @@ -153,7 +162,7 @@ class Display: d.update() - **Example with rgb565 data:** + **Example with RGB565 data:** .. code-block:: python @@ -165,7 +174,7 @@ class Display: img = array.array('H', [0x07E0 for x in range(32 * 20)]) with display.open() as d: d.clear() - d.blit(10, 10, 32, 20, img, rgb565=True) + d.blit(10, 10, 32, 20, img, format=display.RGB565) d.update() @@ -180,20 +189,13 @@ class Display: f = framebuf.FrameBuffer(bytearray(160 * 80 * 2), 160, 80, framebuf.RGB565) with display.open() as d: f.text("Hello World", 0, 0, 0xF800) # red - d.blit(0, 0, 160, 80, f, rgb565=True) + d.blit(0, 0, 160, 80, f, format=display.RGB565) d.update() """ - # TODO: alpha is not yet supported by epicardium - if alpha is not None: - raise ValueError("alpha not yet supported") - - if alpha is None: - sys_display.blit(x, y, w, h, img, rgb565) - else: - sys_display.blit(x, y, w, h, img, rgb565, alpha) + sys_display.blit(x, y, w, h, img, format) return self diff --git a/pycardium/modules/sys_display.c b/pycardium/modules/sys_display.c index 37011cae8..60a99aadd 100644 --- a/pycardium/modules/sys_display.c +++ b/pycardium/modules/sys_display.c @@ -97,14 +97,11 @@ static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN( /* blit image to display */ static mp_obj_t mp_display_blit(size_t n_args, const mp_obj_t *args) { - /* Required arguments: posx, posy (on display), - width, height (of image), - buffer (rgb data of image) */ - int pos_x = mp_obj_get_int(args[0]); - int pos_y = mp_obj_get_int(args[1]); - int width = mp_obj_get_int(args[2]); - int height = mp_obj_get_int(args[3]); - bool rgb565 = mp_obj_is_true(args[5]); + int pos_x = mp_obj_get_int(args[0]); + int pos_y = mp_obj_get_int(args[1]); + int width = mp_obj_get_int(args[2]); + int height = mp_obj_get_int(args[3]); + enum epic_rgb_format format = mp_obj_get_int(args[5]); mp_buffer_info_t img; int res = 0; @@ -114,46 +111,7 @@ static mp_obj_t mp_display_blit(size_t n_args, const mp_obj_t *args) mp_raise_TypeError("'img' does not support buffer protocol."); } - int bpp = rgb565 ? 2 : 3; - if ((int)img.len < width * height * bpp) { - mp_raise_ValueError("'img' is too small."); - } - - uint16_t *buf = NULL; - if (rgb565) { - buf = (uint16_t *)img.buf; - } else { - /* Will raise an exception if out of memory: */ - buf = m_malloc(width * height * bpp); - for (int i = 0; i < width * height; i++) { - buf[i] = rgb888_to_rgb565(((uint8_t *)img.buf) + 3 * i); - } - } - - if (n_args > 6) { - mp_buffer_info_t alpha; - - /* Load alpha buffer and check size */ - if (!mp_get_buffer(args[6], &alpha, MP_BUFFER_READ)) { - mp_raise_TypeError( - "'alpha' does not support buffer protocol." - ); - } - if ((int)alpha.len < width * height) { - mp_raise_ValueError("'alpha' is too small."); - } - - res = epic_disp_blit( - pos_x, pos_y, width, height, buf, (uint8_t *)alpha.buf - ); - } else { - res = epic_disp_blit(pos_x, pos_y, width, height, buf, NULL); - } - - if (!rgb565) { - /* Do not free rgb565 data. It is owned by the caller */ - m_free(buf); - } + res = epic_disp_blit(pos_x, pos_y, width, height, img.buf, format); if (res < 0) { mp_raise_OSError(-res); @@ -162,7 +120,7 @@ static mp_obj_t mp_display_blit(size_t n_args, const mp_obj_t *args) return mp_const_none; } static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN( - display_blit_obj, 5, 6, mp_display_blit + display_blit_obj, 6, 6, mp_display_blit ); /* set display backlight brightness */ -- GitLab