Commit 294627cf authored by schneider's avatar schneider
Browse files

feat(mp): Add png support

parent a8cdee86
Pipeline #5152 passed with stages
in 1 minute and 6 seconds
......@@ -124,6 +124,7 @@ autodoc_mock_imports = [
"sys_leds",
"sys_max30001",
"sys_max86150",
"sys_png",
"sys_config",
"ucollections",
"uerrno",
......
......@@ -36,6 +36,7 @@ Last but not least, if you want to start hacking the lower-level firmware, the
pycardium/light-sensor
pycardium/os
pycardium/personal_state
pycardium/png
pycardium/power
pycardium/pride
pycardium/simple_menu
......
``png`` - PNG Decoder
===============
The ``png`` module provides functions to decode PNG files into raw pixel data
which can be displayed using the card10's display or its LEDs.
.. automodule:: png
:members:
......@@ -21,6 +21,7 @@ modsrc = files(
'modules/sys_bme680.c',
'modules/sys_display.c',
'modules/sys_leds.c',
'modules/sys_png.c',
'modules/utime.c',
'modules/vibra.c',
'modules/ws2812.c',
......@@ -99,7 +100,7 @@ elf = executable(
mp_headers,
version_hdr,
include_directories: micropython_includes,
dependencies: [max32665_startup_core1, periphdriver, rd117, api_caller],
dependencies: [max32665_startup_core1, periphdriver, rd117, api_caller, lodepng],
link_with: upy,
link_whole: [max32665_startup_core1_lib, api_caller_lib],
link_args: [
......
......@@ -10,6 +10,7 @@ python_modules = files(
'leds.py',
'max30001.py',
'max86150.py',
'png.py',
'pride.py',
'simple_menu.py',
......
import sys_png
import color
def decode(png_data, format="RGB", bg=color.BLACK):
"""
Decode a PNG image and return raw pixel data.
:param str format: The intended output format:
- ``RGB``: 24 bit RGB.
- ``RGBA``: 24 bit RGB + 8 bit alpha.
- ``565``: 16 bit RGB. This consumes 1 byte less RAM per pixel than ``RGB``.
- ``565A``: 16 bit RGB + 8 bit alpha.
Default is ``RGB``.
:param Color bg: Background color.
If the PNG contains an alpha channel but no alpha
channel is requested in the output (``RGB`` or ``565``)
this color will be used as the background color.
Default is ``Color.BLACK``.
:returns: Typle ``(width, height, data)``
.. versionadded:: 1.17
**Example with RGB data:**
.. code-block:: python
import display
import png
# Draw a PNG file to the display
f = open("example.png")
w, h, img = png.decode(f.read())
f.close()
with display.open() as d:
d.clear()
d.blit(0, 0, w, h, img)
d.update()
**Example with rgb565 data:**
.. code-block:: python
import display
import png
# Draw a PNG file to the display
f = open("example.png")
w, h, img = png.decode(f.read(), "565")
f.close()
with display.open() as d:
d.clear()
d.blit(0, 0, w, h, img, True)
d.update()
"""
formats = ("RGB", "RGBA", "565", "565A")
if format not in formats:
raise ValueError("Supported formats: " + ",".join(formats))
if format == "RGB":
return sys_png.decode(png_data, 0, 0, bg)
if format == "RGBA":
return sys_png.decode(png_data, 0, 1, bg)
if format == "565":
return sys_png.decode(png_data, 1, 0, bg)
if format == "565A":
return sys_png.decode(png_data, 1, 1, bg)
......@@ -220,3 +220,7 @@ Q(send_report)
/* SpO2 */
Q(spo2_algo)
Q(maxim_rd117)
/* PNG */
Q(sys_png)
Q(decode)
#include "epicardium.h"
#define LODEPNG_NO_COMPILE_ENCODER
#define LODEPNG_NO_COMPILE_DISK
#define LODEPNG_NO_COMPILE_ALLOCATORS
#include "lodepng.h"
#include "py/builtin.h"
#include "py/binary.h"
#include "py/obj.h"
#include "py/objarray.h"
#include "py/runtime.h"
#include "py/gc.h"
void *lodepng_malloc(size_t size)
{
return m_malloc(size);
}
void *lodepng_realloc(void *ptr, size_t new_size)
{
return m_realloc(ptr, new_size);
}
void lodepng_free(void *ptr)
{
m_free(ptr);
}
static void lode_raise(unsigned int status)
{
if (status) {
nlr_raise(mp_obj_new_exception_msg_varg(
&mp_type_ValueError, lodepng_error_text(status))
);
}
}
static inline uint16_t rgb888_to_rgb565(uint8_t *bytes)
{
return ((bytes[0] & 0b11111000) << 8) | ((bytes[1] & 0b11111100) << 3) |
(bytes[2] >> 3);
}
static inline uint8_t apply_alpha(uint8_t in, uint8_t bg, uint8_t alpha)
{
/* Not sure if it is worth (or even a good idea) to have
* the special cases here. */
if (bg == 0) {
return (in * alpha) / 255;
}
uint8_t beta = 255 - alpha;
if (bg == 255) {
return ((in * alpha) / 255) + beta;
}
return (in * alpha + bg * beta) / 255;
}
static mp_obj_t mp_png_decode(size_t n_args, const mp_obj_t *args)
{
mp_buffer_info_t png_info;
mp_obj_t png = args[0];
mp_obj_t rgb565_out = args[1];
mp_obj_t alpha_out = args[2];
mp_obj_t bg = args[3];
/* Load buffer and ensure it contains enough data */
if (!mp_get_buffer(png, &png_info, MP_BUFFER_READ)) {
mp_raise_TypeError("png does not support buffer protocol.");
}
if (mp_obj_get_int(mp_obj_len(bg)) < 3) {
mp_raise_ValueError("bg must have 3 elements.");
}
int i, j;
unsigned int w, h;
uint8_t *raw;
int raw_len;
int raw_len_original;
LodePNGState state;
lodepng_state_init(&state);
state.decoder.ignore_crc = 1;
lode_raise(lodepng_inspect(&w, &h, &state, png_info.buf, png_info.len));
unsigned alpha_in = lodepng_can_have_alpha(&(state.info_png.color));
/* Do we need to consider an alpha channel? */
if (alpha_in || mp_obj_is_true(alpha_out)) {
lode_raise(lodepng_decode32(
&raw, &w, &h, png_info.buf, png_info.len)
);
raw_len = w * h * 4;
} else {
lode_raise(lodepng_decode24(
&raw, &w, &h, png_info.buf, png_info.len)
);
raw_len = w * h * 4;
}
raw_len_original = raw_len;
/* User did not ask for alpha, but input might contain alpha.
* Remove alpha using provided background color. */
if (alpha_in && !mp_obj_is_true(alpha_out)) {
uint8_t bg_red = mp_obj_get_int(
mp_obj_subscr(bg, mp_obj_new_int(0), MP_OBJ_SENTINEL)
);
uint8_t bg_green = mp_obj_get_int(
mp_obj_subscr(bg, mp_obj_new_int(1), MP_OBJ_SENTINEL)
);
uint8_t bg_blue = mp_obj_get_int(
mp_obj_subscr(bg, mp_obj_new_int(2), MP_OBJ_SENTINEL)
);
for (i = 0, j = 0; i < raw_len; i += 4, j += 3) {
uint8_t alpha = raw[i + 3];
raw[j] = apply_alpha(raw[i], bg_red, alpha);
raw[j + 1] = apply_alpha(raw[i + 1], bg_green, alpha);
raw[j + 2] = apply_alpha(raw[i + 2], bg_blue, alpha);
}
raw_len = w * h * 3;
}
if (mp_obj_is_true(rgb565_out)) {
if (mp_obj_is_true(alpha_out)) {
for (i = 0, j = 0; i < raw_len; i += 4, j += 3) {
uint16_t c = rgb888_to_rgb565(&raw[i]);
raw[j] = c & 0xFF;
raw[j + 1] = c >> 8;
raw[j + 2] = raw[i + 3];
}
raw_len = w * h * 3;
} else {
for (i = 0, j = 0; i < raw_len; i += 3, j += 2) {
uint16_t c = rgb888_to_rgb565(&raw[i]);
raw[j] = c & 0xFF;
raw[j + 1] = c >> 8;
}
raw_len = w * h * 2;
}
}
if (raw_len != raw_len_original) {
/* Some conversion shrank the buffer.
* Reallocate to free the unneeded RAM. */
m_realloc(raw, raw_len);
}
mp_obj_t mp_w = MP_OBJ_NEW_SMALL_INT(w);
mp_obj_t mp_h = MP_OBJ_NEW_SMALL_INT(h);
mp_obj_t mp_raw = mp_obj_new_memoryview(
MP_OBJ_ARRAY_TYPECODE_FLAG_RW | BYTEARRAY_TYPECODE,
raw_len,
raw
);
mp_obj_t tup[] = { mp_w, mp_h, mp_raw };
return mp_obj_new_tuple(3, tup);
}
static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(decode, 4, 4, mp_png_decode);
static const mp_rom_map_elem_t png_module_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_sys_png) },
{ MP_ROM_QSTR(MP_QSTR_decode), MP_ROM_PTR(&decode) },
};
static MP_DEFINE_CONST_DICT(png_module_globals, png_module_globals_table);
// Define module object.
const mp_obj_module_t png_module = {
.base = { &mp_type_module },
.globals = (mp_obj_dict_t *)&png_module_globals,
};
/* Register the module to make it available in Python */
/* clang-format off */
MP_REGISTER_MODULE(MP_QSTR_sys_png, png_module, MODULE_PNG_ENABLED);
......@@ -78,6 +78,7 @@ int mp_hal_csprng_read_int(void);
#define MODULE_WS2812_ENABLED (1)
#define MODULE_CONFIG_ENABLED (1)
#define MODULE_BLE_ENABLED (1)
#define MODULE_PNG_ENABLED (1)
#define MICROPY_BLUETOOTH_CARD10 (1)
/*
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment