Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bitmaptools: Add readinto, arrayblit #4403

Merged
merged 13 commits into from
Mar 17, 2021
18 changes: 17 additions & 1 deletion locale/circuitpython.pot
Original file line number Diff line number Diff line change
Expand Up @@ -3108,6 +3108,7 @@ msgstr ""

#: extmod/ulab/code/numerical/numerical.c
#: ports/esp32s2/common-hal/pulseio/PulseIn.c py/obj.c
#: shared-bindings/bitmaptools/__init__.c
msgid "index out of range"
msgstr ""

Expand Down Expand Up @@ -3212,6 +3213,11 @@ msgstr ""
msgid "invalid arguments"
msgstr ""

#: shared-bindings/bitmaptools/__init__.c
#, c-format
msgid "invalid bits_per_pixel %d, must be, 1, 4, 8, 16, 24, or 32"
msgstr ""

#: extmod/modussl_axtls.c
msgid "invalid cert"
msgstr ""
Expand All @@ -3220,6 +3226,16 @@ msgstr ""
msgid "invalid dupterm index"
msgstr ""

#: shared-bindings/bitmaptools/__init__.c
#, c-format
msgid "invalid element size %d for bits_per_pixel %d\n"
msgstr ""

#: shared-bindings/bitmaptools/__init__.c
#, c-format
msgid "invalid element_size %d, must be, 1, 2, or 4"
msgstr ""

#: extmod/modframebuf.c
msgid "invalid format"
msgstr ""
Expand Down Expand Up @@ -3678,7 +3694,7 @@ msgstr ""
msgid "parameters must be registers in sequence r0 to r3"
msgstr ""

#: shared-bindings/displayio/Bitmap.c
#: shared-bindings/bitmaptools/__init__.c shared-bindings/displayio/Bitmap.c
msgid "pixel coordinates out of bounds"
msgstr ""

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ EXTERNAL_FLASH_DEVICES = AT25DF081A
CIRCUITPY_AUDIOIO = 0
CIRCUITPY_AUDIOBUSIO = 0
CIRCUITPY_BITBANGIO = 0
CIRCUITPY_BITMAPTOOLS = 0
CIRCUITPY_COUNTIO = 0
CIRCUITPY_FREQUENCYIO = 0
CIRCUITPY_I2CPERIPHERAL = 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ CIRCUITPY_AUDIOBUSIO = 0
# No DAC on SAMR21G
CIRCUITPY_AUDIOIO = 0
CIRCUITPY_BITBANGIO = 0
CIRCUITPY_BITMAPTOOLS = 0
CIRCUITPY_COUNTIO = 0
CIRCUITPY_RTC = 0
CIRCUITPY_FREQUENCYIO = 0
Expand Down
156 changes: 156 additions & 0 deletions shared-bindings/bitmaptools/__init__.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

#include <stdint.h>

#include "py/binary.h"
#include "py/obj.h"
#include "py/runtime.h"

