@@ -134,7 +134,7 @@ STATIC mp_obj_t bitmapfilter_morph(size_t n_args, const mp_obj_t *pos_args, mp_m
134134 mp_obj_t weights = args [ARG_weights ].u_obj ;
135135 mp_obj_t obj_len = mp_obj_len (weights );
136136 if (obj_len == MP_OBJ_NULL || !mp_obj_is_small_int (obj_len )) {
137- mp_raise_ValueError_varg (MP_ERROR_TEXT ("%q must be of type %q, not %q" ), MP_QSTR_weights , MP_QSTR_Sequence , mp_obj_get_type (weights )-> name );
137+ mp_raise_ValueError_varg (MP_ERROR_TEXT ("%q must be of type %q, not %q" ), MP_QSTR_weights , MP_QSTR_Sequence , mp_obj_get_type_qstr (weights ));
138138 }
139139
140140 size_t n_weights = MP_OBJ_SMALL_INT_VALUE (obj_len );
@@ -608,8 +608,131 @@ STATIC mp_obj_t bitmapfilter_false_color(size_t n_args, const mp_obj_t *pos_args
608608 shared_module_bitmapfilter_false_color (bitmap , mask , palette -> colors );
609609 return args [ARG_bitmap ].u_obj ;
610610}
611-
612611MP_DEFINE_CONST_FUN_OBJ_KW (bitmapfilter_false_color_obj , 0 , bitmapfilter_false_color );
612+
613+ #define BLEND_TABLE_SIZE (4096)
614+ STATIC uint8_t * get_blend_table (mp_obj_t lookup , int mode ) {
615+ mp_buffer_info_t lookup_buf ;
616+ if (!mp_get_buffer (lookup , & lookup_buf , mode ) || lookup_buf .len != BLEND_TABLE_SIZE ) {
617+ return NULL ;
618+ }
619+ return lookup_buf .buf ;
620+ }
621+ //|
622+ //| BlendFunction = Callable[[float, float], float]
623+ //| """A function used to blend two images"""
624+ //|
625+ //| BlendTable = bytearray
626+ //| """A precomputed blend table
627+ //|
628+ //| There is not actually a BlendTable type. The real type is actually any
629+ //| buffer 4096 bytes in length."""
630+ //|
631+ //| def blend_precompute(lookup: BlendFunction, table: BlendTable | None = None) -> BlendTable:
632+ //| """Precompute a BlendTable from a BlendFunction
633+ //|
634+ //| If the optional ``table`` argument is provided, an existing `BlendTable` is updated
635+ //| with the new function values.
636+ //|
637+ //| The function's two arguments will range from 0 to 1. The returned value should also range from 0 to 1.
638+ //|
639+ //| A function to do a 33% blend of each source image could look like this:
640+ //|
641+ //| .. code-block:: python
642+ //|
643+ //| def blend_one_third(a, b):
644+ //| return a * .33 + b * .67
645+ //| """
646+ //|
647+ STATIC mp_obj_t blend_precompute (size_t n_args , const mp_obj_t * pos_args , mp_map_t * kw_args ) {
648+ enum { ARG_lookup , ARG_table };
649+ static const mp_arg_t allowed_args [] = {
650+ { MP_QSTR_lookup , MP_ARG_REQUIRED | MP_ARG_OBJ , { .u_obj = MP_OBJ_NULL } },
651+ { MP_QSTR_table , MP_ARG_OBJ , { .u_obj = MP_ROM_NONE } },
652+ };
653+ mp_arg_val_t args [MP_ARRAY_SIZE (allowed_args )];
654+ mp_arg_parse_all (n_args , pos_args , kw_args , MP_ARRAY_SIZE (allowed_args ), allowed_args , args );
655+
656+ mp_obj_t table = args [ARG_table ].u_obj ;
657+ if (table == mp_const_none ) {
658+ table = mp_obj_new_bytearray_of_zeros (BLEND_TABLE_SIZE );
659+ }
660+ uint8_t * buf = get_blend_table (table , MP_BUFFER_WRITE );
661+ if (!buf ) {
662+ mp_raise_TypeError_varg (MP_ERROR_TEXT ("%q must be of type %q or %q, not %q" ),
663+ MP_QSTR_table , MP_QSTR_NoneType , MP_QSTR_WritableBuffer ,
664+ mp_obj_get_type_qstr (table ));
665+ }
666+ shared_module_bitmapfilter_blend_precompute (args [ARG_lookup ].u_obj , buf );
667+ return table ;
668+ }
669+ MP_DEFINE_CONST_FUN_OBJ_KW (bitmapfilter_blend_precompute_obj , 0 , blend_precompute );
670+
671+ //|
672+ //| def blend(
673+ //| dest: displayio.Bitmap,
674+ //| src1: displayio.Bitmap,
675+ //| src2: displayio.Bitmap,
676+ //| lookup: BlendFunction | BlendTable,
677+ //| mask: displayio.Bitmap | None = None,
678+ //| ) -> displayio.Bitmap:
679+ //| """Blend the 'src1' and 'src2' images according to lookup function or table 'lookup'
680+ //|
681+ //| If ``lookup`` is a function, it is converted to a `BlendTable` by
682+ //| internally calling blend_precompute. If a blend function is used repeatedly
683+ //| it can be more efficient to compute it once with `blend_precompute`.
684+ //|
685+ //| If the mask is supplied, pixels from ``src1`` are taken unchanged in masked areas.
686+ //|
687+ //| The source and destination bitmaps may be the same bitmap.
688+ //|
689+ //| The destination bitmap is returned.
690+ //| """
691+ //|
692+
693+ STATIC mp_obj_t bitmapfilter_blend (size_t n_args , const mp_obj_t * pos_args , mp_map_t * kw_args ) {
694+ enum { ARG_dest , ARG_src1 , ARG_src2 , ARG_lookup , ARG_mask };
695+ static const mp_arg_t allowed_args [] = {
696+ { MP_QSTR_dest , MP_ARG_REQUIRED | MP_ARG_OBJ , { .u_obj = MP_OBJ_NULL } },
697+ { MP_QSTR_src1 , MP_ARG_REQUIRED | MP_ARG_OBJ , { .u_obj = MP_OBJ_NULL } },
698+ { MP_QSTR_src2 , MP_ARG_REQUIRED | MP_ARG_OBJ , { .u_obj = MP_OBJ_NULL } },
699+ { MP_QSTR_lookup , MP_ARG_REQUIRED | MP_ARG_OBJ , { .u_obj = MP_OBJ_NULL } },
700+ { MP_QSTR_mask , MP_ARG_OBJ , { .u_obj = MP_ROM_NONE } },
701+ };
702+ mp_arg_val_t args [MP_ARRAY_SIZE (allowed_args )];
703+ mp_arg_parse_all (n_args , pos_args , kw_args , MP_ARRAY_SIZE (allowed_args ), allowed_args , args );
704+
705+ mp_arg_validate_type (args [ARG_dest ].u_obj , & displayio_bitmap_type , MP_QSTR_dest );
706+ displayio_bitmap_t * dest = MP_OBJ_TO_PTR (args [ARG_dest ].u_obj );
707+
708+ mp_arg_validate_type (args [ARG_src1 ].u_obj , & displayio_bitmap_type , MP_QSTR_src1 );
709+ displayio_bitmap_t * src1 = MP_OBJ_TO_PTR (args [ARG_src1 ].u_obj );
710+
711+ mp_arg_validate_type (args [ARG_src2 ].u_obj , & displayio_bitmap_type , MP_QSTR_src2 );
712+ displayio_bitmap_t * src2 = MP_OBJ_TO_PTR (args [ARG_src2 ].u_obj );
713+
714+ mp_obj_t lookup = args [ARG_lookup ].u_obj ;
715+ if (mp_obj_is_callable (lookup )) {
716+ lookup = mp_call_function_1 (MP_OBJ_FROM_PTR (& bitmapfilter_blend_precompute_obj ), lookup );
717+ }
718+ uint8_t * lookup_buf = get_blend_table (lookup , MP_BUFFER_READ );
719+ if (!lookup_buf ) {
720+ mp_raise_TypeError_varg (MP_ERROR_TEXT ("%q must be of type %q or %q, not %q" ),
721+ MP_QSTR_lookup , MP_QSTR_callable , MP_QSTR_ReadableBuffer ,
722+ mp_obj_get_type_qstr (lookup ));
723+ }
724+
725+ displayio_bitmap_t * mask = NULL ;
726+ if (args [ARG_mask ].u_obj != mp_const_none ) {
727+ mp_arg_validate_type (args [ARG_mask ].u_obj , & displayio_bitmap_type , MP_QSTR_mask );
728+ mask = MP_OBJ_TO_PTR (args [ARG_mask ].u_obj );
729+ }
730+
731+ shared_module_bitmapfilter_blend (dest , src1 , src2 , mask , lookup_buf );
732+ return args [ARG_dest ].u_obj ;
733+ }
734+ MP_DEFINE_CONST_FUN_OBJ_KW (bitmapfilter_blend_obj , 0 , bitmapfilter_blend );
735+
613736STATIC const mp_rom_map_elem_t bitmapfilter_module_globals_table [] = {
614737 { MP_ROM_QSTR (MP_QSTR___name__ ), MP_ROM_QSTR (MP_QSTR_bitmapfilter ) },
615738 { MP_ROM_QSTR (MP_QSTR_morph ), MP_ROM_PTR (& bitmapfilter_morph_obj ) },
@@ -621,6 +744,8 @@ STATIC const mp_rom_map_elem_t bitmapfilter_module_globals_table[] = {
621744 { MP_ROM_QSTR (MP_QSTR_ChannelScaleOffset ), MP_ROM_PTR (& bitmapfilter_channel_scale_offset_type ) },
622745 { MP_ROM_QSTR (MP_QSTR_ChannelMixer ), MP_ROM_PTR (& bitmapfilter_channel_mixer_type ) },
623746 { MP_ROM_QSTR (MP_QSTR_ChannelMixerOffset ), MP_ROM_PTR (& bitmapfilter_channel_mixer_offset_type ) },
747+ { MP_ROM_QSTR (MP_QSTR_blend ), MP_ROM_PTR (& bitmapfilter_blend_obj ) },
748+ { MP_ROM_QSTR (MP_QSTR_blend_precompute ), MP_ROM_PTR (& bitmapfilter_blend_precompute_obj ) },
624749};
625750STATIC MP_DEFINE_CONST_DICT (bitmapfilter_module_globals , bitmapfilter_module_globals_table );
626751
0 commit comments