/*****************************************************************************/
/* Copyright (c) 2008 NXP B.V.                                               */
/*                                                                           */
/* 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, using version 2 of the License.             */
/*                                                                           */
/* This program is distributed in the hope that it will be useful,           */
/* but WITHOUT ANY WARRANTY; without even the implied warranty of            */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the              */
/* GNU General Public License for more details.                              */
/*                                                                           */
/* You should have received a copy of the GNU General Public License         */
/* along with this program; if not, write to the Free Software               */
/* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307       */
/* USA.                                                                      */
/*                                                                           */
/*****************************************************************************/
/**
    @file   phStbPng.c
    @brief  phStbPng component

@b  Application:  phStbPng

*//*

Rev Date        Author      Comments
--------------------------------------------------------------------------------
  1 20060329    neill       Original
  2 20060405    neill       Update include file name
  3 20061117    neill       Make library re-entrant
  4 20070320    neill       Changes after QAC checking
  5 20071023    neill       More QAC checking
  7 20080208    batelaan    Update file copyright/license text
  8 20081219    neill       Add support for multiple graphics modes
  9 20090116    neill       Add support for YUV format output
--------------------------------------------------------------------------------
    For consistency and standardisation retain the Section Separators.
*/

/***********************************************
* INCLUDE FILES                                *
************************************************/

#include <stdlib.h>
#include <string.h>
#include <fcntl.h>

#include "phStbImage.h"
#include "phStbImageUtils.h"
#include "phStbPng.h"
#include "png.h"

/***********************************************
* LOCAL MACROS                                 *
************************************************/

#define PH_STB_PNG_INPUT_BUFFER_SIZE     (16*1024)

#define PNG_BYTES_TO_CHECK          (4)

#define ARGB8888_BYTES_PER_PIXEL    (4)

#define BITMAP_GREY_TRANS_BYTES     (2)
#define BITMAP_1COLOUR24_BYTES   (4)
#define BITMAP_ARGB_BYTES           (4)

#define NO_ALPHA                    (0xFF)

#define PH_STB_PNG_CHECK(a) \
{ \
    phStbImageErrorCode_t err; \
    err = (a); \
    if (err != PH_STB_OK) \
    { \
        return err; \
    } \
}

#define PH_STB_PNG_CHECK_LABEL(a, b) \
{ \
    status = (a); \
    if (status != PH_STB_OK) \
    { \
        DBG_PRINT((dbgPng,DBG_DEFAULT,"%s : Error at line %d\n", __FUNCTION__, __LINE__)); \
        goto b; \
    } \
}

/******************************************************************
* LOCAL TYPEDEFS                                                  *
*******************************************************************/

typedef enum {
    GFX_BITMAP_DATATYPE_1COLOUR,
    GFX_BITMAP_DATATYPE_1COLOUR24,
    GFX_BITMAP_DATATYPE_PALETTE,
    GFX_BITMAP_DATATYPE_GREY
} phStbBitmapDataType_t;


typedef struct _phStbPng_Input_t
{
    int32_t   file;
    uint8_t   inputBuffer[PH_STB_PNG_INPUT_BUFFER_SIZE];
    uint32_t  noOfBytesLeft;    /* No of bytes left in the Buffer, ie. packet */
    uint8_t * pCurrentByte;     /* pointer to the current byte accessed from the buffer, ie. packet*/
}phStbPng_Input_t, * pphStbPng_Input_t;

typedef struct
{
    phStbPng_Input_t pPngInput;
}phStbPng_contextProperties_t;

/******************************************************************
* STATIC FUNCTION PROTOTYPES                  <Module>_<Word>+    *
*******************************************************************/

/******************************************************************
* STATIC DATA                  g[k|p|kp|pk|kpk]<Module>_<Word>+   *
*******************************************************************/

/*********************************************************(((((((**********
* EXPORTED DATA      g[k|p|kp|pk|kpk]ph[<lnx|tm|NONE>]StbTemplate_<Word>+ *
***************************************************************************/

/*******************************************************************************
* FUNCTION IMPLEMENTATION  <Module>[_<Word>+] for static functions             *
*                          tm[<layer>]<Module>[_<Word>+] for exported functions*
********************************************************************************/

/*******************************************************************************
*@begin
* NAME: phStbPng_FillBuffer
*       Local function used to refill the input buffer
*       
* Returns : PH_STB_OK if successful - else error
* 
* Parameter     Flow    Description
* ------------------------------------------------------------------------------
* pContext      IN      Pointer to context information
* 
* Externals     Flow    Usage
* ------------------------------------------------------------------------------
* 
* Additional information:
*
*@end
*******************************************************************************/

static phStbImageErrorCode_t phStbPng_FillBuffer(phStbPng_contextProperties_t* pContext)
{
    uint32_t bytesToRead;
    int32_t bytesRead;
    bytesToRead                = PH_STB_PNG_INPUT_BUFFER_SIZE;
    pContext->pPngInput.noOfBytesLeft = 0;
    pContext->pPngInput.pCurrentByte  = pContext->pPngInput.inputBuffer;

    do
    {
        bytesRead = read(pContext->pPngInput.file, &pContext->pPngInput.inputBuffer[pContext->pPngInput.noOfBytesLeft], bytesToRead);

        if (bytesRead < 0) 
        {
            return PH_STB_ERR_FILE_TRUNCATED;
        }

        pContext->pPngInput.noOfBytesLeft += (uint32_t)bytesRead;

        if (bytesRead == 0) 
        {
            if (pContext->pPngInput.noOfBytesLeft!=0) 
            {
                return PH_STB_OK;
            }
            return PH_STB_ERR_FILE_TRUNCATED;
        }

        bytesToRead -= (uint32_t)bytesRead;

    }while(bytesToRead);

    return PH_STB_OK;
}

