Skip to content

Commit 8cabd2a

Browse files
bitmaptools.alphablend: add optional L8 mask= kwarg
Adds an optional mask= keyword argument to bitmaptools.alphablend() that scales source2's per-pixel contribution by an L8 bitmap (one byte per pixel, 0..255). When mask is None (default), behavior is unchanged from the existing alphablend. Mask bitmap must match the output bitmap's width and height and have a bits_per_value of 8.
1 parent 6b4970b commit 8cabd2a

4 files changed

Lines changed: 65 additions & 7 deletions

File tree

locale/circuitpython.pot

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1441,6 +1441,14 @@ msgstr ""
14411441
msgid "Mapping must be a tuple"
14421442
msgstr ""
14431443

1444+
#: shared-bindings/bitmaptools/__init__.c
1445+
msgid "Mask bitmap must have 8 bits per pixel"
1446+
msgstr ""
1447+
1448+
#: shared-bindings/bitmaptools/__init__.c
1449+
msgid "Mask bitmap size must match the other bitmaps"
1450+
msgstr ""
1451+
14441452
#: py/persistentcode.c
14451453
msgid "MicroPython .mpy file; use CircuitPython mpy-cross"
14461454
msgstr ""

shared-bindings/bitmaptools/__init__.c

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,7 @@ MAKE_ENUM_TYPE(bitmaptools, BlendMode, bitmaptools_blendmode);
311311
//| blendmode: Optional[BlendMode] = BlendMode.Normal,
312312
//| skip_source1_index: Union[int, None] = None,
313313
//| skip_source2_index: Union[int, None] = None,
314+
//| mask: Optional[displayio.Bitmap] = None,
314315
//| ) -> None:
315316
//| """Alpha blend the two source bitmaps into the destination.
316317
//|
@@ -326,14 +327,19 @@ MAKE_ENUM_TYPE(bitmaptools, BlendMode, bitmaptools_blendmode);
326327
//| :param bitmaptools.BlendMode blendmode: The blend mode to use. Default is Normal.
327328
//| :param int skip_source1_index: Bitmap palette or luminance index in source_bitmap_1 that will not be blended, set to None to blend all pixels
328329
//| :param int skip_source2_index: Bitmap palette or luminance index in source_bitmap_2 that will not be blended, set to None to blend all pixels
330+
//| :param displayio.Bitmap mask: Optional 8-bits-per-value grayscale mask bitmap controlling per-pixel opacity of ``source_bitmap_2``.
331+
//| The mask must have the same width and height as the other bitmaps and a ``bits_per_value`` of 8. A mask value of 0
332+
//| means ``source_bitmap_2`` is fully transparent at that pixel (only ``source_bitmap_1`` contributes); a value of 255 means
333+
//| ``source_bitmap_2`` is fully opaque at that pixel (subject to ``factor2``). Intermediate values scale ``factor2`` linearly.
334+
//| Pass ``None`` to disable per-pixel masking.
329335
//|
330336
//| For the L8 colorspace, the bitmaps must have a bits-per-value of 8.
331337
//| For the RGB colorspaces, they must have a bits-per-value of 16."""
332338
//|
333339
//|
334340

335341
static mp_obj_t bitmaptools_alphablend(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
336-
enum {ARG_dest_bitmap, ARG_source_bitmap_1, ARG_source_bitmap_2, ARG_colorspace, ARG_factor_1, ARG_factor_2, ARG_blendmode, ARG_skip_source1_index, ARG_skip_source2_index};
342+
enum {ARG_dest_bitmap, ARG_source_bitmap_1, ARG_source_bitmap_2, ARG_colorspace, ARG_factor_1, ARG_factor_2, ARG_blendmode, ARG_skip_source1_index, ARG_skip_source2_index, ARG_mask};
337343

338344
static const mp_arg_t allowed_args[] = {
339345
{MP_QSTR_dest_bitmap, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = NULL}},
@@ -345,6 +351,7 @@ static mp_obj_t bitmaptools_alphablend(size_t n_args, const mp_obj_t *pos_args,
345351
{MP_QSTR_blendmode, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = (void *)&bitmaptools_blendmode_Normal_obj}},
346352
{MP_QSTR_skip_source1_index, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} },
347353
{MP_QSTR_skip_source2_index, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} },
354+
{MP_QSTR_mask, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} },
348355
};
349356
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
350357
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
@@ -411,8 +418,19 @@ static mp_obj_t bitmaptools_alphablend(size_t n_args, const mp_obj_t *pos_args,
411418
skip_source2_index_none = false;
412419
}
413420

