/**************************************************************
 * 
 * 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 <boost/bind.hpp>

#include "basebmp/scanlineformats.hxx"
#include "basebmp/color.hxx"

#include "basegfx/vector/b2ivector.hxx"

#include "tools/color.hxx"

#include "vcl/bitmap.hxx" // for BitmapSystemData
#include "vcl/salbtype.hxx"

#include "aqua/salbmp.h"
#include "aqua/salinst.h"

#include "bmpfast.hxx"

// =======================================================================

static bool isValidBitCount( sal_uInt16 nBitCount )
{
	return (nBitCount == 1) || (nBitCount == 4) || (nBitCount == 8) || (nBitCount == 16) || (nBitCount == 24) || (nBitCount == 32);
}

// =======================================================================

AquaSalBitmap::AquaSalBitmap()
: mxGraphicContext( NULL )
, mxCachedImage( NULL )
, mnBits(0)
, mnWidth(0)
, mnHeight(0)
, mnBytesPerRow(0)
{
}

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

AquaSalBitmap::~AquaSalBitmap()
{
	Destroy();
}

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

bool AquaSalBitmap::Create( CGLayerRef xLayer, int nBitmapBits,
	int nX, int nY, int nWidth, int nHeight, bool /*bMirrorVert*/ )
{
	DBG_ASSERT( xLayer, "AquaSalBitmap::Create() from non-layered context" );

	// sanitize input parameters
	if( nX < 0 )
		nWidth += nX, nX = 0;
	if( nY < 0 )
		nHeight += nY, nY = 0;
	const CGSize aLayerSize = CGLayerGetSize( xLayer );
	if( nWidth >= (int)aLayerSize.width - nX )
		nWidth = (int)aLayerSize.width - nX;
	if( nHeight >= (int)aLayerSize.height - nY )
		nHeight = (int)aLayerSize.height - nY;
	if( (nWidth < 0) || (nHeight < 0) )
		nWidth = nHeight = 0;

	// initialize properties
	mnWidth  = nWidth;
	mnHeight = nHeight;
	mnBits   = nBitmapBits ? nBitmapBits : 32;

	// initialize drawing context
	CreateContext();

	// copy layer content into the bitmap buffer
	const CGPoint aSrcPoint = CGPointMake( -nX, -nY);
	::CGContextDrawLayerAtPoint( mxGraphicContext, aSrcPoint, xLayer );
	return true;
}

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

bool AquaSalBitmap::Create( const Size& rSize, sal_uInt16 nBits, const BitmapPalette& rBitmapPalette )
{
	if( !isValidBitCount( nBits ) )
		return false;
	maPalette = rBitmapPalette;
	mnBits = nBits;
    mnWidth = rSize.Width();
	mnHeight = rSize.Height();
	return AllocateUserData();
}

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

bool AquaSalBitmap::Create( const SalBitmap& rSalBmp )
{
	return Create( rSalBmp, rSalBmp.GetBitCount() );
}

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

bool AquaSalBitmap::Create( const SalBitmap& rSalBmp, SalGraphics* pGraphics )
{
	return Create( rSalBmp, pGraphics ? pGraphics->GetBitCount() : rSalBmp.GetBitCount() );
}

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

bool AquaSalBitmap::Create( const SalBitmap& rSalBmp, sal_uInt16 nNewBitCount )
{
	const AquaSalBitmap& rSourceBitmap = static_cast<const AquaSalBitmap&>(rSalBmp);

	if( isValidBitCount( nNewBitCount ) && 	rSourceBitmap.maUserBuffer.get() )
	{
		mnBits = nNewBitCount;
		mnWidth = rSourceBitmap.mnWidth;
		mnHeight = rSourceBitmap.mnHeight;
		maPalette = rSourceBitmap.maPalette;

		if( AllocateUserData() )
		{
			ConvertBitmapData( mnWidth, mnHeight, mnBits, mnBytesPerRow, maPalette, maUserBuffer.get(), rSourceBitmap.mnBits, rSourceBitmap.mnBytesPerRow, rSourceBitmap.maPalette, rSourceBitmap.maUserBuffer.get() );
			return true;
		}
	}
	return false;
}

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

