/*****************************************************************************/
/* 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   phStbImageUtils.c
    @brief  phStbImage utilities component

@b  Application:  phStbImage

*//*

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

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

#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include "phStbImage.h"
#include "phStbImageUtils.h"
#include <magic.h>

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

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

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

phStbImageFileFormat phStbImage_getFileType(const char* filename)
{
	// Lets guess at bitmap in case we don't figure it out...
	phStbImageFileFormat image_type = phStbBmpFile;
	
	magic_t magicCookie = NULL;
	int magicStatus;
	const char* magicString = NULL;

	magicCookie = magic_open(MAGIC_SYMLINK | MAGIC_DEVICES);
	if (magicCookie != NULL)
	{
		// Load default magic database
		magicStatus = magic_load(magicCookie, NULL);

		if(magicStatus == 0)
		{
			// Check type of file
			magicString = magic_file(magicCookie, filename);
		}
	}

	if(magicString)
	{
		// Use magic string to determine file type
	    if(strstr(magicString, "JPEG"))
	    {
            image_type = phStbJpegFile;
		}
        else if(strstr(magicString, "PC bitmap"))
        {
            image_type = phStbBmpFile;
		}
        else if(strstr(magicString, "PNG"))
        {
            image_type = phStbPngFile;
		}
		else if(strstr(magicString, "GIF"))
		{
            image_type = phStbGifFile;
		}
	}
	else
	{
		// Use same old filename extension code as before
		char* pChar;
		char* ptr;

		ptr = pChar = (char *)malloc(strlen(filename)+1);
		if (pChar)
		{
			(void)strcpy(pChar, filename);

			/* Convert to lowercase */
			while(*ptr)
			{
				*ptr = (char)tolower((int32_t)*ptr);
				ptr++;
			}

			if (strstr(pChar, ".bmp")) 
			{
				image_type = phStbBmpFile;
			}
			else if (strstr(pChar, ".gif")) 
			{
				image_type = phStbGifFile;
			}
			else if (strstr(pChar, ".png")) 
			{
				image_type = phStbPngFile;
			}
			else if (strstr(pChar, ".jpg") || strstr(pChar, ".jpeg")) 
			{
				image_type = phStbJpegFile;
			}
			else if (strstr(pChar, "/dev/vrend/display")) 
			{
				image_type = phStbVideoDeviceFile;
			}

			free(pChar);
		}
	}
	
	// Close magic database
	if(magicCookie != NULL)
		magic_close(magicCookie);

	return image_type;
}


uint32_t phStbImage_convertPixel(uint32_t inPixel, phStbImage_GfxMode_t mode, phStbImage_cmapFunction pColourMapFunction)
{
    uint32_t retValue;
    switch(mode)
    {
        case(phStbImage_GfxMode_ARGB8888) : 
            retValue = inPixel;
            break;
        case(phStbImage_GfxMode_RGB565) :
            retValue = (((inPixel & 0x00F80000) >> 8) |
                        ((inPixel & 0x0000FC00) >> 5) |
                        ((inPixel & 0x000000F8) >> 3));
            break;
        case(phStbImage_GfxMode_ARGB1555) :
            retValue = (((inPixel & 0x80000000) >> 16) | 
                        ((inPixel & 0x00F80000) >> 9)  |
                        ((inPixel & 0x0000F800) >> 6)  |
                        ((inPixel & 0x000000F8) >> 3));
            break;
        case(phStbImage_GfxMode_ARGB4444) :
            retValue = (((inPixel & 0xF0000000) >> 16) | 
                        ((inPixel & 0x00F00000) >> 12) |
                        ((inPixel & 0x0000F000) >> 8)  |
                        ((inPixel & 0x000000F0) >> 4));
            break;
        case(phStbImage_GfxMode_Clut256) :
            if (pColourMapFunction)
            {
                retValue = pColourMapFunction(inPixel);
            }
            else
            {
                retValue = 0;
            }
            break;
        default :
            retValue = 0;
            break;
    }
    return retValue;
}

#define coef_a_x (47)
#define coef_b_x (157)
#define coef_c_x (16)
#define coef_a_y (-26)
#define coef_b_y (-86)
#define coef_c_y (112)
#define coef_a_z (112)
#define coef_b_z (-102)
#define coef_c_z (-10)
#define coef_cst_x (16)
#define coef_cst_y (128)
#define coef_cst_z (128)

