diff --git a/docs/shared_bindings_matrix.py b/docs/shared_bindings_matrix.py index ca29a481276af..6a72fe40f322b 100644 --- a/docs/shared_bindings_matrix.py +++ b/docs/shared_bindings_matrix.py @@ -173,6 +173,11 @@ def get_settings_from_makefile(port_dir, board_name): This list must explicitly include any setting queried by tools/ci_set_matrix.py. """ + if os.getenv('NO_BINDINGS_MATRIX'): + return { + 'CIRCUITPY_BUILD_EXTENSIONS': '.bin' + } + contents = subprocess.run( ["make", "-C", port_dir, "-f", "Makefile", f"BOARD={board_name}", "print-CFLAGS", "print-CIRCUITPY_BUILD_EXTENSIONS", "print-FROZEN_MPY_DIRS", "print-SRC_PATTERNS", "print-SRC_SUPERVISOR"], encoding="utf-8", diff --git a/shared-bindings/bitmapfilter/__init__.c b/shared-bindings/bitmapfilter/__init__.c index 1f3f242b1a3c0..e086c10a2916e 100644 --- a/shared-bindings/bitmapfilter/__init__.c +++ b/shared-bindings/bitmapfilter/__init__.c @@ -134,7 +134,7 @@ STATIC mp_obj_t bitmapfilter_morph(size_t n_args, const mp_obj_t *pos_args, mp_m mp_obj_t weights = args[ARG_weights].u_obj; mp_obj_t obj_len = mp_obj_len(weights); if (obj_len == MP_OBJ_NULL || !mp_obj_is_small_int(obj_len)) { - 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); + 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)); } 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 shared_module_bitmapfilter_false_color(bitmap, mask, palette->colors); return args[ARG_bitmap].u_obj; } - MP_DEFINE_CONST_FUN_OBJ_KW(bitmapfilter_false_color_obj, 0, bitmapfilter_false_color); + +#define BLEND_TABLE_SIZE (4096) +STATIC uint8_t *get_blend_table(mp_obj_t lookup, int mode) { + mp_buffer_info_t lookup_buf; + if (!mp_get_buffer(lookup, &lookup_buf, mode) || lookup_buf.len != BLEND_TABLE_SIZE) { + return NULL; + } + return lookup_buf.buf; +} +//| +//| BlendFunction = Callable[[float, float], float] +//| """A function used to blend two images""" +//| +//| BlendTable = bytearray +//| """A precomputed blend table +//| +//| There is not actually a BlendTable type. The real type is actually any +//| buffer 4096 bytes in length.""" +//| +//| def blend_precompute(lookup: BlendFunction, table: BlendTable | None = None) -> BlendTable: +//| """Precompute a BlendTable from a BlendFunction +//| +//| If the optional ``table`` argument is provided, an existing `BlendTable` is updated +//| with the new function values. +//| +//| The function's two arguments will range from 0 to 1. The returned value should also range from 0 to 1. +//| +//| A function to do a 33% blend of each source image could look like this: +//| +//| .. code-block:: python +//| +//| def blend_one_third(a, b): +//| return a * .33 + b * .67 +//| """ +//| +STATIC mp_obj_t blend_precompute(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_lookup, ARG_table }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_lookup, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_obj = MP_OBJ_NULL } }, + { MP_QSTR_table, MP_ARG_OBJ, { .u_obj = MP_ROM_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); + + mp_obj_t table = args[ARG_table].u_obj; + if (table == mp_const_none) { + table = mp_obj_new_bytearray_of_zeros(BLEND_TABLE_SIZE); + } + uint8_t *buf = get_blend_table(table, MP_BUFFER_WRITE); + if (!buf) { + mp_raise_TypeError_varg(MP_ERROR_TEXT("%q must be of type %q or %q, not %q"), + MP_QSTR_table, MP_QSTR_NoneType, MP_QSTR_WritableBuffer, + mp_obj_get_type_qstr(table)); + } + shared_module_bitmapfilter_blend_precompute(args[ARG_lookup].u_obj, buf); + return table; +} +MP_DEFINE_CONST_FUN_OBJ_KW(bitmapfilter_blend_precompute_obj, 0, blend_precompute); + +//| +//| def blend( +//| dest: displayio.Bitmap, +//| src1: displayio.Bitmap, +//| src2: displayio.Bitmap, +//| lookup: BlendFunction | BlendTable, +//| mask: displayio.Bitmap | None = None, +//| ) -> displayio.Bitmap: +//| """Blend the 'src1' and 'src2' images according to lookup function or table 'lookup' +//| +//| If ``lookup`` is a function, it is converted to a `BlendTable` by +//| internally calling blend_precompute. If a blend function is used repeatedly +//| it can be more efficient to compute it once with `blend_precompute`. +//| +//| If the mask is supplied, pixels from ``src1`` are taken unchanged in masked areas. +//| +//| The source and destination bitmaps may be the same bitmap. +//| +//| The destination bitmap is returned. +//| """ +//| + +STATIC mp_obj_t bitmapfilter_blend(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_dest, ARG_src1, ARG_src2, ARG_lookup, ARG_mask }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_dest, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_obj = MP_OBJ_NULL } }, + { MP_QSTR_src1, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_obj = MP_OBJ_NULL } }, + { MP_QSTR_src2, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_obj = MP_OBJ_NULL } }, + { MP_QSTR_lookup, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_obj = MP_OBJ_NULL } }, + { MP_QSTR_mask, MP_ARG_OBJ, { .u_obj = MP_ROM_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); + + mp_arg_validate_type(args[ARG_dest].u_obj, &displayio_bitmap_type, MP_QSTR_dest); + displayio_bitmap_t *dest = MP_OBJ_TO_PTR(args[ARG_dest].u_obj); + + mp_arg_validate_type(args[ARG_src1].u_obj, &displayio_bitmap_type, MP_QSTR_src1); + displayio_bitmap_t *src1 = MP_OBJ_TO_PTR(args[ARG_src1].u_obj); + + mp_arg_validate_type(args[ARG_src2].u_obj, &displayio_bitmap_type, MP_QSTR_src2); + displayio_bitmap_t *src2 = MP_OBJ_TO_PTR(args[ARG_src2].u_obj); + + mp_obj_t lookup = args[ARG_lookup].u_obj; + if (mp_obj_is_callable(lookup)) { + lookup = mp_call_function_1(MP_OBJ_FROM_PTR(&bitmapfilter_blend_precompute_obj), lookup); + } + uint8_t *lookup_buf = get_blend_table(lookup, MP_BUFFER_READ); + if (!lookup_buf) { + mp_raise_TypeError_varg(MP_ERROR_TEXT("%q must be of type %q or %q, not %q"), + MP_QSTR_lookup, MP_QSTR_callable, MP_QSTR_ReadableBuffer, + mp_obj_get_type_qstr(lookup)); + } + + displayio_bitmap_t *mask = NULL; + if (args[ARG_mask].u_obj != mp_const_none) { + mp_arg_validate_type(args[ARG_mask].u_obj, &displayio_bitmap_type, MP_QSTR_mask); + mask = MP_OBJ_TO_PTR(args[ARG_mask].u_obj); + } + + shared_module_bitmapfilter_blend(dest, src1, src2, mask, lookup_buf); + return args[ARG_dest].u_obj; +} +MP_DEFINE_CONST_FUN_OBJ_KW(bitmapfilter_blend_obj, 0, bitmapfilter_blend); + STATIC const mp_rom_map_elem_t bitmapfilter_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_bitmapfilter) }, { 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[] = { { MP_ROM_QSTR(MP_QSTR_ChannelScaleOffset), MP_ROM_PTR(&bitmapfilter_channel_scale_offset_type) }, { MP_ROM_QSTR(MP_QSTR_ChannelMixer), MP_ROM_PTR(&bitmapfilter_channel_mixer_type) }, { MP_ROM_QSTR(MP_QSTR_ChannelMixerOffset), MP_ROM_PTR(&bitmapfilter_channel_mixer_offset_type) }, + { MP_ROM_QSTR(MP_QSTR_blend), MP_ROM_PTR(&bitmapfilter_blend_obj) }, + { MP_ROM_QSTR(MP_QSTR_blend_precompute), MP_ROM_PTR(&bitmapfilter_blend_precompute_obj) }, }; STATIC MP_DEFINE_CONST_DICT(bitmapfilter_module_globals, bitmapfilter_module_globals_table); diff --git a/shared-bindings/bitmapfilter/__init__.h b/shared-bindings/bitmapfilter/__init__.h index f90035d1c2125..bf4fc3a114b72 100644 --- a/shared-bindings/bitmapfilter/__init__.h +++ b/shared-bindings/bitmapfilter/__init__.h @@ -73,3 +73,12 @@ void shared_module_bitmapfilter_false_color( displayio_bitmap_t *bitmap, displayio_bitmap_t *mask, _displayio_color_t palette[256]); + +void shared_module_bitmapfilter_blend_precompute(mp_obj_t fun, uint8_t lookup[4096]); + +void shared_module_bitmapfilter_blend( + displayio_bitmap_t *dest, + displayio_bitmap_t *src1, + displayio_bitmap_t *src2, + displayio_bitmap_t *mask, + const uint8_t lookup[4096]); diff --git a/shared-module/bitmapfilter/__init__.c b/shared-module/bitmapfilter/__init__.c index 72f38eceb1794..cafa841367cb6 100644 --- a/shared-module/bitmapfilter/__init__.c +++ b/shared-module/bitmapfilter/__init__.c @@ -30,7 +30,7 @@ #pragma GCC diagnostic ignored "-Wshadow" static void check_matching_details(displayio_bitmap_t *b1, displayio_bitmap_t *b2) { - if (b1->width != b2->width || b1->height != b2->height) { + if (b1->width != b2->width || b1->height != b2->height || b1->bits_per_value != b2->bits_per_value) { mp_raise_ValueError(MP_ERROR_TEXT("bitmap size and depth must match")); } } @@ -193,8 +193,6 @@ void shared_module_bitmapfilter_morph( const int32_t m_int = (int32_t)MICROPY_FLOAT_C_FUN(round)(65536 * m); const int32_t b_int = (int32_t)MICROPY_FLOAT_C_FUN(round)(65536 * COLOR_G6_MAX * b); - check_matching_details(bitmap, bitmap); - switch (bitmap->bits_per_value) { default: mp_raise_ValueError(MP_ERROR_TEXT("unsupported bitmap depth")); @@ -308,8 +306,6 @@ void shared_module_bitmapfilter_mix( wt[i] = (int32_t)MICROPY_FLOAT_C_FUN(round)(scale * weights[i]); } - check_matching_details(bitmap, bitmap); - switch (bitmap->bits_per_value) { default: mp_raise_ValueError(MP_ERROR_TEXT("unsupported bitmap depth")); @@ -456,3 +452,64 @@ void shared_module_bitmapfilter_false_color( } } } + +void shared_module_bitmapfilter_blend_precompute(mp_obj_t fun, uint8_t lookup[4096]) { + uint8_t *ptr = lookup; + for (int i = 0; i < 64; i++) { + mp_obj_t fi = mp_obj_new_float(i * (1 / MICROPY_FLOAT_CONST(63.))); + for (int j = 0; j < 64; j++) { + mp_obj_t fj = mp_obj_new_float(j * (1 / MICROPY_FLOAT_CONST(63.))); + mp_float_t res = mp_obj_get_float(mp_call_function_2(fun, fi, fj)); + *ptr++ = res < 0 ? 0 : res > 1 ? 1 : (uint8_t)MICROPY_FLOAT_C_FUN(round)(63 * res); + } + } +} + +#define FIVE_TO_SIX(x) ({ int tmp = (x); (tmp << 1) | (tmp & 1); }) +#define SIX_TO_FIVE(x) ((x) >> 1) + +void shared_module_bitmapfilter_blend( + displayio_bitmap_t *bitmap, + displayio_bitmap_t *src1, + displayio_bitmap_t *src2, + displayio_bitmap_t *mask, + const uint8_t lookup[4096]) { + + check_matching_details(bitmap, src1); + check_matching_details(bitmap, src2); + + switch (bitmap->bits_per_value) { + default: + mp_raise_ValueError(MP_ERROR_TEXT("unsupported bitmap depth")); + case 16: { + for (int y = 0, yy = bitmap->height; y < yy; y++) { + uint16_t *dest_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(bitmap, y); + uint16_t *src1_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(src1, y); + uint16_t *src2_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(src2, y); + for (int x = 0, xx = bitmap->width; x < xx; x++) { + int pixel1 = IMAGE_GET_RGB565_PIXEL_FAST(src1_ptr, x); + if (mask && common_hal_displayio_bitmap_get_pixel(mask, x, y)) { + IMAGE_PUT_RGB565_PIXEL_FAST(dest_ptr, x, pixel1); + continue; // Short circuit. + } + int pixel2 = IMAGE_GET_RGB565_PIXEL_FAST(src2_ptr, x); + + int r1 = FIVE_TO_SIX(COLOR_RGB565_TO_R5(pixel1)); + int r2 = FIVE_TO_SIX(COLOR_RGB565_TO_R5(pixel2)); + int r = SIX_TO_FIVE(lookup[r1 * 64 + r2]); + + int g1 = COLOR_RGB565_TO_G6(pixel1); + int g2 = COLOR_RGB565_TO_G6(pixel2); + int g = lookup[g1 * 64 + g2]; + + int b1 = FIVE_TO_SIX(COLOR_RGB565_TO_B5(pixel1)); + int b2 = FIVE_TO_SIX(COLOR_RGB565_TO_B5(pixel2)); + int b = SIX_TO_FIVE(lookup[b1 * 64 + b2]); + + int pixel = COLOR_R5_G6_B5_TO_RGB565(r, g, b); + IMAGE_PUT_RGB565_PIXEL_FAST(dest_ptr, x, pixel); + } + } + } + } +} diff --git a/tests/circuitpython/bitmapfilter_blend.py b/tests/circuitpython/bitmapfilter_blend.py new file mode 100644 index 0000000000000..afac62f66cae1 --- /dev/null +++ b/tests/circuitpython/bitmapfilter_blend.py @@ -0,0 +1,51 @@ +from displayio import Bitmap +import bitmapfilter +from dump_bitmap import dump_bitmap_rgb_swapped +from blinka_image import decode_resource + + +def test_pattern(): + return decode_resource("testpattern", 2) + + +def blinka(): + return decode_resource("blinka_32x32", 0) + + +def blendfunc(frac): + nfrac = 1 - frac + + def inner(x, y): + return x * frac + y * nfrac + + return inner + + +def make_quadrant_bitmap(): + b = Bitmap(17, 17, 1) + for i in range(b.height): + for j in range(b.width): + b[i, j] = (i < 8) ^ (j < 8) + return b + + +b = Bitmap(32, 32, 65535) +print(test_pattern().width) +print(blinka().width) +print(b.width) +print(test_pattern().height) +print(blinka().height) +print(b.height) + +mask = make_quadrant_bitmap() +blend_table = bitmapfilter.blend_precompute(blendfunc(0.1)) +bitmapfilter.blend(b, test_pattern(), blinka(), blend_table, mask) +dump_bitmap_rgb_swapped(b) + +bitmapfilter.blend(b, test_pattern(), blinka(), blendfunc(0.5), mask) +dump_bitmap_rgb_swapped(b) + +bitmapfilter.blend(b, test_pattern(), blinka(), max, mask) +dump_bitmap_rgb_swapped(b) +bitmapfilter.blend(b, test_pattern(), blinka(), min) +dump_bitmap_rgb_swapped(b) diff --git a/tests/circuitpython/bitmapfilter_blend.py.exp b/tests/circuitpython/bitmapfilter_blend.py.exp new file mode 100644 index 0000000000000..5e9caed56e6cb --- /dev/null +++ b/tests/circuitpython/bitmapfilter_blend.py.exp @@ -0,0 +1,138 @@ +32 +32 +32 +32 +32 +32 +········█████████··············· ········█████████··············· ········█████████··············· +········█████████▒··░··········· ········█████████▒··░··········· ░······░█████████░··░··········· +········█████████▓░············· ········██████████░············· ········█████████▓░············· +·····░▒▒█████████▓▓············· ·····░▒▓█████████▓▓░············ ·····░▒▒█████████▒▓░············ +·····░█▓█████████▓▓░░··········· ·····░█▓████▓▓▓▓▓▓▓░············ ··░··░▓▓█████████▒▓░░··░········ +·····░▓▓█████████▓▓░············ ·····░▓█████▓▓▓▓▓▓█░············ ···░░░▓▓▓▓▓▓▓▓▓▓█▒▓░··░········· +····░·░▓█████████▓▓░············ ······░▓████▓▓▓▓▓▓▓░············ ····░·░▓▓▓▓▓▓▓▓▓█▒▓░············ +·······░█████████▓▓░············ ·······░████▓▓▓▓▓▓▓░············ ·······░▓▓▓▓▓▓▓▓█▒▓░············ +▓▓▓▓▓▓▓▓·░░░▓▓▓▓▓▓▓············· ▓▓▓▓▓▓▓▓··░░▓▓▓▓▓▓▓············· ▓▓▓▓▓▓▓▓··░░▓▒▒▒▒▒▓░············ +▓▓▓▓▓▓▓▓··░·░▓▓▓▓▓▒············· ▓▓▓▓▓▓▓▓····▒▓▓▓▓▓▒············· ▓▓▓▓▓▓▓▓···░░▓▒▒▒▒▒···░········· +▓▓▓▓▓▓▓▓····▓▓▒▓▓▓░············· ▓▓▓▓▓▓▓▓····▒▓▓▓▓▓░············· ▓▓▓▓▓▓▓▓····▒▒▒▒▒▓░············· +▓▓▓▓▓▓▓▓·░·░▓▒▓▓▓▓░············· ▓▓▓▓▓▓▓▓···░▓▓▓▓▓▓·············· ▓▓▓▓▓▓▓▓···░▓▒▒▒▒▓░············· +▒▒▒▒▒▒▒▒···▒▒▒▓▓▓▒·············· ▒▒▒▒▒▒▒▒···▒▓▓▓▓▓▒·············· ▒▒▒▒▒▒▒▒···▒▒▒▒▒▒▒░············· +▒▒▒▒▒▒▒▒·░░▓▒▓▓▒▓░·············· ▒▒▒▒▒▒▒▒··░▓▒▒▓▓▓░·············· ▒▒▒▒▒▒▒▒···▓▒▒▒▒▓░·············· +▒▒▒▒▒▒▒▒··▒▒░▓▓▓▓··············· ▒▒▒▒▒▒▒▒··▒█▓▓▓▓▓··············· ▒▒▒▒▒▒▒▒··▒▒▒▒▒▒▓░··░··········· +▒▒▒▒▒▒▒▒·░▓░▒▓▒▓▒··············· ▒▒▒▒▒▒▒▒·░▓▓▓▓▓▓▒··············· ▒▒▒▒▒▒▒▒··▓▒▒▒▒▒▒░··░··········· +▒▒▒▒▒▒▒▒·▒▒░▓▓▓▓░·░░▒▒░········· ▒▒▒▒▒▒▒▒·▒▓▒▒▓▓▓···░▒▒░··░······ ▒▒▒▒▒▒▒▒·▒▓▒░▒▒▓░░░░▒▒▒░·░······ +········░▒░░▒▓▓▓░▒▓▓▓▓▓▓········ ········░▓▓▓▓▓▓▒░▒▓▓▓▓▓▓░······· ·········▓▒▒▒▒▒▒░▒▓▓▓▓▓▓░·░····· +·······▒▓▒░▒▓▓▓▓▓▓▓▓▒▒▒▒▒░······ ·······▒▓▓▒▓▓▓▓▓▓▓▓▓▓▓▒▓▓░······ ·······▒▓▒░░▒▒▒▓▓▓▒▒▒▒░▒▒░░····· +······░▓▓░░▒▓▓▓▓▓▓▓▓▒░░▒▓▓▓░···· ······░▓█▓▓▓▓▓▓▓▓▓▓▓▒▓▒▓█▓▓░·░·· ······░▓▓▒▒▒▒▒▓▒▒▒▒▒▒▒░▒▓▓▓░···· +·····░▓▓▒░░▒▓▓▓▒▒▒░░▒░▒▓▓▒▓▒···· ·····░▓▓▓▓▓▓▓▓▓▒▓▒▓▓▒▓▓▓▓▓█▒···· ·····░▓▓▒▒▒▒▒▒▒░▒░▒▒▒▒▓▓▒▒▓▒···· +····░▓▓▓▒▒▒▓▒▓█▒▒▒▒▒▓▓▓▒▒▓▒▓▒░·· ·····▓▓▓▓▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▒░·· ····░▓▒▓▒░░▒▒▒▓▒▓▒▓▓▓▓▓▒▒▒▒▓▒░·· +····░▒▒▓░░▒▓▓▓▓▓▓▓▓▓▓▒▒▒▓░▒▓▓▓▒· ····░▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓█▒· ····░▒░▓▒▒▒▒▒▒▓▒▓▓▓▒▒▒▒▒▒▒▒▓▒▓░· +····░▓▒▒░░▒▓▓▓▓▒▓▒▓▓▒▒▒░▒▒▓▒▒▒▓░ ····░▓▒▓▓▓▓▓▓▓▓▓▒▓▒▒▓▒▒▓▒▓█▓▓▓█░ ····░▓░▓░▒▒▒▒▓▒░░▒▒▒▒▒▒▒░▓▓▒▒▒▓░ +····▓▓▓▒▒▒▓▓▒▓▒░▒░░▒·░░░▓▓▓▒▒▒▓▒ ····▓▓▓▓▓▒▓▓▓▓▓▓▒▓▒▒▓▓▓▓▓█▓▓▓▓▓▒ ····▓▒▓▓░░░▒▒▓▒▒▒▒▒▒▒▒▒▓▓▓▒▒▒▒▒▒ +···░▓▒▓░░░▒▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒▓▒▒▒ ···░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒ ···░▓▒▒▒▒▒▒▒▒▒▓▒▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒ +···▒▓▒▓▓░▒▒▓▓▓▓▓▓▓▓▓▓▒▒▒▒▓▒▒▓▒▒░ ···░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒█░ ···░▒░▒▓░▒░▒▒▒▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒░▓░ +···▓▒░▒▓░░▒▓▓▓▓▓▓▓▓▓▒▒▓▒▒▒▒▒░▒▒· ···▓▓▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓· ···▓▒░░▓░▒░▒▒▒▓▒▒▒▒▒▒▒▒▒▒▒▒▒░▒▒· +·░▓▓▓▒░▒▒░░▒▓▒▓▓▒▒▒▒▒▒░▒░░░░▒▒·· ·░▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▒▓▓▒▓▓▒▓▓▒▓█▓░· ·░▓▓▓▒▒▓▒▒▒▒▒▒▒▓▒░▒▒▒▒▒░▒▒░▒▓▒·· +·░░··▒▒▒▓▒░▒▓▓▓▓▓▒▒░░░·░░░▒▒▒··· ·░░··▒▒▓▓▓▓▒▓▓▓▓▓▓▓▓▒▒▓▒▓█▓▓▒··· ·░░··▒▒▒▓░░░▒▒▒▒▓▓▒▒▒▒▒▒▒▓▓▓▒··· +········░▓▒░░▒▒▓▓▓▒▒▒▒▒▒▒▒░····· ········░▓▓▓▓▓▓▓▓▓▒▓▓▓▓▓▓▒░····░ ········░▓▒▒▒▒▒▒▓▓▒▓▓▓▓▓▓▒░░···· +·········░▒▒▒▒▒▒░··░·░░········· ··········▒▓▓▓▒▒░··············· ··········░▒▒▒▒▒▒··░░░░░········ + +▒▒▒▒▒▒▒▒█████████▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒█████████▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒█████████▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ +▒▒▒▒▒▒▒▒█████████▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒█████████▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒█████████▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒ +▒▒▒▒▒▒▒▒██████████▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒██████████▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒█████████▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒ +▒▒▒▒▒▒▓▓█████████▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▓▓█████████▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▓▓█████████▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒ +▒▒▒▒▒▒█▓█████████▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒█▓████▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▓▓█████████▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒ +▒▒▒▒▒▒▓▓█████████▓█▒▒▒▒▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▓▓████▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓█▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒ +▒▒▒▒▒▒▒▓█████████▓█▒▒▒▒▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▓████▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓█▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒ +▒▒▒▒▒▒▒▒█████████▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒████▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓█▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒ +▓▓▓▓▓▓▓▓▒▒▒▒▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒ ▓▓▓▓▓▓▓▓▒▒▒▒▓▓▓▓▓▓▓░▒░░░▒▒▒▒▒▒▒▒ ▓▓▓▓▓▓▓▓▒▒▒▒▓▓▒▒▓▓▓▒▒▒▒▒▒▒▒▒░░░░ +▓▓▓▓▓▓▓▓▒▒▒▒▓█▓▓▓▓▓▒░░░░░░░░░░░░ ▓▓▓▓▓▓▓▓▒▒▒▒▒▓▓▓▓▓▓░░░░░▒▒▒▒▒▒▒▒ ▓▓▓▓▓▓▓▓░░▒▒▒▓▒▒▓▓▓▒▒▒▒▒▒▒▒▒░░░░ +▓▓▓▓▓▓▓▓▒▒▒▒▓▓▓▓▓█▒▒░░░░░░░░░░░░ ▓▓▓▓▓▓▓▓▒▒▒▒▒▓▓▓▓▓▒░░░░░▒▒▒▒▒▒▒▒ ▓▓▓▓▓▓▓▓░░░░▒▓▒▒▓▓▒▒▒▒▒▒▒▒▒▒░░░░ +▓▓▓▓▓▓▓▓▒▒▒▒▓▓▓▓▓▓▒▒░░░░░░░░░░░░ ▓▓▓▓▓▓▓▓▒▒▒▒▓▓▓▓▓▓░░░░░░▒▒▒▒▒▒▒▒ ▓▓▓▓▓▓▓▓░░░▒▓▒▒▒▓▓▒▒▒▒▒▒▒▒▒▒░░░░ +▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▒▒░░░░░░░░░░░░ ▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▒░░░░░░▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒░░░▒▒▒▒▒▓▓▒▒▒▒▒▒▒▒▒▒░░░░ +▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓█▒▒▒░░░░░░░░░░░░ ▒▒▒▒▒▒▒▒▒▒▒█▒▒▓▓▓▒░░░░░░▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒░░░▓▒▒▒▒▓▒▒▒▒▒▒▒▒▒▒▒░░░░ +▒▒▒▒▒▒▒▒▒▒▓▓▒▓▓▓▓▒▒▒░░░░░░░░░░░░ ▒▒▒▒▒▒▒▒▒▒▓█▓▓▓▓▒░░░░░░░▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒░░▒▒▒▒▒▒▓▒▒▒▒▒▒▒▒▒▒▒░░░░ +▒▒▒▒▒▒▒▒▒▒▓▒▓▓▓█▓▒▒▒░░░░░░░░░░░░ ▒▒▒▒▒▒▒▒▒▒▓▓▒▓▓▓▒░░░░░░░▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒░░▒▒▒▒▒▒▓▒▒▒▒▒▒▒▒▒▒▒░░░░ +▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓█▒▒▒▒▒▒▒░░░░░░░░░ ▒▒▒▒▒▒▒▒▒▓█▓▒▒▓▓░░░░▒▒▒░▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒░▒▒▒▒▒▒▒▒▒▒▒▓▓▓▒▒▒▒▒░░░░ +░░░░░░░░▒▓▒▒▓▓▓▓▒▓▓▓▒▒▓▒░░░░░░░░ ░░░░░░░░▒███▓▒▓▒░▒▒▓▓▓▓▒▒▒▒▒▒▒▒▒ ░░░░░░░░░▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▒▒▒▒░░░░ +░░░░░░░▒█▓▓▓▓▓▓█▓▓▓▓▒▒▒▒▒░░░░░░░ ░░░░░░░▒█▓▓▓▒▒▓▓▓▓▒▒▒▒▒▓▓▒▒▒▒▒▒▒ ░░░░░░░▒▒▒░░▒▒▒▒▓▓▓▓▓▓▓▓▓▒▒▒░░░░ +░░░░░░░▒▓▒▒▓▓▓█▓▓▓▓▓▒░░▒▒▒▒░░░░░ ░░░░░░░▒█▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒██▓▒▒▒▒▒ ░░░░░░░▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓█▓▓▒░░░░ +░░░░░░▒▒▓▒▒▓▓▓█▓▓▓▒▒░░▒▒▒▒▒▒░░░░ ░░░░░░▒▒█▓▓█▒▒▒▒▒▒▒▒▒▒▒▒▓▓█▓▒▒▒▒ ░░░░░░▒▒▒▒▒▒▒▒▒░▓▒▓▓▓▓▓▓▓▓▓▓░░░░ +░░░░░▒▒▒▓▓▓▓▓▓█▓▓▓▓▓▒▒▒▒▒▒▒▒▒░░░ ░░░░░▒▒▒▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒█▓▓█▓▒▒▒ ░░░░░▒▒▒▒░░░▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▒░░░ +░░░░░▒▒▒▒▒▓▓▓▓█▓█▓▓▓▒▒▒▒▒░▒▒▒▒░░ ░░░░░▒▒▒▓▓██▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓███▓▒ ░░░░░▒░▒▒▒▒▒░▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▒▒░░ +░░░░░▒░▒▒▒▓▓▓▓▓▓▓▓▓▓░░▒░░░▒▒▒▒▒░ ░░░░░▒▒▒▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▓███▓▓█▒ ░░░░░▒░▒░▒░░░▒▒░▓▓▓▓▓▓▓▓▓▓█▓░░▒░ +·░·░▒▒▒▒▓▓▓▓▓█▓▒▓▒▒▓░░░░▒▒▒▒▒▒▒░ ···░▒▒▒▒▓▓▓█▒▒▒▒▒▒▒▒▒▒▒▒██▓█▓▓▓▓ ···░▒▒▒▒░░░░░▒▒░▓▓▓▓▓▓▓▓▓▓▓▓░░▒░ +···░▒▒▒░▒▒▓▓▓▓█▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒░░ ·░·░▒▒▒▒▓███▒▒▒▒▒▒▒▒▒▒▒▒██▓▓▓██▓ ···░▒░▒▒░░░░░▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓░░▒░ +···░▒░▒▒▒▓▓▓▓▓█▓▓▓▓▓▒▒▒▒▒▒▒▒▒░░░ ···░▒▒▒▒▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒█▓██▓▓█▒ ···░▒░░▒░░░░░░▒░▓▓▓▓▓▓▓▓▓▓▓▓░░▒░ +···▒░░░▒▓▒▓▓▓▓█▓▓▓▓▓▒▒▒▒▒░▒░░░░· ···▒▒░░▒▓▓▓█▒▒▒▒▒▒▒▒▒▒▒▒▓█▓▓▓▓▓▒ ···▒▒░░▒░░░░░░▒░▓▓▓▓▓▓▓▓▓▓▓▓░░░· +·░▒▒▒░░░▓▒▒▓▓▓▓█▓▓▓▓░░░░░·░░░▒·· ·░▒▒▒▒▒▒███▓▒▒▒▒▒░▒▒░▒▒░▓▓▓▓█▓▒▒ ·░▒▒▒░░▒░░░░░░░▒▓▒▓▓▓▓▓▒▓▓▓▓▒░·· +·····░░░█▓▓▓▓▓▓▓▓▓▓▒░░·░░░░░░··· ·····░░▒█▓▓▓▒▒▒▒▒▒▒▒░░▒░▓██▓▓▒▒▒ ·░···░░░▒░░░░░░░█▓▓▓▓▓▓▓▓▓▓▓░··· +········▒▓▓▒▒▓▓▓██▓▓░░░░░░░····· ········▒███▒▒▒▒▒▒░▒▒▒▒░▓▓▓▒▒▒▒▒ ·········░░░░░░░▓▓▓▓▓▓▓▓▓▓▒▒···· +········▒▒▓▓▓▓▓▓▓▒▒▒············ ········▒▒▓▓░░░░░·······▒▒▒▒▒▒▒▒ ··········░░░░░░▓▒▒▒▒▒▒▒▒▒▒▒···· + +········█████████··············· ········█████████··············· ········█████████··············· +········█████████░·············· ········█████████░·············· ········█████████░·············· +········█████████▓░············· ········██████████░············· ········█████████▓░············· +······▒▒█████████▓▓············· ······▒▒█████████▓▓············· ······░▒█████████▒▓············· +·····░█▓█████████▒▓░············ ·····░█▓████▓▓▓▓▓▓▓░············ ·····░▓▓█████████▒▓░············ +······▓▓█████████▒▓░············ ······▓▓████▓▓▓▓▓▓▓░············ ······▓▓▓▓▓▓▓▓▓▓█▒▓░············ +······░▓█████████▓▓░············ ······░▓████▓▓▓▓▓▓▓░············ ······░▓▓▓▓▓▓▓▓▓█▒▓░············ +········█████████▓▓············· ·······░████▓▓▓▓▓▓▓············· ·······░▓▓▓▓▓▓▓▓█▒▓░············ +▓▓▓▓▓▓▓▓···░▓▓▓▒▒▓▓············· ▓▓▓▓▓▓▓▓···░▓▓▓▓▓▓▓············· ▓▓▓▓▓▓▓▓···░▓▒▒▒▒▒▓············· +▓▓▓▓▓▓▓▓····░▓▒▓▒▓▒············· ▓▓▓▓▓▓▓▓····░▓▓▓▓▓▒············· ▓▓▓▓▓▓▓▓····░▓▒▒▒▒▒············· +▓▓▓▓▓▓▓▓····▒▓▒▓▒▓░············· ▓▓▓▓▓▓▓▓····▒▓▓▓▓▓░············· ▓▓▓▓▓▓▓▓····▒▒▒▒▒▓░············· +▓▓▓▓▓▓▓▓····▓▒▒▓▓▓·············· ▓▓▓▓▓▓▓▓····▓▓▓▓▓▓·············· ▓▓▓▓▓▓▓▓····▓▒▒▒▒▓·············· +▒▒▒▒▒▒▒▒···▒▒▒▓▒▓▒·············· ▒▒▒▒▒▒▒▒···▒▒▒▒▒▒▒·············· ▒▒▒▒▒▒▒▒···▒▒▒▒▒▒▒·············· +▒▒▒▒▒▒▒▒···▓▒▓▓▒▓░·············· ▒▒▒▒▒▒▒▒···▓▒▒▒▒▒░·············· ▒▒▒▒▒▒▒▒···▒░░▒▒▓░·············· +▒▒▒▒▒▒▒▒··▒░·▒▓▓▓··············· ▒▒▒▒▒▒▒▒··▒█▒▒▒▒▒··············· ▒▒▒▒▒▒▒▒··▒▒▒▒▒▒▓··············· +▒▒▒▒▒▒▒▒··▓░▒▓▒▓▒··············· ▒▒▒▒▒▒▒▒··▓▓▒▒▒▒▒··············· ▒▒▒▒▒▒▒▒··▒▒▒▒▒▒▒··············· +▒▒▒▒▒▒▒▒·▒▒░▓▓▒▓···░▒▒░········· ▒▒▒▒▒▒▒▒·▒▓▒▒▒▒▒···░▒▒░········· ▒▒▒▒▒▒▒▒·▒▒░░▒▒▒···░▒▒░········· +·········▒░░▒▓▓▒·▒▓▓▒▒▒▒········ ·········▓▓▓▒▒▒▒·▒▒▒▒▒▒▒········ ·········▒▒▒▒▒▒▒·░▓▓▒▒▓▓········ +·······▒▓▒░▒▓▒▓▓▓▓▒▒▒▒▒▒▒░······ ·······▒▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░······ ·······▒▒▒░░▒▒▒▒▓▓▒▒▒▒░▒▒░······ +······░▒▓░░▒▓▓▓▓▓▓▓▓▒░░▒▒▒▒····· ······░▒█▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒█▓▓░···· ······░▒▒▒▒▒▒▒▒▒▒▒▒▒░▒░▒▓▓▓░···· +·····░░░▒·░▒▓▓▓▒░▒░░░░░░░░░░···· ·····░░░▓▓▓▓░░░░░░░░░░░░▓▓▓▒···· ·····░░░░░░░░░░░▒░▒▒░▒▓▓▒▒▓▒···· +·····░░░▒░▒▓▒▓▓▒▒▒▒▒░░░░░░░░░░·· ·····░░░▓▒▒▓░░░░░░░░░░░░▓▓▒▓▒░·· ·····░░░░░░░░░░░▓▒▓▓▓▓▓▒▒▒░▓░░·· +····░░░░░·▒▓▓▓▓▓▓▓▓▓░░░░░░░░░░░· ····░░░░▓▓▓▓░░░░░░░░░░░░▒▓▓▓▓█▒· ····░░░░░░░░░░░░▓▒▒▒▒▒▒▒░▒▒▓░░░· +····░░░░░░▒▓▒▓▓▒▓▒▓▒░░░░░░░░░░░░ ····░░░░▓▓▓▓░░░░░░░░░░░░▒▓▓▓▓▓█░ ····░░░░░░░░░░░░░▒▒░▒░░▒░▓▓▒░░░░ +····░░░░░▒▓▓▒▓░░░·░▒·░░░░░░░░░░░ ····░░░░▓▒▒▓░░░░░░░░░░░░▓▓▓▓▓▓▓▒ ····░░░░░░░░░░░░░▒▒░▒▒▒▓▓▓▒▒░░░░ +···░░░░░··▒▓▓▓▓▓▓▒▓▓░░░░░░░░░░░░ ···░░░░░▓▓▓▓░░░░░░░░░░░░▓▓▓▓▓▓▓▒ ···░░░░░░░░░░░░░▓▓▓▓▓▓▓▒▒▒▒▒░░░░ +···░░░░░░░▒▓▓▓▓▓▓▓▓▓░░░░░░░░░░░░ ···░░░░░▓▓▒▓░░░░░░░░░░░░▓▓▓▓▓▒█░ ···░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒░░░░ +···░░░░░░░▒▒▓▓▓▓▓▓▓▓░░░░░░░░░░░· ···░░░░░▓▓▓▓░░░░░░░░░░░░▓▓▒▓▓▓▒· ···░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒░▒░░░· +········░·░▒▓▒▓▓▒▒▒▒············ ········▓▓▓▓············▓▓▒▓▓▒·· ················▒░▒▒░▒▒░▒▒░▒···· +········▓▒░▒▓▓▒▓▓▒░············· ········▓▓▒▒············▓▓▓▓▒··· ················▓▒▒▒░▒▒▒▒▓▓▓···· +········░▒▒░░▒▒▓▓▓▒▒············ ········░▓▓▓············▓▒░····· ················▓▓▒▒▓▒▒▓▓▒░····· +··········▒▒▒▒▒▒░··············· ··········░▒···················· ················░···░░░········· + +████████████████████████████████ ████████████████████████████████ ████████████████████████████████ +████████████████████████████████ ████████████████████████████████ ████████████████████████████████ +████████████████████████████████ ████████████████████████████████ ████████████████████████████████ +████████████████████████████████ ████████████████████████████████ ████████████████████████████████ +████████████████████████████████ ████████████▓█▓▓▓▓▓▓████████████ ████████████████████████████████ +▓▓▓▓▓▓▓▓████████████▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓█████▓▓▓▓▓▓█▓▓▓▓▓████████ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓████████████▓▓▓▓ +▓▓▓▓▓▓▓▓████████████▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓████▓▓▓▓▓▓▓▓▓▓▓▓████████ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓████████████▓▓▓▓ +▓▓▓▓▓▓▓▓████████████▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓████▓▓▓▓▓▓▓▓▓▓▓▓████████ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓████████████▓▓▓▓ +▓▓▓▓▓▓▓▓████████████▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓████▓▓▓▓▓▓▓▓▓▓▓▓████████ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓████████████▓▓▓▓ +▓▓▓▓▓▓▓▓████████████▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓████▓▓▓▓▓▓▓▓▓▓▓▓████████ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓████████████▓▓▓▓ +▓▓▓▓▓▓▓▓████████████▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓████▓▓▓▓▓█▓▓▓▓▓▓████████ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓████████████▓▓▓▓ +▓▓▓▓▓▓▓▓████████████▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓████▓▓▓▓▓▓▓▓▓▓▓▓████████ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓████████████▓▓▓▓ +▒▒▒▒▒▒▒▒████████████▒▒▒▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒████▓▓▓▓▓▒▒▒▒▒▒▒████████ ▒▒▒▒▒▒▒▒▓▓▓▓▒▒▒▒████████████▓▓▓▓ +▒▒▒▒▒▒▒▒████████████▒▒▒▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒████▒▒▓▓█▒▒▒▒▒▒▒████████ ▒▒▒▒▒▒▒▒▒▒▒▓▒▒▒▒████████████▒▒▒▒ +▒▒▒▒▒▒▒▒████████████▒▒▒▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒████▓▓▓▓▓▒▒▒▒▒▒▒████████ ▒▒▒▒▒▒▒▒▒▒▒▓▒▒▒▒████████████▒▒▒▒ +▒▒▒▒▒▒▒▒████████████▒▒▒▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒████▓▓▓▓▒▒▒▒▒▒▒▒████████ ▒▒▒▒▒▒▒▒▒▒▓▒▒▒▒▒████████████▒▒▒▒ +▒▒▒▒▒▒▒▒████████████▒▒▒▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒████▒▓▓▓▒▒▒▒▒▒▒▒████████ ▒▒▒▒▒▒▒▒▒▒▓▒▒▒▒▓████████████▒▒▒▒ +▒▒▒▒▒▒▒▒████████████▓▓▓▓▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒████▓▓▓▒▒▒▓█▓▓█▓████████ ▒▒▒▒▒▒▒▒▒▓▒▒▒▒▒▒████████████▒▒▒▒ +▒▒▒▒▒▒▒▒████████████▓▒▒▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒████▓▓▓█▓▓▓▓▓▓▒▓████████ ▒▒▒▒▒▒▒▒▓▒▒▒▒▒▒▓████████████▒▒▒▒ +▒▒▒▒▒▒▒▓████████████▒▒▒▒▓▓▓▒▒▒▒▒ ▒▒▒▒▒▒▒▓████▓▓▓▓▓▓▓▓▒▓▒▓████████ ▒▒▒▒▒▒▒▓▓▒▒▒▒▒▓▒████████████▒▒▒▒ +░░░░░░▓▓████████████▒░▒▓▓▒▓▒░░░░ ░░░░░░▓█████▓▓▓▒▓▒▓▓▒▓▓▓████████ ░░░░░░▓▓▓▒▒▒▒▒▒░████████████░░░░ +░░░░░▓▓▓████████████▓▓▓▒▒▓▒▓▒░░░ ░░░░░▓▓█████▓▓▓▓▓▓▓▓▓▓▓▓████████ ░░░░░▓▒▓▒░░▒▒▒▓▒████████████▒░░░ +░░░░░▓▒▓████████████▓▒▓▒▓░▒▓▓▓▒░ ░░░░░▓▒█████▓▓█▓▓▓▓▓▓▓▓▓████████ ░░░░░▒░▓▒▒▒▒▒▒▓▓████████████▒▓░░ +░░░░░▓▒▓████████████▒▒▒░▒▒▓▒▒▒▓░ ░░░░░▓▓▓████▓▓▓▓▒▓▓▒▓▓▒▓████████ ░░░░░▓░▓░▒▒▒▒▓▓░████████████▒▒▓░ +░░░░▓▓▓▒████████████░░▒▒▓▓▓▓▒▒▓▒ ░░░░▓▓█▓████▓▓▓▓▒▓▓▒▓▓▓▓████████ ░░░░▓▓▓▓░░▒▒▒▓▓▒████████████▒▒▒▒ +░░░▒▓▒▓▒████████████▓▓▓▓▓▒▒▒▓▒▒▒ ░░░░▓▓▓▓████▓▓█▓▓▓▓▓█▓▓▓████████ ░░░▒▓▒▓▓▒▒▒▒▒▒▓▓████████████▒▒▒▒ +░░░▒▓▒▓▓████████████▓▓▒▒▒▓▓▓▓▒▒░ ░░░▒▓▓▓▓████▓▓█▓▓▓▓▓▓▓▓▓████████ ░░░▒▒▒▒▓░▒░▒▒▒▓▒████████████▒░▓░ +░░░▓▒░▒▓████████████▓▓▓▓▓▒▓▒░▒▒░ ░░░▓█▒▒▓████▓▓█▓▓▓▓▓▓▓▓▓████████ ░░░▓▓░░▓▒▒░▒▒▒▓▒████████████▒▒▒░ +·░▓▓▓▒░▒████████████▒▒░▒░░░░▒▓·· ·░▓▓▓▓▓█████▓▓▓█▓▒▓▓▒▓▓▒████████ ·░▓▓▓▒▒▓▒▒▒▒▒▒▒▓████████████▓▒·· +·░░··▒▒▒████████████░░·░░░▒▓▒··· ·░░··▒▒▓████▓▓▓▓█▓▓▓▒▓▓▓████████ ·░░··▒▒▓▓▒░░▒▒▒▒████████████▒··· +········████████████▓▒▒▓▒▒░····· ········████▓▓▓▓▓▓▒▓▓▓▓▓████████ ········░▓▓▒▒▒▒▒████████████···· +········████████████·░░········· ········████▓▓▓▒░····░··████████ ··········▒▒▓▒▒▒████████████···· + diff --git a/tests/testlib/blinka_32x32.jpg b/tests/testlib/blinka_32x32.jpg new file mode 100644 index 0000000000000..2758fd967e8a8 Binary files /dev/null and b/tests/testlib/blinka_32x32.jpg differ