Png support start

This commit is contained in:
nitko12 2020-09-14 15:44:57 +02:00
parent 615f3b939c
commit cb5c74138b
15 changed files with 6513 additions and 16 deletions

View File

@ -109,18 +109,26 @@ char *months[] = {
// Out UI elements data
textElement elements[] = {
{50, 130, &Roboto_Light_160, currencyAbbr, 0}, {390, 80, &Roboto_Light_40, date, 0},
{190, 185, &Roboto_Light_40, fromToDate, 0}, {570, 140, &Roboto_Light_40, "Current price:", 0},
{790, 190, &Roboto_Light_40, current, 1}, {630, 275, &Roboto_Light_40, "Minimum:", 0},
{790, 320, &Roboto_Light_40, minimum, 1}, {625, 420, &Roboto_Light_40, "Maximum:", 0},
{50, 130, &Roboto_Light_160, currencyAbbr, 0},
{390, 80, &Roboto_Light_40, date, 0},
{190, 185, &Roboto_Light_40, fromToDate, 0},
{570, 140, &Roboto_Light_40, "Current price:", 0},
{790, 190, &Roboto_Light_40, current, 1},
{630, 275, &Roboto_Light_40, "Minimum:", 0},
{790, 320, &Roboto_Light_40, minimum, 1},
{625, 420, &Roboto_Light_40, "Maximum:", 0},
{790, 466, &Roboto_Light_40, maximum, 1},
{18, 570, &Roboto_Light_36, dates, 0}, {122, 570, &Roboto_Light_36, dates + 8, 0},
{227, 570, &Roboto_Light_36, dates + 16, 0}, {342, 570, &Roboto_Light_36, dates + 24, 0},
{18, 570, &Roboto_Light_36, dates, 0},
{122, 570, &Roboto_Light_36, dates + 8, 0},
{227, 570, &Roboto_Light_36, dates + 16, 0},
{342, 570, &Roboto_Light_36, dates + 24, 0},
{466, 570, &Roboto_Light_36, dates + 32, 0},
{450, 240, &Roboto_Light_36, prices, 0}, {450, 322, &Roboto_Light_36, prices + 16, 0},
{450, 401, &Roboto_Light_36, prices + 32, 0}, {450, 483, &Roboto_Light_36, prices + 48, 0},
{450, 240, &Roboto_Light_36, prices, 0},
{450, 322, &Roboto_Light_36, prices + 16, 0},
{450, 401, &Roboto_Light_36, prices + 32, 0},
{450, 483, &Roboto_Light_36, prices + 48, 0},
};
// Our functions declared below setup and loop

View File

@ -0,0 +1 @@
<mxfile host="app.diagrams.net" modified="2020-09-14T10:51:16.904Z" agent="5.0 (Macintosh)" etag="wPpNYQGGCjD756OMAaPR" version="13.6.10" type="device"><diagram id="oV9ivTpqu0Y3tAoBFJ3n" name="Page-1">7VxLc6M4EP41Pk4KEM9jHk4ys5spV3lrZ/a0xRgZ2GDECjk2++tXgHgJh8HBjkyGHFKo1RJS96dutVp4Bm43+wdsR94TcmAwUyRnPwN3M0WxVJ3+TwlJTtBMkBNc7Ds5Sa4IS/8/yIgSo259B8YNRoJQQPyoSVyhMIQr0qDZGKNdk22NguZbI9uFLcJyZQdt6jffIV5ONRWjoj9C3/WKN8u6ldds7IKZzST2bAftaiQwn4FbjBDJnzb7Wxiksivkkre7f6W2HBiGIenTIPQ2853+p6w+atZquX1Wf3uUPjHtxCQpJgwdOn9WRJh4yEWhHcwr6g1G29CBaa8SLVU8vyMUUaJMif9AQhKmTHtLECV5ZBOwWrj3yfe0+ZWmseJfWdEqind71ntWSGqFBcT+BhKIC1pIcJJ3phbFvDO5KFadZaWkXuK7y8WRyuBVKTNSjLZ4BTtEW6DVxi4kHXxKiQW6hiCio8EJbYdhYBP/pTkOm6HZLfkqhdMHpvMj9G9M+hetfyBS/6zfFzvYsjfNFD2gw71x/Bf66KaPn8PniA4GXnlFJX1Xrb6rySqKXmvEAy8IqFFPAUYtZZQSVwHa0sHf7DyfwGVkZ+LeUTfThBObAsQE7ru11pYyawA0NW/CvJRcGO1dZfNLmlez96Z0LsUo08o818pUeq5Mc+DKzJpeY2wnNYYI+SGJaz0vUkINisWOKSmgybl1jl82zC5++pCPoIJiOZUB6AQTOkWj0xoDOhVLBDrVCZ2i0SlLY4AnKPz3G/k1qZtflc0u/vPAH0ybeuHwB/oo4G8dCX+enz9y4OGvmF3854E/U1F3SLNMYgI3RwQ0rME4whmFM1OKJjqcKQ/BJpt0cpsE+rpkY6BNGoYAeUKAcARoIrySJnFeSe/2GrqqdPGfadPUx2tkKQ5/FY/ABajqpbkAs4+E53F0hFNOuWse+QfmeS9YQTLg9lKWaAVZfRT0tDpGQSn3WBVUrpiLUVCRE+3W0FdIdgg/3wY+negRumq2G6vWgMFF6EC01ooYaNr4nH7jk29o+iRZxWbZtD4Ld5kulrjXiq2BSf93m94guFmjkHyKMzhcUwYr2ld1fB8p75s7YcP8mYHI39FtNy5qRgsUJHSNcSH+oWmMyiBqVjO1Kd6NKYLPJ82GPSxKH8IcGqMwh4olFgBAnhAwOD8zCAFgyh+LhwAQkqLTuIMJ/cgcAsd/ptMgbcKneHyqY8CnZorAp+CYUjIa+FSHwDPHeoVPSxsNQIcmeYcFlUafoPLzxnb7Xdz8efCknSICO9hJPsp3DynPPKGbp0WPaPKSZ/BlMX84rJasevG1q/bOJx7EHy2eVo2Li6d75a6uHXuNtz75++H++8WfMTUGe+YldL5ZuOt9ytaQ9gcc/qiWL7+9E58fAL2yOn98iaB7WL5X9O+iZMzfktSF556B2pIx9V3ZZpC+wPND96IE2IpBxAtQ7wPSpXNvkx5WrcLt1SiNSOvasHgj0k7eO3Dth3k+6YJEJ/P2V7TkistGDWTzAgud6/Rb4kxSdhz7q9nZDm5mpwtkwSufiNRkrR0QdUE77pyl/emH0tS0zKswHz9r1XHCwl/EaHWUB+qtjk512KIe2l+/GSGDkw8fBiAyp9fym/+jASJxHUnvDJD27mIAQCy9ARDj1wUIf4exdLPHAgRwACmd0HsB5NDu6c0AGZq/nvDRMiDcfk7WT4UPWqx+4SNnr34mBcz/Bw==</diagram></mxfile>

View File

@ -27,6 +27,8 @@ bool Image::drawImage(const char *path, int x, int y, bool dither, bool invert)
return drawBitmapFromWeb(path, x, y, dither, invert);
if (strstr(path, ".jpg") != NULL || strstr(path, ".jpeg") != NULL)
return drawJpegFromWeb(path, x, y, dither, invert);
if (strstr(path, ".png") != NULL)
return drawJpegFromWeb(path, x, y, dither, invert);
}
else
{
@ -34,6 +36,8 @@ bool Image::drawImage(const char *path, int x, int y, bool dither, bool invert)
return drawBitmapFromSd(path, x, y, dither, invert);
if (strstr(path, ".jpg") != NULL || strstr(path, ".jpeg") != NULL)
return drawJpegFromSd(path, x, y, dither, invert);
if (strstr(path, ".png") != NULL)
return drawJpegFromSd(path, x, y, dither, invert);
}
return 0;
};

View File

@ -47,6 +47,11 @@ class Image : virtual public NetworkClient, virtual public Adafruit_GFX
bool drawJpegFromWeb(const char *url, int x, int y, bool dither = 0, bool invert = 0);
bool drawJpegFromWeb(WiFiClient *s, int x, int y, int32_t len, bool dither = 0, bool invert = 0);
bool drawPngFromSd(const char *fileName, int x, int y, bool dither = 0, bool invert = 0);
bool drawPngFromSd(SdFile *p, int x, int y, bool dither = 0, bool invert = 0);
bool drawPngFromWeb(const char *url, int x, int y, bool dither = 0, bool invert = 0);
bool drawPngFromWeb(WiFiClient *s, int x, int y, int32_t len, bool dither = 0, bool invert = 0);
private:
virtual void startWrite(void) = 0;
@ -92,5 +97,4 @@ class Image : virtual public NetworkClient, virtual public Adafruit_GFX
// -------------------------------------------
};
#endif

View File

