Alphablend changes

This commit is contained in:
Melissa LeBlanc-Williams 2023-08-08 12:42:48 -07:00
parent fb57c08013
commit 549bbdc31c
3 changed files with 153 additions and 21 deletions

View File

@ -273,6 +273,28 @@ 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);
MAKE_ENUM_VALUE(bitmaptools_blendmode_type, bitmaptools_blendmode, NORMAL, BITMAPTOOLS_BLENDMODE_NORMAL);
MAKE_ENUM_VALUE(bitmaptools_blendmode_type, bitmaptools_blendmode, SCREEN, BITMAPTOOLS_BLENDMODE_SCREEN);
//| class BlendMode:
//| """The blend mode for `alphablend` to operate use"""
//|
//| NORMAL: Blendmode
//| """Blend with equal parts of the two source bitmaps"""
//|
//| SCREEN: Blendmode
//| """Blend based on the value in each color channel. The result keeps the lighter colors and discards darker colors."""
//|
MAKE_ENUM_MAP(bitmaptools_blendmode) {
MAKE_ENUM_MAP_ENTRY(bitmaptools_blendmode, NORMAL),
MAKE_ENUM_MAP_ENTRY(bitmaptools_blendmode, SCREEN),
};
STATIC MP_DEFINE_CONST_DICT(bitmaptools_blendmode_locals_dict, bitmaptools_blendmode_locals_table);
MAKE_PRINTER(bitmaptools, bitmaptools_blendmode);
MAKE_ENUM_TYPE(bitmaptools, BlendMode, bitmaptools_blendmode);
// requires at least 2 arguments (destination bitmap and source bitmap)
//| def alphablend(
@ -282,6 +304,9 @@ MP_DEFINE_CONST_FUN_OBJ_KW(bitmaptools_rotozoom_obj, 0, bitmaptools_obj_rotozoom
//| colorspace: displayio.Colorspace,
//| factor1: float = 0.5,
//| factor2: Optional[float] = None,
//| blendmode: Optional[Blendmode] = Blendmode.NORMAL,
//| skip_source1_index: int,
//| skip_source2_index: int,
//| ) -> None:
//| """Alpha blend the two source bitmaps into the destination.
//|
@ -294,13 +319,18 @@ MP_DEFINE_CONST_FUN_OBJ_KW(bitmaptools_rotozoom_obj, 0, bitmaptools_obj_rotozoom
//| :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``.
//| :param bitmaptools.BlendMode blendmode: The blend mode to use. Default is NORMAL.
//| :param int skip_source1_index: bitmap palette index in the source that will not be blended,
//| set to None to blended all pixels
//| :param int skip_source2_index: bitmap palette index in the source that will not be blended,
//| set to None to blended all pixels
//|
//| 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};
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};
static const mp_arg_t allowed_args[] = {
{MP_QSTR_dest_bitmap, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = NULL}},
@ -309,6 +339,9 @@ STATIC mp_obj_t bitmaptools_alphablend(size_t n_args, const mp_obj_t *pos_args,
{MP_QSTR_colorspace, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = NULL}},
{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_QSTR_blendmode, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = (void *)&bitmaptools_blendmode_NORMAL_obj}},
{MP_QSTR_skip_source1_index, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} },
{MP_QSTR_skip_source2_index, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_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);
@ -321,6 +354,7 @@ STATIC mp_obj_t bitmaptools_alphablend(size_t n_args, const mp_obj_t *pos_args,
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);
displayio_colorspace_t colorspace = (displayio_colorspace_t)cp_enum_value(&displayio_colorspace_type, args[ARG_colorspace].u_obj, MP_QSTR_colorspace);
bitmaptools_blendmode_t blendmode = (bitmaptools_blendmode_t)cp_enum_value(&bitmaptools_blendmode_type, args[ARG_blendmode].u_obj, MP_QSTR_blendmode);
if (destination->width != source1->width
|| destination->height != source1->height
@ -352,7 +386,30 @@ STATIC mp_obj_t bitmaptools_alphablend(size_t n_args, const mp_obj_t *pos_args,
mp_raise_ValueError(translate("Unsupported colorspace"));
}
common_hal_bitmaptools_alphablend(destination, source1, source2, colorspace, factor1, factor2);
uint32_t skip_source1_index;
bool skip_source1_index_none; // flag whether skip_value was None
if (args[ARG_skip_source1_index].u_obj == mp_const_none) {
skip_source1_index = 0;
skip_source1_index_none = true;
} else {
skip_source1_index = mp_obj_get_int(args[ARG_skip_source1_index].u_obj);
skip_source1_index_none = false;
}
uint32_t skip_source2_index;
bool skip_source2_index_none; // flag whether skip_self_value was None
if (args[ARG_skip_source2_index].u_obj == mp_const_none) {
skip_source2_index = 0;
skip_source2_index_none = true;
} else {
skip_source2_index = mp_obj_get_int(args[ARG_skip_source2_index].u_obj);
skip_source2_index_none = false;
}
common_hal_bitmaptools_alphablend(destination, source1, source2, colorspace, factor1, factor2, blendmode, skip_source1_index,
skip_source1_index_none, skip_source2_index, skip_source2_index_none);
return mp_const_none;
}
@ -1080,6 +1137,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_Blendmode), MP_ROM_PTR(&bitmaptools_blendmode_type) },
{ 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) },