void AquaSalBitmap::Destroy()
{
	DestroyContext();
	maUserBuffer.reset();
}

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

void AquaSalBitmap::DestroyContext()
{
	CGImageRelease( mxCachedImage );
	mxCachedImage = NULL;

	if( mxGraphicContext )
	{
		CGContextRelease( mxGraphicContext );
		mxGraphicContext = NULL;
		maContextBuffer.reset();
	}
}

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

bool AquaSalBitmap::CreateContext()
{
	DestroyContext();

	// prepare graphics context
	// convert image from user input if available
	const bool bSkipConversion = !maUserBuffer;
	if( bSkipConversion )
		AllocateUserData();

	// default to RGBA color space
	CGColorSpaceRef aCGColorSpace = GetSalData()->mxRGBSpace;
    CGBitmapInfo aCGBmpInfo = kCGImageAlphaNoneSkipFirst;

    // convert data into something accepted by CGBitmapContextCreate()
	size_t bitsPerComponent = (mnBits == 16) ? 5 : 8;
	sal_uInt32 nContextBytesPerRow = mnBytesPerRow;
	if( (mnBits == 16) || (mnBits == 32) )
	{
		// no conversion needed for truecolor
		maContextBuffer = maUserBuffer;
	}
	else if( (mnBits == 8) && maPalette.IsGreyPalette() )
	{
		// no conversion needed for grayscale
		maContextBuffer = maUserBuffer;
		aCGColorSpace = GetSalData()->mxGraySpace;
		aCGBmpInfo = kCGImageAlphaNone;
		bitsPerComponent = mnBits;
	}
	// TODO: is special handling for 1bit input buffers worth it?
	else
	{
		// convert user data to 32 bit
		nContextBytesPerRow = mnWidth << 2;
        try
        {
            maContextBuffer.reset( new sal_uInt8[ mnHeight * nContextBytesPerRow ] );

            if( !bSkipConversion )
                ConvertBitmapData( mnWidth, mnHeight,
                               32, nContextBytesPerRow, maPalette, maContextBuffer.get(),
                               mnBits, mnBytesPerRow, maPalette, maUserBuffer.get() );
        }
        catch( std::bad_alloc )
        {
            mxGraphicContext = 0;
        }
	}

    if( maContextBuffer.get() )
    {
        mxGraphicContext = ::CGBitmapContextCreate( maContextBuffer.get(), mnWidth, mnHeight,
            bitsPerComponent, nContextBytesPerRow, aCGColorSpace, aCGBmpInfo );
    }

	if( !mxGraphicContext )
		maContextBuffer.reset();

	return mxGraphicContext != NULL;
}

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

bool AquaSalBitmap::AllocateUserData()
{
	Destroy();

	if( mnWidth && mnHeight )
	{
		mnBytesPerRow =  0;

		switch( mnBits )
		{
		case 1:		mnBytesPerRow = (mnWidth + 7) >> 3; break;
		case 4:		mnBytesPerRow = (mnWidth + 1) >> 1; break;
		case 8:		mnBytesPerRow = mnWidth; break;
		case 16:	mnBytesPerRow = mnWidth << 1; break;
		case 24:	mnBytesPerRow = (mnWidth << 1) + mnWidth; break;
		case 32:	mnBytesPerRow = mnWidth << 2; break;
		default:
			DBG_ERROR("vcl::AquaSalBitmap::AllocateUserData(), illegal bitcount!");
		}
	}
	
    try
    {
        if( mnBytesPerRow )
            maUserBuffer.reset( new sal_uInt8[mnBytesPerRow * mnHeight] );
    }
    catch( const std::bad_alloc& )
    {
        DBG_ERROR( "vcl::AquaSalBitmap::AllocateUserData: bad alloc" );
        maUserBuffer.reset( NULL );
        mnBytesPerRow = 0;
    }
	
	return maUserBuffer.get() != 0;
}

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

class ImplPixelFormat
{
protected:
	sal_uInt8* pData;
public:
	static ImplPixelFormat* GetFormat( sal_uInt16 nBits, const BitmapPalette& rPalette );
	