/* RGB to YUV conversion routine - should use the same parameters as the HW conversion */
void phStbImage_yuvConvertPixel(uint32_t value, uint8_t *pY, uint8_t *pU, uint8_t *pV)
{
    int32_t blue;
    int32_t green;
    int32_t red;

    red   = (int32_t)(((value >> 16) & 0xFF)); 
    green = (int32_t)(((value >> 8) & 0xFF));
    blue  = (int32_t)((value & 0xFF));

    if (pY != NULL) 
    {
        *pY = (uint8_t)((((uint32_t)((coef_a_x*red) + (coef_b_x*green) + (coef_c_x*blue))) >> 8) + coef_cst_x);
    }

    if (pU != NULL) 
    {
        *pU = (uint8_t)((((uint32_t)((coef_a_y*red) + (coef_b_y*green) + (coef_c_y*blue))) >> 8) + coef_cst_y);
    }
    
    if (pV != NULL) 
    {
        *pV = (uint8_t)((((uint32_t)((coef_a_z*red) + (coef_b_z*green) + (coef_c_z*blue))) >> 8) + coef_cst_z);
    }
}


void phStbImage_BitmapScale( uint32_t *pDataBuffer, 
                             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,
                             phStbImage_GfxMode_t mode,
                             phStbImage_cmapFunction cmapFunction)
{
    int32_t row;
    int32_t col;
    int32_t destWidth;
    int32_t destHeight;
    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((dbgBmp,DBG_DEFAULT,"Scaling from %d,%d to %d,%d at %d,%d\n", imageWidth, imageHeight, destWidth, destHeight, outOffsetX, outOffsetY));

    switch(mode)
    {
        case(phStbImage_GfxMode_ARGB8888) : 
            {
                uint32_t *destRowPtr;
                /* Set up the pointer to the write location in the bitmap */
                destRowPtr = (uint32_t *)pInBuffer + (bufferWidth*outOffsetY) + outOffsetX;
                /* If we are scaling up then use a simple algorithm */
                for(row=0; row<destHeight; row++)
                {
                    int32_t srcRow;
                    uint32_t *destPtr;
                    uint32_t *srcPtr;
                    /* Work out the source row that contribute to the destination row pixel */
                    srcRow = (row * imageHeight) / outHeight;
                    CLAMP_VALUE(srcRow, (imageHeight-1))
                    destPtr = destRowPtr;
                    srcPtr = (uint32_t *)pDataBuffer + (imageWidth*srcRow);
                    for(col=0; col<destWidth; col++)
                    {
                        *(destPtr++) = srcPtr[(imageWidth*col)/destWidth];
                    }
                    destRowPtr += bufferWidth;
                }
            }
            break;
        case(phStbImage_GfxMode_RGB565) :
        case(phStbImage_GfxMode_ARGB1555) :
        case(phStbImage_GfxMode_ARGB4444) :
            {
                uint16_t *destRowPtr;
                /* Set up the pointer to the write location in the bitmap */
                destRowPtr = (uint16_t *)pInBuffer + (bufferWidth*outOffsetY) + outOffsetX;
                /* If we are scaling up then use a simple algorithm */
                for(row=0; row<destHeight; row++)
                {
                    int32_t srcRow;
                    uint16_t *destPtr;
                    uint32_t *srcPtr;
                    /* Work out the source row that contribute to the destination row pixel */
                    srcRow = (row * imageHeight) / outHeight;
                    CLAMP_VALUE(srcRow, (imageHeight-1))
                    destPtr = destRowPtr;
                    srcPtr = (uint32_t *)pDataBuffer + (imageWidth*srcRow);
                    for(col=0; col<destWidth; col++)
                    {
                        *(destPtr++) = (uint16_t)phStbImage_convertPixel(srcPtr[(imageWidth*col)/destWidth], mode, NULL);
                    }
                    destRowPtr += bufferWidth;
                }
            }
            break;
        case(phStbImage_GfxMode_Clut256) :
            {
                uint8_t *destRowPtr;
                /* Set up the pointer to the write location in the bitmap */
                destRowPtr = (uint8_t *)pInBuffer + (bufferWidth*outOffsetY) + outOffsetX;
                /* If we are scaling up then use a simple algorithm */
                for(row=0; row<destHeight; row++)
                {
                    int32_t srcRow;
                    uint8_t *destPtr;
                    uint32_t *srcPtr;
                    /* Work out the source row that contribute to the destination row pixel */
                    srcRow = (row * imageHeight) / outHeight;
                    CLAMP_VALUE(srcRow, (imageHeight-1))
                    destPtr = destRowPtr;
                    srcPtr = (uint32_t *)pDataBuffer + (imageWidth*srcRow);
                    for(col=0; col<destWidth; col++)
                    {
                        *(destPtr++) = (uint8_t)phStbImage_convertPixel(srcPtr[(imageWidth*col)/destWidth], mode, cmapFunction);
                    }
                    destRowPtr += bufferWidth;
                }
            }
            break;
        case(phStbImage_GfxMode_YUV) :
            {
                /* Go through each row to be written */
                for(row=0; row<destHeight; row++)
                {
                    int32_t srcRow;
                    uint32_t *srcPtr;
                    uint8_t *destUVPtr;
                    uint8_t *destPtr;
                    /* Work out the source row that contribute to the destination row pixel */
                    srcRow = (row * imageHeight) / outHeight;
                    CLAMP_VALUE(srcRow, (imageHeight-1))
                    srcPtr = (uint32_t *)pDataBuffer + (imageWidth*srcRow);
    
                    destPtr = (uint8_t *)pInBuffer + (bufferWidth*outOffsetY) + outOffsetX;
                    destUVPtr = (uint8_t *)pInBuffer2 + (bufferWidth*(outOffsetY/2)) + outOffsetX;
                    for(col=0; col<destWidth; col++)
                    {
                        uint8_t U;
                        uint8_t V;

                        phStbImage_yuvConvertPixel(srcPtr[(imageWidth*col)/destWidth], destPtr, &U, &V);
                        if (outOffsetY%2 == 0)
                        {
                            if (col%2 == 1) 
                            {
                                *destUVPtr = V;
                            }
                            else
                            {
                                *destUVPtr = U;
                            }
                            destUVPtr++;
                        }
                        destPtr++;
                    }
                    outOffsetY++;
                    srcPtr += imageWidth;
                }
            }
            break;
        default :
            break;
    }

}