static inline uint8_t* phStbPng_SetSrcRowPtr(uint8_t               *pDataBuffer, 
                                             int32_t               imageWidth,
                                             int32_t               srcRow,
                                             int32_t               transparentPixels, 
                                             phStbBitmapDataType_t dataType)
{
    uint8_t* retValue;
    switch(dataType)
    {
        case(GFX_BITMAP_DATATYPE_GREY):
           if (transparentPixels)
           {
              retValue = (uint8_t*)pDataBuffer + (imageWidth*BITMAP_GREY_TRANS_BYTES*srcRow);
           }
           else
           {
              retValue = (uint8_t*)pDataBuffer + (imageWidth*srcRow);
           }
           break;
        case(GFX_BITMAP_DATATYPE_PALETTE):
           retValue = (uint8_t*)pDataBuffer + (imageWidth*srcRow);
           break;
        case(GFX_BITMAP_DATATYPE_1COLOUR24):
           retValue = (uint8_t*)pDataBuffer + (imageWidth*BITMAP_1COLOUR24_BYTES*srcRow);
           break;
        default:
           retValue = (uint8_t*)pDataBuffer + (imageWidth*BITMAP_ARGB_BYTES*srcRow);
           break;
    }

    return retValue;
}

static inline uint32_t phStbPng_pixelWidth(phStbImage_GfxMode_t mode)
{
    uint32_t retValue;
    switch(mode)
    {
        case(phStbImage_GfxMode_ARGB8888) : 
            retValue = sizeof(uint32_t);
            break;
        case(phStbImage_GfxMode_RGB565) :
        case(phStbImage_GfxMode_ARGB1555) :
        case(phStbImage_GfxMode_ARGB4444) :
            retValue = sizeof(uint16_t);
            break;
        default :
            retValue = sizeof(uint8_t);
            break;
    }

    return retValue;
}

static inline void phStbPng_addPixel(uint32_t   pixel,
                                     uint8_t  **pDestPtr,
                                     uint8_t  **pDestUPtr,
                                     uint8_t  **pDestVPtr,
                                     phStbImage_GfxMode_t mode,
                                     phStbImage_cmapFunction cmapFunction)
{
    if (mode == phStbImage_GfxMode_YUV)
    {
        uint8_t U;
        uint8_t V;

        phStbImage_yuvConvertPixel(pixel, *pDestPtr, &U, &V);

        if (*pDestUPtr && *pDestVPtr)
        {
            if (pDestUPtr == pDestVPtr)
            {
                **pDestUPtr = U;
                *pDestUPtr += 1;
            }
            else
            {
                **pDestVPtr = V;
                *pDestVPtr += 1;
            }
        }
        *pDestPtr += 1;
    }
    else
    {
        pixel = phStbImage_convertPixel(pixel, mode, cmapFunction);

        switch(mode)
        {
            case(phStbImage_GfxMode_ARGB8888) : 
                *((uint32_t*)*pDestPtr) = pixel;
                break;
            case(phStbImage_GfxMode_RGB565) :
            case(phStbImage_GfxMode_ARGB1555) :
            case(phStbImage_GfxMode_ARGB4444) :
                *((uint16_t*)*pDestPtr) = (uint16_t)pixel;
                break;
            default :
                **pDestPtr = (uint8_t)pixel;
                break;
        }
        *pDestPtr += phStbPng_pixelWidth(mode);
    }
}