	virtual void StartLine( sal_uInt8* pLine ) { pData = pLine; }
	virtual void SkipPixel( sal_uInt32 nPixel ) = 0;
	virtual ColorData ReadPixel() = 0;
	virtual void WritePixel( ColorData nColor ) = 0;
};

class ImplPixelFormat32 : public ImplPixelFormat
// currently ARGB-format for 32bit depth
{
public:
	virtual void SkipPixel( sal_uInt32 nPixel )
	{
		pData += nPixel << 2;
	}
	virtual ColorData ReadPixel()
	{
		const ColorData c = RGB_COLORDATA( pData[1], pData[2], pData[3] );
		pData += 4;
		return c;
	}
	virtual void WritePixel( ColorData nColor )
	{
		*pData++ = 0;
		*pData++ = COLORDATA_RED( nColor );
		*pData++ = COLORDATA_GREEN( nColor );
		*pData++ = COLORDATA_BLUE( nColor );
	}
};

class ImplPixelFormat24 : public ImplPixelFormat
// currently BGR-format for 24bit depth
{
public:
	virtual void SkipPixel( sal_uInt32 nPixel )
	{
		pData += (nPixel << 1) + nPixel;
	}
	virtual ColorData ReadPixel()
	{
		const ColorData c = RGB_COLORDATA( pData[2], pData[1], pData[0] );
		pData += 3;
		return c;
	}
	virtual void WritePixel( ColorData nColor )
	{
		*pData++ = COLORDATA_BLUE( nColor );
		*pData++ = COLORDATA_GREEN( nColor );
		*pData++ = COLORDATA_RED( nColor );
	}
};

class ImplPixelFormat16 : public ImplPixelFormat
// currently R5G6B5-format for 16bit depth
{
protected:
	sal_uInt16* pData16;
public:
	
	virtual void StartLine( sal_uInt8* pLine )
	{
		pData16 = (sal_uInt16*)pLine;
	}
	virtual void SkipPixel( sal_uInt32 nPixel )
	{
		pData += nPixel;
	}
	virtual ColorData ReadPixel()
	{
		const ColorData c = RGB_COLORDATA( (*pData & 0x7c00) >> 7, (*pData & 0x03e0) >> 2 , (*pData & 0x001f) << 3 );
		pData++;
		return c;
	}
	virtual void WritePixel( ColorData nColor )
	{
		*pData++ =	((COLORDATA_RED( nColor ) & 0xf8 ) << 7 ) ||
					((COLORDATA_GREEN( nColor ) & 0xf8 ) << 2 ) ||
					((COLORDATA_BLUE( nColor ) & 0xf8 ) >> 3 );
	}
};

class ImplPixelFormat8 : public ImplPixelFormat
{
private:
	const BitmapPalette& mrPalette;

public:
	ImplPixelFormat8( const BitmapPalette& rPalette )
	: mrPalette( rPalette )
	{
	}
	virtual void SkipPixel( sal_uInt32 nPixel )
	{
		pData += nPixel;
	}
	virtual ColorData ReadPixel()
	{
		return mrPalette[ *pData++ ].operator Color().GetColor();
	}
	virtual void WritePixel( ColorData nColor )
	{
		const BitmapColor aColor( COLORDATA_RED( nColor ), COLORDATA_GREEN( nColor ), COLORDATA_BLUE( nColor ) );
		*pData++ = static_cast< sal_uInt8 >( mrPalette.GetBestIndex( aColor ) );
	}
};