View File

@ -40,6 +40,11 @@ typedef enum {
extern const mp_obj_type_t bitmaptools_dither_algorithm_type;
typedef enum bitmaptools_blendmode {
BITMAPTOOLS_BLENDMODE_NORMAL,
BITMAPTOOLS_BLENDMODE_SCREEN,
} bitmaptools_blendmode_t;
void common_hal_bitmaptools_rotozoom(displayio_bitmap_t *self, int16_t ox, int16_t oy,
int16_t dest_clip0_x, int16_t dest_clip0_y,
int16_t dest_clip1_x, int16_t dest_clip1_y,
@ -78,6 +83,10 @@ void common_hal_bitmaptools_readinto(displayio_bitmap_t *self, mp_obj_t *file, i
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_dither(displayio_bitmap_t *dest_bitmap, displayio_bitmap_t *source_bitmap, displayio_colorspace_t colorspace, bitmaptools_dither_algorithm_t algorithm);
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);
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,
bitmaptools_blendmode_t blendmode, uint32_t skip_source1_index, bool skip_source1_index_none, uint32_t skip_source2_index, bool skip_source2_index_none);
extern const mp_obj_type_t bitmaptools_blendmode_type;
extern const cp_enum_obj_t bitmaptools_blendmode_NORMAL_obj;
#endif // MICROPY_INCLUDED_SHARED_BINDINGS_BITMAPTOOLS__INIT__H

View File

@ -861,26 +861,53 @@ void common_hal_bitmaptools_dither(displayio_bitmap_t *dest_bitmap, displayio_bi
displayio_bitmap_set_dirty_area(dest_bitmap, &a);
}
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) {
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,
bitmaptools_blendmode_t blendmode, uint32_t skip_source1_index, bool skip_source1_index_none, uint32_t skip_source2_index, bool skip_source2_index_none) {
displayio_area_t a = {0, 0, dest->width, dest->height, NULL};
displayio_bitmap_set_dirty_area(dest, &a);
int ifactor1 = (int)(factor1 * 256);
int ifactor2 = (int)(factor2 * 256);
bool blend_source1, blend_source2;
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);
int pixel;
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;
blend_source1 = skip_source1_index_none || *sptr1 != (uint8_t)skip_source1_index;
blend_source2 = skip_source2_index_none || *sptr2 != (uint8_t)skip_source2_index;
if (blend_source1 && blend_source2) {
// Premultiply by the alpha factor
int sca1 = *sptr1++ * ifactor1;
int sca2 = *sptr2++ * ifactor2;
// Blend
int blend;
if (blendmode == BITMAPTOOLS_BLENDMODE_SCREEN) {
blend = sca2 + sca1 - sca2 * sca1;
} else {
blend = sca2 + sca1 * (256 - ifactor2);
}
// Divide by the alpha factor
pixel = (blend / (256 * ifactor1 + 256 * ifactor2 - ifactor1 * ifactor2));
} else if (blend_source1) {
// Apply iFactor1 to source1 only
pixel = *sptr1++ *ifactor1 / 256;
} else if (blend_source2) {
// Apply iFactor2 to source1 only
pixel = *sptr2++ *ifactor2 / 256;
} else {
// Use the destination value
pixel = *dptr;
}
*dptr++ = MIN(255, MAX(0, pixel));
}
}
} else {
bool swap = (colorspace == DISPLAYIO_COLORSPACE_RGB565_SWAPPED) || (colorspace == DISPLAYIO_COLORSPACE_BGR565_SWAPPED);
uint16_t pixel;
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);
@ -897,25 +924,63 @@ void common_hal_bitmaptools_alphablend(displayio_bitmap_t *dest, displayio_bitma
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;
blend_source1 = skip_source1_index_none || spix1 != (int)skip_source1_index;
blend_source2 = skip_source2_index_none || spix2 != (int)skip_source2_index;
// ditto
int g = ((spix1 & g_mask) * ifactor1
+ (spix2 & g_mask) * ifactor2 + g_mask / 2) / 256;
g = MIN(g_mask, MAX(0, g)) & g_mask;
if (blend_source1 && blend_source2) {
// Blend based on the SVG alpha compositing specs
// https://dev.w3.org/SVG/modules/compositing/master/#alphaCompositing
int b = ((spix1 & b_mask) * ifactor1
+ (spix2 & b_mask) * ifactor2 + b_mask / 2) / 256;
b = MIN(b_mask, MAX(0, b)) & b_mask;
int ifactor_blend = ifactor1 + ifactor2 - ifactor1 * ifactor2 / 256;
uint16_t pixel = r | g | b;
if (swap) {
pixel = __builtin_bswap16(pixel);
// Premultiply the colors by the alpha factor
int red_sca1 = ((spix1 & r_mask) >> 11) * ifactor1;
int green_sca1 = ((spix1 & g_mask) >> 5) * ifactor1;
int blue_sca1 = (spix1 & b_mask) * ifactor1;
int red_sca2 = ((spix2 & r_mask) >> 11) * ifactor2;
int green_sca2 = ((spix2 & g_mask) >> 5) * ifactor2;
int blue_sca2 = (spix2 & b_mask) * ifactor2;
int red_blend, green_blend, blue_blend;
if (blendmode == BITMAPTOOLS_BLENDMODE_SCREEN) {
// Perform a screen blend
red_blend = red_sca2 + red_sca1 - (red_sca2 * red_sca1);
green_blend = green_sca2 + green_sca1 - (green_sca2 * green_sca1);
blue_blend = blue_sca2 + blue_sca1 - (blue_sca2 * blue_sca1);
} else {
// Perform a normal blend
red_blend = red_sca2 + red_sca1 * (256 - ifactor2) / 256;
green_blend = green_sca2 + green_sca1 * (256 - ifactor2) / 256;
blue_blend = blue_sca2 + blue_sca1 * (256 - ifactor2) / 256;
}
// Divide by the alpha factor
int r = ((red_blend / ifactor_blend) << 11) & r_mask;
int g = ((green_blend / ifactor_blend) << 5) & g_mask;
int b = (blue_blend / ifactor_blend) & b_mask;
// Clamp to the appropriate range
r = MIN(r_mask, MAX(0, r)) & r_mask;
g = MIN(g_mask, MAX(0, g)) & g_mask;
b = MIN(b_mask, MAX(0, b)) & b_mask;
pixel = r | g | b;
if (swap) {
pixel = __builtin_bswap16(pixel);
}
} else if (blend_source1) {
// Apply iFactor1 to source1 only
pixel = spix1 * ifactor1 / 256;
} else if (blend_source2) {
// Apply iFactor2 to source1 only
pixel = spix2 * ifactor2 / 256;
} else {
// Use the destination value
pixel = *dptr;
}
*dptr++ = pixel;
}
}