Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/shared_bindings_matrix.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
129 changes: 127 additions & 2 deletions shared-bindings/bitmapfilter/__init__.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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) },
Expand All @@ -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);

Expand Down
9 changes: 9 additions & 0 deletions shared-bindings/bitmapfilter/__init__.h
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
67 changes: 62 additions & 5 deletions shared-module/bitmapfilter/__init__.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"));
}
}
Expand Down Expand Up @@ -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"));
Expand Down Expand Up @@ -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"));
Expand Down Expand Up @@ -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);
}
}
}
}
}
51 changes: 51 additions & 0 deletions tests/circuitpython/bitmapfilter_blend.py
Original file line number Diff line number Diff line change
@@ -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)
Loading