class ImplPixelFormat4 : public ImplPixelFormat
{
private:
	const BitmapPalette& mrPalette;
	sal_uInt32 mnX;
	sal_uInt32 mnShift;

public:
	ImplPixelFormat4( const BitmapPalette& rPalette )
	: mrPalette( rPalette )
	{
	}
	virtual void SkipPixel( sal_uInt32 nPixel )
	{
		mnX += nPixel;
		if( (nPixel & 1) )
			mnShift ^= 4;
	}
	virtual void StartLine( sal_uInt8* pLine )
	{
		pData = pLine;
		mnX = 0;
		mnShift = 4;
	}
	virtual ColorData ReadPixel()
	{
		const BitmapColor& rColor = mrPalette[( pData[mnX >> 1] >> mnShift) & 0x0f]; 
		mnX++;
		mnShift ^= 4;
		return rColor.operator Color().GetColor();
	}
	virtual void WritePixel( ColorData nColor )
	{
		const BitmapColor aColor( COLORDATA_RED( nColor ), COLORDATA_GREEN( nColor ), COLORDATA_BLUE( nColor ) ); 
		pData[mnX>>1] &= (0xf0 >> mnShift);
		pData[mnX>>1] |= (static_cast< sal_uInt8 >( mrPalette.GetBestIndex( aColor ) ) & 0x0f);
		mnX++;
		mnShift ^= 4;
	}
};

class ImplPixelFormat1 : public ImplPixelFormat
{
private:
	const BitmapPalette& mrPalette;
	sal_uInt32 mnX;

public:
	ImplPixelFormat1( const BitmapPalette& rPalette )
	: mrPalette( rPalette )
	{
	}
	virtual void SkipPixel( sal_uInt32 nPixel )
	{
		mnX += nPixel;
	}
	virtual void StartLine( sal_uInt8* pLine )
	{
		pData = pLine;
		mnX = 0;
	}
	virtual ColorData ReadPixel()
	{
		const BitmapColor& rColor = mrPalette[ (pData[mnX >> 3 ] >> ( 7 - ( mnX & 7 ) )) & 1]; 
		mnX++;
		return rColor.operator Color().GetColor();
	}
	virtual void WritePixel( ColorData nColor )
	{
		const BitmapColor aColor( COLORDATA_RED( nColor ), COLORDATA_GREEN( nColor ), COLORDATA_BLUE( nColor ) ); 
		if( mrPalette.GetBestIndex( aColor ) & 1 )
			pData[ mnX >> 3 ] |= 1 << ( 7 - ( mnX & 7 ) );
		else
			pData[ mnX >> 3 ] &= ~( 1 << ( 7 - ( mnX & 7 ) ) );
		mnX++;
	}
};

ImplPixelFormat* ImplPixelFormat::GetFormat( sal_uInt16 nBits, const BitmapPalette& rPalette )
{
	switch( nBits )
	{
	case 1: return new ImplPixelFormat1( rPalette );
	case 4: return new ImplPixelFormat4( rPalette );
	case 8: return new ImplPixelFormat8( rPalette );
	case 16: return new ImplPixelFormat16;
	case 24: return new ImplPixelFormat24;
	case 32: return new ImplPixelFormat32;
	}
	
	return 0;
}

void AquaSalBitmap::ConvertBitmapData( sal_uInt32 nWidth, sal_uInt32 nHeight,
									   sal_uInt16 nDestBits, sal_uInt32 nDestBytesPerRow, const BitmapPalette& rDestPalette, sal_uInt8* pDestData,
									   sal_uInt16 nSrcBits, sal_uInt32 nSrcBytesPerRow, const BitmapPalette& rSrcPalette, sal_uInt8* pSrcData )

