Skip to content

Commit 2ec2761

Browse files
committed
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.
1 parent 102af55 commit 2ec2761

File tree

4 files changed

+166
-3
lines changed

4 files changed

+166
-3
lines changed

locale/circuitpython.pot

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,10 @@ msgstr ""
552552
msgid "Bit depth must be multiple of 8."
553553
msgstr ""
554554

555+
#: shared-bindings/bitmaptools/__init__.c
556+
msgid "Bitmap size and bits per value must match"
557+
msgstr ""
558+
555559
#: supervisor/shared/safe_mode.c
556560
msgid "Boot device must be first device (interface #0)."
557561
msgstr ""
@@ -1071,6 +1075,14 @@ msgstr ""
10711075
msgid "Firmware image is invalid"
10721076
msgstr ""
10731077

1078+
#: shared-bindings/bitmaptools/__init__.c
1079+
msgid "For L8 colorspace, input bitmap must have 8 bits per pixel"
1080+
msgstr ""
1081+
1082+
#: shared-bindings/bitmaptools/__init__.c
1083+
msgid "For RGB colorspaces, input bitmap must have 16 bits per pixel"
1084+
msgstr ""
1085+
10741086
#: ports/cxd56/common-hal/camera/Camera.c
10751087
msgid "Format not supported"
10761088
msgstr ""
@@ -2390,6 +2402,10 @@ msgstr ""
23902402
msgid "Unsupported baudrate"
23912403
msgstr ""
23922404

2405+
#: shared-bindings/bitmaptools/__init__.c
2406+
msgid "Unsupported colorspace"
2407+
msgstr ""
2408+
23932409
#: shared-module/displayio/display_core.c
23942410
msgid "Unsupported display bus type"
23952411
msgstr ""

shared-bindings/bitmaptools/__init__.c

