3030#include "shared-bindings/displayio/Bitmap.h"
3131#include "shared-bindings/bitmapfilter/__init__.h"
3232
33+ //|
34+ //| def morph(
35+ //| bitmap: displayio.Bitmap,
36+ //| weights: tuple[int],
37+ //| mul: float = 1.0,
38+ //| add: int = 0,
39+ //| mask: displayio.Bitmap | None = None,
40+ //| threshold=False,
41+ //| offset: int = 0,
42+ //| invert: bool = False,
43+ //| ):
44+ //| """Convolve an image with a kernel
45+ //|
46+ //| The ``bitmap``, which must be in RGB565_SWAPPED format, is modified
47+ //| according to the ``weights``. Then a scaling factor ``m`` and an
48+ //| offset factor ``b`` are applied.
49+ //|
50+ //| The ``weights`` must be a tuple of integers. The length of the tuple
51+ //| must be the square of an odd number, usually 9 and sometimes 25.
52+ //| Specific weights create different effects. For instance, these
53+ //| weights represent a 3x3 gaussian blur:
54+ //|
55+ //| ``mul`` is number to multiply the convolution pixel results by. When
56+ //| not set it defaults to a value that will prevent scaling in the
57+ //| convolution output.
58+ //|
59+ //| ``add`` is a value to add to each convolution pixel result.
60+ //|
61+ //| ``mul`` basically allows you to do a global contrast adjustment and
62+ //| add allows you to do a global brightness adjustment. Pixels that go
63+ //| outside of the image mins and maxes for color channels will be
64+ //| clipped.
65+ //|
66+ //| If you’d like to adaptive threshold the image on the output of the
67+ //| filter you can pass ``threshold=True`` which will enable adaptive
68+ //| thresholding of the image which sets pixels to one or zero based on a
69+ //| pixel’s brightness in relation to the brightness of the kernel of pixels
70+ //| around them. A negative ``offset`` value sets more pixels to 1 as you make
71+ //| it more negative while a positive value only sets the sharpest contrast
72+ //| changes to 1. Set ``invert`` to invert the binary image resulting output.
73+ //|
74+ //| ``mask`` is another image to use as a pixel level mask for the operation.
75+ //| The mask should be an image with just black or white pixels and should
76+ //| be the same size as the image being operated on. Only pixels set in the
77+ //| mask are modified.
78+ //|
79+ //| .. code-block:: python
80+ //|
81+ //| kernel_gauss_3 = [
82+ //| 1, 2, 1,
83+ //| 2, 4, 2,
84+ //| 1, 2, 1]
85+ //|
86+ //| def blur(bitmap):
87+ //| \"""Blur the bitmap with a 3x3 gaussian kernel\"""
88+ //| bitmapfilter.morph(bitmap, kernel_gauss_3, 1/sum(kernel_gauss_3))
89+ //| """
90+ //|
91+
3392STATIC mp_obj_t bitmapfilter_morph (size_t n_args , const mp_obj_t * pos_args , mp_map_t * kw_args ) {
34- enum { ARG_dest_bitmap , ARG_source_bitmap , ARG_weights , ARG_m , ARG_b };
93+ enum { ARG_bitmap , ARG_weights , ARG_mul , ARG_add , ARG_threshold , ARG_offset , ARG_invert , ARG_mask };
3594 static const mp_arg_t allowed_args [] = {
36- { MP_QSTR_dest_bitmap , MP_ARG_REQUIRED | MP_ARG_OBJ , { .u_obj = MP_OBJ_NULL } },
37- { MP_QSTR_source_bitmap , MP_ARG_REQUIRED | MP_ARG_OBJ , { .u_obj = MP_OBJ_NULL } },
95+ { MP_QSTR_bitmap , MP_ARG_REQUIRED | MP_ARG_OBJ , { .u_obj = MP_OBJ_NULL } },
3896 { MP_QSTR_weights , MP_ARG_REQUIRED | MP_ARG_OBJ , { .u_obj = MP_OBJ_NULL } },
39- { MP_QSTR_m , MP_ARG_OBJ , { .u_obj = MP_ROM_INT (1 ) } },
40- { MP_QSTR_b , MP_ARG_OBJ , { .u_obj = MP_ROM_INT (0 ) } },
97+ { MP_QSTR_mul , MP_ARG_OBJ , { .u_obj = MP_ROM_NONE } },
98+ { MP_QSTR_add , MP_ARG_OBJ , { .u_obj = MP_ROM_INT (0 ) } },
99+ { MP_QSTR_threshold , MP_ARG_BOOL , { .u_bool = false } },
100+ { MP_QSTR_offset , MP_ARG_INT , { .u_int = 0 } },
101+ { MP_QSTR_invert , MP_ARG_BOOL , { .u_bool = false } },
102+ { MP_QSTR_mask , MP_ARG_OBJ , { .u_obj = MP_ROM_NONE } },
41103 };
42104 mp_arg_val_t args [MP_ARRAY_SIZE (allowed_args )];
43105 mp_arg_parse_all (n_args , pos_args , kw_args , MP_ARRAY_SIZE (allowed_args ), allowed_args , args );
44106
45- mp_arg_validate_type (args [ARG_dest_bitmap ].u_obj , & displayio_bitmap_type , MP_QSTR_dest_bitmap );
46- mp_arg_validate_type (args [ARG_source_bitmap ].u_obj , & displayio_bitmap_type , MP_QSTR_source_bitmap );
47- displayio_bitmap_t * destination = MP_OBJ_TO_PTR (args [ARG_dest_bitmap ].u_obj ); // the destination bitmap
48- displayio_bitmap_t * source = MP_OBJ_TO_PTR (args [ARG_source_bitmap ].u_obj ); // the source bitmap
107+ mp_arg_validate_type (args [ARG_bitmap ].u_obj , & displayio_bitmap_type , MP_QSTR_bitmap );
108+ displayio_bitmap_t * bitmap = args [ARG_bitmap ].u_obj ;
109+
110+ displayio_bitmap_t * mask = NULL ; // the mask bitmap
111+ if (args [ARG_mask ].u_obj != mp_const_none ) {
112+ mp_arg_validate_type (args [ARG_mask ].u_obj , & displayio_bitmap_type , MP_QSTR_mask );
113+ mask = MP_OBJ_TO_PTR (args [ARG_mask ].u_obj );
114+ }
49115
50- mp_float_t m = mp_obj_get_float (args [ARG_m ].u_obj );
51- mp_int_t b = mp_obj_get_int (args [ARG_b ].u_obj );
116+ mp_int_t b = mp_obj_get_int (args [ARG_add ].u_obj );
52117
53118 size_t n_weights ;
54119 mp_obj_t weights = mp_arg_validate_type (args [ARG_weights ].u_obj , & mp_type_tuple , MP_QSTR_weights );
@@ -61,11 +126,17 @@ STATIC mp_obj_t bitmapfilter_morph(size_t n_args, const mp_obj_t *pos_args, mp_m
61126 }
62127
63128 int iweights [n_weights ];
129+ int weight_sum = 0 ;
64130 for (size_t i = 0 ; i < n_weights ; i ++ ) {
65- iweights [i ] = mp_obj_get_int (items [i ]);
131+ mp_int_t j = mp_obj_get_int (items [i ]);
132+ iweights [i ] = j ;
133+ weight_sum += j ;
66134 }
67135
68- shared_module_bitmapfilter_morph (source , destination , sq_n_weights / 2 , iweights , (float )m , b , false, 0 , false);
136+ mp_float_t m = args [ARG_mul ].u_obj != mp_const_none ? mp_obj_get_float (args [ARG_mul ].u_obj ) : 1 / (mp_float_t )weight_sum ;
137+
138+ shared_module_bitmapfilter_morph (bitmap , mask , sq_n_weights / 2 , iweights , (float )m , b ,
139+ args [ARG_threshold ].u_bool , args [ARG_offset ].u_bool , args [ARG_invert ].u_bool );
69140 return mp_const_none ;
70141}
71142
0 commit comments