// // PNG Decoder // // written by Larry Bank // bitbank@pobox.com // Arduino port started 5/3/2021 // Original PNG code written 20+ years ago :) // The goal of this code is to decode PNG images on embedded systems // // Copyright 2021 BitBank Software, Inc. All Rights Reserved. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // http://www.apache.org/licenses/LICENSE-2.0 // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //=========================================================================== // #include "zlib.h" // // Convert 8-bit grayscale into RGB565 // static const uint16_t usGrayTo565[] = {0x0000,0x0000,0x0000,0x0000,0x0020,0x0020,0x0020,0x0020, // 0 0x0841,0x0841,0x0841,0x0841,0x0861,0x0861,0x0861,0x0861, 0x1082,0x1082,0x1082,0x1082,0x10a2,0x10a2,0x10a2,0x10a2, 0x18c3,0x18c3,0x18c3,0x18c3,0x18e3,0x18e3,0x18e3,0x18e3, 0x2104,0x2104,0x2104,0x2104,0x2124,0x2124,0x2124,0x2124, 0x2945,0x2945,0x2945,0x2945,0x2965,0x2965,0x2965,0x2965, 0x3186,0x3186,0x3186,0x3186,0x31a6,0x31a6,0x31a6,0x31a6, 0x39c7,0x39c7,0x39c7,0x39c7,0x39e7,0x39e7,0x39e7,0x39e7, 0x4208,0x4208,0x4208,0x4208,0x4228,0x4228,0x4228,0x4228, 0x4a49,0x4a49,0x4a49,0x4a49,0x4a69,0x4a69,0x4a69,0x4a69, 0x528a,0x528a,0x528a,0x528a,0x52aa,0x52aa,0x52aa,0x52aa, 0x5acb,0x5acb,0x5acb,0x5acb,0x5aeb,0x5aeb,0x5aeb,0x5aeb, 0x630c,0x630c,0x630c,0x630c,0x632c,0x632c,0x632c,0x632c, 0x6b4d,0x6b4d,0x6b4d,0x6b4d,0x6b6d,0x6b6d,0x6b6d,0x6b6d, 0x738e,0x738e,0x738e,0x738e,0x73ae,0x73ae,0x73ae,0x73ae, 0x7bcf,0x7bcf,0x7bcf,0x7bcf,0x7bef,0x7bef,0x7bef,0x7bef, 0x8410,0x8410,0x8410,0x8410,0x8430,0x8430,0x8430,0x8430, 0x8c51,0x8c51,0x8c51,0x8c51,0x8c71,0x8c71,0x8c71,0x8c71, 0x9492,0x9492,0x9492,0x9492,0x94b2,0x94b2,0x94b2,0x94b2, 0x9cd3,0x9cd3,0x9cd3,0x9cd3,0x9cf3,0x9cf3,0x9cf3,0x9cf3, 0xa514,0xa514,0xa514,0xa514,0xa534,0xa534,0xa534,0xa534, 0xad55,0xad55,0xad55,0xad55,0xad75,0xad75,0xad75,0xad75, 0xb596,0xb596,0xb596,0xb596,0xb5b6,0xb5b6,0xb5b6,0xb5b6, 0xbdd7,0xbdd7,0xbdd7,0xbdd7,0xbdf7,0xbdf7,0xbdf7,0xbdf7, 0xc618,0xc618,0xc618,0xc618,0xc638,0xc638,0xc638,0xc638, 0xce59,0xce59,0xce59,0xce59,0xce79,0xce79,0xce79,0xce79, 0xd69a,0xd69a,0xd69a,0xd69a,0xd6ba,0xd6ba,0xd6ba,0xd6ba, 0xdedb,0xdedb,0xdedb,0xdedb,0xdefb,0xdefb,0xdefb,0xdefb, 0xe71c,0xe71c,0xe71c,0xe71c,0xe73c,0xe73c,0xe73c,0xe73c, 0xef5d,0xef5d,0xef5d,0xef5d,0xef7d,0xef7d,0xef7d,0xef7d, 0xf79e,0xf79e,0xf79e,0xf79e,0xf7be,0xf7be,0xf7be,0xf7be, 0xffdf,0xffdf,0xffdf,0xffdf,0xffff,0xffff,0xffff,0xffff}; // // C interface // #ifndef __cplusplus // C API int PNG_openRAM(PNGIMAGE *pPNG, uint8_t *pData, int iDataSize, PNG_DRAW_CALLBACK *pfnDraw) { pPNG->iError = PNG_SUCCESS; pPNG->pfnRead = readMem; pPNG->pfnSeek = seekMem; pPNG->pfnDraw = pfnDraw; pPNG->pfnOpen = NULL; pPNG->pfnClose = NULL; pPNG->PNGFile.iSize = iDataSize; pPNG->PNGFile.pData = pData; return PNGInit(pPNG); } /* PNG_openRAM() */ #ifdef __LINUX__ int PNG_openFile(PNGIMAGE *pPNG, const char *szFilename, PNG_DRAW_CALLBACK *pfnDraw) { pPNG->iError = PNG_SUCCESS; pPNG->pfnRead = readFile; pPNG->pfnSeek = seekFile; pPNG->pfnDraw = pfnDraw; pPNG->pfnOpen = NULL; pPNG->pfnClose = closeFile; pPNG->PNGFile.fHandle = fopen(szFilename, "r+b"); if (pPNG->PNGFile.fHandle == NULL) return 0; fseek((FILE *)pPNG->PNGFile.fHandle, 0, SEEK_END); pPNG->PNGFile.iSize = (int)ftell((FILE *)pPNG->PNGFile.fHandle); fseek((FILE *)pPNG->PNGFile.fHandle, 0, SEEK_SET); return PNGInit(pPNG); } /* PNG_openFile() */ #endif // __LINUX__ void PNG_close(PNGIMAGE *pPNG) { if (pPNG->pfnClose) (*pPNG->pfnClose)(pPNG->PNGFile.fHandle); } /* PNG_close() */ int PNG_getWidth(PNGIMAGE *pPNG) { return pPNG->iWidth; } /* PNG_getsWidth() */ int PNG_getHeight(PNGIMAGE *pPNG) { return pPNG->iHeight; } /* PNG_getHeight() */ int PNG_getLastError(PNGIMAGE *pPNG) { return pPNG->iError; } /* PNG_getLastError() */ uint8_t *PNG_getPalette(PNGIMAGE *pPNG) { return pPNG->ucPalette; } /* PNG_getPalette() */ int PNG_getBufferSize(PNGIMAGE *pPNG) { return pPNG->iHeight * pPNG->iPitch; } /* PNG_getBufferSize() */ uint8_t * PNG_getBuffer(PNGIMAGE *pPNG) { return pPNG->pImage; } /* PNG_getBuffer() */ #endif // !__cplusplus PNG_STATIC uint8_t PNGMakeMask(PNGDRAW *pDraw, uint8_t *pMask, uint8_t ucThreshold) { uint8_t alpha, c, *s, *d, *pPal; uint8_t cHasOpaque = 0; int i, x; switch (pDraw->iPixelType) { case PNG_PIXEL_TRUECOLOR_ALPHA: // truecolor + alpha s = pDraw->pPixels; d = pMask; for (x=0; xiWidth; x+=8) { // groups of 8 pixels in each byte of mask c = 0; for (i=0; i<8; i++) { c <<= 1; alpha = s[3]; if (alpha >= ucThreshold) // if opaque 'enough', set the bit c |= 1; s += 4; } *d++ = c; cHasOpaque |= c; } break; case PNG_PIXEL_GRAY_ALPHA: s = pDraw->pPixels; d = pMask; for (x=0; xiWidth; x+=8) { // groups of 8 pixels in each byte of mask c = 0; for (i=0; i<8; i++) { c <<= 1; alpha = s[1]; if (alpha >= ucThreshold) // if opaque 'enough', set the bit c |= 1; s += 2; } *d++ = c; cHasOpaque |= c; } break; case PNG_PIXEL_INDEXED: s = pDraw->pPixels; pPal = &pDraw->pPalette[768]; d = pMask; for (x=0; xiWidth; x+=8) { // groups of 8 pixels in each byte of mask uint8_t ucPix = 0; c = 0; switch (pDraw->iBpp) { case 2: for (i=0; i<8; i++) { if (i == 0 || i == 4) ucPix = *s++; c <<= 1; alpha = pPal[ucPix >> 6]; // get palette alpha for this color if (alpha >= ucThreshold) // if opaque 'enough', set the bit c |= 1; ucPix <<= 2; } break; case 4: for (i=0; i<8; i++) { if ((i & 1) == 0) ucPix = *s++; c <<= 1; alpha = pPal[ucPix >> 4]; // get palette alpha for this color if (alpha >= ucThreshold) // if opaque 'enough', set the bit c |= 1; ucPix <<= 4; } break; case 8: for (i=0; i<8; i++) { c <<= 1; alpha = pPal[s[0]]; // get palette alpha for this color if (alpha >= ucThreshold) // if opaque 'enough', set the bit c |= 1; s++; } break; } // switch on bit depth *d++ = c; cHasOpaque |= c; } break; default: // No alpha channel; make a mask of all 1's memset(pMask, 0xff, (pDraw->iWidth+7)>>3); cHasOpaque = 1; break; } // switch on pixel type return cHasOpaque; // let the caller know if any pixels are opaque } /* PNGMakeMask() */ // // Convert a line of native PNG pixels into RGB565 // handles all standard pixel types // written for simplicity, not necessarily performance // PNG_STATIC void PNGRGB565(PNGDRAW *pDraw, uint16_t *pPixels, int iEndiannes, uint32_t u32Bkgd, int iHasAlpha) { int x, j; uint16_t usPixel, *pDest = pPixels; uint8_t c, a, *pPal, *s = pDraw->pPixels; switch (pDraw->iPixelType) { case PNG_PIXEL_GRAY_ALPHA: for (x=0; xiWidth; x++) { c = *s++; // gray level a = *s++; j = (a * c) >> 8; // multiply by the alpha usPixel = usGrayTo565[j]; if (iEndiannes == PNG_RGB565_BIG_ENDIAN) usPixel = __builtin_bswap16(usPixel); *pDest++ = usPixel; } break; case PNG_PIXEL_GRAYSCALE: for (x=0; xiWidth; x++) { c = *s++; usPixel = (c >> 3); // blue usPixel |= ((c >> 2) << 5); // green usPixel |= ((c >> 3) << 11); // red if (iEndiannes == PNG_RGB565_BIG_ENDIAN) usPixel = __builtin_bswap16(usPixel); *pDest++ = usPixel; } break; case PNG_PIXEL_TRUECOLOR: for (x=0; xiWidth; x++) { usPixel = (s[2] >> 3); // blue usPixel |= ((s[1] >> 2) << 5); // green usPixel |= ((s[0] >> 3) << 11); // red if (iEndiannes == PNG_RGB565_BIG_ENDIAN) usPixel = __builtin_bswap16(usPixel); *pDest++ = usPixel; s += 3; } break; case PNG_PIXEL_INDEXED: // palette color (can be 1/2/4 or 8 bits per pixel) if (pDraw->pFastPalette && !pDraw->iHasAlpha) { // faster RGB565 palette exists switch (pDraw->iBpp) { case 8: for (x=0; xiWidth; x++) { c = *s++; usPixel = pDraw->pFastPalette[c]; if (iEndiannes == PNG_RGB565_BIG_ENDIAN) usPixel = __builtin_bswap16(usPixel); *pDest++ = usPixel; } break; case 4: for (x=0; xiWidth; x+=2) { c = *s++; usPixel = pDraw->pFastPalette[c >> 4]; if (iEndiannes == PNG_RGB565_BIG_ENDIAN) usPixel = __builtin_bswap16(usPixel); *pDest++ = usPixel; usPixel = pDraw->pFastPalette[c & 0xf]; if (iEndiannes == PNG_RGB565_BIG_ENDIAN) usPixel = __builtin_bswap16(usPixel); *pDest++ = usPixel; } break; case 2: for (x=0; xiWidth; x+=4) { c = *s++; for (j=0; j<4; j++) { // work on pairs of bits usPixel = pDraw->pFastPalette[c >> 6]; if (iEndiannes == PNG_RGB565_BIG_ENDIAN) usPixel = __builtin_bswap16(usPixel); *pDest++ = usPixel; c <<= 2; } } break; case 1: for (x=0; xiWidth; x+=4) { c = *s++; for (j=0; j<8; j++) { // work on pairs of bits usPixel = pDraw->pFastPalette[c >> 7]; if (iEndiannes == PNG_RGB565_BIG_ENDIAN) usPixel = __builtin_bswap16(usPixel); *pDest++ = usPixel; c <<= 1; } } break; } // switch on bpp return; } switch (pDraw->iBpp) { case 8: // 8-bit palette also supports palette alpha if (pDraw->iHasAlpha) { // use the alpha to modify the palette for (x=0; xiWidth; x++) { int a; c = *s++; a = pDraw->pPalette[768+c]; // get alpha pPal = &pDraw->pPalette[c * 3]; usPixel = ((pPal[2] * a) >> 11); // blue usPixel |= (((pPal[1] * a) >> 10) << 5); // green usPixel |= (((pPal[0] * a) >> 11) << 11); // red if (iEndiannes == PNG_RGB565_BIG_ENDIAN) usPixel = __builtin_bswap16(usPixel); *pDest++ = usPixel; } // for x } else { for (x=0; xiWidth; x++) { c = *s++; pPal = &pDraw->pPalette[c * 3]; usPixel = (pPal[2] >> 3); // blue usPixel |= ((pPal[1] >> 2) << 5); // green usPixel |= ((pPal[0] >> 3) << 11); // red if (iEndiannes == PNG_RGB565_BIG_ENDIAN) usPixel = __builtin_bswap16(usPixel); *pDest++ = usPixel; } // for x } // not alpha palette break; case 4: for (x=0; xiWidth; x+=2) { c = *s++; pPal = &pDraw->pPalette[(c >> 4) * 3]; usPixel = (pPal[2] >> 3); // blue usPixel |= ((pPal[1] >> 2) << 5); // green usPixel |= ((pPal[0] >> 3) << 11); // red if (iEndiannes == PNG_RGB565_BIG_ENDIAN) usPixel = __builtin_bswap16(usPixel); *pDest++ = usPixel; pPal = &pDraw->pPalette[(c & 0xf) * 3]; usPixel = (pPal[2] >> 3); // blue usPixel |= ((pPal[1] >> 2) << 5); // green usPixel |= ((pPal[0] >> 3) << 11); // red if (iEndiannes == PNG_RGB565_BIG_ENDIAN) usPixel = __builtin_bswap16(usPixel); *pDest++ = usPixel; } break; case 2: for (x=0; xiWidth; x+=4) { c = *s++; for (j=0; j<4; j++) { // work on pairs of bits pPal = &pDraw->pPalette[(c >> 6) * 3]; usPixel = (pPal[2] >> 3); // blue usPixel |= ((pPal[1] >> 2) << 5); // green usPixel |= ((pPal[0] >> 3) << 11); // red if (iEndiannes == PNG_RGB565_BIG_ENDIAN) usPixel = __builtin_bswap16(usPixel); *pDest++ = usPixel; c <<= 2; } } break; case 1: for (x=0; xiWidth; x+=4) { c = *s++; for (j=0; j<8; j++) { // work on pairs of bits pPal = &pDraw->pPalette[(c >> 7) * 3]; usPixel = (pPal[2] >> 3); // blue usPixel |= ((pPal[1] >> 2) << 5); // green usPixel |= ((pPal[0] >> 3) << 11); // red if (iEndiannes == PNG_RGB565_BIG_ENDIAN) usPixel = __builtin_bswap16(usPixel); *pDest++ = usPixel; c <<= 1; } } break; } // switch on bits per pixel break; case PNG_PIXEL_TRUECOLOR_ALPHA: // truecolor + alpha if (u32Bkgd != 0xffffffff) { // user wants to blend it with a background color uint32_t r, g, b, a; uint32_t b_r, b_g, b_b; b_r = u32Bkgd & 0xff; b_g = (u32Bkgd & 0xff00) >> 8; b_b = (u32Bkgd >> 16) & 0xff; uint16_t u16Clr = (u32Bkgd & 0xf8) << 8; u16Clr |= ((u32Bkgd & 0xfc00) >> 5); u16Clr |= ((u32Bkgd & 0xf80000) >> 19); for (x=0; xiWidth; x++) { r = s[0]; g = s[1]; b = s[2]; a = s[3]; if (a == 0) usPixel = u16Clr; else if (a == 255) { // fully opaque usPixel = (s[2] >> 3); // blue usPixel |= ((s[1] >> 2) << 5); // green usPixel |= ((s[0] >> 3) << 11); // red } else { // mix the colors r = ((r * a) + (b_r * (255-a))) >> 8; g = ((g * a) + (b_g * (255-a))) >> 8; b = ((b * a) + (b_b * (255-a))) >> 8; usPixel = (b >> 3); // blue usPixel |= ((g >> 2) << 5); // green usPixel |= ((r >> 3) << 11); // red } if (iEndiannes == PNG_RGB565_BIG_ENDIAN) usPixel = __builtin_bswap16(usPixel); *pDest++ = usPixel; s += 4; // skip alpha } } else { // ignore alpha for (x=0; xiWidth; x++) { usPixel = (s[2] >> 3); // blue usPixel |= ((s[1] >> 2) << 5); // green usPixel |= ((s[0] >> 3) << 11); // red if (iEndiannes == PNG_RGB565_BIG_ENDIAN) usPixel = __builtin_bswap16(usPixel); *pDest++ = usPixel; s += 4; // skip alpha } } break; } } /* PNGRGB565() */ // // Helper functions for memory based images // PNG_STATIC int32_t seekMem(PNGFILE *pFile, int32_t iPosition) { if (iPosition < 0) iPosition = 0; else if (iPosition >= pFile->iSize) iPosition = pFile->iSize-1; pFile->iPos = iPosition; return iPosition; } /* seekMem() */ PNG_STATIC int32_t readFLASH(PNGFILE *pFile, uint8_t *pBuf, int32_t iLen) { int32_t iBytesRead; iBytesRead = iLen; if ((pFile->iSize - pFile->iPos) < iLen) iBytesRead = pFile->iSize - pFile->iPos; if (iBytesRead <= 0) return 0; memcpy_P(pBuf, &pFile->pData[pFile->iPos], iBytesRead); pFile->iPos += iBytesRead; return iBytesRead; } /* readFLASH() */ PNG_STATIC int32_t readRAM(PNGFILE *pFile, uint8_t *pBuf, int32_t iLen) { int32_t iBytesRead; iBytesRead = iLen; if ((pFile->iSize - pFile->iPos) < iLen) iBytesRead = pFile->iSize - pFile->iPos; if (iBytesRead <= 0) return 0; memcpy(pBuf, &pFile->pData[pFile->iPos], iBytesRead); pFile->iPos += iBytesRead; return iBytesRead; } /* readRAM() */ // // Verify it's a PNG file and then parse the IHDR chunk // to get basic image size/type/etc // PNG_STATIC int PNGParseInfo(PNGIMAGE *pPage) { uint8_t *s = pPage->ucFileBuf; int iBytesRead; pPage->iHasAlpha = pPage->iInterlaced = 0; // Read a few bytes to just parse the size/pixel info iBytesRead = (*pPage->pfnRead)(&pPage->PNGFile, s, 32); if (iBytesRead < 32) { // a PNG file this tiny? probably bad pPage->iError = PNG_INVALID_FILE; return pPage->iError; } if (MOTOLONG(s) != (int32_t)0x89504e47) { // check that it's a PNG file pPage->iError = PNG_INVALID_FILE; return pPage->iError; } if (MOTOLONG(&s[12]) == 0x49484452/*'IHDR'*/) { pPage->iWidth = MOTOLONG(&s[16]); pPage->iHeight = MOTOLONG(&s[20]); pPage->ucBpp = s[24]; // bits per pixel pPage->ucPixelType = s[25]; // pixel type pPage->iInterlaced = s[28]; if (pPage->iInterlaced || pPage->ucBpp > 8) { // 16-bit pixels are not supported (yet) pPage->iError = PNG_UNSUPPORTED_FEATURE; return pPage->iError; } // calculate the number of bytes per line of pixels switch (pPage->ucPixelType) { case PNG_PIXEL_GRAYSCALE: // grayscale case PNG_PIXEL_INDEXED: // indexed pPage->iPitch = (pPage->iWidth * pPage->ucBpp + 7)/8; // bytes per pixel break; case PNG_PIXEL_TRUECOLOR: // truecolor pPage->iPitch = ((3 * pPage->ucBpp) * pPage->iWidth + 7)/8; break; case PNG_PIXEL_GRAY_ALPHA: // grayscale + alpha pPage->iPitch = ((2 * pPage->ucBpp) * pPage->iWidth + 7)/8; pPage->iHasAlpha = 1; break; case PNG_PIXEL_TRUECOLOR_ALPHA: // truecolor + alpha pPage->iPitch = ((4 * pPage->ucBpp) * pPage->iWidth + 7)/8; pPage->iHasAlpha = 1; } // switch } if (pPage->iPitch >= PNG_MAX_BUFFERED_PIXELS) return PNG_TOO_BIG; return PNG_SUCCESS; } /* PNGParseInfo() */ // // De-filter the current line of pixels // PNG_STATIC void DeFilter(uint8_t *pCurr, uint8_t *pPrev, int iWidth, int iPitch) { uint8_t ucFilter = *pCurr++; int x, iBpp; if (iPitch <= iWidth) iBpp = 1; else iBpp = iPitch / iWidth; pPrev++; // skip filter of previous line switch (ucFilter) { // switch on filter type case PNG_FILTER_NONE: // nothing to do :) break; case PNG_FILTER_SUB: for (x=iBpp; xucFileBuf; struct inflate_state *state; // Either the image buffer must be allocated or a draw callback must be set before entering if (pPage->pImage == NULL && pPage->pfnDraw == NULL) { pPage->iError = PNG_NO_BUFFER; return 0; } // Use internal buffer to maintain the current and previous lines pCurr = pPage->ucPixels; pPrev = &pPage->ucPixels[pPage->iPitch+1]; pPage->iError = PNG_SUCCESS; // Start decoding the image bDone = FALSE; // Inflate the compressed image data // The allocation functions are disabled and zlib has been modified // to not use malloc/free and instead the buffer is part of the PNG class d_stream.zalloc = (alloc_func)0; d_stream.zfree = (free_func)0; d_stream.opaque = (voidpf)0; // Insert the memory pointer here to avoid having to use malloc() inside zlib state = (struct inflate_state FAR *)pPage->ucZLIB; d_stream.state = (struct internal_state FAR *)state; state->window = &pPage->ucZLIB[sizeof(inflate_state)]; // point to 32k dictionary buffer err = inflateInit(&d_stream); #ifdef FUTURE // if (inpage->cCompression == PIL_COMP_IPHONE_FLATE) // err = mz_inflateInit2(&d_stream, -15); // undocumented option which ignores header and crcs // else // err = mz_inflateInit2(&d_stream, 15); #endif // FUTURE iFileOffset = 8; // skip PNG file signature iOffset = 0; // internal buffer offset starts at 0 // Read some data to start (*pPage->pfnSeek)(&pPage->PNGFile, iFileOffset); iBytesRead = (*pPage->pfnRead)(&pPage->PNGFile, s, PNG_FILE_BUF_SIZE); iFileOffset += iBytesRead; y = 0; d_stream.avail_out = 0; d_stream.next_out = pPage->pImage; while (y < pPage->iHeight) { // continue until fully decoded // parse the markers until the next data block while (!bDone) { iLen = MOTOLONG(&s[iOffset]); // chunk length if (iLen < 0 || iLen + (iFileOffset - iBytesRead) > pPage->PNGFile.iSize) // invalid data { pPage->iError = PNG_DECODE_ERROR; return 1; } iMarker = MOTOLONG(&s[iOffset+4]); iOffset += 8; // point to the marker data switch (iMarker) { case 0x44474b62: // 'bKGD' DEBUG break; case 0x67414d41: //'gAMA' break; #ifdef FUTURE case 0x6663544C: //'fcTL' frame control block for animated PNG (need to get size of this partial image) pPage->iWidth = MOTOLONG(&pPage->pData[iOffset + 4]); // frame width pPage->iHeight = MOTOLONG(&pPage->pData[iOffset + 8]); // frame height bDone = TRUE; break; #endif case 0x504c5445: //'PLTE' palette colors memset(&pPage->ucPalette[768], 0xff, 256); // assume all colors are opaque unless specified memcpy(pPage->ucPalette, &s[iOffset], iLen); if (iOptions & PNG_FAST_PALETTE) { // create a RGB565 palette int i, iColors = 1 << pPage->ucBpp; uint16_t usPixel, *d; uint8_t *s = pPage->ucPalette; d = (uint16_t *)&pPage->ucPixels[sizeof(pPage->ucPixels)-512]; for (i=0; i> 3); // blue usPixel |= ((s[1] >> 2) << 5); // green usPixel |= ((s[0] >> 3) << 11); // red *d++ = usPixel; s += 3; } } break; case 0x74524e53: //'tRNS' transparency info if (pPage->ucPixelType == PNG_PIXEL_INDEXED) // if palette exists { memcpy(&pPage->ucPalette[768], &s[iOffset], iLen); pPage->iHasAlpha = 1; } else if (iLen == 2) // for grayscale images { pPage->iTransparent = s[iOffset + 1]; // lower part of 2-byte value is transparent color index pPage->iHasAlpha = 1; } else if (iLen == 6) // transparent color for 24-bpp image { pPage->iTransparent = s[iOffset + 5]; // lower part of 2-byte value is transparent color value pPage->iTransparent |= (s[iOffset + 3] << 8); pPage->iTransparent |= (s[iOffset + 1] << 16); pPage->iHasAlpha = 1; } break; case 0x49444154: //'IDAT' image data block while (iLen) { if (iOffset >= iBytesRead) { // we ran out of data; get some more iBytesRead = (*pPage->pfnRead)(&pPage->PNGFile, pPage->ucFileBuf, (iLen > PNG_FILE_BUF_SIZE) ? PNG_FILE_BUF_SIZE : iLen); iFileOffset += iBytesRead; iOffset = 0; } else { // number of bytes remaining in buffer iBytesRead -= iOffset; } d_stream.next_in = &pPage->ucFileBuf[iOffset]; d_stream.avail_in = iBytesRead; iLen -= iBytesRead; if (iLen < 0) iLen = 0; iOffset += iBytesRead; // if (iMarker == 0x66644154) // data starts at offset 4 in APNG frame data block // { // d_stream.next_in += 4; // d_stream.avail_in -= 4; // } err = 0; while (err == Z_OK) { if (d_stream.avail_out == 0) { // reset for next line d_stream.avail_out = pPage->iPitch+1; d_stream.next_out = pCurr; } // otherwise it could be a continuation of an unfinished line err = inflate(&d_stream, Z_NO_FLUSH, iOptions & PNG_CHECK_CRC); if ((err == Z_OK || err == Z_STREAM_END) && d_stream.avail_out == 0) {// successfully decoded line DeFilter(pCurr, pPrev, pPage->iWidth, pPage->iPitch); if (pPage->pImage == NULL) { // no image buffer, send it line by line PNGDRAW pngd; pngd.pUser = pUser; pngd.iPitch = pPage->iPitch; pngd.iWidth = pPage->iWidth; pngd.pPalette = pPage->ucPalette; pngd.pFastPalette = (iOptions & PNG_FAST_PALETTE) ? (uint16_t *)&pPage->ucPixels[sizeof(pPage->ucPixels)-512] : NULL; pngd.pPixels = pCurr+1; pngd.iPixelType = pPage->ucPixelType; pngd.iHasAlpha = pPage->iHasAlpha; pngd.iBpp = pPage->ucBpp; pngd.y = y; (*pPage->pfnDraw)(&pngd); } else { // copy to destination bitmap memcpy(&pPage->pImage[y * pPage->iPitch], &pCurr[1], pPage->iPitch); } y++; // swap current and previous lines tmp = pCurr; pCurr = pPrev; pPrev = tmp; } else { // some error tmp = NULL; } } if (err == Z_STREAM_END && d_stream.avail_out == 0) { // successful decode, stop here y = pPage->iHeight; bDone = TRUE; } else if (err == Z_DATA_ERROR || err == Z_STREAM_ERROR) { iLen = 0; // quit now y = pPage->iHeight; pPage->iError = PNG_DECODE_ERROR; bDone = TRUE; // force loop to exit with error } else if (err == Z_BUF_ERROR) { y |= 0; // need more data } } // while (iLen) if (y != pPage->iHeight && iFileOffset < pPage->PNGFile.iSize) { // need to read more IDAT chunks iBytesRead = (*pPage->pfnRead)(&pPage->PNGFile, pPage->ucFileBuf, PNG_FILE_BUF_SIZE); iFileOffset += iBytesRead; iOffset = 0; } break; // case 0x69545874: //'iTXt' // case 0x7a545874: //'zTXt' #ifdef FUTURE case 0x74455874: //'tEXt' { char szTemp[256]; char *pDest = NULL; memcpy(szTemp, &s[iOffset], 80); // get the label length (Title, Author, Description, Copyright, Creation Time, Software, Disclaimer, Warning, Source, Comment) i = (int)strlen(szTemp) + 1; // start of actual text if (strcmp(szTemp, "Comment") == 0 || strcmp(szTemp, "Description") == 0) pDest = &pPage->szComment[0]; else if (strcmp(szTemp, "Software") == 0) pDest = &pPage->szSoftware[0]; else if (strcmp(szTemp, "Author") == 0) pDest = &pPage->szArtist[0]; if (pDest != NULL) { if ((iLen - i) < 128) { memcpy(pPage->szComment, &pPage->pData[iOffset + i], iLen - i); pPage->szComment[iLen - i + 1] = 0; } else { memcpy(pPage->szComment, &pPage->pData[iOffset + i], 127); pPage->szComment[127] = '\0'; } } } break; #endif } // switch iOffset += (iLen + 4); // skip data + CRC if (iOffset > iBytesRead-8) { // need to read more data iFileOffset += (iOffset - iBytesRead); (*pPage->pfnSeek)(&pPage->PNGFile, iFileOffset); iBytesRead = (*pPage->pfnRead)(&pPage->PNGFile, s, PNG_FILE_BUF_SIZE); iFileOffset += iBytesRead; iOffset = 0; } } // while !bDone } // while y < height err = inflateEnd(&d_stream); return pPage->iError; } /* DecodePNG() */