/*******************************************************************************
*@begin
* NAME: phStbPng_BitmapScale
*       Local function used to scale a PNG image into a 32 bit bitmap
*       
* Returns : void
* 
* Parameter         Flow    Description
* ------------------------------------------------------------------------------
* pDataBuffer       IN      Decoded PNG pixel data
* pPaletteBuffer    IN      Decoded PNG palette data
* imageWidth        IN      Width of the decoded PNG image
* imageHeight       IN      Height of the decoded PNG image
* pInBuffer         OUT     Storage location for scaled 32 bit image
* bufferWidth       IN      Width of the 32 bit image buffer
* bufferHeight      IN      Height of the 32 bit image buffer
* transparentPixels IN      Flag indicating if the PNG image contains transparent pixels
* dataType          IN      Type of decoded PNG data
* 
* Externals     Flow    Usage
* ------------------------------------------------------------------------------
* 
* Additional information:
*
*@end
*******************************************************************************/
static void phStbPng_BitmapScale( uint8_t        *pDataBuffer, 
                                  uint8_t const  *pPaletteBuffer, 
                                  int32_t         imageWidth,
                                  int32_t         imageHeight,
                                  uint8_t        *pInBuffer,
                                  uint8_t        *pInBuffer2,
                                  int32_t         bufferWidth,
                                  int32_t         bufferHeight,
                                  int32_t         outOffsetX, 
                                  int32_t         outOffsetY, 
                                  int32_t         outWidth, 
                                  int32_t         outHeight,
                                  int32_t         transparentPixels, 
                                  phStbBitmapDataType_t dataType,
                                  phStbImage_GfxMode_t mode,
                                  phStbImage_cmapFunction cmapFunction)
{
   uint8_t *srcPtr;
   uint8_t *srcRowPtr;
   uint8_t *destPtr;
   uint8_t *destRowPtr;
   uint8_t *destUPtr = NULL;
   uint8_t *destVPtr = NULL;
   int32_t row;
   int32_t col;
   int32_t destWidth;
   int32_t destHeight;
   uint32_t pixelWidth;
   float heightScale;
   float widthScale;

   /* Calculate the scaling required to fit the image into the given buffer */
   heightScale = (float)outHeight / (float)imageHeight;
   widthScale = (float)outWidth / (float)imageWidth;
   destWidth = (int32_t)((float)imageWidth * widthScale);
   destHeight = (int32_t)((float)imageHeight * heightScale);

   /* Clip destWidth to bufferWidth */
   if ((outOffsetX+destWidth) > bufferWidth)
   {
      destWidth = bufferWidth-outOffsetX;
   }
   /* Clip destHeight to bufferHeight */
   if ((outOffsetY+destHeight) > bufferHeight)
   {
      destHeight = bufferHeight-outOffsetY;
   }
   DBG_PRINT((dbgPng,DBG_DEFAULT,"Scaling from %d,%d to %d,%d at %d,%d\n", imageWidth, imageHeight, destWidth, destHeight, outOffsetX, outOffsetY));

   /* Set up the pointer to the read location in the bitmap */
   srcRowPtr = pDataBuffer;

   pixelWidth = phStbPng_pixelWidth(mode);

   /* Set up the pointer to the write location in the bitmap */
   destRowPtr  = pInBuffer + (((bufferWidth*outOffsetY) + outOffsetX) * pixelWidth);

   for(row=0; row<destHeight; row++)
   {
       int32_t srcRow;
       /* Work out the source rows that contribute to the destination row pixel */
       srcRow = (int32_t)(((float)row) / heightScale);
       CLAMP_VALUE(srcRow, (imageHeight-1))
       srcRowPtr = phStbPng_SetSrcRowPtr(pDataBuffer, imageWidth, srcRow, transparentPixels, dataType);

       destPtr = destRowPtr;

       if (mode == phStbImage_GfxMode_YUV)
       {
           if (row%2 == 0)
           {
               destUPtr = destVPtr = pInBuffer2 + (((bufferWidth*(outOffsetY/2)) + outOffsetX) * pixelWidth);
           }
           else
           {
               destUPtr = destVPtr = NULL;
           }
       }
       for(col=0; col<destWidth; col++)
       {
           int32_t srcCol;
           uint32_t pixel;
           srcCol = (imageWidth*col) / destWidth;
           CLAMP_VALUE(srcCol, (imageWidth-1))
           switch(dataType)
           {
               case(GFX_BITMAP_DATATYPE_GREY):
                  if (transparentPixels)
                  {
                     srcPtr = srcRowPtr + (srcCol*BITMAP_GREY_TRANS_BYTES);
                     pixel = RGBA8888( *srcPtr, *srcPtr, *srcPtr, (uint32_t)(*(srcPtr+1)));
                  }
                  else
                  {
                     srcPtr = srcRowPtr + srcCol;
                     pixel = RGBA8888( *srcPtr, *srcPtr, *srcPtr, NO_ALPHA);
                  }
                  break;
               case(GFX_BITMAP_DATATYPE_PALETTE):
                  srcPtr = srcRowPtr + srcCol;
                  pixel = ((uint32_t*)pPaletteBuffer)[*srcPtr];
                  break;
               case(GFX_BITMAP_DATATYPE_1COLOUR24):
                  srcPtr = srcRowPtr + (srcCol*BITMAP_1COLOUR24_BYTES);
                  pixel = RGBA8888(srcPtr[0], srcPtr[1], srcPtr[2], NO_ALPHA);
                  break;
               default:
                  srcPtr = srcRowPtr + (srcCol*BITMAP_ARGB_BYTES);
                  pixel = RGBA8888(srcPtr[0], srcPtr[1], srcPtr[2], srcPtr[3]);
                  break;
           }

           phStbPng_addPixel(pixel,
                             &destPtr,
                             &destUPtr,
                             &destVPtr,
                             mode,
                             cmapFunction);
       }
       destRowPtr += bufferWidth * pixelWidth;
       outOffsetY++;
   }
}

/*******************************************************************************
*@begin
* NAME: phStbPng_BitmapCopy
*       Local function used to copy a PNG image into a 32 bit bitmap
*       
* Returns : void
* 
* Parameter     Flow    Description
* ------------------------------------------------------------------------------
* pDataBuffer       IN      Decoded PNG pixel data
* pPaletteBuffer    IN      Decoded PNG palette data
* imageWidth        IN      Width of the decoded PNG image
* imageHeight       IN      Height of the decoded PNG image
* pInBuffer           OUT     Storage location for scaled 32 bit image
* bufferWidth       IN      Width of the 32 bit image buffer
* bufferHeight      IN      Height of the 32 bit image buffer
* transparentPixels IN      Flag indicating if the PNG image contains transparent pixels
* dataType          IN      Type of decoded PNG data
* 
* Externals     Flow    Usage
* ------------------------------------------------------------------------------
* 
* Additional information:
*
*@end
*******************************************************************************/