Expand Down Expand Up @@ -357,8 +358,163 @@ STATIC mp_obj_t bitmaptools_obj_draw_line(size_t n_args, const mp_obj_t *pos_arg
MP_DEFINE_CONST_FUN_OBJ_KW(bitmaptools_draw_line_obj, 0, bitmaptools_obj_draw_line);
// requires all 6 arguments

//| def arrayblit(bitmap: displayio.Bitmap, data: ReadableBuffer, x1: int=0, y1: int=0, x2: Optional[int]=None, y2: Optional[int]=None, skip_index:Optional[int]=None) -> None:
//| """Inserts pixels from ``data`` into the rectangle of width×height pixels with the upper left corner at ``(x,y)``
//|
//| The values from ``data`` are taken modulo the number of color values
//| avalable in the destination bitmap.
//|
//| If x1 or y1 are not specified, they are taken as 0. If x2 or y2
//| are not specified, or are given as -1, they are taken as the width
//| and height of the image.
//|
//| The coordinates affected by the blit are ``x1 <= x < x2`` and ``y1 <= y < y2``.
//|
//| ``data`` must contain at least as many elements as required. If it
//| contains excess elements, they are ignored.
//|
//| The blit takes place by rows, so the first elements of ``data`` go
//| to the first row, the next elements to the next row, and so on.
//|
//| :param displayio.Bitmap bitmap: A writable bitmap
//| :param ReadableBuffer data: Buffer containing the source pixel values
//| :param int x1: The left corner of the area to blit into (inclusive)
//| :param int y1: The top corner of the area to blit into (inclusive)
//| :param int x2: The right of the area to blit into (exclusive)
//| :param int y2: The bottom corner of the area to blit into (exclusive)
//| :param int skip_index: Bitmap palette index in the source that will not be copied,
//| set to None to copy all pixels
//| """
//| ...
//|
STATIC mp_obj_t bitmaptools_arrayblit(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
enum { ARG_bitmap, ARG_data, ARG_x1, ARG_y1, ARG_x2, ARG_y2, ARG_skip_index };
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_bitmap, MP_ARG_REQUIRED | MP_ARG_OBJ },
{ MP_QSTR_data, MP_ARG_REQUIRED | MP_ARG_OBJ },
{ MP_QSTR_x1, MP_ARG_INT, {.u_int = 0} },
{ MP_QSTR_y1, MP_ARG_INT, {.u_int = 0} },
{ MP_QSTR_x2, MP_ARG_INT, {.u_int = -1} },
{ MP_QSTR_y2, MP_ARG_INT, {.u_int = -1} },
{ MP_QSTR_skip_index, MP_ARG_OBJ, {.u_obj = mp_const_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);

if (!MP_OBJ_IS_TYPE(args[ARG_bitmap].u_obj, &displayio_bitmap_type)) {
mp_raise_TypeError(NULL);
}
displayio_bitmap_t *bitmap = MP_OBJ_TO_PTR(args[ARG_bitmap].u_obj);

mp_buffer_info_t bufinfo;
mp_get_buffer_raise(args[ARG_data].u_obj, &bufinfo, MP_BUFFER_READ);

int x1 = args[ARG_x1].u_int;
int y1 = args[ARG_y1].u_int;
int x2 = args[ARG_x2].u_int == -1 ? bitmap->width : args[ARG_x2].u_int;
int y2 = args[ARG_y2].u_int == -1 ? bitmap->height : args[ARG_y2].u_int;

if ((x1 < 0) || (y1 < 0) || (x1 > x2) || (y1 > y2) || (x2 > bitmap->width) || (y2 > bitmap->height)) {
mp_raise_IndexError(translate("pixel coordinates out of bounds"));
}

size_t output_element_count = (x2 - x1) * (y2 - y1);
size_t element_size = mp_binary_get_size('@', bufinfo.typecode, NULL);
size_t input_element_count = bufinfo.len / element_size;

bool skip_specified = args[ARG_skip_index].u_obj != mp_const_none;
uint32_t skip_index = skip_specified ? mp_obj_get_int(args[ARG_skip_index].u_obj) : 0;
if (input_element_count < output_element_count) {
mp_raise_IndexError(translate("index out of range"));
}

common_hal_bitmaptools_arrayblit(bitmap, bufinfo.buf, element_size, x1, y1, x2, y2, skip_specified, skip_index);

return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_KW(bitmaptools_arrayblit_obj, 0, bitmaptools_arrayblit);


//| def readinto(bitmap: displayio.Bitmap, file: typing.BinaryIO, bits_per_pixel: int, element_size: int = 1, reverse_pixels_in_element: bool = False, swap_bytes_in_element: bool = False) -> None:
//| """Read from a binary file into a bitmap
//| The file must be positioned so that it consists of ``bitmap.height`` rows of pixel data, where each row is the smallest multiple of ``element_size`` bytes that can hold ``bitmap.width`` pixels.
//|
//| The bytes in an element can be optionally swapped, and the pixels in an element can be reversed.
//|
//| This function doesn't parse image headers, but is useful to speed up loading of uncompressed image formats such as PCF glyph data.
//|
//| :param displayio.Bitmap bitmap: A writable bitmap
//| :param typing.BinaryIO file: A file opened in binary mode
//| :param int bits_per_pixel: Number of bits per pixel. Values 1, 2, 4, 8, 16, 24, and 32 are supported;
//| :param int element_size: Number of bytes per element. Values of 1, 2, and 4 are supported, except that 24 ``bits_per_pixel`` requires 1 byte per element.
//| :param bool reverse_pixels_in_element: If set, the first pixel in a word is taken from the Most Signficant Bits; otherwise, it is taken from the Least Significant Bits.
//| :param bool swap_bytes_in_element: If the ``element_size`` is not 1, then reverse the byte order of each element read.
//| """
//| ...
//|

STATIC mp_obj_t bitmaptools_readinto(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
enum { ARG_bitmap, ARG_file, ARG_bits_per_pixel, ARG_element_size, ARG_reverse_pixels_in_element, ARG_swap_bytes_in_element };
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_bitmap, MP_ARG_REQUIRED | MP_ARG_OBJ },
{ MP_QSTR_file, MP_ARG_REQUIRED | MP_ARG_OBJ },
{ MP_QSTR_bits_per_pixel, MP_ARG_REQUIRED | MP_ARG_INT },
{ MP_QSTR_element_size, MP_ARG_INT, { .u_int = 1 } },
{ MP_QSTR_reverse_pixels_in_element, MP_ARG_BOOL, { .u_bool = false } },
{ MP_QSTR_swap_bytes_in_element, MP_ARG_BOOL, { .u_bool = false } },
};

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);

