From c0850943942d56130a68f118e50c50c951f44de3 Mon Sep 17 00:00:00 2001 From: Michal Moskal Date: Mon, 30 Oct 2017 12:45:37 +0000 Subject: [PATCH] Add screen::unpackPNG() --- libs/core/Makefile | 2 +- libs/core/_locales/core-jsdoc-strings.json | 1 + libs/core/png.cpp | 130 +++++++++++++++++++++ libs/core/pxt.json | 1 + libs/core/shims.d.ts | 6 + pxtarget.json | 2 +- 6 files changed, 140 insertions(+), 2 deletions(-) create mode 100644 libs/core/png.cpp diff --git a/libs/core/Makefile b/libs/core/Makefile index 34b2e643..c871a84a 100644 --- a/libs/core/Makefile +++ b/libs/core/Makefile @@ -20,7 +20,7 @@ DEPS = $(PXT_HEADERS) package.json Makefile Makefile.inc all: $(EXE) $(EXE): $(PXT_OBJS) - $(LD) -o $(EXE) $(LDFLAGS) -Wl,-Map,$(EXE:.elf=.map) $(PXT_OBJS) $(LIBSTDCPP) -lm -lpthread $(NPM_LIBS) + $(LD) -o $(EXE) $(LDFLAGS) -Wl,-Map,$(EXE:.elf=.map) $(PXT_OBJS) $(LIBSTDCPP) -lm -lpthread -lz $(NPM_LIBS) cp $(EXE) $(EXE:.elf=.full) $(PREF)strip $(EXE) node -p 'require("fs").readFileSync("$(EXE)").toString("hex")' > $(HEX) diff --git a/libs/core/_locales/core-jsdoc-strings.json b/libs/core/_locales/core-jsdoc-strings.json index c7c1731e..e3412e25 100644 --- a/libs/core/_locales/core-jsdoc-strings.json +++ b/libs/core/_locales/core-jsdoc-strings.json @@ -85,6 +85,7 @@ "screen.setPixel|param|on": "a value indicating if the pixel should be on or off", "screen.setPixel|param|x": "the starting position's x coordinate, eg: 0", "screen.setPixel|param|y": "the starting position's x coordinate, eg: 0", + "screen.unpackPNG": "Decompresses a 1-bit gray scale PNG image to icon format.", "serial": "Reading and writing data over a serial connection.", "serial.writeDmesg": "Send DMESG debug buffer over serial." } \ No newline at end of file diff --git a/libs/core/png.cpp b/libs/core/png.cpp new file mode 100644 index 00000000..6d78b771 --- /dev/null +++ b/libs/core/png.cpp @@ -0,0 +1,130 @@ +#include "pxt.h" +#include "ev3const.h" +#include +#include + +struct PNGHeader { + uint8_t pngHeader[8]; + uint32_t lenIHDR; + uint8_t IHDR[4]; + uint32_t width; + uint32_t height; + uint8_t bitDepth; + uint8_t colorType; + uint8_t compressionMethod; + uint8_t filterMethod; + uint8_t interlaceMethod; + uint32_t hdCRC; + uint32_t lenIDAT; + uint8_t IDAT[4]; +} __attribute__((packed)); + +namespace screen { + +static uint32_t swap(uint32_t num) { + return ((num >> 24) & 0xff) | ((num << 8) & 0xff0000) | ((num >> 8) & 0xff00) | + ((num << 24) & 0xff000000); +} + +static uint8_t revbits(uint8_t v) { + v = (v & 0xf0) >> 4 | (v & 0x0f) << 4; + v = (v & 0xcc) >> 2 | (v & 0x33) << 2; + v = (v & 0xaa) >> 1 | (v & 0x55) << 1; + return v; +} + +/** Decompresses a 1-bit gray scale PNG image to icon format. */ +//% +Buffer unpackPNG(Buffer png) { + if (!png) { + DMESG("PNG: Missing image"); + return NULL; + } + if (png->length < sizeof(PNGHeader) + 4) { + DMESG("PNG: File too small"); + return NULL; + } + + if (memcmp(png->data, "\x89PNG\r\n\x1A\n", 8) != 0) { + DMESG("PNG: Invalid header"); + return NULL; + } + + struct PNGHeader hd; + memcpy(&hd, png->data, sizeof(hd)); + + if (memcmp(hd.IHDR, "IHDR", 4) != 0) { + DMESG("PNG: missing IHDR"); + return NULL; + } + + hd.lenIHDR = swap(hd.lenIHDR); + hd.width = swap(hd.width); + hd.height = swap(hd.height); + hd.lenIDAT = swap(hd.lenIDAT); + + if (hd.lenIHDR != 13) { + DMESG("PNG: bad IHDR len"); + return NULL; + } + if (hd.bitDepth != 1 || hd.colorType != 0 || hd.compressionMethod != 0 || + hd.filterMethod != 0 || hd.interlaceMethod != 0) { + DMESG("PNG: not 1-bit grayscale"); + return NULL; + } + if (memcmp(hd.IDAT, "IDAT", 4) != 0) { + DMESG("PNG: missing IDAT"); + return NULL; + } + if (hd.lenIDAT + sizeof(hd) >= png->length) { + DMESG("PNG: buffer too short"); + return NULL; + } + if (hd.width > 300 || hd.height > 300) { + DMESG("PNG: too big"); + return NULL; + } + + uint32_t byteW = (hd.width + 7) >> 3; + uint32_t expSize = (byteW + 1) * hd.height; + unsigned long sz = expSize; + uint8_t *tmp = (uint8_t *)malloc(sz); + int code = uncompress(tmp, &sz, png->data + sizeof(hd), hd.lenIDAT); + if (code != 0) { + DMESG("PNG: zlib failed: %d", code); + free(tmp); + return NULL; + } + if (sz != expSize) { + DMESG("PNG: invalid compressed size"); + free(tmp); + return NULL; + } + + Buffer res = mkBuffer(NULL, 2 + byteW * hd.height); + res->data[0] = 0xf0; + res->data[1] = hd.width; + uint8_t *dst = res->data + 2; + uint8_t *src = tmp; + uint8_t lastMask = (1 << (hd.width & 7)) - 1; + if (lastMask == 0) + lastMask = 0xff; + for (uint32_t i = 0; i < hd.height; ++i) { + if (*src++ != 0) { + DMESG("PNG: unsupported filter"); + free(tmp); + decrRC(res); + return NULL; + } + for (uint32_t j = 0; j < byteW; ++j) { + *dst = ~revbits(*src++); + if (j == byteW - 1) { + *dst &= lastMask; + } + dst++; + } + } + free(tmp); + return res; +} +} diff --git a/libs/core/pxt.json b/libs/core/pxt.json index 50e20cee..22af1604 100644 --- a/libs/core/pxt.json +++ b/libs/core/pxt.json @@ -11,6 +11,7 @@ "mmap.cpp", "control.cpp", "buttons.ts", + "png.cpp", "screen.cpp", "screen.ts", "output.cpp", diff --git a/libs/core/shims.d.ts b/libs/core/shims.d.ts index 5d20f4f5..a15e3362 100644 --- a/libs/core/shims.d.ts +++ b/libs/core/shims.d.ts @@ -70,6 +70,12 @@ declare namespace serial { //% shim=serial::writeDmesg function writeDmesg(): void; } +declare namespace screen { + + /** Decompresses a 1-bit gray scale PNG image to icon format. */ + //% shim=screen::unpackPNG + function unpackPNG(png: Buffer): Buffer; +} declare namespace screen { /** Double size of an icon. */ diff --git a/pxtarget.json b/pxtarget.json index 85a0e8de..3c894543 100644 --- a/pxtarget.json +++ b/pxtarget.json @@ -62,7 +62,7 @@ }, "compileService": { "buildEngine": "dockermake", - "dockerImage": "pext/ev3", + "dockerImage": "pext/ev3:zlib", "serviceId": "ev3" }, "appTheme": {