static void phStbPng_BitmapCopy( uint8_t        *pDataBuffer, 
                                 uint8_t const  *pPaletteBuffer, 
                                 int32_t         imageWidth,
                                 int32_t         imageHeight,
                                 uint8_t        *pInBuffer,
                                 uint8_t        *pInBuffer2,
                                 int32_t         bufferWidth,
                                 int32_t         bufferHeight,
                                 int32_t         outOffsetX, 
                                 int32_t         outOffsetY, 
                                 int32_t         outWidth, 
                                 int32_t         outHeight,
                                 int32_t         transparentPixels, 
                                 phStbBitmapDataType_t dataType,
                                 phStbImage_GfxMode_t mode,
                                 phStbImage_cmapFunction cmapFunction)
{
   uint8_t *srcPtr;
   uint8_t *srcRowPtr;
   uint8_t *destPtr;
   uint8_t *destRowPtr;
   uint8_t *destUPtr = NULL;
   uint8_t *destVPtr = NULL;
   int32_t row;
   int32_t col;
   int32_t min_width;
   int32_t min_height;
   uint32_t pixelWidth;

   /* Clip outWidth to buffer */
   if ((outOffsetX+outWidth) > bufferWidth)
   {
      outWidth = bufferWidth-outOffsetX;
   }
   /* Clip outHeight to buffer */
   if ((outOffsetY+outHeight) > bufferHeight)
   {
      outHeight = bufferHeight-outOffsetY;
   }
   /* Work out the smaller area that needs to be written to */
   min_width = (outWidth < bufferWidth) ? outWidth : bufferWidth;
   min_width = (min_width<imageWidth) ? min_width : imageWidth;
   min_height = (outHeight < bufferHeight) ? outHeight : bufferHeight;
   min_height = (min_height<imageHeight) ? min_height : imageHeight;
   DBG_PRINT((dbgPng,DBG_DEFAULT,"Copying %d,%d to %d,%d\n", min_width, min_height, outOffsetX, outOffsetY));

   pixelWidth = phStbPng_pixelWidth(mode);

   /* Set up the pointer to the write location in the bitmap */
   destRowPtr = pInBuffer + (((bufferWidth*outOffsetY) + outOffsetX) * pixelWidth);
   /* Set up the pointer to the read location in the bitmap */
   srcRowPtr = pDataBuffer;

   if (dataType == GFX_BITMAP_DATATYPE_GREY)
   {
      /* Data in grey scale format */
      /* Does the image contain transparent pixels? */
      if (transparentPixels)
      {
         /* Transparent pixels - but no blending. */
         row = min_height;
         /* Go through each row to be written */
         while(row)
         {
            destPtr = destRowPtr;
            if (mode == phStbImage_GfxMode_YUV)
            {
                if (row%2 == 0)
                {
                    destUPtr = destVPtr = pInBuffer2 + (((bufferWidth*(outOffsetY/2)) + outOffsetX) * pixelWidth);
                }
                else
                {
                    destUPtr = destVPtr = NULL;
                }
            }

            srcPtr = srcRowPtr;
            col = min_width;

            /* Go through each pixel to be written */
            while(col)
            {
               uint32_t Value;
               Value = (uint32_t) *srcPtr;
               /* Write the bitmap value to the destination */
               phStbPng_addPixel(RGBA8888( Value, Value, Value, (uint32_t)(*(srcPtr+1))),
                                 &destPtr,
                                 &destUPtr,
                                 &destVPtr,
                                 mode,
                                 cmapFunction);
               srcPtr += BITMAP_GREY_TRANS_BYTES;
               col--;
            }
            destRowPtr += bufferWidth * pixelWidth;
            outOffsetY++;
            srcRowPtr += imageWidth*BITMAP_GREY_TRANS_BYTES;
            row--;
         }
      }
      else
      {
         /* No transparent pixels - therefore no blending. */
         row = min_height;
         /* Go through each row to be written */
         while(row)
         {
            destPtr = destRowPtr;
            if (mode == phStbImage_GfxMode_YUV)
            {
                if (row%2 == 0)
                {
                    destUPtr = destVPtr = pInBuffer2 + (((bufferWidth*(outOffsetY/2)) + outOffsetX) * pixelWidth);
                }
                else
                {
                    destUPtr = destVPtr = NULL;
                }
            }
            srcPtr = srcRowPtr;
            col = min_width;
            /* Go through each pixel to be written */
            while(col)
            {
               uint32_t Value;
               Value = (uint32_t) *srcPtr;
               /* Write the bitmap value to the destination */
               phStbPng_addPixel(RGBA8888( Value, Value, Value, NO_ALPHA),
                                 &destPtr,
                                 &destUPtr,
                                 &destVPtr,
                                 mode,
                                 cmapFunction);
               srcPtr++;
               col--;
            }
            destRowPtr += bufferWidth * pixelWidth;
            outOffsetY++;
            srcRowPtr += imageWidth;
            row--;
         }
      }
   }
   else if (dataType == GFX_BITMAP_DATATYPE_PALETTE)
   {
      /* Data in 8 bit palette format */
      row = min_height;
      /* Go through each row to be written */
      while(row)
      {
         destPtr = destRowPtr;
         if (mode == phStbImage_GfxMode_YUV)
         {
             if (row%2 == 0)
             {
                 destUPtr = destVPtr = pInBuffer2 + (((bufferWidth*(outOffsetY/2)) + outOffsetX) * pixelWidth);
             }
             else
             {
                 destUPtr = destVPtr = NULL;
             }
         }
         srcPtr = srcRowPtr;
         col = min_width;
         /* Go through each pixel to be written */
         while(col)
         {
             /* Write the bitmap value to the destination */
             phStbPng_addPixel(((uint32_t*)pPaletteBuffer)[*srcPtr],
                               &destPtr,
                               &destUPtr,
                               &destVPtr,
                               mode,
                               cmapFunction);
             srcPtr++;
             col--;
         }
         destRowPtr += bufferWidth * pixelWidth;
         outOffsetY++;
         srcRowPtr += imageWidth;
         row--;
      }
   }
   else if (dataType == GFX_BITMAP_DATATYPE_1COLOUR24)
   {
      /* Data in RGB format */
      row = min_height;
      /* Go through each row to be written */
      while(row)
      {
         destPtr = destRowPtr;
         if (mode == phStbImage_GfxMode_YUV)
         {
             if (row%2 == 0)
             {
                 destUPtr = destVPtr = pInBuffer2 + (((bufferWidth*(outOffsetY/2)) + outOffsetX) * pixelWidth);
             }
             else
             {
                 destUPtr = destVPtr = NULL;
             }
         }
         srcPtr = srcRowPtr;
         col = min_width;
         /* Go through each pixel to be written */
         while(col)
         {
             /* Write the bitmap value to the destination */
             phStbPng_addPixel(RGBA8888(srcPtr[0], srcPtr[1], srcPtr[2], NO_ALPHA),
                               &destPtr,
                               &destUPtr,
                               &destVPtr,
                               mode,
                               cmapFunction);

             srcPtr += BITMAP_1COLOUR24_BYTES;
             col--;
         }
         destRowPtr += bufferWidth * pixelWidth;
         outOffsetY++;
         srcRowPtr += imageWidth*BITMAP_1COLOUR24_BYTES;
         row--;
      }
   }
   else
   {
      /* Data in RGBA format */
      row = min_height;
      /* Go through each row to be written */
      while(row)
      {
         destPtr = destRowPtr;
         if (mode == phStbImage_GfxMode_YUV)
         {
             if (row%2 == 0)
             {
                 destUPtr = destVPtr = pInBuffer2 + (((bufferWidth*(outOffsetY/2)) + outOffsetX) * pixelWidth);
             }
             else
             {
                 destUPtr = destVPtr = NULL;
             }
         }
         srcPtr = srcRowPtr;
         col = min_width;
         /* Go through each pixel to be written */
         while(col)
         {
            /* Write the bitmap value to the destination */
            phStbPng_addPixel(RGBA8888(srcPtr[0], srcPtr[1], srcPtr[2], srcPtr[3]),
                              &destPtr,
                              &destUPtr,
                              &destVPtr,
                              mode,
                              cmapFunction);

            srcPtr += BITMAP_ARGB_BYTES;
            col--;
         }
         destRowPtr += bufferWidth * pixelWidth;
         outOffsetY++;
         srcRowPtr += imageWidth*BITMAP_ARGB_BYTES;
         row--;
      }
   }
}

