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.
This commit is contained in:
Jeff Epler 2021-11-03 08:35:50 -05:00
parent 102af55e0a
commit 2ec2761ce0
4 changed files with 166 additions and 3 deletions

View File

@ -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 ""

View File

@ -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,
@ -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) },

View File

@ -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

View File

@ -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;
}
}
}
}