/*****************************************************************************/
/* 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   phStbJpg.c
    @brief  phStbJpg component

@b  Application:  phStbJpg

*//*

Rev Date        Author      Comments
--------------------------------------------------------------------------------
  1 20060329    neill       Original
  2 20060405    neill       Update include file name
  3 20061117    neill       Make library re-entrant
  4 20061117    neill       Changes after crosscheck
  5 20070320    neill       Changes after QAC checking
  6 20071023    neill       More QAC checking
  8 20080208    batelaan    Update file copyright/license text
  9 20080416    neill       Add centre weighted averaging for downscales
 10 20081219    neill       Add support for multiple graphics modes
 11 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 <stdio.h>
#include "jpeglib.h"
#include <setjmp.h>

#include "phStbImage.h"
#include "phStbImageUtils.h"
#include "phStbJpg.h"

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

#define PH_STB_JPG_FULL_ALPHA               (0xFF)

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

struct my_error_mgr {
  struct jpeg_error_mgr pub;    /* "public" fields */

  jmp_buf setjmp_buffer;    /* for return to caller */
};

typedef struct my_error_mgr * my_error_ptr;

typedef struct
{
   int32_t   width;
   int32_t   height;
   int32_t   outputX;
   int32_t   outputY;
   int32_t   scalePosition;
   float scaleWidthFactor;
   float scaleHeightFactor;
   int32_t   scaleWidth;
   int32_t   scaleHeight;
   bool      scaleDown;
} phStbJpg_GlobalProperties_t;


typedef struct
{
    /* Variables to hold the global and local image properties */
    phStbJpg_GlobalProperties_t globalProperties;
    
    /* Pointer to the decode buffer */
    uint32_t * dataBuffer;
    uint32_t * scaleBuffer;

}phStbJpg_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*
********************************************************************************/

static void my_error_exit (j_common_ptr cinfo)
{
  /* cinfo->err really points to a my_error_mgr struct, so coerce pointer */
  my_error_ptr myerr = (my_error_ptr) cinfo->err;

  /* Always display the message. */
  /* We could postpone this until after returning, if we chose. */
  (*cinfo->err->output_message) (cinfo);

  /* Return control to the setjmp point */
  longjmp(myerr->setjmp_buffer, 1);
}

/*******************************************************************************
*@begin
* NAME: phStbJpg_convertGlobalPosition
*       Converts a global pixel position
*       
* Returns : Pointer to the pixel location within the output buffer
* 
* Parameter     Flow    Description
* ------------------------------------------------------------------------------
* pContext      IN      Pointer to context information
* 
* Externals     Flow    Usage
* ------------------------------------------------------------------------------
* 
* Additional information:
*
*@end
*******************************************************************************/
static inline uint32_t * phStbJpg_convertGlobalPosition(phStbJpg_contextProperties_t const *pContext)
{
    if ( (pContext->globalProperties.outputX >= 0) &&
         (pContext->globalProperties.outputX < pContext->globalProperties.width) &&
         (pContext->globalProperties.outputY >= 0) &&
         (pContext->globalProperties.outputY < pContext->globalProperties.height))
    {
        if (pContext->globalProperties.scaleDown)
        {
            return pContext->scaleBuffer+pContext->globalProperties.outputX;
        }
        else
        {
            return pContext->dataBuffer+(pContext->globalProperties.outputY*pContext->globalProperties.width)+pContext->globalProperties.outputX;
        }
    }
 
    return NULL;
}

/*******************************************************************************
*@begin
* NAME: phStbJpg_outputPixel
*       Writes a single pixel value to the output buffer
*       
* Returns : void
* 
* Parameter     Flow    Description
* ------------------------------------------------------------------------------
* pContext      IN      Pointer to context information
* value         IN    Pixel value
* 
* Externals     Flow    Usage
* ------------------------------------------------------------------------------
* 
* Additional information:
*
*@end
*******************************************************************************/
static inline void phStbJpg_outputPixel(phStbJpg_contextProperties_t const *pContext, uint32_t value)
{
    uint32_t * pOutput;

    pOutput = phStbJpg_convertGlobalPosition(pContext);

    if (pOutput)
    {
        *pOutput = value;
    }
}

