Skip to content

Commit d0f313c

Browse files
authored
Merge pull request #8270 from makermelissa/better-alphablend
Better alphablend features
2 parents 41ee1e0 + ce61fd8 commit d0f313c

File tree

4 files changed

+157
-21
lines changed

4 files changed

+157
-21
lines changed

ports/atmel-samd/boards/feather_m4_express/mpconfigboard.mk

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ EXTERNAL_FLASH_DEVICES = GD25Q16C
1111
LONGINT_IMPL = MPZ
1212

1313
CIRCUITPY__EVE = 1
14+
CIRCUITPY_FLOPPYIO = 0
1415
CIRCUITPY_SYNTHIO = 0
1516

1617
# We don't have room for the fonts for terminalio for certain languages,

shared-bindings/bitmaptools/__init__.c

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,28 @@ STATIC mp_obj_t bitmaptools_obj_rotozoom(size_t n_args, const mp_obj_t *pos_args
273273
}
274274

275275
MP_DEFINE_CONST_FUN_OBJ_KW(bitmaptools_rotozoom_obj, 0, bitmaptools_obj_rotozoom);
276+
277+
//| class BlendMode:
278+
//| """The blend mode for `alphablend` to operate use"""
279+
//|
280+
//| Normal: BlendMode
281+
//| """Blend with equal parts of the two source bitmaps"""
282+
//|
283+
//| Screen: BlendMode
284+
//| """Blend based on the value in each color channel. The result keeps the lighter colors and discards darker colors."""
285+
//|
286+
MAKE_ENUM_VALUE(bitmaptools_blendmode_type, bitmaptools_blendmode, Normal, BITMAPTOOLS_BLENDMODE_NORMAL);
287+
MAKE_ENUM_VALUE(bitmaptools_blendmode_type, bitmaptools_blendmode, Screen, BITMAPTOOLS_BLENDMODE_SCREEN);
288+
289+
MAKE_ENUM_MAP(bitmaptools_blendmode) {
290+
MAKE_ENUM_MAP_ENTRY(bitmaptools_blendmode, Normal),
291+
MAKE_ENUM_MAP_ENTRY(bitmaptools_blendmode, Screen),
292+
};
293+
STATIC MP_DEFINE_CONST_DICT(bitmaptools_blendmode_locals_dict, bitmaptools_blendmode_locals_table);
294+
295+
MAKE_PRINTER(bitmaptools, bitmaptools_blendmode);
296+
MAKE_ENUM_TYPE(bitmaptools, BlendMode, bitmaptools_blendmode);
297+
276298
// requires at least 2 arguments (destination bitmap and source bitmap)
277299

