From e26d0c62e07d1b1c1087a065242ed7ab1a2c522f Mon Sep 17 00:00:00 2001 From: Marcin Bukat Date: Sun, 31 Oct 2010 12:40:49 +0000 Subject: Fix and extend imageviewer png support. FS#11641 by me git-svn-id: svn://svn.rockbox.org/rockbox/trunk@28413 a1c6a512-1295-4272-9138-f99709370657 --- apps/plugins/imageviewer/png/png_decoder.c | 2195 ++++++++++++++++++++++++++++ 1 file changed, 2195 insertions(+) create mode 100644 apps/plugins/imageviewer/png/png_decoder.c (limited to 'apps/plugins/imageviewer/png/png_decoder.c') diff --git a/apps/plugins/imageviewer/png/png_decoder.c b/apps/plugins/imageviewer/png/png_decoder.c new file mode 100644 index 0000000000..b09d2e2ece --- /dev/null +++ b/apps/plugins/imageviewer/png/png_decoder.c @@ -0,0 +1,2195 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$id $ + * + * Copyright (C) 2009 by Christophe Gouiran + * + * Based on lodepng, a lightweight png decoder/encoder + * (c) 2005-2008 Lode Vandevenne + * + * Copyright (c) 2010 Marcin Bukat + * - pixel format conversion & transparency handling + * - adaptation of tinf (tiny inflate library) + * - code refactoring & cleanups + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +/* +LodePNG version 20080927 + +Copyright (c) 2005-2008 Lode Vandevenne + +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. +*/ + +/* +The manual and changelog can be found in the header file "lodepng.h" +You are free to name this file lodepng.cpp or lodepng.c depending on your usage. +*/ + +/* supported chunk types: + * critical: + * IHDR + * PLTE + * IDAT + * IEND + * + * ancillary: + * tRNS + * bKGD + */ + +#include "plugin.h" +#include "lcd.h" +#include +#include "tinf.h" +#include "bmp.h" +#include "png_decoder.h" +#if LCD_DEPTH < 8 +#include +#endif + +#ifndef resize_bitmap +#if defined(HAVE_LCD_COLOR) +#define resize_bitmap smooth_resize_bitmap +#else +#define resize_bitmap grey_resize_bitmap +#endif +#endif + +static const char *png_error_messages[PNG_ERROR_MAX-PNG_ERROR_MIN+1] = +{ + "png file smaller than a png header", /*27*/ + "incorrect png signature", /*28*/ + "first chunk is not IHDR", /*29*/ + "chunk length too large", /*30*/ + "illegal PNG color type or bpp", /*31*/ + "illegal PNG compression method", /*32*/ + "illegal PNG filter method", /*33*/ + "illegal PNG interlace method", /*34*/ + "chunk length of a chunk is too large or the chunk too small", /*35*/ + "illegal PNG filter type encountered", /*36*/ + "illegal bit depth for this color type given", /*37*/ + "the palette is too big (more than 256 colors)", /*38*/ + "more palette alpha values given in tRNS, than there are colors in the palette", /*39*/ + "tRNS chunk has wrong size for greyscale image", /*40*/ + "tRNS chunk has wrong size for RGB image", /*41*/ + "tRNS chunk appeared while it was not allowed for this color type", /*42*/ + "bKGD chunk has wrong size for palette image", /*43*/ + "bKGD chunk has wrong size for greyscale image", /*44*/ + "bKGD chunk has wrong size for RGB image", /*45*/ + "value encountered in indexed image is larger than the palette size", /*46*/ + "value encountered in indexed image is larger than the palette size", /*47*/ + "input file is empty", /*48*/ + NULL, /*49*/ + NULL, /*50*/ + NULL, /*51*/ + NULL, /*52*/ + NULL, /*53*/ + NULL, /*54*/ + NULL, /*55*/ + NULL, /*56*/ + "invalid CRC", /*57*/ + NULL, /*58*/ + "conversion to unexisting or unsupported color type or bit depth", /*59*/ + NULL, /*60*/ + NULL, /*61*/ + NULL, /*62*/ + "png chunk too long", /*63*/ + NULL, /*64*/ + NULL, /*65*/ + NULL, /*66*/ + NULL, /*67*/ + NULL, /*68*/ + "unknown critical chunk", /*69*/ + NULL, /*70*/ + NULL, /*71*/ + NULL, /*72*/ + "invalid tIME chunk size", /*73*/ + "invalid pHYs chunk size", /*74*/ +}; + +/* +The two functions below (LodePNG_decompress and LodePNG_compress) directly call the +LodeZlib_decompress and LodeZlib_compress functions. The only purpose of the functions +below, is to provide the ability to let LodePNG use a different Zlib encoder by only +changing the two functions below, instead of changing it inside the vareous places +in the other LodePNG functions. + +*out must be NULL and *outsize must be 0 initially, and after the function is done, +*out must point to the decompressed data, *outsize must be the size of it, and must +be the size of the useful data in bytes, not the alloc size. +*/ + +static unsigned LodePNG_decompress(unsigned char* out, + size_t* outsize, + const unsigned char* in, + size_t insize) +{ + int err; + err = tinf_zlib_uncompress((void *)out, + (unsigned int*)outsize, + (const void*)in, + (unsigned int)insize); + return err; +} + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Reading and writing single bits and bytes from/to stream for LodePNG / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +static unsigned char readBitFromReversedStream(size_t* bitpointer, + const unsigned char* bitstream) +{ + unsigned char result = (unsigned char)((bitstream[(*bitpointer) >> 3] >> + (7 - ((*bitpointer) & 0x7))) & 1); + (*bitpointer)++; + return result; +} + +static unsigned readBitsFromReversedStream(size_t* bitpointer, + const unsigned char* bitstream, + size_t nbits) +{ + unsigned result = 0; + size_t i; + for (i = nbits - 1; i < nbits; i--) + result += (unsigned)readBitFromReversedStream(bitpointer, bitstream)<> 3] |= (bit << (7 - ((*bitpointer) & 0x7))); + + (*bitpointer)++; +} + +static void setBitOfReversedStream(size_t* bitpointer, + unsigned char* bitstream, + unsigned char bit) +{ + /* the current bit in bitstream may be 0 or 1 for this to work */ + if (bit == 0) + bitstream[(*bitpointer) >> 3] &= + (unsigned char)(~(1 << (7 - ((*bitpointer) & 0x7)))); + else + bitstream[(*bitpointer) >> 3] |= (1 << (7 - ((*bitpointer) & 0x7))); + + (*bitpointer)++; +} + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / PNG chunks / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/* get the length of the data of the chunk. + * Total chunk length has 12 bytes more. + */ +static unsigned LodePNG_chunk_length(const uint8_t* chunk) +{ + return chunk[0]<<24|chunk[1]<<16|chunk[2]<<8|chunk[3]; +} + +/* check if the type is the given type */ +static bool LodePNG_chunk_type_equals(const uint8_t* chunk, uint32_t type) +{ + /* chunk type field: A 4-byte chunk type code. For convenience in + * description and in examining PNG files, type codes are restricted + * to consist of uppercase and lowercase ASCII letters + * (A-Z and a-z, or 65-90 and 97-122 decimal). However, encoders and + * decoders must treat the codes as fixed binary values, not character + * strings." + */ + return ((uint32_t)(chunk[4]<<24|chunk[5]<<16|chunk[6]<<8|chunk[7]) == (uint32_t)type); +} + +/* properties of PNG chunks gotten from capitalization of chunk type name, + * as defined by the standard + * 0: ancillary chunk + * 1: critical chunk type + */ +static inline bool LodePNG_chunk_critical(const uint8_t* chunk) +{ + return((chunk[4] & 32) == 0); +} + +/* 0: public, 1: private */ +static inline bool LodePNG_chunk_private(const uint8_t* chunk) +{ + return((chunk[6] & 32) != 0); +} + +/* get pointer to the data of the chunk */ +static inline const uint8_t* LodePNG_chunk_data(const uint8_t* chunk) +{ + return &chunk[8]; +} + +/* returns 0 if the crc is correct, error code if it's incorrect */ +static bool LodePNG_chunk_check_crc(const uint8_t* chunk) +{ + uint32_t length = LodePNG_chunk_length(chunk); + uint32_t crc = chunk[length + 8]<<24|chunk[length + 8 + 1]<<16| + chunk[length + 8 + 2]<<8|chunk[length + 8 + 3]; + + /* the CRC is taken of the data and the 4 chunk type letters, + * not the length + */ + uint32_t checksum = tinf_crc32(chunk + 4, length + 4); + return (crc == checksum); +} + +/* don't use on IEND chunk, as there is no next chunk then */ +static const uint8_t* LodePNG_chunk_next(const uint8_t* chunk) +{ + uint32_t total_chunk_length = LodePNG_chunk_length(chunk) + 12; + return &chunk[total_chunk_length]; +} + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Color types and such / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/* return type is a LodePNG error code + * bd - bit depth + */ +static uint8_t checkColorValidity(uint8_t colorType, uint8_t bd) +{ + switch (colorType) + { + case PNG_COLORTYPE_GREY: + if (!(bd == 1 || bd == 2 || + bd == 4 || bd == 8 || + bd == 16)) + return 37; + break; /*grey*/ + + case PNG_COLORTYPE_RGB: + if (!(bd == 8 || bd == 16)) + return 37; + break; /*RGB*/ + + case PNG_COLORTYPE_PALETTE: + if (!(bd == 1 || bd == 2 || + bd == 4 || bd == 8 )) + return 37; + break; /*palette*/ + + case PNG_COLORTYPE_GREYA: + if (!( bd == 8 || bd == 16 )) + return 37; + break; /*grey + alpha*/ + + case PNG_COLORTYPE_RGBA: + if (!( bd == 8 || bd == 16 )) + return 37; + break; /*RGBA*/ + + default: + return 31; + } + return 0; /*allowed color type / bits combination*/ +} + +static uint8_t getNumColorChannels(uint8_t colorType) +{ + switch (colorType) + { + case PNG_COLORTYPE_GREY: + return 1; /*grey*/ + case PNG_COLORTYPE_RGB: + return 3; /*RGB*/ + case PNG_COLORTYPE_PALETTE: + return 1; /*palette*/ + case PNG_COLORTYPE_GREYA: + return 2; /*grey + alpha*/ + case PNG_COLORTYPE_RGBA: + return 4; /*RGBA*/ + } + return 0; /*unexisting color type*/ +} + +static uint8_t getBpp(uint8_t colorType, uint8_t bitDepth) +{ + /* bits per pixel is amount of channels * bits per channel */ + return getNumColorChannels(colorType) * bitDepth; +} + +static void LodePNG_InfoColor_init(LodePNG_InfoColor* info) +{ + info->key_defined = 0; + info->key_r = info->key_g = info->key_b = 0; + info->colorType = PNG_COLORTYPE_RGBA; + info->bitDepth = 8; + memset(info->palette, 0, 256 * 4 * sizeof(unsigned char)); + info->palettesize = 0; +} + +static void LodePNG_InfoColor_cleanup(LodePNG_InfoColor* info) +{ + info->palettesize = 0; +} + +static void LodePNG_InfoPng_init(LodePNG_InfoPng* info) +{ + info->width = info->height = 0; + LodePNG_InfoColor_init(&info->color); + info->interlaceMethod = 0; + info->compressionMethod = 0; + info->filterMethod = 0; +#ifdef HAVE_LCD_COLOR + info->background_r = info->background_g = info->background_b = 0; +#else + info->background_r = info->background_g = info->background_b = 255; +#endif +} + +void LodePNG_InfoPng_cleanup(LodePNG_InfoPng* info) +{ + LodePNG_InfoColor_cleanup(&info->color); +} + +/* + * Convert from every colortype to rockbox native pixel format (color targets) or + * greylib pixel format (grey targets) + */ +static void LodePNG_convert(LodePNG_Decoder *decoder) +{ + + LodePNG_InfoPng *infoIn = &decoder->infoPng; + const uint8_t *in = decoder->decoded_img; + uint8_t *out = decoder->buf; + uint16_t w = infoIn->width; + uint16_t h = infoIn->height; + size_t i, j, bp = 0; /*bitpointer, used by less-than-8-bit color types*/ + size_t x, y; + uint16_t value, alpha, alpha_complement; + + /* line buffer for pixel format transformation */ +#ifdef HAVE_LCD_COLOR + struct uint8_rgb *line_buf = (struct uint8_rgb *)(out + w * h * FB_DATA_SZ); +#else + uint8_t *line_buf = (unsigned char *)(out + w * h * FB_DATA_SZ); +#endif + + struct bitmap bm = { + .width = w, + .height = h, + .data = (unsigned char*)out, + }; + + struct scaler_context ctx = { + .bm = &bm, + .dither = 0, + }; + +#if LCD_DEPTH < 8 + const struct custom_format *cformat = &format_grey; +#else + const struct custom_format *cformat = &format_native; +#endif + +void (*output_row_8)(uint32_t, void*, struct scaler_context*) = cformat->output_row_8; + +#ifdef HAVE_LCD_COLOR +struct uint8_rgb *pixel; +#else +unsigned char *pixel; +#endif + +#ifdef HAVE_LCD_COLOR + if (infoIn->color.bitDepth == 8) + { + switch (infoIn->color.colorType) + { + case PNG_COLORTYPE_GREY: /*greyscale color*/ + i = 0; + for (y = 0 ; y < h ; y++) { + /* reset line buf */ + pixel = line_buf; + + for (x = 0; x < w ; x++) { + value = in[i++]; + if (infoIn->color.key_defined) + if ( (uint8_t)value == (uint8_t)infoIn->color.key_r ) + value = infoIn->background_r; /* full transparent */ + + pixel->red = (uint8_t)value; + pixel->green = (uint8_t)value; + pixel->blue = (uint8_t)value; + pixel++; + } + output_row_8(y,(void *)line_buf,&ctx); + } + break; + case PNG_COLORTYPE_RGB: /*RGB color*/ + i = 0; + for (y = 0 ; y < h ; y++) { + pixel = line_buf; + for (x = 0 ; x < w ; x++) { + j = 3*i++; + + /* tRNs & bKGD */ + if (infoIn->color.key_defined && + in[j] == (uint8_t)infoIn->color.key_r && + in[j + 1] == (uint8_t)infoIn->color.key_g && + in[j + 2] == (uint8_t)infoIn->color.key_b) + { + pixel->red = (uint8_t)infoIn->background_r; + pixel->green = (uint8_t)infoIn->background_g; + pixel->blue = (uint8_t)infoIn->background_b; + } + else + { + pixel->red = in[j]; + pixel->green = in[j + 1]; + pixel->blue = in[j + 2]; + } + pixel++; + } + output_row_8(y,(void *)line_buf,&ctx); + } + break; + case PNG_COLORTYPE_PALETTE: /*indexed color (palette)*/ + i = 0; + for (y = 0 ; y < h ; y++) { + /* reset line buf */ + pixel = line_buf; + for (x = 0 ; x < w ; x++) { + if (in[i] >= infoIn->color.palettesize) + { + decoder->error = 46; + return; + } + + j = in[i++]<<2; + alpha = infoIn->color.palette[j + 3]; + alpha_complement = (256 - alpha); + + /* tRNS and bKGD */ + pixel->red = (infoIn->color.palette[j] * alpha + + alpha_complement*infoIn->background_r)>>8; + pixel->green = (infoIn->color.palette[j + 1] * alpha + + alpha_complement*infoIn->background_g)>>8; + pixel->blue = (infoIn->color.palette[j + 2] * alpha + + alpha_complement*infoIn->background_b)>>8; + pixel++; + } + output_row_8(y,(void *)(line_buf),&ctx); + } + break; + case PNG_COLORTYPE_GREYA: /*greyscale with alpha*/ + i = 0; + for (y = 0 ; y < h ; y++) { + pixel = line_buf; + for (x = 0 ; x < w ; x++) { + alpha = in[(i << 1) + 1]; + alpha_complement = (256 - alpha)*infoIn->background_r; + value = (alpha * in[i++ << 1] + alpha_complement)>>8; + pixel->red = (uint8_t)(value); + pixel->green = (uint8_t)value; + pixel->blue = (uint8_t)value; + pixel++; + } + output_row_8(y,(void *)line_buf,&ctx); + } + break; + case PNG_COLORTYPE_RGBA: /*RGB with alpha*/ + i = 0; + for (y = 0 ; y < h ; y++) { + pixel = line_buf; + for (x = 0 ; x < w ; x++) { + j = i++ << 2; + alpha = in[j + 3]; + alpha_complement = (256 - alpha); + pixel->red = (in[j] * alpha + + alpha_complement*infoIn->background_r)>>8; + pixel->green = (in[j + 1] * alpha + + alpha_complement*infoIn->background_g)>>8; + pixel->blue = (in[j + 2] * alpha + + alpha_complement*infoIn->background_b)>>8; + pixel++; + } + output_row_8(y,(void *)line_buf,&ctx); + } + break; + default: + break; + } + } + else if (infoIn->color.bitDepth == 16) + { + switch (infoIn->color.colorType) + { + case PNG_COLORTYPE_GREY: /*greyscale color*/ + i = 0; + for (y = 0 ; y < h ; y++) { + pixel = line_buf; + for (x = 0 ; x < w ; x++) { + value = (in[i<<1]<<8)|in[(i << 1) + 1]; + i++; + + /* tRNS and bKGD */ + if (infoIn->color.key_defined && + value == infoIn->color.key_r) + value = infoIn->background_r<<8; + + pixel->red = + pixel->green = + pixel->blue = (uint8_t)(value>>8); + pixel++; + } + output_row_8(y,(void *)line_buf,&ctx); + } + break; + case PNG_COLORTYPE_RGB: /*RGB color*/ + i = 0; + for (y = 0 ; y < h ; y++) { + pixel = line_buf; + for (x = 0 ; x < w ; x++) { + j = 6 * i++; + + /* tRNS and bKGD */ + if (infoIn->color.key_defined && + ((uint16_t)(in[j]<<8|in[j + 1]) == + infoIn->color.key_r) && + ((uint16_t)(in[j + 2]<<8|in[j + 3]) == + infoIn->color.key_g) && + ((uint16_t)(in[j + 4]<<8|in[j + 5]) == + infoIn->color.key_b)) + { + pixel->red = (uint8_t)infoIn->background_r; + pixel->green = (uint8_t)infoIn->background_g; + pixel->blue = (uint8_t)infoIn->background_b; + } + else + { + pixel->red = in[j]; + pixel->green = in[j + 2]; + pixel->blue = in[j + 4]; + } + pixel++; + } + output_row_8(y,(void *)line_buf,&ctx); + } + break; + case PNG_COLORTYPE_GREYA: /*greyscale with alpha*/ + i = 0; + for (y = 0 ; y < h ; y++) { + pixel = line_buf; + for (x = 0 ; x < w ; x++) { + alpha = in[(i << 2) + 2]; + alpha_complement = (256-alpha)*infoIn->background_r; + value = (in[i++ << 2] * alpha + alpha_complement)>>8; + pixel->red = (uint8_t)value; + pixel->green = (uint8_t)value; + pixel->blue = (uint8_t)value; + pixel++; + } + output_row_8(y,(void *)line_buf,&ctx); + } + break; + case PNG_COLORTYPE_RGBA: /*RGB with alpha*/ + i = 0; + for (y = 0 ; y < h ; y++) { + pixel = line_buf; + for (x = 0 ; x < w ; x++) { + j = i++ << 3; + alpha = in[j + 6]; + alpha_complement = (256-alpha); + pixel->red = (in[j] * alpha + + alpha_complement*infoIn->background_r)>>8; + pixel->green = (in[j + 2] * alpha + + alpha_complement*infoIn->background_g)>>8; + pixel->blue = (in[j + 4] * alpha + + alpha_complement*infoIn->background_b)>>8; + pixel++; + } + output_row_8(y,(void *)line_buf,&ctx); + } + break; + default: + break; + } + } + else /*infoIn->bitDepth is less than 8 bit per channel*/ + { + switch (infoIn->color.colorType) + { + case PNG_COLORTYPE_GREY: /*greyscale color*/ + i = 0; + for (y = 0 ; y < h ; y++) { + pixel = line_buf; + for (x = 0 ; x < w ; x++) { + value = readBitsFromReversedStream(&bp, in, infoIn->color.bitDepth); + + /* tRNS and bKGD */ + if (infoIn->color.key_defined) + if ( value == infoIn->color.key_r ) + value = infoIn->background_r; /* full transparent */ + + /* scale value from 0 to 255 */ + value = (value * 255) / ((1 << infoIn->color.bitDepth) - 1); + + pixel->red = (uint8_t)value; + pixel->green = (uint8_t)value; + pixel->blue = (uint8_t)value; + pixel++; + } + output_row_8(y,(void *)line_buf,&ctx); + } + break; + case PNG_COLORTYPE_PALETTE: /*indexed color (palette)*/ + i = 0; + for (y = 0 ; y < h ; y++) { + pixel = line_buf; + for (x = 0 ; x < w ; x++) { + value = readBitsFromReversedStream(&bp, in, infoIn->color.bitDepth); + if (value >= infoIn->color.palettesize) + { + decoder->error = 47; + return; + } + + j = value << 2; + + /* tRNS and bKGD */ + alpha = infoIn->color.palette[j + 3]; + alpha_complement = (256 - alpha); + pixel->red = (alpha * infoIn->color.palette[j] + + alpha_complement*infoIn->background_r)>>8; + pixel->green = (alpha * infoIn->color.palette[j + 1] + + alpha_complement*infoIn->background_g)>>8; + pixel->blue = (alpha * infoIn->color.palette[j + 2] + + alpha_complement*infoIn->background_b)>>8; + pixel++; + } + output_row_8(y,(void *)line_buf,&ctx); + } + break; + default: + break; + } + } +#else /* greyscale targets */ +struct uint8_rgb px_rgb; /* for rgb(a) -> greyscale conversion */ +uint8_t background_grey; /* for rgb background -> greyscale background */ + + if (infoIn->color.bitDepth == 8) + { + switch (infoIn->color.colorType) + { + case PNG_COLORTYPE_GREY: /*greyscale color*/ + i = 0; + for (y = 0 ; y < h ; y++) { + pixel = line_buf; + for (x = 0 ; x < w ; x++ ) { + value = in[i++]; + + /* transparent color defined in tRNS chunk */ + if (infoIn->color.key_defined) + if ( (uint8_t)value == (uint8_t)infoIn->color.key_r ) + value = infoIn->background_r; + + *pixel++ = (uint8_t)value; + } + output_row_8(y,(void *)line_buf,&ctx); + } + break; + case PNG_COLORTYPE_RGB: /*RGB color*/ + /* convert background rgb color to greyscale */ + px_rgb.red = infoIn->background_r; + px_rgb.green = infoIn->background_g; + px_rgb.blue = infoIn->background_b; + background_grey = brightness(px_rgb); + + i = 0; + for (y = 0 ; y < h ; y++) { + pixel = line_buf; + for (x = 0 ; x < w ; x++) { + j = 3*i++; + + /* tRNs & bKGD */ + if (infoIn->color.key_defined && + in[j] == (uint8_t)infoIn->color.key_r && + in[j + 1] == (uint8_t)infoIn->color.key_g && + in[j + 2] == (uint8_t)infoIn->color.key_b) + { + *pixel = background_grey; + } + else + { + /* rgb -> greyscale */ + px_rgb.red = in[j]; + px_rgb.green = in[j + 1]; + px_rgb.blue = in[j + 2]; + *pixel = brightness(px_rgb); + } + pixel++; + + } + output_row_8(y,(void *)line_buf,&ctx); + } + break; + case PNG_COLORTYPE_PALETTE: /*indexed color (palette)*/ + i = 0; + /* calculate grey value of rgb background */ + px_rgb.red = infoIn->background_r; + px_rgb.green = infoIn->background_g; + px_rgb.blue = infoIn->background_b; + background_grey = brightness(px_rgb); + + for (y = 0 ; y < h ; y++) { + /* reset line buf */ + pixel = line_buf; + for (x = 0 ; x < w ; x++) { + if (in[i] >= infoIn->color.palettesize) + { + decoder->error = 46; + return; + } + + j = in[i++] << 2; + alpha = infoIn->color.palette[j + 3]; + alpha_complement = (256 - alpha); + + /* tRNS and bKGD */ + px_rgb.red = (alpha * infoIn->color.palette[j] + + alpha_complement*background_grey)>>8; + px_rgb.green = (alpha * infoIn->color.palette[j + 1] + + alpha_complement*background_grey)>>8; + px_rgb.blue = (alpha * infoIn->color.palette[j + 2] + + alpha_complement*background_grey)>>8; + + *pixel++ = brightness(px_rgb); + } + output_row_8(y,(void *)(line_buf),&ctx); + } + break; + case PNG_COLORTYPE_GREYA: /*greyscale with alpha*/ + i = 0; + for (y = 0 ; y < h ; y++) { + pixel = line_buf; + for (x = 0 ; x < w ; x++) { + alpha = in[(i << 1) + 1]; + alpha_complement = ((256 - alpha)*infoIn->background_r); + *pixel++ = (alpha * in[i++ << 1] + alpha_complement)>>8; + } + output_row_8(y,(void *)line_buf,&ctx); + } + break; + case PNG_COLORTYPE_RGBA: /*RGB with alpha*/ + px_rgb.red = infoIn->background_r; + px_rgb.green = infoIn->background_g; + px_rgb.blue = infoIn->background_b; + background_grey = brightness(px_rgb); + + i = 0; + for (y = 0 ; y < h ; y++) { + pixel = line_buf; + for (x = 0 ; x < w ; x++) { + j = i++ << 2; + alpha = in[j + 3]; + alpha_complement = ((256 - alpha)*background_grey); + + px_rgb.red = in[j]; + px_rgb.green = in[j + 1]; + px_rgb.blue = in[j + 2]; + *pixel++ = (alpha * brightness(px_rgb) + + alpha_complement)>>8; + } + output_row_8(y,(void *)line_buf,&ctx); + } + break; + default: + break; + } + } + else if (infoIn->color.bitDepth == 16) + { + switch (infoIn->color.colorType) + { + case PNG_COLORTYPE_GREY: /*greyscale color*/ + i = 0; + for (y = 0 ; y < h ; y++) { + pixel = line_buf; + for (x = 0 ; x < w ; x++) { + /* specification states that we have to compare + * colors for simple transparency in 16bits + * even if we scale down to 8bits later + */ + value = in[i<<1]<<8|in[(i << 1) + 1]; + i++; + + /* tRNS and bKGD */ + if (infoIn->color.key_defined && + value == infoIn->color.key_r) + value = infoIn->background_r<<8; + + /* we take upper 8bits */ + *pixel++ = (uint8_t)(value>>8); + } + + output_row_8(y,(void *)line_buf,&ctx); + } + break; + case PNG_COLORTYPE_RGB: /*RGB color*/ + i = 0; + px_rgb.red = infoIn->background_r; + px_rgb.green = infoIn->background_g; + px_rgb.blue = infoIn->background_b; + background_grey = brightness(px_rgb); + + for (y = 0 ; y < h ; y++) { + pixel = line_buf; + for (x = 0 ; x < w ; x++) { + j = 6 * i++; + + /* tRNS and bKGD */ + if (infoIn->color.key_defined && + (uint16_t)(in[j]<<8|in[j + 1]) == + infoIn->color.key_r && + (uint16_t)(in[j + 2]<<8|in[j + 3]) == + infoIn->color.key_g && + (uint16_t)(in[j + 4]<<8|in[j + 5]) == + infoIn->color.key_b) + { + *pixel = background_grey; + } + else + { + /* we take only upper byte of 16bit value */ + px_rgb.red = in[j]; + px_rgb.green = in[j + 2]; + px_rgb.blue = in[j + 4]; + *pixel = brightness(px_rgb); + } + pixel++; + } + output_row_8(y,(void *)line_buf,&ctx); + } + break; + case PNG_COLORTYPE_GREYA: /*greyscale with alpha*/ + i = 0; + for (y = 0 ; y < h ; y++) { + pixel = line_buf; + for (x = 0 ; x < w ; x++) { + alpha = in[(i << 2) + 2]; + alpha_complement = (256 - alpha)*infoIn->background_r; + *pixel++ = (alpha * in[i++ << 2] + alpha_complement)>>8; + } + output_row_8(y,(void *)line_buf,&ctx); + } + break; + case PNG_COLORTYPE_RGBA: /*RGB with alpha*/ + px_rgb.red = infoIn->background_r; + px_rgb.green = infoIn->background_g; + px_rgb.blue = infoIn->background_b; + background_grey = brightness(px_rgb); + + i = 0; + for (y = 0 ; y < h ; y++) { + pixel = line_buf; + for (x = 0 ; x < w ; x++) { + j = i++ << 3; + alpha = in[j + 6]; + alpha_complement = (256 - alpha)*background_grey; + px_rgb.red = in[j]; + px_rgb.green = in[j + 2]; + px_rgb.blue = in[j + 4]; + *pixel++ = (alpha * brightness(px_rgb) + alpha_complement)>>8; + } + output_row_8(y,(void *)line_buf,&ctx); + } + break; + default: + break; + } + } + else /*infoIn->bitDepth is less than 8 bit per channel*/ + { + switch (infoIn->color.colorType) + { + case PNG_COLORTYPE_GREY: /*greyscale color*/ + i = 0; + for (y = 0 ; y < h ; y++) { + pixel = line_buf; + for (x = 0 ; x < w ; x++) { + value = readBitsFromReversedStream(&bp, in, infoIn->color.bitDepth); + + /* tRNS and bKGD */ + if (infoIn->color.key_defined) + if ( value == infoIn->color.key_r ) + value = infoIn->background_r; /* full transparent */ + + /*scale value from 0 to 255*/ + value = (value * 255) / ((1 << infoIn->color.bitDepth) - 1); + + *pixel++ = (unsigned char)value; + } + output_row_8(y,(void *)line_buf,&ctx); + } + break; + case PNG_COLORTYPE_PALETTE: /*indexed color (palette)*/ + i = 0; + px_rgb.red = infoIn->background_r; + px_rgb.green = infoIn->background_g; + px_rgb.blue = infoIn->background_b; + uint8_t background_grey = brightness(px_rgb); + + for (y = 0 ; y < h ; y++) { + pixel = line_buf; + for (x = 0 ; x < w ; x++) { + value = readBitsFromReversedStream(&bp, in, infoIn->color.bitDepth); + if (value >= infoIn->color.palettesize) + { + decoder->error = 47; + return; + } + + j = value << 2; + + /* tRNS and bKGD */ + alpha = infoIn->color.palette[j + 3]; + alpha_complement = (256 - alpha) * background_grey; + + px_rgb.red = (alpha * infoIn->color.palette[j] + + alpha_complement)>>8; + px_rgb.green = (alpha * infoIn->color.palette[j + 1] + + alpha_complement)>>8; + px_rgb.blue = (alpha * infoIn->color.palette[j + 2] + + alpha_complement)>>8; + *pixel++ = brightness(px_rgb); + } + output_row_8(y,(void *)line_buf,&ctx); + } + break; + default: + break; + } + } +#endif +} + +/*Paeth predicter, used by PNG filter type 4*/ +static int paethPredictor(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; +} + +/*shared values used by multiple Adam7 related functions*/ + +static const uint8_t ADAM7_IX[7] = { 0, 4, 0, 2, 0, 1, 0 }; /*x start values*/ +static const uint8_t ADAM7_IY[7] = { 0, 0, 4, 0, 2, 0, 1 }; /*y start values*/ +static const uint8_t ADAM7_DX[7] = { 8, 8, 4, 4, 2, 2, 1 }; /*x delta values*/ +static const uint8_t ADAM7_DY[7] = { 8, 8, 8, 4, 4, 2, 2 }; /*y delta values*/ + +static void Adam7_getpassvalues(uint16_t passw[7], + uint16_t passh[7], + size_t filter_passstart[8], + size_t padded_passstart[8], + size_t passstart[8], + uint16_t w, + uint16_t h, + uint8_t bpp) +{ + /* the passstart values have 8 values: + * the 8th one actually indicates the byte after the end + * of the 7th (= last) pass + */ + uint8_t i; + + /*calculate width and height in pixels of each pass*/ + for (i = 0; i < 7; i++) + { + passw[i] = (w + ADAM7_DX[i] - ADAM7_IX[i] - 1) / ADAM7_DX[i]; + passh[i] = (h + ADAM7_DY[i] - ADAM7_IY[i] - 1) / ADAM7_DY[i]; + if (passw[i] == 0) passh[i] = 0; + if (passh[i] == 0) passw[i] = 0; + } + + filter_passstart[0] = padded_passstart[0] = passstart[0] = 0; + for (i = 0; i < 7; i++) + { + /* if passw[i] is 0, it's 0 bytes, not 1 (no filtertype-byte) */ + filter_passstart[i + 1] = filter_passstart[i] + ((passw[i] && passh[i])? + passh[i] * (1 + (passw[i] * bpp + 7) / 8):0); + + /* bits padded if needed to fill full byte at end of each scanline */ + padded_passstart[i + 1] = padded_passstart[i] + + passh[i] * ((passw[i] * bpp + 7) / 8); + + /* only padded at end of reduced image */ + passstart[i + 1] = passstart[i] + (passh[i] * passw[i] * bpp + 7) / 8; + } +} + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / PNG Decoder / */ +/* ////////////////////////////////////////////////////////////////////////// */ + + + +static uint8_t unfilterScanline(uint8_t* recon, + const uint8_t* scanline, + const uint8_t* precon, + size_t bytewidth, + uint8_t filterType, + size_t 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. + */ + +/* storage space for cached portion of scanline */ +unsigned char cache[512+16]; + +/* ptr to second element of the cache */ +unsigned char *cache_1 = cache + bytewidth; +unsigned char *p_cache = cache + 256 + 8; /* half way */ +unsigned char *p_cache_1 = p_cache + bytewidth; + + size_t i; + switch (filterType) + { + case PNG_FILTERTYPE_NONE: + /* for(i = 0; i < length; i++) recon[i] = scanline[i]; */ + memcpy(recon, scanline, length * sizeof(uint8_t)); + break; + case PNG_FILTERTYPE_SUB: + /* + for(i = 0; i < bytewidth; i++) recon[i] = scanline[i]; + for (i = bytewidth; i < length; i++) + recon[i] = scanline[i] + recon[i - bytewidth]; + */ + + /* first pixel */ + memcpy(cache, scanline, bytewidth * sizeof(unsigned char)); + scanline += bytewidth; + + while ((length - bytewidth) >> 9) /* length >> 9 */ + { + /* cache part of scanline */ + memcpy(cache_1, scanline, 512); + + /* filtering */ + for (i=bytewidth; i < 512 + bytewidth; i++) + cache[i] += cache[i - bytewidth]; + + /* copy part of filtered scanline */ + memcpy(recon, cache, 512); + + /* adjust pointers */ + recon += 512; + scanline += 512; + length -= 512; + + /* copy last pixel back to the begining of the cache */ + memcpy(cache, cache + 512, bytewidth * sizeof(unsigned char)); + } + + /* less than our cache size */ + if (length) + { + /* cache last part of the scanline */ + memcpy(cache_1, scanline, length - bytewidth); + + /* filtering */ + for (i=bytewidth; i < length; i++) + cache[i] += cache[i - bytewidth]; + + /* copy remaining part of the filtered scanline */ + memcpy(recon, cache, length * sizeof(unsigned char)); + } + break; + case PNG_FILTERTYPE_UP: + /* + if (precon) for (i = 0; i < length; i++) + recon[i] = scanline[i] + precon[i]; + */ + if (precon) + { + while (length >> 8) + { + memcpy(cache, scanline, 256); + memcpy(p_cache, precon, 256); + + for (i=0; i < 256; i++) + cache[i] += p_cache[i]; + + memcpy(recon, cache, 256); + + scanline += 256; + recon += 256; + precon += 256; + length -= 256; + } + + if (length) + { + memcpy(cache, scanline, length); + memcpy(p_cache, precon, length); + + for (i=0; i < length; i++) + cache[i] += p_cache[i]; + + memcpy(recon, cache, length); + } + } + else + /* for(i = 0; i < length; i++) recon[i] = scanline[i]; */ + memcpy(recon, scanline, length * sizeof(uint8_t)); + break; + case PNG_FILTERTYPE_AVERAGE: + 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); + */ + memcpy(cache, scanline, bytewidth * sizeof(unsigned char)); + memcpy(p_cache, precon, bytewidth * sizeof(unsigned char)); + + for (i = 0; i < bytewidth; i++) + cache[i] += p_cache[i]>>1; + + scanline += bytewidth; + precon += bytewidth; + + while ((length - bytewidth)>> 8) /* length/256 */ + { + memcpy(cache_1, scanline, 256); + memcpy(p_cache_1, precon, 256); + + for (i=bytewidth; i < 256 + bytewidth; i++) + cache[i] += (cache[i - bytewidth] + p_cache[i])>>1; + + memcpy(recon, cache, 256); + + recon += 256; + scanline += 256; + precon += 256; + length -= 256; + + memcpy(cache, cache + 256, bytewidth * sizeof(unsigned char)); + memcpy(p_cache, p_cache + 256, bytewidth * sizeof(unsigned char)); + } + + /* less than our cache size */ + if (length) + { + /* cache last part of the scanline */ + memcpy(cache_1, scanline, length - bytewidth); + memcpy(p_cache_1, precon, length - bytewidth); + + /* filtering */ + for (i=bytewidth; i < length; i++) + cache[i] += (cache[i - bytewidth] + p_cache[i])>>1; + + /* copy remaining part of the filtered scanline */ + memcpy(recon, cache, length * sizeof(unsigned char)); + } + } + 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; + */ + + /* first pixel */ + memcpy(cache, scanline, bytewidth * sizeof(unsigned char)); + scanline += bytewidth; + + while ((length - bytewidth) >> 9) /* length/512 */ + { + /* cache part of scanline */ + memcpy(cache_1, scanline, 512); + + /* filtering */ + for (i=bytewidth; i < 512 + bytewidth; i++) + cache[i] += (cache[i - bytewidth])>>1; + + /* copy part of filtered scanline */ + memcpy(recon, cache, 512); + + /* adjust pointers */ + recon += 512; + scanline += 512; + length -= 512; + + /* copy last pixel back to the begining of the cache */ + memcpy(cache, cache + 512, bytewidth * sizeof(unsigned char)); + } + + /* less than our cache size */ + if (length) + { + /* cache last part of the scanline */ + memcpy(cache_1, scanline, length - bytewidth); + + /* filtering */ + for (i=bytewidth; i < length; i++) + cache[i] += (cache[i - bytewidth])>>1; + + /* copy remaining part of the filtered scanline */ + memcpy(recon, cache, length * sizeof(unsigned char)); + } + } + break; + case PNG_FILTERTYPE_PAETH: + if (precon) + { + /* + for (i = 0; i < bytewidth; i++) + recon[i] = (uint8_t)(scanline[i] + + paethPredictor(0, precon[i], 0)); + for (i = bytewidth; i < length; i++) + recon[i] = (uint8_t)(scanline[i] + + paethPredictor(recon[i - bytewidth], + precon[i], + precon[i - bytewidth])); + */ + + memcpy(cache, scanline, bytewidth * sizeof(unsigned char)); + memcpy(p_cache, precon, bytewidth * sizeof(unsigned char)); + + for (i = 0; i < bytewidth; i++) + cache[i] += paethPredictor(0, p_cache[i], 0); + + scanline += bytewidth; + precon += bytewidth; + + while ((length - bytewidth)>> 8) /* length/256 */ + { + memcpy(cache_1, scanline, 256); + memcpy(p_cache_1, precon, 256); + + for (i=bytewidth; i < 256 + bytewidth; i++) + cache[i] += paethPredictor(cache[i - bytewidth], + p_cache[i], + p_cache[i - bytewidth]); + + memcpy(recon, cache, 256); + + recon += 256; + scanline += 256; + precon += 256; + length -= 256; + + memcpy(cache, cache + 256, bytewidth * sizeof(unsigned char)); + memcpy(p_cache, p_cache + 256, bytewidth * sizeof(unsigned char)); + } + + /* less than our cache size */ + if (length) + { + /* cache last part of the scanline */ + memcpy(cache_1, scanline, length - bytewidth); + memcpy(p_cache_1, precon, length - bytewidth); + + /* filtering */ + for (i=bytewidth; i < length; i++) + cache[i] += paethPredictor(cache[i - bytewidth], + p_cache[i], + p_cache[i - bytewidth]); + + /* copy remaining part of the filtered scanline */ + memcpy(recon, cache, length * sizeof(unsigned char)); + } + } + else + { + /* + for(i = 0; i < bytewidth; i++) recon[i] = scanline[i]; + for (i = bytewidth; i < length; i++) + recon[i] = (uint8_t)(scanline[i] + + paethPredictor(recon[i - bytewidth], + 0, 0)); + */ + + memcpy(cache, scanline, bytewidth * sizeof(unsigned char)); + scanline += bytewidth; + + while ((length - bytewidth) >> 9) /* length/512 */ + { + /* cache part of scanline */ + memcpy(cache_1, scanline, 512); + + /* filtering */ + for (i=bytewidth; i < 512 + bytewidth; i++) + cache[i] += paethPredictor(cache[i - bytewidth], 0, 0); + + /* copy part of filtered scanline */ + memcpy(recon, cache, 512); + + /* adjust pointers */ + recon += 512; + scanline += 512; + length -= 512; + + /* copy last pixel back to the begining of the cache */ + memcpy(cache, cache + 512, bytewidth * sizeof(unsigned char)); + } + + /* less than our cache size */ + if (length) + { + /* cache last part of the scanline */ + memcpy(cache_1, scanline, length - bytewidth); + + /* filtering */ + for (i=bytewidth; i < length; i++) + cache[i] += paethPredictor(cache[i - bytewidth], 0, 0); + + /* copy remaining part of the filtered scanline */ + memcpy(recon, cache, length * sizeof(unsigned char)); + } + } + break; + default: + return 36; /*error: unexisting filter type given*/ + } + return 0; +} + +static uint8_t unfilter(uint8_t* out, + const uint8_t* in, + uint16_t w, + uint16_t h, + uint8_t 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 bits per pixel + * + * in and out are allowed to be the same memory address! + */ + + uint16_t y; + uint8_t* prevline = 0; + + /* bytewidth is used for filtering, is 1 when bpp < 8, + * number of bytes per pixel otherwise + */ + size_t bytewidth = (bpp + 7) / 8; + size_t linebytes = (w * bpp + 7) / 8; + + for (y = 0; y < h; y++) + { + size_t outindex = linebytes * y; + + /* the extra filterbyte added to each row */ + size_t inindex = (1 + linebytes) * y; + uint8_t filterType = in[inindex]; + + uint8_t error = unfilterScanline(&out[outindex], &in[inindex + 1], + prevline, bytewidth, filterType, + linebytes); + if (error) + return error; + + prevline = &out[outindex]; + } + + return 0; +} + +static void Adam7_deinterlace(uint8_t* out, + const uint8_t* in, + uint16_t w, + uint16_t h, + uint8_t bpp) +{ + /* Note: this function works on image buffers WITHOUT padding bits at end + * of scanlines with non-multiple-of-8 bit amounts, only between reduced + * images is padding + * out must be big enough AND must be 0 everywhere if bpp < 8 + * in the current implementation (because that's likely a little bit faster) + */ + uint16_t passw[7], passh[7]; + size_t filter_passstart[8], padded_passstart[8], passstart[8]; + uint8_t i; + + Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, + passstart, w, h, bpp); + + if (bpp >= 8) + { + for (i = 0; i < 7; i++) + { + uint16_t x, y, b; + size_t bytewidth = bpp >> 3; + for (y = 0; y < passh[i]; y++) + for (x = 0; x < passw[i]; x++) + { + size_t pixelinstart = passstart[i] + + (y * passw[i] + x) * bytewidth; + size_t pixeloutstart = ((ADAM7_IY[i] + y * ADAM7_DY[i]) * w + + ADAM7_IX[i] + x * ADAM7_DX[i]) * bytewidth; + for (b = 0; b < bytewidth; b++) + { + out[pixeloutstart + b] = in[pixelinstart + b]; + } + } + } + } + else /*bpp < 8: Adam7 with pixels < 8 bit is a bit trickier: with bit pointers*/ + { + for (i = 0; i < 7; i++) + { + uint16_t x, y, b; + uint32_t ilinebits = bpp * passw[i]; + uint32_t olinebits = bpp * w; + size_t obp, ibp; /*bit pointers (for out and in buffer)*/ + for (y = 0; y < passh[i]; y++) + for (x = 0; x < passw[i]; x++) + { + ibp = (8 * passstart[i]) + (y * ilinebits + x * bpp); + obp = (ADAM7_IY[i] + y * ADAM7_DY[i]) * olinebits + + (ADAM7_IX[i] + x * ADAM7_DX[i]) * bpp; + for (b = 0; b < bpp; b++) + { + uint8_t bit = readBitFromReversedStream(&ibp, in); + /* note that this function assumes the out buffer + * is completely 0, use setBitOfReversedStream + * otherwise*/ + setBitOfReversedStream0(&obp, out, bit); + } + } + } + } +} + +static void removePaddingBits(uint8_t* out, + const uint8_t* in, + size_t olinebits, + size_t ilinebits, + uint16_t h) +{ + /* After filtering there are still padding bits 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 bits, + * out must have >= olinebits*h bits, olinebits must be <= ilinebits + * also used to move bits 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 + */ + uint16_t y; + size_t diff = ilinebits - olinebits; + size_t obp = 0, ibp = 0; /*bit pointers*/ + for (y = 0; y < h; y++) + { + size_t x; + for (x = 0; x < olinebits; x++) + { + uint8_t bit = readBitFromReversedStream(&ibp, in); + setBitOfReversedStream(&obp, out, bit); + } + 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 uint8_t postProcessScanlines(uint8_t* out, + uint8_t* in, + const LodePNG_Decoder* decoder) +{ + /*return value is error*/ + + /* This function converts the filtered-padded-interlaced data into pure 2D + * image buffer with the PNG's colortype. + * Steps: + * I) if no Adam7: + * 1) unfilter + * 2) remove padding bits (= posible extra bits per scanline if bpp < 8) + * II) if adam7: + * 1) 7x unfilter + * 2) 7x remove padding bits + * 3) Adam7_deinterlace + * + * NOTE: the in buffer will be overwritten with intermediate data! + */ + uint8_t bpp = getBpp(decoder->infoPng.color.colorType, + decoder->infoPng.color.bitDepth); + uint16_t w = decoder->infoPng.width; + uint16_t h = decoder->infoPng.height; + uint8_t error = 0; + + if (bpp == 0) + return 31; /*error: invalid colortype*/ + + if (decoder->infoPng.interlaceMethod == 0) + { + if (bpp < 8 && w * bpp != ((w * bpp + 7) / 8) * 8) + { + error = unfilter(in, in, w, h, bpp); + if (error) return error; + removePaddingBits(out, in, w * bpp, ((w * bpp + 7) / 8) * 8, h); + } + else + /* we can immediatly filter into the out buffer, + * no other steps needed + */ + error = unfilter(out, in, w, h, bpp); + } + else /*interlaceMethod is 1 (Adam7)*/ + { + uint16_t passw[7], passh[7]; + size_t filter_passstart[8], padded_passstart[8], passstart[8]; + uint8_t i; + + Adam7_getpassvalues(passw, + passh, + filter_passstart, + padded_passstart, + passstart, + w, + h, + bpp); + + for (i = 0; i < 7; i++) + { + error = unfilter(&in[padded_passstart[i]], + &in[filter_passstart[i]], + passw[i], + passh[i], + bpp); + if (error) + return error; + if (bpp < 8) + /* TODO: possible efficiency improvement: if in this reduced + * image the bits fit nicely in 1 scanline, move bytes instead + * of bits or move not at all + */ + { + /* remove padding bits in scanlines; after this there still + * may be padding bits between the different reduced images: + * each reduced image still starts nicely at a byte + */ + removePaddingBits(&in[passstart[i]], + &in[padded_passstart[i]], + passw[i] * bpp, + ((passw[i] * bpp + 7) / 8) * 8, + passh[i]); + } + } + + Adam7_deinterlace(out, in, w, h, bpp); + } + + return error; +} + +/* read a PNG, the result will be in the same color type as the PNG + * (hence "generic") + */ +static void decodeGeneric(LodePNG_Decoder* decoder) +{ + uint8_t *in = decoder->file; + + uint8_t IEND = 0; + const uint8_t* chunk; + size_t i; + + size_t chunkLength; /* chunk length */ + const uint8_t* data; /*the data in the chunk*/ + + uint8_t *idat = decoder->buf; /* allocated buffer */ + + size_t idat_size = 0; + + signed long free_mem = decoder->buf_size; + + /* for unknown chunk order */ + bool unknown = false; + uint8_t critical_pos = 1; /*1 = after IHDR, 2 = after PLTE, 3 = after IDAT*/ + + if (decoder->file_size == 0 || in == NULL) + { + /* the given data is empty */ + decoder->error = 48; + return; + } + + chunk = in + 33; /*first byte of the first chunk after the header*/ + + /* loop through the chunks, ignoring unknown chunks and stopping at IEND + * chunk. IDAT data is put at the start of the in buffer + */ + while (!IEND) { + + /* minimal size of chunk is 12 bytes */ + if ((size_t)((chunk - in) + 12) > decoder->file_size || chunk < in) + { + /* error: size of the in buffer too small to contain next chunk */ + decoder->error = 30; + break; + } + + /* length of the data of the chunk, excluding the length bytes, + * chunk type and CRC bytes + * + * data field of the chunk is restricted to 2^31-1 bytes in size + */ + chunkLength = LodePNG_chunk_length(chunk); + + if (chunkLength > 2147483647) + { + decoder->error = 63; + break; + } + + /* check if chunk fits in buffer */ + if ((size_t)((chunk - in) + chunkLength + 12) > decoder->file_size || + (chunk + chunkLength + 12) < in) + { + /* error: size of the in buffer too small to contain next chunk */ + decoder->error = 35; + break; + } + data = LodePNG_chunk_data(chunk); + + /* IDAT chunk, containing compressed image data + * there may be more than 1 IDAT chunk, complete + * compressed stream is concatenation of consecutive + * chunks data + */ + if (LodePNG_chunk_type_equals(chunk, PNG_CHUNK_IDAT)) + { + free_mem -= chunkLength; + + if (free_mem < 0) + { + decoder->error = OUT_OF_MEMORY; + break; + } + /* copy compressed data */ + memcpy(idat+idat_size, data, chunkLength * sizeof(uint8_t)); + idat_size += chunkLength; + critical_pos = 3; + } + /*IEND chunk*/ + else if (LodePNG_chunk_type_equals(chunk, PNG_CHUNK_IEND)) + { + IEND = 1; + } + /*palette chunk (PLTE)*/ + else if (LodePNG_chunk_type_equals(chunk, PNG_CHUNK_PLTE)) + { + uint32_t pos = 0; + decoder->infoPng.color.palettesize = chunkLength / 3; + if (decoder->infoPng.color.palettesize > 256) + { + /*error: palette too big*/ + decoder->error = 38; + break; + } + + for (i = 0; i < decoder->infoPng.color.palettesize; i++) + { + decoder->infoPng.color.palette[(i<<2)] = data[pos++]; /*R*/ + decoder->infoPng.color.palette[(i<<2) | 1] = data[pos++]; /*G*/ + decoder->infoPng.color.palette[(i<<2) | 2] = data[pos++]; /*B*/ + decoder->infoPng.color.palette[(i<<2) | 3] = 255; /*alpha*/ + } + critical_pos = 2; + } + /*palette transparency chunk (tRNS)*/ + else if (LodePNG_chunk_type_equals(chunk, PNG_CHUNK_tRNS)) + { + if (decoder->infoPng.color.colorType == PNG_COLORTYPE_PALETTE) + { + if (chunkLength > decoder->infoPng.color.palettesize) + { + /* error: more alpha values given than there are palette + * entries + */ + decoder->error = 39; + break; + } + for (i = 0; i < chunkLength; i++) + /* copy alpha informations for palette colors */ + decoder->infoPng.color.palette[(i<<2) | 3] = data[i]; + } + else if (decoder->infoPng.color.colorType == PNG_COLORTYPE_GREY) + { + if (chunkLength != 2) + { + /* error: this chunk must be 2 bytes for greyscale image */ + decoder->error = 40; + break; + } + /* transparent color definition */ + decoder->infoPng.color.key_defined = 1; + decoder->infoPng.color.key_r = + decoder->infoPng.color.key_g = + decoder->infoPng.color.key_b = data[0]<<8|data[1]; + } + else if (decoder->infoPng.color.colorType == PNG_COLORTYPE_RGB) + { + if (chunkLength != 6) + { + /* error: this chunk must be 6 bytes for RGB image */ + decoder->error = 41; + break; + } + /* transparent color definition */ + decoder->infoPng.color.key_defined = 1; + decoder->infoPng.color.key_r = data[0]<<8|data[1]; + decoder->infoPng.color.key_g = data[2]<<8|data[3]; + decoder->infoPng.color.key_b = data[4]<<8|data[5]; + } + else + { + /* error: tRNS chunk not allowed for other color models */ + decoder->error = 42; + break; + } + } + /*background color chunk (bKGD)*/ + else if (LodePNG_chunk_type_equals(chunk, PNG_CHUNK_bKGD)) + { + if (decoder->infoPng.color.colorType == PNG_COLORTYPE_PALETTE) + { + if (chunkLength != 1) + { + /* error: this chunk must be 1 byte for indexed color image */ + decoder->error = 43; + break; + } + decoder->infoPng.background_r = + decoder->infoPng.color.palette[(data[0]<<2)]; + + decoder->infoPng.background_g = + decoder->infoPng.color.palette[(data[0]<<2) | 1]; + + decoder->infoPng.background_b = + decoder->infoPng.color.palette[(data[0]<<2) | 2]; + + } + else if (decoder->infoPng.color.colorType == PNG_COLORTYPE_GREY || + decoder->infoPng.color.colorType == PNG_COLORTYPE_GREYA) + { + if (chunkLength != 2) + { + /* error: this chunk must be 2 bytes for greyscale image */ + decoder->error = 44; + break; + } + decoder->infoPng.background_r = + decoder->infoPng.background_g = + decoder->infoPng.background_b = data[0]; + } + else if (decoder->infoPng.color.colorType == PNG_COLORTYPE_RGB || + decoder->infoPng.color.colorType == PNG_COLORTYPE_RGBA) + { + if (chunkLength != 6) + { + /* error: this chunk must be 6 bytes for greyscale image */ + decoder->error = 45; + break; + } + decoder->infoPng.background_r = data[0]; + decoder->infoPng.background_g = data[2]; + decoder->infoPng.background_b = data[4]; + } + } + else + { + /* it's not an implemented chunk type, + * so ignore it (unless it is critical) + * skip over the data + */ + if (LodePNG_chunk_critical(chunk)) + { + /* error: unknown critical chunk + * (5th bit of first byte of chunk type is 0) + */ + decoder->error = 69; + break; + } + unknown = true; + } + + if (!unknown) /*check CRC if wanted, only on known chunk types*/ + { + if (!LodePNG_chunk_check_crc(chunk)) + { + decoder->error = 57; + break; + } + } + + if (!IEND) + chunk = LodePNG_chunk_next(chunk); + } + + if (!decoder->error) + { + /* ptr to buffer just after concatenated IDATs */ + uint8_t *scanlines = idat + idat_size; + size_t scanline_size = free_mem; + + /* decompress with the Zlib decompressor + * decompressor updates scanlines_size to actual size + * of decompressed data + */ + decoder->error = LodePNG_decompress(scanlines, + &scanline_size, + idat, + idat_size); + + free_mem -= scanline_size; + /* possible memory saving (at cost of memcpy) + * memcpy(decoder->buf - scanlines_size, + * scanlines, + * scanlines_size * sizeof(uint8_t)); + * this will free compressed IDATs and + * will trash raw PNG file (it is trashed anyway + */ + if (!decoder->error) + { + /* size of decoded image in bytes rounded up */ + size_t decoded_img_size = (decoder->infoPng.height * + decoder->infoPng.width * + getBpp(decoder->infoPng.color.colorType, + decoder->infoPng.color.bitDepth) + + 7) / 8; + + /* at this time buffer contains: + * compressed IDATs + * decompressed IDATs + * png raw file at the end of the buffer (not needed any more ) + */ + free_mem -= decoded_img_size; + + if (free_mem < 0) + { + decoder->error = OUT_OF_MEMORY; + return; + } + + /* ptr to decoded png image + * this will overwrite raw png file loaded into memory + * decoded image is put in the end of allocated buffer + */ + decoder->decoded_img = decoder->buf + + decoder->buf_size - + decoded_img_size; + + /* clear memory as filters assume 0'ed memory */ + memset(decoder->decoded_img,0,decoded_img_size*sizeof(uint8_t)); + + decoder->error = postProcessScanlines(decoder->decoded_img, + scanlines, + decoder); + } + } +} + +/* Public functions */ + +/* read the information from the header and store it in the decoder + * context struct + * value is error + */ +void LodePNG_inspect(LodePNG_Decoder* decoder, uint8_t *in, size_t inlength) +{ + uint32_t header_crc, checksum; + if (inlength == 0 || in == NULL) + { + /* the given data is empty */ + decoder->error = 48; + return; + } + + if (inlength < 29) + { + /*error: the data length is smaller than the length of the header*/ + decoder->error = 27; + return; + } + + /* when decoding a new PNG image, make sure all parameters created after + * previous decoding are reset + */ + LodePNG_InfoPng_cleanup(&decoder->infoPng); + LodePNG_InfoPng_init(&decoder->infoPng); + decoder->error = 0; + + decoder->file = in; + decoder->file_size = inlength; + + if (in[0] != 137 || in[1] != 80 || in[2] != 78 || in[3] != 71 || + in[4] != 13 || in[5] != 10 || in[6] != 26 || in[7] != 10) + { + /* error: the first 8 bytes are not the correct PNG signature */ + decoder->error = 28; + return; + } + + if (in[12] != 'I' || in[13] != 'H' || in[14] != 'D' || in[15] != 'R') + { + /* error: it doesn't start with a IHDR chunk! */ + decoder->error = 29; + return; + } + + /* read the values given in the header */ + decoder->infoPng.width = in[16]<<24|in[17]<<16|in[18]<<8|in[19]; + decoder->infoPng.height = in[20]<<24|in[21]<<16|in[22]<<8|in[23]; + decoder->infoPng.color.bitDepth = in[24]; + decoder->infoPng.color.colorType = in[25]; + decoder->infoPng.compressionMethod = in[26]; + decoder->infoPng.filterMethod = in[27]; + decoder->infoPng.interlaceMethod = in[28]; + + /* get the value from the chunk's crc field */ + header_crc = in[29]<<24|in[30]<<16|in[31]<<8|in[32]; + + /* calculate crc of the header chunk */ + checksum = tinf_crc32(in + 12, 17); + + if (header_crc != checksum) + { + decoder->error = 57; + return; + } + + if (decoder->infoPng.compressionMethod != 0) + { + /* error: only compression method 0 is allowed in the specification */ + decoder->error = 32; + return; + } + + if (decoder->infoPng.filterMethod != 0) + { + /* error: only filter method 0 is allowed in the specification */ + decoder->error = 33; + return; + } + + if (decoder->infoPng.interlaceMethod > 1) + { + /* error: only interlace methods 0 and 1 exist in the specification */ + decoder->error = 34; + return; + } + + /* check validity of colortype and bitdepth combination */ + decoder->error = checkColorValidity(decoder->infoPng.color.colorType, + decoder->infoPng.color.bitDepth); +} + +void LodePNG_decode(LodePNG_Decoder* decoder, + uint8_t* in, + size_t insize, + void (*pf_progress)(int current, int total)) +{ + size_t line_buf_size; + /* parse header */ + LodePNG_inspect(decoder, in, insize); + + + /* Check memory available against worst case where + * we have to have decoded PNG image + * and converted to the native pixel format image + * in buffer at the same time (do we realy need that much?) + */ + + size_t decoded_img_size = (decoder->infoPng.height * + decoder->infoPng.width * + getBpp(decoder->infoPng.color.colorType, + decoder->infoPng.color.bitDepth) + + 7) / 8; + + /* one line more as temp buffer for conversion */ +#ifdef HAVE_LCD_COLOR + decoder->native_img_size = decoder->infoPng.width * + (decoder->infoPng.height)*FB_DATA_SZ; + line_buf_size = decoder->infoPng.width * sizeof(struct uint8_rgb); +#else + decoder->native_img_size = decoder->infoPng.width * + decoder->infoPng.height; + line_buf_size = decoder->infoPng.width; +#endif + + if (decoded_img_size + decoder->native_img_size + line_buf_size + > decoder->buf_size) + { + decoder->error = OUT_OF_MEMORY; + return; + } + + if (pf_progress != NULL) + pf_progress(0, 100); + + long time = *rb->current_tick; + /* put decoded png data (pure 2D array of pixels in format + * defined by PNG header at the end of the allocated buffer + */ + decodeGeneric(decoder); + if (decoder->error) return; + + if (pf_progress != NULL) + pf_progress(50, 100); + + /* convert decoded png data into native rockbox + * pixel format (native LCD data for color + * or greylib pixel format for greyscale) + * + * converted image will be put at the begining + * of the allocated buffer + */ + LodePNG_convert(decoder); + + /* correct aspect ratio */ +#if (LCD_PIXEL_ASPECT_HEIGHT != 1 || LCD_PIXEL_ASPECT_WIDTH != 1) + struct bitmap img_src, img_dst; /* scaler vars */ + struct dim dim_src, dim_dst; /* recalc_dimensions vars */ + unsigned int c_native_img_size; /* size of the image after correction */ + + dim_src.width = decoder->infoPng.width; + dim_src.height = decoder->infoPng.height; + + dim_dst.width = decoder->infoPng.width; + dim_dst.height = decoder->infoPng.height; + + /* defined in apps/recorder/resize.c */ + if (!recalc_dimension(&dim_dst, &dim_src)) + { + /* calculate 'corrected' image size */ +#ifdef HAVE_LCD_COLOR + c_native_img_size = dim_dst.width * + (dim_dst.height)*FB_DATA_SZ; +#else + c_native_img_size = dim_dst.width * + dim_dst.height; +#endif + /* check memory constraints + * do the correction only if there is enough + * free memory + */ + if ( decoder->native_img_size + c_native_img_size <= + decoder->buf_size ) + { + img_src.width = dim_src.width; + img_src.height = dim_src.height; + img_src.data = (unsigned char *)decoder->buf; + + img_dst.width = dim_dst.width; + img_dst.height = dim_dst.height; + img_dst.data = (unsigned char *)decoder->buf + + decoder->native_img_size; + + /* scale the bitmap to correct physical + * pixel dimentions + */ + resize_bitmap(&img_src, &img_dst); + + /* update decoder struct */ + decoder->infoPng.width = img_dst.width; + decoder->infoPng.height = img_dst.height; + decoder->native_img_size = c_native_img_size; + + /* copy back corrected image to the begining of the buffer */ + memcpy(img_src.data, img_dst.data, decoder->native_img_size); + } + } + +#endif /* (LCD_PIXEL_ASPECT_HEIGHT != 1 || LCD_PIXEL_ASPECT_WIDTH != 1) */ +time = *rb->current_tick - time; +if (pf_progress) pf_progress(100, 100); +} + +void LodePNG_Decoder_init(LodePNG_Decoder* decoder, + uint8_t *buf, + size_t buf_size) +{ + LodePNG_InfoPng_init(&decoder->infoPng); + decoder->error = 0; + decoder->buf = buf; + decoder->buf_size = buf_size; + decoder->decoded_img = NULL; + decoder->file = NULL; + decoder->file_size = 0; +} + +const char* LodePNG_perror(LodePNG_Decoder *decoder) +{ + return png_error_messages[decoder->error-PNG_ERROR_MIN]; +} -- cgit v1.2.3