void phStbImage_BitmapCopy( uint32_t *pDataBuffer, 
                            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,
                            phStbImage_GfxMode_t mode,
                            phStbImage_cmapFunction cmapFunction)
{
    uint32_t *srcPtr;
    int32_t row;
    int32_t min_width;
    int32_t min_height;
    /* 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((dbgBmp,DBG_DEFAULT,"Copying %d,%d to %d,%d\n", min_width, min_height, outOffsetX, outOffsetY));

    /* Set up the pointer to the read location in the bitmap */
    srcPtr = (uint32_t *)pDataBuffer;

    row = min_height;

    switch(mode)
    {
        case(phStbImage_GfxMode_ARGB8888) : 
            {
                uint32_t *destPtr;
                /* Set up the pointer to the write location in the bitmap */
                destPtr = (uint32_t *)pInBuffer + (bufferWidth*outOffsetY) + outOffsetX;
                /* Go through each row to be written */
                while(row)
                {
                    (void)memcpy(destPtr, srcPtr, (uint32_t)min_width*sizeof(uint32_t));
                    destPtr += bufferWidth;
                    srcPtr += imageWidth;
                    row--;
                }
            }
            break;
        case(phStbImage_GfxMode_RGB565) :
        case(phStbImage_GfxMode_ARGB1555) :
        case(phStbImage_GfxMode_ARGB4444) :
            {
                uint16_t *destRowPtr;
                /* Set up the pointer to the write location in the bitmap */
                destRowPtr = (uint16_t *)pInBuffer + (bufferWidth*outOffsetY) + outOffsetX;
                /* Go through each row to be written */
                while(row)
                {
                    uint16_t *destPtr;
                    int32_t col;

                    destPtr = destRowPtr;
                    for(col=0; col<min_width; col++)
                    {
                        *(destPtr++) = (uint16_t)phStbImage_convertPixel(srcPtr[col], mode, NULL);
                    }
                    destRowPtr += bufferWidth;
                    srcPtr += imageWidth;
                    row--;
                }
            }
            break;
        case(phStbImage_GfxMode_Clut256) :
            {
                uint8_t *destRowPtr;
                /* Set up the pointer to the write location in the bitmap */
                destRowPtr = (uint8_t *)pInBuffer + (bufferWidth*outOffsetY) + outOffsetX;
                /* Go through each row to be written */
                while(row)
                {
                    uint8_t *destPtr;
                    int32_t col;

                    destPtr = destRowPtr;
                    for(col=0; col<min_width; col++)
                    {
                        *(destPtr++) = (uint8_t)phStbImage_convertPixel(srcPtr[col], mode, cmapFunction);
                    }
                    destRowPtr += bufferWidth;
                    srcPtr += imageWidth;
                    row--;
                }
            }
            break;
        case(phStbImage_GfxMode_YUV) :
            {
                /* Go through each row to be written */
                while(row)
                {
                    uint8_t *destPtr;
                    uint8_t *destUVPtr;
                    int32_t col;
    
                    destPtr = (uint8_t *)pInBuffer + (bufferWidth*outOffsetY) + outOffsetX;
                    destUVPtr = (uint8_t *)pInBuffer2 + (bufferWidth*(outOffsetY/2)) + outOffsetX;
                    for(col=0; col<min_width; col++)
                    {
                        uint8_t U;
                        uint8_t V;

                        phStbImage_yuvConvertPixel(srcPtr[col], destPtr, &U, &V);
                        if (outOffsetY%2 == 0)
                        {
                            if (col%2 == 1) 
                            {
                                *destUVPtr = V;
                            }
                            else
                            {
                                *destUVPtr = U;
                            }
                            destUVPtr++;
                        }
                        destPtr++;
                    }
                    outOffsetY++;
                    srcPtr += imageWidth;
                    row--;
                }
            }
            break;
        default :
            break;
    }
}