/*******************************************************************************
*@begin
* NAME: phStbJpg_weightedAverage
*       Provides the centre weighted average for a group of 8 bits at a given
*       position in some source data
*       
* Returns : Centre weighted average value
* 
* Parameter     Flow    Description
* ------------------------------------------------------------------------------
* srcPtr        IN      pointer to source data
* prev_pixel    IN      offset to the pixel to the left of the target pixel
* this_pixel    IN      offset to the target pixel
* next_pixel    IN      offset to the pixel to the right of the target pixel
* offset        IN      offset in bits for the 8 bits to be averaged
* 
* Externals     Flow    Usage
* ------------------------------------------------------------------------------
* 
* Additional information:
*
*@end
*******************************************************************************/
static inline uint32_t phStbJpg_weightedAverage(uint32_t *srcPtr, 
                                                int32_t prev_pixel, 
                                                int32_t this_pixel,
                                                int32_t next_pixel, 
                                                uint32_t offset)
{
    uint32_t prev;
    uint32_t this;
    uint32_t next;

    prev = (srcPtr[prev_pixel] >> offset) & 0xFF;
    this = (srcPtr[this_pixel] >> offset) & 0xFF;
    next = (srcPtr[next_pixel] >> offset) & 0xFF;

    return ((prev + (2*this) + next) / 4);
}

/*******************************************************************************
*@begin
* NAME: phStbJpg_updateScaledInfo
*       Checks to see if a suffiecient number of decoded lines are available
*       to allow the image scaling for the current output line to be done - and
*       then scales the output line of pixels
*       
* Returns : True if scaling was performed else False
* 
* Parameter     Flow    Description
* ------------------------------------------------------------------------------
* pContext      IN      Pointer to context information
* 
* Externals     Flow    Usage
* ------------------------------------------------------------------------------
* 
* Additional information:
*
*@end
*******************************************************************************/
static int32_t phStbJpg_updateScaledInfo(phStbJpg_contextProperties_t *pContext)
{
    int32_t endRow;

    endRow = (int32_t)((float)pContext->globalProperties.scalePosition/pContext->globalProperties.scaleHeightFactor);

    CLAMP_VALUE(endRow, (pContext->globalProperties.height))
    /* Check to see if a sufficient number of lines of pixels are available */
    if ((pContext->globalProperties.scalePosition < pContext->globalProperties.scaleHeight) && (endRow <= pContext->globalProperties.outputY))
    {
        int32_t col;
        uint32_t * destPtr;
        uint32_t * srcPtr;

        /* Set up the pointer to the write location in the bitmap */
        destPtr = (uint32_t *)pContext->dataBuffer + (pContext->globalProperties.scaleWidth*pContext->globalProperties.scalePosition);
        srcPtr = pContext->scaleBuffer;

        if (pContext->globalProperties.scaleWidth >= pContext->globalProperties.width)
        {
            /* Scale up */
            for(col=0; col<pContext->globalProperties.scaleWidth; col++)
            {
                destPtr[col] = srcPtr[(pContext->globalProperties.width*col)/pContext->globalProperties.scaleWidth];
            }
        }
        else
        {
            /* Scale down - use a centre weighted average method */
            for(col=0; col<pContext->globalProperties.scaleWidth; col++)
            {
                int32_t prev_pixel;
                int32_t next_pixel;
                int32_t this_pixel;
                uint32_t red;
                uint32_t green;
                uint32_t blue;
                uint32_t alpha;

                /* This is the 'main' contributing pixel to the destination */
                this_pixel = (pContext->globalProperties.width*col)/pContext->globalProperties.scaleWidth;

                /* Calculate the pixel to the left */
                if (this_pixel != 0) 
                {
                    prev_pixel = this_pixel-1;
                }
                else
                {
                    prev_pixel = 0;
                }

                /* Calculate the pixel to the right */
                if (this_pixel != pContext->globalProperties.width) 
                {
                    next_pixel = this_pixel+1;
                }
                else
                {
                    next_pixel = this_pixel;
                }

                /* Calculate the weighted averages for the colour components */
                alpha = phStbJpg_weightedAverage(srcPtr, prev_pixel, this_pixel, next_pixel, 24);
                red   = phStbJpg_weightedAverage(srcPtr, prev_pixel, this_pixel, next_pixel, 16);
                green = phStbJpg_weightedAverage(srcPtr, prev_pixel, this_pixel, next_pixel, 8);
                blue  = phStbJpg_weightedAverage(srcPtr, prev_pixel, this_pixel, next_pixel, 0);

                /* Combine the averages to make the final pixel value */
                destPtr[col] = (uint32_t)((alpha<<24) | (red<<16) | (green<<8) | blue);
            }
        }

        /* Move onto the next output line */
        pContext->globalProperties.scalePosition++;
        return 1;
    }
    return 0;
}