{
	if( (nDestBytesPerRow == nSrcBytesPerRow) && (nDestBits == nSrcBits) && ((nSrcBits != 8) || (rDestPalette.operator==( rSrcPalette ))) )
	{
		// simple case, same format, so just copy
		memcpy( pDestData, pSrcData, nHeight * nDestBytesPerRow );
		return;
	}

	// try accelerated conversion if possible
	// TODO: are other truecolor conversions except BGR->ARGB worth it?
	bool bConverted = false;
	if( (nSrcBits == 24) && (nDestBits == 32) )
	{
		// TODO: extend bmpfast.cxx with a method that can be directly used here
		BitmapBuffer aSrcBuf;
		aSrcBuf.mnFormat = BMP_FORMAT_24BIT_TC_BGR;
		aSrcBuf.mpBits = pSrcData;
		aSrcBuf.mnBitCount = nSrcBits;
		aSrcBuf.mnScanlineSize = nSrcBytesPerRow;
		BitmapBuffer aDstBuf;
		aDstBuf.mnFormat = BMP_FORMAT_32BIT_TC_ARGB;
		aDstBuf.mpBits = pDestData;
		aSrcBuf.mnBitCount = nDestBits;
		aDstBuf.mnScanlineSize = nDestBytesPerRow;

		aSrcBuf.mnWidth = aDstBuf.mnWidth = nWidth;
		aSrcBuf.mnHeight = aDstBuf.mnHeight = nHeight;

		SalTwoRect aTwoRects;
		aTwoRects.mnSrcX = aTwoRects.mnDestX = 0;
		aTwoRects.mnSrcY = aTwoRects.mnDestY = 0;
		aTwoRects.mnSrcWidth = aTwoRects.mnDestWidth = mnWidth;
		aTwoRects.mnSrcHeight = aTwoRects.mnDestHeight = mnHeight;
		bConverted = ::ImplFastBitmapConversion( aDstBuf, aSrcBuf, aTwoRects );
	}

	if( !bConverted )
	{
		// TODO: this implementation is for clarety, not for speed

		ImplPixelFormat* pD = ImplPixelFormat::GetFormat( nDestBits, rDestPalette );
		ImplPixelFormat* pS = ImplPixelFormat::GetFormat( nSrcBits, rSrcPalette );

		if( pD && pS )
		{
			sal_uInt32 nY = nHeight;
			while( nY-- )
			{
				pD->StartLine( pDestData );
				pS->StartLine( pSrcData );

				sal_uInt32 nX = nWidth;
				while( nX-- )
					pD->WritePixel( pS->ReadPixel() );
	
				pSrcData += nSrcBytesPerRow;
				pDestData += nDestBytesPerRow;
			}
		}
		delete pS;
		delete pD;
	}
}

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

Size AquaSalBitmap::GetSize() const
{
	return Size( mnWidth, mnHeight );
}

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

sal_uInt16 AquaSalBitmap::GetBitCount() const
{
	return mnBits;
}

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

static struct pal_entry
{
    sal_uInt8 mnRed;
    sal_uInt8 mnGreen;
    sal_uInt8 mnBlue;
}
const aImplSalSysPalEntryAry[ 16 ] =
{
{	 0,    0,	 0 },
{	 0,    0, 0x80 },
{	 0, 0x80,	 0 },
{	 0, 0x80, 0x80 },
{ 0x80,    0,	 0 },
{ 0x80,    0, 0x80 },
{ 0x80, 0x80,	 0 },
{ 0x80, 0x80, 0x80 },
{ 0xC0, 0xC0, 0xC0 },
{	 0,    0, 0xFF },
{	 0, 0xFF,	 0 },
{	 0, 0xFF, 0xFF },
{ 0xFF,    0,	 0 },
{ 0xFF,    0, 0xFF },
{ 0xFF, 0xFF,	 0 },
{ 0xFF, 0xFF, 0xFF }
};