/*******************************************************************************
*@begin
* NAME: phStbPng_CheckIfPng
*       
* Returns : 1/1 if data is a PNG file else 0/0
* 
* Parameter     Flow    Description
* ------------------------------------------------------------------------------
* pContext      IN      Pointer to context information
* 
* Externals     Flow    Usage
* ------------------------------------------------------------------------------
* 
* Additional information:
*
*@end
*******************************************************************************/

static int32_t phStbPng_CheckIfPng(phStbPng_contextProperties_t const * pContext)
{
    return(png_check_sig((png_bytep)pContext->pPngInput.pCurrentByte, PNG_BYTES_TO_CHECK));
}

/*******************************************************************************
*@begin
* NAME: phStbPng_readFunc
*       Replacement read function used during the PNG decoding
*       
* Returns : None
* 
* Parameter     Flow    Description
* ------------------------------------------------------------------------------
* 
* Externals     Flow    Usage
* ------------------------------------------------------------------------------
* 
* Additional information:
*
*@end
*******************************************************************************/

static void phStbPng_readFunc(png_structp png_ptr, png_bytep data, png_size_t length)
{
    phStbPng_contextProperties_t* pContext = (phStbPng_contextProperties_t*)png_get_io_ptr(png_ptr);

    if (pContext->pPngInput.noOfBytesLeft>=length)
    {
        /* Enough bytes in buffer to fill PNG buffer */
        (void)memcpy(data, pContext->pPngInput.pCurrentByte, length);
        pContext->pPngInput.pCurrentByte  += length;
        pContext->pPngInput.noOfBytesLeft -= length;
    }
    else
    {
        /* Not enough bytes in buffer to fill PNG buffer */
        (void)memcpy(data, pContext->pPngInput.pCurrentByte, pContext->pPngInput.noOfBytesLeft);
        length -= pContext->pPngInput.noOfBytesLeft;
        data += pContext->pPngInput.noOfBytesLeft;

        while(length > 0)
        {
            if (phStbPng_FillBuffer(pContext)!=PH_STB_OK)
            {
                /* Error while trying to get more data - give up quick! */
                return;
            }
            if (pContext->pPngInput.noOfBytesLeft>=length)
            {
                /* Enough bytes in buffer to fill PNG buffer */
                (void)memcpy(data, pContext->pPngInput.pCurrentByte, length);
                pContext->pPngInput.pCurrentByte  += length;
                pContext->pPngInput.noOfBytesLeft -= length;
                length = 0;
            }
            else
            {
                (void)memcpy(data, pContext->pPngInput.pCurrentByte, pContext->pPngInput.noOfBytesLeft);
                length -= pContext->pPngInput.noOfBytesLeft;
                data += pContext->pPngInput.noOfBytesLeft;
            }
        }
    }
}

/*******************************************************************************
*@begin
* NAME: phStbPng_prepareInput
*       Opens the .bmp image file and fill internal data buffers.
*       
* Returns : PH_STB_OK if successful - else error
* 
* Parameter     Flow    Description
* ------------------------------------------------------------------------------
* pContext      IN      Pointer to context information
* filename      In      Name of input .gif image file
* 
* Externals     Flow    Usage
* ------------------------------------------------------------------------------
* 
* Additional information:
*
*@end
*******************************************************************************/
static phStbImageErrorCode_t phStbPng_prepareInput(phStbPng_contextProperties_t* pContext, const char* filename)
{
    pContext->pPngInput.file = open(filename, O_RDONLY, 0);

    if (pContext->pPngInput.file < 0) 
    {
        return PH_STB_ERR_FILE_NOT_OPEN;
    }
    pContext->pPngInput.pCurrentByte   = NULL;
    pContext->pPngInput.noOfBytesLeft  = 0;

    return phStbPng_FillBuffer(pContext);
}

/*******************************************************************************
*@begin
* NAME: phStbPng_closeInput
*       Closes the .gif image file.
*       
* Returns : void
* 
* Parameter     Flow    Description
* ------------------------------------------------------------------------------
* pContext      IN      Pointer to context information
* 
* Externals     Flow    Usage
* ------------------------------------------------------------------------------
* 
* Additional information:
*
*@end
*******************************************************************************/
static void phStbPng_closeInput(phStbPng_contextProperties_t const * pContext)
{
    if (pContext->pPngInput.file >= 0) 
    {
        (void)close(pContext->pPngInput.file);
    }
}

