/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id$id $
 *
 * Copyright (C) 2009 by Christophe Gouiran <bechris13250 -at- gmail -dot- com>
 *
 * 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 <lib/pluginlib_bmp.h>
#include "tinf.h"
#include "bmp.h"
#include "png_decoder.h"
#if LCD_DEPTH < 8
#include <lib/grey.h>
#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)<<i;

    return result;
}

static void setBitOfReversedStream0(size_t* bitpointer, 
                                    unsigned char* bitstream,
                                    unsigned char bit)
{
    /* the current bit in bitstream must be 0 for this to work
     * earlier bit of huffman code is in a lesser significant bit
     * of an earlier byte
     */
    if (bit)
        bitstream[(*bitpointer) >> 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
}

static 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);
#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;

    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;
        }
        /*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*/
            }
        }
        /*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)
{
    if (decoder->error >= PNG_ERROR_MIN && decoder->error <= PNG_ERROR_MAX)
        return png_error_messages[decoder->error-PNG_ERROR_MIN];
    else
        return NULL;
}
