ugfx/src/gdisp/gdisp_image_png.c

1632 lines
45 KiB
C

/*
* This file is subject to the terms of the GFX License. If a copy of
* the license was not distributed with this file, you can obtain one at:
*
* http://ugfx.org/license.html
*/
#include "../../gfx.h"
#if GFX_USE_GDISP && GDISP_NEED_IMAGE && GDISP_NEED_IMAGE_PNG
#include "gdisp_image_support.h"
/*-----------------------------------------------------------------
* 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
gColor 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[GDISP_IMAGE_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;
gCoord x, y;
gCoord cx, cy;
gCoord sx, sy;
gCoord ix, iy;
unsigned cnt;
gPixel buf[GDISP_IMAGE_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[GDISP_IMAGE_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 gBool PNG_iLoadData(PNG_decode *d) {
uint32_t sz;
// Is there data still left in the buffer?
if (d->i.buflen)
return gTrue;
// If we are cached then we have no more data
if (!d->i.f)
return gFalse;
// 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 gFalse;
// 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 gFalse;
}
}
}
gotchunk:
// Try to read data some from the chunk
sz = d->i.chunklen;
if (sz > GDISP_IMAGE_PNG_FILE_BUFFER_SIZE)
sz = GDISP_IMAGE_PNG_FILE_BUFFER_SIZE;
if (gfileRead(d->i.f, d->i.buf, sz) != sz)
return gFalse;
d->i.chunklen -= sz;
d->i.buflen = sz;
d->i.pbuf = d->i.buf;
return gTrue;
}
// 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, gCoord x, gCoord y, gCoord cx, gCoord cy, gCoord sx, gCoord 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 gBool PNG_oStartY(PNG_output *o, gCoord y) {
if (y < o->sy || y >= o->sy+o->cy)
return gFalse;
o->ix = 0;
o->iy = y;
return gTrue;
}
// Feed a pixel color to the display buffer
static void PNG_oColor(PNG_output *o, gColor c) {
// Is it in the window
if (o->ix+(gCoord)o->cnt < o->sx || o->ix+(gCoord)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 (GDISP_IMAGE_PNG_Z_BUFFER_SIZE & ~(GDISP_IMAGE_PNG_Z_BUFFER_SIZE-1)) == GDISP_IMAGE_PNG_Z_BUFFER_SIZE
#define WRAP_ZBUF(x) { x &= GDISP_IMAGE_PNG_Z_BUFFER_SIZE-1; }
#else
#if GFX_COMPILER_WARNING_TYPE == GFX_COMPILER_WARNING_DIRECT
#warning "PNG: GDISP_IMAGE_PNG_Z_BUFFER_SIZE is more efficient as a power of 2"
#elif GFX_COMPILER_WARNING_TYPE == GFX_COMPILER_WARNING_MACRO
COMPILER_WARNING("PNG: GDISP_IMAGE_PNG_Z_BUFFER_SIZE is more efficient as a power of 2")
#endif
#define WRAP_ZBUF(x) { if (x >= GDISP_IMAGE_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 gBool PNG_zGetHeader(PNG_decode *d) {
if (!PNG_iLoadData(d))
return gFalse;
d->z.tmp[0] = PNG_iGetByte(d);
if (!PNG_iLoadData(d))
return gFalse;
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 gFalse;
return gTrue;
}
// 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 gBool 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 gFalse;
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 gFalse;
// 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 gFalse;
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 gTrue;
}
// Copy bytes from the input stream. Completing the copy completes the block.
static gBool PNG_zCopyInput(PNG_decode *d, unsigned length) {
// Copy the block
while(length--) {
if (!PNG_iLoadData(d)) { // EOF?
d->z.flags |= PNG_ZFLG_EOF;
return gFalse;
}
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 gTrue;
}
}
// The block is done
d->z.flags = (d->z.flags & ~PNG_ZFLG_RESUME_MASK) | PNG_ZFLG_RESUME_NEW;
return gTrue;
}
// Copy an uncompressed inflate block into the output
static gBool 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 gFalse;
}
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 gFalse;
}
// Copy the block
return PNG_zCopyInput(d, length);
}
// Inflate a compressed inflate block into the output
static gBool 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 gTrue;
}
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 gTrue;
}
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 >= GDISP_IMAGE_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 >= GDISP_IMAGE_PNG_Z_BUFFER_SIZE) // Bad offset?
goto iserror;
// Work out the source buffer position allowing for wrapping
if (offset > d->z.bufend)
offset -= GDISP_IMAGE_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 gTrue;
}
}
}
iserror:
d->z.flags |= PNG_ZFLG_EOF;
return gFalse;
}
// Start a new uncompressed/inflate block
static gBool 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 gFalse;
// 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 gFalse;
break;
case 1: // Decompress block with fixed huffman trees
PNG_zBuildFixedTrees(d);
if (!PNG_zInflateBlock(d))
return gFalse;
break;
case 2: // Decompress block with dynamic huffman trees
if (!PNG_zDecodeTrees(d))
return gFalse;
if (!PNG_zInflateBlock(d))
return gFalse;
break;
default: // Bad block type
// Mark it as an error
d->z.flags |= PNG_ZFLG_EOF;
return gFalse;
}
return gTrue;
}
// Resume an offset copy
static gBool 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 gTrue;
}
}
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 gBool 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 gFalse;
// 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 gTrue;
}
/*-----------------------------------------------------------------
* 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, gCoord x, gCoord y, gCoord cx, gCoord cy, gCoord sx, gCoord 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;
}
gDelay gdispImageNext_PNG(gdispImage *img) {
(void) img;
/* No more frames/pages */
return gDelayForever;
}
#endif /* GFX_USE_GDISP && GDISP_NEED_IMAGE && GDISP_NEED_IMAGE_PNG */