421+
displayio_bitmap_t *mask = NULL;
422+
if (args[ARG_mask].u_obj != mp_const_none) {
423+
mask = MP_OBJ_TO_PTR(mp_arg_validate_type(args[ARG_mask].u_obj, &displayio_bitmap_type, MP_QSTR_mask));
424+
if (mask->width != destination->width || mask->height != destination->height) {
425+
mp_raise_ValueError(MP_ERROR_TEXT("Mask bitmap size must match the other bitmaps"));
426+
}
427+
if (mask->bits_per_value != 8) {
428+
mp_raise_ValueError(MP_ERROR_TEXT("Mask bitmap must have 8 bits per pixel"));
429+
}
430+
}
431+
414432
common_hal_bitmaptools_alphablend(destination, source1, source2, colorspace, factor1, factor2, blendmode, skip_source1_index,
415-
skip_source1_index_none, skip_source2_index, skip_source2_index_none);
433+
skip_source1_index_none, skip_source2_index, skip_source2_index_none, mask);
416434

417435
return mp_const_none;
418436
}

shared-bindings/bitmaptools/__init__.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,8 @@ void common_hal_bitmaptools_arrayblit(displayio_bitmap_t *self, void *data, int
6969
void common_hal_bitmaptools_dither(displayio_bitmap_t *dest_bitmap, displayio_bitmap_t *source_bitmap, displayio_colorspace_t colorspace, bitmaptools_dither_algorithm_t algorithm);
7070

7171
void common_hal_bitmaptools_alphablend(displayio_bitmap_t *destination, displayio_bitmap_t *source1, displayio_bitmap_t *source2, displayio_colorspace_t colorspace, mp_float_t factor1, mp_float_t factor2,
72-
bitmaptools_blendmode_t blendmode, uint32_t skip_source1_index, bool skip_source1_index_none, uint32_t skip_source2_index, bool skip_source2_index_none);
72+
bitmaptools_blendmode_t blendmode, uint32_t skip_source1_index, bool skip_source1_index_none, uint32_t skip_source2_index, bool skip_source2_index_none,
73+
displayio_bitmap_t *mask);
7374

