bitmapfilter: refine morph, add docs
This commit is contained in:
parent
75be426377
commit
7e23fac766
5 changed files with 243 additions and 71 deletions
|
|
@ -30,25 +30,90 @@
|
|||
#include "shared-bindings/displayio/Bitmap.h"
|
||||
#include "shared-bindings/bitmapfilter/__init__.h"
|
||||
|
||||
//|
|
||||
//| def morph(
|
||||
//| bitmap: displayio.Bitmap,
|
||||
//| weights: tuple[int],
|
||||
//| mul: float = 1.0,
|
||||
//| add: int = 0,
|
||||
//| mask: displayio.Bitmap | None = None,
|
||||
//| threshold=False,
|
||||
//| offset: int = 0,
|
||||
//| invert: bool = False,
|
||||
//| ):
|
||||
//| """Convolve an image with a kernel
|
||||
//|
|
||||
//| The ``bitmap``, which must be in RGB565_SWAPPED format, is modified
|
||||
//| according to the ``weights``. Then a scaling factor ``m`` and an
|
||||
//| offset factor ``b`` are applied.
|
||||
//|
|
||||
//| The ``weights`` must be a tuple of integers. The length of the tuple
|
||||
//| must be the square of an odd number, usually 9 and sometimes 25.
|
||||
//| Specific weights create different effects. For instance, these
|
||||
//| weights represent a 3x3 gaussian blur:
|
||||
//|
|
||||
//| ``mul`` is number to multiply the convolution pixel results by. When
|
||||
//| not set it defaults to a value that will prevent scaling in the
|
||||
//| convolution output.
|
||||
//|
|
||||
//| ``add`` is a value to add to each convolution pixel result.
|
||||
//|
|
||||
//| ``mul`` basically allows you to do a global contrast adjustment and
|
||||
//| add allows you to do a global brightness adjustment. Pixels that go
|
||||
//| outside of the image mins and maxes for color channels will be
|
||||
//| clipped.
|
||||
//|
|
||||
//| If you’d like to adaptive threshold the image on the output of the
|
||||
//| filter you can pass ``threshold=True`` which will enable adaptive
|
||||
//| thresholding of the image which sets pixels to one or zero based on a
|
||||
//| pixel’s brightness in relation to the brightness of the kernel of pixels
|
||||
//| around them. A negative ``offset`` value sets more pixels to 1 as you make
|
||||
//| it more negative while a positive value only sets the sharpest contrast
|
||||
//| changes to 1. Set ``invert`` to invert the binary image resulting output.
|
||||
//|
|
||||
//| ``mask`` is another image to use as a pixel level mask for the operation.
|
||||
//| The mask should be an image with just black or white pixels and should
|
||||
//| be the same size as the image being operated on. Only pixels set in the
|
||||
//| mask are modified.
|
||||
//|
|
||||
//| .. code-block:: python
|
||||
//|
|
||||
//| kernel_gauss_3 = [
|
||||
//| 1, 2, 1,
|
||||
//| 2, 4, 2,
|
||||
//| 1, 2, 1]
|
||||
//|
|
||||
//| def blur(bitmap):
|
||||
//| \"""Blur the bitmap with a 3x3 gaussian kernel\"""
|
||||
//| bitmapfilter.morph(bitmap, kernel_gauss_3, 1/sum(kernel_gauss_3))
|
||||
//| """
|
||||
//|
|
||||
|
||||
STATIC mp_obj_t bitmapfilter_morph(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
|
||||
enum { ARG_dest_bitmap, ARG_source_bitmap, ARG_weights, ARG_m, ARG_b };
|
||||
enum { ARG_bitmap, ARG_weights, ARG_mul, ARG_add, ARG_threshold, ARG_offset, ARG_invert, ARG_mask };
|
||||
static const mp_arg_t allowed_args[] = {
|
||||
{ MP_QSTR_dest_bitmap, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_obj = MP_OBJ_NULL } },
|
||||
{ MP_QSTR_source_bitmap, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_obj = MP_OBJ_NULL } },
|
||||
{ MP_QSTR_bitmap, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_obj = MP_OBJ_NULL } },
|
||||
{ MP_QSTR_weights, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_obj = MP_OBJ_NULL } },
|
||||
{ MP_QSTR_m, MP_ARG_OBJ, { .u_obj = MP_ROM_INT(1) } },
|
||||
{ MP_QSTR_b, MP_ARG_OBJ, { .u_obj = MP_ROM_INT(0) } },
|
||||
{ MP_QSTR_mul, MP_ARG_OBJ, { .u_obj = MP_ROM_NONE } },
|
||||
{ MP_QSTR_add, MP_ARG_OBJ, { .u_obj = MP_ROM_INT(0) } },
|
||||
{ MP_QSTR_threshold, MP_ARG_BOOL, { .u_bool = false } },
|
||||
{ MP_QSTR_offset, MP_ARG_INT, { .u_int = 0 } },
|
||||
{ MP_QSTR_invert, MP_ARG_BOOL, { .u_bool = false } },
|
||||
{ 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_bitmap].u_obj, &displayio_bitmap_type, MP_QSTR_dest_bitmap);
|
||||
mp_arg_validate_type(args[ARG_source_bitmap].u_obj, &displayio_bitmap_type, MP_QSTR_source_bitmap);
|
||||
displayio_bitmap_t *destination = MP_OBJ_TO_PTR(args[ARG_dest_bitmap].u_obj); // the destination bitmap
|
||||
displayio_bitmap_t *source = MP_OBJ_TO_PTR(args[ARG_source_bitmap].u_obj); // the source bitmap
|
||||
mp_arg_validate_type(args[ARG_bitmap].u_obj, &displayio_bitmap_type, MP_QSTR_bitmap);
|
||||
displayio_bitmap_t *bitmap = args[ARG_bitmap].u_obj;
|
||||
|
||||
mp_float_t m = mp_obj_get_float(args[ARG_m].u_obj);
|
||||
mp_int_t b = mp_obj_get_int(args[ARG_b].u_obj);
|
||||
displayio_bitmap_t *mask = NULL; // the mask bitmap
|
||||
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);
|
||||
}
|
||||
|
||||
mp_int_t b = mp_obj_get_int(args[ARG_add].u_obj);
|
||||
|
||||
size_t n_weights;
|
||||
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
|
|||
}
|
||||
|
||||
int iweights[n_weights];
|
||||
int weight_sum = 0;
|
||||
for (size_t i = 0; i < n_weights; i++) {
|
||||
iweights[i] = mp_obj_get_int(items[i]);
|
||||
mp_int_t j = mp_obj_get_int(items[i]);
|
||||
iweights[i] = j;
|
||||
weight_sum += j;
|
||||
}
|
||||
|
||||
shared_module_bitmapfilter_morph(source, destination, sq_n_weights / 2, iweights, (float)m, b, false, 0, false);
|
||||
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;
|
||||
|
||||
shared_module_bitmapfilter_morph(bitmap, mask, sq_n_weights / 2, iweights, (float)m, b,
|
||||
args[ARG_threshold].u_bool, args[ARG_offset].u_bool, args[ARG_invert].u_bool);
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,8 +29,8 @@
|
|||
#include "shared-module/displayio/Bitmap.h"
|
||||
|
||||
void shared_module_bitmapfilter_morph(
|
||||
displayio_bitmap_t *src,
|
||||
displayio_bitmap_t *dest,
|
||||
displayio_bitmap_t *bitmap,
|
||||
displayio_bitmap_t *mask,
|
||||
const int ksize,
|
||||
const int *krn,
|
||||
const float m,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
* Copyright (c) 2024 Jeff Epler for Adafruit Industries
|
||||
*
|
||||
* This work is licensed under the MIT license, see the file LICENSE for details.
|
||||
* Adapted from https://github.com/openmv/openmv/blob/master/src/omv/imlib/filter.c#L2083
|
||||
* Adapted from https://github.com/openmv/openmv/blob/master/bitmap/omv/imlib/filter.c#L2083
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
#include "py/runtime.h"
|
||||
|
||||
#include "shared-bindings/displayio/Bitmap.h"
|
||||
#include "shared-bindings/bitmapfilter/__init__.h"
|
||||
#include "shared-module/bitmapfilter/__init__.h"
|
||||
|
||||
|
|
@ -28,7 +29,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 || b1->bits_per_value != b2->bits_per_value) {
|
||||
if (b1->width != b2->width || b1->height != b2->height) {
|
||||
mp_raise_ValueError(MP_ERROR_TEXT("bitmap size and depth must match"));
|
||||
}
|
||||
}
|
||||
|
|
@ -135,8 +136,8 @@ static void scratch_bitmap16(displayio_bitmap_t *buf, int rows, int cols) {
|
|||
})
|
||||
|
||||
void shared_module_bitmapfilter_morph(
|
||||
displayio_bitmap_t *src,
|
||||
displayio_bitmap_t *dest,
|
||||
displayio_bitmap_t *bitmap,
|
||||
displayio_bitmap_t *mask,
|
||||
const int ksize,
|
||||
const int *krn,
|
||||
const float m,
|
||||
|
|
@ -149,25 +150,30 @@ void shared_module_bitmapfilter_morph(
|
|||
|
||||
const int32_t m_int = (int32_t)MICROPY_FLOAT_C_FUN(round)(65536 * m);
|
||||
|
||||
check_matching_details(src, dest);
|
||||
check_matching_details(bitmap, bitmap);
|
||||
|
||||
switch (src->bits_per_value) {
|
||||
switch (bitmap->bits_per_value) {
|
||||
default:
|
||||
mp_raise_ValueError(MP_ERROR_TEXT("unsupported bitmap depth"));
|
||||
case 16: {
|
||||
displayio_bitmap_t buf;
|
||||
scratch_bitmap16(&buf, brows, src->width);
|
||||
scratch_bitmap16(&buf, brows, bitmap->width);
|
||||
|
||||
for (int y = 0, yy = src->height; y < yy; y++) {
|
||||
uint16_t *row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(src, y);
|
||||
for (int y = 0, yy = bitmap->height; y < yy; y++) {
|
||||
uint16_t *row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(bitmap, y);
|
||||
uint16_t *buf_row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(&buf, (y % brows));
|
||||
|
||||
for (int x = 0, xx = src->width; x < xx; x++) {
|
||||
for (int x = 0, xx = bitmap->width; x < xx; x++) {
|
||||
if (mask && common_hal_displayio_bitmap_get_pixel(mask, x, y)) {
|
||||
IMAGE_PUT_RGB565_PIXEL_FAST(buf_row_ptr, x, IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x));
|
||||
continue; // Short circuit.
|
||||
|
||||
}
|
||||
int32_t tmp, r_acc = 0, g_acc = 0, b_acc = 0, ptr = 0;
|
||||
|
||||
if (x >= ksize && x < src->width - ksize && y >= ksize && y < src->height - ksize) {
|
||||
if (x >= ksize && x < bitmap->width - ksize && y >= ksize && y < bitmap->height - ksize) {
|
||||
for (int j = -ksize; j <= ksize; j++) {
|
||||
uint16_t *k_row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(src, y + j);
|
||||
uint16_t *k_row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(bitmap, y + j);
|
||||
for (int k = -ksize; k <= ksize; k++) {
|
||||
int pixel = IMAGE_GET_RGB565_PIXEL_FAST(k_row_ptr, x + k);
|
||||
r_acc += krn[ptr] * COLOR_RGB565_TO_R5(pixel);
|
||||
|
|
@ -177,11 +183,11 @@ void shared_module_bitmapfilter_morph(
|
|||
}
|
||||
} else {
|
||||
for (int j = -ksize; j <= ksize; j++) {
|
||||
uint16_t *k_row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(src,
|
||||
IM_MIN(IM_MAX(y + j, 0), (src->height - 1)));
|
||||
uint16_t *k_row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(bitmap,
|
||||
IM_MIN(IM_MAX(y + j, 0), (bitmap->height - 1)));
|
||||
for (int k = -ksize; k <= ksize; k++) {
|
||||
int pixel = IMAGE_GET_RGB565_PIXEL_FAST(k_row_ptr,
|
||||
IM_MIN(IM_MAX(x + k, 0), (src->width - 1)));
|
||||
IM_MIN(IM_MAX(x + k, 0), (bitmap->width - 1)));
|
||||
r_acc += krn[ptr] * COLOR_RGB565_TO_R5(pixel);
|
||||
g_acc += krn[ptr] * COLOR_RGB565_TO_G6(pixel);
|
||||
b_acc += krn[ptr++] * COLOR_RGB565_TO_B5(pixel);
|
||||
|
|
@ -224,17 +230,17 @@ void shared_module_bitmapfilter_morph(
|
|||
}
|
||||
|
||||
if (y >= ksize) { // Transfer buffer lines...
|
||||
memcpy(IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(dest, (y - ksize)),
|
||||
memcpy(IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(bitmap, (y - ksize)),
|
||||
IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(&buf, ((y - ksize) % brows)),
|
||||
IMAGE_RGB565_LINE_LEN_BYTES(src));
|
||||
IMAGE_RGB565_LINE_LEN_BYTES(bitmap));
|
||||
}
|
||||
}
|
||||
|
||||
// Copy any remaining lines from the buffer image...
|
||||
for (int y = IM_MAX(src->height - ksize, 0), yy = src->height; y < yy; y++) {
|
||||
memcpy(IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(dest, y),
|
||||
for (int y = IM_MAX(bitmap->height - ksize, 0), yy = bitmap->height; y < yy; y++) {
|
||||
memcpy(IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(bitmap, y),
|
||||
IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(&buf, (y % brows)),
|
||||
IMAGE_RGB565_LINE_LEN_BYTES(src));
|
||||
IMAGE_RGB565_LINE_LEN_BYTES(bitmap));
|
||||
}
|
||||
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -1,34 +1,52 @@
|
|||
from displayio import Bitmap
|
||||
import bitmapfilter
|
||||
import displayio
|
||||
|
||||
b = displayio.Bitmap(5, 5, 65535)
|
||||
b[2, 2] = 0x1F00 # Blue in RGB565_SWAPPED
|
||||
b[0, 0] = 0xE007 # Green in RGB565_SWAPPED
|
||||
b[0, 4] = 0x00F8 # Red in RGB565_SWAPPED
|
||||
weights = (1, 1, 1, 1, 1, 1, 1, 1, 1)
|
||||
palette = list(" ░░▒▒▓▓█")
|
||||
|
||||
|
||||
def print_bitmap(bitmap):
|
||||
for i in range(bitmap.height):
|
||||
for j in range(bitmap.width):
|
||||
p = bitmap[j, i]
|
||||
p = ((p << 8) & 0xFF00) | (p >> 8)
|
||||
|
||||
r = (p >> 8) & 0xF8
|
||||
r |= r >> 5
|
||||
|
||||
g = (p >> 3) & 0xFC
|
||||
g |= g >> 6
|
||||
|
||||
b = (p << 3) & 0xF8
|
||||
b |= b >> 5
|
||||
print(f"{r:02x}{g:02x}{b:02x}", end=" ")
|
||||
def dump_bitmap(b):
|
||||
for i in range(b.height):
|
||||
for j in range(b.width):
|
||||
# Bit order is gggBBBBBRRRRRGGG" so this takes high order bits of G
|
||||
p = b[i, j] & 7
|
||||
print(end=palette[p])
|
||||
print()
|
||||
print()
|
||||
|
||||
|
||||
print(len(weights))
|
||||
def make_circle_bitmap():
|
||||
b = Bitmap(17, 17, 65535)
|
||||
for i in range(b.height):
|
||||
y = i - 8
|
||||
for j in range(b.width):
|
||||
x = j - 8
|
||||
c = (x * x + y * y) > 64
|
||||
b[i, j] = 0xFFFF if c else 0
|
||||
return b
|
||||
|
||||
print_bitmap(b)
|
||||
bitmapfilter.morph(b, b, weights, m=1 / 9)
|
||||
print_bitmap(b)
|
||||
|
||||
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
|
||||
|
||||
|
||||
blur = (1, 2, 1, 2, 4, 2, 1, 2, 1)
|
||||
sharpen = (-1, -2, -1, -2, 4, -2, -1, -2, -1)
|
||||
b = make_circle_bitmap()
|
||||
dump_bitmap(b)
|
||||
bitmapfilter.morph(b, weights=blur)
|
||||
dump_bitmap(b)
|
||||
|
||||
b = make_circle_bitmap()
|
||||
q = make_quadrant_bitmap()
|
||||
dump_bitmap(q)
|
||||
bitmapfilter.morph(b, mask=q, weights=blur, add=32)
|
||||
dump_bitmap(b)
|
||||
|
||||
# This is a kind of edge filter
|
||||
b = make_circle_bitmap()
|
||||
bitmapfilter.morph(b, weights=sharpen, threshold=True, add=8, invert=True)
|
||||
dump_bitmap(b)
|
||||
|
|
|
|||
|
|
@ -1,13 +1,90 @@
|
|||
9
|
||||
00ff00 000000 000000 000000 000000
|
||||
000000 000000 000000 000000 000000
|
||||
000000 000000 0000ff 000000 000000
|
||||
000000 000000 000000 000000 000000
|
||||
ff0000 000000 000000 000000 000000
|
||||
████████ ████████
|
||||
█████ █████
|
||||
███ ███
|
||||
██ ██
|
||||
██ ██
|
||||
█ █
|
||||
█ █
|
||||
█ █
|
||||
|
||||
█ █
|
||||
█ █
|
||||
█ █
|
||||
██ ██
|
||||
██ ██
|
||||
███ ███
|
||||
█████ █████
|
||||
████████ ████████
|
||||
|
||||
007100 003800 000000 000000 000000
|
||||
003800 001c18 000018 000018 000000
|
||||
000000 000018 000018 000018 000000
|
||||
310000 180018 000018 000018 000000
|
||||
6b0000 310000 000000 000000 000000
|
||||
█████▓▓▒░▒▓▓█████
|
||||
███▓▒░░░ ░░░▒▓███
|
||||
██▓░░ ░░▓██
|
||||
█▓░ ░▓█
|
||||
█▒░ ░▒█
|
||||
▓░ ░▓
|
||||
▓░ ░▓
|
||||
▒░ ░▒
|
||||
░ ░
|
||||
▒░ ░▒
|
||||
▓░ ░▓
|
||||
▓░ ░▓
|
||||
█▒░ ░▒█
|
||||
█▓░ ░▓█
|
||||
██▓░░ ░░▓██
|
||||
███▓▒░░░ ░░░▒▓███
|
||||
█████▓▓▒░▒▓▓█████
|
||||
|
||||
░░░░░░░░░
|
||||
░░░░░░░░░
|
||||
░░░░░░░░░
|
||||
░░░░░░░░░
|
||||
░░░░░░░░░
|
||||
░░░░░░░░░
|
||||
░░░░░░░░░
|
||||
░░░░░░░░░
|
||||
░░░░░░░░
|
||||
░░░░░░░░
|
||||
░░░░░░░░
|
||||
░░░░░░░░
|
||||
░░░░░░░░
|
||||
░░░░░░░░
|
||||
░░░░░░░░
|
||||
░░░░░░░░
|
||||
░░░░░░░░
|
||||
|
||||
████████ ████████
|
||||
█████▓▓▓ █████
|
||||
███▓▓▒▒▒ ███
|
||||
██▓▒▒▒▒▒ ██
|
||||
██▓▒▒▒▒▒ ██
|
||||
█▓▒▒▒▒▒▒ █
|
||||
█▓▒▒▒▒▒▒ █
|
||||
█▓▒▒▒▒▒▒ █
|
||||
▒▒▒▒▒▒▒▒▓
|
||||
█ ▒▒▒▒▒▒▒▓█
|
||||
█ ▒▒▒▒▒▒▒▓█
|
||||
█ ▒▒▒▒▒▒▒▓█
|
||||
██ ▒▒▒▒▒▒▓██
|
||||
██ ▒▒▒▒▒▒▓██
|
||||
███ ▒▒▒▒▓▓███
|
||||
█████ ▒▓▓▓█████
|
||||
████████▓████████
|
||||
|
||||
█████ █ █████
|
||||
███ ███████ ███
|
||||
██ ███████████ ██
|
||||
█ █████████████ █
|
||||
█ █████████████ █
|
||||
███████████████
|
||||
███████████████
|
||||
███████████████
|
||||
█████████████████
|
||||
███████████████
|
||||
███████████████
|
||||
███████████████
|
||||
█ █████████████ █
|
||||
█ █████████████ █
|
||||
██ ███████████ ██
|
||||
███ ███████ ███
|
||||
█████ █ █████
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue