Skip to content

Commit

Permalink
Merge pull request #4 from pimoroni/feature/pngdec
Browse files Browse the repository at this point in the history
PNG and PVS Support
  • Loading branch information
Gadgetoid authored Jul 19, 2023
2 parents 85b092e + 14746ae commit 483bb7d
Show file tree
Hide file tree
Showing 28 changed files with 9,340 additions and 64 deletions.
1 change: 1 addition & 0 deletions firmware/PIMORONI_PICOVISION/micropython.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ include(modules/picographics/micropython)

# Pico Graphics Extra
include(jpegdec/micropython)
include(modules/pngdec/micropython)
include(qrcode/micropython/micropython)

# Sensors & Breakouts
Expand Down
14 changes: 6 additions & 8 deletions modules/picographics/picographics.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,11 @@ MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(ModPicoGraphics_triangle_obj, 7, 7, ModPicoG
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(ModPicoGraphics_line_obj, 5, 6, ModPicoGraphics_line);

// Sprites
MP_DEFINE_CONST_FUN_OBJ_2(ModPicoGraphics_set_spritesheet_obj, ModPicoGraphics_set_spritesheet);
MP_DEFINE_CONST_FUN_OBJ_2(ModPicoGraphics_load_spritesheet_obj, ModPicoGraphics_load_spritesheet);
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(ModPicoGraphics_sprite_obj, 5, 7, ModPicoGraphics_sprite);
MP_DEFINE_CONST_FUN_OBJ_KW(ModPicoGraphics_load_sprite_obj, 3, ModPicoGraphics_load_sprite);
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(ModPicoGraphics_display_sprite_obj, 5, 5, ModPicoGraphics_display_sprite);
MP_DEFINE_CONST_FUN_OBJ_2(ModPicoGraphics_clear_sprite_obj, ModPicoGraphics_clear_sprite);

// Utility
//MP_DEFINE_CONST_FUN_OBJ_2(ModPicoGraphics_set_scanline_callback_obj, ModPicoGraphics_set_scanline_callback);
MP_DEFINE_CONST_FUN_OBJ_1(ModPicoGraphics_get_bounds_obj, ModPicoGraphics_get_bounds);
MP_DEFINE_CONST_FUN_OBJ_2(ModPicoGraphics_set_font_obj, ModPicoGraphics_set_font);

Expand Down Expand Up @@ -76,17 +75,16 @@ STATIC const mp_rom_map_elem_t ModPicoGraphics_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR_triangle), MP_ROM_PTR(&ModPicoGraphics_triangle_obj) },
{ MP_ROM_QSTR(MP_QSTR_line), MP_ROM_PTR(&ModPicoGraphics_line_obj) },

{ MP_ROM_QSTR(MP_QSTR_set_spritesheet), MP_ROM_PTR(&ModPicoGraphics_set_spritesheet_obj) },
{ MP_ROM_QSTR(MP_QSTR_load_spritesheet), MP_ROM_PTR(&ModPicoGraphics_load_spritesheet_obj) },
{ MP_ROM_QSTR(MP_QSTR_sprite), MP_ROM_PTR(&ModPicoGraphics_sprite_obj) },
{ MP_ROM_QSTR(MP_QSTR_load_sprite), MP_ROM_PTR(&ModPicoGraphics_load_sprite_obj) },
{ MP_ROM_QSTR(MP_QSTR_display_sprite), MP_ROM_PTR(&ModPicoGraphics_display_sprite_obj) },
{ MP_ROM_QSTR(MP_QSTR_clear_sprite), MP_ROM_PTR(&ModPicoGraphics_clear_sprite_obj) },

{ MP_ROM_QSTR(MP_QSTR_create_pen), MP_ROM_PTR(&ModPicoGraphics_create_pen_obj) },
{ MP_ROM_QSTR(MP_QSTR_create_pen_hsv), MP_ROM_PTR(&ModPicoGraphics_create_pen_hsv_obj) },
{ MP_ROM_QSTR(MP_QSTR_update_pen), MP_ROM_PTR(&ModPicoGraphics_update_pen_obj) },
{ MP_ROM_QSTR(MP_QSTR_reset_pen), MP_ROM_PTR(&ModPicoGraphics_reset_pen_obj) },
{ MP_ROM_QSTR(MP_QSTR_set_palette), MP_ROM_PTR(&ModPicoGraphics_set_palette_obj) },

//{ MP_ROM_QSTR(MP_QSTR_set_scanline_callback), MP_ROM_PTR(&ModPicoGraphics_set_scanline_callback_obj) },
{ MP_ROM_QSTR(MP_QSTR_get_bounds), MP_ROM_PTR(&ModPicoGraphics_get_bounds_obj) },
{ MP_ROM_QSTR(MP_QSTR_set_font), MP_ROM_PTR(&ModPicoGraphics_set_font_obj) },

Expand Down
205 changes: 153 additions & 52 deletions modules/picographics/picographics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
using namespace pimoroni;

extern "C" {
#include "lib/PNGdec.h"
#include "pngdec.h"
#include "picographics.h"
#include "pimoroni_i2c.h"
#include "py/stream.h"
Expand All @@ -27,9 +29,22 @@ static DVDisplay dv_display(&dv_i2c);
typedef struct _ModPicoGraphics_obj_t {
mp_obj_base_t base;
PicoGraphics *graphics;
void *spritedata;
DVDisplay *display;
} ModPicoGraphics_obj_t;

typedef struct _PNG_obj_t {
mp_obj_base_t base;
PNG *png;
void *dither_buffer;
mp_obj_t file;
mp_buffer_info_t buf;
PNG_DRAW_CALLBACK *decode_callback;
void *decode_target;
bool decode_into_buffer;
int width;
int height;
} _PNG_obj_t;

size_t get_required_buffer_size(PicoGraphicsPenType pen_type, uint width, uint height) {
switch(pen_type) {
case PEN_DV_RGB888:
Expand All @@ -43,6 +58,58 @@ size_t get_required_buffer_size(PicoGraphicsPenType pen_type, uint width, uint h
}
}

void PNGDrawSprite(PNGDRAW *pDraw) {
#ifdef MICROPY_EVENT_POLL_HOOK
MICROPY_EVENT_POLL_HOOK
#endif
// "pixel" is slow and clipped,
// guaranteeing we wont draw png data out of the framebuffer..
// Can we clip beforehand and make this faster?
int y = pDraw->y;

uint16_t *sprite_data = (uint16_t *)pDraw->pUser;
sprite_data += y * pDraw->iWidth;

// TODO we need to handle PicoGraphics palette pen types, plus grayscale and gray alpha PNG modes
// For DV P5 we could copy over the raw palette values (p & 0b00011111) and assume the user has prepped their palette accordingly
// also dither, maybe?

//mp_printf(&mp_plat_print, "Read sprite line at %d, %dbpp, type: %d, width: %d pitch: %d alpha: %d\n", y, pDraw->iBpp, pDraw->iPixelType, pDraw->iWidth, pDraw->iPitch, pDraw->iHasAlpha);
uint8_t *pixel = (uint8_t *)pDraw->pPixels;
if(pDraw->iPixelType == PNG_PIXEL_TRUECOLOR ) {
for(int x = 0; x < pDraw->iWidth; x++) {
uint16_t r = *pixel++;
uint16_t g = *pixel++;
uint16_t b = *pixel++;
*sprite_data++ = 0x8000 | ((r & 0xF8) << 7) | ((g & 0xF8) << 2) | (b >> 3);
}
} else if (pDraw->iPixelType == PNG_PIXEL_TRUECOLOR_ALPHA) {
for(int x = 0; x < pDraw->iWidth; x++) {
uint16_t r = *pixel++;
uint16_t g = *pixel++;
uint16_t b = *pixel++;
uint8_t a = *pixel++;
*sprite_data++ = (a ? 0x8000 : 0) | ((r & 0xF8) << 7) | ((g & 0xF8) << 2) | (b >> 3);
}
} else if (pDraw->iPixelType == PNG_PIXEL_INDEXED) {
for(int x = 0; x < pDraw->iWidth; x++) {
uint8_t i = 0;
if(pDraw->iBpp == 8) {
i = *pixel++;
} else {
i = pixel[x / 2];
i >>= (x & 0b1) ? 0 : 4;
i &= 0xf;
}
uint16_t r = pDraw->pPalette[(i * 3) + 0];
uint16_t g = pDraw->pPalette[(i * 3) + 1];
uint16_t b = pDraw->pPalette[(i * 3) + 2];
uint8_t a = pDraw->iHasAlpha ? pDraw->pPalette[768 + i] : 1;
*sprite_data++ = (a ? 0x8000 : 0) | ((r & 0xF8) << 7) | ((g & 0xF8) << 2) | (b >> 3);
}
}
}

mp_obj_t ModPicoGraphics_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) {
ModPicoGraphics_obj_t *self = nullptr;

Expand Down Expand Up @@ -90,7 +157,7 @@ mp_obj_t ModPicoGraphics_make_new(const mp_obj_type_t *type, size_t n_args, size
break;
}

self->spritedata = nullptr;
self->display = &dv_display;

// Clear each buffer
for(auto x = 0u; x < 2u; x++){
Expand Down Expand Up @@ -143,77 +210,111 @@ mp_obj_t ModPicoGraphics_set_scroll_index_for_lines(size_t n_args, const mp_obj_
return mp_const_none;
}

mp_obj_t ModPicoGraphics_load_sprite(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
enum { ARG_self, ARG_filename, ARG_index, ARG_scale, ARG_dither };
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ },
{ MP_QSTR_filename, MP_ARG_REQUIRED | MP_ARG_OBJ },
{ MP_QSTR_index, MP_ARG_REQUIRED | MP_ARG_INT },
{ MP_QSTR_scale, MP_ARG_INT, {.u_int = 0} },
{ MP_QSTR_dither, MP_ARG_OBJ, {.u_obj = mp_const_true} },
};

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

ModPicoGraphics_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self].u_obj, ModPicoGraphics_obj_t);