7475
typedef struct {
7576
union {

shared-module/bitmaptools/__init__.c

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -857,23 +857,36 @@ void common_hal_bitmaptools_dither(displayio_bitmap_t *dest_bitmap, displayio_bi
857857
}
858858

859859
void common_hal_bitmaptools_alphablend(displayio_bitmap_t *dest, displayio_bitmap_t *source1, displayio_bitmap_t *source2, displayio_colorspace_t colorspace, mp_float_t factor1, mp_float_t factor2,
860-
bitmaptools_blendmode_t blendmode, uint32_t skip_source1_index, bool skip_source1_index_none, uint32_t skip_source2_index, bool skip_source2_index_none) {
860+
bitmaptools_blendmode_t blendmode, uint32_t skip_source1_index, bool skip_source1_index_none, uint32_t skip_source2_index, bool skip_source2_index_none,
861+
displayio_bitmap_t *mask) {
861862
displayio_area_t a = {0, 0, dest->width, dest->height, NULL};
862863
displayio_bitmap_set_dirty_area(dest, &a);
863864

864-
int ifactor1 = (int)(factor1 * 256);
865-
int ifactor2 = (int)(factor2 * 256);
865+
int ifactor1_base = (int)(factor1 * 256);
866+
int ifactor2_base = (int)(factor2 * 256);
867+
int ifactor1 = ifactor1_base;
868+
int ifactor2 = ifactor2_base;
866869
bool blend_source1, blend_source2;
867870

868871
if (colorspace == DISPLAYIO_COLORSPACE_L8) {
869872
for (int y = 0; y < dest->height; y++) {
870873
uint8_t *dptr = (uint8_t *)(dest->data + y * dest->stride);
871874
uint8_t *sptr1 = (uint8_t *)(source1->data + y * source1->stride);
872875
uint8_t *sptr2 = (uint8_t *)(source2->data + y * source2->stride);
876+
uint8_t *mptr = mask ? (uint8_t *)(mask->data + y * mask->stride) : NULL;
873877
int pixel;
874878
for (int x = 0; x < dest->width; x++) {
875879
blend_source1 = skip_source1_index_none || *sptr1 != (uint8_t)skip_source1_index;
876880
blend_source2 = skip_source2_index_none || *sptr2 != (uint8_t)skip_source2_index;
881+
if (mptr) {
882+
uint8_t m = *mptr;
883+
// Scale source2's contribution by the mask (0..255)
884+
ifactor2 = (ifactor2_base * m + 127) / 255;
885+
if (m == 0) {
886+
// Mask says fully transparent: drop source2 entirely
887+
blend_source2 = false;
888+
}
889+
}
877890
if (blend_source1 && blend_source2) {
878891
// Premultiply by the alpha factor
879892
int sda = *sptr1++ *ifactor1;
@@ -886,7 +899,8 @@ void common_hal_bitmaptools_alphablend(displayio_bitmap_t *dest, displayio_bitma
886899
blend = sca + sda * (256 - ifactor2) / 256;
887900
}
888901
// Divide by the alpha factor
889-
pixel = (blend / (ifactor1 + ifactor2 - ifactor1 * ifactor2 / 256));
902+
int denom = ifactor1 + ifactor2 - ifactor1 * ifactor2 / 256;
903+
pixel = (denom > 0) ? (blend / denom) : 0;
890904
} else if (blend_source1) {
891905
// Apply iFactor1 to source1 only
892906
pixel = *sptr1++ *ifactor1 / 256;
@@ -898,6 +912,9 @@ void common_hal_bitmaptools_alphablend(displayio_bitmap_t *dest, displayio_bitma
898912
pixel = *dptr;
899913
}
900914
*dptr++ = MIN(255, MAX(0, pixel));
915+
if (mptr) {
916+
mptr++;
917+
}
901918
}
902919
}
903920
} else {
@@ -907,6 +924,7 @@ void common_hal_bitmaptools_alphablend(displayio_bitmap_t *dest, displayio_bitma
907924
uint16_t *dptr = (uint16_t *)(dest->data + y * dest->stride);
908925
uint16_t *sptr1 = (uint16_t *)(source1->data + y * source1->stride);
909926
uint16_t *sptr2 = (uint16_t *)(source2->data + y * source2->stride);
927+
uint8_t *mptr = mask ? (uint8_t *)(mask->data + y * mask->stride) : NULL;
910928
for (int x = 0; x < dest->width; x++) {
911929
int spix1 = *sptr1++;
912930
int spix2 = *sptr2++;
@@ -922,11 +940,24 @@ void common_hal_bitmaptools_alphablend(displayio_bitmap_t *dest, displayio_bitma
922940
blend_source1 = skip_source1_index_none || spix1 != (int)skip_source1_index;
923941
blend_source2 = skip_source2_index_none || spix2 != (int)skip_source2_index;
924942

943+
if (mptr) {
944+
uint8_t m = *mptr++;
945+
ifactor2 = (ifactor2_base * m + 127) / 255;
946+
if (m == 0) {
947+
blend_source2 = false;
948+
}
949+
}
950+
925951
if (blend_source1 && blend_source2) {
926952
// Blend based on the SVG alpha compositing specs
927953
// https://dev.w3.org/SVG/modules/compositing/master/#alphaCompositing
928954

929955
int ifactor_blend = ifactor1 + ifactor2 - ifactor1 * ifactor2 / 256;
956+
if (ifactor_blend <= 0) {
957+
// Both factors are zero at this pixel; keep destination.
958+
dptr++;
959+
continue;
960+
}
930961

931962
// Premultiply the colors by the alpha factor
932963
int red_dca = ((spix1 & r_mask) >> 8) * ifactor1;

0 commit comments

Comments
 (0)