blob: 349bff6fa801a2a7941de6811893a0553ddb2eb5 [file] [log] [blame]
/**************************************************************
*
* 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();
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 CGFloat* 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;
}