Lines changed: 83 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,85 @@ STATIC mp_obj_t bitmaptools_obj_rotozoom(size_t n_args, const mp_obj_t *pos_args
244244
MP_DEFINE_CONST_FUN_OBJ_KW(bitmaptools_rotozoom_obj, 0, bitmaptools_obj_rotozoom);
245245
// requires at least 2 arguments (destination bitmap and source bitmap)
246246

247+
//|
248+
//| def alphablend(dest_bitmap, source_bitmap_1, source_bitmap_2, colorspace: displayio.Colorspace, factor1: float=.5, factor2: float=None):
249+
//| """Alpha blend the two source bitmaps into the destination.
250+
//|
251+
//| It is permitted for the destination bitmap to be one of the two
252+
//| source bitmaps.
253+
//|
254+
//| :param bitmap dest_bitmap: Destination bitmap that will be written into
255+
//| :param bitmap source_bitmap_1: The first source bitmap
256+
//| :param bitmap source_bitmap_2: The second source bitmap
257+
//| :param float factor1: The proportion of bitmap 1 to mix in
258+
//| :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.
259+
//| :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``.
260+
//|
261+
//| For the L8 colorspace, the bitmaps must have a bits-per-value of 8.
262+
//| For the RGB colorspaces, they must have a bits-per-value of 16."""
263+
//|
264+
265+
STATIC mp_obj_t bitmaptools_alphablend(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
266+
enum {ARG_dest_bitmap, ARG_source_bitmap_1, ARG_source_bitmap_2, ARG_colorspace, ARG_factor_1, ARG_factor_2};
267+
268+
static const mp_arg_t allowed_args[] = {
269+
{MP_QSTR_dest_bitmap, MP_ARG_REQUIRED | MP_ARG_OBJ},
270+
{MP_QSTR_source_bitmap_1, MP_ARG_REQUIRED | MP_ARG_OBJ},
271+
{MP_QSTR_source_bitmap_2, MP_ARG_REQUIRED | MP_ARG_OBJ},
272+
{MP_QSTR_colorspace, MP_ARG_REQUIRED | MP_ARG_OBJ},
273+
{MP_QSTR_factor_1, MP_ARG_OBJ, {.u_obj = MP_ROM_NONE}},
274+
{MP_QSTR_factor_2, MP_ARG_OBJ, {.u_obj = MP_ROM_NONE}},
275+
};
276+
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
277+
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
278+
279+
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
280+
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
281+
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
282+
283+
float factor1 = (args[ARG_factor_1].u_obj == mp_const_none) ? .5f : mp_obj_float_get(args[ARG_factor_1].u_obj);
284+
float factor2 = (args[ARG_factor_2].u_obj == mp_const_none) ? 1 - factor1 : mp_obj_float_get(args[ARG_factor_2].u_obj);
285+
286+
displayio_colorspace_t colorspace = (displayio_colorspace_t)cp_enum_value(&displayio_colorspace_type, args[ARG_colorspace].u_obj);
287+
288+
if (destination->width != source1->width
289+
|| destination->height != source1->height
290+
|| destination->bits_per_value != source1->bits_per_value
291+
|| destination->width != source2->width
292+
|| destination->height != source2->height
293+
|| destination->bits_per_value != source2->bits_per_value
294+
) {
295+
mp_raise_ValueError(translate("Bitmap size and bits per value must match"));
296+
}
297+
298+
switch (colorspace) {
299+
#if 0
300+
case DISPLAYIO_COLORSPACE_L8:
301+
if (destination->bits_per_value != 8) {
302+
mp_raise_ValueError(translate("For L8 colorspace, input bitmap must have 8 bits per pixel"));
303+
}
304+
break;
305+
#endif
306+
307+
case DISPLAYIO_COLORSPACE_RGB565:
308+
case DISPLAYIO_COLORSPACE_RGB565_SWAPPED:
309+
case DISPLAYIO_COLORSPACE_BGR565:
310+
case DISPLAYIO_COLORSPACE_BGR565_SWAPPED:
311+
if (destination->bits_per_value != 16) {
312+
mp_raise_ValueError(translate("For RGB colorspaces, input bitmap must have 16 bits per pixel"));
313+
}
314+
break;
315+
316+
default:
317+
mp_raise_ValueError(translate("Unsupported colorspace"));
318+
}
319+
320+
common_hal_bitmaptools_alphablend(destination, source1, source2, colorspace, factor1, factor2);
321+
322+
return mp_const_none;
323+
}
324+
MP_DEFINE_CONST_FUN_OBJ_KW(bitmaptools_alphablend_obj, 0, bitmaptools_alphablend);
325+
247326
//|
248327
//| def fill_region(
249328
//| 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
276355
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
277356
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
278357

279-
displayio_bitmap_t *destination = MP_OBJ_TO_PTR(args[ARG_dest_bitmap].u_obj); // the destination bitmap
358+
displayio_bitmap_t *destination = MP_OBJ_TO_PTR(args[ARG_dest_bitmap].u_obj); // the destination bitmap
280359

281360
uint32_t value, color_depth;
282361
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
327406
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
328407
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
329408

330-
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
409+
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
331410

332411
uint32_t fill_color_value, color_depth;
333412
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
391470
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
392471
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
393472

394-
displayio_bitmap_t *destination = MP_OBJ_TO_PTR(args[ARG_dest_bitmap].u_obj); // the destination bitmap
473+
displayio_bitmap_t *destination = MP_OBJ_TO_PTR(args[ARG_dest_bitmap].u_obj); // the destination bitmap
395474

396475
uint32_t value, color_depth;
397476
value = args[ARG_value].u_int;
@@ -573,6 +652,7 @@ STATIC const mp_rom_map_elem_t bitmaptools_module_globals_table[] = {
573652
{ MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&bitmaptools_readinto_obj) },
574653
{ MP_ROM_QSTR(MP_QSTR_rotozoom), MP_ROM_PTR(&bitmaptools_rotozoom_obj) },
575654
{ MP_ROM_QSTR(MP_QSTR_arrayblit), MP_ROM_PTR(&bitmaptools_arrayblit_obj) },
655+
{ MP_ROM_QSTR(MP_QSTR_alphablend), MP_ROM_PTR(&bitmaptools_alphablend_obj) },
576656
{ MP_ROM_QSTR(MP_QSTR_fill_region), MP_ROM_PTR(&bitmaptools_fill_region_obj) },
577657
{ MP_ROM_QSTR(MP_QSTR_boundary_fill), MP_ROM_PTR(&bitmaptools_boundary_fill_obj) },
578658
{ MP_ROM_QSTR(MP_QSTR_draw_line), MP_ROM_PTR(&bitmaptools_draw_line_obj) },

shared-bindings/bitmaptools/__init__.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#define MICROPY_INCLUDED_SHARED_BINDINGS_BITMAPTOOLS__INIT__H
2929

3030
#include "shared-module/displayio/Bitmap.h"
31+
#include "shared-bindings/displayio/__init__.h"
3132
#include "py/obj.h"
3233
#include "extmod/vfs_fat.h"
3334

@@ -58,4 +59,6 @@ void common_hal_bitmaptools_draw_line(displayio_bitmap_t *destination,
5859
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);
5960
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);
6061