278300
//| def alphablend(
@@ -282,6 +304,9 @@ MP_DEFINE_CONST_FUN_OBJ_KW(bitmaptools_rotozoom_obj, 0, bitmaptools_obj_rotozoom
282304
//| colorspace: displayio.Colorspace,
283305
//| factor1: float = 0.5,
284306
//| factor2: Optional[float] = None,
307+
//| blendmode: Optional[BlendMode] = BlendMode.Normal,
308+
//| skip_source1_index: Union[int, None] = None,
309+
//| skip_source2_index: Union[int, None] = None,
285310
//| ) -> None:
286311
//| """Alpha blend the two source bitmaps into the destination.
287312
//|
@@ -294,13 +319,16 @@ MP_DEFINE_CONST_FUN_OBJ_KW(bitmaptools_rotozoom_obj, 0, bitmaptools_obj_rotozoom
294319
//| :param float factor1: The proportion of bitmap 1 to mix in
295320
//| :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.
296321
//| :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``.
322+
//| :param bitmaptools.BlendMode blendmode: The blend mode to use. Default is Normal.
323+
//| :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
324+
//| :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
297325
//|
298326
//| For the L8 colorspace, the bitmaps must have a bits-per-value of 8.
299327
//| For the RGB colorspaces, they must have a bits-per-value of 16."""
300328
//|
301329

302330
STATIC mp_obj_t bitmaptools_alphablend(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
303-
enum {ARG_dest_bitmap, ARG_source_bitmap_1, ARG_source_bitmap_2, ARG_colorspace, ARG_factor_1, ARG_factor_2};
331+
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};
304332

305333
static const mp_arg_t allowed_args[] = {
306334
{MP_QSTR_dest_bitmap, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = NULL}},
@@ -309,6 +337,9 @@ STATIC mp_obj_t bitmaptools_alphablend(size_t n_args, const mp_obj_t *pos_args,
309337
{MP_QSTR_colorspace, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = NULL}},
310338
{MP_QSTR_factor_1, MP_ARG_OBJ, {.u_obj = MP_ROM_NONE}},
311339
{MP_QSTR_factor_2, MP_ARG_OBJ, {.u_obj = MP_ROM_NONE}},
340+
{MP_QSTR_blendmode, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = (void *)&bitmaptools_blendmode_Normal_obj}},
341+
{MP_QSTR_skip_source1_index, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} },
342+
{MP_QSTR_skip_source2_index, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} },
312343
};
313344
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
314345
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
@@ -321,6 +352,7 @@ STATIC mp_obj_t bitmaptools_alphablend(size_t n_args, const mp_obj_t *pos_args,
321352
mp_float_t factor2 = (args[ARG_factor_2].u_obj == mp_const_none) ? 1 - factor1 : mp_obj_get_float(args[ARG_factor_2].u_obj);
322353

323354
displayio_colorspace_t colorspace = (displayio_colorspace_t)cp_enum_value(&displayio_colorspace_type, args[ARG_colorspace].u_obj, MP_QSTR_colorspace);
355+
bitmaptools_blendmode_t blendmode = (bitmaptools_blendmode_t)cp_enum_value(&bitmaptools_blendmode_type, args[ARG_blendmode].u_obj, MP_QSTR_blendmode);
324356

325357
if (destination->width != source1->width
326358
|| destination->height != source1->height
@@ -352,7 +384,30 @@ STATIC mp_obj_t bitmaptools_alphablend(size_t n_args, const mp_obj_t *pos_args,
352384
mp_raise_ValueError(translate("Unsupported colorspace"));
353385
}
354386

355-
common_hal_bitmaptools_alphablend(destination, source1, source2, colorspace, factor1, factor2);
387+
uint32_t skip_source1_index;
388+
bool skip_source1_index_none; // flag whether skip_value was None
389+
390+
if (args[ARG_skip_source1_index].u_obj == mp_const_none) {
391+
skip_source1_index = 0;
392+
skip_source1_index_none = true;
393+
} else {
394+
skip_source1_index = mp_obj_get_int(args[ARG_skip_source1_index].u_obj);
395+
skip_source1_index_none = false;
396+
}
397+
398+
uint32_t skip_source2_index;
399+
bool skip_source2_index_none; // flag whether skip_self_value was None
400+
401+
if (args[ARG_skip_source2_index].u_obj == mp_const_none) {
402+
skip_source2_index = 0;
403+
skip_source2_index_none = true;
404+
} else {
405+
skip_source2_index = mp_obj_get_int(args[ARG_skip_source2_index].u_obj);
406+
skip_source2_index_none = false;
407+
}
408+
409+
common_hal_bitmaptools_alphablend(destination, source1, source2, colorspace, factor1, factor2, blendmode, skip_source1_index,
410+
skip_source1_index_none, skip_source2_index, skip_source2_index_none);
356411

357412
return mp_const_none;
358413
}
@@ -1088,6 +1143,7 @@ STATIC const mp_rom_map_elem_t bitmaptools_module_globals_table[] = {
10881143
{ MP_ROM_QSTR(MP_QSTR_draw_circle), MP_ROM_PTR(&bitmaptools_draw_circle_obj) },
10891144
{ MP_ROM_QSTR(MP_QSTR_blit), MP_ROM_PTR(&bitmaptools_blit_obj) },
10901145
{ MP_ROM_QSTR(MP_QSTR_dither), MP_ROM_PTR(&bitmaptools_dither_obj) },
1146+
{ MP_ROM_QSTR(MP_QSTR_BlendMode), MP_ROM_PTR(&bitmaptools_blendmode_type) },
10911147
{ MP_ROM_QSTR(MP_QSTR_DitherAlgorithm), MP_ROM_PTR(&bitmaptools_dither_algorithm_type) },
10921148
};
10931149
STATIC MP_DEFINE_CONST_DICT(bitmaptools_module_globals, bitmaptools_module_globals_table);

shared-bindings/bitmaptools/__init__.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,13 @@ typedef enum {
4040

4141
extern const mp_obj_type_t bitmaptools_dither_algorithm_type;
4242

43+
typedef enum {
44+
BITMAPTOOLS_BLENDMODE_NORMAL, BITMAPTOOLS_BLENDMODE_SCREEN,
45+
} bitmaptools_blendmode_t;
46+
47+
extern const mp_obj_type_t bitmaptools_blendmode_type;
48+
extern const cp_enum_obj_t bitmaptools_blendmode_Normal_obj;
49+
4350
void common_hal_bitmaptools_rotozoom(displayio_bitmap_t *self, int16_t ox, int16_t oy,
4451
int16_t dest_clip0_x, int16_t dest_clip0_y,
4552
int16_t dest_clip1_x, int16_t dest_clip1_y,
@@ -78,6 +85,7 @@ void common_hal_bitmaptools_readinto(displayio_bitmap_t *self, mp_obj_t *file, i
7885
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);
7986
void common_hal_bitmaptools_dither(displayio_bitmap_t *dest_bitmap, displayio_bitmap_t *source_bitmap, displayio_colorspace_t colorspace, bitmaptools_dither_algorithm_t algorithm);
8087

81-
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);
88+
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,
89+
bitmaptools_blendmode_t blendmode, uint32_t skip_source1_index, bool skip_source1_index_none, uint32_t skip_source2_index, bool skip_source2_index_none);
8290

8391
#endif // MICROPY_INCLUDED_SHARED_BINDINGS_BITMAPTOOLS__INIT__H

shared-module/bitmaptools/__init__.c

Lines changed: 89 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -861,26 +861,53 @@ void common_hal_bitmaptools_dither(displayio_bitmap_t *dest_bitmap, displayio_bi
861861
displayio_bitmap_set_dirty_area(dest_bitmap, &a);
862862
}
863863

864-
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) {
864+
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,
865+
bitmaptools_blendmode_t blendmode, uint32_t skip_source1_index, bool skip_source1_index_none, uint32_t skip_source2_index, bool skip_source2_index_none) {
865866
displayio_area_t a = {0, 0, dest->width, dest->height, NULL};
866867
displayio_bitmap_set_dirty_area(dest, &a);
867868

868869
int ifactor1 = (int)(factor1 * 256);
869870
int ifactor2 = (int)(factor2 * 256);
871+
bool blend_source1, blend_source2;
870872

871873
if (colorspace == DISPLAYIO_COLORSPACE_L8) {
872874
for (int y = 0; y < dest->height; y++) {
873875
uint8_t *dptr = (uint8_t *)(dest->data + y * dest->stride);
874876
uint8_t *sptr1 = (uint8_t *)(source1->data + y * source1->stride);
875877
uint8_t *sptr2 = (uint8_t *)(source2->data + y * source2->stride);
878+
int pixel;
876879
for (int x = 0; x < dest->width; x++) {
877-
// This is round(l1*f1 + l2*f2) & clip to range in fixed-point
878-
int pixel = (*sptr1++ *ifactor1 + *sptr2++ *ifactor2 + 128) / 256;
880+
blend_source1 = skip_source1_index_none || *sptr1 != (uint8_t)skip_source1_index;
881+
blend_source2 = skip_source2_index_none || *sptr2 != (uint8_t)skip_source2_index;
882+
if (blend_source1 && blend_source2) {
883+
// Premultiply by the alpha factor
884+
int sda = *sptr1++ *ifactor1;
885+
int sca = *sptr2++ *ifactor2;
886+
// Blend
887+
int blend;
888+
if (blendmode == BITMAPTOOLS_BLENDMODE_SCREEN) {
889+
blend = sca + sda - (sca * sda / 65536);
890+
} else {
891+
blend = sca + sda * (256 - ifactor2) / 256;
892+
}
893+
// Divide by the alpha factor
894+
pixel = (blend / (ifactor1 + ifactor2 - ifactor1 * ifactor2 / 256));
895+
} else if (blend_source1) {
896+
// Apply iFactor1 to source1 only
897+
pixel = *sptr1++ *ifactor1 / 256;
898+
} else if (blend_source2) {
899+
// Apply iFactor2 to source1 only
900+
pixel = *sptr2++ *ifactor2 / 256;
901+
} else {
902+
// Use the destination value
903+
pixel = *dptr;
904+
}
879905
*dptr++ = MIN(255, MAX(0, pixel));
880906
}
881907
}
882908
} else {
883909
bool swap = (colorspace == DISPLAYIO_COLORSPACE_RGB565_SWAPPED) || (colorspace == DISPLAYIO_COLORSPACE_BGR565_SWAPPED);
910+
uint16_t pixel;
884911
for (int y = 0; y < dest->height; y++) {
885912
uint16_t *dptr = (uint16_t *)(dest->data + y * dest->stride);
886913
uint16_t *sptr1 = (uint16_t *)(source1->data + y * source1->stride);
@@ -897,25 +924,69 @@ void common_hal_bitmaptools_alphablend(displayio_bitmap_t *dest, displayio_bitma
897924
const int g_mask = 0x07e0;
898925
const int b_mask = 0x001f; // (or r mask, if BGR)
899926

900-
// This is round(r1*f1 + r2*f2) & clip to range in fixed-point
901-
// but avoiding shifting it down to start at bit 0
902-
int r = ((spix1 & r_mask) * ifactor1
903-
+ (spix2 & r_mask) * ifactor2 + r_mask / 2) / 256;
904-
r = MIN(r_mask, MAX(0, r)) & r_mask;
927+
blend_source1 = skip_source1_index_none || spix1 != (int)skip_source1_index;
928+
blend_source2 = skip_source2_index_none || spix2 != (int)skip_source2_index;
929+
930+
if (blend_source1 && blend_source2) {
931+
// Blend based on the SVG alpha compositing specs
932+
// https://dev.w3.org/SVG/modules/compositing/master/#alphaCompositing
933+
934+
int ifactor_blend = ifactor1 + ifactor2 - ifactor1 * ifactor2 / 256;
935+
936+
// Premultiply the colors by the alpha factor
937+
int red_dca = ((spix1 & r_mask) >> 8) * ifactor1;
938+
int grn_dca = ((spix1 & g_mask) >> 3) * ifactor1;
939+
int blu_dca = ((spix1 & b_mask) << 3) * ifactor1;
940+
941+
int red_sca = ((spix2 & r_mask) >> 8) * ifactor2;
942+
int grn_sca = ((spix2 & g_mask) >> 3) * ifactor2;
943+
int blu_sca = ((spix2 & b_mask) << 3) * ifactor2;
944+
945+
int red_blend, grn_blend, blu_blend;
946+
if (blendmode == BITMAPTOOLS_BLENDMODE_SCREEN) {
947+
// Perform a screen blend Sca + Dca - Sca × Dca
948+
red_blend = red_sca + red_dca - (red_sca * red_dca / 65536);
949+
grn_blend = grn_sca + grn_dca - (grn_sca * grn_dca / 65536);
950+
blu_blend = blu_sca + blu_dca - (blu_sca * blu_dca / 65536);
951+
} else {
952+
// Perform a normal (src-over) blend
953+
red_blend = red_sca + red_dca * (256 - ifactor2) / 256;
954+
grn_blend = grn_sca + grn_dca * (256 - ifactor2) / 256;
955+
blu_blend = blu_sca + blu_dca * (256 - ifactor2) / 256;
956+
}
905957

906-
// ditto
907-
int g = ((spix1 & g_mask) * ifactor1
908-
+ (spix2 & g_mask) * ifactor2 + g_mask / 2) / 256;
909-
g = MIN(g_mask, MAX(0, g)) & g_mask;
958+
// Divide by the alpha factor
959+
int r = ((red_blend / ifactor_blend) << 8) & r_mask;
960+
int g = ((grn_blend / ifactor_blend) << 3) & g_mask;
961+
int b = ((blu_blend / ifactor_blend) >> 3) & b_mask;
910962

911-
int b = ((spix1 & b_mask) * ifactor1
912-
+ (spix2 & b_mask) * ifactor2 + b_mask / 2) / 256;
913-
b = MIN(b_mask, MAX(0, b)) & b_mask;
963+
// Clamp to the appropriate range
964+
r = MIN(r_mask, MAX(0, r)) & r_mask;
965+
g = MIN(g_mask, MAX(0, g)) & g_mask;
966+
b = MIN(b_mask, MAX(0, b)) & b_mask;
914967

915-
uint16_t pixel = r | g | b;
916-
if (swap) {
917-
pixel = __builtin_bswap16(pixel);
968+
pixel = r | g | b;
969+
970+
if (swap) {
971+
pixel = __builtin_bswap16(pixel);
972+
}
973+
} else if (blend_source1) {
974+
// Apply iFactor1 to source1 only
975+
int r = (spix1 & r_mask) * ifactor1 / 256;
976+
int g = (spix1 & g_mask) * ifactor1 / 256;
977+
int b = (spix1 & b_mask) * ifactor1 / 256;
978+
pixel = r | g | b;
979+
} else if (blend_source2) {
980+
// Apply iFactor2 to source1 only
981+
int r = (spix2 & r_mask) * ifactor2 / 256;
982+
int g = (spix2 & g_mask) * ifactor2 / 256;
983+
int b = (spix2 & b_mask) * ifactor2 / 256;
984+
pixel = r | g | b;
985+
} else {
986+
// Use the destination value
987+
pixel = *dptr;
918988
}
989+
919990
*dptr++ = pixel;
920991
}
921992
}

0 commit comments

Comments
 (0)