const BitmapPalette& GetDefaultPalette( int mnBits, bool bMonochrome )
{
	if( bMonochrome )
		return Bitmap::GetGreyPalette( 1U << mnBits );

	// at this point we should provide some kind of default palette
	// since all other platforms do so, too.
	static bool bDefPalInit = false;
	static BitmapPalette aDefPalette256;
	static BitmapPalette aDefPalette16;
	static BitmapPalette aDefPalette2;
	if( ! bDefPalInit )
	{
		bDefPalInit = true;
		aDefPalette256.SetEntryCount( 256 );
		aDefPalette16.SetEntryCount( 16 );
		aDefPalette2.SetEntryCount( 2 );
	        
		// Standard colors
		unsigned int i;
		for( i = 0; i < 16; i++ )
		{
			aDefPalette16[i] =
			aDefPalette256[i] = BitmapColor( aImplSalSysPalEntryAry[i].mnRed,
			                                 aImplSalSysPalEntryAry[i].mnGreen,
			                                 aImplSalSysPalEntryAry[i].mnBlue );
	        }

		aDefPalette2[0] = BitmapColor( 0, 0, 0 );
		aDefPalette2[1] = BitmapColor( 0xff, 0xff, 0xff );

		// own palette (6/6/6)
		const int DITHER_PAL_STEPS = 6;
		const sal_uInt8 DITHER_PAL_DELTA = 51;
		int nB, nG, nR;
		sal_uInt8 nRed, nGreen, nBlue;
		for( nB=0, nBlue=0; nB < DITHER_PAL_STEPS; nB++, nBlue += DITHER_PAL_DELTA )
		{
			for( nG=0, nGreen=0; nG < DITHER_PAL_STEPS; nG++, nGreen += DITHER_PAL_DELTA )
			{
				for( nR=0, nRed=0; nR < DITHER_PAL_STEPS; nR++, nRed += DITHER_PAL_DELTA )
				{
					aDefPalette256[ i ] = BitmapColor( nRed, nGreen, nBlue );
					i++;
				}
			}
		}
	}

	// now fill in appropriate palette
	switch( mnBits )
	{
	case 1: return aDefPalette2;
	case 4: return aDefPalette16;
	case 8: return aDefPalette256;
	default: break;
	}

	const static BitmapPalette aEmptyPalette;
	return aEmptyPalette;
}

BitmapBuffer* AquaSalBitmap::AcquireBuffer( bool /*bReadOnly*/ )
{
	if( !maUserBuffer.get() )
//	|| maContextBuffer.get() && (maUserBuffer.get() != maContextBuffer.get()) )
	{
		fprintf(stderr,"ASB::Acq(%dx%d,d=%d)\n",mnWidth,mnHeight,mnBits);
		// TODO: AllocateUserData();
		return NULL;
	}

	BitmapBuffer* pBuffer = new BitmapBuffer;
	pBuffer->mnWidth = mnWidth;
	pBuffer->mnHeight = mnHeight;
	pBuffer->maPalette = maPalette;
	pBuffer->mnScanlineSize = mnBytesPerRow;
	pBuffer->mpBits = maUserBuffer.get();
	pBuffer->mnBitCount = mnBits;
	switch( mnBits )
	{
	case 1:		pBuffer->mnFormat = BMP_FORMAT_1BIT_MSB_PAL; break;
	case 4:		pBuffer->mnFormat = BMP_FORMAT_4BIT_MSN_PAL; break;
	case 8:		pBuffer->mnFormat = BMP_FORMAT_8BIT_PAL; break;
	case 16:	pBuffer->mnFormat = BMP_FORMAT_16BIT_TC_MSB_MASK;
				pBuffer->maColorMask  = ColorMask( k16BitRedColorMask, k16BitGreenColorMask, k16BitBlueColorMask );
				break;
	case 24:	pBuffer->mnFormat = BMP_FORMAT_24BIT_TC_BGR; break;
	case 32:	pBuffer->mnFormat = BMP_FORMAT_32BIT_TC_ARGB;
				pBuffer->maColorMask  = ColorMask( k32BitRedColorMask, k32BitGreenColorMask, k32BitBlueColorMask );
				break;
	}
	pBuffer->mnFormat |= BMP_FORMAT_BOTTOM_UP;

	// some BitmapBuffer users depend on a complete palette
    if( (mnBits <= 8) && !maPalette )
		pBuffer->maPalette = GetDefaultPalette( mnBits, true );

	return pBuffer;
}

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

void AquaSalBitmap::ReleaseBuffer( BitmapBuffer* pBuffer, bool bReadOnly )
{
	// invalidate graphic context if we have different data
	if( !bReadOnly )
	{
		maPalette = pBuffer->maPalette;
		if( mxGraphicContext )
			DestroyContext();
	}
	
	delete pBuffer;
}

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

