/**************************************************************
 * 
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 * 
 *************************************************************/



// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_vcl.hxx"
#include <rtl/memory.h>
#include <vcl/bmpacc.hxx>
#include <vcl/salbtype.hxx>
#include <bmpfast.hxx>

// -----------
// - Defines -
// -----------

#define IMPL_CASE_GET_FORMAT( Format )							\
case( BMP_FORMAT##Format ):									\
	pFncGetPixel = BitmapReadAccess::GetPixelFor##Format;		\
break

// -----------------------------------------------------------------------------

#define IMPL_CASE_SET_FORMAT( Format, BitCount )				\
case( BMP_FORMAT##Format ):									\
{																\
	pFncSetPixel = BitmapReadAccess::SetPixelFor##Format;		\
	pDstBuffer->mnBitCount = BitCount;							\
}																\
break

// -----------------------------------------------------------------------------

#define DOUBLE_SCANLINES()														\
while( ( nActY < nHeight1 ) && ( pMapY[ nActY + 1 ] == nMapY ) )				\
{																				\
	memcpy( pDstScanMap[ nActY + 1L ], pDstScan, rDstBuffer.mnScanlineSize );	\
	nActY++;																	\
}

// -----------
// - Inlines -
// -----------

#define TC_TO_PAL_COLORS	4096

static long ImplIndexFromColor( const BitmapColor& rCol )
{
#if TC_TO_PAL_COLORS == 4096

	return( ( ( (long) rCol.GetBlue() >> 4L) << 8L ) |
			( ( (long) rCol.GetGreen() >> 4L ) << 4L ) |
			( (long) rCol.GetRed() >> 4L ) );

#elif TC_TO_PAL_COLORS == 32768

	return( ( ( (long) rCol.GetBlue() >> 3L) << 10L ) |
			( ( (long) rCol.GetGreen() >> 3L ) << 5L ) |
			( (long) rCol.GetRed() >> 3L ) );

#endif
}


#define COLOR_TO_INDEX( _def_rCol ) 

// ------------------------
// - conversion functions -
// ------------------------

static void	ImplPALToPAL( const BitmapBuffer& rSrcBuffer, BitmapBuffer& rDstBuffer,
						  FncGetPixel pFncGetPixel, FncSetPixel pFncSetPixel,
						  Scanline* pSrcScanMap, Scanline* pDstScanMap, long* pMapX, long* pMapY )
{
	const long			nWidth = rDstBuffer.mnWidth, nHeight = rDstBuffer.mnHeight, nHeight1 = nHeight - 1;
	const ColorMask&	rSrcMask = rSrcBuffer.maColorMask;
	const ColorMask&	rDstMask = rDstBuffer.maColorMask;
	BitmapPalette		aColMap( rSrcBuffer.maPalette.GetEntryCount() );
	BitmapColor*		pColMapBuf = aColMap.ImplGetColorBuffer();
	BitmapColor			aIndex( 0 );

	for( sal_uInt16 i = 0, nSrcCount = aColMap.GetEntryCount(), nDstCount = rDstBuffer.maPalette.GetEntryCount(); i < nSrcCount; i++ )
	{
	    if( ( i < nDstCount ) && ( rSrcBuffer.maPalette[ i ] == rDstBuffer.maPalette[ i ] ) )
		    aIndex.SetIndex( sal::static_int_cast<sal_uInt8>(i) );
		else
			aIndex.SetIndex( sal::static_int_cast<sal_uInt8>(rDstBuffer.maPalette.GetBestIndex( rSrcBuffer.maPalette[ i ] )) );

		pColMapBuf[ i ] = aIndex;
	}

	for( long nActY = 0, nMapY; nActY < nHeight; nActY++ )
	{
		Scanline pSrcScan( pSrcScanMap[ nMapY = pMapY[ nActY ] ] ), pDstScan( pDstScanMap[ nActY ] );

		for( long nX = 0L; nX < nWidth; nX++ )
			pFncSetPixel( pDstScan, nX, pColMapBuf[ pFncGetPixel( pSrcScan, pMapX[ nX ], rSrcMask ).GetIndex() ], rDstMask );

		DOUBLE_SCANLINES();
	}
}

// -----------------------------------------------------------------------------

static void	ImplPALToTC( const BitmapBuffer& rSrcBuffer, BitmapBuffer& rDstBuffer,
						 FncGetPixel pFncGetPixel, FncSetPixel pFncSetPixel,
						 Scanline* pSrcScanMap, Scanline* pDstScanMap, long* pMapX, long* pMapY )
{
	const long			nWidth = rDstBuffer.mnWidth, nHeight = rDstBuffer.mnHeight, nHeight1 = nHeight - 1;
	const ColorMask&	rSrcMask = rSrcBuffer.maColorMask;
	const ColorMask&	rDstMask = rDstBuffer.maColorMask;
	const BitmapColor*	pColBuf = rSrcBuffer.maPalette.ImplGetColorBuffer();

	if( BMP_SCANLINE_FORMAT( rSrcBuffer.mnFormat ) == BMP_FORMAT_1BIT_MSB_PAL )
	{
		const BitmapColor	aCol0( pColBuf[ 0 ] );
		const BitmapColor	aCol1( pColBuf[ 1 ] );
		long				nMapX;

		for( long nActY = 0, nMapY; nActY < nHeight; nActY++ )
		{
			Scanline pSrcScan( pSrcScanMap[ nMapY = pMapY[ nActY ] ] ), pDstScan( pDstScanMap[ nActY ] );

			for( long nX = 0L; nX < nWidth; )
			{
				nMapX = pMapX[ nX ];
				pFncSetPixel( pDstScan, nX++, 
							  pSrcScan[ nMapX >> 3 ] & ( 1 << ( 7 - ( nMapX & 7 ) ) ) ? aCol1 : aCol0,
							  rDstMask );
			}

			DOUBLE_SCANLINES();
		}
	}
	else if( BMP_SCANLINE_FORMAT( rSrcBuffer.mnFormat ) == BMP_FORMAT_4BIT_MSN_PAL )
	{
		long nMapX;

		for( long nActY = 0, nMapY; nActY < nHeight; nActY++ )
		{
			Scanline pSrcScan( pSrcScanMap[ nMapY = pMapY[ nActY ] ] ), pDstScan( pDstScanMap[ nActY ] );

			for( long nX = 0L; nX < nWidth; )
			{
				nMapX = pMapX[ nX ];
				pFncSetPixel( pDstScan, nX++, 
							  pColBuf[ ( pSrcScan[ nMapX >> 1 ] >> ( nMapX & 1 ? 0 : 4 ) ) & 0x0f ],
							  rDstMask );
			}

			DOUBLE_SCANLINES();
		}
	}
	else if( BMP_SCANLINE_FORMAT( rSrcBuffer.mnFormat ) == BMP_FORMAT_8BIT_PAL )
	{
		for( long nActY = 0, nMapY; nActY < nHeight; nActY++ )
		{
			Scanline pSrcScan( pSrcScanMap[ nMapY = pMapY[ nActY ] ] ), pDstScan( pDstScanMap[ nActY ] );

			for( long nX = 0L; nX < nWidth; nX++ )
				pFncSetPixel( pDstScan, nX, pColBuf[ pSrcScan[ pMapX[ nX ] ] ], rDstMask );

			DOUBLE_SCANLINES();
		}
	}
	else
	{
		for( long nActY = 0, nMapY; nActY < nHeight; nActY++ )
		{
			Scanline pSrcScan( pSrcScanMap[ nMapY = pMapY[ nActY ] ] ), pDstScan( pDstScanMap[ nActY ] );

			for( long nX = 0L; nX < nWidth; nX++ )
				pFncSetPixel( pDstScan, nX, pColBuf[ pFncGetPixel( pSrcScan, pMapX[ nX ], rSrcMask ).GetIndex() ], rDstMask );

			DOUBLE_SCANLINES();
		}
	}
}

// -----------------------------------------------------------------------------

static void	ImplTCToTC( const BitmapBuffer& rSrcBuffer, BitmapBuffer& rDstBuffer,
						FncGetPixel pFncGetPixel, FncSetPixel pFncSetPixel,
						Scanline* pSrcScanMap, Scanline* pDstScanMap, long* pMapX, long* pMapY )
{
	const long			nWidth = rDstBuffer.mnWidth, nHeight = rDstBuffer.mnHeight, nHeight1 = nHeight - 1;
	const ColorMask&	rSrcMask = rSrcBuffer.maColorMask;
	const ColorMask&	rDstMask = rDstBuffer.maColorMask;

	if( BMP_SCANLINE_FORMAT( rSrcBuffer.mnFormat ) == BMP_FORMAT_24BIT_TC_BGR )
	{
		BitmapColor aCol;
		sal_uInt8*		pPixel;

		for( long nActY = 0, nMapY; nActY < nHeight; nActY++ )
		{
			Scanline pSrcScan( pSrcScanMap[ nMapY = pMapY[ nActY ] ] ), pDstScan( pDstScanMap[ nActY ] );

			for( long nX = 0L; nX < nWidth; nX++ )
			{
				aCol.SetBlue( *( pPixel = ( pSrcScan + pMapX[ nX ] * 3 ) )++ );
				aCol.SetGreen( *pPixel++ );
				aCol.SetRed( *pPixel );
				pFncSetPixel( pDstScan, nX, aCol, rDstMask );
			}

			DOUBLE_SCANLINES()
		}
	}
	else
	{
		for( long nActY = 0, nMapY; nActY < nHeight; nActY++ )
		{
			Scanline pSrcScan( pSrcScanMap[ nMapY = pMapY[ nActY ] ] ), pDstScan( pDstScanMap[ nActY ] );

			for( long nX = 0L; nX < nWidth; nX++ )
				pFncSetPixel( pDstScan, nX, pFncGetPixel( pSrcScan, pMapX[ nX ], rSrcMask ), rDstMask );

			DOUBLE_SCANLINES();
		}
	}
}

// -----------------------------------------------------------------------------

static void	ImplTCToPAL( const BitmapBuffer& rSrcBuffer, BitmapBuffer& rDstBuffer,
						 FncGetPixel pFncGetPixel, FncSetPixel pFncSetPixel,
						 Scanline* pSrcScanMap, Scanline* pDstScanMap, long* pMapX, long* pMapY )
{
	const long			nWidth = rDstBuffer.mnWidth, nHeight = rDstBuffer.mnHeight, nHeight1 = nHeight - 1;
	const ColorMask&	rSrcMask = rSrcBuffer.maColorMask;
	const ColorMask&	rDstMask = rDstBuffer.maColorMask;
	BitmapPalette		aColMap( rSrcBuffer.maPalette.GetEntryCount() );
	sal_uInt8*				pColToPalMap = new sal_uInt8[ TC_TO_PAL_COLORS ];
	BitmapColor			aIndex( 0 );

	for( long nR = 0; nR < 16; nR++ )
	{
		for( long nG = 0; nG < 16; nG++ )
		{
			for( long nB = 0; nB < 16; nB++ )
			{
				BitmapColor aCol( sal::static_int_cast<sal_uInt8>(nR << 4),
                                  sal::static_int_cast<sal_uInt8>(nG << 4),
                                  sal::static_int_cast<sal_uInt8>(nB << 4) );
				pColToPalMap[ ImplIndexFromColor( aCol ) ] = (sal_uInt8) rDstBuffer.maPalette.GetBestIndex( aCol );
			}
		}
	}

	for( long nActY = 0, nMapY; nActY < nHeight; nActY++ )
	{
		Scanline pSrcScan( pSrcScanMap[ nMapY = pMapY[ nActY ] ] ), pDstScan( pDstScanMap[ nActY ] );

		for( long nX = 0L; nX < nWidth; nX++ )
		{
			aIndex.SetIndex( pColToPalMap[ ImplIndexFromColor( pFncGetPixel( pSrcScan, pMapX[ nX ], rSrcMask ) ) ] );
			pFncSetPixel( pDstScan, nX, aIndex, rDstMask );
		}

		DOUBLE_SCANLINES();
	}

	delete[] pColToPalMap;
}

// -----------------------------------------------------------------------------

// ---------------------
// - StretchAndConvert -
// ---------------------

BitmapBuffer* StretchAndConvert( 
    const BitmapBuffer& rSrcBuffer, const SalTwoRect& rTwoRect, 
	sal_uLong nDstBitmapFormat, const BitmapPalette* pDstPal, const ColorMask* pDstMask )
{
    FncGetPixel		pFncGetPixel;
    FncSetPixel		pFncSetPixel;
	BitmapBuffer*	pDstBuffer = new BitmapBuffer;
    long            i;

	// set function for getting pixels
	switch( BMP_SCANLINE_FORMAT( rSrcBuffer.mnFormat ) )
	{
		IMPL_CASE_GET_FORMAT( _1BIT_MSB_PAL );
		IMPL_CASE_GET_FORMAT( _1BIT_LSB_PAL );
		IMPL_CASE_GET_FORMAT( _4BIT_MSN_PAL );
		IMPL_CASE_GET_FORMAT( _4BIT_LSN_PAL );
		IMPL_CASE_GET_FORMAT( _8BIT_PAL );
		IMPL_CASE_GET_FORMAT( _8BIT_TC_MASK );
		IMPL_CASE_GET_FORMAT( _16BIT_TC_MSB_MASK );
		IMPL_CASE_GET_FORMAT( _16BIT_TC_LSB_MASK );
		IMPL_CASE_GET_FORMAT( _24BIT_TC_BGR );
		IMPL_CASE_GET_FORMAT( _24BIT_TC_RGB );
		IMPL_CASE_GET_FORMAT( _24BIT_TC_MASK );
		IMPL_CASE_GET_FORMAT( _32BIT_TC_ABGR );
		IMPL_CASE_GET_FORMAT( _32BIT_TC_ARGB );
		IMPL_CASE_GET_FORMAT( _32BIT_TC_BGRA );
		IMPL_CASE_GET_FORMAT( _32BIT_TC_RGBA );
		IMPL_CASE_GET_FORMAT( _32BIT_TC_MASK );

		default:
            // should never come here
            // initialize pFncGetPixel to something valid that is
            // least likely to crash
            pFncGetPixel = BitmapReadAccess::GetPixelFor_1BIT_MSB_PAL;
			DBG_ERROR( "unknown read format" );
		break;
	}

	// set function for setting pixels
    const sal_uLong nDstScanlineFormat = BMP_SCANLINE_FORMAT( nDstBitmapFormat );
	switch( nDstScanlineFormat )
	{
		IMPL_CASE_SET_FORMAT( _1BIT_MSB_PAL, 1 );
		IMPL_CASE_SET_FORMAT( _1BIT_LSB_PAL, 1 );
		IMPL_CASE_SET_FORMAT( _4BIT_MSN_PAL, 1 );
		IMPL_CASE_SET_FORMAT( _4BIT_LSN_PAL, 4 );
		IMPL_CASE_SET_FORMAT( _8BIT_PAL, 8 );
		IMPL_CASE_SET_FORMAT( _8BIT_TC_MASK, 8 );
		IMPL_CASE_SET_FORMAT( _16BIT_TC_MSB_MASK, 16 );
		IMPL_CASE_SET_FORMAT( _16BIT_TC_LSB_MASK, 16 );
		IMPL_CASE_SET_FORMAT( _24BIT_TC_BGR, 24 );
		IMPL_CASE_SET_FORMAT( _24BIT_TC_RGB, 24 );
		IMPL_CASE_SET_FORMAT( _24BIT_TC_MASK, 24 );
		IMPL_CASE_SET_FORMAT( _32BIT_TC_ABGR, 32 );
		IMPL_CASE_SET_FORMAT( _32BIT_TC_ARGB, 32 );
		IMPL_CASE_SET_FORMAT( _32BIT_TC_BGRA, 32 );
		IMPL_CASE_SET_FORMAT( _32BIT_TC_RGBA, 32 );
		IMPL_CASE_SET_FORMAT( _32BIT_TC_MASK, 32 );

		default:
            // should never come here
            // initialize pFncSetPixel to something valid that is
            // least likely to crash
            pFncSetPixel = BitmapReadAccess::SetPixelFor_1BIT_MSB_PAL;
            pDstBuffer->mnBitCount = 1;
			DBG_ERROR( "unknown write format" );
		break;
	}

	// fill destination buffer
	pDstBuffer->mnFormat = nDstBitmapFormat;
	pDstBuffer->mnWidth = rTwoRect.mnDestWidth;
	pDstBuffer->mnHeight = rTwoRect.mnDestHeight;
	pDstBuffer->mnScanlineSize = AlignedWidth4Bytes( pDstBuffer->mnBitCount * pDstBuffer->mnWidth );
    try
    {
        pDstBuffer->mpBits = new sal_uInt8[ pDstBuffer->mnScanlineSize * pDstBuffer->mnHeight ];
    }
    catch( const std::bad_alloc& )
    {
        // memory exception, clean up
        pDstBuffer->mpBits = NULL;
        delete pDstBuffer;
        return NULL;
    }

	// do we need a destination palette or color mask?
	if( ( nDstScanlineFormat == BMP_FORMAT_1BIT_MSB_PAL ) ||
		( nDstScanlineFormat == BMP_FORMAT_1BIT_LSB_PAL ) ||
		( nDstScanlineFormat == BMP_FORMAT_4BIT_MSN_PAL ) ||
		( nDstScanlineFormat == BMP_FORMAT_4BIT_LSN_PAL ) ||
		( nDstScanlineFormat == BMP_FORMAT_8BIT_PAL ) )
	{
		DBG_ASSERT( pDstPal, "destination buffer requires palette" );
		pDstBuffer->maPalette = *pDstPal;
	}
	else if( ( nDstScanlineFormat == BMP_FORMAT_8BIT_TC_MASK ) ||
			 ( nDstScanlineFormat == BMP_FORMAT_16BIT_TC_MSB_MASK ) ||
			 ( nDstScanlineFormat == BMP_FORMAT_16BIT_TC_LSB_MASK ) ||
			 ( nDstScanlineFormat == BMP_FORMAT_24BIT_TC_MASK ) ||
			 ( nDstScanlineFormat == BMP_FORMAT_32BIT_TC_MASK ) )
	{
		DBG_ASSERT( pDstMask, "destination buffer requires color mask" );
		pDstBuffer->maColorMask = *pDstMask;
	}

    // short circuit the most important conversions
    bool bFastConvert = ImplFastBitmapConversion( *pDstBuffer, rSrcBuffer, rTwoRect );
    if( bFastConvert )
        return pDstBuffer;
    
    const long      nSrcX = rTwoRect.mnSrcX, nSrcY = rTwoRect.mnSrcY;
    const long      nSrcDX = rTwoRect.mnSrcWidth, nSrcDY = rTwoRect.mnSrcHeight;
    const long      nDstDX = rTwoRect.mnDestWidth, nDstDY = rTwoRect.mnDestHeight;
    Scanline*       pSrcScan = NULL;
    Scanline*       pDstScan = NULL;
    long*           pMapX = NULL;
    long*           pMapY = NULL;
    long            nTmp, nOffset;

    try
    {
        pSrcScan = new Scanline[ rSrcBuffer.mnHeight ];
        pDstScan = new Scanline[ nDstDY ];
        pMapX = new long[ nDstDX ];
        pMapY = new long[ nDstDY ];
    }
    catch( const std::bad_alloc& )
    {
        // memory exception, clean up
        // remark: the buffer ptr causing the exception
        // is still NULL here
        delete[] pSrcScan;
        delete[] pDstScan;
        delete[] pMapX;
        delete[] pMapY;
        delete pDstBuffer;
        return NULL;
    }
    
    // horizontal mapping table
	if( nDstDX != nSrcDX )
	{
		const double fFactorX = ( nDstDX > 1 ) ? (double) ( nSrcDX - 1 ) / ( nDstDX - 1 ) : 0.0;

		for( i = 0L; i < nDstDX; i++ )
			pMapX[ i ] = nSrcX + FRound( i * fFactorX );
	}
	else
	{
		for( i = 0L, nTmp = nSrcX; i < nDstDX; i++ )
			pMapX[ i ] = nTmp++;
	}

	// vertical mapping table
	if( nDstDY != nSrcDY )
	{
		const double fFactorY = ( nDstDY > 1 ) ? (double) ( nSrcDY - 1 ) / ( nDstDY - 1 ) : 0.0;

		for( i = 0L; i < nDstDY; i++ )
			pMapY[ i ] = nSrcY + FRound( i * fFactorY );
	}
	else
	{
		for( i = 0L, nTmp = nSrcY; i < nDstDY; i++ )
			pMapY[ i ] = nTmp++;
	}

	// source scanline buffer
    Scanline pTmpScan;
	if( BMP_SCANLINE_ADJUSTMENT( rSrcBuffer.mnFormat ) == BMP_FORMAT_TOP_DOWN )
		pTmpScan = rSrcBuffer.mpBits, nOffset = rSrcBuffer.mnScanlineSize;
	else
	{
		pTmpScan = rSrcBuffer.mpBits + ( rSrcBuffer.mnHeight - 1 ) * rSrcBuffer.mnScanlineSize;
		nOffset = -rSrcBuffer.mnScanlineSize;
	}

	for( i = 0L; i < rSrcBuffer.mnHeight; i++, pTmpScan += nOffset )
		pSrcScan[ i ] = pTmpScan;

	// destination scanline buffer
	if( BMP_SCANLINE_ADJUSTMENT( pDstBuffer->mnFormat ) == BMP_FORMAT_TOP_DOWN )
		pTmpScan = pDstBuffer->mpBits, nOffset = pDstBuffer->mnScanlineSize;
	else
	{
		pTmpScan = pDstBuffer->mpBits + ( nDstDY - 1 ) * pDstBuffer->mnScanlineSize;
		nOffset = -pDstBuffer->mnScanlineSize;
	}

	for( i = 0L; i < nDstDY; i++, pTmpScan += nOffset )
		pDstScan[ i ] = pTmpScan;

	// do buffer scaling and conversion
	if( rSrcBuffer.mnBitCount <= 8 && pDstBuffer->mnBitCount <= 8 )
	{
		ImplPALToPAL( rSrcBuffer, *pDstBuffer, pFncGetPixel, pFncSetPixel, 
					  pSrcScan, pDstScan, pMapX, pMapY );
	}
	else if( rSrcBuffer.mnBitCount <= 8 && pDstBuffer->mnBitCount > 8 )
	{
		ImplPALToTC( rSrcBuffer, *pDstBuffer, pFncGetPixel, pFncSetPixel,
					 pSrcScan, pDstScan, pMapX, pMapY );
	}
	else if( rSrcBuffer.mnBitCount > 8 && pDstBuffer->mnBitCount > 8 )
	{
		ImplTCToTC( rSrcBuffer, *pDstBuffer, pFncGetPixel, pFncSetPixel,
					pSrcScan, pDstScan, pMapX, pMapY );
	}
	else
	{
		ImplTCToPAL( rSrcBuffer, *pDstBuffer, pFncGetPixel, pFncSetPixel,
					 pSrcScan, pDstScan, pMapX, pMapY );
	}

	// cleanup
	delete[] pSrcScan;
	delete[] pDstScan;
	delete[] pMapX;
	delete[] pMapY;

	return pDstBuffer;
}
