diff --git a/epicardium/epicardium.h b/epicardium/epicardium.h index 1e6c48cef47b349311450b3d666f3e16d34c8871..96fbbbeab7d5b1201587ea3a6904089ab795a8a9 100644 --- a/epicardium/epicardium.h +++ b/epicardium/epicardium.h @@ -56,6 +56,7 @@ typedef _Bool bool; #define API_DISP_FRAMEBUFFER 0x29 #define API_DISP_BACKLIGHT 0x2a #define API_DISP_PRINT_ADV 0x2b +#define API_DISP_BLIT 0x2d /* API_BATTERY_VOLTAGE 0x30 */ #define API_BATTERY_CURRENT 0x31 @@ -1670,6 +1671,25 @@ API(API_DISP_PIXEL, int epic_disp_pixel( int16_t x, int16_t y, uint16_t color )); +/** + * Blit an image buffer to 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. + */ +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 +)); + /** * Draws a line on the display * diff --git a/epicardium/modules/display.c b/epicardium/modules/display.c index c3a84812fcb3f105a8e1ccf4c710c64854d63269..d7607a2027735db1813c86fc49c8a1718756d0b3 100644 --- a/epicardium/modules/display.c +++ b/epicardium/modules/display.c @@ -87,6 +87,68 @@ int epic_disp_pixel(int16_t x, int16_t y, uint16_t color) } } +int epic_disp_blit( + int16_t pos_x, + int16_t pos_y, + int16_t width, + int16_t height, + uint16_t *img, + uint8_t *alpha +) { + /* 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; + } + + if (offset_x == 0 && offset_y == 0 && count_x == width && + count_y == height) { + /* Simply copy full image, no cropping or alpha blending */ + gfx_copy_region( + &display_screen, + pos_x, + pos_y, + width, + height, + GFX_RAW, + width * height * 2, + img + ); + } 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; + } +} + int epic_disp_line( int16_t xstart, int16_t ystart, diff --git a/pycardium/modules/py/display.py b/pycardium/modules/py/display.py index a6ef82778a9b7638d900f89818435ffcffcd1b8e..1c94d47b35844db5272447265c94389c2eac918b 100644 --- a/pycardium/modules/py/display.py +++ b/pycardium/modules/py/display.py @@ -120,6 +120,83 @@ class Display: sys_display.pixel(x, y, col) return self + def blit(self, x, y, w, h, img, rgb565=False, alpha=None): + """ + Draws an image on the display. + + :param x: X coordinate + :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. + + .. note:: + Alpha mask support is not yet implemented. + + .. versionadded:: 1.17 + + **Example with RGB data:** + + .. code-block:: python + + import display + import color + + # Draw a blue 32x20 pixel rectangle: + # Each pixel is 3 bytes big. Order is red, green, blue + img = bytes(color.BLUE) * 32 * 20 + with display.open() as d: + d.clear() + d.blit(10, 10, 32, 20, img) + d.update() + + + **Example with rgb565 data:** + + .. code-block:: python + + import array + import display + + # Draw a green 32x20 pixel rectangle: + # 0x07E0 has all bits for green set to 1. Order is RRRR RGGG GGGB BBBB + 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.update() + + + **Example with a MicroPython FrameBuffer:** + + .. code-block:: python + + import framebuf + import display + + # Create a 160x80 pixel frame buffer and write "Hello World" on the 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.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) + + return self + def backlight(self, brightness): """ Set display backlight brightness diff --git a/pycardium/modules/qstrdefs.h b/pycardium/modules/qstrdefs.h index 721509d776b4d180418f530c64a2f14de6180f17..58f4f80d59d9b8ab97b9687a0dcf462f701b81a6 100644 --- a/pycardium/modules/qstrdefs.h +++ b/pycardium/modules/qstrdefs.h @@ -98,6 +98,7 @@ Q(display) Q(print) Q(print_adv) Q(pixel) +Q(blit) Q(backlight) Q(line) Q(rect) diff --git a/pycardium/modules/sys_display.c b/pycardium/modules/sys_display.c index 40ec6125bfef12132ab9cbe3ce6273b807131d37..eedeb705e12531d3ddf46a153abafbe8adebca0a 100644 --- a/pycardium/modules/sys_display.c +++ b/pycardium/modules/sys_display.c @@ -94,6 +94,76 @@ static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN( display_pixel_obj, 3, 3, mp_display_pixel ); +/* 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]); + mp_buffer_info_t img; + + int res = 0; + + /* Load buffer and ensure it contains enough data */ + if (!mp_get_buffer(args[4], &img, MP_BUFFER_READ)) { + 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 (buf) { + m_free(buf); + } + + if (res < 0) { + mp_raise_OSError(-res); + } + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN( + display_blit_obj, 5, 6, mp_display_blit +); + /* set display backlight brightness */ static mp_obj_t mp_display_backlight(size_t n_args, const mp_obj_t *args) { @@ -233,6 +303,7 @@ static const mp_rom_map_elem_t display_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_print), MP_ROM_PTR(&display_print_obj) }, { MP_ROM_QSTR(MP_QSTR_print_adv), MP_ROM_PTR(&display_print_adv_obj) }, { MP_ROM_QSTR(MP_QSTR_pixel), MP_ROM_PTR(&display_pixel_obj) }, + { MP_ROM_QSTR(MP_QSTR_blit), MP_ROM_PTR(&display_blit_obj) }, { MP_ROM_QSTR(MP_QSTR_backlight), MP_ROM_PTR(&display_backlight_obj) }, { MP_ROM_QSTR(MP_QSTR_line), MP_ROM_PTR(&display_line_obj) }, { MP_ROM_QSTR(MP_QSTR_rect), MP_ROM_PTR(&display_rect_obj) },