@ -1 +1,23 @@
#include "Image.h"
#include "../libs/pngle/pngle.h"
bool Image::drawPngFromSd(const char *fileName, int x, int y, bool dither, bool invert)
{
return 0;
}
bool Image::drawPngFromSd(SdFile *p, int x, int y, bool dither, bool invert)
{
return 0;
}
bool Image::drawPngFromWeb(const char *url, int x, int y, bool dither, bool invert)
{
return 0;
}
bool Image::drawPngFromWeb(WiFiClient *s, int x, int y, int32_t len, bool dither, bool invert)
{
return 0;
}

21
src/libs/pngle/LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 kikuchan
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

112
src/libs/pngle/README.md Normal file
View File

@ -0,0 +1,112 @@
Pngle
=====
This is a stream based portable **PNG** **L**oader for **E**mbedding, **Pngle**.
## Background
Basically PNG is a memory consuming format from an embedded system perspective, especially on decoding, it mandatory require 32KiB working memory for a sliding window of Deflate, and a scanline buffer (its size depend on image width & format) to reconstruct the image, at least.
Unfortunately, due to the memory requirements, we could say traditional Arduino boards, that uses ATmega328 or ATmega32U4 for example, lack of ability to decode standard PNG images.
Today, we have variety of SoC boards for embedded systems or Arduino that have enough memory to decode PNG images, but we need to be concerned about memory consumption sometimes.
While there are many other PNG decoders, some of them always require the full size of frame-buffer, some of them don't but do require using complicated APIs instead, and some of them are hard to use in Arduino because of deep dependency.
This is why I reinvent the wheel for my own.
## Features & Restrictions
- **All standard types of PNG files are supported**
- Interlaced files are also supported
- Tested with PngSuite
- Easy to use
- **Simple API**
- Feed PNG binary data with `pngle_feed` API (in the same way as `write` syscall)
- A super simple single callback interface is used to draw an image
- You need to render pixel-by-pixel (it's neither line-by-line nor frame-by-frame)
- Pixel values are always normalized to 8bits/ch x 4ch for convenience (even if they are indexed, grayscaled, ... or 16bits/ch)
- Drawing x and y values are passed via the interface, so...
- You can roughly resize an image on-the-fly by adjusting drawing x and y values
- You can draw an interlaced image as soon as possible (x and y values occur in Adam7 sequence)
- Easy to embed
- **Reasonably small memory footprint** on runtime
- Theoretical minimum scanline buffer (depends on width & format) + decompression working memory for Deflate (~43KiB) + α
- **No frame-buffer required**
- It simply renders pixel-by-pixel instead, mentioned above
- If you prefer off-screen canvas, you can allocate the canvas by yourself and draw pixels to it
- **Less dependency**
- It only requires `miniz` (don't worry, battery included!)
- **Portable**
- Written in C99 with `stdint.h` (but not for `miniz` yet...)
- MIT License
- **Transparency support**
- A value of transparency is always available as 4th channel of pixel
- tRNS chunk is also supported
- If you need full featured alpha-blending, you can implement it easily (as long as you could know its background source color value, and how to blend them)
- **Gamma correction support** (gAMA chunk only)
- You can activate the feature by calling `pngle_set_display_gamma` API with display gamma value (Typically 2.2)
- It require additional memory (depends on image depth, 64KiB at most), math library (libm), and floating point arithmetic to generate gamma lookup table
- You can remove the feature by defining `PNGLE_NO_GAMMA_CORRECTION` in case of emergency
## Usage & How it works
1. Allocate Pngle object by calling `pngle_new()`
2. Setup draw callback by calling `pngle_set_draw_callback()`
3. Feed PNG binary data by calling `pngle_feed()` until exhausted
4. During the feeding, callback function `on_draw()` (for example) is called repeatedly
5. In the `on_draw()` function, put the pixel on a screen (or wherever you want)
6. Finally, you'll get an image
## Examples
### Generic C
```c
void on_draw(pngle_t *pngle, uint32_t x, uint32_t y, uint32_t w, uint32_t h, uint8_t rgba[4])
{
uint8_t r = rgba[0]; // 0 - 255
uint8_t g = rgba[1]; // 0 - 255
uint8_t b = rgba[2]; // 0 - 255
uint8_t a = rgba[3]; // 0: fully transparent, 255: fully opaque
if (a) printf("put pixel at (%d, %d) with color #%02x%02x%02x\n", x, y, r, g, b);
}
int main(int argc, char *argv[])
{
pngle_t *pngle = pngle_new();
pngle_set_draw_callback(pngle, on_draw);
// Feed data to pngle
char buf[1024];
int remain = 0;
int len;
while ((len = read(STDIN_FILENO, buf + remain, sizeof(buf) - remain)) > 0) {
int fed = pngle_feed(pngle, buf, remain + len);
if (fed < 0) errx(1, "%s", pngle_error(pngle));
remain = remain + len - fed;
if (remain > 0) memmove(buf, buf + fed, remain);
}
pngle_destroy(pngle);
return 0;
}
```
### Arduino (for M5Stack)
See [examples/m5stack-png.ino](examples/m5stack-png.ino)
## API
See [pngle.h](pngle.h)
## Author
kikuchan / @kikuchan98
## License
MIT License

View File

@ -0,0 +1,114 @@
#include <M5Stack.h>
#include <HTTPClient.h>
#include <math.h>
#include "pngle.h"
#define WIFI_SSID "XXXXXXXX"
#define WIFI_PASS "XXXXXXXX"
void cls()
{
M5.Lcd.fillScreen(TFT_BLACK);
M5.Lcd.setCursor(0, 0);
M5.Lcd.setTextColor(WHITE);
M5.Lcd.setTextSize(1);
}
// ===================================================
// pngle example for m5stack
// ===================================================
double g_scale = 1.0;
void pngle_on_draw(pngle_t *pngle, uint32_t x, uint32_t y, uint32_t w, uint32_t h, uint8_t rgba[4])
{
uint16_t color = (rgba[0] << 8 & 0xf800) | (rgba[1] << 3 & 0x07e0) | (rgba[2] >> 3 & 0x001f);
if (rgba[3]) {
x = ceil(x * g_scale);
y = ceil(y * g_scale);
w = ceil(w * g_scale);
h = ceil(h * g_scale);
M5.Lcd.fillRect(x, y, w, h, color);
}
}
void load_png(const char *url, double scale = 1.0)
{
HTTPClient http;
http.begin(url);
int httpCode = http.GET();
if (httpCode != HTTP_CODE_OK) {
M5.Lcd.printf("HTTP ERROR: %d\n", httpCode);
http.end();
return ;
}
WiFiClient *stream = http.getStreamPtr();
pngle_t *pngle = pngle_new();
pngle_set_draw_callback(pngle, pngle_on_draw);
g_scale = scale; // XXX:
uint8_t buf[2048];
int remain = 0;
while (http.connected()) {
size_t size = stream->available();
if (!size) { delay(1); continue; }
if (size > sizeof(buf) - remain) {
size = sizeof(buf) - remain;
}
int len = stream->readBytes(buf + remain, size);
if (len > 0) {
int fed = pngle_feed(pngle, buf, remain + len);
if (fed < 0) {
cls();
M5.Lcd.printf("ERROR: %s\n", pngle_error(pngle));
break;
}
remain = remain + len - fed;
if (remain > 0) memmove(buf, buf + fed, remain);
} else {
delay(1);
}
}
pngle_destroy(pngle);
http.end();
}
// ===================================================
void setup()
{
M5.begin();
M5.Lcd.printf("Welcome.\n");
WiFi.begin(WIFI_SSID, WIFI_PASS);
while (WiFi.status() != WL_CONNECTED) {
delay(100);
}
M5.Lcd.printf("WiFi connected.\n");
}
void loop()
{
M5.update();
if (M5.BtnA.wasReleased()) {
load_png("https://raw.githubusercontent.com/kikuchan/pngle/master/tests/pngsuite/PngSuite.png");
} else if (M5.BtnB.wasReleased()) {
load_png("https://raw.githubusercontent.com/kikuchan/pngle/master/tests/pngsuite/tbrn2c08.png", 7);
} else if (M5.BtnC.wasReleased()) {
load_png("https://avatars3.githubusercontent.com/u/17420673?s=240");
}
}

4931
src/libs/pngle/miniz.c Normal file

File diff suppressed because it is too large Load Diff

2
src/libs/pngle/miniz.h Normal file
View File

@ -0,0 +1,2 @@
#define MINIZ_HEADER_FILE_ONLY
#include "miniz.c"

874
src/libs/pngle/pngle.c Normal file
View File

@ -0,0 +1,874 @@
/*-
* MIT License
*
* Copyright (c) 2019 kikuchan
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <math.h>
#include "miniz.h"
#include "pngle.h"
#ifndef MIN
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#endif
#ifdef PNGLE_DEBUG
#define debug_printf(...) fprintf(stderr, __VA_ARGS__)
#else
#define debug_printf(...) ((void)0)
#endif
#define PNGLE_ERROR(s) (pngle->error = (s), pngle->state = PNGLE_STATE_ERROR, -1)
#define PNGLE_CALLOC(a, b, name) (debug_printf("[pngle] Allocating %zu bytes for %s\n", (size_t)(a) * (size_t)(b), (name)), calloc((size_t)(a), (size_t)(b)))
#define PNGLE_UNUSED(x) (void)(x)
typedef enum {
PNGLE_STATE_ERROR = -2,
PNGLE_STATE_EOF = -1,
PNGLE_STATE_INITIAL = 0,
PNGLE_STATE_FIND_CHUNK_HEADER,
PNGLE_STATE_HANDLE_CHUNK,
PNGLE_STATE_CRC,
} pngle_state_t;
typedef enum {
// Supported chunks
// Filter chunk names by following command to (re)generate hex constants;
// % perl -ne 'chomp; s/.*\s*\/\/\s*//; print "\tPNGLE_CHUNK_$_ = 0x" . unpack("H*") . "UL, // $_\n";'
PNGLE_CHUNK_IHDR = 0x49484452UL, // IHDR
PNGLE_CHUNK_PLTE = 0x504c5445UL, // PLTE
PNGLE_CHUNK_IDAT = 0x49444154UL, // IDAT
PNGLE_CHUNK_IEND = 0x49454e44UL, // IEND
PNGLE_CHUNK_tRNS = 0x74524e53UL, // tRNS
PNGLE_CHUNK_gAMA = 0x67414d41UL, // gAMA
} pngle_chunk_t;
// typedef struct _pngle_t pngle_t; // declared in pngle.h
struct _pngle_t {
pngle_ihdr_t hdr;
uint_fast8_t channels; // 0 indicates IHDR hasn't been processed yet
// PLTE chunk
size_t n_palettes;
uint8_t *palette;
// tRNS chunk
size_t n_trans_palettes;
uint8_t *trans_palette;
// parser state (reset on every chunk header)
pngle_state_t state;
uint32_t chunk_type;
uint32_t chunk_remain;
mz_ulong crc32;
// decompression state (reset on IHDR)
tinfl_decompressor inflator; // 11000 bytes
uint8_t lz_buf[TINFL_LZ_DICT_SIZE]; // 32768 bytes
uint8_t *next_out; // NULL indicates IDAT hasn't been processed yet
size_t avail_out;
// scanline decoder (reset on every set_interlace_pass() call)
uint8_t *scanline_ringbuf;
size_t scanline_ringbuf_size;
size_t scanline_ringbuf_cidx;
int_fast8_t scanline_remain_bytes_to_render;
int_fast8_t filter_type;
uint32_t drawing_x;
uint32_t drawing_y;
// interlace
uint_fast8_t interlace_pass;
const char *error;
#ifndef PNGLE_NO_GAMMA_CORRECTION
uint8_t *gamma_table;
double display_gamma;
#endif
pngle_init_callback_t init_callback;
pngle_draw_callback_t draw_callback;
pngle_done_callback_t done_callback;
void *user_data;
};
// magic
static const uint8_t png_sig[] = { 137, 80, 78, 71, 13, 10, 26, 10 };
static uint32_t interlace_off_x[8] = { 0, 0, 4, 0, 2, 0, 1, 0 };
static uint32_t interlace_off_y[8] = { 0, 0, 0, 4, 0, 2, 0, 1 };
static uint32_t interlace_div_x[8] = { 1, 8, 8, 4, 4, 2, 2, 1 };
static uint32_t interlace_div_y[8] = { 1, 8, 8, 8, 4, 4, 2, 2 };
static inline uint8_t read_uint8(const uint8_t *p)
{
return *p;
}
static inline uint32_t read_uint32(const uint8_t *p)
{
return (p[0] << 24)
| (p[1] << 16)
| (p[2] << 8)
| (p[3] << 0)
;
}
static inline uint32_t U32_CLAMP_ADD(uint32_t a, uint32_t b, uint32_t top)
{
uint32_t v = a + b;
if (v < a) return top; // uint32 overflow
if (v > top) return top; // clamp
return v;
}
void pngle_reset(pngle_t *pngle)
{
if (!pngle) return ;
pngle->state = PNGLE_STATE_INITIAL;
pngle->error = "No error";
if (pngle->scanline_ringbuf) free(pngle->scanline_ringbuf);
if (pngle->palette) free(pngle->palette);
if (pngle->trans_palette) free(pngle->trans_palette);
#ifndef PNGLE_NO_GAMMA_CORRECTION
if (pngle->gamma_table) free(pngle->gamma_table);
#endif
pngle->scanline_ringbuf = NULL;
pngle->palette = NULL;
pngle->trans_palette = NULL;
#ifndef PNGLE_NO_GAMMA_CORRECTION
pngle->gamma_table = NULL;
#endif
pngle->channels = 0; // indicates IHDR hasn't been processed yet
pngle->next_out = NULL; // indicates IDAT hasn't been processed yet
// clear them just in case...
memset(&pngle->hdr, 0, sizeof(pngle->hdr));
pngle->n_palettes = 0;
pngle->n_trans_palettes = 0;
tinfl_init(&pngle->inflator);
}
pngle_t *pngle_new()
{
pngle_t *pngle = (pngle_t *)PNGLE_CALLOC(1, sizeof(pngle_t), "pngle_t");
if (!pngle) return NULL;
pngle_reset(pngle);
return pngle;
}
void pngle_destroy(pngle_t *pngle)
{
if (pngle) {
pngle_reset(pngle);
free(pngle);
}
}
const char *pngle_error(pngle_t *pngle)
{
if (!pngle) return "Uninitialized";
return pngle->error;
}
uint32_t pngle_get_width(pngle_t *pngle)
{
if (!pngle) return 0;
return pngle->hdr.width;
}
uint32_t pngle_get_height(pngle_t *pngle)
{
if (!pngle) return 0;
return pngle->hdr.height;
}
pngle_ihdr_t *pngle_get_ihdr(pngle_t *pngle)
{
if (!pngle) return NULL;
if (pngle->channels == 0) return NULL;
return &pngle->hdr;
}
static int is_trans_color(pngle_t *pngle, uint16_t *value, size_t n)
{
if (pngle->n_trans_palettes != 1) return 0; // false (none or indexed)
for (size_t i = 0; i < n; i++) {
if (value[i] != (pngle->trans_palette[i * 2 + 0] * 0x100 + pngle->trans_palette[i * 2 + 1])) return 0; // false
}
return 1; // true
}
static inline void scanline_ringbuf_push(pngle_t *pngle, uint8_t value)
{
pngle->scanline_ringbuf[pngle->scanline_ringbuf_cidx] = value;
pngle->scanline_ringbuf_cidx = (pngle->scanline_ringbuf_cidx + 1) % pngle->scanline_ringbuf_size;
}
static inline uint16_t get_value(pngle_t *pngle, size_t *ridx, int *bitcount, int depth)
{
uint16_t v;
switch (depth) {
case 1:
case 2:
case 4:
if (*bitcount >= 8) {
*bitcount = 0;
*ridx = (*ridx + 1) % pngle->scanline_ringbuf_size;
}
*bitcount += depth;
uint8_t mask = ((1UL << depth) - 1);
uint8_t shift = (8 - *bitcount);
return (pngle->scanline_ringbuf[*ridx] >> shift) & mask;
case 8:
v = pngle->scanline_ringbuf[*ridx];
*ridx = (*ridx + 1) % pngle->scanline_ringbuf_size;
return v;
case 16:
v = pngle->scanline_ringbuf[*ridx];
*ridx = (*ridx + 1) % pngle->scanline_ringbuf_size;
v = v * 0x100 + pngle->scanline_ringbuf[*ridx];
*ridx = (*ridx + 1) % pngle->scanline_ringbuf_size;
return v;
}
return 0;
}
static int pngle_draw_pixels(pngle_t *pngle, size_t scanline_ringbuf_xidx)
{
uint16_t v[4]; // MAX_CHANNELS
int bitcount = 0;
uint8_t pixel_depth = (pngle->hdr.color_type & 1) ? 8 : pngle->hdr.depth;
uint16_t maxval = (1UL << pixel_depth) - 1;
int n_pixels = pngle->hdr.depth == 16 ? 1 : (8 / pngle->hdr.depth);
for (; n_pixels-- > 0 && pngle->drawing_x < pngle->hdr.width; pngle->drawing_x = U32_CLAMP_ADD(pngle->drawing_x, interlace_div_x[pngle->interlace_pass], pngle->hdr.width)) {
for (uint_fast8_t c = 0; c < pngle->channels; c++) {
v[c] = get_value(pngle, &scanline_ringbuf_xidx, &bitcount, pngle->hdr.depth);
}
// color type: 0000 0111
// ^-- indexed color (palette)
// ^--- Color
// ^---- Alpha channel
if (pngle->hdr.color_type & 2) {
// color
if (pngle->hdr.color_type & 1) {
// indexed color: type 3
// lookup palette info
uint16_t pidx = v[0];
if (pidx >= pngle->n_palettes) return PNGLE_ERROR("Color index is out of range");
v[0] = pngle->palette[pidx * 3 + 0];
v[1] = pngle->palette[pidx * 3 + 1];
v[2] = pngle->palette[pidx * 3 + 2];
// tRNS as an indexed alpha value table (for color type 3)
v[3] = pidx < pngle->n_trans_palettes ? pngle->trans_palette[pidx] : maxval;
} else {
// true color: 2, and 6
v[3] = (pngle->hdr.color_type & 4) ? v[3] : is_trans_color(pngle, v, 3) ? 0 : maxval;
}
} else {
// alpha, tRNS, or opaque
v[3] = (pngle->hdr.color_type & 4) ? v[1] : is_trans_color(pngle, v, 1) ? 0 : maxval;
// monochrome
v[1] = v[2] = v[0];
}
if (pngle->draw_callback) {
uint8_t rgba[4] = {
(v[0] * 255 + maxval / 2) / maxval,
(v[1] * 255 + maxval / 2) / maxval,
(v[2] * 255 + maxval / 2) / maxval,
(v[3] * 255 + maxval / 2) / maxval
};
#ifndef PNGLE_NO_GAMMA_CORRECTION
if (pngle->gamma_table) {
for (int i = 0; i < 3; i++) {
rgba[i] = pngle->gamma_table[v[i]];
}
}
#endif
pngle->draw_callback(pngle, pngle->drawing_x, pngle->drawing_y
, MIN(interlace_div_x[pngle->interlace_pass] - interlace_off_x[pngle->interlace_pass], pngle->hdr.width - pngle->drawing_x)
, MIN(interlace_div_y[pngle->interlace_pass] - interlace_off_y[pngle->interlace_pass], pngle->hdr.height - pngle->drawing_y)
, rgba
);
}
}
return 0;
}
static inline int paeth(int a, int b, int c)
{
int p = a + b - c;
int pa = abs(p - a);
int pb = abs(p - b);
int pc = abs(p - c);
if (pa <= pb && pa <= pc) return a;
if (pb <= pc) return b;
return c;
}
static int set_interlace_pass(pngle_t *pngle, uint_fast8_t pass)
{
pngle->interlace_pass = pass;
uint_fast8_t bytes_per_pixel = (pngle->channels * pngle->hdr.depth + 7) / 8; // 1 if depth <= 8
size_t scanline_pixels = (pngle->hdr.width - interlace_off_x[pngle->interlace_pass] + interlace_div_x[pngle->interlace_pass] - 1) / interlace_div_x[pngle->interlace_pass];
size_t scanline_stride = (scanline_pixels * pngle->channels * pngle->hdr.depth + 7) / 8;
pngle->scanline_ringbuf_size = scanline_stride + bytes_per_pixel * 2; // 2 rooms for c/x and a
if (pngle->scanline_ringbuf) free(pngle->scanline_ringbuf);
if ((pngle->scanline_ringbuf = PNGLE_CALLOC(pngle->scanline_ringbuf_size, 1, "scanline ringbuf")) == NULL) return PNGLE_ERROR("Insufficient memory");
pngle->drawing_x = interlace_off_x[pngle->interlace_pass];
pngle->drawing_y = interlace_off_y[pngle->interlace_pass];
pngle->filter_type = -1;
pngle->scanline_ringbuf_cidx = 0;
pngle->scanline_remain_bytes_to_render = -1;
return 0;
}
static int setup_gamma_table(pngle_t *pngle, uint32_t png_gamma)
{
#ifndef PNGLE_NO_GAMMA_CORRECTION
if (pngle->gamma_table) free(pngle->gamma_table);
if (pngle->display_gamma <= 0) return 0; // disable gamma correction
if (png_gamma == 0) return 0;
uint8_t pixel_depth = (pngle->hdr.color_type & 1) ? 8 : pngle->hdr.depth;
uint16_t maxval = (1UL << pixel_depth) - 1;
pngle->gamma_table = PNGLE_CALLOC(1, maxval + 1, "gamma table");
if (!pngle->gamma_table) return PNGLE_ERROR("Insufficient memory");
for (int i = 0; i < maxval + 1; i++) {
pngle->gamma_table[i] = (uint8_t)floor(pow(i / (double)maxval, 100000.0 / png_gamma / pngle->display_gamma) * 255.0 + 0.5);
}
debug_printf("[pngle] gamma value = %d\n", png_gamma);
#else
PNGLE_UNUSED(pngle);
PNGLE_UNUSED(png_gamma);
#endif
return 0;
}
static int pngle_on_data(pngle_t *pngle, const uint8_t *p, int len)
{
const uint8_t *ep = p + len;
uint_fast8_t bytes_per_pixel = (pngle->channels * pngle->hdr.depth + 7) / 8; // 1 if depth <= 8
while (p < ep) {
if (pngle->drawing_x >= pngle->hdr.width) {
// New row
pngle->drawing_x = interlace_off_x[pngle->interlace_pass];
pngle->drawing_y = U32_CLAMP_ADD(pngle->drawing_y, interlace_div_y[pngle->interlace_pass], pngle->hdr.height);
pngle->filter_type = -1; // Indicate new line
}
if (pngle->drawing_x >= pngle->hdr.width || pngle->drawing_y >= pngle->hdr.height) {
if (pngle->interlace_pass == 0 || pngle->interlace_pass >= 7) return len; // Do nothing further
// Interlace: Next pass
if (set_interlace_pass(pngle, pngle->interlace_pass + 1) < 0) return -1;
debug_printf("[pngle] interlace pass changed to: %d\n", pngle->interlace_pass);
continue; // This is required because "No filter type bytes are present in an empty pass".
}
if (pngle->filter_type < 0) {
if (*p > 4) {
debug_printf("[pngle] Invalid filter type is found; 0x%02x\n", *p);
return PNGLE_ERROR("Invalid filter type is found");
}
pngle->filter_type = (int_fast8_t)*p++; // 0 - 4
// push sentinel bytes for new line
for (uint_fast8_t i = 0; i < bytes_per_pixel; i++) {
scanline_ringbuf_push(pngle, 0);
}
continue;
}
size_t cidx = pngle->scanline_ringbuf_cidx;
size_t bidx = (pngle->scanline_ringbuf_cidx + bytes_per_pixel) % pngle->scanline_ringbuf_size;
size_t aidx = (pngle->scanline_ringbuf_cidx + pngle->scanline_ringbuf_size - bytes_per_pixel) % pngle->scanline_ringbuf_size;
// debug_printf("[pngle] cidx = %zd, bidx = %zd, aidx = %zd\n", cidx, bidx, aidx);
uint8_t c = pngle->scanline_ringbuf[cidx]; // left-up
uint8_t b = pngle->scanline_ringbuf[bidx]; // up
uint8_t a = pngle->scanline_ringbuf[aidx]; // left
uint8_t x = *p++; // target
// debug_printf("[pngle] c = 0x%02x, b = 0x%02x, a = 0x%02x, x = 0x%02x\n", c, b, a, x);
// Reverse the filter
switch (pngle->filter_type) {
case 0: break; // None
case 1: x += a; break; // Sub
case 2: x += b; break; // Up
case 3: x += (a + b) / 2; break; // Average
case 4: x += paeth(a, b, c); break; // Paeth
}
scanline_ringbuf_push(pngle, x); // updates scanline_ringbuf_cidx
if (pngle->scanline_remain_bytes_to_render < 0) pngle->scanline_remain_bytes_to_render = bytes_per_pixel;
if (--pngle->scanline_remain_bytes_to_render == 0) {
size_t xidx = (pngle->scanline_ringbuf_cidx + pngle->scanline_ringbuf_size - bytes_per_pixel) % pngle->scanline_ringbuf_size;
if (pngle_draw_pixels(pngle, xidx) < 0) return -1;
pngle->scanline_remain_bytes_to_render = -1; // reset
}
}
return len;
}
static int pngle_handle_chunk(pngle_t *pngle, const uint8_t *buf, size_t len)
{
size_t consume = 0;
switch (pngle->chunk_type) {
case PNGLE_CHUNK_IHDR:
// parse IHDR
consume = 13;
if (len < consume) return 0;
debug_printf("[pngle] Parse IHDR\n");
pngle->hdr.width = read_uint32(buf + 0);
pngle->hdr.height = read_uint32(buf + 4);
pngle->hdr.depth = read_uint8 (buf + 8);
pngle->hdr.color_type = read_uint8 (buf + 9);
pngle->hdr.compression = read_uint8 (buf + 10);
pngle->hdr.filter = read_uint8 (buf + 11);
pngle->hdr.interlace = read_uint8 (buf + 12);
debug_printf("[pngle] width : %d\n", pngle->hdr.width );
debug_printf("[pngle] height : %d\n", pngle->hdr.height );
debug_printf("[pngle] depth : %d\n", pngle->hdr.depth );
debug_printf("[pngle] color_type : %d\n", pngle->hdr.color_type );
debug_printf("[pngle] compression: %d\n", pngle->hdr.compression);
debug_printf("[pngle] filter : %d\n", pngle->hdr.filter );
debug_printf("[pngle] interlace : %d\n", pngle->hdr.interlace );
/*
Color Allowed Interpretation channels
Type Bit Depths
0 1,2,4,8,16 Each pixel is a grayscale sample. 1 channels (Brightness)
2 8,16 Each pixel is an R,G,B triple. 3 channels (R, G, B)
3 1,2,4,8 Each pixel is a palette index; 1 channels (palette info)
a PLTE chunk must appear.
4 8,16 Each pixel is a grayscale sample, 2 channels (Brightness, Alpha)
followed by an alpha sample.
6 8,16 Each pixel is an R,G,B triple, 4 channels (R, G, B, Alpha)
followed by an alpha sample.
*/
// 111
// ^-- indexed color (palette)
// ^--- Color
// ^---- Alpha channel
switch (pngle->hdr.color_type) {
case 0: pngle->channels = 1; if (pngle->hdr.depth != 1 && pngle->hdr.depth != 2 && pngle->hdr.depth != 4 && pngle->hdr.depth != 8 && pngle->hdr.depth != 16) return PNGLE_ERROR("Invalid bit depth"); break; // grayscale
case 2: pngle->channels = 3; if ( pngle->hdr.depth != 8 && pngle->hdr.depth != 16) return PNGLE_ERROR("Invalid bit depth"); break; // truecolor
case 3: pngle->channels = 1; if (pngle->hdr.depth != 1 && pngle->hdr.depth != 2 && pngle->hdr.depth != 4 && pngle->hdr.depth != 8 ) return PNGLE_ERROR("Invalid bit depth"); break; // indexed color
case 4: pngle->channels = 2; if ( pngle->hdr.depth != 8 && pngle->hdr.depth != 16) return PNGLE_ERROR("Invalid bit depth"); break; // grayscale + alpha
case 6: pngle->channels = 4; if ( pngle->hdr.depth != 8 && pngle->hdr.depth != 16) return PNGLE_ERROR("Invalid bit depth"); break; // truecolor + alpha
default:
return PNGLE_ERROR("Incorrect IHDR info");
}
if (pngle->hdr.compression != 0) return PNGLE_ERROR("Unsupported compression type in IHDR");
if (pngle->hdr.filter != 0) return PNGLE_ERROR("Unsupported filter type in IHDR");
// interlace
if (set_interlace_pass(pngle, pngle->hdr.interlace ? 1 : 0) < 0) return -1;
// callback
if (pngle->init_callback) pngle->init_callback(pngle, pngle->hdr.width, pngle->hdr.height);
break;
case PNGLE_CHUNK_IDAT:
// parse & decode IDAT chunk
if (len < 1) return 0;
debug_printf("[pngle] Reading IDAT (len %zd / chunk remain %u)\n", len, pngle->chunk_remain);
size_t in_bytes = len;
size_t out_bytes = pngle->avail_out;
//debug_printf("[pngle] in_bytes %zd, out_bytes %zd, next_out %p\n", in_bytes, out_bytes, pngle->next_out);
// XXX: tinfl_decompress always requires (next_out - lz_buf + avail_out) == TINFL_LZ_DICT_SIZE
tinfl_status status = tinfl_decompress(&pngle->inflator, (const mz_uint8 *)buf, &in_bytes, pngle->lz_buf, (mz_uint8 *)pngle->next_out, &out_bytes, TINFL_FLAG_HAS_MORE_INPUT | TINFL_FLAG_PARSE_ZLIB_HEADER);
//debug_printf("[pngle] tinfl_decompress\n");
//debug_printf("[pngle] => in_bytes %zd, out_bytes %zd, next_out %p, status %d\n", in_bytes, out_bytes, pngle->next_out, status);
if (status < TINFL_STATUS_DONE) {
// Decompression failed.
debug_printf("[pngle] tinfl_decompress() failed with status %d!\n", status);
return PNGLE_ERROR("Failed to decompress the IDAT stream");
}
pngle->next_out += out_bytes;
pngle->avail_out -= out_bytes;
// debug_printf("[pngle] => avail_out %zd, next_out %p\n", pngle->avail_out, pngle->next_out);
if (status == TINFL_STATUS_DONE || pngle->avail_out == 0) {
// Output buffer is full, or decompression is done, so write buffer to output file.
// XXX: This is the only chance to process the buffer.
uint8_t *read_ptr = pngle->lz_buf;
size_t n = TINFL_LZ_DICT_SIZE - (size_t)pngle->avail_out;
// pngle_on_data() usually returns n, otherwise -1 on error
if (pngle_on_data(pngle, read_ptr, n) < 0) return -1;
// XXX: tinfl_decompress always requires (next_out - lz_buf + avail_out) == TINFL_LZ_DICT_SIZE
pngle->next_out = pngle->lz_buf;
pngle->avail_out = TINFL_LZ_DICT_SIZE;
}
consume = in_bytes;
break;
case PNGLE_CHUNK_PLTE:
consume = 3;
if (len < consume) return 0;
memcpy(pngle->palette + pngle->n_palettes * 3, buf, 3);
debug_printf("[pngle] PLTE[%zd]: (%d, %d, %d)\n"
, pngle->n_palettes
, pngle->palette[pngle->n_palettes * 3 + 0]
, pngle->palette[pngle->n_palettes * 3 + 1]
, pngle->palette[pngle->n_palettes * 3 + 2]
);
pngle->n_palettes++;
break;
case PNGLE_CHUNK_IEND:
consume = 0;
break;
case PNGLE_CHUNK_tRNS:
switch (pngle->hdr.color_type) {
case 3: consume = 1; break;
case 0: consume = 2 * 1; break;
case 2: consume = 2 * 3; break;
default:
return PNGLE_ERROR("tRNS chunk is prohibited on the color type");
}
if (len < consume) return 0;
memcpy(pngle->trans_palette + pngle->n_trans_palettes, buf, consume);
pngle->n_trans_palettes++;
break;
case PNGLE_CHUNK_gAMA:
consume = 4;
if (len < consume) return 0;
if (setup_gamma_table(pngle, read_uint32(buf)) < 0) return -1;
break;
default:
// unknown chunk
consume = len;
debug_printf("[pngle] Unknown chunk; %zd bytes discarded\n", consume);
break;
}
return consume;
}
static int pngle_feed_internal(pngle_t *pngle, const uint8_t *buf, size_t len)
{
if (!pngle) return -1;
switch (pngle->state) {
case PNGLE_STATE_ERROR:
return -1;
case PNGLE_STATE_EOF:
return len;
case PNGLE_STATE_INITIAL:
// find PNG header
if (len < sizeof(png_sig)) return 0;
if (memcmp(png_sig, buf, sizeof(png_sig))) return PNGLE_ERROR("Incorrect PNG signature");
debug_printf("[pngle] PNG signature found\n");
pngle->state = PNGLE_STATE_FIND_CHUNK_HEADER;
return sizeof(png_sig);
case PNGLE_STATE_FIND_CHUNK_HEADER:
if (len < 8) return 0;
pngle->chunk_remain = read_uint32(buf);
pngle->chunk_type = read_uint32(buf + 4);
pngle->crc32 = mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)(buf + 4), 4);
debug_printf("[pngle] Chunk '%.4s' len %u\n", buf + 4, pngle->chunk_remain);
pngle->state = PNGLE_STATE_HANDLE_CHUNK;
// initialize & sanity check
switch (pngle->chunk_type) {
case PNGLE_CHUNK_IHDR:
if (pngle->chunk_remain != 13) return PNGLE_ERROR("Invalid IHDR chunk size");
if (pngle->channels != 0) return PNGLE_ERROR("Multiple IHDR chunks are not allowed");
break;
case PNGLE_CHUNK_IDAT:
if (pngle->chunk_remain <= 0) return PNGLE_ERROR("Invalid IDAT chunk size");
if (pngle->channels == 0) return PNGLE_ERROR("No IHDR chunk is found");
if (pngle->hdr.color_type == 3 && pngle->palette == NULL) return PNGLE_ERROR("No PLTE chunk is found");
if (pngle->next_out == NULL) {
// Very first IDAT
pngle->next_out = pngle->lz_buf;
pngle->avail_out = TINFL_LZ_DICT_SIZE;
}
break;
case PNGLE_CHUNK_PLTE:
if (pngle->chunk_remain <= 0) return PNGLE_ERROR("Invalid PLTE chunk size");
if (pngle->channels == 0) return PNGLE_ERROR("No IHDR chunk is found");
if (pngle->palette) return PNGLE_ERROR("Too many PLTE chunk");
switch (pngle->hdr.color_type) {
case 3: // indexed color
break;
case 2: // truecolor
case 6: // truecolor + alpha
// suggested palettes
break;
default:
return PNGLE_ERROR("PLTE chunk is prohibited on the color type");
}
if (pngle->chunk_remain % 3) return PNGLE_ERROR("Invalid PLTE chunk size");
if (pngle->chunk_remain / 3 > MIN(256, (1UL << pngle->hdr.depth))) return PNGLE_ERROR("Too many palettes in PLTE");
if ((pngle->palette = PNGLE_CALLOC(pngle->chunk_remain / 3, 3, "palette")) == NULL) return PNGLE_ERROR("Insufficient memory");
pngle->n_palettes = 0;
break;
case PNGLE_CHUNK_IEND:
if (pngle->next_out == NULL) return PNGLE_ERROR("No IDAT chunk is found");
if (pngle->chunk_remain > 0) return PNGLE_ERROR("Invalid IEND chunk size");
break;
case PNGLE_CHUNK_tRNS:
if (pngle->chunk_remain <= 0) return PNGLE_ERROR("Invalid tRNS chunk size");
if (pngle->channels == 0) return PNGLE_ERROR("No IHDR chunk is found");
if (pngle->trans_palette) return PNGLE_ERROR("Too many tRNS chunk");
switch (pngle->hdr.color_type) {
case 3: // indexed color
if (pngle->chunk_remain > (1UL << pngle->hdr.depth)) return PNGLE_ERROR("Too many palettes in tRNS");
break;
case 0: // grayscale
if (pngle->chunk_remain != 2) return PNGLE_ERROR("Invalid tRNS chunk size");
break;
case 2: // truecolor
if (pngle->chunk_remain != 6) return PNGLE_ERROR("Invalid tRNS chunk size");
break;
default:
return PNGLE_ERROR("tRNS chunk is prohibited on the color type");
}
if ((pngle->trans_palette = PNGLE_CALLOC(pngle->chunk_remain, 1, "trans palette")) == NULL) return PNGLE_ERROR("Insufficient memory");
pngle->n_trans_palettes = 0;
break;
default:
break;
}
return 8;
case PNGLE_STATE_HANDLE_CHUNK:
len = MIN(len, pngle->chunk_remain);
int consumed = pngle_handle_chunk(pngle, buf, len);
if (consumed > 0) {
if (pngle->chunk_remain < (uint32_t)consumed) return PNGLE_ERROR("Chunk data has been consumed too much");
pngle->chunk_remain -= consumed;
pngle->crc32 = mz_crc32(pngle->crc32, (const mz_uint8 *)buf, consumed);
}
if (pngle->chunk_remain <= 0) pngle->state = PNGLE_STATE_CRC;
return consumed;
case PNGLE_STATE_CRC:
if (len < 4) return 0;
uint32_t crc32 = read_uint32(buf);
if (crc32 != pngle->crc32) {
debug_printf("[pngle] CRC: %08x vs %08x => NG\n", crc32, (uint32_t)pngle->crc32);
return PNGLE_ERROR("CRC mismatch");
}
debug_printf("[pngle] CRC: %08x vs %08x => OK\n", crc32, (uint32_t)pngle->crc32);
pngle->state = PNGLE_STATE_FIND_CHUNK_HEADER;
// XXX:
if (pngle->chunk_type == PNGLE_CHUNK_IEND) {
pngle->state = PNGLE_STATE_EOF;
if (pngle->done_callback) pngle->done_callback(pngle);
debug_printf("[pngle] DONE\n");
}
return 4;
default:
break;
}
return PNGLE_ERROR("Invalid state");
}
int pngle_feed(pngle_t *pngle, const void *buf, size_t len)
{
size_t pos = 0;
pngle_state_t last_state = pngle->state;
while (pos < len) {
int r = pngle_feed_internal(pngle, (const uint8_t *)buf + pos, len - pos);
if (r < 0) return r; // error
if (r == 0 && last_state == pngle->state) break;
last_state = pngle->state;
pos += r;
}
return pos;
}
void pngle_set_display_gamma(pngle_t *pngle, double display_gamma)
{
if (!pngle) return ;
#ifndef PNGLE_NO_GAMMA_CORRECTION
pngle->display_gamma = display_gamma;
#else
PNGLE_UNUSED(display_gamma);
#endif
}
void pngle_set_init_callback(pngle_t *pngle, pngle_init_callback_t callback)
{
if (!pngle) return ;
pngle->init_callback = callback;
}
void pngle_set_draw_callback(pngle_t *pngle, pngle_draw_callback_t callback)
{
if (!pngle) return ;
pngle->draw_callback = callback;
}
void pngle_set_done_callback(pngle_t *pngle, pngle_done_callback_t callback)
{
if (!pngle) return ;
pngle->done_callback = callback;
}
void pngle_set_user_data(pngle_t *pngle, void *user_data)
{
if (!pngle) return ;
pngle->user_data = user_data;
}
void *pngle_get_user_data(pngle_t *pngle)
{
if (!pngle) return NULL;
return pngle->user_data;
}
/* vim: set ts=4 sw=4 noexpandtab: */

86
src/libs/pngle/pngle.h Normal file
View File

@ -0,0 +1,86 @@
/*-
* MIT License
*
* Copyright (c) 2019 kikuchan
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef __PNGLE_H__
#define __PNGLE_H__
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
// Main Pngle object
typedef struct _pngle_t pngle_t;
// Callback signatures
typedef void (*pngle_init_callback_t)(pngle_t *pngle, uint32_t w, uint32_t h);
typedef void (*pngle_draw_callback_t)(pngle_t *pngle, uint32_t x, uint32_t y, uint32_t w, uint32_t h, uint8_t rgba[4]);
typedef void (*pngle_done_callback_t)(pngle_t *pngle);
// ----------------
// Basic interfaces
// ----------------
pngle_t *pngle_new();
void pngle_destroy(pngle_t *pngle);
void pngle_reset(pngle_t *pngle); // clear its internal state (not applied to pngle_set_* functions)
const char *pngle_error(pngle_t *pngle);
int pngle_feed(pngle_t *pngle, const void *buf, size_t len); // returns -1: On error, 0: Need more data, n: n bytes eaten
uint32_t pngle_get_width(pngle_t *pngle);
uint32_t pngle_get_height(pngle_t *pngle);
void pngle_set_init_callback(pngle_t *png, pngle_init_callback_t callback);
void pngle_set_draw_callback(pngle_t *png, pngle_draw_callback_t callback);
void pngle_set_done_callback(pngle_t *png, pngle_done_callback_t callback);
void pngle_set_display_gamma(pngle_t *pngle, double display_gamma); // enables gamma correction by specifying display gamma, typically 2.2. No effect when gAMA chunk is missing
void pngle_set_user_data(pngle_t *pngle, void *user_data);
void *pngle_get_user_data(pngle_t *pngle);
// ----------------
// Debug interfaces
// ----------------
typedef struct _pngle_ihdr_t {
uint32_t width;
uint32_t height;
uint8_t depth;
uint8_t color_type;
uint8_t compression;
uint8_t filter;
uint8_t interlace;
} pngle_ihdr_t;
// Get IHDR information
pngle_ihdr_t *pngle_get_ihdr(pngle_t *pngle);
#ifdef __cplusplus
}
#endif
#endif /* __PNGLE_H__ */