CGImageRef AquaSalBitmap::CreateCroppedImage( int nX, int nY, int nNewWidth, int nNewHeight ) const
{
	if( !mxCachedImage )
	{
		if( !mxGraphicContext )
			if( !const_cast<AquaSalBitmap*>(this)->CreateContext() )
				return NULL;

		mxCachedImage = CGBitmapContextCreateImage( mxGraphicContext );
	}

	CGImageRef xCroppedImage = NULL;
    // short circuit if there is nothing to crop
	if( !nX && !nY && (mnWidth == nNewWidth) && (mnHeight == nNewHeight) )
	{
		  xCroppedImage = mxCachedImage;
		  CFRetain( xCroppedImage );
	}
	else
	{
		nY = mnHeight - (nY + nNewHeight); // adjust for y-mirrored context
		const CGRect aCropRect = CGRectMake( nX, nY, nNewWidth, nNewHeight);
		xCroppedImage = CGImageCreateWithImageInRect( mxCachedImage, aCropRect );
	}

	return xCroppedImage;
}

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

static void CFRTLFree(void* /*info*/, const void* data, size_t /*size*/)
{
    rtl_freeMemory( const_cast<void*>(data) );
}

CGImageRef AquaSalBitmap::CreateWithMask( const AquaSalBitmap& rMask,
	int nX, int nY, int nWidth, int nHeight ) const
{
    CGImageRef xImage( CreateCroppedImage( nX, nY, nWidth, nHeight ) );
    if( !xImage )
    	return NULL;

	CGImageRef xMask = rMask.CreateCroppedImage( nX, nY, nWidth, nHeight );
	if( !xMask )
		return xImage;

	// CGImageCreateWithMask() only likes masks or greyscale images => convert if needed
	// TODO: isolate in an extra method?
	if( !CGImageIsMask(xMask) || (CGImageGetColorSpace(xMask) != GetSalData()->mxGraySpace) )
	{
	    const CGRect xImageRect=CGRectMake( 0, 0, nWidth, nHeight );//the rect has no offset
	
	    // create the alpha mask image fitting our image
	    // TODO: is caching the full mask or the subimage mask worth it?
	    int nMaskBytesPerRow = ((nWidth + 3) & ~3);
	    void* pMaskMem = rtl_allocateMemory( nMaskBytesPerRow * nHeight );
	    CGContextRef xMaskContext = CGBitmapContextCreate( pMaskMem,
	    	nWidth, nHeight, 8, nMaskBytesPerRow, GetSalData()->mxGraySpace, kCGImageAlphaNone );
	    CGContextDrawImage( xMaskContext, xImageRect, xMask );
	    CFRelease( xMask );
		CGDataProviderRef xDataProvider( CGDataProviderCreateWithData( NULL,
		pMaskMem, nHeight * nMaskBytesPerRow, &CFRTLFree ) );
		static const float* pDecode = NULL;
		xMask = CGImageMaskCreate( nWidth, nHeight, 8, 8, nMaskBytesPerRow, xDataProvider, pDecode, false );
		CFRelease( xDataProvider );
		CFRelease( xMaskContext );
	}    
	
	if( !xMask )
		return xImage;

    // combine image and alpha mask
    CGImageRef xMaskedImage = CGImageCreateWithMask( xImage, xMask );                        
    CFRelease( xMask );
	CFRelease( xImage );
    return xMaskedImage;
}

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