// Try loading the file/stream and checking if it's a PNG graphic
// if it's *not* a PNG then assume it's a raw PicoVision sprite.
const char PNG_HEADER[] = {137, 80, 78, 71, 13, 10, 26, 10};

// Is the supplied filename a string or bytearray
if(mp_obj_is_str_or_bytes(args[ARG_filename].u_obj)){
GET_STR_DATA_LEN(args[ARG_filename].u_obj, str, str_len);

// It's a file, try opening it
int32_t fsize;
mp_obj_t *fhandle = (mp_obj_t *)pngdec_open_callback((const char*)str, &fsize);
void *buf = m_malloc(fsize);
int error;

mp_stream_read_exactly(fhandle, buf, sizeof(PNG_HEADER), &error);

if (strncmp((const char *)buf, PNG_HEADER, sizeof(PNG_HEADER)) != 0) {
mp_printf(&mp_plat_print, "Not a PNG, loading as a PVS sprite.\n");
mp_stream_read_exactly(fhandle, (uint8_t *)buf + sizeof(PNG_HEADER), fsize - sizeof(PNG_HEADER), &error);
self->display->load_pvs_sprite(args[ARG_index].u_int, (uint32_t *)buf, fsize);

return mp_const_none;
}

mp_obj_t ModPicoGraphics_set_spritesheet(mp_obj_t self_in, mp_obj_t spritedata) {
ModPicoGraphics_obj_t *self = MP_OBJ_TO_PTR2(self_in, ModPicoGraphics_obj_t);
if(spritedata == mp_const_none) {
self->spritedata = nullptr;
} else {
// It might be a buffer...
mp_buffer_info_t bufinfo;
mp_get_buffer_raise(spritedata, &bufinfo, MP_BUFFER_RW);
mp_get_buffer_raise(args[ARG_filename].u_obj, &bufinfo, MP_BUFFER_READ);

int required_size = get_required_buffer_size((PicoGraphicsPenType)self->graphics->pen_type, 128, 128);
if (strncmp((const char *)bufinfo.buf, PNG_HEADER, sizeof(PNG_HEADER)) != 0) {
mp_printf(&mp_plat_print, "Not a PNG, loading as a PVS sprite.\n");
self->display->load_pvs_sprite(args[ARG_index].u_int , (uint32_t*)bufinfo.buf, bufinfo.len);
m_del(uint8_t, bufinfo.buf, bufinfo.len);

if(bufinfo.len != (size_t)(required_size)) {
mp_raise_ValueError("Spritesheet the wrong size!");
return mp_const_none;
}

self->spritedata = bufinfo.buf;
m_del(uint8_t, bufinfo.buf, bufinfo.len);
}
return mp_const_none;
}

mp_obj_t ModPicoGraphics_load_spritesheet(mp_obj_t self_in, mp_obj_t filename) {
ModPicoGraphics_obj_t *self = MP_OBJ_TO_PTR2(self_in, ModPicoGraphics_obj_t);
mp_obj_t args[2] = {
filename,
MP_OBJ_NEW_QSTR(MP_QSTR_r),
// Construct a pngdec object with our callback and a null buffer
// ask it to decode into a new, correctly-sized buffer
_PNG_obj_t *pngdec = m_new_obj_with_finaliser(_PNG_obj_t);
pngdec->base.type = &PNG_type;
pngdec->png = m_new_class(PNG);
pngdec->decode_callback = PNGDrawSprite;
pngdec->decode_target = nullptr;
pngdec->decode_into_buffer = true;
pngdec->width = 0;
pngdec->height = 0;

// Open the file, sets the width/height values
_PNG_openFILE(pngdec, args[ARG_filename].u_obj);

// Create a buffer big enough to hold the data decoded from PNG by our draw callback
pngdec->decode_target = m_malloc(pngdec->width * pngdec->height * sizeof(uint16_t));

mp_obj_t decode_args[] = {
pngdec,
mp_obj_new_int(0),
mp_obj_new_int(0),
mp_obj_new_int(args[ARG_scale].u_int),
mp_const_false
};
_PNG_decode(MP_ARRAY_SIZE(args), &decode_args[0], (mp_map_t *)&mp_const_empty_map);

// Stat the file to get its size
// example tuple response: (32768, 0, 0, 0, 0, 0, 5153, 1654709815, 1654709815, 1654709815)
mp_obj_t stat = mp_vfs_stat(filename);
mp_obj_tuple_t *tuple = MP_OBJ_TO_PTR2(stat, mp_obj_tuple_t);
size_t filesize = mp_obj_get_int(tuple->items[6]);

mp_buffer_info_t bufinfo;
bufinfo.buf = (void *)m_new(uint8_t, filesize);
mp_obj_t file = mp_vfs_open(MP_ARRAY_SIZE(args), &args[0], (mp_map_t *)&mp_const_empty_map);
int errcode;
bufinfo.len = mp_stream_rw(file, bufinfo.buf, filesize, &errcode, MP_STREAM_RW_READ | MP_STREAM_RW_ONCE);
if (errcode != 0) {
mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("Failed to open sprite file!"));
}
self->display->define_sprite(args[ARG_index].u_int, pngdec->width, pngdec->height, (uint16_t *)pngdec->decode_target);

self->spritedata = bufinfo.buf;
m_free(pngdec->decode_target);
m_free(pngdec->png);
m_free(pngdec);

return mp_const_none;
}

mp_obj_t ModPicoGraphics_sprite(size_t n_args, const mp_obj_t *args) {
enum { ARG_self, ARG_sprite_x, ARG_sprite_y, ARG_x, ARG_y, ARG_scale, ARG_transparent };
mp_obj_t ModPicoGraphics_display_sprite(size_t n_args, const mp_obj_t *args) {
enum { ARG_self, ARG_slot, ARG_sprite_index, ARG_x, ARG_y };

ModPicoGraphics_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self], ModPicoGraphics_obj_t);

if(self->spritedata == nullptr) return mp_const_false;

int scale = 1;
int transparent = 0;

if(n_args >= 6) scale = mp_obj_get_int(args[ARG_scale]);
if(n_args >= 7) transparent = mp_obj_get_int(args[ARG_transparent]);

self->graphics->sprite(
self->spritedata,
{mp_obj_get_int(args[ARG_sprite_x]), mp_obj_get_int(args[ARG_sprite_y])},
{mp_obj_get_int(args[ARG_x]), mp_obj_get_int(args[ARG_y])},
scale,
transparent
);
dv_display.set_sprite(mp_obj_get_int(args[ARG_slot]),
mp_obj_get_int(args[ARG_sprite_index]),
{mp_obj_get_int(args[ARG_x]), mp_obj_get_int(args[ARG_y])});

return mp_const_true;
}

mp_obj_t ModPicoGraphics_clear_sprite(mp_obj_t self_in, mp_obj_t slot) {
dv_display.clear_sprite(mp_obj_get_int(slot));
return mp_const_none;
}

mp_obj_t ModPicoGraphics_set_font(mp_obj_t self_in, mp_obj_t font) {
ModPicoGraphics_obj_t *self = MP_OBJ_TO_PTR2(self_in, ModPicoGraphics_obj_t);
self->graphics->set_font(mp_obj_to_string_r(font));
Expand Down
7 changes: 3 additions & 4 deletions modules/picographics/picographics.h
Original file line number Diff line number Diff line change
Expand Up @@ -103,12 +103,11 @@ extern mp_obj_t ModPicoGraphics_triangle(size_t n_args, const mp_obj_t *args);
extern mp_obj_t ModPicoGraphics_line(size_t n_args, const mp_obj_t *args);

// Sprites
extern mp_obj_t ModPicoGraphics_set_spritesheet(mp_obj_t self_in, mp_obj_t spritedata);
extern mp_obj_t ModPicoGraphics_load_spritesheet(mp_obj_t self_in, mp_obj_t filename);
extern mp_obj_t ModPicoGraphics_sprite(size_t n_args, const mp_obj_t *args);
extern mp_obj_t ModPicoGraphics_load_sprite(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args);
extern mp_obj_t ModPicoGraphics_display_sprite(size_t n_args, const mp_obj_t *args);
extern mp_obj_t ModPicoGraphics_clear_sprite(mp_obj_t self_in, mp_obj_t slot);

// Utility
//extern mp_obj_t ModPicoGraphics_set_scanline_callback(mp_obj_t self_in, mp_obj_t cb_in);
extern mp_obj_t ModPicoGraphics_set_font(mp_obj_t self_in, mp_obj_t font);
extern mp_obj_t ModPicoGraphics_get_bounds(mp_obj_t self_in);
extern mp_obj_t ModPicoGraphics_set_framebuffer(mp_obj_t self_in, mp_obj_t framebuffer);
Expand Down
Loading

0 comments on commit 483bb7d

Please sign in to comment.