|
35 | 35 | //| def morph( |
36 | 36 | //| bitmap: displayio.Bitmap, |
37 | 37 | //| weights: Sequence[int], |
38 | | -//| mul: float = 1.0, |
| 38 | +//| mul: float | None = None, |
39 | 39 | //| add: float = 0, |
40 | 40 | //| mask: displayio.Bitmap | None = None, |
41 | 41 | //| threshold=False, |
|
89 | 89 | //| """ |
90 | 90 | //| |
91 | 91 |
|
| 92 | + |
| 93 | +STATIC mp_float_t get_m(mp_obj_t mul_obj, mp_float_t sum) { |
| 94 | + return mul_obj != mp_const_none ? mp_obj_get_float(mul_obj) : sum ? 1 / (mp_float_t)sum : 1; |
| 95 | +} |
| 96 | + |
92 | 97 | STATIC mp_obj_t bitmapfilter_morph(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { |
93 | 98 | enum { ARG_bitmap, ARG_weights, ARG_mul, ARG_add, ARG_threshold, ARG_offset, ARG_invert, ARG_mask }; |
94 | 99 | static const mp_arg_t allowed_args[] = { |
@@ -136,18 +141,161 @@ STATIC mp_obj_t bitmapfilter_morph(size_t n_args, const mp_obj_t *pos_args, mp_m |
136 | 141 | weight_sum += j; |
137 | 142 | } |
138 | 143 |
|
139 | | - 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; |
| 144 | + mp_float_t m = get_m(args[ARG_mul].u_obj, weight_sum); |
140 | 145 |
|
141 | 146 | shared_module_bitmapfilter_morph(bitmap, mask, sq_n_weights / 2, iweights, m, b, |
142 | 147 | args[ARG_threshold].u_bool, args[ARG_offset].u_bool, args[ARG_invert].u_bool); |
143 | 148 | return args[ARG_bitmap].u_obj; |
144 | 149 | } |
145 | 150 | MP_DEFINE_CONST_FUN_OBJ_KW(bitmapfilter_morph_obj, 0, bitmapfilter_morph); |
146 | 151 |
|
| 152 | +//| |
| 153 | +//| def morph9( |
| 154 | +//| bitmap: displayio.Bitmap, |
| 155 | +//| weights: Sequence[int], |
| 156 | +//| mul: Sequence[float] | None, |
| 157 | +//| add: Sequence[float] | None, |
| 158 | +//| mask: displayio.Bitmap | None = None, |
| 159 | +//| threshold=False, |
| 160 | +//| offset: int = 0, |
| 161 | +//| invert: bool = False, |
| 162 | +//| ) -> displayio.Bitmap: |
| 163 | +//| """Convolve an image with a kernel |
| 164 | +//| |
| 165 | +//| This is like a combination of 9 calls to morph plus one call to mix. It's |
| 166 | +//| so complicated and hard to explain that it must be good for something. |
| 167 | +//| |
| 168 | +//| The ``bitmap``, which must be in RGB565_SWAPPED format, is modified |
| 169 | +//| according to the ``weights``. Then a scaling factor ``m`` and an |
| 170 | +//| offset factor ``b`` are applied. |
| 171 | +//| |
| 172 | +//| The ``weights`` must be a tuple of integers. The length of the tuple |
| 173 | +//| must be 9 times the square of an odd number, such as 81 or 225. The weights |
| 174 | +//| are taken in groups of 9, with the first 3 giving the proportion of each of |
| 175 | +//| R, G, and B that are mixed into the output R, the next three the |
| 176 | +//| proportions mixed into the output G, and the last 3 the proportions that |
| 177 | +//| are mixed into the output blue. |
| 178 | +//| |
| 179 | +//| ``mul`` is a sequence of 3 numbers to multiply the convolution pixel |
| 180 | +//| results by. When not set it defaults to a value that will prevent scaling |
| 181 | +//| in the convolution output. |
| 182 | +//| |
| 183 | +//| ``add`` is a sequence of 3 numbers giving a value to add to each |
| 184 | +//| convolution pixel result. If unspecified or None, 0 is used for all 3 channels. |
| 185 | +//| |
| 186 | +//| ``mul`` basically allows you to do a global contrast adjustment and |
| 187 | +//| add allows you to do a global brightness adjustment. Pixels that go |
| 188 | +//| outside of the image mins and maxes for color channels will be |
| 189 | +//| clipped. |
| 190 | +//| |
| 191 | +//| If you’d like to adaptive threshold the image on the output of the |
| 192 | +//| filter you can pass ``threshold=True`` which will enable adaptive |
| 193 | +//| thresholding of the image which sets pixels to one or zero based on a |
| 194 | +//| pixel’s brightness in relation to the brightness of the kernel of pixels |
| 195 | +//| around them. A negative ``offset`` value sets more pixels to 1 as you make |
| 196 | +//| it more negative while a positive value only sets the sharpest contrast |
| 197 | +//| changes to 1. Set ``invert`` to invert the binary image resulting output. |
| 198 | +//| |
| 199 | +//| ``mask`` is another image to use as a pixel level mask for the operation. |
| 200 | +//| The mask should be an image the same size as the image being operated on. |
| 201 | +//| Only pixels set to a non-zero value in the mask are modified. |
| 202 | +//| |
| 203 | +//| .. code-block:: python |
| 204 | +//| |
| 205 | +//| kernel_gauss_3 = [ |
| 206 | +//| 1, 2, 1, |
| 207 | +//| 2, 4, 2, |
| 208 | +//| 1, 2, 1] |
| 209 | +//| |
| 210 | +//| def blur(bitmap): |
| 211 | +//| \"""Blur the bitmap with a 3x3 gaussian kernel\""" |
| 212 | +//| bitmapfilter.morph(bitmap, kernel_gauss_3, 1/sum(kernel_gauss_3)) |
| 213 | +//| """ |
| 214 | +//| |
| 215 | + |
| 216 | +static mp_obj_t subscr(mp_obj_t o, int i) { |
| 217 | + return mp_obj_subscr(o, MP_OBJ_NEW_SMALL_INT(i), MP_OBJ_SENTINEL); |
| 218 | +} |
| 219 | + |
| 220 | +static mp_obj_t subscr_maybe(mp_obj_t o, int i) { |
| 221 | + return o == mp_const_none ? o : subscr(o, i); |
| 222 | +} |
| 223 | + |
147 | 224 | static mp_float_t float_subscr(mp_obj_t o, int i) { |
148 | | - return mp_obj_get_float(mp_obj_subscr(o, MP_OBJ_NEW_SMALL_INT(i), MP_OBJ_SENTINEL)); |
| 225 | + return mp_obj_get_float(subscr(o, i)); |
| 226 | +} |
| 227 | + |
| 228 | +STATIC mp_float_t float_subscr_maybe(mp_obj_t seq_maybe, int i, mp_float_t defval) { |
| 229 | + if (seq_maybe == mp_const_none) { |
| 230 | + return defval; |
| 231 | + } |
| 232 | + return float_subscr(seq_maybe, i); |
| 233 | +} |
| 234 | + |
| 235 | +STATIC mp_obj_t bitmapfilter_morph9(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { |
| 236 | + enum { ARG_bitmap, ARG_weights, ARG_mul, ARG_add, ARG_threshold, ARG_offset, ARG_invert, ARG_mask }; |
| 237 | + static const mp_arg_t allowed_args[] = { |
| 238 | + { MP_QSTR_bitmap, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_obj = MP_OBJ_NULL } }, |
| 239 | + { MP_QSTR_weights, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_obj = MP_OBJ_NULL } }, |
| 240 | + { MP_QSTR_mul, MP_ARG_OBJ, { .u_obj = MP_ROM_NONE } }, |
| 241 | + { MP_QSTR_add, MP_ARG_OBJ, { .u_obj = MP_ROM_NONE } }, |
| 242 | + { MP_QSTR_threshold, MP_ARG_BOOL, { .u_bool = false } }, |
| 243 | + { MP_QSTR_offset, MP_ARG_INT, { .u_int = 0 } }, |
| 244 | + { MP_QSTR_invert, MP_ARG_BOOL, { .u_bool = false } }, |
| 245 | + { MP_QSTR_mask, MP_ARG_OBJ, { .u_obj = MP_ROM_NONE } }, |
| 246 | + }; |
| 247 | + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; |
| 248 | + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); |
| 249 | + |
| 250 | + mp_arg_validate_type(args[ARG_bitmap].u_obj, &displayio_bitmap_type, MP_QSTR_bitmap); |
| 251 | + displayio_bitmap_t *bitmap = args[ARG_bitmap].u_obj; |
| 252 | + |
| 253 | + displayio_bitmap_t *mask = NULL; // the mask bitmap |
| 254 | + if (args[ARG_mask].u_obj != mp_const_none) { |
| 255 | + mp_arg_validate_type(args[ARG_mask].u_obj, &displayio_bitmap_type, MP_QSTR_mask); |
| 256 | + mask = MP_OBJ_TO_PTR(args[ARG_mask].u_obj); |
| 257 | + } |
| 258 | + |
| 259 | + mp_float_t b[3] = { |
| 260 | + float_subscr_maybe(args[ARG_add].u_obj, 0, 0), |
| 261 | + float_subscr_maybe(args[ARG_add].u_obj, 1, 0), |
| 262 | + float_subscr_maybe(args[ARG_add].u_obj, 2, 0), |
| 263 | + }; |
| 264 | + |
| 265 | + mp_obj_t weights = args[ARG_weights].u_obj; |
| 266 | + mp_obj_t obj_len = mp_obj_len(weights); |
| 267 | + if (obj_len == MP_OBJ_NULL || !mp_obj_is_small_int(obj_len)) { |
| 268 | + 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); |
| 269 | + } |
| 270 | + |
| 271 | + size_t n_weights = MP_OBJ_SMALL_INT_VALUE(obj_len); |
149 | 272 |
|
| 273 | + size_t sq_n_weights = (int)MICROPY_FLOAT_C_FUN(sqrt)(n_weights / 9); |
| 274 | + if (sq_n_weights % 2 == 0 || sq_n_weights * sq_n_weights * 9 != n_weights) { |
| 275 | + mp_raise_ValueError(MP_ERROR_TEXT("weights must be a sequence with 9 times an odd square number of elements (usually 81 or 225)")); |
| 276 | + } |
| 277 | + |
| 278 | + int iweights[n_weights]; |
| 279 | + int weight_sum[3] = {0, 0, 0}; |
| 280 | + for (size_t i = 0; i < n_weights; i++) { |
| 281 | + mp_int_t j = mp_obj_get_int(mp_obj_subscr(weights, MP_OBJ_NEW_SMALL_INT(i), MP_OBJ_SENTINEL)); |
| 282 | + iweights[i] = j; |
| 283 | + int target_channel = (i / 3) % 3; |
| 284 | + weight_sum[target_channel] += j; |
| 285 | + } |
| 286 | + |
| 287 | + mp_float_t m[3] = { |
| 288 | + get_m(subscr_maybe(args[ARG_mul].u_obj, 0), weight_sum[0]), |
| 289 | + get_m(subscr_maybe(args[ARG_mul].u_obj, 1), weight_sum[1]), |
| 290 | + get_m(subscr_maybe(args[ARG_mul].u_obj, 2), weight_sum[2]) |
| 291 | + }; |
| 292 | + |
| 293 | + shared_module_bitmapfilter_morph9(bitmap, mask, sq_n_weights / 2, iweights, m, b, |
| 294 | + args[ARG_threshold].u_bool, args[ARG_offset].u_bool, args[ARG_invert].u_bool); |
| 295 | + return args[ARG_bitmap].u_obj; |
150 | 296 | } |
| 297 | +MP_DEFINE_CONST_FUN_OBJ_KW(bitmapfilter_morph9_obj, 0, bitmapfilter_morph9); |
| 298 | + |
151 | 299 | //| def mix( |
152 | 300 | //| bitmap: displayio.Bitmap, weights: Sequence[int], mask: displayio.Bitmap | None = None |
153 | 301 | //| ) -> displayio.Bitmap: |
@@ -409,6 +557,7 @@ MP_DEFINE_CONST_FUN_OBJ_KW(bitmapfilter_false_color_obj, 0, bitmapfilter_false_c |
409 | 557 | STATIC const mp_rom_map_elem_t bitmapfilter_module_globals_table[] = { |
410 | 558 | { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_bitmapfilter) }, |
411 | 559 | { MP_ROM_QSTR(MP_QSTR_morph), MP_ROM_PTR(&bitmapfilter_morph_obj) }, |
| 560 | + { MP_ROM_QSTR(MP_QSTR_morph9), MP_ROM_PTR(&bitmapfilter_morph9_obj) }, |
412 | 561 | { MP_ROM_QSTR(MP_QSTR_mix), MP_ROM_PTR(&bitmapfilter_mix_obj) }, |
413 | 562 | { MP_ROM_QSTR(MP_QSTR_solarize), MP_ROM_PTR(&bitmapfilter_solarize_obj) }, |
414 | 563 | { MP_ROM_QSTR(MP_QSTR_false_color), MP_ROM_PTR(&bitmapfilter_false_color_obj) }, |
|
0 commit comments