62+
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);
63+
6164
#endif // MICROPY_INCLUDED_SHARED_BINDINGS_BITMAPTOOLS__INIT__H

shared-module/bitmaptools/__init__.c

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -602,3 +602,67 @@ void common_hal_bitmaptools_readinto(displayio_bitmap_t *self, pyb_file_obj_t *f
602602
}
603603
}
604604
}
605+
606+
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) {
607+
displayio_area_t a = {0, 0, dest->width, dest->height};
608+
displayio_bitmap_set_dirty_area(dest, &a);
609+
610+
int ifactor1 = (int)(factor1 * 256);
611+
int ifactor2 = (int)(factor2 * 256);
612+
613+
#if 0
614+
if (colorspace == DISPLAYIO_COLORSPACE_L8) {
615+
for (int y = 0; y < dest->height; y++) {
616+
uint8_t *dptr = (uint8_t *)(dest->data + y * dest->stride);
617+
uint8_t *sptr1 = (uint8_t *)(source1->data + y * source1->stride);
618+
uint8_t *sptr2 = (uint8_t *)(source2->data + y * source2->stride);
619+
for (int x = 0; x < dest->width; x++) {
620+
// This is round(l1*f1 + l2*f2) & clip to range in fixed-point
621+
int pixel = (*sptr1++ *ifactor1 + *sptr2++ *ifactor2 + 128) / 256;
622+
*dptr++ = MIN(255, MAX(0, pixel));
623+
}
624+
}
625+
} else
626+
#endif
627+
{
628+
bool swap = (colorspace == DISPLAYIO_COLORSPACE_RGB565_SWAPPED) || (colorspace == DISPLAYIO_COLORSPACE_BGR565_SWAPPED);
629+
for (int y = 0; y < dest->height; y++) {
630+
uint16_t *dptr = (uint16_t *)(dest->data + y * dest->stride);
631+
uint16_t *sptr1 = (uint16_t *)(source1->data + y * source1->stride);
632+
uint16_t *sptr2 = (uint16_t *)(source2->data + y * source2->stride);
633+
for (int x = 0; x < dest->width; x++) {
634+
int spix1 = *sptr1++;
635+
int spix2 = *sptr2++;
636+
637+
if (swap) {
638+
spix1 = __builtin_bswap16(spix1);
639+
spix2 = __builtin_bswap16(spix2);
640+
}
641+
const int r_mask = 0xf800; // (or b mask, if BGR)
642+
const int g_mask = 0x07e0;
643+
const int b_mask = 0x001f; // (or r mask, if BGR)
644+
645+
// This is round(r1*f1 + r2*f2) & clip to range in fixed-point
646+
// but avoiding shifting it down to start at bit 0
647+
int r = ((spix1 & r_mask) * ifactor1
648+
+ (spix2 & r_mask) * ifactor2 + r_mask / 2) / 256;
649+
r = MIN(r_mask, MAX(0, r)) & r_mask;
650+
651+
// ditto
652+
int g = ((spix1 & g_mask) * ifactor1
653+
+ (spix2 & g_mask) * ifactor2 + g_mask / 2) / 256;
654+
g = MIN(g_mask, MAX(0, g)) & g_mask;
655+
656+
int b = ((spix1 & b_mask) * ifactor1
657+
+ (spix2 & b_mask) * ifactor2 + b_mask / 2) / 256;
658+
b = MIN(b_mask, MAX(0, b)) & b_mask;
659+
660+
uint16_t pixel = r | g | b;
661+
if (swap) {
662+
pixel = __builtin_bswap16(pixel);
663+
}
664+
*dptr++ = pixel;
665+
}
666+
}
667+
}
668+
}

0 commit comments

Comments
 (0)