From 2ec2761ce04df4a607a04dd375abf1db82e2fe8f Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Wed, 3 Nov 2021 08:35:50 -0500 Subject: [PATCH] bitmaptools: add alphablend This blends two "565"-format bitmaps, including byteswapped ones. All the bitmaps have to have the same memory format. The routine takes about 63ms on a Kaluga when operating on 320x240 bitmaps. Of course, displaying the bitmap also takes time. There's untested code for the L8 (8-bit greyscale) case. This can be enabled once gifio is merged. --- locale/circuitpython.pot | 16 +++++ shared-bindings/bitmaptools/__init__.c | 86 +++++++++++++++++++++++++- shared-bindings/bitmaptools/__init__.h | 3 + shared-module/bitmaptools/__init__.c | 64 +++++++++++++++++++ 4 files changed, 166 insertions(+), 3 deletions(-) diff --git a/locale/circuitpython.pot b/locale/circuitpython.pot index 1d7fc6854d..b4bcf35427 100644 --- a/locale/circuitpython.pot +++ b/locale/circuitpython.pot @@ -552,6 +552,10 @@ msgstr "" msgid "Bit depth must be multiple of 8." msgstr "" +#: shared-bindings/bitmaptools/__init__.c +msgid "Bitmap size and bits per value must match" +msgstr "" + #: supervisor/shared/safe_mode.c msgid "Boot device must be first device (interface #0)." msgstr "" @@ -1071,6 +1075,14 @@ msgstr "" msgid "Firmware image is invalid" msgstr "" +#: shared-bindings/bitmaptools/__init__.c +msgid "For L8 colorspace, input bitmap must have 8 bits per pixel" +msgstr "" + +#: shared-bindings/bitmaptools/__init__.c +msgid "For RGB colorspaces, input bitmap must have 16 bits per pixel" +msgstr "" + #: ports/cxd56/common-hal/camera/Camera.c msgid "Format not supported" msgstr "" @@ -2390,6 +2402,10 @@ msgstr "" msgid "Unsupported baudrate" msgstr "" +#: shared-bindings/bitmaptools/__init__.c +msgid "Unsupported colorspace" +msgstr "" + #: shared-module/displayio/display_core.c msgid "Unsupported display bus type" msgstr "" diff --git a/shared-bindings/bitmaptools/__init__.c b/shared-bindings/bitmaptools/__init__.c index ad814e8881..2c853ccb2a 100644 --- a/shared-bindings/bitmaptools/__init__.c +++ b/shared-bindings/bitmaptools/__init__.c @@ -244,6 +244,85 @@ STATIC mp_obj_t bitmaptools_obj_rotozoom(size_t n_args, const mp_obj_t *pos_args MP_DEFINE_CONST_FUN_OBJ_KW(bitmaptools_rotozoom_obj, 0, bitmaptools_obj_rotozoom); // requires at least 2 arguments (destination bitmap and source bitmap) +//| +//| def alphablend(dest_bitmap, source_bitmap_1, source_bitmap_2, colorspace: displayio.Colorspace, factor1: float=.5, factor2: float=None): +//| """Alpha blend the two source bitmaps into the destination. +//| +//| It is permitted for the destination bitmap to be one of the two +//| source bitmaps. +//| +//| :param bitmap dest_bitmap: Destination bitmap that will be written into +//| :param bitmap source_bitmap_1: The first source bitmap +//| :param bitmap source_bitmap_2: The second source bitmap +//| :param float factor1: The proportion of bitmap 1 to mix in +//| :param float factor2: The proportion of bitmap 2 to mix in. If specified as `None`, ``1-factor1`` is used. Usually the proportions should sum to 1. +//| :param displayio.Colorspace colorspace: The colorspace of the bitmaps. They must all have the same colorspace. Only the following colorspaces are permitted: ``L8``, ``RGB565``, ``RGB565_SWAPPED``, ``BGR565`` and ``BGR565_SWAPPED``. +//| +//| For the L8 colorspace, the bitmaps must have a bits-per-value of 8. +//| For the RGB colorspaces, they must have a bits-per-value of 16.""" +//| + +STATIC mp_obj_t bitmaptools_alphablend(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum {ARG_dest_bitmap, ARG_source_bitmap_1, ARG_source_bitmap_2, ARG_colorspace, ARG_factor_1, ARG_factor_2}; + + static const mp_arg_t allowed_args[] = { + {MP_QSTR_dest_bitmap, MP_ARG_REQUIRED | MP_ARG_OBJ}, + {MP_QSTR_source_bitmap_1, MP_ARG_REQUIRED | MP_ARG_OBJ}, + {MP_QSTR_source_bitmap_2, MP_ARG_REQUIRED | MP_ARG_OBJ}, + {MP_QSTR_colorspace, MP_ARG_REQUIRED | MP_ARG_OBJ}, + {MP_QSTR_factor_1, MP_ARG_OBJ, {.u_obj = MP_ROM_NONE}}, + {MP_QSTR_factor_2, MP_ARG_OBJ, {.u_obj = MP_ROM_NONE}}, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + displayio_bitmap_t *destination = MP_OBJ_TO_PTR(mp_arg_validate_type(args[ARG_dest_bitmap].u_obj, &displayio_bitmap_type, MP_QSTR_dest_bitmap)); // the destination bitmap + displayio_bitmap_t *source1 = MP_OBJ_TO_PTR(mp_arg_validate_type(args[ARG_source_bitmap_1].u_obj, &displayio_bitmap_type, MP_QSTR_source_bitmap_1)); // the first source bitmap + displayio_bitmap_t *source2 = MP_OBJ_TO_PTR(mp_arg_validate_type(args[ARG_source_bitmap_2].u_obj, &displayio_bitmap_type, MP_QSTR_source_bitmap_2)); // the second source bitmap + + float factor1 = (args[ARG_factor_1].u_obj == mp_const_none) ? .5f : mp_obj_float_get(args[ARG_factor_1].u_obj); + float factor2 = (args[ARG_factor_2].u_obj == mp_const_none) ? 1 - factor1 : mp_obj_float_get(args[ARG_factor_2].u_obj); + + displayio_colorspace_t colorspace = (displayio_colorspace_t)cp_enum_value(&displayio_colorspace_type, args[ARG_colorspace].u_obj); + + if (destination->width != source1->width + || destination->height != source1->height + || destination->bits_per_value != source1->bits_per_value + || destination->width != source2->width + || destination->height != source2->height + || destination->bits_per_value != source2->bits_per_value + ) { + mp_raise_ValueError(translate("Bitmap size and bits per value must match")); + } + + switch (colorspace) { + #if 0 + case DISPLAYIO_COLORSPACE_L8: + if (destination->bits_per_value != 8) { + mp_raise_ValueError(translate("For L8 colorspace, input bitmap must have 8 bits per pixel")); + } + break; + #endif + + case DISPLAYIO_COLORSPACE_RGB565: + case DISPLAYIO_COLORSPACE_RGB565_SWAPPED: + case DISPLAYIO_COLORSPACE_BGR565: + case DISPLAYIO_COLORSPACE_BGR565_SWAPPED: + if (destination->bits_per_value != 16) { + mp_raise_ValueError(translate("For RGB colorspaces, input bitmap must have 16 bits per pixel")); + } + break; + + default: + mp_raise_ValueError(translate("Unsupported colorspace")); + } + + common_hal_bitmaptools_alphablend(destination, source1, source2, colorspace, factor1, factor2); + + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_KW(bitmaptools_alphablend_obj, 0, bitmaptools_alphablend); + //| //| def fill_region( //| dest_bitmap: displayio.Bitmap, @@ -276,7 +355,7 @@ STATIC mp_obj_t bitmaptools_obj_fill_region(size_t n_args, const mp_obj_t *pos_a mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - displayio_bitmap_t *destination = MP_OBJ_TO_PTR(args[ARG_dest_bitmap].u_obj); // the destination bitmap + displayio_bitmap_t *destination = MP_OBJ_TO_PTR(args[ARG_dest_bitmap].u_obj); // the destination bitmap uint32_t value, color_depth; value = args[ARG_value].u_int; @@ -327,7 +406,7 @@ STATIC mp_obj_t bitmaptools_obj_boundary_fill(size_t n_args, const mp_obj_t *pos mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - displayio_bitmap_t *destination = MP_OBJ_TO_PTR(mp_arg_validate_type(args[ARG_dest_bitmap].u_obj, &displayio_bitmap_type, MP_QSTR_dest_bitmap)); // the destination bitmap + displayio_bitmap_t *destination = MP_OBJ_TO_PTR(mp_arg_validate_type(args[ARG_dest_bitmap].u_obj, &displayio_bitmap_type, MP_QSTR_dest_bitmap)); // the destination bitmap uint32_t fill_color_value, color_depth; fill_color_value = args[ARG_fill_color_value].u_int; @@ -391,7 +470,7 @@ STATIC mp_obj_t bitmaptools_obj_draw_line(size_t n_args, const mp_obj_t *pos_arg mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - displayio_bitmap_t *destination = MP_OBJ_TO_PTR(args[ARG_dest_bitmap].u_obj); // the destination bitmap + displayio_bitmap_t *destination = MP_OBJ_TO_PTR(args[ARG_dest_bitmap].u_obj); // the destination bitmap uint32_t value, color_depth; value = args[ARG_value].u_int; @@ -573,6 +652,7 @@ STATIC const mp_rom_map_elem_t bitmaptools_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&bitmaptools_readinto_obj) }, { MP_ROM_QSTR(MP_QSTR_rotozoom), MP_ROM_PTR(&bitmaptools_rotozoom_obj) }, { MP_ROM_QSTR(MP_QSTR_arrayblit), MP_ROM_PTR(&bitmaptools_arrayblit_obj) }, + { MP_ROM_QSTR(MP_QSTR_alphablend), MP_ROM_PTR(&bitmaptools_alphablend_obj) }, { MP_ROM_QSTR(MP_QSTR_fill_region), MP_ROM_PTR(&bitmaptools_fill_region_obj) }, { MP_ROM_QSTR(MP_QSTR_boundary_fill), MP_ROM_PTR(&bitmaptools_boundary_fill_obj) }, { MP_ROM_QSTR(MP_QSTR_draw_line), MP_ROM_PTR(&bitmaptools_draw_line_obj) }, diff --git a/shared-bindings/bitmaptools/__init__.h b/shared-bindings/bitmaptools/__init__.h index 6415b20258..31f0a1078c 100644 --- a/shared-bindings/bitmaptools/__init__.h +++ b/shared-bindings/bitmaptools/__init__.h @@ -28,6 +28,7 @@ #define MICROPY_INCLUDED_SHARED_BINDINGS_BITMAPTOOLS__INIT__H #include "shared-module/displayio/Bitmap.h" +#include "shared-bindings/displayio/__init__.h" #include "py/obj.h" #include "extmod/vfs_fat.h" @@ -58,4 +59,6 @@ void common_hal_bitmaptools_draw_line(displayio_bitmap_t *destination, void common_hal_bitmaptools_readinto(displayio_bitmap_t *self, pyb_file_obj_t *file, int element_size, int bits_per_pixel, bool reverse_pixels_in_word, bool swap_bytes, bool reverse_rows); void common_hal_bitmaptools_arrayblit(displayio_bitmap_t *self, void *data, int element_size, int x1, int y1, int x2, int y2, bool skip_specified, uint32_t skip_index); +void common_hal_bitmaptools_alphablend(displayio_bitmap_t *destination, displayio_bitmap_t *source1, displayio_bitmap_t *source2, displayio_colorspace_t colorspace, float factor1, float factor2); + #endif // MICROPY_INCLUDED_SHARED_BINDINGS_BITMAPTOOLS__INIT__H diff --git a/shared-module/bitmaptools/__init__.c b/shared-module/bitmaptools/__init__.c index b6e0764fdb..ed186c2c1b 100644 --- a/shared-module/bitmaptools/__init__.c +++ b/shared-module/bitmaptools/__init__.c @@ -602,3 +602,67 @@ void common_hal_bitmaptools_readinto(displayio_bitmap_t *self, pyb_file_obj_t *f } } } + +void common_hal_bitmaptools_alphablend(displayio_bitmap_t *dest, displayio_bitmap_t *source1, displayio_bitmap_t *source2, displayio_colorspace_t colorspace, float factor1, float factor2) { + displayio_area_t a = {0, 0, dest->width, dest->height}; + displayio_bitmap_set_dirty_area(dest, &a); + + int ifactor1 = (int)(factor1 * 256); + int ifactor2 = (int)(factor2 * 256); + + #if 0 + if (colorspace == DISPLAYIO_COLORSPACE_L8) { + for (int y = 0; y < dest->height; y++) { + uint8_t *dptr = (uint8_t *)(dest->data + y * dest->stride); + uint8_t *sptr1 = (uint8_t *)(source1->data + y * source1->stride); + uint8_t *sptr2 = (uint8_t *)(source2->data + y * source2->stride); + for (int x = 0; x < dest->width; x++) { + // This is round(l1*f1 + l2*f2) & clip to range in fixed-point + int pixel = (*sptr1++ *ifactor1 + *sptr2++ *ifactor2 + 128) / 256; + *dptr++ = MIN(255, MAX(0, pixel)); + } + } + } else + #endif + { + bool swap = (colorspace == DISPLAYIO_COLORSPACE_RGB565_SWAPPED) || (colorspace == DISPLAYIO_COLORSPACE_BGR565_SWAPPED); + for (int y = 0; y < dest->height; y++) { + uint16_t *dptr = (uint16_t *)(dest->data + y * dest->stride); + uint16_t *sptr1 = (uint16_t *)(source1->data + y * source1->stride); + uint16_t *sptr2 = (uint16_t *)(source2->data + y * source2->stride); + for (int x = 0; x < dest->width; x++) { + int spix1 = *sptr1++; + int spix2 = *sptr2++; + + if (swap) { + spix1 = __builtin_bswap16(spix1); + spix2 = __builtin_bswap16(spix2); + } + const int r_mask = 0xf800; // (or b mask, if BGR) + const int g_mask = 0x07e0; + const int b_mask = 0x001f; // (or r mask, if BGR) + + // This is round(r1*f1 + r2*f2) & clip to range in fixed-point + // but avoiding shifting it down to start at bit 0 + int r = ((spix1 & r_mask) * ifactor1 + + (spix2 & r_mask) * ifactor2 + r_mask / 2) / 256; + r = MIN(r_mask, MAX(0, r)) & r_mask; + + // ditto + int g = ((spix1 & g_mask) * ifactor1 + + (spix2 & g_mask) * ifactor2 + g_mask / 2) / 256; + g = MIN(g_mask, MAX(0, g)) & g_mask; + + int b = ((spix1 & b_mask) * ifactor1 + + (spix2 & b_mask) * ifactor2 + b_mask / 2) / 256; + b = MIN(b_mask, MAX(0, b)) & b_mask; + + uint16_t pixel = r | g | b; + if (swap) { + pixel = __builtin_bswap16(pixel); + } + *dptr++ = pixel; + } + } + } +}