if (!MP_OBJ_IS_TYPE(args[ARG_bitmap].u_obj, &displayio_bitmap_type)) {
mp_raise_TypeError(NULL);
}
displayio_bitmap_t *bitmap = MP_OBJ_TO_PTR(args[ARG_bitmap].u_obj);

if (!MP_OBJ_IS_TYPE(args[ARG_file].u_obj, &mp_type_fileio)) {
mp_raise_TypeError(NULL);
}
pyb_file_obj_t *file = MP_OBJ_TO_PTR(args[ARG_file].u_obj);

int element_size = args[ARG_element_size].u_int;
if (element_size != 1 && element_size != 2 && element_size != 4) {
mp_raise_ValueError_varg(translate("invalid element_size %d, must be, 1, 2, or 4"), element_size);
}

int bits_per_pixel = args[ARG_bits_per_pixel].u_int;
switch (bits_per_pixel) {
case 24:
if (element_size != 1) {
mp_raise_ValueError_varg(translate("invalid element size %d for bits_per_pixel %d\n"), element_size, bits_per_pixel);
}
break;
case 1:
case 2:
case 4:
case 8:
case 16:
case 32:
break;
default:
mp_raise_ValueError_varg(translate("invalid bits_per_pixel %d, must be, 1, 4, 8, 16, 24, or 32"), bits_per_pixel);
}

bool reverse_pixels_in_element = args[ARG_reverse_pixels_in_element].u_bool;
bool swap_bytes_in_element = args[ARG_swap_bytes_in_element].u_bool;

common_hal_bitmaptools_readinto(bitmap, file, element_size, bits_per_pixel, reverse_pixels_in_element, swap_bytes_in_element);

return mp_const_none;
}

MP_DEFINE_CONST_FUN_OBJ_KW(bitmaptools_readinto_obj, 0, bitmaptools_readinto);

STATIC const mp_rom_map_elem_t bitmaptools_module_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&bitmaptools_readinto_obj) },
{ MP_ROM_QSTR(MP_QSTR_rotozoom), MP_ROM_PTR(&bitmaptools_rotozoom_obj) },
{ MP_ROM_QSTR(MP_QSTR_arrayblit), MP_ROM_PTR(&bitmaptools_arrayblit_obj) },
{ MP_ROM_QSTR(MP_QSTR_fill_region), MP_ROM_PTR(&bitmaptools_fill_region_obj) },
{ MP_ROM_QSTR(MP_QSTR_draw_line), MP_ROM_PTR(&bitmaptools_draw_line_obj) },
};
Expand Down
5 changes: 5 additions & 0 deletions shared-bindings/bitmaptools/__init__.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@
#ifndef MICROPY_INCLUDED_SHARED_BINDINGS_BITMAPTOOLS__INIT__H
#define MICROPY_INCLUDED_SHARED_BINDINGS_BITMAPTOOLS__INIT__H

#include "shared-module/displayio/Bitmap.h"
#include "py/obj.h"
#include "extmod/vfs_fat.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,
Expand All @@ -49,4 +51,7 @@ void common_hal_bitmaptools_draw_line(displayio_bitmap_t *destination,
int16_t x1, int16_t y1,
uint32_t value);

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);
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);

