bitmaptools: add dither
This can convert a BGR565_SWAPPED bitmap to B&W in about 82ms on esp32-s2.
This commit is contained in:
parent
5572876d29
commit
eaf8bc0abe
@ -2611,6 +2611,10 @@ msgstr ""
|
||||
msgid "binary op %q not implemented"
|
||||
msgstr ""
|
||||
|
||||
#: shared-bindings/bitmaptools/__init__.c
|
||||
msgid "bitmap sizes must match"
|
||||
msgstr ""
|
||||
|
||||
#: extmod/modurandom.c
|
||||
msgid "bits must be 32 or less"
|
||||
msgstr ""
|
||||
@ -4102,6 +4106,18 @@ msgstr ""
|
||||
msgid "source palette too large"
|
||||
msgstr ""
|
||||
|
||||
#: shared-bindings/bitmaptools/__init__.c
|
||||
msgid "source_bitmap must have value_count of 2 or 65536"
|
||||
msgstr ""
|
||||
|
||||
#: shared-bindings/bitmaptools/__init__.c
|
||||
msgid "source_bitmap must have value_count of 65536"
|
||||
msgstr ""
|
||||
|
||||
#: shared-bindings/bitmaptools/__init__.c
|
||||
msgid "source_bitmap must have value_count of 8"
|
||||
msgstr ""
|
||||
|
||||
#: py/objstr.c
|
||||
msgid "start/end indices"
|
||||
msgstr ""
|
||||
@ -4350,6 +4366,10 @@ msgstr ""
|
||||
msgid "unsupported colorspace for GifWriter"
|
||||
msgstr ""
|
||||
|
||||
#: shared-bindings/bitmaptools/__init__.c
|
||||
msgid "unsupported colorspace for dither"
|
||||
msgstr ""
|
||||
|
||||
#: py/objstr.c
|
||||
#, c-format
|
||||
msgid "unsupported format character '%c' (0x%x) at index %d"
|
||||
|
@ -25,11 +25,14 @@
|
||||
*/
|
||||
|
||||
#include "shared-bindings/displayio/Bitmap.h"
|
||||
#include "shared-bindings/displayio/Palette.h"
|
||||
#include "shared-bindings/displayio/ColorConverter.h"
|
||||
#include "shared-bindings/bitmaptools/__init__.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "py/binary.h"
|
||||
#include "py/enum.h"
|
||||
#include "py/obj.h"
|
||||
#include "py/runtime.h"
|
||||
|
||||
@ -565,9 +568,92 @@ STATIC mp_obj_t bitmaptools_readinto(size_t n_args, const mp_obj_t *pos_args, mp
|
||||
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
MP_DEFINE_CONST_FUN_OBJ_KW(bitmaptools_readinto_obj, 0, bitmaptools_readinto);
|
||||
|
||||
//| class DitherAlgorithm:
|
||||
//| """Identifies the algorith for dither to use"""
|
||||
//|
|
||||
//| Atkinson: object
|
||||
//| """The classic Atkinson dither, often associated with the Hypercard esthetic"""
|
||||
//|
|
||||
//| FloydStenberg: object
|
||||
//| """The Floyd-Stenberg dither"""
|
||||
//|
|
||||
MAKE_ENUM_VALUE(bitmaptools_dither_algorithm_type, dither_algorithm, Atkinson, DITHER_ALGORITHM_ATKINSON);
|
||||
MAKE_ENUM_VALUE(bitmaptools_dither_algorithm_type, dither_algorithm, FloydStenberg, DITHER_ALGORITHM_ATKINSON);
|
||||
|
||||
MAKE_ENUM_MAP(bitmaptools_dither_algorithm) {
|
||||
MAKE_ENUM_MAP_ENTRY(dither_algorithm, Atkinson),
|
||||
MAKE_ENUM_MAP_ENTRY(dither_algorithm, FloydStenberg),
|
||||
};
|
||||
STATIC MP_DEFINE_CONST_DICT(bitmaptools_dither_algorithm_locals_dict, bitmaptools_dither_algorithm_locals_table);
|
||||
|
||||
MAKE_PRINTER(bitmaptools, bitmaptools_dither_algorithm);
|
||||
|
||||
MAKE_ENUM_TYPE(bitmaptools, DitherAlgorithm, bitmaptools_dither_algorithm);
|
||||
|
||||
//| def dither(dest_bitmap: displayio.Bitmap, source_bitmapp: displayio.Bitmap, source_colorspace: displayio.Colorspace, algorithm: DitherAlgorithm=DitherAlgorithm.Atkinson) -> None:
|
||||
//| """Convert the input image into a 2-level output image using the given dither algorithm.
|
||||
//|
|
||||
//| :param bitmap dest_bitmap: Destination bitmap. It must have a value_count of 2 or 65536. The stored values are 0 and the maximum pixel value.
|
||||
//| :param bitmap source_bitmap: Source bitmap that contains the graphical region to be dithered. It must have a value_count of 65536.
|
||||
//| :param colorspace: The colorspace of the image. The supported colorspaces are ``RGB565``, ``BGR565``, ``RGB565_SWAPPED``, and ``BGR565_SWAPPED``
|
||||
//| :param algorithm: The dither algorithm to use, one of the `DitherAlgorithm `values.
|
||||
//| """
|
||||
//| ...
|
||||
//|
|
||||
STATIC mp_obj_t bitmaptools_dither(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
|
||||
enum { ARG_dest_bitmap, ARG_source_bitmap, ARG_source_colorspace, ARG_algorithm };
|
||||
static const mp_arg_t allowed_args[] = {
|
||||
{ MP_QSTR_dest_bitmap, MP_ARG_REQUIRED | MP_ARG_OBJ },
|
||||
{ MP_QSTR_source_bitmap, MP_ARG_REQUIRED | MP_ARG_OBJ },
|
||||
{ MP_QSTR_source_colorspace, MP_ARG_REQUIRED | MP_ARG_OBJ },
|
||||
{ MP_QSTR_algorithm, MP_ARG_OBJ, { .u_obj = MP_ROM_PTR((void *)&dither_algorithm_Atkinson_obj) } },
|
||||
};
|
||||
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);
|
||||
displayio_bitmap_t *source_bitmap = mp_arg_validate_type(args[ARG_source_bitmap].u_obj, &displayio_bitmap_type, MP_QSTR_source_bitmap);
|
||||
displayio_bitmap_t *dest_bitmap = mp_arg_validate_type(args[ARG_dest_bitmap].u_obj, &displayio_bitmap_type, MP_QSTR_dest_bitmap);
|
||||
bitmaptools_dither_algorithm_t algorithm = cp_enum_value(&bitmaptools_dither_algorithm_type, args[ARG_algorithm].u_obj);
|
||||
displayio_colorspace_t colorspace = cp_enum_value(&displayio_colorspace_type, args[ARG_source_colorspace].u_obj);
|
||||
|
||||
if (source_bitmap->width != dest_bitmap->width || source_bitmap->height != dest_bitmap->height) {
|
||||
mp_raise_TypeError(translate("bitmap sizes must match"));
|
||||
}
|
||||
|
||||
if (dest_bitmap->bits_per_value != 16 && dest_bitmap->bits_per_value != 1) {
|
||||
mp_raise_TypeError(translate("source_bitmap must have value_count of 2 or 65536"));
|
||||
}
|
||||
|
||||
|
||||
switch (colorspace) {
|
||||
case DISPLAYIO_COLORSPACE_RGB565:
|
||||
case DISPLAYIO_COLORSPACE_RGB565_SWAPPED:
|
||||
case DISPLAYIO_COLORSPACE_BGR565:
|
||||
case DISPLAYIO_COLORSPACE_BGR565_SWAPPED:
|
||||
if (source_bitmap->bits_per_value != 16) {
|
||||
mp_raise_TypeError(translate("source_bitmap must have value_count of 65536"));
|
||||
}
|
||||
break;
|
||||
|
||||
case DISPLAYIO_COLORSPACE_L8:
|
||||
if (source_bitmap->bits_per_value != 8) {
|
||||
mp_raise_TypeError(translate("source_bitmap must have value_count of 8"));
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
mp_raise_TypeError(translate("unsupported colorspace for dither"));
|
||||
}
|
||||
|
||||
|
||||
common_hal_bitmaptools_dither(dest_bitmap, source_bitmap, colorspace, algorithm);
|
||||
|
||||
return mp_const_none;
|
||||
}
|
||||
MP_DEFINE_CONST_FUN_OBJ_KW(bitmaptools_dither_obj, 0, bitmaptools_dither);
|
||||
|
||||
|
||||
STATIC const mp_rom_map_elem_t bitmaptools_module_globals_table[] = {
|
||||
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_bitmaptools) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&bitmaptools_readinto_obj) },
|
||||
@ -576,6 +662,8 @@ STATIC const mp_rom_map_elem_t bitmaptools_module_globals_table[] = {
|
||||
{ MP_ROM_QSTR(MP_QSTR_fill_region), MP_ROM_PTR(&bitmaptools_fill_region_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_boundary_fill), MP_ROM_PTR(&bitmaptools_boundary_fill_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_draw_line), MP_ROM_PTR(&bitmaptools_draw_line_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_dither), MP_ROM_PTR(&bitmaptools_dither_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_DitherAlgorithm), MP_ROM_PTR(&bitmaptools_dither_algorithm_type) },
|
||||
};
|
||||
STATIC MP_DEFINE_CONST_DICT(bitmaptools_module_globals, bitmaptools_module_globals_table);
|
||||
|
||||
|
@ -28,9 +28,17 @@
|
||||
#define MICROPY_INCLUDED_SHARED_BINDINGS_BITMAPTOOLS__INIT__H
|
||||
|
||||
#include "shared-module/displayio/Bitmap.h"
|
||||
#include "shared-module/displayio/Palette.h"
|
||||
#include "shared-bindings/displayio/ColorConverter.h"
|
||||
#include "py/obj.h"
|
||||
#include "extmod/vfs_fat.h"
|
||||
|
||||
typedef enum {
|
||||
DITHER_ALGORITHM_ATKINSON, DITHER_ALGORITHM_FLOYD_STENBERG,
|
||||
} bitmaptools_dither_algorithm_t;
|
||||
|
||||
extern const mp_obj_type_t bitmaptools_dither_algorithm_type;
|
||||
|
||||
void common_hal_bitmaptools_rotozoom(displayio_bitmap_t *self, int16_t ox, int16_t oy,
|
||||
int16_t dest_clip0_x, int16_t dest_clip0_y,
|
||||
int16_t dest_clip1_x, int16_t dest_clip1_y,
|
||||
@ -57,5 +65,6 @@ void common_hal_bitmaptools_draw_line(displayio_bitmap_t *destination,
|
||||
|
||||
void common_hal_bitmaptools_readinto(displayio_bitmap_t *self, pyb_file_obj_t *file, int element_size, int bits_per_pixel, bool reverse_pixels_in_word, bool swap_bytes, bool reverse_rows);
|
||||
void common_hal_bitmaptools_arrayblit(displayio_bitmap_t *self, void *data, int element_size, int x1, int y1, int x2, int y2, bool skip_specified, uint32_t skip_index);
|
||||
void common_hal_bitmaptools_dither(displayio_bitmap_t *dest_bitmap, displayio_bitmap_t *source_bitmap, displayio_colorspace_t colorspace, bitmaptools_dither_algorithm_t algorithm);
|
||||
|
||||
#endif // MICROPY_INCLUDED_SHARED_BINDINGS_BITMAPTOOLS__INIT__H
|
||||
|
@ -26,13 +26,17 @@
|
||||
|
||||
#include "shared-bindings/bitmaptools/__init__.h"
|
||||
#include "shared-bindings/displayio/Bitmap.h"
|
||||
#include "shared-bindings/displayio/Palette.h"
|
||||
#include "shared-bindings/displayio/ColorConverter.h"
|
||||
#include "shared-module/displayio/Bitmap.h"
|
||||
|
||||
#include "py/runtime.h"
|
||||
#include "py/mperrno.h"
|
||||
|
||||
#include "math.h"
|
||||
#include "stdlib.h"
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
void common_hal_bitmaptools_rotozoom(displayio_bitmap_t *self, int16_t ox, int16_t oy,
|
||||
int16_t dest_clip0_x, int16_t dest_clip0_y,
|
||||
@ -602,3 +606,196 @@ void common_hal_bitmaptools_readinto(displayio_bitmap_t *self, pyb_file_obj_t *f
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
uint8_t count; // The number of items in terms[]
|
||||
uint8_t mx; // the maximum of the absolute value of the dx values
|
||||
uint8_t dl; // the scaled dither value applied to the pixel at distance [1,0]
|
||||
struct { // dl is the scaled dither values applied to the pixel at [dx,dy]
|
||||
int8_t dx, dy, dl;
|
||||
} terms[];
|
||||
} bitmaptools_dither_algorithm_info_t;
|
||||
|
||||
static bitmaptools_dither_algorithm_info_t atkinson = {
|
||||
4, 2, 256 / 8, {
|
||||
{2, 0, 256 / 8},
|
||||
{-1, 1, 256 / 8},
|
||||
{0, 1, 256 / 8},
|
||||
{0, 2, 256 / 8},
|
||||
}
|
||||
};
|
||||
|
||||
static bitmaptools_dither_algorithm_info_t floyd_stenberg = {
|
||||
3, 1, 7 * 256 / 16,
|
||||
{
|
||||
{-1, 1, 3 * 256 / 16},
|
||||
{0, 1, 5 * 256 / 16},
|
||||
{1, 1, 1 * 256 / 16},
|
||||
}
|
||||
};
|
||||
|
||||
bitmaptools_dither_algorithm_info_t *algorithms[] = {
|
||||
[DITHER_ALGORITHM_ATKINSON] = &atkinson,
|
||||
[DITHER_ALGORITHM_FLOYD_STENBERG] = &floyd_stenberg,
|
||||
};
|
||||
|
||||
enum {
|
||||
SWAP_BYTES = 1 << 0,
|
||||
SWAP_RB = 1 << 1,
|
||||
};
|
||||
|
||||
STATIC void fill_row(displayio_bitmap_t *bitmap, int swap, int16_t *luminance_data, int y, int mx) {
|
||||
if (y >= bitmap->height) {
|
||||
return;
|
||||
}
|
||||
|
||||
// zero out padding area
|
||||
for (int i = 0; i < mx; i++) {
|
||||
luminance_data[-mx + i] = 0;
|
||||
luminance_data[bitmap->width + i] = 0;
|
||||
}
|
||||
|
||||
if (bitmap->bits_per_value == 8) {
|
||||
uint8_t *pixel_data = (uint8_t *)(bitmap->data + bitmap->stride * y);
|
||||
for (int x = 0; x < bitmap->width; x++) {
|
||||
*luminance_data++ = *pixel_data++;
|
||||
}
|
||||
} else {
|
||||
uint16_t *pixel_data = (uint16_t *)(bitmap->data + bitmap->stride * y);
|
||||
for (int x = 0; x < bitmap->width; x++) {
|
||||
uint16_t pixel = *pixel_data++;
|
||||
if (swap & SWAP_BYTES) {
|
||||
pixel = __builtin_bswap16(pixel);
|
||||
}
|
||||
int r = (pixel >> 8) & 0xf8;
|
||||
int g = (pixel >> 3) & 0xfc;
|
||||
int b = (pixel << 3) & 0xf8;
|
||||
|
||||
if (swap & SWAP_RB) {
|
||||
uint8_t tmp = r;
|
||||
r = b;
|
||||
b = tmp;
|
||||
}
|
||||
|
||||
// ideal coefficients are around .299, .587, .114 (according to
|
||||
// ppmtopnm), this differs from the 'other' luma-converting
|
||||
// function in circuitpython (why?)
|
||||
|
||||
// we correct for the fact that the input ranges are 0..0xf8 (or
|
||||
// 0xfc) rather than 0x00..0xff
|
||||
// Check: (0xf8 * 78 + 0xfc * 154 + 0xf8 * 29) // 256 == 255
|
||||
*luminance_data++ = (r * 78 + g * 154 + b * 29) / 256;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void write_pixels(displayio_bitmap_t *bitmap, int y, bool *data) {
|
||||
if (bitmap->bits_per_value == 1) {
|
||||
uint32_t *pixel_data = (uint32_t *)(bitmap->data + bitmap->stride * y);
|
||||
for (int i = 0; i < bitmap->stride; i++) {
|
||||
uint32_t p = 0;
|
||||
for (int j = 0; j < 32; i++) {
|
||||
p = (p << 1);
|
||||
if (*data++) {
|
||||
p |= 1;
|
||||
}
|
||||
}
|
||||
*pixel_data++ = p;
|
||||
}
|
||||
} else {
|
||||
uint16_t *pixel_data = (uint16_t *)(bitmap->data + bitmap->stride * y);
|
||||
for (int i = 0; i < bitmap->width; i++) {
|
||||
*pixel_data++ = *data++ ? 65535 : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void common_hal_bitmaptools_dither(displayio_bitmap_t *dest_bitmap, displayio_bitmap_t *source_bitmap, displayio_colorspace_t colorspace, bitmaptools_dither_algorithm_t algorithm) {
|
||||
int height = dest_bitmap->height, width = dest_bitmap->width;
|
||||
|
||||
int swap = 0;
|
||||
if (colorspace == DISPLAYIO_COLORSPACE_RGB565_SWAPPED || colorspace == DISPLAYIO_COLORSPACE_BGR565_SWAPPED) {
|
||||
swap |= SWAP_BYTES;
|
||||
}
|
||||
if (colorspace == DISPLAYIO_COLORSPACE_BGR565 || colorspace == DISPLAYIO_COLORSPACE_BGR565_SWAPPED) {
|
||||
swap |= SWAP_RB;
|
||||
}
|
||||
|
||||
bitmaptools_dither_algorithm_info_t *info = algorithms[algorithm];
|
||||
// rowdata holds 3 rows of data. Each one is larger than the input
|
||||
// bitmap's width, beacuse `mx` extra pixels are allocated at the start and
|
||||
// end of the row so that no conditionals are needed when storing the error data.
|
||||
int16_t rowdata[(width + 2 * info->mx) * 3];
|
||||
int16_t *rows[3] = {
|
||||
rowdata + info->mx, rowdata + width + info->mx * 3, rowdata + 2 * width + info->mx * 5
|
||||
};
|
||||
// out holds one output row of pixels, and is padded to be a multiple of 32 so that the 1bpp storage loop can be simplified
|
||||
bool out[(width + 31) / 32 * 32];
|
||||
|
||||
fill_row(source_bitmap, swap, rows[0], 0, info->mx);
|
||||
fill_row(source_bitmap, swap, rows[1], 1, info->mx);
|
||||
fill_row(source_bitmap, swap, rows[2], 2, info->mx);
|
||||
|
||||
int16_t err = 0;
|
||||
|
||||
for (int y = 0; y < height; y++) {
|
||||
|
||||
// Serpentine dither. Going left-to-right...
|
||||
for (int x = 0; x < width; x++) {
|
||||
int32_t pixel_in = rows[0][x] + err;
|
||||
bool pixel_out = pixel_in >= 128;
|
||||
out[x] = pixel_out;
|
||||
|
||||
err = pixel_in - (pixel_out ? 255 : 0);
|
||||
|
||||
for (int i = 0; i < info->count; i++) {
|
||||
int x1 = x + info->terms[i].dx;
|
||||
int dy = info->terms[i].dy;
|
||||
|
||||
rows[dy][x1] = ((info->terms[i].dl * err) >> 8) + rows[dy][x1];
|
||||
}
|
||||
err = err * info->dl >> 8;
|
||||
}
|
||||
write_pixels(dest_bitmap, y, out);
|
||||
|
||||
// Cycle the rows by shuffling pointers, this is faster than copying the data.
|
||||
int16_t *tmp = rows[0];
|
||||
rows[0] = rows[1];
|
||||
rows[1] = rows[2];
|
||||
rows[2] = tmp;
|
||||
|
||||
fill_row(source_bitmap, swap, rows[2], y + 2, info->mx);
|
||||
|
||||
y++;
|
||||
if (y == height) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Serpentine dither. Going right-to-left...
|
||||
for (int x = width; x--;) {
|
||||
int16_t pixel_in = rows[0][x] + err;
|
||||
bool pixel_out = pixel_in >= 128;
|
||||
out[x] = pixel_out;
|
||||
err = pixel_in - (pixel_out ? 255 : 0);
|
||||
|
||||
for (int i = 0; i < info->count; i++) {
|
||||
int x1 = x - info->terms[i].dx;
|
||||
int dy = info->terms[i].dy;
|
||||
|
||||
rows[dy][x1] = ((info->terms[i].dl * err) >> 8) + rows[dy][x1];
|
||||
}
|
||||
err = err * info->dl >> 8;
|
||||
}
|
||||
write_pixels(dest_bitmap, y, out);
|
||||
|
||||
tmp = rows[0];
|
||||
rows[0] = rows[1];
|
||||
rows[1] = rows[2];
|
||||
rows[2] = tmp;
|
||||
|
||||
fill_row(source_bitmap, swap, rows[2], y + 2, info->mx);
|
||||
}
|
||||
|
||||
displayio_area_t a = { 0, 0, width, height };
|
||||
displayio_bitmap_set_dirty_area(dest_bitmap, &a);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user