Skip to content

Commit d0f313c

Browse files
authored
Merge pull request micropython#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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy