aboutsummaryrefslogtreecommitdiffstats
path: root/src/gdisp
diff options
context:
space:
mode:
Diffstat (limited to 'src/gdisp')
-rw-r--r--src/gdisp/gdisp_image.c10
-rw-r--r--src/gdisp/gdisp_image_png.c1632
-rw-r--r--src/gdisp/gdisp_options.h121
3 files changed, 1762 insertions, 1 deletions
diff --git a/src/gdisp/gdisp_image.c b/src/gdisp/gdisp_image.c
index 853d2f75..356ba64f 100644
--- a/src/gdisp/gdisp_image.c
+++ b/src/gdisp/gdisp_image.c
@@ -154,6 +154,16 @@ gdispImageError gdispImageCache(gdispImage *img) {
gdispImageError gdispGImageDraw(GDisplay *g, gdispImage *img, coord_t x, coord_t y, coord_t cx, coord_t cy, coord_t sx, coord_t sy) {
if (!img->fns) return GDISP_IMAGE_ERR_BADFORMAT;
+
+ // Check on window
+ if (cx <= 0 || cy <= 0) return GDISP_IMAGE_ERR_OK;
+ if (sx < 0) sx = 0;
+ if (sy < 0) sy = 0;
+ if (sx >= img->width || sy >= img->height) return GDISP_IMAGE_ERR_OK;
+ if (sx + cx > img->width) cx = img->width - sx;
+ if (sy + cy > img->height) cy = img->height - sy;
+
+ // Draw
return img->fns->draw(g, img, x, y, cx, cy, sx, sy);
}
diff --git a/src/gdisp/gdisp_image_png.c b/src/gdisp/gdisp_image_png.c
index 9a3e3cf0..8b2bfb04 100644
--- a/src/gdisp/gdisp_image_png.c
+++ b/src/gdisp/gdisp_image_png.c
@@ -11,6 +11,1636 @@
#include "gdisp_image_support.h"
-#error "PNG support not implemented yet"
+/**
+ * How big a pixel array to allocate for blitting the image to the display (in pixels)
+ * Bigger is faster but uses more RAM.
+ */
+#define PNG_BLIT_BUFFER_SIZE 32
+/**
+ * How big a byte array to use for input file buffer
+ * Bigger is faster but uses more RAM.
+ * Must be more than 8 bytes
+ */
+#define PNG_FILE_BUFFER_SIZE 8
+/**
+ * How big a byte array to use for inflate decompression
+ * Bigger is faster but uses more RAM.
+ * Must be >= 32768 due to the PNG 32K sliding window
+ * More efficient code is generated if it is a power of 2
+ */
+#define PNG_Z_BUFFER_SIZE 32768
+
+/*-----------------------------------------------------------------
+ * Structure definitions
+ *---------------------------------------------------------------*/
+
+struct PNG_decode;
+
+// PNG info (comes from the PNG header)
+typedef struct PNG_info {
+ uint8_t flags; // Flags (global)
+ #define PNG_FLG_HEADERDONE 0x01 // The header has been processed
+ #define PNG_FLG_TRANSPARENT 0x02 // Has transparency
+ #define PNG_FLG_INTERLACE 0x04 // Is Interlaced
+ #define PNG_FLG_BACKGROUND 0x08 // Has a specified background color
+ uint8_t bitdepth; // 1, 2, 4, 8, 16
+ uint8_t mode; // The PNG color-mode
+ #define PNG_COLORMODE_GRAY 0x00 // Grayscale
+ #define PNG_COLORMODE_RGB 0x02 // RGB
+ #define PNG_COLORMODE_PALETTE 0x03 // Pallete
+ #define PNG_COLORMODE_GRAYALPHA 0x04 // Grayscale with Alpha
+ #define PNG_COLORMODE_RGBA 0x06 // RGBA
+ uint8_t bpp; // Bits per pixel
+
+ uint8_t *cache; // The image cache
+ unsigned cachesz; // The image cache size
+
+ void (*out)(struct PNG_decode *); // The scan line output function
+
+ #if GDISP_NEED_IMAGE_PNG_BACKGROUND
+ color_t bg; // The background color
+ #endif
+ #if GDISP_NEED_IMAGE_PNG_TRANSPARENCY
+ uint16_t trans_r; // Red/grayscale component of the transparent color (PNG_COLORMODE_GRAY and PNG_COLORMODE_RGB only)
+ uint16_t trans_g; // Green component of the transparent color (PNG_COLORMODE_RGB only)
+ uint16_t trans_b; // Blue component of the transparent color (PNG_COLORMODE_RGB only)
+ #endif
+ #if GDISP_NEED_IMAGE_PNG_PALETTE_124 || GDISP_NEED_IMAGE_PNG_PALETTE_8
+ uint16_t palsize; // palette size in number of colors
+ uint8_t *palette; // palette in RGBA RGBA... order (4 bytes per entry - PNG_COLORMODE_PALETTE only)
+ #endif
+ } PNG_info;
+
+// Handle the PNG file stream
+typedef struct PNG_input {
+ GFILE * f; // The gfile to retrieve data from
+ unsigned buflen; // The number of bytes left in the buffer
+ uint8_t *pbuf; // The pointer to the next byte
+ uint32_t chunklen; // The number of bytes left in the current PNG chunk
+ uint32_t chunknext; // The file position of the next PNG chunk
+ uint8_t buf[PNG_FILE_BUFFER_SIZE]; // Must be a minimum of 8 bytes to hold a chunk header
+ } PNG_input;
+
+// Handle the display output and windowing
+typedef struct PNG_output {
+ GDisplay *g;
+ coord_t x, y;
+ coord_t cx, cy;
+ coord_t sx, sy;
+ coord_t ix, iy;
+ unsigned cnt;
+ pixel_t buf[PNG_BLIT_BUFFER_SIZE];
+ } PNG_output;
+
+// Handle the PNG scan line filter
+typedef struct PNG_filter {
+ unsigned scanbytes;
+ unsigned bytewidth;
+ uint8_t *line;
+ uint8_t *prev;
+ } PNG_filter;
+
+// Handle the PNG inflate decompression
+typedef struct PNG_zTree {
+ uint16_t table[16]; // Table of code length counts
+ uint16_t trans[288]; // Code to symbol translation table
+ } PNG_zTree;
+
+typedef struct PNG_zinflate {
+ uint8_t data; // The current input stream data byte
+ uint8_t bits; // The number of bits left in the data byte
+ uint8_t flags; // Decompression flags
+ #define PNG_ZFLG_EOF 0x01 // No more input data
+ #define PNG_ZFLG_FINAL 0x02 // This is the final block
+ #define PNG_ZFLG_RESUME_MASK 0x0C // The mask of bits for the resume state
+ #define PNG_ZFLG_RESUME_NEW 0x00 // Process a new block
+ #define PNG_ZFLG_RESUME_COPY 0x04 // Resume a byte copy from the input stream (length in tmp)
+ #define PNG_ZFLG_RESUME_INFLATE 0x08 // Resume using the specified symbol (symbol in tmp[0])
+ #define PNG_ZFLG_RESUME_OFFSET 0x0C // Resume a byte offset copy from the buffer (length and offset in tmp)
+
+ unsigned bufpos; // The current buffer output position
+ unsigned bufend; // The current buffer end position (wraps)
+
+ PNG_zTree ltree; // The dynamic length tree
+ PNG_zTree dtree; // The dynamic distance tree
+ uint8_t tmp[288+32]; // Temporary space for decoding dynamic trees and other temporary uses
+ uint8_t buf[PNG_Z_BUFFER_SIZE]; // The decoding buffer and sliding window
+ } PNG_zinflate;
+
+// Put all the decoding structures together.
+// Note this is immediately followed by 2 scan lines of uncompressed image data for filtering (dynamic size).
+typedef struct PNG_decode {
+ gdispImage *img;
+ PNG_info *pinfo;
+ PNG_input i;
+ PNG_output o;
+ PNG_filter f;
+ PNG_zinflate z;
+ } PNG_decode;
+
+/*-----------------------------------------------------------------
+ * PNG input data stream functions
+ *---------------------------------------------------------------*/
+
+// Input initialization
+static void PNG_iInit(PNG_decode *d) {
+ if (d->pinfo->cache) {
+ d->i.pbuf = d->pinfo->cache;
+ d->i.buflen = d->pinfo->cachesz;
+ d->i.f = 0;
+ } else {
+ d->i.buflen = 0;
+ d->i.chunklen = 0;
+ d->i.chunknext = 8;
+ d->i.f = d->img->f;
+ }
+}
+
+// Load the next byte of image data from the PNG file
+static bool_t PNG_iLoadData(PNG_decode *d) {
+ uint32_t sz;
+
+ // Is there data still left in the buffer?
+ if (d->i.buflen)
+ return TRUE;
+
+ // If we are cached then we have no more data
+ if (!d->i.f)
+ return FALSE;
+
+ // Have we finished the current chunk?
+ if (!d->i.chunklen) {
+ while(1) {
+ // Find a new chunk
+ gfileSetPos(d->i.f, d->i.chunknext);
+ if (gfileRead(d->i.f, d->i.buf, 8) != 8)
+ return FALSE;
+
+ // Calculate the chunk length and next chunk
+ d->i.chunklen = gdispImageGetAlignedBE32(d->i.buf, 0);
+ d->i.chunknext += d->i.chunklen + 12;
+
+ // Process only image data chunks
+ switch (gdispImageGetAlignedBE32(d->i.buf, 4)) {
+ case 0x49444154: // "IDAT" - Image Data
+ if (!d->i.chunklen)
+ break;
+ goto gotchunk;
+ case 0x49454E44: // "IEND" - All done
+ return FALSE;
+ }
+ }
+ }
+
+gotchunk:
+
+ // Try to read data some from the chunk
+ sz = d->i.chunklen;
+ if (sz > PNG_FILE_BUFFER_SIZE)
+ sz = PNG_FILE_BUFFER_SIZE;
+ if (gfileRead(d->i.f, d->i.buf, sz) != sz)
+ return FALSE;
+ d->i.chunklen -= sz;
+ d->i.buflen = sz;
+ d->i.pbuf = d->i.buf;
+ return TRUE;
+}
+
+// Get the last loaded byte of image data from the PNG file
+static uint8_t PNG_iGetByte(PNG_decode *d) {
+ d->i.buflen--;
+ return *d->i.pbuf++;
+}
+
+/*-----------------------------------------------------------------
+ * Display output and windowing functions
+ *---------------------------------------------------------------*/
+
+// Initialize the display output window
+static void PNG_oInit(PNG_output *o, GDisplay *g, coord_t x, coord_t y, coord_t cx, coord_t cy, coord_t sx, coord_t sy) {
+ o->g = g;
+ o->x = x;
+ o->y = y;
+ o->cx = cx;
+ o->cy = cy;
+ o->sx = sx;
+ o->sy = sy;
+ o->ix = o->iy = 0;
+ o->cnt = 0;
+}
+
+// Flush the output buffer to the display
+static void PNG_oFlush(PNG_output *o) {
+ switch(o->cnt) {
+ case 0: return;
+ case 1: gdispGDrawPixel(o->g, o->x+o->ix-o->sx, o->y+o->iy-o->sy, o->buf[0]); break;
+ default: gdispGBlitArea(o->g, o->x+o->ix-o->sx, o->y+o->iy-o->sy, o->cnt, 1, 0, 0, o->cnt, o->buf); break;
+ }
+ o->ix += o->cnt;
+ o->cnt = 0;
+}
+
+// Start a new image line
+static bool_t PNG_oStartY(PNG_output *o, coord_t y) {
+ if (y < o->sy || y >= o->sy+o->cy)
+ return FALSE;
+ o->ix = 0;
+ o->iy = y;
+ return TRUE;
+}
+
+// Feed a pixel color to the display buffer
+static void PNG_oColor(PNG_output *o, color_t c) {
+ // Is it in the window
+ if (o->ix+(coord_t)o->cnt < o->sx || o->ix+(coord_t)o->cnt >= o->sx+o->cx) {
+ // No - just skip the pixel
+ PNG_oFlush(o);
+ o->ix++;
+ return;
+ }
+
+ // Is the buffer full
+ if (o->cnt >= sizeof(o->buf)/sizeof(o->buf[0]))
+ PNG_oFlush(o);
+
+ // Save the pixel
+ o->buf[o->cnt++] = c;
+}
+
+#if GDISP_NEED_IMAGE_PNG_TRANSPARENCY || GDISP_NEED_IMAGE_PNG_ALPHACLIFF > 0
+ // Feed a transparent pixel to the display buffer
+ static void PNG_oTransparent(PNG_output *o) {
+ // Flush any existing pixels
+ PNG_oFlush(o);
+
+ // Just skip the pixel
+ o->ix++;
+ }
+#endif
+
+/*-----------------------------------------------------------------
+ * Inflate uncompress functions
+ *---------------------------------------------------------------*/
+
+// Wrap the zInflate buffer position (after increment)
+#if (PNG_Z_BUFFER_SIZE & ~(PNG_Z_BUFFER_SIZE-1)) == PNG_Z_BUFFER_SIZE
+ #define WRAP_ZBUF(x) { x &= PNG_Z_BUFFER_SIZE-1; }
+#else
+ #warning "PNG: PNG_Z_BUFFER_SIZE is more efficient as a power of 2"
+ #define WRAP_ZBUF(x) { if (x >= PNG_Z_BUFFER_SIZE) x = 0; }
+#endif
+
+// Initialize the inflate decompressor
+static void PNG_zInit(PNG_zinflate *z) {
+ z->bits = 0;
+ z->flags = 0;
+ z->bufpos = z->bufend = 0;
+}
+
+// Get the inflate header (slightly customized for PNG validity testing)
+static bool_t PNG_zGetHeader(PNG_decode *d) {
+ if (!PNG_iLoadData(d))
+ return FALSE;
+ d->z.tmp[0] = PNG_iGetByte(d);
+ if (!PNG_iLoadData(d))
+ return FALSE;
+ d->z.tmp[1] = PNG_iGetByte(d);
+ if (gdispImageGetAlignedBE16(d->z.tmp, 0) % 31 != 0 // Must be modulo 31, the FCHECK value is made that way
+ || (d->z.tmp[0] & 0x0F) != 8 || (d->z.tmp[0] & 0x80) // only method 8: inflate 32k sliding window
+ || (d->z.tmp[1] & 0x20)) // no preset dictionary
+ return FALSE;
+ return TRUE;
+}
+
+// Get a bit from the input (treated as a LSB first stream)
+static unsigned PNG_zGetBit(PNG_decode *d) {
+ unsigned bit;
+
+ // Check for EOF
+ if ((d->z.flags & PNG_ZFLG_EOF))
+ return 1;
+
+ // Check if data is empty
+ if (!d->z.bits) {
+ if (!PNG_iLoadData(d)) {
+ d->z.flags |= PNG_ZFLG_EOF;
+ return 1;
+ }
+ d->z.data = PNG_iGetByte(d);
+ d->z.bits = 8;
+ }
+
+ // Get the next bit
+ d->z.bits--;
+ bit = d->z.data & 0x01;
+ d->z.data >>= 1;
+ return bit;
+}
+
+// Get multiple bits from the input (treated as a LSB first stream with bit order retained)
+static unsigned PNG_zGetBits(PNG_decode *d, unsigned num) {
+ unsigned val;
+ unsigned limit;
+ unsigned mask;
+
+ val = 0;
+ limit = 1 << num;
+
+ for (mask = 1; mask < limit; mask <<= 1)
+ if (PNG_zGetBit(d))
+ val += mask;
+ return val;
+}
+
+// Build an inflate dynamic tree using a string of byte lengths
+static void PNG_zBuildTree(PNG_zTree *t, const uint8_t *lengths, unsigned num) {
+ unsigned i, sum;
+ uint16_t offs[16];
+
+ for (i = 0; i < 16; ++i)
+ t->table[i] = 0;
+ for (i = 0; i < num; ++i)
+ t->table[lengths[i]]++;
+
+ t->table[0] = 0;
+
+ for (sum = 0, i = 0; i < 16; ++i) {
+ offs[i] = sum;
+ sum += t->table[i];
+ }
+ for (i = 0; i < num; ++i) {
+ if (lengths[i])
+ t->trans[offs[lengths[i]]++] = i;
+ }
+}
+
+// Get an inflate decode symbol
+static uint16_t PNG_zGetSymbol(PNG_decode *d, PNG_zTree *t) {
+ int sum, cur;
+ unsigned len;
+
+ sum = cur = 0;
+ len = 0;
+ do {
+ cur <<= 1;
+ cur += PNG_zGetBit(d);
+ if ((d->z.flags & PNG_ZFLG_EOF))
+ return 0;
+ len++;
+
+ sum += t->table[len];
+ cur -= t->table[len];
+ } while (cur >= 0);
+
+ return t->trans[sum + cur];
+}
+
+// Build inflate fixed length and distance trees
+static void PNG_zBuildFixedTrees(PNG_decode *d) {
+ unsigned i;
+
+ for (i = 0; i < 16; ++i) d->z.ltree.table[i] = 0;
+ d->z.ltree.table[7] = 24;
+ d->z.ltree.table[8] = 152;
+ d->z.ltree.table[9] = 112;
+ for (i = 0; i < 24; ++i) d->z.ltree.trans[i] = 256 + i;
+ for (i = 0; i < 144; ++i) d->z.ltree.trans[24 + i] = i;
+ for (i = 0; i < 8; ++i) d->z.ltree.trans[24 + 144 + i] = 280 + i;
+ for (i = 0; i < 112; ++i) d->z.ltree.trans[24 + 144 + 8 + i] = 144 + i;
+
+ for (i = 0; i < 16; ++i) d->z.dtree.table[i] = 0;
+ d->z.dtree.table[5] = 32;
+ for (i = 0; i < 32; ++i) d->z.dtree.trans[i] = i;
+ for ( ; i < 288; ++i) d->z.dtree.trans[i] = 0;
+}
+
+// Build inflate dynamic length and distance trees
+static bool_t PNG_zDecodeTrees(PNG_decode *d) {
+ static const uint8_t IndexLookup[19] = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 };
+ unsigned hlit, hdist, hclen;
+ unsigned i, num;
+ uint16_t symbol;
+ uint8_t val;
+
+ hlit = PNG_zGetBits(d, 5) + 257; // 257 - 286
+ hdist = PNG_zGetBits(d, 5) + 1; // 1 - 32
+ hclen = PNG_zGetBits(d, 4) + 4; // 4 - 19
+
+ if ((d->z.flags & PNG_ZFLG_EOF))
+ return FALSE;
+
+ for (i = 0; i < 19; ++i)
+ d->z.tmp[i] = 0;
+
+ // Get code lengths for the code length alphabet
+ for (i = 0; i < hclen; ++i)
+ d->z.tmp[IndexLookup[i]] = PNG_zGetBits(d, 3);
+
+ if ((d->z.flags & PNG_ZFLG_EOF))
+ return FALSE;
+
+ // Build the code length tree
+ PNG_zBuildTree(&d->z.ltree, d->z.tmp, 19);
+
+ // Decode code lengths
+ for (num = 0; num < hlit + hdist; ) {
+ symbol = PNG_zGetSymbol(d, &d->z.ltree);
+ if ((d->z.flags & PNG_ZFLG_EOF))
+ return FALSE;
+
+ switch(symbol) {
+ case 16: // Copy the previous code length 3-6 times
+ val = d->z.tmp[num - 1];
+ for (i = PNG_zGetBits(d, 2) + 3; i; i--)
+ d->z.tmp[num++] = val;
+ break;
+ case 17: // Repeat code length 0 for 3-10 times
+ for (i = PNG_zGetBits(d, 3) + 3; i; i--)
+ d->z.tmp[num++] = 0;
+ break;
+ case 18: // Repeat code length 0 for 11-138 times
+ for (i = PNG_zGetBits(d, 7) + 11; i; i--)
+ d->z.tmp[num++] = 0;
+ break;
+ default: // symbols 0-15 are the actual code lengths
+ d->z.tmp[num++] = symbol;
+ break;
+ }
+ }
+
+ // Build the trees
+ PNG_zBuildTree(&d->z.ltree, d->z.tmp, hlit);
+ PNG_zBuildTree(&d->z.dtree, d->z.tmp + hlit, hdist);
+ return TRUE;
+}
+
+// Copy bytes from the input stream. Completing the copy completes the block.
+static bool_t PNG_zCopyInput(PNG_decode *d, unsigned length) {
+ // Copy the block
+ while(length--) {
+ if (!PNG_iLoadData(d)) { // EOF?
+ d->z.flags |= PNG_ZFLG_EOF;
+ return FALSE;
+ }
+ d->z.buf[d->z.bufend++] = PNG_iGetByte(d);
+ WRAP_ZBUF(d->z.bufend);
+ if (d->z.bufend == d->z.bufpos) { // Buffer full?
+ d->z.flags = (d->z.flags & ~PNG_ZFLG_RESUME_MASK) | PNG_ZFLG_RESUME_COPY;
+ ((unsigned *)d->z.tmp)[0] = length;
+ return TRUE;
+ }
+ }
+
+ // The block is done
+ d->z.flags = (d->z.flags & ~PNG_ZFLG_RESUME_MASK) | PNG_ZFLG_RESUME_NEW;
+ return TRUE;
+}
+
+// Copy an uncompressed inflate block into the output
+static bool_t PNG_zUncompressedBlock(PNG_decode *d) {
+ unsigned length;
+
+ // This block works on byte boundaries
+ d->z.bits = 0;
+
+ // Get 4 byte header
+ for (length = 0; length < 4; length++) {
+ if (!PNG_iLoadData(d)) { // EOF?
+ d->z.flags |= PNG_ZFLG_EOF;
+ return FALSE;
+ }
+ d->z.tmp[length] = PNG_iGetByte(d);
+ }
+
+ // Get length
+ length = gdispImageGetAlignedLE16(d->z.tmp, 0);
+
+ // Check length
+ if ((uint16_t)length != (uint16_t)~gdispImageGetAlignedLE16(d->z.tmp, 2)) {
+ d->z.flags |= PNG_ZFLG_EOF;
+ return FALSE;
+ }
+
+ // Copy the block
+ return PNG_zCopyInput(d, length);
+}
+
+// Inflate a compressed inflate block into the output
+static bool_t PNG_zInflateBlock(PNG_decode *d) {
+ static const uint8_t lbits[30] = { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 6 };
+ static const uint16_t lbase[30] = { 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 323 };
+ static const uint8_t dbits[30] = { 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13 };
+ static const uint16_t dbase[30] = { 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577 };
+ unsigned length, dist, offset;
+ uint16_t symbol;
+
+ while(1) {
+ symbol = PNG_zGetSymbol(d, &d->z.ltree); // EOF?
+ if ((d->z.flags & PNG_ZFLG_EOF))
+ goto iserror;
+
+ // Is the block done?
+ if (symbol == 256) {
+ d->z.flags = (d->z.flags & ~PNG_ZFLG_RESUME_MASK) | PNG_ZFLG_RESUME_NEW;
+ return TRUE;
+ }
+
+ if (symbol < 256) {
+ // The symbol is the data
+ d->z.buf[d->z.bufend++] = (uint8_t)symbol;
+ WRAP_ZBUF(d->z.bufend);
+ if (d->z.bufend == d->z.bufpos) { // Buffer full?
+ d->z.flags = (d->z.flags & ~PNG_ZFLG_RESUME_MASK) | PNG_ZFLG_RESUME_INFLATE;
+ return TRUE;
+ }
+ continue;
+ }
+
+ // Shift the symbol down into an index
+ symbol -= 257;
+
+ if (symbol >= sizeof(lbits)) // Bad index?
+ goto iserror;
+
+ // Get more bits from length code
+ length = PNG_zGetBits(d, lbits[symbol]) + lbase[symbol];
+ if ((d->z.flags & PNG_ZFLG_EOF) || length >= PNG_Z_BUFFER_SIZE) // Bad length?
+ goto iserror;
+
+ // Get the distance code
+ dist = PNG_zGetSymbol(d, &d->z.dtree); // Bad distance?
+ if ((d->z.flags & PNG_ZFLG_EOF) || dist >= sizeof(dbits))
+ goto iserror;
+
+ // Get more bits from distance code
+ offset = PNG_zGetBits(d, dbits[dist]) + dbase[dist];
+ if ((d->z.flags & PNG_ZFLG_EOF) || offset >= PNG_Z_BUFFER_SIZE) // Bad offset?
+ goto iserror;
+
+ // Work out the source buffer position allowing for wrapping
+ if (offset > d->z.bufend)
+ offset -= PNG_Z_BUFFER_SIZE;
+ offset = d->z.bufend - offset;
+
+ // Copy the matching string
+ while (length--) {
+ d->z.buf[d->z.bufend++] = d->z.buf[offset++];
+ WRAP_ZBUF(d->z.bufend);
+ WRAP_ZBUF(offset);
+ if (d->z.bufend == d->z.bufpos) { // Buffer full?
+ d->z.flags = (d->z.flags & ~PNG_ZFLG_RESUME_MASK) | PNG_ZFLG_RESUME_OFFSET;
+ ((unsigned *)d->z.tmp)[0] = length;
+ ((unsigned *)d->z.tmp)[1] = offset;
+ return TRUE;
+ }
+ }
+ }
+
+iserror:
+ d->z.flags |= PNG_ZFLG_EOF;
+ return FALSE;
+}
+
+// Start a new uncompressed/inflate block
+static bool_t PNG_zStartBlock(PNG_decode *d) {
+ // Check for previous error, EOF or no more blocks
+ if ((d->z.flags & (PNG_ZFLG_EOF|PNG_ZFLG_FINAL)))
+ return FALSE;
+
+ // Is this the final inflate block?
+ if (PNG_zGetBit(d))
+ d->z.flags |= PNG_ZFLG_FINAL;
+
+ // Get the block type
+ switch (PNG_zGetBits(d, 2)) {
+
+ case 0: // Decompress uncompressed block
+ if (!PNG_zUncompressedBlock(d))
+ return FALSE;
+ break;
+
+ case 1: // Decompress block with fixed huffman trees
+ PNG_zBuildFixedTrees(d);
+ if (!PNG_zInflateBlock(d))
+ return FALSE;
+ break;
+
+ case 2: // Decompress block with dynamic huffman trees
+ if (!PNG_zDecodeTrees(d))
+ return FALSE;
+ if (!PNG_zInflateBlock(d))
+ return FALSE;
+ break;
+
+ default: // Bad block type
+ // Mark it as an error
+ d->z.flags |= PNG_ZFLG_EOF;
+ return FALSE;
+ }
+ return TRUE;
+}
+
+// Resume an offset copy
+static bool_t PNG_zResumeOffset(PNG_decode *d, unsigned length, unsigned offset) {
+ // Copy the matching string
+ while (length--) {
+ d->z.buf[d->z.bufend++] = d->z.buf[offset++];
+ WRAP_ZBUF(d->z.bufend);
+ WRAP_ZBUF(offset);
+ if (d->z.bufend == d->z.bufpos) { // Buffer full?
+ d->z.flags = (d->z.flags & ~PNG_ZFLG_RESUME_MASK) | PNG_ZFLG_RESUME_OFFSET;
+ ((unsigned *)d->z.tmp)[0] = length;
+ ((unsigned *)d->z.tmp)[1] = offset;
+ return TRUE;
+ }
+ }
+ return PNG_zInflateBlock(d);
+}
+
+// Get a fully decompressed byte from the inflate data stream
+static uint8_t PNG_zGetByte(PNG_decode *d) {
+ uint8_t data;
+
+ // Do we have any data in the buffers
+ while (d->z.bufpos == d->z.bufend) {
+
+ // No, get some more data
+
+ switch((d->z.flags & PNG_ZFLG_RESUME_MASK)) {
+ case PNG_ZFLG_RESUME_NEW: // Start a new inflate block
+ if (!PNG_zStartBlock(d))
+ return 0xFF;
+ break;
+ case PNG_ZFLG_RESUME_COPY: // Resume uncompressed block copy for length bytes
+ if (!PNG_zCopyInput(d, ((unsigned *)d->z.tmp)[0]))
+ return 0xFF;
+ break;
+ case PNG_ZFLG_RESUME_INFLATE: // Resume compressed block
+ if (!PNG_zInflateBlock(d))
+ return 0xFF;
+ break;
+ case PNG_ZFLG_RESUME_OFFSET: // Resume compressed block using offset copy for length bytes
+ if (!PNG_zResumeOffset(d, ((unsigned *)d->z.tmp)[0], ((unsigned *)d->z.tmp)[1]))
+ return 0xFF;
+ break;
+ }
+
+ // Check for no data being provided
+ // A resume code means the buffer is completely full so the test must be skipped
+ if ((d->z.flags & PNG_ZFLG_RESUME_MASK) != PNG_ZFLG_RESUME_NEW)
+ break;
+ }
+
+ // Get the next data byte
+ data = d->z.buf[d->z.bufpos++];
+ WRAP_ZBUF(d->z.bufpos);
+
+ return data;
+}
+
+/*-----------------------------------------------------------------
+ * Scan-line filter functions
+ *---------------------------------------------------------------*/
+
+// Initialise the scan-line engine
+static void PNG_fInit(PNG_filter *f, uint8_t *buf, unsigned bytewidth, unsigned scanbytes) {
+ f->scanbytes = scanbytes;
+ f->bytewidth = bytewidth;
+ f->line = buf;
+ f->prev = 0;
+}
+
+// Get ready for the next scan-line
+static void PNG_fNext(PNG_filter *f) {
+ if (f->prev && f->line > f->prev) {
+ f->line = f->prev;
+ f->prev += f->scanbytes;
+ } else {
+ f->prev = f->line;
+ f->line += f->scanbytes;
+ }
+}
+
+// Predictor function for filter0 mode 4
+static uint8_t PNG_fCalcPath(uint16_t a, uint16_t b, uint16_t c) {
+ uint16_t pa = b > c ? (b - c) : (c - b);
+ uint16_t pb = a > c ? (a - c) : (c - a);
+ uint16_t pc = a + b > c + c ? (a + b - c - c) : (c + c - a - b);
+
+ if (pc < pa && pc < pb)
+ return (uint8_t)c;
+ if (pb < pa)
+ return (uint8_t)b;
+ return (uint8_t)a;
+}
+
+// Scan-line filter type 0
+static bool_t PNG_unfilter_type0(PNG_decode *d) { // PNG filter method 0
+ uint8_t ft;
+ unsigned i;
+
+ // Get the filter type and check for validity (eg not EOF)
+ ft = PNG_zGetByte(d);
+ if (ft > 0x04)
+ return FALSE;
+
+ // Uncompress the scan line
+ for(i = 0; i < d->f.scanbytes; i++)
+ d->f.line[i] = PNG_zGetByte(d);
+
+ // Adjust the scan line based on the filter type
+ // 0 = no adjustment
+ switch(ft) {
+ case 1:
+ for(i = d->f.bytewidth; i < d->f.scanbytes; i++)
+ d->f.line[i] += d->f.line[i - d->f.bytewidth];
+ break;
+ case 2:
+ if (d->f.prev) {
+ for(i = 0; i < d->f.scanbytes; i++)
+ d->f.line[i] += d->f.prev[i];
+ }
+ break;
+ case 3:
+ if (d->f.prev) {
+ for(i = 0; i < d->f.bytewidth; i++)
+ d->f.line[i] += d->f.prev[i] / 2;
+ for( ; i < d->f.scanbytes; i++)
+ d->f.line[i] += (d->f.line[i - d->f.bytewidth] + d->f.prev[i]) / 2;
+ } else {
+ for(i = d->f.bytewidth; i < d->f.scanbytes; i++)
+ d->f.line[i] += d->f.line[i - d->f.bytewidth] / 2;
+ }
+ break;
+ case 4:
+ if (d->f.prev) {
+ for(i = 0; i < d->f.bytewidth; i++)
+ d->f.line[i] += d->f.prev[i]; // PNG_fCalcPath(0, val, 0) is always val
+ for( ; i < d->f.scanbytes; i++)
+ d->f.line[i] += PNG_fCalcPath(d->f.line[i - d->f.bytewidth], d->f.prev[i], d->f.prev[i - d->f.bytewidth]);
+ } else {
+ for(i = d->f.bytewidth; i < d->f.scanbytes; i++)
+ d->f.line[i] += d->f.line[i - d->f.bytewidth]; // PNG_fCalcPath(val, 0, 0) is always val
+ }
+ break;
+ }
+
+ return TRUE;
+}
+
+/*-----------------------------------------------------------------
+ * Scan-line output and color conversion functions
+ *---------------------------------------------------------------*/
+
+#if GDISP_NEED_IMAGE_PNG_GRAYSCALE_124
+ static void PNG_OutGRAY124(PNG_decode *d) {
+ unsigned i;
+ PNG_info *pinfo;
+ uint8_t px;
+ uint8_t bits;
+
+ pinfo = d->pinfo;
+ for(i = 0; i < d->f.scanbytes; i++) {
+ for(bits = 8; bits; bits -= pinfo->bitdepth) {
+ px = (d->f.line[i] >> (bits - pinfo->bitdepth)) & ((1U << pinfo->bitdepth)-1);
+ #if GDISP_NEED_IMAGE_PNG_TRANSPARENCY
+ if ((pinfo->flags & PNG_FLG_TRANSPARENT) && (uint16_t)px == pinfo->trans_r) {
+ #if GDISP_NEED_IMAGE_PNG_BACKGROUND
+ if ((pinfo->flags & PNG_FLG_BACKGROUND)) {
+ PNG_oColor(&d->o, pinfo->bg);
+ continue;
+ }
+ #endif
+ PNG_oTransparent(&d->o);
+ continue;
+ }
+ #endif
+ px = px << (8-pinfo->bitdepth);
+ if (px >= 0x80) px += ((1U << (8-pinfo->bitdepth))-1);
+ PNG_oColor(&d->o, LUMA2COLOR(px));
+ }
+ }
+ }
+#endif
+#if GDISP_NEED_IMAGE_PNG_GRAYSCALE_8
+ static void PNG_OutGRAY8(PNG_decode *d) {
+ unsigned i;
+ uint8_t px;
+ #if GDISP_NEED_IMAGE_PNG_TRANSPARENCY
+ PNG_info *pinfo = d->pinfo;
+ #endif
+
+ for(i = 0; i < d->f.scanbytes; i++) {
+ px = d->f.line[i];
+ #if GDISP_NEED_IMAGE_PNG_TRANSPARENCY
+ if ((pinfo->flags & PNG_FLG_TRANSPARENT) && (uint16_t)px == pinfo->trans_r) {
+ #if GDISP_NEED_IMAGE_PNG_BACKGROUND
+ if ((pinfo->flags & PNG_FLG_BACKGROUND)) {
+ PNG_oColor(&d->o, pinfo->bg);
+ continue;
+ }
+ #endif
+ PNG_oTransparent(&d->o);
+ continue;
+ }
+ #endif
+ PNG_oColor(&d->o, LUMA2COLOR(px));
+ }
+ }
+#endif
+#if GDISP_NEED_IMAGE_PNG_GRAYSCALE_16
+ static void PNG_OutGRAY16(PNG_decode *d) {
+ unsigned i;
+ uint8_t px;
+ #if GDISP_NEED_IMAGE_PNG_TRANSPARENCY
+ PNG_info *pinfo = d->pinfo;
+ #endif
+
+ for(i = 0; i < d->f.scanbytes; i+=2) {
+ px = d->f.line[i];
+ #if GDISP_NEED_IMAGE_PNG_TRANSPARENCY
+ if ((pinfo->flags & PNG_FLG_TRANSPARENT) && gdispImageGetBE16(d->f.line, i) == pinfo->trans_r) {
+ #if GDISP_NEED_IMAGE_PNG_BACKGROUND
+ if ((pinfo->flags & PNG_FLG_BACKGROUND)) {
+ PNG_oColor(&d->o, pinfo->bg);
+ continue;
+ }
+ #endif
+ PNG_oTransparent(&d->o);
+ continue;
+ }
+ #endif
+ PNG_oColor(&d->o, LUMA2COLOR(px));
+ }
+ }
+#endif
+#if GDISP_NEED_IMAGE_PNG_RGB_8
+ static void PNG_OutRGB8(PNG_decode *d) {
+ unsigned i;
+ #if GDISP_NEED_IMAGE_PNG_TRANSPARENCY
+ PNG_info *pinfo = d->pinfo;
+ #endif
+
+ for(i = 0; i < d->f.scanbytes; i+=3) {
+ #if GDISP_NEED_IMAGE_PNG_TRANSPARENCY
+ if ((pinfo->flags & PNG_FLG_TRANSPARENT)
+ && (uint16_t)d->f.line[i+0] == pinfo->trans_r
+ && (uint16_t)d->f.line[i+1] == pinfo->trans_g
+ && (uint16_t)d->f.line[i+2] == pinfo->trans_b) {
+ #if GDISP_NEED_IMAGE_PNG_BACKGROUND
+ if ((pinfo->flags & PNG_FLG_BACKGROUND)) {
+ PNG_oColor(&d->o, pinfo->bg);
+ continue;
+ }
+ #endif
+ PNG_oTransparent(&d->o);
+ continue;
+ }
+ #endif
+ PNG_oColor(&d->o, RGB2COLOR(d->f.line[i+0], d->f.line[i+1], d->f.line[i+2]));
+ }
+ }
+#endif
+#if GDISP_NEED_IMAGE_PNG_RGB_16
+ static void PNG_OutRGB16(PNG_decode *d) {
+ unsigned i;
+ #if GDISP_NEED_IMAGE_PNG_TRANSPARENCY
+ PNG_info *pinfo = d->pinfo;
+ #endif
+
+ for(i = 0; i < d->f.scanbytes; i+=6) {
+ #if GDISP_NEED_IMAGE_PNG_TRANSPARENCY
+ if ((pinfo->flags & PNG_FLG_TRANSPARENT)
+ && gdispImageGetBE16(d->f.line, i+0) == pinfo->trans_r
+ && gdispImageGetBE16(d->f.line, i+2) == pinfo->trans_g
+ && gdispImageGetBE16(d->f.line, i+4) == pinfo->trans_b) {
+ #if GDISP_NEED_IMAGE_PNG_BACKGROUND
+ if ((pinfo->flags & PNG_FLG_BACKGROUND)) {
+ PNG_oColor(&d->o, pinfo->bg);
+ continue;
+ }
+ #endif
+ PNG_oTransparent(&d->o);
+ continue;
+ }
+ #endif
+ PNG_oColor(&d->o, RGB2COLOR(d->f.line[i+0], d->f.line[i+2], d->f.line[i+4]));
+ }
+ }
+#endif
+#if GDISP_NEED_IMAGE_PNG_PALETTE_124
+ static void PNG_OutPAL124(PNG_decode *d) {
+ unsigned i;
+ PNG_info *pinfo;
+ unsigned idx;
+ uint8_t bits;
+
+ pinfo = d->pinfo;
+ for(i = 0; i < d->f.scanbytes; i++) {
+ for(bits = 8; bits; bits -= pinfo->bitdepth) {
+ idx = (d->f.line[i] >> (bits - pinfo->bitdepth)) & ((1U << pinfo->bitdepth)-1);
+
+ if ((uint16_t)idx >= pinfo->palsize) {
+ PNG_oColor(&d->o, RGB2COLOR(0, 0, 0));
+ continue;
+ }
+ idx *= 4;
+
+ #define pix_color RGB2COLOR(pinfo->palette[idx], pinfo->palette[idx+1], pinfo->palette[idx+2])
+ #define pix_alpha pinfo->palette[idx+3]
+
+ #if GDISP_NEED_IMAGE_PNG_TRANSPARENCY
+ #if GDISP_NEED_IMAGE_PNG_BACKGROUND
+ if (pix_alpha != 255 && (pinfo->flags & PNG_FLG_BACKGROUND)) {
+ PNG_oColor(&d->o, gdispBlendColor(pix_color, pinfo->bg, pix_alpha));
+ continue;
+ }
+ #endif
+ #if GDISP_NEED_IMAGE_PNG_ALPHACLIFF > 0
+ if (pix_alpha < GDISP_NEED_IMAGE_PNG_ALPHACLIFF) {
+ PNG_oTransparent(&d->o);
+ continue;
+ }
+ #endif
+ #endif
+
+ PNG_oColor(&d->o, pix_color);
+
+ #undef pix_color
+ #undef pix_alpha
+ }
+ }
+ }
+#endif
+#if GDISP_NEED_IMAGE_PNG_PALETTE_8
+ static void PNG_OutPAL8(PNG_decode *d) {
+ unsigned i;
+ PNG_info *pinfo;
+ unsigned idx;
+
+ pinfo = d->pinfo;
+ for(i = 0; i < d->f.scanbytes; i++) {
+ idx = (unsigned)d->f.line[i];
+
+ if ((uint16_t)idx >= pinfo->palsize) {
+ PNG_oColor(&d->o, RGB2COLOR(0, 0, 0));
+ continue;
+ }
+ idx *= 4;
+
+ #define pix_color RGB2COLOR(pinfo->palette[idx], pinfo->palette[idx+1], pinfo->palette[idx+2])
+ #define pix_alpha pinfo->palette[idx+3]
+
+ #if GDISP_NEED_IMAGE_PNG_TRANSPARENCY
+ #if GDISP_NEED_IMAGE_PNG_BACKGROUND
+ if (pix_alpha != 255 && (pinfo->flags & PNG_FLG_BACKGROUND)) {
+ PNG_oColor(&d->o, gdispBlendColor(pix_color, pinfo->bg, pix_alpha));
+ continue;
+ }
+ #endif
+ #if GDISP_NEED_IMAGE_PNG_ALPHACLIFF > 0
+ if (pix_alpha < GDISP_NEED_IMAGE_PNG_ALPHACLIFF) {
+ PNG_oTransparent(&d->o);
+ continue;
+ }
+ #endif
+ #endif
+
+ PNG_oColor(&d->o, pix_color);
+
+ #undef pix_color
+ #undef pix_alpha
+ }
+ }
+#endif
+#if GDISP_NEED_IMAGE_PNG_GRAYALPHA_8
+ static void PNG_OutGRAYA8(PNG_decode *d) {
+ unsigned i;
+ #if GDISP_NEED_IMAGE_PNG_BACKGROUND
+ PNG_info *pinfo = d->pinfo;
+ #endif
+
+ for(i = 0; i < d->f.scanbytes; i+=2) {
+ #define pix_color LUMA2COLOR(d->f.line[i])
+ #define pix_alpha d->f.line[i+1]
+
+ #if GDISP_NEED_IMAGE_PNG_BACKGROUND
+ if (pix_alpha != 255 && (pinfo->flags & PNG_FLG_BACKGROUND)) {
+ PNG_oColor(&d->o, gdispBlendColor(pix_color, pinfo->bg, pix_alpha));
+ continue;
+ }
+ #endif
+ #if GDISP_NEED_IMAGE_PNG_ALPHACLIFF > 0
+ if (pix_alpha < GDISP_NEED_IMAGE_PNG_ALPHACLIFF) {
+ PNG_oTransparent(&d->o);
+ continue;
+ }
+ #endif
+
+ PNG_oColor(&d->o, pix_color);
+
+ #undef pix_color
+ #undef pix_alpha
+ }
+ }
+#endif
+#if GDISP_NEED_IMAGE_PNG_GRAYALPHA_16
+ static void PNG_OutGRAYA16(PNG_decode *d) {
+ unsigned i;
+ #if GDISP_NEED_IMAGE_PNG_BACKGROUND
+ PNG_info *pinfo = d->pinfo;
+ #endif
+
+ for(i = 0; i < d->f.scanbytes; i+=4) {
+ #define pix_color LUMA2COLOR(d->f.line[i])
+ #define pix_alpha d->f.line[i+2]
+
+ #if GDISP_NEED_IMAGE_PNG_BACKGROUND
+ if (pix_alpha != 255 && (pinfo->flags & PNG_FLG_BACKGROUND)) {
+ PNG_oColor(&d->o, gdispBlendColor(pix_color, pinfo->bg, pix_alpha));
+ continue;
+ }
+ #endif
+ #if GDISP_NEED_IMAGE_PNG_ALPHACLIFF > 0
+ if (pix_alpha < GDISP_NEED_IMAGE_PNG_ALPHACLIFF) {
+ PNG_oTransparent(&d->o);
+ continue;
+ }
+ #endif
+
+ PNG_oColor(&d->o, pix_color);
+
+ #undef pix_color
+ #undef pix_alpha
+ }
+ }
+#endif
+#if GDISP_NEED_IMAGE_PNG_RGBALPHA_8
+ static void PNG_OutRGBA8(PNG_decode *d) {
+ unsigned i;
+ #if GDISP_NEED_IMAGE_PNG_BACKGROUND
+ PNG_info *pinfo = d->pinfo;
+ #endif
+
+ for(i = 0; i < d->f.scanbytes; i+=4) {
+ #define pix_color RGB2COLOR(d->f.line[i+0], d->f.line[i+1], d->f.line[i+2])
+ #define pix_alpha d->f.line[i+3]
+
+ #if GDISP_NEED_IMAGE_PNG_BACKGROUND
+ if (pix_alpha != 255 && (pinfo->flags & PNG_FLG_BACKGROUND)) {
+ PNG_oColor(&d->o, gdispBlendColor(pix_color, pinfo->bg, pix_alpha));
+ continue;
+ }
+ #endif
+ #if GDISP_NEED_IMAGE_PNG_ALPHACLIFF > 0
+ if (pix_alpha < GDISP_NEED_IMAGE_PNG_ALPHACLIFF) {
+ PNG_oTransparent(&d->o);
+ continue;
+ }
+ #endif
+
+ PNG_oColor(&d->o, pix_color);
+
+ #undef pix_color
+ #undef pix_alpha
+ }
+ }
+#endif
+#if GDISP_NEED_IMAGE_PNG_RGBALPHA_16
+ static void PNG_OutRGBA16(PNG_decode *d) {
+ unsigned i;
+ #if GDISP_NEED_IMAGE_PNG_BACKGROUND
+ PNG_info *pinfo = d->pinfo;
+ #endif
+
+ for(i = 0; i < d->f.scanbytes; i+=8) {
+ #define pix_color RGB2COLOR(d->f.line[i+0], d->f.line[i+2], d->f.line[i+4])
+ #define pix_alpha d->f.line[i+6]
+
+ #if GDISP_NEED_IMAGE_PNG_BACKGROUND
+ if (pix_alpha != 255 && (pinfo->flags & PNG_FLG_BACKGROUND)) {
+ PNG_oColor(&d->o, gdispBlendColor(pix_color, pinfo->bg, pix_alpha));
+ continue;
+ }
+ #endif
+ #if GDISP_NEED_IMAGE_PNG_ALPHACLIFF > 0
+ if (pix_alpha < GDISP_NEED_IMAGE_PNG_ALPHACLIFF) {
+ PNG_oTransparent(&d->o);
+ continue;
+ }
+ #endif
+
+ PNG_oColor(&d->o, pix_color);
+
+ #undef pix_color
+ #undef pix_alpha
+ }
+ }
+#endif
+
+/*-----------------------------------------------------------------
+ * Public PNG functions
+ *---------------------------------------------------------------*/
+
+void gdispImageClose_PNG(gdispImage *img) {
+ PNG_info *pinfo;
+
+ pinfo = (PNG_info *)img->priv;
+ if (pinfo) {
+ if (pinfo->palette)
+ gdispImageFree(img, (void *)pinfo->palette, pinfo->palsize*4);
+ if (pinfo->cache)
+ gdispImageFree(img, (void *)pinfo->cache, pinfo->cachesz);
+ gdispImageFree(img, (void *)pinfo, sizeof(PNG_info));
+ img->priv = 0;
+ }
+}
+
+gdispImageError gdispImageOpen_PNG(gdispImage *img) {
+ PNG_info *pinfo;
+ uint32_t pos;
+ uint32_t len;
+ uint8_t buf[13];
+
+ /* Read the file identifier */
+ if (gfileRead(img->f, buf, 8) != 8)
+ return GDISP_IMAGE_ERR_BADFORMAT; // It can't be us
+
+ // Check the PNG signature
+ if(buf[0] != 137 || buf[1] != 80 || buf[2] != 78 || buf[3] != 71 || buf[4] != 13 || buf[5] != 10 || buf[6] != 26 || buf[7] != 10)
+ return GDISP_IMAGE_ERR_BADFORMAT; // It can't be us
+
+ /* We know we are a PNG format image */
+ img->flags = 0;
+ img->priv = 0;
+ img->type = GDISP_IMAGE_TYPE_PNG;
+
+ /* Allocate our private area */
+ if (!(img->priv = gdispImageAlloc(img, sizeof(PNG_info))))
+ return GDISP_IMAGE_ERR_NOMEMORY;
+
+ /* Initialise the essential bits in the private area */
+ pinfo = (PNG_info *)img->priv;
+ pinfo->flags = 0;
+ pinfo->cache = 0;
+ pinfo->trans_r = 0;
+ pinfo->trans_g = 0;
+ pinfo->trans_b = 0;
+ #if GDISP_NEED_IMAGE_PNG_PALETTE_124 || GDISP_NEED_IMAGE_PNG_PALETTE_8
+ pinfo->palsize = 0;
+ pinfo->palette = 0;
+ #endif
+
+ // Cycle the chunks to get other information
+ for(pos = 8; ; pos += len+12, gfileSetPos(img->f, pos)) {
+ // Get a chunk header
+ if (gfileRead(img->f, buf, 8) != 8)
+ goto exit_baddata;
+
+ // Calculate the chunk length
+ len = gdispImageGetAlignedBE32(buf, 0);
+
+ // Process the interesting information chunks
+ switch (gdispImageGetAlignedBE32(buf, 4)) {
+ case 0x49484452: // "IHDR" - Header block
+
+ // Check if the header is already done
+ if ((pinfo->flags & PNG_FLG_HEADERDONE))
+ goto exit_baddata;
+
+ // Read the image parameters
+ if (len < 13 || gfileRead(img->f, buf, 13) != 13)
+ goto exit_baddata;
+
+ img->width = gdispImageGetAlignedBE16(buf, 2);
+ img->height = gdispImageGetAlignedBE16(buf, 6);
+ pinfo->bitdepth = gdispImageGetVar(uint8_t, buf, 8);
+ pinfo->mode = gdispImageGetVar(uint8_t, buf, 9);
+ if (gdispImageGetVar(uint8_t, buf, 12)) {
+ pinfo->flags |= PNG_FLG_INTERLACE;
+ #if !GDISP_NEED_IMAGE_PNG_INTERLACED
+ goto exit_unsupported;
+ #endif
+ }
+
+ // Check width and height, filter, compression and interlacing
+ if (gdispImageGetVar(uint16_t, buf, 0) != 0 || img->width <= 0 // width
+ || gdispImageGetVar(uint16_t, buf, 4) != 0 || img->height <= 0 // height
+ || gdispImageGetVar(uint8_t, buf, 10) != 0 // compression
+ || gdispImageGetVar(uint8_t, buf, 11) != 0 // filter
+ || gdispImageGetVar(uint8_t, buf, 12) > 1 // interlace
+ )
+ goto exit_unsupported;
+
+ // Check mode, bitdepth and calculate bits per pixel
+ switch(pinfo->mode) {
+ #if GDISP_NEED_IMAGE_PNG_GRAYSCALE_124 || GDISP_NEED_IMAGE_PNG_GRAYSCALE_8 || GDISP_NEED_IMAGE_PNG_GRAYSCALE_16
+ case PNG_COLORMODE_GRAY:
+ switch(pinfo->bitdepth) {
+ #if GDISP_NEED_IMAGE_PNG_GRAYSCALE_124
+ case 1:
+ case 2:
+ case 4: pinfo->out = PNG_OutGRAY124; break;
+ #endif
+ #if GDISP_NEED_IMAGE_PNG_GRAYSCALE_8
+ case 8: pinfo->out = PNG_OutGRAY8; break;
+ #endif
+ #if GDISP_NEED_IMAGE_PNG_GRAYSCALE_16
+ case 16: pinfo->out = PNG_OutGRAY16; break;
+ #endif
+ default: goto exit_unsupported;
+ }
+ pinfo->bpp = pinfo->bitdepth;
+ break;
+ #endif
+ #if GDISP_NEED_IMAGE_PNG_RGB_8 || GDISP_NEED_IMAGE_PNG_RGB_16
+ case PNG_COLORMODE_RGB:
+ switch(pinfo->bitdepth) {
+ #if GDISP_NEED_IMAGE_PNG_RGB_8
+ case 8: pinfo->out = PNG_OutRGB8; break;
+ #endif
+ #if GDISP_NEED_IMAGE_PNG_RGB_16
+ case 16: pinfo->out = PNG_OutRGB16; break;
+ #endif
+ default: goto exit_unsupported;
+ }
+ pinfo->bpp = pinfo->bitdepth * 3;
+ break;
+ #endif
+ #if GDISP_NEED_IMAGE_PNG_PALETTE_124 || GDISP_NEED_IMAGE_PNG_PALETTE_8
+ case PNG_COLORMODE_PALETTE:
+ switch(pinfo->bitdepth) {
+ #if GDISP_NEED_IMAGE_PNG_PALETTE_124
+ case 1:
+ case 2:
+ case 4: pinfo->out = PNG_OutPAL124; break;
+ #endif
+ #if GDISP_NEED_IMAGE_PNG_PALETTE_8
+ case 8: pinfo->out = PNG_OutPAL8; break;
+ #endif
+ default: goto exit_unsupported;
+ }
+ pinfo->bpp = pinfo->bitdepth;
+ break;
+ #endif
+ #if GDISP_NEED_IMAGE_PNG_GRAYALPHA_8 || GDISP_NEED_IMAGE_PNG_GRAYALPHA_16
+ case PNG_COLORMODE_GRAYALPHA:
+ switch(pinfo->bitdepth) {
+ #if GDISP_NEED_IMAGE_PNG_GRAYALPHA_8
+ case 8: pinfo->out = PNG_OutGRAYA8; break;
+ #endif
+ #if GDISP_NEED_IMAGE_PNG_GRAYALPHA_16
+ case 16: pinfo->out = PNG_OutGRAYA16; break;
+ #endif
+ default: goto exit_unsupported;
+ }
+ pinfo->bpp = pinfo->bitdepth * 2;
+ break;
+ #endif
+ #if GDISP_NEED_IMAGE_PNG_RGBALPHA_8 || GDISP_NEED_IMAGE_PNG_RGBALPHA_16
+ case PNG_COLORMODE_RGBA:
+ switch(pinfo->bitdepth) {
+ #if GDISP_NEED_IMAGE_PNG_RGBALPHA_8
+ case 8: pinfo->out = PNG_OutRGBA8; break;
+ #endif
+ #if GDISP_NEED_IMAGE_PNG_RGBALPHA_16
+ case 16: pinfo->out = PNG_OutRGBA16; break;
+ #endif
+ default: goto exit_unsupported;
+ }
+ pinfo->bpp = pinfo->bitdepth * 4;
+ break;
+ #endif
+ default:
+ goto exit_unsupported;
+ }
+
+ pinfo->flags |= PNG_FLG_HEADERDONE;
+ break;
+
+ case 0x49454E44: // "IEND" - All done
+ goto exit_baddata; // Oops we didn't get any data.
+
+ case 0x49444154: // "IDAT" - Image Data
+
+ // Check if the header is already done
+ if (!(pinfo->flags & PNG_FLG_HEADERDONE))
+ goto exit_baddata;
+
+ #if GDISP_NEED_IMAGE_PNG_PALETTE_124 || GDISP_NEED_IMAGE_PNG_PALETTE_8
+ // Make sure a palette image actually has a palette
+ if (pinfo->mode == PNG_COLORMODE_PALETTE && !pinfo->palette)
+ goto exit_baddata;
+ #endif
+
+ // All good
+ return GDISP_IMAGE_ERR_OK;
+
+ #if GDISP_NEED_IMAGE_PNG_PALETTE_124 || GDISP_NEED_IMAGE_PNG_PALETTE_8
+ case 0x504C5445: // "PLTE" - Palette
+
+ // Check if the header is already done
+ if (!(pinfo->flags & PNG_FLG_HEADERDONE))
+ goto exit_baddata;
+
+ // Skip a palette if we don't need it.
+ if (pinfo->mode != PNG_COLORMODE_PALETTE)
+ break;
+
+ // Check the size and that we don't have one already
+ if (len > 3 * 256 || pinfo->palette)
+ goto exit_baddata;
+
+ // Allocate the palette
+ pinfo->palsize = len / 3;
+ if (!(pinfo->palette = gfxAlloc(pinfo->palsize * 4)))
+ goto exit_nonmem;
+
+ // Read the palette
+ {
+ uint16_t idx;
+ uint8_t *p;
+
+ for(idx=pinfo->palsize, p=pinfo->palette; idx; p += 4, idx--) {
+ if (gfileRead(img->f, p, 3) != 3)
+ goto exit_baddata;
+ p[3] = 255;
+ }
+ }
+
+ break;
+ #endif
+
+ #if GDISP_NEED_IMAGE_PNG_TRANSPARENCY
+ case 0x74524E53: // "tRNS" - Transparency
+
+ // Check if the header is already done
+ if (!(pinfo->flags & PNG_FLG_HEADERDONE))
+ goto exit_baddata;
+
+ // Transparency is handled differently depending on the mode
+ switch(pinfo->mode) {
+
+ #if GDISP_NEED_IMAGE_PNG_PALETTE_124 || GDISP_NEED_IMAGE_PNG_PALETTE_8
+ case PNG_COLORMODE_PALETTE:
+ if (len > pinfo->palsize)
+ goto exit_baddata;
+
+ // Adjust the palette
+ {
+ uint16_t idx;
+ uint8_t *p;
+
+ for(idx=len, p=pinfo->palette+3; idx; p += 4, idx--) {
+ if (gfileRead(img->f, p, 1) != 1)
+ goto exit_baddata;
+ }
+ }
+ break;
+ #endif
+
+ #if GDISP_NEED_IMAGE_PNG_GRAYSCALE_124 || GDISP_NEED_IMAGE_PNG_GRAYSCALE_8 || GDISP_NEED_IMAGE_PNG_GRAYSCALE_16
+ case PNG_COLORMODE_GRAY:
+ // Read the transparency color
+ if (len != 2 || gfileRead(img->f, buf, 2) != 2)
+ goto exit_baddata;
+
+ pinfo->flags |= PNG_FLG_TRANSPARENT;
+ pinfo->trans_r = gdispImageGetAlignedBE16(buf, 0);
+ break;
+ #endif
+ #if GDISP_NEED_IMAGE_PNG_RGB_8 || GDISP_NEED_IMAGE_PNG_RGB_16
+ case PNG_COLORMODE_RGB:
+ // Read the transparency color
+ if (len != 6 || gfileRead(img->f, buf, 6) != 6)
+ goto exit_baddata;
+
+ pinfo->flags |= PNG_FLG_TRANSPARENT;
+ pinfo->trans_r = gdispImageGetAlignedBE16(buf, 0);
+ pinfo->trans_g = gdispImageGetAlignedBE16(buf, 2);
+ pinfo->trans_b = gdispImageGetAlignedBE16(buf, 4);
+ break;
+ #endif
+ default:
+ goto exit_unsupported;
+ }
+
+ break;
+ #endif
+
+ #if GDISP_NEED_IMAGE_PNG_BACKGROUND
+ case 0x624B4744: // "bKGD" - Background
+
+ // Check if the header is already done
+ if (!(pinfo->flags & PNG_FLG_HEADERDONE))
+ goto exit_baddata;
+
+ pinfo->flags |= PNG_FLG_BACKGROUND;
+
+ // Background data is handled differently depending on the mode
+ switch(pinfo->mode) {
+
+ #if GDISP_NEED_IMAGE_PNG_PALETTE_124 || GDISP_NEED_IMAGE_PNG_PALETTE_8
+ case PNG_COLORMODE_PALETTE:
+ if (!pinfo->palette || len < 1 || gfileRead(img->f, buf, 1) != 1 || (uint16_t)buf[0] >= pinfo->palsize)
+ goto exit_baddata;
+ pinfo->bg = RGB2COLOR(pinfo->palette[((unsigned)buf[0])*4+0],
+ pinfo->palette[((unsigned)buf[0])*4+1],
+ pinfo->palette[((unsigned)buf[0])*4+2]);
+ break;
+ #endif
+
+ #if GDISP_NEED_IMAGE_PNG_GRAYSCALE_124 || GDISP_NEED_IMAGE_PNG_GRAYSCALE_8 || GDISP_NEED_IMAGE_PNG_GRAYSCALE_16 || GDISP_NEED_IMAGE_PNG_GRAYALPHA_8 || GDISP_NEED_IMAGE_PNG_GRAYALPHA_16
+ case PNG_COLORMODE_GRAY:
+ case PNG_COLORMODE_GRAYALPHA:
+ if (len < 2 || gfileRead(img->f, buf, 2) != 2)
+ goto exit_baddata;
+ switch(pinfo->bitdepth) {
+ #if GDISP_NEED_IMAGE_PNG_GRAYSCALE_124
+ case 1:
+ case 2:
+ case 4:
+ buf[1] <<= 8-pinfo->bitdepth;
+ if (buf[1] >= 0x80)
+ buf[1] += ((1U << (8-pinfo->bitdepth))-1);
+ pinfo->bg = LUMA2COLOR(buf[1]);
+ break;
+ #endif
+ #if GDISP_NEED_IMAGE_PNG_GRAYSCALE_8 || GDISP_NEED_IMAGE_PNG_GRAYALPHA_8
+ case 8:
+ pinfo->bg = LUMA2COLOR(buf[1]);
+ break;
+ #endif
+ #if GDISP_NEED_IMAGE_PNG_GRAYSCALE_16 || GDISP_NEED_IMAGE_PNG_GRAYALPHA_16
+ case 16:
+ pinfo->bg = LUMA2COLOR(buf[0]);
+ break;
+ #endif
+ }
+ break;
+ #endif
+ #if GDISP_NEED_IMAGE_PNG_RGB_8 || GDISP_NEED_IMAGE_PNG_RGB_16 || GDISP_NEED_IMAGE_PNG_RGBALPHA_8 || GDISP_NEED_IMAGE_PNG_RGBALPHA_16
+ case PNG_COLORMODE_RGB:
+ case PNG_COLORMODE_RGBA:
+ if (len < 6 || gfileRead(img->f, buf, 6) != 6)
+ goto exit_baddata;
+
+ #if GDISP_NEED_IMAGE_PNG_RGB_16 || GDISP_NEED_IMAGE_PNG_RGBALPHA_16
+ if (pinfo->bitdepth == 16) {
+ pinfo->bg = RGB2COLOR(buf[0], buf[2], buf[4]);
+ } else
+ #endif
+ pinfo->bg = RGB2COLOR(buf[1], buf[3], buf[5]);
+ break;
+ #endif
+ default:
+ goto exit_unsupported;
+ }
+ break;
+ #endif
+
+ }
+ }
+exit_baddata:
+ gdispImageClose_PNG(img);
+ return GDISP_IMAGE_ERR_BADDATA;
+exit_unsupported:
+ gdispImageClose_PNG(img);
+ return GDISP_IMAGE_ERR_UNSUPPORTED;
+exit_nonmem:
+ gdispImageClose_PNG(img);
+ return GDISP_IMAGE_ERR_NOMEMORY;
+}
+
+gdispImageError gdispGImageDraw_PNG(GDisplay *g, gdispImage *img, coord_t x, coord_t y, coord_t cx, coord_t cy, coord_t sx, coord_t sy) {
+ PNG_info *pinfo;
+ PNG_decode *d;
+
+ // Allocate the space to decode with including space for 2 full scan lines for filtering.
+ pinfo = (PNG_info *)img->priv;
+ if (!(d = gdispImageAlloc(img, sizeof(PNG_decode) + (img->width * pinfo->bpp + 7) / 4)))
+ return GDISP_IMAGE_ERR_NOMEMORY;
+
+
+ // Initialise the decoder
+ d->img = img;
+ d->pinfo = pinfo;
+ PNG_iInit(d);
+ PNG_oInit(&d->o, g, x, y, cx, cy, sx, sy);
+ PNG_zInit(&d->z);
+
+ // Process the zlib inflate header
+ if (!PNG_zGetHeader(d))
+ goto exit_baddata;
+
+ #if GDISP_NEED_IMAGE_PNG_INTERLACED
+ if ((pinfo->flags & PNG_FLG_INTERLACE)) {
+ // Interlaced decoding
+ #error "PNG Decoder: Interlaced PNG's are not supported yet!"
+ } else
+ #endif
+ {
+ // Non-interlaced decoding
+ PNG_fInit(&d->f, (uint8_t *)(d+1), (pinfo->bpp + 7) / 8, (img->width * pinfo->bpp + 7) / 8);
+ for(y = 0; y < sy+cy; PNG_fNext(&d->f), y++) {
+ if (!PNG_unfilter_type0(d))
+ goto exit_baddata;
+ if (PNG_oStartY(&d->o, y)) {
+ pinfo->out(d);
+ PNG_oFlush(&d->o);
+ }
+ }
+ }
+
+ // Clean up
+ gdispImageFree(img, d, sizeof(PNG_decode) + (img->width * pinfo->bpp + 7) / 4);
+ return GDISP_IMAGE_ERR_OK;
+
+exit_baddata:
+ gdispImageFree(img, d, sizeof(PNG_decode) + (img->width * pinfo->bpp + 7) / 4);
+ return GDISP_IMAGE_ERR_BADDATA;
+}
+
+gdispImageError gdispImageCache_PNG(gdispImage *img) {
+ PNG_info *pinfo;
+ unsigned chunknext;
+ unsigned chunklen;
+ uint8_t *pcache;
+ uint8_t buf[8];
+
+ // If we are already cached - just return OK
+ pinfo = (PNG_info *)img->priv;
+ if (pinfo->cache)
+ return GDISP_IMAGE_ERR_OK;
+
+ // Calculate the size of all the image data blocks in the image
+ pinfo->cachesz = 0;
+ chunknext = 8;
+ while(1) {
+ // Find a new chunk
+ gfileSetPos(img->f, chunknext);
+ if (gfileRead(img->f, buf, 8) != 8)
+ return GDISP_IMAGE_ERR_BADDATA;
+
+ // Calculate the chunk length and next chunk
+ chunklen = gdispImageGetAlignedBE32(buf, 0);
+ chunknext += chunklen + 12;
+
+ // Process only image data chunks
+ switch (gdispImageGetAlignedBE32(buf, 4)) {
+ case 0x49444154: // "IDAT" - Image Data
+ pinfo->cachesz += chunklen;
+ break;
+ case 0x49454E44: // "IEND" - All done
+ if (!pinfo->cachesz)
+ return GDISP_IMAGE_ERR_BADDATA;
+ goto gotsize;
+ }
+ }
+
+gotsize:
+ // Allocate the cache
+ if (!(pcache = gdispImageAlloc(img, pinfo->cachesz)))
+ return GDISP_IMAGE_ERR_NOMEMORY;
+
+ pinfo->cache = pcache;
+
+ // Read the image data into the cache
+ chunknext = 8;
+ while(1) {
+ // Find a new chunk
+ gfileSetPos(img->f, chunknext);
+ if (gfileRead(img->f, buf, 8) != 8)
+ goto baddata;
+
+ // Calculate the chunk length and next chunk
+ chunklen = gdispImageGetAlignedBE32(buf, 0);
+ chunknext += chunklen + 12;
+
+ // Process only image data chunks
+ switch (gdispImageGetAlignedBE32(buf, 4)) {
+ case 0x49444154: // "IDAT" - Image Data
+ if (gfileRead(img->f, pcache, chunklen) != chunklen)
+ goto baddata;
+ pcache += chunklen;
+ break;
+ case 0x49454E44: // "IEND" - All done
+ return GDISP_IMAGE_ERR_OK;
+ }
+ }
+
+baddata:
+ // Oops - can't read the data. Throw away the cache.
+ gdispImageFree(img, pinfo->cache, pinfo->cachesz);
+ pinfo->cache = 0;
+ return GDISP_IMAGE_ERR_BADDATA;
+}
+
+delaytime_t gdispImageNext_PNG(gdispImage *img) {
+ (void) img;
+
+ /* No more frames/pages */
+ return TIME_INFINITE;
+}
#endif /* GFX_USE_GDISP && GDISP_NEED_IMAGE && GDISP_NEED_IMAGE_PNG */
diff --git a/src/gdisp/gdisp_options.h b/src/gdisp/gdisp_options.h
index c3eec451..4c004808 100644
--- a/src/gdisp/gdisp_options.h
+++ b/src/gdisp/gdisp_options.h
@@ -383,6 +383,127 @@
/**
* @}
*
+ * @name GDISP PNG Image Options
+ * @pre GDISP_NEED_IMAGE and GDISP_NEED_IMAGE_PNG must be TRUE
+ * @{
+ */
+ /**
+ * @brief Is PNG Interlaced image decoding required.
+ * @details Defaults to FALSE
+ * @note Currently not supported due to the complex decoding and display requirements
+ */
+ #ifndef GDISP_NEED_IMAGE_PNG_INTERLACED
+ #define GDISP_NEED_IMAGE_PNG_INTERLACED FALSE
+ #endif
+ /**
+ * @brief Is PNG image transparency processed.
+ * @details Defaults to TRUE
+ */
+ #ifndef GDISP_NEED_IMAGE_PNG_TRANSPARENCY
+ #define GDISP_NEED_IMAGE_PNG_TRANSPARENCY TRUE
+ #endif
+ /**
+ * @brief Is PNG background data processed.
+ * @details Defaults to TRUE
+ * @note If the background is specified in the image file and this define is TRUE,
+ * that background color is used for transparency and alpha blending.
+ */
+ #ifndef GDISP_NEED_IMAGE_PNG_BACKGROUND
+ #define GDISP_NEED_IMAGE_PNG_BACKGROUND TRUE
+ #endif
+ /**
+ * @brief What is the cliff between non-blended alpha pixels being displayed or not.
+ * @details Range of 0 to 255
+ * @note If GDISP_NEED_IMAGE_PNG_BACKGROUND is TRUE and the PNG file contains a
+ * background color then the pixel will be blended with the background color
+ * according to the alpha.
+ * If not then no blending occurs. The pixel will either be set or not.
+ * Any alpha value greater or equal to this number will be displayed.
+ * Anything less than this number is not displayed.
+ */
+ #ifndef GDISP_NEED_IMAGE_PNG_ALPHACLIFF
+ #define GDISP_NEED_IMAGE_PNG_ALPHACLIFF 32
+ #endif
+ /**
+ * @brief Is 1, 2 and 4 bit PNG palettized image decoding required.
+ * @details Defaults to TRUE
+ */
+ #ifndef GDISP_NEED_IMAGE_PNG_PALETTE_124
+ #define GDISP_NEED_IMAGE_PNG_PALETTE_124 TRUE
+ #endif
+ /**
+ * @brief Is 8 bit PNG palettized image decoding required.
+ * @details Defaults to TRUE
+ */
+ #ifndef GDISP_NEED_IMAGE_PNG_PALETTE_8
+ #define GDISP_NEED_IMAGE_PNG_PALETTE_8 TRUE
+ #endif
+ /**
+ * @brief Is 1,2 and 4 bit PNG grayscale image decoding required.
+ * @details Defaults to TRUE
+ */
+ #ifndef GDISP_NEED_IMAGE_PNG_GRAYSCALE_124
+ #define GDISP_NEED_IMAGE_PNG_GRAYSCALE_124 TRUE
+ #endif
+ /**
+ * @brief Is 8 bit PNG grayscale image decoding required.
+ * @details Defaults to TRUE
+ */
+ #ifndef GDISP_NEED_IMAGE_PNG_GRAYSCALE_8
+ #define GDISP_NEED_IMAGE_PNG_GRAYSCALE_8 TRUE
+ #endif
+ /**
+ * @brief Is 16 bit PNG grayscale image decoding required.
+ * @details Defaults to TRUE
+ */
+ #ifndef GDISP_NEED_IMAGE_PNG_GRAYSCALE_16
+ #define GDISP_NEED_IMAGE_PNG_GRAYSCALE_16 TRUE
+ #endif
+ /**
+ * @brief Is 8 bit PNG grayscale with 8 bit alpha image decoding required.
+ * @details Defaults to TRUE
+ */
+ #ifndef GDISP_NEED_IMAGE_PNG_GRAYALPHA_8
+ #define GDISP_NEED_IMAGE_PNG_GRAYALPHA_8 TRUE
+ #endif
+ /**
+ * @brief Is 16 bit PNG grayscale with 16 bit alpha image decoding required.
+ * @details Defaults to TRUE
+ */
+ #ifndef GDISP_NEED_IMAGE_PNG_GRAYALPHA_16
+ #define GDISP_NEED_IMAGE_PNG_GRAYALPHA_16 TRUE
+ #endif
+ /**
+ * @brief Is 8/8/8 bit PNG RGB image decoding required.
+ * @details Defaults to TRUE
+ */
+ #ifndef GDISP_NEED_IMAGE_PNG_RGB_8
+ #define GDISP_NEED_IMAGE_PNG_RGB_8 TRUE
+ #endif
+ /**
+ * @brief Is 16/16/16 bit PNG RGB image decoding required.
+ * @details Defaults to TRUE
+ */
+ #ifndef GDISP_NEED_IMAGE_PNG_RGB_16
+ #define GDISP_NEED_IMAGE_PNG_RGB_16 TRUE
+ #endif
+ /**
+ * @brief Is 8/8/8 bit PNG RGB with 8 bit alpha image decoding required.
+ * @details Defaults to TRUE
+ */
+ #ifndef GDISP_NEED_IMAGE_PNG_RGBALPHA_8
+ #define GDISP_NEED_IMAGE_PNG_RGBALPHA_8 TRUE
+ #endif
+ /**
+ * @brief Is 16/16/16 bit PNG RGB with 16 bit alpha image decoding required.
+ * @details Defaults to TRUE
+ */
+ #ifndef GDISP_NEED_IMAGE_PNG_RGBALPHA_16
+ #define GDISP_NEED_IMAGE_PNG_RGBALPHA_16 TRUE
+ #endif
+/**
+ * @}
+ *
* @name GDISP Text Rendering Options
* @{
*/