/** creates an image from the given rectangle, replacing all black pixels with nMaskColor and make all other full transparent */
CGImageRef AquaSalBitmap::CreateColorMask( int nX, int nY, int nWidth, int nHeight, SalColor nMaskColor ) const
{
	CGImageRef xMask = 0;
	if( maUserBuffer.get() && (nX + nWidth <= mnWidth) && (nY + nHeight <= mnHeight) )
	{
		const sal_uInt32 nDestBytesPerRow = nWidth << 2;
		sal_uInt32* pMaskBuffer = static_cast<sal_uInt32*>( rtl_allocateMemory( nHeight * nDestBytesPerRow ) );
		sal_uInt32* pDest = pMaskBuffer;

		ImplPixelFormat* pSourcePixels = ImplPixelFormat::GetFormat( mnBits, maPalette );

		if( pMaskBuffer && pSourcePixels )
		{
			sal_uInt32 nColor;
			reinterpret_cast<sal_uInt8*>(&nColor)[0] = 0xff;
			reinterpret_cast<sal_uInt8*>(&nColor)[1] = SALCOLOR_RED( nMaskColor );
			reinterpret_cast<sal_uInt8*>(&nColor)[2] = SALCOLOR_GREEN( nMaskColor );
			reinterpret_cast<sal_uInt8*>(&nColor)[3] = SALCOLOR_BLUE( nMaskColor );

			sal_uInt8* pSource = maUserBuffer.get();
			if( nY )
				pSource += nY * mnBytesPerRow;
				
			int y = nHeight;
			while( y-- )
			{
				pSourcePixels->StartLine( pSource );
				pSourcePixels->SkipPixel(nX);
				sal_uInt32 x = nWidth;
				while( x-- )
				{
					*pDest++ = ( pSourcePixels->ReadPixel() == 0 ) ? nColor : 0;
				}
				pSource += mnBytesPerRow;
			}

			CGDataProviderRef xDataProvider( CGDataProviderCreateWithData(NULL, pMaskBuffer, nHeight * nDestBytesPerRow, &CFRTLFree) );
			xMask = CGImageCreate(nWidth, nHeight, 8, 32, nDestBytesPerRow, GetSalData()->mxRGBSpace, kCGImageAlphaPremultipliedFirst, xDataProvider, NULL, true, kCGRenderingIntentDefault);
			CFRelease(xDataProvider);
		}
        else
        {
            free(pMaskBuffer);
        }

		delete pSourcePixels;
	}
	return xMask;
}

// =======================================================================

/** AquaSalBitmap::GetSystemData Get platform native image data from existing image
 *  
 *  @param rData struct BitmapSystemData, defined in vcl/inc/bitmap.hxx
 *  @return true if successful
**/
bool AquaSalBitmap::GetSystemData( BitmapSystemData& rData )
{
    bool bRet = false;

    if( !mxGraphicContext )
        CreateContext();
    
    if ( mxGraphicContext )
    {
        bRet = true;

#ifdef CAIRO
        if ((CGBitmapContextGetBitsPerPixel(mxGraphicContext) == 32) &&
            (CGBitmapContextGetBitmapInfo(mxGraphicContext) & kCGBitmapByteOrderMask) != kCGBitmapByteOrder32Host) {
            /**
             * We need to hack things because VCL does not use kCGBitmapByteOrder32Host, while Cairo requires it.
             */
            OSL_TRACE("AquaSalBitmap::%s(): kCGBitmapByteOrder32Host not found => inserting it.",__func__);
            
            CGImageRef xImage = CGBitmapContextCreateImage (mxGraphicContext);
            
            // re-create the context with single change: include kCGBitmapByteOrder32Host flag.
            CGContextRef mxGraphicContextNew = CGBitmapContextCreate( CGBitmapContextGetData(mxGraphicContext),
                                                                      CGBitmapContextGetWidth(mxGraphicContext),
                                                                      CGBitmapContextGetHeight(mxGraphicContext),
                                                                      CGBitmapContextGetBitsPerComponent(mxGraphicContext),
                                                                      CGBitmapContextGetBytesPerRow(mxGraphicContext),
                                                                      CGBitmapContextGetColorSpace(mxGraphicContext),
                                                                      CGBitmapContextGetBitmapInfo(mxGraphicContext) | kCGBitmapByteOrder32Host);
            CFRelease(mxGraphicContext);
            
            // Needs to be flipped
            CGContextSaveGState( mxGraphicContextNew );
            CGContextTranslateCTM (mxGraphicContextNew, 0, CGBitmapContextGetHeight(mxGraphicContextNew));
            CGContextScaleCTM (mxGraphicContextNew, 1.0, -1.0);
            
            CGContextDrawImage(mxGraphicContextNew, CGRectMake( 0, 0, CGImageGetWidth(xImage), CGImageGetHeight(xImage)), xImage);
            
            // Flip back
            CGContextRestoreGState( mxGraphicContextNew );
            
            CGImageRelease( xImage );
            mxGraphicContext = mxGraphicContextNew;
        } 
#endif

        rData.rImageContext = (void *) mxGraphicContext;
        rData.mnWidth = mnWidth;
        rData.mnHeight = mnHeight;
    }
    
    return bRet;
}