/*******************************************************************************
*@begin
* NAME: phStbJpg_scaleOutput
*       Checks to see if down-scaling is required - and scales lines as necessary
*       
* Returns : void
* 
* Parameter     Flow    Description
* ------------------------------------------------------------------------------
* pContext      IN      Pointer to context information
* 
* Externals     Flow    Usage
* ------------------------------------------------------------------------------
* 
* Additional information:
*
*@end
*******************************************************************************/
static inline void phStbJpg_scaleOutput(phStbJpg_contextProperties_t *pContext)
{
    if (pContext->globalProperties.scaleDown)
    {
        while(phStbJpg_updateScaledInfo(pContext))
        {
        }
    }
}

/*******************************************************************************
*@begin
* NAME: phStbJpg_GetDimensions
*       Gets dimensions of a bitmap supplied in Jpg 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 phStbJpg_GetDimensions( const char* filename, int32_t * pWidth, int32_t * pHeight )
{
    struct jpeg_decompress_struct cinfo;
    struct my_error_mgr jerr;
    FILE * infile;      /* source file */
    
    infile = fopen(filename, "rb"); 
    if (infile == NULL) 
    {
        (void)fprintf(stderr, "can't open %s\n", filename);
        return PH_STB_ERR_FILE_NOT_OPEN;
    }
    
    /* Step 1: allocate and initialize JPEG decompression object */
    
    /* We set up the normal JPEG error routines, then override error_exit. */
    cinfo.err = jpeg_std_error(&jerr.pub);
    jerr.pub.error_exit = my_error_exit;
    /* Establish the setjmp return context for my_error_exit to use. */
    if (setjmp(jerr.setjmp_buffer)) 
    {
        /* If we get here, the JPEG code has signaled an error.
         * We need to clean up the JPEG object, close the input file, and return.
         */
        jpeg_destroy_decompress(&cinfo);
        (void)fclose(infile);
        return PH_STB_ERR_FILE_NOT_OPEN;
    }

    /* Now we can initialize the JPEG decompression object. */
    jpeg_create_decompress(&cinfo);
    
    /* Step 2: specify data source (eg, a file) */
    jpeg_stdio_src(&cinfo, infile);
    
    /* Step 3: read file parameters with jpeg_read_header() */    
    (void) jpeg_read_header(&cinfo, TRUE);
    
    if (pWidth)
    {
      *pWidth = (int32_t)cinfo.image_width;
    }
    if (pHeight)
    {
      *pHeight = (int32_t)cinfo.image_height;
    }

    jpeg_destroy_decompress(&cinfo);
    (void)fclose(infile);
    
    return PH_STB_OK;
}

