From 13f6ed14f1ea233b66ec082f10586bf0bc69ca47 Mon Sep 17 00:00:00 2001 From: Pim van Pelt Date: Sun, 26 Nov 2017 21:03:48 +0100 Subject: [PATCH] Remove JPG and BMP support. Add uPNG support. --- libs/ili9341/include/tft.h | 40 +- libs/ili9341/include/upng.h | 81 +++ libs/ili9341/src/ili9341.c | 539 +-------------- libs/ili9341/src/upng.c | 1281 +++++++++++++++++++++++++++++++++++ 4 files changed, 1400 insertions(+), 541 deletions(-) create mode 100644 libs/ili9341/include/upng.h create mode 100644 libs/ili9341/src/upng.c diff --git a/libs/ili9341/include/tft.h b/libs/ili9341/include/tft.h index 8006dfd..8fe0eaf 100644 --- a/libs/ili9341/include/tft.h +++ b/libs/ili9341/include/tft.h @@ -9,6 +9,7 @@ #include #include "tftspi.h" +#include "upng.h" typedef struct { uint16_t x1; @@ -601,43 +602,8 @@ void mgos_ili9341_clearStringRect(int x, int y, char *str); //---------------------------------------------------------- color_t HSBtoRGB(float _hue, float _sat, float _brightness); -/* - * Decodes and displays JPG image - * Limits: - * Baseline only. Progressive and Lossless JPEG format are not supported. - * Image size: Up to 65520 x 65520 pixels - * Color space: YCbCr three components only. Gray scale image is not supported. - * Sampling factor: 4:4:4, 4:2:2 or 4:2:0. - * - * Params: - * x: image left position; constants CENTER & RIGHT can be used; negative value is accepted - * y: image top position; constants CENTER & BOTTOM can be used; negative value is accepted - * scale: image scale factor: 0~3; if scale>0, image is scaled by factor 1/(2^scale) (1/2, 1/4 or 1/8) - * fname: pointer to the name of the file from which the image will be read - * if set to NULL, image will be read from memory buffer pointed to by 'buf' - * buf: pointer to the memory buffer from which the image will be read; used if fname=NULL - * size: size of the memory buffer from which the image will be read; used if fname=NULL & buf!=NULL - * - */ -//----------------------------------------------------------------------------------- -void mgos_ili9341_jpg_image(int x, int y, uint8_t scale, char *fname, uint8_t *buf, int size); - -/* - * Decodes and displays BMP image - * Only uncompressed RGB 24-bit with no color space information BMP images can be displayed - * - * Params: - * x: image left position; constants CENTER & RIGHT can be used; negative value is accepted - * y: image top position; constants CENTER & BOTTOM can be used; negative value is accepted - * scale: image scale factor: 0~7; if scale>0, image is scaled by factor 1/(scale+1) - * fname: pointer to the name of the file from which the image will be read - * if set to NULL, image will be read from memory buffer pointed to by 'imgbuf' - * imgbuf: pointer to the memory buffer from which the image will be read; used if fname=NULL - * size: size of the memory buffer from which the image will be read; used if fname=NULL & imgbuf!=NULL - * - */ -//------------------------------------------------------------------------------------- -int mgos_ili9341_bmp_image(int x, int y, uint8_t scale, char *fname, uint8_t *imgbuf, int size); +/* Draw PNG at coords x,y */ +int mgos_ili9341_png(int x, int y, char *fname); /* * Compile font c source file to .fnt file diff --git a/libs/ili9341/include/upng.h b/libs/ili9341/include/upng.h new file mode 100644 index 0000000..35a0e9a --- /dev/null +++ b/libs/ili9341/include/upng.h @@ -0,0 +1,81 @@ +/* +uPNG -- derived from LodePNG version 20100808 + +Copyright (c) 2005-2010 Lode Vandevenne +Copyright (c) 2010 Sean Middleditch + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#if !defined(UPNG_H) +#define UPNG_H + +typedef enum upng_error { + UPNG_EOK = 0, /* success (no error) */ + UPNG_ENOMEM = 1, /* memory allocation failed */ + UPNG_ENOTFOUND = 2, /* resource not found (file missing) */ + UPNG_ENOTPNG = 3, /* image data does not have a PNG header */ + UPNG_EMALFORMED = 4, /* image data is not a valid PNG image */ + UPNG_EUNSUPPORTED = 5, /* critical PNG chunk type is not supported */ + UPNG_EUNINTERLACED = 6, /* image interlacing is not supported */ + UPNG_EUNFORMAT = 7, /* image color format is not supported */ + UPNG_EPARAM = 8 /* invalid parameter to method call */ +} upng_error; + +typedef enum upng_format { + UPNG_BADFORMAT, + UPNG_RGB8, + UPNG_RGB16, + UPNG_RGBA8, + UPNG_RGBA16, + UPNG_LUMINANCE1, + UPNG_LUMINANCE2, + UPNG_LUMINANCE4, + UPNG_LUMINANCE8, + UPNG_LUMINANCE_ALPHA1, + UPNG_LUMINANCE_ALPHA2, + UPNG_LUMINANCE_ALPHA4, + UPNG_LUMINANCE_ALPHA8 +} upng_format; + +typedef struct upng_t upng_t; + +upng_t* upng_new_from_bytes (const unsigned char* buffer, unsigned long size); +upng_t* upng_new_from_file (const char* path); +void upng_free (upng_t* upng); + +upng_error upng_header (upng_t* upng); +upng_error upng_decode (upng_t* upng); + +upng_error upng_get_error (const upng_t* upng); +unsigned upng_get_error_line (const upng_t* upng); + +unsigned upng_get_width (const upng_t* upng); +unsigned upng_get_height (const upng_t* upng); +unsigned upng_get_bpp (const upng_t* upng); +unsigned upng_get_bitdepth (const upng_t* upng); +unsigned upng_get_components (const upng_t* upng); +unsigned upng_get_pixelsize (const upng_t* upng); +upng_format upng_get_format (const upng_t* upng); + +const unsigned char* upng_get_buffer (const upng_t* upng); +unsigned upng_get_size (const upng_t* upng); + +#endif /*defined(UPNG_H)*/ diff --git a/libs/ili9341/src/ili9341.c b/libs/ili9341/src/ili9341.c index 1fed129..91eccd6 100644 --- a/libs/ili9341/src/ili9341.c +++ b/libs/ili9341/src/ili9341.c @@ -15,7 +15,7 @@ #include "tft.h" #include "time.h" #include -#include "rom/tjpgd.h" +// #include "rom/tjpgd.h" #include "esp_heap_caps.h" #include "tftspi.h" #include "mgos.h" @@ -2242,520 +2242,51 @@ void mgos_ili9341_restoreClipWin() } -// ================ JPG SUPPORT ================================================ -// User defined device identifier -typedef struct { - FILE *fhndl; // File handler for input function - int x; // image top left point X position - int y; // image top left point Y position - uint8_t *membuff; // memory buffer containing the image - uint32_t bufsize; // size of the memory buffer - uint32_t bufptr; // memory buffer current position - color_t *linbuf[2]; // memory buffer used for display output - uint8_t linbuf_idx; -} JPGIODEV; - - -// User defined call-back function to input JPEG data from file -//--------------------- -static UINT tjd_input ( - JDEC* jd, // Decompression object - BYTE* buff, // Pointer to the read buffer (NULL:skip) - UINT nd // Number of bytes to read/skip from input stream -) +int mgos_ili9341_png(int x, int y, char *fname) { - int rb = 0; - // Device identifier for the session (5th argument of jd_prepare function) - JPGIODEV *dev = (JPGIODEV*)jd->device; + upng_t* upng; + const uint8_t *png_buf; + uint16_t i,j; + color_t pixel; - if (buff) { // Read nd bytes from the input strem - rb = fread(buff, 1, nd, dev->fhndl); - return rb; // Returns actual number of bytes read - } - else { // Remove nd bytes from the input stream - if (fseek(dev->fhndl, nd, SEEK_CUR) >= 0) return nd; - else return 0; - } -} + int ret = -1; -// User defined call-back function to input JPEG data from memory buffer -//------------------------- -static UINT tjd_buf_input ( - JDEC* jd, // Decompression object - BYTE* buff, // Pointer to the read buffer (NULL:skip) - UINT nd // Number of bytes to read/skip from input stream -) -{ - // Device identifier for the session (5th argument of jd_prepare function) - JPGIODEV *dev = (JPGIODEV*)jd->device; - if (!dev->membuff) return 0; - if (dev->bufptr >= (dev->bufsize + 2)) return 0; // end of stream + if (!(upng = upng_new_from_file(fname))) { + LOG(LL_ERROR, ("Can't read %s", fname)); + goto exit; + } - if ((dev->bufptr + nd) > (dev->bufsize + 2)) nd = (dev->bufsize + 2) - dev->bufptr; + upng_decode(upng); + if (upng_get_error(upng) != UPNG_EOK) { + LOG(LL_ERROR, ("PNG decode error")); + goto exit; + } + if (upng_get_format(upng) != UPNG_RGB8) { + LOG(LL_ERROR, ("PNG is not in RGB8 format")); + goto exit; + } + // Do stuff with upng data + LOG(LL_INFO, ("%s: w=%d h=%d size=%d bpp=%d bitdepth=%d pixelsize=%d", fname, upng_get_width(upng), upng_get_height(upng), upng_get_size(upng), upng_get_bpp(upng), upng_get_bitdepth(upng), upng_get_pixelsize(upng))); - if (buff) { // Read nd bytes from the input strem - memcpy(buff, dev->membuff + dev->bufptr, nd); - dev->bufptr += nd; - return nd; // Returns number of bytes read - } - else { // Remove nd bytes from the input stream - dev->bufptr += nd; - return nd; - } -} - -// User defined call-back function to output RGB bitmap to display device -//---------------------- -static UINT tjd_output ( - JDEC* jd, // Decompression object of current session - void* bitmap, // Bitmap data to be output - JRECT* rect // Rectangular region to output -) -{ - // Device identifier for the session (5th argument of jd_prepare function) - JPGIODEV *dev = (JPGIODEV*)jd->device; - - // ** Put the rectangular into the display device ** - int x; - int y; - int dleft, dtop, dright, dbottom; - BYTE *src = (BYTE*)bitmap; - - int left = rect->left + dev->x; - int top = rect->top + dev->y; - int right = rect->right + dev->x; - int bottom = rect->bottom + dev->y; - - if ((left > dispWin.x2) || (top > dispWin.y2)) return 1; // out of screen area, return - if ((right < dispWin.x1) || (bottom < dispWin.y1)) return 1;// out of screen area, return - - if (left < dispWin.x1) dleft = dispWin.x1; - else dleft = left; - if (top < dispWin.y1) dtop = dispWin.y1; - else dtop = top; - if (right > dispWin.x2) dright = dispWin.x2; - else dright = right; - if (bottom > dispWin.y2) dbottom = dispWin.y2; - else dbottom = bottom; - - if ((dleft > dispWin.x2) || (dtop > dispWin.y2)) return 1; // out of screen area, return - if ((dright < dispWin.x1) || (dbottom < dispWin.y1)) return 1; // out of screen area, return - - uint32_t len = ((dright-dleft+1) * (dbottom-dtop+1)); // calculate length of data - - - if ((len > 0) && (len <= JPG_IMAGE_LINE_BUF_SIZE)) { - uint8_t *dest = (uint8_t *)(dev->linbuf[dev->linbuf_idx]); - - for (y = top; y <= bottom; y++) { - for (x = left; x <= right; x++) { - // Clip to display area - if ((x >= dleft) && (y >= dtop) && (x <= dright) && (y <= dbottom)) { - *dest++ = (*src++) & 0xFC; - *dest++ = (*src++) & 0xFC; - *dest++ = (*src++) & 0xFC; - } - else src += 3; // skip - } - } - wait_trans_finish(1); - send_data(dleft, dtop, dright, dbottom, len, dev->linbuf[dev->linbuf_idx]); - dev->linbuf_idx = ((dev->linbuf_idx + 1) & 1); - } - else { - wait_trans_finish(1); - LOG(LL_ERROR, ("Data size error: %d jpg: (%d,%d,%d,%d) disp: (%d,%d,%d,%d)", len, left,top,right,bottom, dleft,dtop,dright,dbottom)); - return 0; // stop decompression - } - - return 1; // Continue to decompression -} - -// tft.jpgimage(X, Y, scale, file_name, buf, size] -// X & Y can be < 0 ! -//================================================================================== -void mgos_ili9341_jpg_image(int x, int y, uint8_t scale, char *fname, uint8_t *buf, int size) -{ - JPGIODEV dev; - struct stat sb; - char *work = NULL; // Pointer to the working buffer (must be 4-byte aligned) - UINT sz_work = 3800; // Size of the working buffer (must be power of 2) - JDEC jd; // Decompression object (70 bytes) - JRESULT rc; - - dev.linbuf[0] = NULL; - dev.linbuf[1] = NULL; - dev.linbuf_idx = 0; - - dev.fhndl = NULL; - - - x+= dispWin.x1; - y+= dispWin.y1; - - if (fname == NULL) { - // image from buffer - dev.membuff = buf; - dev.bufsize = size; - dev.bufptr = 0; + png_buf = upng_get_buffer(upng); + disp_select(); + for(j=0; j 3) scale = 3; - - work = malloc(sz_work); - if (work) { - if (dev.membuff) rc = jd_prepare(&jd, tjd_buf_input, (void *)work, sz_work, &dev); - else rc = jd_prepare(&jd, tjd_input, (void *)work, sz_work, &dev); - if (rc == JDR_OK) { - if (x == CENTER) x = ((dispWin.x2 - dispWin.x1 + 1 - (int)(jd.width >> scale)) / 2) + dispWin.x1; - else if (x == RIGHT) x = dispWin.x2 + 1 - (int)(jd.width >> scale); - - if (y == CENTER) y = ((dispWin.y2 - dispWin.y1 + 1 - (int)(jd.height >> scale)) / 2) + dispWin.y1; - else if (y == BOTTOM) y = dispWin.y2 + 1 - (int)(jd.height >> scale); - - if (x < ((dispWin.x2-1) * -1)) x = (dispWin.x2-1) * -1; - if (y < ((dispWin.y2-1)) * -1) y = (dispWin.y2-1) * -1; - if (x > (dispWin.x2-1)) x = dispWin.x2 - 1; - if (y > (dispWin.y2-1)) y = dispWin.y2-1; - - dev.x = x; - dev.y = y; - - dev.linbuf[0] = heap_caps_malloc(JPG_IMAGE_LINE_BUF_SIZE*3, MALLOC_CAP_DMA); - if (dev.linbuf[0] == NULL) { - if (image_debug) LOG(LL_ERROR, ("Error allocating line buffer #0")); - goto exit; - } - dev.linbuf[1] = heap_caps_malloc(JPG_IMAGE_LINE_BUF_SIZE*3, MALLOC_CAP_DMA); - if (dev.linbuf[1] == NULL) { - if (image_debug) LOG(LL_ERROR, ("Error allocating line buffer #1")); - goto exit; - } - - // Start to decode the JPEG file - disp_select(); - rc = jd_decomp(&jd, tjd_output, scale); - disp_deselect(); - - if (rc != JDR_OK) { - if (image_debug) LOG(LL_ERROR, ("Jpg decompression error %d", rc)); - } - if (image_debug) LOG(LL_INFO, ("Jpg size: %dx%d, position; %d,%d, scale: %d, bytes used: %d", jd.width, jd.height, x, y, scale, jd.sz_pool)); - } - else { - if (image_debug) LOG(LL_ERROR, ("jpg prepare error %d", rc)); - } - } - else { - if (image_debug) LOG(LL_ERROR, ("work buffer allocation error")); - } + } + disp_deselect(); + exit: - if (work) free(work); // free work buffer - if (dev.linbuf[0]) free(dev.linbuf[0]); - if (dev.linbuf[1]) free(dev.linbuf[1]); - if (dev.fhndl) fclose(dev.fhndl); // close input file + if (upng) upng_free(upng); + return ret; } -//==================================================================================== -int mgos_ili9341_bmp_image(int x, int y, uint8_t scale, char *fname, uint8_t *imgbuf, int size) -{ - FILE *fhndl = NULL; - struct stat sb; - int i, err=0; - int img_xsize, img_ysize, img_xstart, img_xlen, img_ystart, img_ylen; - int img_pos, img_pix_pos, scan_lines, rd_len; - uint8_t tmpc; - uint16_t wtemp; - uint32_t temp; - int disp_xstart, disp_xend, disp_ystart, disp_yend; - uint8_t buf[56]; - char err_buf[64]; - uint8_t *line_buf[2] = {NULL,NULL}; - uint8_t lb_idx = 0; - uint8_t *scale_buf = NULL; - uint8_t scale_pix; - uint16_t co[3] = {0,0,0}; // RGB sum - uint8_t npix; - - if (scale > 7) scale = 7; - scale_pix = scale+1; // scale factor ( 1~8 ) - - if (fname) { - // * File name is given, reading image from file - if (stat(fname, &sb) != 0) { - sprintf(err_buf, "opening file"); - err = -1; - goto exit; - } - size = sb.st_size; - fhndl = fopen(fname, "r"); - if (!fhndl) { - sprintf(err_buf, "opening file"); - err = -2; - goto exit; - } - - i = fread(buf, 1, 54, fhndl); // read header - } - else { - // * Reading image from buffer - if ((imgbuf) && (size > 54)) { - memcpy(buf, imgbuf, 54); - i = 54; - } - else i = 0; - } - - sprintf(err_buf, "reading header"); - if (i != 54) {err = -3; goto exit;} - - // ** Check image header and get image properties - if ((buf[0] != 'B') || (buf[1] != 'M')) {err=-4; goto exit;} // accept only images with 'BM' id - - memcpy(&temp, buf+2, 4); // file size - if (temp != size) {err=-5; goto exit;} - - memcpy(&img_pos, buf+10, 4); // start of pixel data - - memcpy(&temp, buf+14, 4); // BMP header size - if (temp != 40) {err=-6; goto exit;} - - memcpy(&wtemp, buf+26, 2); // the number of color planes - if (wtemp != 1) {err=-7; goto exit;} - - memcpy(&wtemp, buf+28, 2); // the number of bits per pixel - if (wtemp != 24) {err=-8; goto exit;} - - memcpy(&temp, buf+30, 4); // the compression method being used - if (temp != 0) {err=-9; goto exit;} - - memcpy(&img_xsize, buf+18, 4); // the bitmap width in pixels - memcpy(&img_ysize, buf+22, 4); // the bitmap height in pixels - - - // * scale image dimensions - - img_xlen = img_xsize / scale_pix; // image display horizontal size - img_ylen = img_ysize / scale_pix; // image display vertical size - - if (x == CENTER) x = ((dispWin.x2 - dispWin.x1 + 1 - img_xlen) / 2) + dispWin.x1; - else if (x == RIGHT) x = dispWin.x2 + 1 - img_xlen; - - if (y == CENTER) y = ((dispWin.y2 - dispWin.y1 + 1 - img_ylen) / 2) + dispWin.y1; - else if (y == BOTTOM) y = dispWin.y2 + 1 - img_ylen; - - if ((x < ((dispWin.x2 + 1) * -1)) || (x > (dispWin.x2 + 1)) || (y < ((dispWin.y2 + 1) * -1)) || (y > (dispWin.y2 + 1))) { - sprintf(err_buf, "out of display area (%d,%d", x, y); - err = -10; - goto exit; - } - - // ** set display and image areas - if (x < dispWin.x1) { - disp_xstart = dispWin.x1; - img_xstart = -x; // image pixel line X offset - img_xlen += x; - } - else { - disp_xstart = x; - img_xstart = 0; - } - if (y < dispWin.y1) { - disp_ystart = dispWin.y1; - img_ystart = -y; // image pixel line Y offset - img_ylen += y; - } - else { - disp_ystart = y; - img_ystart = 0; - } - disp_xend = disp_xstart + img_xlen - 1; - disp_yend = disp_ystart + img_ylen - 1; - if (disp_xend > dispWin.x2) { - disp_xend = dispWin.x2; - img_xlen = disp_xend - disp_xstart + 1; - } - if (disp_yend > dispWin.y2) { - disp_yend = dispWin.y2; - img_ylen = disp_yend - disp_ystart + 1; - } - - if ((img_xlen < 8) || (img_ylen < 8) || (img_xstart >= (img_xsize-2)) || ((img_ysize - img_ystart) < 2)) { - sprintf(err_buf, "image too small"); - err = -11; - goto exit; - } - - // ** Allocate memory for 2 lines of image pixels - line_buf[0] = heap_caps_malloc(img_xsize*3, MALLOC_CAP_DMA); - if (line_buf[0] == NULL) { - sprintf(err_buf, "allocating line buffer #1"); - err=-12; - goto exit; - } - - line_buf[1] = heap_caps_malloc(img_xsize*3, MALLOC_CAP_DMA); - if (line_buf[1] == NULL) { - sprintf(err_buf, "allocating line buffer #2"); - err=-13; - goto exit; - } - - if (scale) { - // Allocate memory for scale buffer - rd_len = img_xlen * 3 * scale_pix; - scale_buf = malloc(rd_len*scale_pix); - if (scale_buf == NULL) { - sprintf(err_buf, "allocating scale buffer"); - err=-14; - goto exit; - } - } - else rd_len = img_xlen * 3; - - // ** ***************************************************** ** - // ** BMP images are stored in file from LAST to FIRST line ** - // ** ***************************************************** ** - - /* Used variables: - img_xsize horizontal image size in pixels - img_ysize number of image lines - img_xlen image display horizontal scaled size in pixels - img_ylen image display vertical scaled size in pixels - img_xstart first pixel in line to be displayed - img_ystart first image line to be displayed - img_xlen number of pixels in image line to be displayed, starting with 'img_xstart' - img_ylen number of lines in image to be displayed, starting with 'img_ystart' - rd_len length of color data which are read from image line in bytes - */ - - // Set position in image to the first color data (beginning of the LAST line) - img_pos += (img_ystart * (img_xsize*3)); - if (fhndl) { - if (fseek(fhndl, img_pos, SEEK_SET) != 0) { - sprintf(err_buf, "file seek at %d", img_pos); - err = -15; - goto exit; - } - } - - if (image_debug) LOG(LL_INFO, ("BMP: image size: (%d,%d) scale: %d disp size: (%d,%d) img xofs: %d img yofs: %d at: %d,%d; line buf: 2* %d scale buf: %d", - img_xsize, img_ysize, scale_pix, img_xlen, img_ylen, img_xstart, img_ystart, disp_xstart, disp_ystart, img_xsize*3, ((scale) ? (rd_len*scale_pix) : 0))); - - // * Select the display - disp_select(); - - while ((disp_yend >= disp_ystart) && ((img_pos + (img_xsize*3)) <= size)) { - if (img_pos > size) { - sprintf(err_buf, "EOF reached: %d > %d", img_pos, size); - err = -16; - goto exit1; - } - if (scale == 0) { - // Read the line of color data into color buffer - if (fhndl) { - i = fread(line_buf[lb_idx], 1, img_xsize*3, fhndl); // read line from file - if (i != (img_xsize*3)) { - sprintf(err_buf, "file read at %d (%d<>%d)", img_pos, i, img_xsize*3); - err = -16; - goto exit1; - } - } - else memcpy(line_buf[lb_idx], imgbuf+img_pos, img_xsize*3); - - if (img_xstart > 0) memmove(line_buf[lb_idx], line_buf[lb_idx]+(img_xstart*3), rd_len); - // Convert colors BGR-888 (BMP) -> RGB-888 (DISPLAY) === - for (i=0; i < rd_len; i += 3) { - tmpc = line_buf[lb_idx][i+2] & 0xfc; // save R - line_buf[lb_idx][i+2] = line_buf[lb_idx][i] & 0xfc; // B -> R - line_buf[lb_idx][i] = tmpc; // R -> B - line_buf[lb_idx][i+1] &= 0xfc; // G - } - img_pos += (img_xsize*3); - } - else { - // scale image, read 'scale_pix' lines and find the average color - for (scan_lines=0; scan_lines size) break; - if (fhndl) { - i = fread(line_buf[lb_idx], 1, img_xsize*3, fhndl); // read line from file - if (i != (img_xsize*3)) { - sprintf(err_buf, "file read at %d (%d<>%d)", img_pos, i, img_xsize*3); - err = -17; - goto exit1; - } - } - else memcpy(line_buf[lb_idx], imgbuf+img_pos, img_xsize*3); - img_pos += (img_xsize*3); - - // copy only data which are displayed to scale buffer - memcpy(scale_buf + (rd_len * scan_lines), line_buf[lb_idx]+img_xstart, rd_len); - } - - // Populate display line buffer - for (int n=0;n<(img_xlen*3);n += 3) { - memset(co, 0, sizeof(co)); // initialize color sum - npix = 0; // initialize number of pixels in scale rectangle - - // sum all pixels in scale rectangle - for (int sc_line=0; sc_line RGB-888 (DISPLAY) - line_buf[lb_idx][n+2] = (uint8_t)(co[0] / npix); // B - line_buf[lb_idx][n+1] = (uint8_t)(co[1] / npix); // G - line_buf[lb_idx][n] = (uint8_t)(co[2] / npix); // R - } - } - - wait_trans_finish(1); - send_data(disp_xstart, disp_yend, disp_xend, disp_yend, img_xlen, (color_t *)line_buf[lb_idx]); - lb_idx = (lb_idx + 1) & 1; // change buffer - - disp_yend--; - } - err = 0; -exit1: - disp_deselect(); -exit: - if (scale_buf) free(scale_buf); - if (line_buf[0]) free(line_buf[0]); - if (line_buf[1]) free(line_buf[1]); - if (fhndl) fclose(fhndl); - if ((err) && (image_debug)) LOG(LL_ERROR, ("Error: %d [%s]", err, err_buf)); - - return err; -} - - - void mgos_ili9341_set_fg(const color_t *color) { _fg = *color; diff --git a/libs/ili9341/src/upng.c b/libs/ili9341/src/upng.c new file mode 100644 index 0000000..7de54ee --- /dev/null +++ b/libs/ili9341/src/upng.c @@ -0,0 +1,1281 @@ +/* +uPNG -- derived from LodePNG version 20100808 + +Copyright (c) 2005-2010 Lode Vandevenne +Copyright (c) 2010 Sean Middleditch + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#include +#include +#include +#include + +#include "upng.h" + +#define MAKE_BYTE(b) ((b) & 0xFF) +#define MAKE_DWORD(a,b,c,d) ((MAKE_BYTE(a) << 24) | (MAKE_BYTE(b) << 16) | (MAKE_BYTE(c) << 8) | MAKE_BYTE(d)) +#define MAKE_DWORD_PTR(p) MAKE_DWORD((p)[0], (p)[1], (p)[2], (p)[3]) + +#define CHUNK_IHDR MAKE_DWORD('I','H','D','R') +#define CHUNK_IDAT MAKE_DWORD('I','D','A','T') +#define CHUNK_IEND MAKE_DWORD('I','E','N','D') + +#define FIRST_LENGTH_CODE_INDEX 257 +#define LAST_LENGTH_CODE_INDEX 285 + +#define NUM_DEFLATE_CODE_SYMBOLS 288 /*256 literals, the end code, some length codes, and 2 unused codes */ +#define NUM_DISTANCE_SYMBOLS 32 /*the distance codes have their own symbols, 30 used, 2 unused */ +#define NUM_CODE_LENGTH_CODES 19 /*the code length codes. 0-15: code lengths, 16: copy previous 3-6 times, 17: 3-10 zeros, 18: 11-138 zeros */ +#define MAX_SYMBOLS 288 /* largest number of symbols used by any tree type */ + +#define DEFLATE_CODE_BITLEN 15 +#define DISTANCE_BITLEN 15 +#define CODE_LENGTH_BITLEN 7 +#define MAX_BIT_LENGTH 15 /* largest bitlen used by any tree type */ + +#define DEFLATE_CODE_BUFFER_SIZE (NUM_DEFLATE_CODE_SYMBOLS * 2) +#define DISTANCE_BUFFER_SIZE (NUM_DISTANCE_SYMBOLS * 2) +#define CODE_LENGTH_BUFFER_SIZE (NUM_DISTANCE_SYMBOLS * 2) + +#define SET_ERROR(upng,code) do { (upng)->error = (code); (upng)->error_line = __LINE__; } while (0) + +#define upng_chunk_length(chunk) MAKE_DWORD_PTR(chunk) +#define upng_chunk_type(chunk) MAKE_DWORD_PTR((chunk) + 4) +#define upng_chunk_critical(chunk) (((chunk)[4] & 32) == 0) + +typedef enum upng_state { + UPNG_ERROR = -1, + UPNG_DECODED = 0, + UPNG_HEADER = 1, + UPNG_NEW = 2 +} upng_state; + +typedef enum upng_color { + UPNG_LUM = 0, + UPNG_RGB = 2, + UPNG_LUMA = 4, + UPNG_RGBA = 6 +} upng_color; + +typedef struct upng_source { + const unsigned char* buffer; + unsigned long size; + char owning; +} upng_source; + +struct upng_t { + unsigned width; + unsigned height; + + upng_color color_type; + unsigned color_depth; + upng_format format; + + unsigned char* buffer; + unsigned long size; + + upng_error error; + unsigned error_line; + + upng_state state; + upng_source source; +}; + +typedef struct huffman_tree { + unsigned* tree2d; + unsigned maxbitlen; /*maximum number of bits a single code can get */ + unsigned numcodes; /*number of symbols in the alphabet = number of codes */ +} huffman_tree; + +static const unsigned LENGTH_BASE[29] = { /*the base lengths represented by codes 257-285 */ + 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 +}; + +static const unsigned LENGTH_EXTRA[29] = { /*the extra bits used by codes 257-285 (added to base length) */ + 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 +}; + +static const unsigned DISTANCE_BASE[30] = { /*the base backwards distances (the bits of distance codes appear after length codes and use their own huffman tree) */ + 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 +}; + +static const unsigned DISTANCE_EXTRA[30] = { /*the extra bits of backwards distances (added to base) */ + 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 unsigned CLCL[NUM_CODE_LENGTH_CODES] /*the order in which "code length alphabet code lengths" are stored, out of this the huffman tree of the dynamic huffman tree lengths is generated */ += { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; + +static const unsigned FIXED_DEFLATE_CODE_TREE[NUM_DEFLATE_CODE_SYMBOLS * 2] = { + 289, 370, 290, 307, 546, 291, 561, 292, 293, 300, 294, 297, 295, 296, 0, 1, + 2, 3, 298, 299, 4, 5, 6, 7, 301, 304, 302, 303, 8, 9, 10, 11, 305, 306, 12, + 13, 14, 15, 308, 339, 309, 324, 310, 317, 311, 314, 312, 313, 16, 17, 18, + 19, 315, 316, 20, 21, 22, 23, 318, 321, 319, 320, 24, 25, 26, 27, 322, 323, + 28, 29, 30, 31, 325, 332, 326, 329, 327, 328, 32, 33, 34, 35, 330, 331, 36, + 37, 38, 39, 333, 336, 334, 335, 40, 41, 42, 43, 337, 338, 44, 45, 46, 47, + 340, 355, 341, 348, 342, 345, 343, 344, 48, 49, 50, 51, 346, 347, 52, 53, + 54, 55, 349, 352, 350, 351, 56, 57, 58, 59, 353, 354, 60, 61, 62, 63, 356, + 363, 357, 360, 358, 359, 64, 65, 66, 67, 361, 362, 68, 69, 70, 71, 364, + 367, 365, 366, 72, 73, 74, 75, 368, 369, 76, 77, 78, 79, 371, 434, 372, + 403, 373, 388, 374, 381, 375, 378, 376, 377, 80, 81, 82, 83, 379, 380, 84, + 85, 86, 87, 382, 385, 383, 384, 88, 89, 90, 91, 386, 387, 92, 93, 94, 95, + 389, 396, 390, 393, 391, 392, 96, 97, 98, 99, 394, 395, 100, 101, 102, 103, + 397, 400, 398, 399, 104, 105, 106, 107, 401, 402, 108, 109, 110, 111, 404, + 419, 405, 412, 406, 409, 407, 408, 112, 113, 114, 115, 410, 411, 116, 117, + 118, 119, 413, 416, 414, 415, 120, 121, 122, 123, 417, 418, 124, 125, 126, + 127, 420, 427, 421, 424, 422, 423, 128, 129, 130, 131, 425, 426, 132, 133, + 134, 135, 428, 431, 429, 430, 136, 137, 138, 139, 432, 433, 140, 141, 142, + 143, 435, 483, 436, 452, 568, 437, 438, 445, 439, 442, 440, 441, 144, 145, + 146, 147, 443, 444, 148, 149, 150, 151, 446, 449, 447, 448, 152, 153, 154, + 155, 450, 451, 156, 157, 158, 159, 453, 468, 454, 461, 455, 458, 456, 457, + 160, 161, 162, 163, 459, 460, 164, 165, 166, 167, 462, 465, 463, 464, 168, + 169, 170, 171, 466, 467, 172, 173, 174, 175, 469, 476, 470, 473, 471, 472, + 176, 177, 178, 179, 474, 475, 180, 181, 182, 183, 477, 480, 478, 479, 184, + 185, 186, 187, 481, 482, 188, 189, 190, 191, 484, 515, 485, 500, 486, 493, + 487, 490, 488, 489, 192, 193, 194, 195, 491, 492, 196, 197, 198, 199, 494, + 497, 495, 496, 200, 201, 202, 203, 498, 499, 204, 205, 206, 207, 501, 508, + 502, 505, 503, 504, 208, 209, 210, 211, 506, 507, 212, 213, 214, 215, 509, + 512, 510, 511, 216, 217, 218, 219, 513, 514, 220, 221, 222, 223, 516, 531, + 517, 524, 518, 521, 519, 520, 224, 225, 226, 227, 522, 523, 228, 229, 230, + 231, 525, 528, 526, 527, 232, 233, 234, 235, 529, 530, 236, 237, 238, 239, + 532, 539, 533, 536, 534, 535, 240, 241, 242, 243, 537, 538, 244, 245, 246, + 247, 540, 543, 541, 542, 248, 249, 250, 251, 544, 545, 252, 253, 254, 255, + 547, 554, 548, 551, 549, 550, 256, 257, 258, 259, 552, 553, 260, 261, 262, + 263, 555, 558, 556, 557, 264, 265, 266, 267, 559, 560, 268, 269, 270, 271, + 562, 565, 563, 564, 272, 273, 274, 275, 566, 567, 276, 277, 278, 279, 569, + 572, 570, 571, 280, 281, 282, 283, 573, 574, 284, 285, 286, 287, 0, 0 +}; + +static const unsigned FIXED_DISTANCE_TREE[NUM_DISTANCE_SYMBOLS * 2] = { + 33, 48, 34, 41, 35, 38, 36, 37, 0, 1, 2, 3, 39, 40, 4, 5, 6, 7, 42, 45, 43, + 44, 8, 9, 10, 11, 46, 47, 12, 13, 14, 15, 49, 56, 50, 53, 51, 52, 16, 17, + 18, 19, 54, 55, 20, 21, 22, 23, 57, 60, 58, 59, 24, 25, 26, 27, 61, 62, 28, + 29, 30, 31, 0, 0 +}; + +static unsigned char read_bit(unsigned long *bitpointer, const unsigned char *bitstream) +{ + unsigned char result = (unsigned char)((bitstream[(*bitpointer) >> 3] >> ((*bitpointer) & 0x7)) & 1); + (*bitpointer)++; + return result; +} + +static unsigned read_bits(unsigned long *bitpointer, const unsigned char *bitstream, unsigned long nbits) +{ + unsigned result = 0, i; + for (i = 0; i < nbits; i++) + result |= ((unsigned)read_bit(bitpointer, bitstream)) << i; + return result; +} + +/* the buffer must be numcodes*2 in size! */ +static void huffman_tree_init(huffman_tree* tree, unsigned* buffer, unsigned numcodes, unsigned maxbitlen) +{ + tree->tree2d = buffer; + + tree->numcodes = numcodes; + tree->maxbitlen = maxbitlen; +} + +/*given the code lengths (as stored in the PNG file), generate the tree as defined by Deflate. maxbitlen is the maximum bits that a code in the tree can have. return value is error.*/ +static void huffman_tree_create_lengths(upng_t* upng, huffman_tree* tree, const unsigned *bitlen) +{ + unsigned tree1d[MAX_SYMBOLS]; + unsigned blcount[MAX_BIT_LENGTH]; + unsigned nextcode[MAX_BIT_LENGTH+1]; + unsigned bits, n, i; + unsigned nodefilled = 0; /*up to which node it is filled */ + unsigned treepos = 0; /*position in the tree (1 of the numcodes columns) */ + + /* initialize local vectors */ + memset(blcount, 0, sizeof(blcount)); + memset(nextcode, 0, sizeof(nextcode)); + + /*step 1: count number of instances of each code length */ + for (bits = 0; bits < tree->numcodes; bits++) { + blcount[bitlen[bits]]++; + } + + /*step 2: generate the nextcode values */ + for (bits = 1; bits <= tree->maxbitlen; bits++) { + nextcode[bits] = (nextcode[bits - 1] + blcount[bits - 1]) << 1; + } + + /*step 3: generate all the codes */ + for (n = 0; n < tree->numcodes; n++) { + if (bitlen[n] != 0) { + tree1d[n] = nextcode[bitlen[n]]++; + } + } + + /*convert tree1d[] to tree2d[][]. In the 2D array, a value of 32767 means uninited, a value >= numcodes is an address to another bit, a value < numcodes is a code. The 2 rows are the 2 possible bit values (0 or 1), there are as many columns as codes - 1 + a good huffmann tree has N * 2 - 1 nodes, of which N - 1 are internal nodes. Here, the internal nodes are stored (what their 0 and 1 option point to). There is only memory for such good tree currently, if there are more nodes (due to too long length codes), error 55 will happen */ + for (n = 0; n < tree->numcodes * 2; n++) { + tree->tree2d[n] = 32767; /*32767 here means the tree2d isn't filled there yet */ + } + + for (n = 0; n < tree->numcodes; n++) { /*the codes */ + for (i = 0; i < bitlen[n]; i++) { /*the bits for this code */ + unsigned char bit = (unsigned char)((tree1d[n] >> (bitlen[n] - i - 1)) & 1); + /* check if oversubscribed */ + if (treepos > tree->numcodes - 2) { + SET_ERROR(upng, UPNG_EMALFORMED); + return; + } + + if (tree->tree2d[2 * treepos + bit] == 32767) { /*not yet filled in */ + if (i + 1 == bitlen[n]) { /*last bit */ + tree->tree2d[2 * treepos + bit] = n; /*put the current code in it */ + treepos = 0; + } else { /*put address of the next step in here, first that address has to be found of course (it's just nodefilled + 1)... */ + nodefilled++; + tree->tree2d[2 * treepos + bit] = nodefilled + tree->numcodes; /*addresses encoded with numcodes added to it */ + treepos = nodefilled; + } + } else { + treepos = tree->tree2d[2 * treepos + bit] - tree->numcodes; + } + } + } + + for (n = 0; n < tree->numcodes * 2; n++) { + if (tree->tree2d[n] == 32767) { + tree->tree2d[n] = 0; /*remove possible remaining 32767's */ + } + } +} + +static unsigned huffman_decode_symbol(upng_t *upng, const unsigned char *in, unsigned long *bp, const huffman_tree* codetree, unsigned long inlength) +{ + unsigned treepos = 0, ct; + unsigned char bit; + for (;;) { + /* error: end of input memory reached without endcode */ + if (((*bp) & 0x07) == 0 && ((*bp) >> 3) > inlength) { + SET_ERROR(upng, UPNG_EMALFORMED); + return 0; + } + + bit = read_bit(bp, in); + + ct = codetree->tree2d[(treepos << 1) | bit]; + if (ct < codetree->numcodes) { + return ct; + } + + treepos = ct - codetree->numcodes; + if (treepos >= codetree->numcodes) { + SET_ERROR(upng, UPNG_EMALFORMED); + return 0; + } + } +} + +/* get the tree of a deflated block with dynamic tree, the tree itself is also Huffman compressed with a known tree*/ +static void get_tree_inflate_dynamic(upng_t* upng, huffman_tree* codetree, huffman_tree* codetreeD, huffman_tree* codelengthcodetree, const unsigned char *in, unsigned long *bp, unsigned long inlength) +{ + unsigned codelengthcode[NUM_CODE_LENGTH_CODES]; + unsigned bitlen[NUM_DEFLATE_CODE_SYMBOLS]; + unsigned bitlenD[NUM_DISTANCE_SYMBOLS]; + unsigned n, hlit, hdist, hclen, i; + + /*make sure that length values that aren't filled in will be 0, or a wrong tree will be generated */ + /*C-code note: use no "return" between ctor and dtor of an uivector! */ + if ((*bp) >> 3 >= inlength - 2) { + SET_ERROR(upng, UPNG_EMALFORMED); + return; + } + + /* clear bitlen arrays */ + memset(bitlen, 0, sizeof(bitlen)); + memset(bitlenD, 0, sizeof(bitlenD)); + + /*the bit pointer is or will go past the memory */ + hlit = read_bits(bp, in, 5) + 257; /*number of literal/length codes + 257. Unlike the spec, the value 257 is added to it here already */ + hdist = read_bits(bp, in, 5) + 1; /*number of distance codes. Unlike the spec, the value 1 is added to it here already */ + hclen = read_bits(bp, in, 4) + 4; /*number of code length codes. Unlike the spec, the value 4 is added to it here already */ + + for (i = 0; i < NUM_CODE_LENGTH_CODES; i++) { + if (i < hclen) { + codelengthcode[CLCL[i]] = read_bits(bp, in, 3); + } else { + codelengthcode[CLCL[i]] = 0; /*if not, it must stay 0 */ + } + } + + huffman_tree_create_lengths(upng, codelengthcodetree, codelengthcode); + + /* bail now if we encountered an error earlier */ + if (upng->error != UPNG_EOK) { + return; + } + + /*now we can use this tree to read the lengths for the tree that this function will return */ + i = 0; + while (i < hlit + hdist) { /*i is the current symbol we're reading in the part that contains the code lengths of lit/len codes and dist codes */ + unsigned code = huffman_decode_symbol(upng, in, bp, codelengthcodetree, inlength); + if (upng->error != UPNG_EOK) { + break; + } + + if (code <= 15) { /*a length code */ + if (i < hlit) { + bitlen[i] = code; + } else { + bitlenD[i - hlit] = code; + } + i++; + } else if (code == 16) { /*repeat previous */ + unsigned replength = 3; /*read in the 2 bits that indicate repeat length (3-6) */ + unsigned value; /*set value to the previous code */ + + if ((*bp) >> 3 >= inlength) { + SET_ERROR(upng, UPNG_EMALFORMED); + break; + } + /*error, bit pointer jumps past memory */ + replength += read_bits(bp, in, 2); + + if ((i - 1) < hlit) { + value = bitlen[i - 1]; + } else { + value = bitlenD[i - hlit - 1]; + } + + /*repeat this value in the next lengths */ + for (n = 0; n < replength; n++) { + /* i is larger than the amount of codes */ + if (i >= hlit + hdist) { + SET_ERROR(upng, UPNG_EMALFORMED); + break; + } + + if (i < hlit) { + bitlen[i] = value; + } else { + bitlenD[i - hlit] = value; + } + i++; + } + } else if (code == 17) { /*repeat "0" 3-10 times */ + unsigned replength = 3; /*read in the bits that indicate repeat length */ + if ((*bp) >> 3 >= inlength) { + SET_ERROR(upng, UPNG_EMALFORMED); + break; + } + + /*error, bit pointer jumps past memory */ + replength += read_bits(bp, in, 3); + + /*repeat this value in the next lengths */ + for (n = 0; n < replength; n++) { + /* error: i is larger than the amount of codes */ + if (i >= hlit + hdist) { + SET_ERROR(upng, UPNG_EMALFORMED); + break; + } + + if (i < hlit) { + bitlen[i] = 0; + } else { + bitlenD[i - hlit] = 0; + } + i++; + } + } else if (code == 18) { /*repeat "0" 11-138 times */ + unsigned replength = 11; /*read in the bits that indicate repeat length */ + /* error, bit pointer jumps past memory */ + if ((*bp) >> 3 >= inlength) { + SET_ERROR(upng, UPNG_EMALFORMED); + break; + } + + replength += read_bits(bp, in, 7); + + /*repeat this value in the next lengths */ + for (n = 0; n < replength; n++) { + /* i is larger than the amount of codes */ + if (i >= hlit + hdist) { + SET_ERROR(upng, UPNG_EMALFORMED); + break; + } + if (i < hlit) + bitlen[i] = 0; + else + bitlenD[i - hlit] = 0; + i++; + } + } else { + /* somehow an unexisting code appeared. This can never happen. */ + SET_ERROR(upng, UPNG_EMALFORMED); + break; + } + } + + if (upng->error == UPNG_EOK && bitlen[256] == 0) { + SET_ERROR(upng, UPNG_EMALFORMED); + } + + /*the length of the end code 256 must be larger than 0 */ + /*now we've finally got hlit and hdist, so generate the code trees, and the function is done */ + if (upng->error == UPNG_EOK) { + huffman_tree_create_lengths(upng, codetree, bitlen); + } + if (upng->error == UPNG_EOK) { + huffman_tree_create_lengths(upng, codetreeD, bitlenD); + } +} + +/*inflate a block with dynamic of fixed Huffman tree*/ +static void inflate_huffman(upng_t* upng, unsigned char* out, unsigned long outsize, const unsigned char *in, unsigned long *bp, unsigned long *pos, unsigned long inlength, unsigned btype) +{ + unsigned codetree_buffer[DEFLATE_CODE_BUFFER_SIZE]; + unsigned codetreeD_buffer[DISTANCE_BUFFER_SIZE]; + unsigned done = 0; + + huffman_tree codetree; + huffman_tree codetreeD; + + if (btype == 1) { + /* fixed trees */ + huffman_tree_init(&codetree, (unsigned*)FIXED_DEFLATE_CODE_TREE, NUM_DEFLATE_CODE_SYMBOLS, DEFLATE_CODE_BITLEN); + huffman_tree_init(&codetreeD, (unsigned*)FIXED_DISTANCE_TREE, NUM_DISTANCE_SYMBOLS, DISTANCE_BITLEN); + } else if (btype == 2) { + /* dynamic trees */ + unsigned codelengthcodetree_buffer[CODE_LENGTH_BUFFER_SIZE]; + huffman_tree codelengthcodetree; + + huffman_tree_init(&codetree, codetree_buffer, NUM_DEFLATE_CODE_SYMBOLS, DEFLATE_CODE_BITLEN); + huffman_tree_init(&codetreeD, codetreeD_buffer, NUM_DISTANCE_SYMBOLS, DISTANCE_BITLEN); + huffman_tree_init(&codelengthcodetree, codelengthcodetree_buffer, NUM_CODE_LENGTH_CODES, CODE_LENGTH_BITLEN); + get_tree_inflate_dynamic(upng, &codetree, &codetreeD, &codelengthcodetree, in, bp, inlength); + } + + while (done == 0) { + unsigned code = huffman_decode_symbol(upng, in, bp, &codetree, inlength); + if (upng->error != UPNG_EOK) { + return; + } + + if (code == 256) { + /* end code */ + done = 1; + } else if (code <= 255) { + /* literal symbol */ + if ((*pos) >= outsize) { + SET_ERROR(upng, UPNG_EMALFORMED); + return; + } + + /* store output */ + out[(*pos)++] = (unsigned char)(code); + } else if (code >= FIRST_LENGTH_CODE_INDEX && code <= LAST_LENGTH_CODE_INDEX) { /*length code */ + /* part 1: get length base */ + unsigned long length = LENGTH_BASE[code - FIRST_LENGTH_CODE_INDEX]; + unsigned codeD, distance, numextrabitsD; + unsigned long start, forward, backward, numextrabits; + + /* part 2: get extra bits and add the value of that to length */ + numextrabits = LENGTH_EXTRA[code - FIRST_LENGTH_CODE_INDEX]; + + /* error, bit pointer will jump past memory */ + if (((*bp) >> 3) >= inlength) { + SET_ERROR(upng, UPNG_EMALFORMED); + return; + } + length += read_bits(bp, in, numextrabits); + + /*part 3: get distance code */ + codeD = huffman_decode_symbol(upng, in, bp, &codetreeD, inlength); + if (upng->error != UPNG_EOK) { + return; + } + + /* invalid distance code (30-31 are never used) */ + if (codeD > 29) { + SET_ERROR(upng, UPNG_EMALFORMED); + return; + } + + distance = DISTANCE_BASE[codeD]; + + /*part 4: get extra bits from distance */ + numextrabitsD = DISTANCE_EXTRA[codeD]; + + /* error, bit pointer will jump past memory */ + if (((*bp) >> 3) >= inlength) { + SET_ERROR(upng, UPNG_EMALFORMED); + return; + } + + distance += read_bits(bp, in, numextrabitsD); + + /*part 5: fill in all the out[n] values based on the length and dist */ + start = (*pos); + backward = start - distance; + + if ((*pos) + length >= outsize) { + SET_ERROR(upng, UPNG_EMALFORMED); + return; + } + + for (forward = 0; forward < length; forward++) { + out[(*pos)++] = out[backward]; + backward++; + + if (backward >= start) { + backward = start - distance; + } + } + } + } +} + +static void inflate_uncompressed(upng_t* upng, unsigned char* out, unsigned long outsize, const unsigned char *in, unsigned long *bp, unsigned long *pos, unsigned long inlength) +{ + unsigned long p; + unsigned len, nlen, n; + + /* go to first boundary of byte */ + while (((*bp) & 0x7) != 0) { + (*bp)++; + } + p = (*bp) / 8; /*byte position */ + + /* read len (2 bytes) and nlen (2 bytes) */ + if (p >= inlength - 4) { + SET_ERROR(upng, UPNG_EMALFORMED); + return; + } + + len = in[p] + 256 * in[p + 1]; + p += 2; + nlen = in[p] + 256 * in[p + 1]; + p += 2; + + /* check if 16-bit nlen is really the one's complement of len */ + if (len + nlen != 65535) { + SET_ERROR(upng, UPNG_EMALFORMED); + return; + } + + if ((*pos) + len >= outsize) { + SET_ERROR(upng, UPNG_EMALFORMED); + return; + } + + /* read the literal data: len bytes are now stored in the out buffer */ + if (p + len > inlength) { + SET_ERROR(upng, UPNG_EMALFORMED); + return; + } + + for (n = 0; n < len; n++) { + out[(*pos)++] = in[p++]; + } + + (*bp) = p * 8; +} + +/*inflate the deflated data (cfr. deflate spec); return value is the error*/ +static upng_error uz_inflate_data(upng_t* upng, unsigned char* out, unsigned long outsize, const unsigned char *in, unsigned long insize, unsigned long inpos) +{ + unsigned long bp = 0; /*bit pointer in the "in" data, current byte is bp >> 3, current bit is bp & 0x7 (from lsb to msb of the byte) */ + unsigned long pos = 0; /*byte position in the out buffer */ + + unsigned done = 0; + + while (done == 0) { + unsigned btype; + + /* ensure next bit doesn't point past the end of the buffer */ + if ((bp >> 3) >= insize) { + SET_ERROR(upng, UPNG_EMALFORMED); + return upng->error; + } + + /* read block control bits */ + done = read_bit(&bp, &in[inpos]); + btype = read_bit(&bp, &in[inpos]) | (read_bit(&bp, &in[inpos]) << 1); + + /* process control type appropriateyly */ + if (btype == 3) { + SET_ERROR(upng, UPNG_EMALFORMED); + return upng->error; + } else if (btype == 0) { + inflate_uncompressed(upng, out, outsize, &in[inpos], &bp, &pos, insize); /*no compression */ + } else { + inflate_huffman(upng, out, outsize, &in[inpos], &bp, &pos, insize, btype); /*compression, btype 01 or 10 */ + } + + /* stop if an error has occured */ + if (upng->error != UPNG_EOK) { + return upng->error; + } + } + + return upng->error; +} + +static upng_error uz_inflate(upng_t* upng, unsigned char *out, unsigned long outsize, const unsigned char *in, unsigned long insize) +{ + /* we require two bytes for the zlib data header */ + if (insize < 2) { + SET_ERROR(upng, UPNG_EMALFORMED); + return upng->error; + } + + /* 256 * in[0] + in[1] must be a multiple of 31, the FCHECK value is supposed to be made that way */ + if ((in[0] * 256 + in[1]) % 31 != 0) { + SET_ERROR(upng, UPNG_EMALFORMED); + return upng->error; + } + + /*error: only compression method 8: inflate with sliding window of 32k is supported by the PNG spec */ + if ((in[0] & 15) != 8 || ((in[0] >> 4) & 15) > 7) { + SET_ERROR(upng, UPNG_EMALFORMED); + return upng->error; + } + + /* the specification of PNG says about the zlib stream: "The additional flags shall not specify a preset dictionary." */ + if (((in[1] >> 5) & 1) != 0) { + SET_ERROR(upng, UPNG_EMALFORMED); + return upng->error; + } + + /* create output buffer */ + uz_inflate_data(upng, out, outsize, in, insize, 2); + + return upng->error; +} + +/*Paeth predicter, used by PNG filter type 4*/ +static int paeth_predictor(int a, int b, int c) +{ + int p = a + b - c; + int pa = p > a ? p - a : a - p; + int pb = p > b ? p - b : b - p; + int pc = p > c ? p - c : c - p; + + if (pa <= pb && pa <= pc) + return a; + else if (pb <= pc) + return b; + else + return c; +} + +static void unfilter_scanline(upng_t* upng, unsigned char *recon, const unsigned char *scanline, const unsigned char *precon, unsigned long bytewidth, unsigned char filterType, unsigned long length) +{ + /* + For PNG filter method 0 + unfilter a PNG image scanline by scanline. when the pixels are smaller than 1 byte, the filter works byte per byte (bytewidth = 1) + precon is the previous unfiltered scanline, recon the result, scanline the current one + the incoming scanlines do NOT include the filtertype byte, that one is given in the parameter filterType instead + recon and scanline MAY be the same memory address! precon must be disjoint. + */ + + unsigned long i; + switch (filterType) { + case 0: + for (i = 0; i < length; i++) + recon[i] = scanline[i]; + break; + case 1: + for (i = 0; i < bytewidth; i++) + recon[i] = scanline[i]; + for (i = bytewidth; i < length; i++) + recon[i] = scanline[i] + recon[i - bytewidth]; + break; + case 2: + if (precon) + for (i = 0; i < length; i++) + recon[i] = scanline[i] + precon[i]; + else + for (i = 0; i < length; i++) + recon[i] = scanline[i]; + break; + case 3: + if (precon) { + for (i = 0; i < bytewidth; i++) + recon[i] = scanline[i] + precon[i] / 2; + for (i = bytewidth; i < length; i++) + recon[i] = scanline[i] + ((recon[i - bytewidth] + precon[i]) / 2); + } else { + for (i = 0; i < bytewidth; i++) + recon[i] = scanline[i]; + for (i = bytewidth; i < length; i++) + recon[i] = scanline[i] + recon[i - bytewidth] / 2; + } + break; + case 4: + if (precon) { + for (i = 0; i < bytewidth; i++) + recon[i] = (unsigned char)(scanline[i] + paeth_predictor(0, precon[i], 0)); + for (i = bytewidth; i < length; i++) + recon[i] = (unsigned char)(scanline[i] + paeth_predictor(recon[i - bytewidth], precon[i], precon[i - bytewidth])); + } else { + for (i = 0; i < bytewidth; i++) + recon[i] = scanline[i]; + for (i = bytewidth; i < length; i++) + recon[i] = (unsigned char)(scanline[i] + paeth_predictor(recon[i - bytewidth], 0, 0)); + } + break; + default: + SET_ERROR(upng, UPNG_EMALFORMED); + break; + } +} + +static void unfilter(upng_t* upng, unsigned char *out, const unsigned char *in, unsigned w, unsigned h, unsigned bpp) +{ + /* + For PNG filter method 0 + this function unfilters a single image (e.g. without interlacing this is called once, with Adam7 it's called 7 times) + out must have enough bytes allocated already, in must have the scanlines + 1 filtertype byte per scanline + w and h are image dimensions or dimensions of reduced image, bpp is bpp per pixel + in and out are allowed to be the same memory address! + */ + + unsigned y; + unsigned char *prevline = 0; + + unsigned long bytewidth = (bpp + 7) / 8; /*bytewidth is used for filtering, is 1 when bpp < 8, number of bytes per pixel otherwise */ + unsigned long linebytes = (w * bpp + 7) / 8; + + for (y = 0; y < h; y++) { + unsigned long outindex = linebytes * y; + unsigned long inindex = (1 + linebytes) * y; /*the extra filterbyte added to each row */ + unsigned char filterType = in[inindex]; + + unfilter_scanline(upng, &out[outindex], &in[inindex + 1], prevline, bytewidth, filterType, linebytes); + if (upng->error != UPNG_EOK) { + return; + } + + prevline = &out[outindex]; + } +} + +static void remove_padding_bits(unsigned char *out, const unsigned char *in, unsigned long olinebits, unsigned long ilinebits, unsigned h) +{ + /* + After filtering there are still padding bpp if scanlines have non multiple of 8 bit amounts. They need to be removed (except at last scanline of (Adam7-reduced) image) before working with pure image buffers for the Adam7 code, the color convert code and the output to the user. + in and out are allowed to be the same buffer, in may also be higher but still overlapping; in must have >= ilinebits*h bpp, out must have >= olinebits*h bpp, olinebits must be <= ilinebits + also used to move bpp after earlier such operations happened, e.g. in a sequence of reduced images from Adam7 + only useful if (ilinebits - olinebits) is a value in the range 1..7 + */ + unsigned y; + unsigned long diff = ilinebits - olinebits; + unsigned long obp = 0, ibp = 0; /*bit pointers */ + for (y = 0; y < h; y++) { + unsigned long x; + for (x = 0; x < olinebits; x++) { + unsigned char bit = (unsigned char)((in[(ibp) >> 3] >> (7 - ((ibp) & 0x7))) & 1); + ibp++; + + if (bit == 0) + out[(obp) >> 3] &= (unsigned char)(~(1 << (7 - ((obp) & 0x7)))); + else + out[(obp) >> 3] |= (1 << (7 - ((obp) & 0x7))); + ++obp; + } + ibp += diff; + } +} + +/*out must be buffer big enough to contain full image, and in must contain the full decompressed data from the IDAT chunks*/ +static void post_process_scanlines(upng_t* upng, unsigned char *out, unsigned char *in, const upng_t* info_png) +{ + unsigned bpp = upng_get_bpp(info_png); + unsigned w = info_png->width; + unsigned h = info_png->height; + + if (bpp == 0) { + SET_ERROR(upng, UPNG_EMALFORMED); + return; + } + + if (bpp < 8 && w * bpp != ((w * bpp + 7) / 8) * 8) { + unfilter(upng, in, in, w, h, bpp); + if (upng->error != UPNG_EOK) { + return; + } + remove_padding_bits(out, in, w * bpp, ((w * bpp + 7) / 8) * 8, h); + } else { + unfilter(upng, out, in, w, h, bpp); /*we can immediatly filter into the out buffer, no other steps needed */ + } +} + +static upng_format determine_format(upng_t* upng) { + switch (upng->color_type) { + case UPNG_LUM: + switch (upng->color_depth) { + case 1: + return UPNG_LUMINANCE1; + case 2: + return UPNG_LUMINANCE2; + case 4: + return UPNG_LUMINANCE4; + case 8: + return UPNG_LUMINANCE8; + default: + return UPNG_BADFORMAT; + } + case UPNG_RGB: + switch (upng->color_depth) { + case 8: + return UPNG_RGB8; + case 16: + return UPNG_RGB16; + default: + return UPNG_BADFORMAT; + } + case UPNG_LUMA: + switch (upng->color_depth) { + case 1: + return UPNG_LUMINANCE_ALPHA1; + case 2: + return UPNG_LUMINANCE_ALPHA2; + case 4: + return UPNG_LUMINANCE_ALPHA4; + case 8: + return UPNG_LUMINANCE_ALPHA8; + default: + return UPNG_BADFORMAT; + } + case UPNG_RGBA: + switch (upng->color_depth) { + case 8: + return UPNG_RGBA8; + case 16: + return UPNG_RGBA16; + default: + return UPNG_BADFORMAT; + } + default: + return UPNG_BADFORMAT; + } +} + +static void upng_free_source(upng_t* upng) +{ + if (upng->source.owning != 0) { + free((void*)upng->source.buffer); + } + + upng->source.buffer = NULL; + upng->source.size = 0; + upng->source.owning = 0; +} + +/*read the information from the header and store it in the upng_Info. return value is error*/ +upng_error upng_header(upng_t* upng) +{ + /* if we have an error state, bail now */ + if (upng->error != UPNG_EOK) { + return upng->error; + } + + /* if the state is not NEW (meaning we are ready to parse the header), stop now */ + if (upng->state != UPNG_NEW) { + return upng->error; + } + + /* minimum length of a valid PNG file is 29 bytes + * FIXME: verify this against the specification, or + * better against the actual code below */ + if (upng->source.size < 29) { + SET_ERROR(upng, UPNG_ENOTPNG); + return upng->error; + } + + /* check that PNG header matches expected value */ + if (upng->source.buffer[0] != 137 || upng->source.buffer[1] != 80 || upng->source.buffer[2] != 78 || upng->source.buffer[3] != 71 || upng->source.buffer[4] != 13 || upng->source.buffer[5] != 10 || upng->source.buffer[6] != 26 || upng->source.buffer[7] != 10) { + SET_ERROR(upng, UPNG_ENOTPNG); + return upng->error; + } + + /* check that the first chunk is the IHDR chunk */ + if (MAKE_DWORD_PTR(upng->source.buffer + 12) != CHUNK_IHDR) { + SET_ERROR(upng, UPNG_EMALFORMED); + return upng->error; + } + + /* read the values given in the header */ + upng->width = MAKE_DWORD_PTR(upng->source.buffer + 16); + upng->height = MAKE_DWORD_PTR(upng->source.buffer + 20); + upng->color_depth = upng->source.buffer[24]; + upng->color_type = (upng_color)upng->source.buffer[25]; + + /* determine our color format */ + upng->format = determine_format(upng); + if (upng->format == UPNG_BADFORMAT) { + SET_ERROR(upng, UPNG_EUNFORMAT); + return upng->error; + } + + /* check that the compression method (byte 27) is 0 (only allowed value in spec) */ + if (upng->source.buffer[26] != 0) { + SET_ERROR(upng, UPNG_EMALFORMED); + return upng->error; + } + + /* check that the compression method (byte 27) is 0 (only allowed value in spec) */ + if (upng->source.buffer[27] != 0) { + SET_ERROR(upng, UPNG_EMALFORMED); + return upng->error; + } + + /* check that the compression method (byte 27) is 0 (spec allows 1, but uPNG does not support it) */ + if (upng->source.buffer[28] != 0) { + SET_ERROR(upng, UPNG_EUNINTERLACED); + return upng->error; + } + + upng->state = UPNG_HEADER; + return upng->error; +} + +/*read a PNG, the result will be in the same color type as the PNG (hence "generic")*/ +upng_error upng_decode(upng_t* upng) +{ + const unsigned char *chunk; + unsigned char* compressed; + unsigned char* inflated; + unsigned long compressed_size = 0, compressed_index = 0; + unsigned long inflated_size; + upng_error error; + + /* if we have an error state, bail now */ + if (upng->error != UPNG_EOK) { + return upng->error; + } + + /* parse the main header, if necessary */ + upng_header(upng); + if (upng->error != UPNG_EOK) { + return upng->error; + } + + /* if the state is not HEADER (meaning we are ready to decode the image), stop now */ + if (upng->state != UPNG_HEADER) { + return upng->error; + } + + /* release old result, if any */ + if (upng->buffer != 0) { + free(upng->buffer); + upng->buffer = 0; + upng->size = 0; + } + + /* first byte of the first chunk after the header */ + chunk = upng->source.buffer + 33; + + /* scan through the chunks, finding the size of all IDAT chunks, and also + * verify general well-formed-ness */ + while (chunk < upng->source.buffer + upng->source.size) { + unsigned long length; + const unsigned char *data; /*the data in the chunk */ + + /* make sure chunk header is not larger than the total compressed */ + if ((unsigned long)(chunk - upng->source.buffer + 12) > upng->source.size) { + SET_ERROR(upng, UPNG_EMALFORMED); + return upng->error; + } + + /* get length; sanity check it */ + length = upng_chunk_length(chunk); + if (length > INT_MAX) { + SET_ERROR(upng, UPNG_EMALFORMED); + return upng->error; + } + + /* make sure chunk header+paylaod is not larger than the total compressed */ + if ((unsigned long)(chunk - upng->source.buffer + length + 12) > upng->source.size) { + SET_ERROR(upng, UPNG_EMALFORMED); + return upng->error; + } + + /* get pointer to payload */ + data = chunk + 8; + + /* parse chunks */ + if (upng_chunk_type(chunk) == CHUNK_IDAT) { + compressed_size += length; + } else if (upng_chunk_type(chunk) == CHUNK_IEND) { + break; + } else if (upng_chunk_critical(chunk)) { + SET_ERROR(upng, UPNG_EUNSUPPORTED); + return upng->error; + } + + chunk += upng_chunk_length(chunk) + 12; + } + + /* allocate enough space for the (compressed and filtered) image data */ + compressed = (unsigned char*)malloc(compressed_size); + if (compressed == NULL) { + SET_ERROR(upng, UPNG_ENOMEM); + return upng->error; + } + + /* scan through the chunks again, this time copying the values into + * our compressed buffer. there's no reason to validate anything a second time. */ + chunk = upng->source.buffer + 33; + while (chunk < upng->source.buffer + upng->source.size) { + unsigned long length; + const unsigned char *data; /*the data in the chunk */ + + length = upng_chunk_length(chunk); + data = chunk + 8; + + /* parse chunks */ + if (upng_chunk_type(chunk) == CHUNK_IDAT) { + memcpy(compressed + compressed_index, data, length); + compressed_index += length; + } else if (upng_chunk_type(chunk) == CHUNK_IEND) { + break; + } + + chunk += upng_chunk_length(chunk) + 12; + } + + /* allocate space to store inflated (but still filtered) data */ + inflated_size = ((upng->width * (upng->height * upng_get_bpp(upng) + 7)) / 8) + upng->height; + inflated = (unsigned char*)malloc(inflated_size); + if (inflated == NULL) { + free(compressed); + SET_ERROR(upng, UPNG_ENOMEM); + return upng->error; + } + + /* decompress image data */ + error = uz_inflate(upng, inflated, inflated_size, compressed, compressed_size); + if (error != UPNG_EOK) { + free(compressed); + free(inflated); + return upng->error; + } + + /* free the compressed compressed data */ + free(compressed); + + /* allocate final image buffer */ + upng->size = (upng->height * upng->width * upng_get_bpp(upng) + 7) / 8; + upng->buffer = (unsigned char*)malloc(upng->size); + if (upng->buffer == NULL) { + free(inflated); + upng->size = 0; + SET_ERROR(upng, UPNG_ENOMEM); + return upng->error; + } + + /* unfilter scanlines */ + post_process_scanlines(upng, upng->buffer, inflated, upng); + free(inflated); + + if (upng->error != UPNG_EOK) { + free(upng->buffer); + upng->buffer = NULL; + upng->size = 0; + } else { + upng->state = UPNG_DECODED; + } + + /* we are done with our input buffer; free it if we own it */ + upng_free_source(upng); + + return upng->error; +} + +static upng_t* upng_new(void) +{ + upng_t* upng; + + upng = (upng_t*)malloc(sizeof(upng_t)); + if (upng == NULL) { + return NULL; + } + + upng->buffer = NULL; + upng->size = 0; + + upng->width = upng->height = 0; + + upng->color_type = UPNG_RGBA; + upng->color_depth = 8; + upng->format = UPNG_RGBA8; + + upng->state = UPNG_NEW; + + upng->error = UPNG_EOK; + upng->error_line = 0; + + upng->source.buffer = NULL; + upng->source.size = 0; + upng->source.owning = 0; + + return upng; +} + +upng_t* upng_new_from_bytes(const unsigned char* buffer, unsigned long size) +{ + upng_t* upng = upng_new(); + if (upng == NULL) { + return NULL; + } + + upng->source.buffer = buffer; + upng->source.size = size; + upng->source.owning = 0; + + return upng; +} + +upng_t* upng_new_from_file(const char *filename) +{ + upng_t* upng; + unsigned char *buffer; + FILE *file; + long size; + + upng = upng_new(); + if (upng == NULL) { + return NULL; + } + + file = fopen(filename, "rb"); + if (file == NULL) { + SET_ERROR(upng, UPNG_ENOTFOUND); + return upng; + } + + /* get filesize */ + fseek(file, 0, SEEK_END); + size = ftell(file); + rewind(file); + + /* read contents of the file into the vector */ + buffer = (unsigned char *)malloc((unsigned long)size); + if (buffer == NULL) { + fclose(file); + SET_ERROR(upng, UPNG_ENOMEM); + return upng; + } + fread(buffer, 1, (unsigned long)size, file); + fclose(file); + + /* set the read buffer as our source buffer, with owning flag set */ + upng->source.buffer = buffer; + upng->source.size = size; + upng->source.owning = 1; + + return upng; +} + +void upng_free(upng_t* upng) +{ + /* deallocate image buffer */ + if (upng->buffer != NULL) { + free(upng->buffer); + } + + /* deallocate source buffer, if necessary */ + upng_free_source(upng); + + /* deallocate struct itself */ + free(upng); +} + +upng_error upng_get_error(const upng_t* upng) +{ + return upng->error; +} + +unsigned upng_get_error_line(const upng_t* upng) +{ + return upng->error_line; +} + +unsigned upng_get_width(const upng_t* upng) +{ + return upng->width; +} + +unsigned upng_get_height(const upng_t* upng) +{ + return upng->height; +} + +unsigned upng_get_bpp(const upng_t* upng) +{ + return upng_get_bitdepth(upng) * upng_get_components(upng); +} + +unsigned upng_get_components(const upng_t* upng) +{ + switch (upng->color_type) { + case UPNG_LUM: + return 1; + case UPNG_RGB: + return 3; + case UPNG_LUMA: + return 2; + case UPNG_RGBA: + return 4; + default: + return 0; + } +} + +unsigned upng_get_bitdepth(const upng_t* upng) +{ + return upng->color_depth; +} + +unsigned upng_get_pixelsize(const upng_t* upng) +{ + unsigned bits = upng_get_bitdepth(upng) * upng_get_components(upng); + bits += bits % 8; + return bits; +} + +upng_format upng_get_format(const upng_t* upng) +{ + return upng->format; +} + +const unsigned char* upng_get_buffer(const upng_t* upng) +{ + return upng->buffer; +} + +unsigned upng_get_size(const upng_t* upng) +{ + return upng->size; +}