View File

@ -1 +1,19 @@
#include "Inkplate.h"
#include "generatedUI.h"
Inkplate display(INKPLATE_3BIT);
void setup()
{
display.begin();
mainDraw();
int x[] = {600, 700, 800, 700, 600}, y[] = {200, 100, 400, 500, 300};
int x2[] = {600, 700, 800, 700, 600}, y2[] = {400, 300, 600, 600, 500};
display.drawPolygon(x, y, 5, 0);
display.fillPolygon(x2, y2, 5, 0);
display.display();
}
void loop()
{
}

View File

@ -0,0 +1,300 @@
#include "Arduino.h"
#include "Inkplate.h"
#include "../res/Fonts/FreeSansBold24pt7b.h"
extern Inkplate display;
String text0_content = "Hello there!";
int text0_cursor_x = 15;
int text0_cursor_y = 33;
const GFXfont *text0_font = &FreeSansBold24pt7b;
int pixel0_x = 17;
int pixel0_y = 56;
int pixel0_color = 0;
int line0_start_x = 17;
int line0_start_y = 75;
int line0_end_x = 171;
int line0_end_y = 72;
int line0_color = 0;
int line0_thickness = 1;
int line0_gradient = 0;
int rect0_a_x = 18;
int rect0_a_y = 91;
int rect0_b_x = 170;
int rect0_b_y = 136;
int rect0_fill = -1;
int rect0_radius = -1;
int rect0_color = 0;
int rect1_a_x = 20;
int rect1_a_y = 153;
int rect1_b_x = 171;
int rect1_b_y = 201;
int rect1_fill = -1;
int rect1_radius = 15;
int rect1_color = 0;
int rect2_a_x = 22;
int rect2_a_y = 220;
int rect2_b_x = 174;
int rect2_b_y = 269;
int rect2_fill = 1;
int rect2_radius = -1;
int rect2_color = 3;
int rect3_a_x = 22;
int rect3_a_y = 290;
int rect3_b_x = 175;
int rect3_b_y = 336;
int rect3_fill = 1;
int rect3_radius = 20;
int rect3_color = 2;
int line1_start_x = 197;
int line1_start_y = 10;
int line1_end_x = 200;
int line1_end_y = 334;
int line1_color = 4;
int line1_thickness = 5;
int line1_gradient = 0;
int line2_start_x = 222;
int line2_start_y = 12;
int line2_end_x = 228;
int line2_end_y = 410;
int line2_color = 0;
int line2_thickness = 10;
int line2_gradient = 7;
int circle0_center_x = 110;
int circle0_center_y = 455;
int circle0_fill = -1;
int circle0_radius = 100;
int circle0_color = 0;
int circle1_center_x = 109;
int circle1_center_y = 454;
int circle1_fill = 1;
int circle1_radius = 50;
int circle1_color = 3;
int triangle0_a_x = 226;
int triangle0_a_y = 424;
int triangle0_b_x = 361;
int triangle0_b_y = 491;
int triangle0_c_x = 228;
int triangle0_c_y = 581;
int triangle0_fill = 1;
int triangle0_radius = -1;
int triangle0_color = 4;
int triangle1_a_x = 257;
int triangle1_a_y = 409;
int triangle1_b_x = 374;
int triangle1_b_y = 479;
int triangle1_c_x = 252;
int triangle1_c_y = 194;
int triangle1_fill = -1;
int triangle1_radius = -1;
int triangle1_color = 0;
int digital_clock0_h = 9;
int digital_clock0_m = 41;
int digital_clock0_location_x = 248;
int digital_clock0_location_y = 12;
int digital_clock0_size = 64;
int digital_clock0_bitmask[] = {119, 48, 93, 121, 58, 107, 111, 49, 127, 59};
int digital_clock0_triangleX[] = {83, 101, 108, 101, 108, 277, 101, 108, 277, 257, 277, 108, 257, 277, 286, 76, 60, 98, 60, 98, 80, 80, 39, 60, 80, 39, 55, 31, 55, 73, 31, 73, 52, 31, 9, 52, 9, 52, 20, 61, 86, 80, 86, 80, 233, 233, 227, 80, 233, 227, 252, 260, 292, 305, 305, 260, 240, 305, 281, 240, 240, 281, 260, 259, 234, 276, 234, 276, 256, 256, 214, 234, 214, 256, 237, 38, 27, 60, 38, 60, 207, 207, 38, 212, 212, 207, 230};
int digital_clock0_triangleY[] = {30, 13, 60, 13, 60, 14, 13, 60, 14, 57, 14, 60, 57, 14, 29, 36, 47, 61, 47, 61, 198, 198, 201, 47, 198, 201, 219, 252, 232, 253, 252, 253, 390, 252, 406, 390, 406, 390, 416, 227, 202, 249, 202, 249, 203, 203, 247, 249, 203, 247, 224, 60, 35, 49, 49, 60, 200, 50, 201, 200, 200, 201, 220, 231, 252, 252, 252, 252, 403, 403, 390, 252, 390, 403, 415, 439, 424, 392, 439, 392, 394, 394, 439, 439, 439, 394, 424};
int digital_clock0_maxX = 310;
int digital_clock0_maxY = 440;
int widget1_h = 9;
int widget1_m = 41;
int widget1_center_x = 290;
int widget1_center_y = 126;
int widget1_size = 64;
int widget1_r0 = (double)widget1_size / 2 * 0.55;
int widget1_r1 = (double)widget1_size / 2 * 0.65;
int widget1_r2 = (double)widget1_size / 2 * 0.9;
int widget1_r3 = (double)widget1_size / 2 * 1.0;
int widget2_h = 9;
int widget2_m = 41;
int widget2_center_x = 386;
int widget2_center_y = 231;
int widget2_size = 151;
int widget2_r0 = (double)widget2_size / 2 * 0.55;
int widget2_r1 = (double)widget2_size / 2 * 0.65;
int widget2_r2 = (double)widget2_size / 2 * 0.9;
int widget2_r3 = (double)widget2_size / 2 * 1.0;
void mainDraw()
{
display.setFont(text0_font);
display.setTextColor(0, 7);
display.setTextSize(1);
display.setCursor(text0_cursor_x, text0_cursor_y);
display.print(text0_content);
display.drawPixel(pixel0_x, pixel0_y, pixel0_color);
if (line0_gradient <= line0_color && line0_thickness == 1)
display.drawLine(line0_start_x, line0_start_y, line0_end_x, line0_end_y, line0_color);
else if (line0_gradient <= line0_color && line0_thickness != 1)
display.drawThickLine(line0_start_x, line0_start_y, line0_end_x, line0_end_y, line0_color, line0_thickness);
else if (line0_gradient > line0_color && line0_thickness == 1)
display.drawGradientLine(line0_start_x, line0_start_y, line0_end_x, line0_end_y, line0_color, line0_gradient, 1);
else if (line0_gradient > line0_color && line0_thickness != 1)
display.drawGradientLine(line0_start_x, line0_start_y, line0_end_x, line0_end_y, line0_color, line0_gradient, line0_thickness);
if (rect0_radius != -1 && rect0_fill != -1)
display.fillRoundRect(rect0_a_x, rect0_a_y, rect0_b_x - rect0_a_x, rect0_b_y - rect0_a_y, rect0_radius, rect0_color);
else if (rect0_radius != -1 && rect0_fill == -1)
display.drawRoundRect(rect0_a_x, rect0_a_y, rect0_b_x - rect0_a_x, rect0_b_y - rect0_a_y, rect0_radius, rect0_color);
else if (rect0_radius == -1 && rect0_fill != -1)
display.fillRect(rect0_a_x, rect0_a_y, rect0_b_x - rect0_a_x, rect0_b_y - rect0_a_y, rect0_color);
else if (rect0_radius == -1 && rect0_fill == -1)
display.drawRect(rect0_a_x, rect0_a_y, rect0_b_x - rect0_a_x, rect0_b_y - rect0_a_y, rect0_color);
if (rect1_radius != -1 && rect1_fill != -1)
display.fillRoundRect(rect1_a_x, rect1_a_y, rect1_b_x - rect1_a_x, rect1_b_y - rect1_a_y, rect1_radius, rect1_color);
else if (rect1_radius != -1 && rect1_fill == -1)
display.drawRoundRect(rect1_a_x, rect1_a_y, rect1_b_x - rect1_a_x, rect1_b_y - rect1_a_y, rect1_radius, rect1_color);
else if (rect1_radius == -1 && rect1_fill != -1)
display.fillRect(rect1_a_x, rect1_a_y, rect1_b_x - rect1_a_x, rect1_b_y - rect1_a_y, rect1_color);
else if (rect1_radius == -1 && rect1_fill == -1)
display.drawRect(rect1_a_x, rect1_a_y, rect1_b_x - rect1_a_x, rect1_b_y - rect1_a_y, rect1_color);
if (rect2_radius != -1 && rect2_fill != -1)
display.fillRoundRect(rect2_a_x, rect2_a_y, rect2_b_x - rect2_a_x, rect2_b_y - rect2_a_y, rect2_radius, rect2_color);
else if (rect2_radius != -1 && rect2_fill == -1)
display.drawRoundRect(rect2_a_x, rect2_a_y, rect2_b_x - rect2_a_x, rect2_b_y - rect2_a_y, rect2_radius, rect2_color);
else if (rect2_radius == -1 && rect2_fill != -1)
display.fillRect(rect2_a_x, rect2_a_y, rect2_b_x - rect2_a_x, rect2_b_y - rect2_a_y, rect2_color);
else if (rect2_radius == -1 && rect2_fill == -1)
display.drawRect(rect2_a_x, rect2_a_y, rect2_b_x - rect2_a_x, rect2_b_y - rect2_a_y, rect2_color);
if (rect3_radius != -1 && rect3_fill != -1)
display.fillRoundRect(rect3_a_x, rect3_a_y, rect3_b_x - rect3_a_x, rect3_b_y - rect3_a_y, rect3_radius, rect3_color);
else if (rect3_radius != -1 && rect3_fill == -1)
display.drawRoundRect(rect3_a_x, rect3_a_y, rect3_b_x - rect3_a_x, rect3_b_y - rect3_a_y, rect3_radius, rect3_color);
else if (rect3_radius == -1 && rect3_fill != -1)
display.fillRect(rect3_a_x, rect3_a_y, rect3_b_x - rect3_a_x, rect3_b_y - rect3_a_y, rect3_color);
else if (rect3_radius == -1 && rect3_fill == -1)
display.drawRect(rect3_a_x, rect3_a_y, rect3_b_x - rect3_a_x, rect3_b_y - rect3_a_y, rect3_color);
if (line1_gradient <= line1_color && line1_thickness == 1)
display.drawLine(line1_start_x, line1_start_y, line1_end_x, line1_end_y, line1_color);
else if (line1_gradient <= line1_color && line1_thickness != 1)
display.drawThickLine(line1_start_x, line1_start_y, line1_end_x, line1_end_y, line1_color, line1_thickness);
else if (line1_gradient > line1_color && line1_thickness == 1)
display.drawGradientLine(line1_start_x, line1_start_y, line1_end_x, line1_end_y, line1_color, line1_gradient, 1);
else if (line1_gradient > line1_color && line1_thickness != 1)
display.drawGradientLine(line1_start_x, line1_start_y, line1_end_x, line1_end_y, line1_color, line1_gradient, line1_thickness);
if (line2_gradient <= line2_color && line2_thickness == 1)
display.drawLine(line2_start_x, line2_start_y, line2_end_x, line2_end_y, line2_color);
else if (line2_gradient <= line2_color && line2_thickness != 1)
display.drawThickLine(line2_start_x, line2_start_y, line2_end_x, line2_end_y, line2_color, line2_thickness);
else if (line2_gradient > line2_color && line2_thickness == 1)
display.drawGradientLine(line2_start_x, line2_start_y, line2_end_x, line2_end_y, line2_color, line2_gradient, 1);
else if (line2_gradient > line2_color && line2_thickness != 1)
display.drawGradientLine(line2_start_x, line2_start_y, line2_end_x, line2_end_y, line2_color, line2_gradient, line2_thickness);
if (circle0_fill != -1)
display.fillCircle(circle0_center_x, circle0_center_y, circle0_radius, circle0_color);
else
display.drawCircle(circle0_center_x, circle0_center_y, circle0_radius, circle0_color);
if (circle1_fill != -1)
display.fillCircle(circle1_center_x, circle1_center_y, circle1_radius, circle1_color);
else
display.drawCircle(circle1_center_x, circle1_center_y, circle1_radius, circle1_color);
if (triangle0_fill != -1)
display.fillTriangle(triangle0_a_x, triangle0_a_y, triangle0_b_x, triangle0_b_y, triangle0_c_x, triangle0_c_y, triangle0_color);
else
display.drawTriangle(triangle0_a_x, triangle0_a_y, triangle0_b_x, triangle0_b_y, triangle0_c_x, triangle0_c_y, triangle0_color);
if (triangle1_fill != -1)
display.fillTriangle(triangle1_a_x, triangle1_a_y, triangle1_b_x, triangle1_b_y, triangle1_c_x, triangle1_c_y, triangle1_color);
else
display.drawTriangle(triangle1_a_x, triangle1_a_y, triangle1_b_x, triangle1_b_y, triangle1_c_x, triangle1_c_y, triangle1_color);
for (int i = 0; i < 4; ++i)
{
for (int j = 0; j < sizeof(digital_clock0_triangleX) / sizeof(digital_clock0_triangleX[0]); j += 3)
{
int temp[4] = {digital_clock0_h / 10 % 10, digital_clock0_h % 10, digital_clock0_m / 10 % 10, digital_clock0_m % 10};
int b = digital_clock0_bitmask[temp[i]];
if (b & (1 << ((j - 1) / (3 * 4))))
{
display.fillTriangle(
(int)((float)i * (float)digital_clock0_maxX / (float)digital_clock0_maxY * (float)digital_clock0_size * 1.1 + (float)digital_clock0_location_x + (float)digital_clock0_maxX / (float)digital_clock0_maxY * (float)digital_clock0_size * (float)digital_clock0_triangleX[j + 0] / (float)digital_clock0_maxX),
(int)((float)digital_clock0_location_y + (float)digital_clock0_size * (float)digital_clock0_triangleY[j + 0] / (float)digital_clock0_maxY),
(int)((float)i * (float)digital_clock0_maxX / (float)digital_clock0_maxY * (float)digital_clock0_size * 1.1 + (float)digital_clock0_location_x + (float)digital_clock0_maxX / (float)digital_clock0_maxY * (float)digital_clock0_size * (float)digital_clock0_triangleX[j + 1] / (float)digital_clock0_maxX),
(int)((float)digital_clock0_location_y + (float)digital_clock0_size * (float)digital_clock0_triangleY[j + 1] / (float)digital_clock0_maxY),
(int)((float)i * (float)digital_clock0_maxX / (float)digital_clock0_maxY * (float)digital_clock0_size * 1.1 + (float)digital_clock0_location_x + (float)digital_clock0_maxX / (float)digital_clock0_maxY * (float)digital_clock0_size * (float)digital_clock0_triangleX[j + 2] / (float)digital_clock0_maxX),
(int)((float)digital_clock0_location_y + (float)digital_clock0_size * (float)digital_clock0_triangleY[j + 2] / (float)digital_clock0_maxY),
0);
}
}
}
int digital_clock0_r = 0.05 * (float)digital_clock0_size;
display.fillCircle((int)((float)digital_clock0_location_x + 4.0 * (float)digital_clock0_maxX / (float)digital_clock0_maxY * (float)digital_clock0_size * 1.075 / 2.0), (int)((float)digital_clock0_location_y + (float)digital_clock0_size * 0.4), digital_clock0_r, 0);
display.fillCircle((int)((float)digital_clock0_location_x + 4.0 * (float)digital_clock0_maxX / (float)digital_clock0_maxY * (float)digital_clock0_size * 1.075 / 2.0), (int)((float)digital_clock0_location_y + (float)digital_clock0_size * 0.6), digital_clock0_r, 0);
for (int i = 0; i < 60; ++i)
{
if (i % 5 == 0)
display.drawThickLine(widget1_center_x + widget1_r1 * cos((double)i / 60.0 * 2.0 * 3.14159265),
widget1_center_y + widget1_r1 * sin((double)i / 60.0 * 2.0 * 3.14159265),
widget1_center_x + widget1_r3 * cos((double)i / 60.0 * 2.0 * 3.14159265),
widget1_center_y + widget1_r3 * sin((double)i / 60.0 * 2.0 * 3.14159265), 0, 3);
else if (widget1_size > 150)
display.drawLine(widget1_center_x + widget1_r1 * cos((double)i / 60.0 * 2.0 * 3.14159265),
widget1_center_y + widget1_r1 * sin((double)i / 60.0 * 2.0 * 3.14159265),
widget1_center_x + widget1_r2 * cos((double)i / 60.0 * 2.0 * 3.14159265),
widget1_center_y + widget1_r2 * sin((double)i / 60.0 * 2.0 * 3.14159265), 2);
}
display.drawThickLine(widget1_center_x,
widget1_center_y,
widget1_center_x + widget1_r0 * cos((double)(widget1_h - 3.0 + widget1_m / 60.0) / 12.0 * 2.0 * 3.14159265),
widget1_center_y + widget1_r0 * sin((double)(widget1_h - 3.0 + widget1_m / 60.0) / 12.0 * 2.0 * 3.14159265), 2, 2);
display.drawThickLine(widget1_center_x,
widget1_center_y,
widget1_center_x + widget1_r2 * cos((double)(widget1_m - 15.0) / 60.0 * 2.0 * 3.14159265),
widget1_center_y + widget1_r2 * sin((double)(widget1_m - 15.0) / 60.0 * 2.0 * 3.14159265), 2, 2);
for (int i = 0; i < 60; ++i)
{
if (i % 5 == 0)
display.drawThickLine(widget2_center_x + widget2_r1 * cos((double)i / 60.0 * 2.0 * 3.14159265),
widget2_center_y + widget2_r1 * sin((double)i / 60.0 * 2.0 * 3.14159265),
widget2_center_x + widget2_r3 * cos((double)i / 60.0 * 2.0 * 3.14159265),
widget2_center_y + widget2_r3 * sin((double)i / 60.0 * 2.0 * 3.14159265), 0, 3);
else if (widget2_size > 150)
display.drawLine(widget2_center_x + widget2_r1 * cos((double)i / 60.0 * 2.0 * 3.14159265),
widget2_center_y + widget2_r1 * sin((double)i / 60.0 * 2.0 * 3.14159265),
widget2_center_x + widget2_r2 * cos((double)i / 60.0 * 2.0 * 3.14159265),
widget2_center_y + widget2_r2 * sin((double)i / 60.0 * 2.0 * 3.14159265), 2);
}
display.drawThickLine(widget2_center_x,
widget2_center_y,
widget2_center_x + widget2_r0 * cos((double)(widget2_h - 3.0 + widget2_m / 60.0) / 12.0 * 2.0 * 3.14159265),
widget2_center_y + widget2_r0 * sin((double)(widget2_h - 3.0 + widget2_m / 60.0) / 12.0 * 2.0 * 3.14159265), 2, 2);
display.drawThickLine(widget2_center_x,
widget2_center_y,
widget2_center_x + widget2_r2 * cos((double)(widget2_m - 15.0) / 60.0 * 2.0 * 3.14159265),
widget2_center_y + widget2_r2 * sin((double)(widget2_m - 15.0) / 60.0 * 2.0 * 3.14159265), 2, 2);
}

View File

@ -1,7 +1,7 @@
#include "Inkplate.h"
#include "SdFat.h"
const int n = 10;
const int n = 500;
Inkplate display(INKPLATE_1BIT);