/*******************************************************************************
*@begin
* NAME: phStbJpg_Decode
*       Decodes a bitmap supplied in Jpg 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 phStbJpg_Decode( const char* filename, phStbImage_SetupParams_t const * pSetup)
{
    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  imageWidth;
    int32_t  imageHeight;
    bool     scale;
    bool     fastScale;

    uint8_t * scanline = NULL;
    struct jpeg_decompress_struct cinfo;
    struct my_error_mgr jerr;
    FILE * infile = NULL;       /* source file */
    int32_t row_stride;     /* physical row width in output buffer */
    phStbImageErrorCode_t status = PH_STB_OK;
    phStbJpg_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 */
    scale            = pSetup->bScalingEnabled;
    fastScale        = pSetup->bFastScalingEnabled;
    pInBuffer        = pSetup->pInBuffer;
    pInBuffer2       = pSetup->pInBuffer2;
    bufferWidth      = pSetup->InBufferWidth;
    bufferHeight     = pSetup->InBufferHeight;
    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 = (phStbJpg_contextProperties_t*)calloc(1, sizeof(phStbJpg_contextProperties_t));
    if (pContext == NULL)
    {
        return PH_STB_ERR_OUT_OF_MEMORY;
    }

    infile = fopen(filename, "rb");
    if (infile == NULL) 
    {
        (void)fprintf(stderr, "can't open %s\n", filename);
        status = PH_STB_ERR_FILE_NOT_OPEN;
        goto freeAndReturn;
    }

    /* Step 1: allocate and initialize JPEG decompression object */

    /* We set up the normal JPEG error routines, then override error_exit. */
    cinfo.err = jpeg_std_error(&jerr.pub);
    jerr.pub.error_exit = my_error_exit;
    /* Establish the setjmp return context for my_error_exit to use. */
    if (setjmp(jerr.setjmp_buffer)) 
    {
        /* If we get here, the JPEG code has signaled an error.
         * We need to clean up the JPEG object, close the input file, and return.
         */
        jpeg_destroy_decompress(&cinfo);
        status = PH_STB_ERR_FILE_NOT_OPEN;
        goto freeAndReturn;
    }

    /* Now we can initialize the JPEG decompression object. */
    jpeg_create_decompress(&cinfo);

    /* Step 2: specify data source (eg, a file) */
    jpeg_stdio_src(&cinfo, infile);

    /* Step 3: read file parameters with jpeg_read_header() */    
    (void) jpeg_read_header(&cinfo, TRUE);

    pContext->globalProperties.width = (int32_t)cinfo.image_width;
    pContext->globalProperties.height = (int32_t)cinfo.image_height;

    pContext->globalProperties.outputX = 0;
    pContext->globalProperties.outputY = 0;

    /* Check if the image is to be scaled */
    if (scale && fastScale) 
    {
        /* Check to see if we can get the JPEG decoder to do some scaling ... speeds things up! */
        if ((pContext->globalProperties.width/2 >= outWidth) &&
            (pContext->globalProperties.height/2 >= outHeight))
        {
            cinfo.scale_denom = 2;
            if ((pContext->globalProperties.width/4 >= outWidth) &&
                (pContext->globalProperties.height/4 >= outHeight))
            {
                cinfo.scale_denom = 4;
                if ((pContext->globalProperties.width/8 >= outWidth) &&
                    (pContext->globalProperties.height/8 >= outHeight))
                {
                    cinfo.scale_denom = 8;
                }
            }
        }
    }
    pContext->globalProperties.width = (pContext->globalProperties.width+(int32_t)cinfo.scale_denom-1) / (int32_t)cinfo.scale_denom;
    pContext->globalProperties.height = (pContext->globalProperties.height+(int32_t)cinfo.scale_denom-1) / (int32_t)cinfo.scale_denom;

    if (scale &&
        ((pContext->globalProperties.height>outHeight) ||
         (pContext->globalProperties.width>outWidth)))
    {
        float heightScale = (float)outHeight / (float)pContext->globalProperties.height;
        float widthScale = (float)outWidth / (float)pContext->globalProperties.width;

        /* Set up all the variables involved with scaling the image */
        pContext->globalProperties.scaleDown = true;
        pContext->globalProperties.scaleWidth = imageWidth = outWidth;
        pContext->globalProperties.scaleHeight = imageHeight = outHeight;
        pContext->globalProperties.scalePosition = 0;
        pContext->globalProperties.scaleWidthFactor = widthScale;
        pContext->globalProperties.scaleHeightFactor = heightScale;
    
        /* Allocate memory to hold the unscaled lines */
        pContext->scaleBuffer = (uint32_t*) calloc((uint32_t)pContext->globalProperties.width, sizeof(uint32_t));
        if (pContext->scaleBuffer==NULL)
        {
            DBG_PRINT((dbgJpg,DBG_DEFAULT,"phStbJpg_Decode: out of memory\n"));
            status = PH_STB_ERR_OUT_OF_MEMORY;
            goto freeAndReturn;
        }
    }
    else
    {
        pContext->globalProperties.scaleDown = false;
        pContext->scaleBuffer = NULL;
        imageWidth = pContext->globalProperties.width;
        imageHeight = pContext->globalProperties.height;
    }

    pContext->dataBuffer = (uint32_t*) calloc((uint32_t)(imageWidth*imageHeight), sizeof(uint32_t));
    if (pContext->dataBuffer==NULL)
    {
        DBG_PRINT((dbgJpg,DBG_DEFAULT,"phStbJpg_Decode: out of memory\n"));
        status = PH_STB_ERR_OUT_OF_MEMORY;
        goto freeAndReturn;
    }
    /* Step 5: Start decompressor */
    (void) jpeg_start_decompress(&cinfo);

    row_stride = (int32_t)cinfo.output_width * cinfo.output_components;
    scanline = (uint8_t*)malloc((uint32_t)row_stride);
    if (scanline==NULL)
    {
        DBG_PRINT((dbgJpg,DBG_DEFAULT,"phStbJpg_Decode: out of memory\n"));
        status = PH_STB_ERR_OUT_OF_MEMORY;
        goto freeAndReturn;
    }

    /* Step 6: while (scan lines remain to be read) */
    /*           jpeg_read_scanlines(...); */

    /* Here we use the library's state variable cinfo.output_scanline as the
     * loop counter, so that we don't have to keep track ourselves.
     */
    if (cinfo.output_components == 1)
    {
        while (cinfo.output_scanline < cinfo.output_height) 
        {
            uint8_t* ptr;
            int32_t i;
            
            ptr = scanline;
            (void) jpeg_read_scanlines(&cinfo, &scanline, 1);
            pContext->globalProperties.outputX=0;
            for(i=0; i<row_stride; i+=cinfo.output_components)
            {
                phStbJpg_outputPixel(pContext, RGBA8888(*ptr, *ptr, *ptr, PH_STB_JPG_FULL_ALPHA));
                pContext->globalProperties.outputX++;
                ptr+=cinfo.output_components;
            }
            
            phStbJpg_scaleOutput(pContext);
            pContext->globalProperties.outputY++;
        }
    }
    else
    {
        while (cinfo.output_scanline < cinfo.output_height) 
        {
            uint8_t* ptr;
            int32_t i;
            
            ptr = scanline;
            (void) jpeg_read_scanlines(&cinfo, &scanline, 1);
            
            pContext->globalProperties.outputX=0;
            for(i=0; i<row_stride; i+=cinfo.output_components)
            {
                phStbJpg_outputPixel(pContext, RGBA8888(*ptr, *(ptr+1), *(ptr+2), PH_STB_JPG_FULL_ALPHA));
                pContext->globalProperties.outputX++;
                ptr+=cinfo.output_components;
            }
            
            phStbJpg_scaleOutput(pContext);
            pContext->globalProperties.outputY++;
        }
    }

    /* Step 7: Finish decompression */
    (void) jpeg_finish_decompress(&cinfo);

    /* Step 8: Release JPEG decompression object */
    jpeg_destroy_decompress(&cinfo);

    if ((outOffsetX < bufferWidth) &&
        (outOffsetY < bufferHeight))
    {
        if (scale &&
            ((imageWidth != outWidth) ||
             (imageHeight != outHeight)))
        {
            phStbImage_BitmapScale( pContext->dataBuffer, imageWidth, imageHeight,
                                    pInBuffer, pInBuffer2, bufferWidth, bufferHeight, 
                                    outOffsetX, outOffsetY, outWidth, outHeight,
                                    pSetup->gfxMode, pSetup->colourMapFunction);
        }
        else
        {
            phStbImage_BitmapCopy( pContext->dataBuffer, imageWidth, imageHeight,
                                   pInBuffer, pInBuffer2, bufferWidth, bufferHeight, 
                                   outOffsetX, outOffsetY, outWidth, outHeight,
                                   pSetup->gfxMode, pSetup->colourMapFunction);
        }
    }

freeAndReturn:
    /* Free off any memory used during the decoding */
    if (scanline) 
    {
        free(scanline);
    }

    if (pContext->dataBuffer)
    {
        free(pContext->dataBuffer);
    }

    if (pContext->scaleBuffer)
    {
        free(pContext->scaleBuffer);
    }

    if (infile)
    {
        (void)fclose(infile);
    }

    free(pContext);

    return status;
}