/*******************************************************************************
*@begin
* NAME: phStbPng_GetDimensions
*       Gets dimensions of a bitmap supplied in PNG format.
*       
* Returns : PH_STB_OK if successful - else error
* 
* Parameter     Flow    Description
* ------------------------------------------------------------------------------
* filename      IN      Image filename
* pWidth        Out     Width of Image
* pHeight       Out     Height of Image
* 
* Externals     Flow    Usage
* ------------------------------------------------------------------------------
* 
* Additional information:
*
*@end
*******************************************************************************/
phStbImageErrorCode_t phStbPng_GetDimensions( const char* filename, int32_t * pWidth, int32_t * pHeight )
{
    int32_t bit_depth;
    int32_t color_type;
    int32_t interlace_type;
    png_structp png_ptr;
    png_infop info_ptr;
    png_uint_32 imageHeight;
    png_uint_32 imageWidth;
    phStbImageErrorCode_t status;
    phStbPng_contextProperties_t *pContext;

    pContext = (phStbPng_contextProperties_t*)calloc(1, sizeof(phStbPng_contextProperties_t));
    if (pContext == NULL)
    {
        return PH_STB_ERR_OUT_OF_MEMORY;
    }

    PH_STB_PNG_CHECK_LABEL(phStbPng_prepareInput(pContext, filename), clean_up_and_exit_dimensions)

    if (!phStbPng_CheckIfPng(pContext))
    {
        /* PNG signature not found. This file is not a PNG */
        DBG_PRINT((dbgPng,DBG_DEFAULT,"phStbDecodePng: PNG failed signature check\n"));
        status = PH_STB_ERR_FILE_WRONG_FORMAT;
        goto clean_up_and_exit_dimensions;
    }
    
    /* Create and initialize the png_struct with the desired error handler
     * functions.  If you want to use the default stderr and longjump method,
     * you can supply NULL for the last three parameters.  We also supply the
     * the compiler header file version, so that we know if the application
     * was compiled with a compatible version of the library.  REQUIRED
     */
    png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,NULL,NULL,NULL);
    if (png_ptr == NULL) 
    {     
        DBG_PRINT((dbgPng,DBG_DEFAULT,"phStbDecodePng: failed create read structure\n"));
        status = PH_STB_ERR_OUT_OF_MEMORY;
        goto clean_up_and_exit_dimensions;
    }
    
    /* Allocate/initialize the memory for image information. */
    info_ptr = png_create_info_struct(png_ptr);
    if (info_ptr == NULL)
    {
        DBG_PRINT((dbgPng,DBG_DEFAULT,"phStbDecodePng: failed create info structure\n"));
        png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL);
        status = PH_STB_ERR_OUT_OF_MEMORY;
        goto clean_up_and_exit_dimensions;
    }
    
    /* Set error handling if you are using the setjmp/longjmp method (this is
     * the normal method of doing things with libpng).  REQUIRED unless you
     * set up your own error handlers in the png_create_read_struct() earlier.
     */
    if (setjmp(png_ptr->jmpbuf))
    {
        /* Free all of the memory associated with the png_ptr and info_ptr */
        DBG_PRINT((dbgPng,DBG_DEFAULT,"phStbDecodePng: PNG ERROR - longjmp called\n"));

        /* Tidy up other stuff properly */
        png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
        status = PH_STB_ERR_FILE_WRONG_FORMAT;
        goto clean_up_and_exit_dimensions;
    }
    
    /* Use a replacement read functions */
    png_set_read_fn(png_ptr, (void *)pContext, phStbPng_readFunc);
    
    /* The call to png_read_info() gives us all of the information from the
     * PNG file before the first IDAT (image data chunk).  */
    png_read_info(png_ptr, info_ptr);
    
    /* The easiest way to read the image */
    (void)png_get_IHDR(png_ptr, info_ptr, &imageWidth, &imageHeight, 
                       &bit_depth, &color_type,
                       &interlace_type, NULL, NULL);

    if (pWidth)
    {
        *pWidth = (int32_t)imageWidth;
    }
    if (pHeight)
    {
        *pHeight = (int32_t)imageHeight;
    }

    /* Tidy up other stuff properly */
    png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);


clean_up_and_exit_dimensions:

    phStbPng_closeInput(pContext);
    free(pContext);
    return status;
}

/*******************************************************************************
*@begin
* NAME: phStbPng_Decode
*       Decodes a bitmap supplied in PNG format.
*       
* Returns : PH_STB_OK if successful - else error
* 
* Parameter     Flow    Description
* ------------------------------------------------------------------------------
* filename      IN      Image filename
* pSetup        IN      Pointer to set up information - holding decode 
*                       information
* 
* Externals     Flow    Usage
* ------------------------------------------------------------------------------
* 
* Additional information:
*
*@end
*******************************************************************************/