#endif // MICROPY_INCLUDED_SHARED_BINDINGS_BITMAPTOOLS__INIT__H
115 changes: 115 additions & 0 deletions shared-module/bitmaptools/__init__.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,12 @@
*/


#include "shared-bindings/bitmaptools/__init__.h"
#include "shared-bindings/displayio/Bitmap.h"
#include "shared-module/displayio/Bitmap.h"

#include "py/runtime.h"
#include "py/mperrno.h"

#include "math.h"
#include "stdlib.h"
Expand Down Expand Up @@ -372,3 +374,116 @@ void common_hal_bitmaptools_draw_line(displayio_bitmap_t *destination,
}
}
}

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_value) {
uint32_t mask = (1 << common_hal_displayio_bitmap_get_bits_per_value(self)) - 1;

for (int y = y1; y < y2; y++) {
for (int x = x1; x < x2; x++) {
uint32_t value;
switch (element_size) {
default:
case 1:
value = *(uint8_t *)data;
data = (void *)((uint8_t *)data + 1);
break;
case 2:
value = *(uint16_t *)data;
data = (void *)((uint16_t *)data + 1);
break;
case 4:
value = *(uint32_t *)data;
data = (void *)((uint32_t *)data + 1);
break;
}
if (!skip_specified || value != skip_value) {
displayio_bitmap_write_pixel(self, x, y, value & mask);
}
}
}
}

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_element, bool swap_bytes) {
uint32_t mask = (1 << common_hal_displayio_bitmap_get_bits_per_value(self)) - 1;

if (self->read_only) {
mp_raise_RuntimeError(translate("Read-only object"));
}

size_t elements_per_row = (self->width * bits_per_pixel + element_size * 8 - 1) / (element_size * 8);
size_t rowsize = element_size * elements_per_row;
size_t rowsize_in_u32 = (rowsize + sizeof(uint32_t) - 1) / sizeof(uint32_t);
size_t rowsize_in_u16 = (rowsize + sizeof(uint16_t) - 1) / sizeof(uint16_t);
for (int y = 0; y < self->height; y++) {
uint32_t rowdata32[rowsize_in_u32];
uint16_t *rowdata16 = (uint16_t *)rowdata32;
uint8_t *rowdata8 = (uint8_t *)rowdata32;

UINT bytes_read = 0;
if (f_read(&file->fp, rowdata32, rowsize, &bytes_read) != FR_OK || bytes_read != rowsize) {
mp_raise_OSError(MP_EIO);
}

if (swap_bytes) {
switch (element_size) {
case 2:
for (size_t i = 0; i < rowsize_in_u16; i++) {
rowdata16[i] = __builtin_bswap16(rowdata16[i]);
}
break;
case 4:
for (size_t i = 0; i < rowsize_in_u32; i++) {
rowdata32[i] = __builtin_bswap32(rowdata32[i]);
}
default:
break;
}
}

for (int x = 0; x < self->width; x++) {
int value = 0;
switch (bits_per_pixel) {
case 1: {
int byte_offset = x / 8;
int bit_offset = reverse_pixels_in_element ? (7 - x % 8) : x % 8;

value = (rowdata8[byte_offset] >> bit_offset) & 1;
break;
}
case 2: {
int byte_offset = x / 4;
int bit_offset = 2 * (reverse_pixels_in_element ? (3 - x % 4) : x % 4);

value = (rowdata8[byte_offset] >> bit_offset) & 3;
break;
}
case 4: {
int byte_offset = x / 2;
int bit_offset = 4 * (reverse_pixels_in_element ? (1 - x % 2) : x % 2);

value = (rowdata8[byte_offset] >> bit_offset) & 7;
break;
}
case 8:
value = rowdata8[x];
break;

case 16:
value = rowdata16[x];
break;

case 24:
value = (rowdata8[x * 3] << 16) | (rowdata8[x * 3 + 1] << 8) | (rowdata8[x * 3 + 2] << 8);
break;

case 32:
value = rowdata32[x];
break;
}

displayio_bitmap_write_pixel(self, x, y, value & mask);
}
}

displayio_bitmap_set_dirty_area(self, 0, 0, self->width, self->height);
}