bitmaptools: Add readinto
When reading uncompressed bitmap data directly, readinto can work much more quickly than a Python-coded loop. On a Raspberry Pi Pico, I benchmarked a modified version of adafruit_bitmap_font's pcf reader which uses readinto instead of the existing code. My test font was a 72-point file created from Arial. This decreased the time to load all the ASCII glyphs from 4.9 seconds to just 0.44 seconds. While this attempts to support many pixel configurations (1/2/4/8/16/24/32 bpp; swapped words and pixels) only the single combination used by PCF fonts was tested.
This commit is contained in:
parent
d2563c57de
commit
9133b23a37
@ -357,7 +357,83 @@ 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 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):
|
||||
//| """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_fill_region), MP_ROM_PTR(&bitmaptools_fill_region_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_draw_line), MP_ROM_PTR(&bitmaptools_draw_line_obj) },
|
||||
|
@ -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,
|
||||
@ -49,4 +51,6 @@ 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);
|
||||
|
||||
#endif // MICROPY_INCLUDED_SHARED_BINDINGS_BITMAPTOOLS__INIT__H
|
||||
|
@ -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"
|
||||
@ -336,3 +338,91 @@ 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_element, bool swap_bytes) {
|
||||
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(int i=0; i< rowsize_in_u16; i++) {
|
||||
rowdata16[i] = __builtin_bswap16(rowdata16[i]);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
for(int 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);
|
||||
}
|
||||
}
|
||||
|
||||
displayio_bitmap_set_dirty_area(self, 0, 0, self->width, self->height);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user