phStbImageErrorCode_t phStbPng_Decode( const char* filename, phStbImage_SetupParams_t const * pSetup)
{
    int32_t bit_depth;
    int32_t color_type;
    int32_t interlace_type;
    int32_t row;
    png_structp png_ptr;
    png_infop info_ptr;
    png_bytep *row_pointers;
    png_uint_32 imageHeight;
    png_uint_32 imageWidth;

    uint32_t bytesPerPixel;
    int32_t transparentPixels;
    phStbBitmapDataType_t dataType;
    uint8_t *pInBuffer;
    uint8_t *pInBuffer2;
    int32_t  bufferWidth;
    int32_t  bufferHeight;
    int32_t  outOffsetX;
    int32_t  outOffsetY;
    int32_t  outWidth;
    int32_t  outHeight;
    bool     scale;

    uint8_t* rowInfo = NULL;
    uint8_t* pDataBuffer = NULL;
    uint8_t* pPaletteBuffer = NULL;

    phStbImageErrorCode_t status;
    phStbPng_contextProperties_t *pContext;

    /* Check that a valid instance set up structure has been provided */
    if (pSetup == NULL)
    {      
        return PH_STB_ERR_NULL_PARAMETER;
    }

    /* Take local copies of important variables */
    pInBuffer        = pSetup->pInBuffer;
    pInBuffer2       = pSetup->pInBuffer2;
    bufferWidth      = pSetup->InBufferWidth;
    bufferHeight     = pSetup->InBufferHeight;
    scale            = pSetup->bScalingEnabled;
    outOffsetX       = pSetup->OutImageOffsetX;
    outOffsetY       = pSetup->OutImageOffsetY;
    outWidth         = pSetup->OutImageWidth;
    outHeight        = pSetup->OutImageHeight;

    /* Check to see if a valid decode buffer has been provided */
    if (pInBuffer == NULL)
    {
        return PH_STB_ERR_NULL_PARAMETER;
    }


    pContext = (phStbPng_contextProperties_t*)calloc(1, sizeof(phStbPng_contextProperties_t));
    if (pContext == NULL)
    {
        return PH_STB_ERR_OUT_OF_MEMORY;
    }

    PH_STB_PNG_CHECK_LABEL(phStbPng_prepareInput(pContext, filename), clean_up_and_exit_decode)

    transparentPixels = 0;
    row_pointers = NULL;

    if (!phStbPng_CheckIfPng(pContext))
    {
        /* PNG signature not found. This file is not a PNG */
        DBG_PRINT((dbgPng,DBG_DEFAULT,"phStbDecodePng: PNG failed signature check\n"));
        status = PH_STB_ERR_FILE_WRONG_FORMAT;
        goto clean_up_and_exit_decode;
    }
    
    /* Create and initialize the png_struct with the desired error handler
     * functions.  If you want to use the default stderr and longjump method,
     * you can supply NULL for the last three parameters.  We also supply the
     * the compiler header file version, so that we know if the application
     * was compiled with a compatible version of the library.  REQUIRED
     */
    png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,NULL,NULL,NULL);
    if (png_ptr == NULL) 
    {     
        DBG_PRINT((dbgPng,DBG_DEFAULT,"phStbDecodePng: failed create read structure\n"));
        status = PH_STB_ERR_OUT_OF_MEMORY;
        goto clean_up_and_exit_decode;
    }
    
    /* Allocate/initialize the memory for image information. */
    info_ptr = png_create_info_struct(png_ptr);
    if (info_ptr == NULL)
    {
        DBG_PRINT((dbgPng,DBG_DEFAULT,"phStbDecodePng: failed create info structure\n"));
        png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL);
        status = PH_STB_ERR_OUT_OF_MEMORY;
        goto clean_up_and_exit_decode;
    }
    
    /* Set error handling if you are using the setjmp/longjmp method (this is
     * the normal method of doing things with libpng).  REQUIRED unless you
     * set up your own error handlers in the png_create_read_struct() earlier.
     */
    if (setjmp(png_ptr->jmpbuf))
    {
        /* Free all of the memory associated with the png_ptr and info_ptr */
        DBG_PRINT((dbgPng,DBG_DEFAULT,"phStbDecodePng: PNG ERROR - longjmp called\n"));

        /* Tidy up other stuff properly */
        png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
        status = PH_STB_ERR_FILE_WRONG_FORMAT;
        goto clean_up_and_exit_decode;
    }
    
    /* Use a replacement read functions */
    png_set_read_fn(png_ptr, (void *)pContext, phStbPng_readFunc);

    /* The call to png_read_info() gives us all of the information from the
     * PNG file before the first IDAT (image data chunk).  */
    png_read_info(png_ptr, info_ptr);
    
    /* The easiest way to read the image */
    (void)png_get_IHDR(png_ptr, info_ptr, &imageWidth, &imageHeight, 
                       &bit_depth, &color_type,
                       &interlace_type, NULL, NULL);

    /* tell libpng to strip 16 bit/color files down to 8 bits/color */
    if (png_ptr->bit_depth == 16)
    {
        png_set_strip_16(png_ptr);
    }

    switch (info_ptr->color_type)
    {
        case PNG_COLOR_TYPE_GRAY :        /* (bit depths 1, 2, 4, 8, 16) */
            dataType = GFX_BITMAP_DATATYPE_GREY;
            bytesPerPixel = 1;

            /* expand grayscale images to the full 8 bits */
            if (info_ptr->bit_depth < 8) 
            {
                png_set_expand(png_ptr);
            }
            if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) 
            {
                transparentPixels = 1;
                bytesPerPixel = 2;
            }
            break;
            
        case PNG_COLOR_TYPE_GRAY_ALPHA :  /* (bit depths 8, 16) */
            dataType = GFX_BITMAP_DATATYPE_GREY;
            transparentPixels = 1;
            bytesPerPixel = 2;
            break;
            
        case PNG_COLOR_TYPE_PALETTE :     /* (bit depths 1, 2, 4, 8) */
        {
            int32_t num_palette;
            png_colorp palette;
            int32_t i;
                
            dataType = GFX_BITMAP_DATATYPE_PALETTE;
            bytesPerPixel = 1;

            /* Extract multiple pixels with bit depths of 1, 2, and 4
             * from a single byte into separate bytes.
             */
            png_set_packing(png_ptr);
        
            (void)png_get_PLTE(png_ptr, info_ptr, &palette, &num_palette);

            /* Create the memory to hold the palette structure */
            pPaletteBuffer = (uint8_t*)calloc((uint32_t)num_palette, sizeof(uint32_t));
            if (pPaletteBuffer==NULL)
            {
                DBG_PRINT((dbgPng,DBG_DEFAULT,"phStbDecodePng: out of memory allocating palette\n"));
                png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
                status = PH_STB_ERR_OUT_OF_MEMORY;
                goto clean_up_and_exit_decode;
            }

            if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
            {
                int32_t num_trans;
                png_bytep trans;
                png_color_16p trans_values;
                
                /* Image contains transparancy information. Convert to 1colour */
                transparentPixels = 1;

                (void)png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, &trans_values);
                for (i = 0; i < num_trans; i++)
                {
                    ((uint32_t*)pPaletteBuffer)[i] = RGBA8888(palette[i].red,
                                                           palette[i].green,
                                                           palette[i].blue,
                                                           trans[i]);
                }
                for (i = num_trans; i < num_palette; i++)
                {
                    ((uint32_t*)pPaletteBuffer)[i] = RGBA8888(palette[i].red,
                                                           palette[i].green,
                                                           palette[i].blue,
                                                           NO_ALPHA);
                }
            }
            else
            {
                for (i = 0; i < num_palette; i++)
                {
                    ((uint32_t*)pPaletteBuffer)[i] = RGBA8888(palette[i].red,
                                                           palette[i].green,
                                                           palette[i].blue,
                                                           NO_ALPHA);
                }
            }
            
            break;
        }
        case PNG_COLOR_TYPE_RGB :         /* (bit_depths 8, 16) */
            dataType = GFX_BITMAP_DATATYPE_1COLOUR24;
            bytesPerPixel = 4;

            png_set_filler(png_ptr, 0, PNG_FILLER_AFTER);
            
            if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) 
            {
                dataType = GFX_BITMAP_DATATYPE_1COLOUR;
                transparentPixels = 1;

                /* Expand RGB images with transparency to full alpha channels
                 * so the data will be available as RGBA quartets.
                 */
                png_set_expand(png_ptr);
            }
            break;
            
        case PNG_COLOR_TYPE_RGB_ALPHA :   /*(bit_depths 8, 16) */
            dataType = GFX_BITMAP_DATATYPE_1COLOUR;
            transparentPixels = 1;
            bytesPerPixel = 4;
            break;
            
        default:
            DBG_PRINT((dbgPng,DBG_DEFAULT,"phStbDecodePng: Unknown PNG color_type\n"));
            png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
            status = PH_STB_ERR_FILE_WRONG_FORMAT;
            goto clean_up_and_exit_decode;
    }

    /* update information in info_ptr */
    png_read_update_info(png_ptr, info_ptr);

    /* Create the memory to hold the image data */
    pDataBuffer = (uint8_t*)calloc(imageWidth*imageHeight*bytesPerPixel, sizeof(uint8_t));
    if (pDataBuffer==NULL)
    {
        DBG_PRINT((dbgPng,DBG_DEFAULT,"phStbDecodePng: out of memory allocating data\n"));
        png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
        status = PH_STB_ERR_OUT_OF_MEMORY;
        goto clean_up_and_exit_decode;
    }
    
    /* Create the memory to hold the row data */
    rowInfo = (uint8_t*)calloc(imageHeight, sizeof(png_bytep));
    if (rowInfo==NULL)
    {
        DBG_PRINT((dbgPng,DBG_DEFAULT,"phStbDecodePng: out of memory allocating row_pointers\n"));
        png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
        status = PH_STB_ERR_OUT_OF_MEMORY;
        goto clean_up_and_exit_decode;
    }

    /* Set up the row pointers */
    row_pointers = (png_bytep*)rowInfo;
    for (row = 0; row < (int32_t) imageHeight; row++)
    {
        row_pointers[row] = (uint8_t *)pDataBuffer + (uint32_t)row * imageWidth * bytesPerPixel;
    }

    /* Read in the image data */
    png_read_image(png_ptr, row_pointers);

    /* read rest of file, and get additional chunks in info_ptr - REQUIRED */
    png_read_end(png_ptr, info_ptr);
    
    /* clean up after the read, and free any memory allocated - REQUIRED */
    png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
    
    /* Free off the row pointers */
    free(rowInfo);

    if ((outOffsetX < bufferWidth) &&
        (outOffsetY < bufferHeight))
    {
        if (scale &&
            ((imageWidth != (png_uint_32)outWidth) ||
             (imageHeight != (png_uint_32)outHeight)))
        {
            phStbPng_BitmapScale( pDataBuffer, pPaletteBuffer, 
                               (int32_t)imageWidth, (int32_t)imageHeight, 
                               pInBuffer, pInBuffer2, bufferWidth, bufferHeight, 
                               outOffsetX, outOffsetY, outWidth, outHeight,
                               transparentPixels, dataType,
                               pSetup->gfxMode, pSetup->colourMapFunction);
        }
        else
        {
            phStbPng_BitmapCopy( pDataBuffer, pPaletteBuffer, 
                              (int32_t)imageWidth, (int32_t)imageHeight, 
                              pInBuffer, pInBuffer2, bufferWidth, bufferHeight, 
                              outOffsetX, outOffsetY, outWidth, outHeight,
                              transparentPixels, dataType,
                              pSetup->gfxMode, pSetup->colourMapFunction);
        }
    }

clean_up_and_exit_decode :

    phStbPng_closeInput(pContext);

    /* Free off any memory used during the decoding */
    if (pPaletteBuffer)
    {
       free(pPaletteBuffer);
    }
    if (pDataBuffer) 
    {
        free(pDataBuffer);
    }
    free(pContext);

    return status;
}

