1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <sal/config.h>
21 #include <sal/log.hxx>
22 #include <osl/diagnose.h>
27 #include <o3tl/make_shared.hxx>
28 #include <tools/color.hxx>
29 #include <vcl/bitmap.hxx>
30 #include <vcl/BitmapAccessMode.hxx>
31 #include <vcl/BitmapBuffer.hxx>
32 #include <vcl/BitmapColor.hxx>
33 #include <vcl/BitmapPalette.hxx>
34 #include <vcl/ColorMask.hxx>
35 #include <vcl/Scanline.hxx>
37 #include <bitmap/bmpfast.hxx>
38 #include <quartz/cgutils.h>
39 #include <quartz/salbmp.h>
40 #include <quartz/utils.h>
41 #include <bitmap/ScanlineTools.hxx>
44 #include <osx/saldata.hxx>
46 #include <ios/iosinst.hxx>
49 const unsigned long k32BitRedColorMask
= 0x00ff0000;
50 const unsigned long k32BitGreenColorMask
= 0x0000ff00;
51 const unsigned long k32BitBlueColorMask
= 0x000000ff;
53 QuartzSalBitmap::QuartzSalBitmap()
54 : mxCachedImage( nullptr )
62 QuartzSalBitmap::~QuartzSalBitmap()
67 bool QuartzSalBitmap::Create( const Size
& rSize
, vcl::PixelFormat ePixelFormat
, const BitmapPalette
& rBitmapPalette
)
69 if (ePixelFormat
== vcl::PixelFormat::INVALID
)
72 maPalette
= rBitmapPalette
;
73 mnBits
= vcl::pixelFormatBitCount(ePixelFormat
);
74 mnWidth
= rSize
.Width();
75 mnHeight
= rSize
.Height();
76 return AllocateUserData();
79 bool QuartzSalBitmap::Create( const SalBitmap
& rSalBmp
)
81 vcl::PixelFormat ePixelFormat
= vcl::bitDepthToPixelFormat(rSalBmp
.GetBitCount());
82 return Create( rSalBmp
, ePixelFormat
);
85 bool QuartzSalBitmap::Create( const SalBitmap
& rSalBmp
, SalGraphics
* pGraphics
)
87 vcl::PixelFormat ePixelFormat
= vcl::PixelFormat::INVALID
;
89 ePixelFormat
= vcl::bitDepthToPixelFormat(pGraphics
->GetBitCount());
91 ePixelFormat
= vcl::bitDepthToPixelFormat(rSalBmp
.GetBitCount());
93 return Create( rSalBmp
, ePixelFormat
);
96 bool QuartzSalBitmap::Create( const SalBitmap
& rSalBmp
, vcl::PixelFormat eNewPixelFormat
)
98 const QuartzSalBitmap
& rSourceBitmap
= static_cast<const QuartzSalBitmap
&>(rSalBmp
);
100 if (eNewPixelFormat
!= vcl::PixelFormat::INVALID
&& rSourceBitmap
.m_pUserBuffer
)
102 mnBits
= vcl::pixelFormatBitCount(eNewPixelFormat
);
103 mnWidth
= rSourceBitmap
.mnWidth
;
104 mnHeight
= rSourceBitmap
.mnHeight
;
105 maPalette
= rSourceBitmap
.maPalette
;
107 if( AllocateUserData() )
109 ConvertBitmapData( mnWidth
, mnHeight
, mnBits
, mnBytesPerRow
, maPalette
,
110 m_pUserBuffer
.get(), rSourceBitmap
.mnBits
,
111 rSourceBitmap
.mnBytesPerRow
, rSourceBitmap
.maPalette
,
112 rSourceBitmap
.m_pUserBuffer
.get() );
119 bool QuartzSalBitmap::Create( const css::uno::Reference
< css::rendering::XBitmapCanvas
>& /*xBitmapCanvas*/,
120 Size
& /*rSize*/, bool /*bMask*/ )
125 void QuartzSalBitmap::Destroy()
130 void QuartzSalBitmap::doDestroy()
133 m_pUserBuffer
.reset();
136 void QuartzSalBitmap::DestroyContext()
140 CGImageRelease( mxCachedImage
);
141 mxCachedImage
= nullptr;
144 if (maGraphicContext
.isSet())
146 CGContextRelease(maGraphicContext
.get());
147 maGraphicContext
.set(nullptr);
148 m_pContextBuffer
.reset();
152 bool QuartzSalBitmap::CreateContext()
156 // prepare graphics context
157 // convert image from user input if available
158 const bool bSkipConversion
= !m_pUserBuffer
;
159 if( bSkipConversion
)
162 // default to RGBA color space
163 CGColorSpaceRef aCGColorSpace
= GetSalData()->mxRGBSpace
;
164 CGBitmapInfo aCGBmpInfo
= kCGImageAlphaNoneSkipFirst
;
166 // convert data into something accepted by CGBitmapContextCreate()
167 size_t bitsPerComponent
= 8;
168 sal_uInt32 nContextBytesPerRow
= mnBytesPerRow
;
171 // no conversion needed for truecolor
172 m_pContextBuffer
= m_pUserBuffer
;
174 else if( mnBits
== 8 && maPalette
.IsGreyPalette8Bit() )
176 // no conversion needed for grayscale
177 m_pContextBuffer
= m_pUserBuffer
;
178 aCGColorSpace
= GetSalData()->mxGraySpace
;
179 aCGBmpInfo
= kCGImageAlphaNone
;
180 bitsPerComponent
= mnBits
;
182 // TODO: is special handling for 1bit input buffers worth it?
185 // convert user data to 32 bit
186 nContextBytesPerRow
= mnWidth
<< 2;
189 m_pContextBuffer
= o3tl::make_shared_array
<sal_uInt8
>(mnHeight
* nContextBytesPerRow
);
191 if( !bSkipConversion
)
193 ConvertBitmapData( mnWidth
, mnHeight
,
194 32, nContextBytesPerRow
, maPalette
, m_pContextBuffer
.get(),
195 mnBits
, mnBytesPerRow
, maPalette
, m_pUserBuffer
.get() );
198 catch( const std::bad_alloc
& )
200 maGraphicContext
.set(nullptr);
204 if (m_pContextBuffer
)
206 maGraphicContext
.set(CGBitmapContextCreate(m_pContextBuffer
.get(), mnWidth
, mnHeight
,
207 bitsPerComponent
, nContextBytesPerRow
,
208 aCGColorSpace
, aCGBmpInfo
));
211 if (!maGraphicContext
.isSet())
212 m_pContextBuffer
.reset();
214 return maGraphicContext
.isSet();
217 bool QuartzSalBitmap::AllocateUserData()
221 if( mnWidth
&& mnHeight
)
227 case 1: mnBytesPerRow
= (mnWidth
+ 7) >> 3; break;
228 case 8: mnBytesPerRow
= mnWidth
; break;
229 case 24: mnBytesPerRow
= (mnWidth
<< 1) + mnWidth
; break;
230 case 32: mnBytesPerRow
= mnWidth
<< 2; break;
232 assert(false && "vcl::QuartzSalBitmap::AllocateUserData(), illegal bitcount!");
237 if (mnBytesPerRow
!= 0 &&
238 mnBytesPerRow
<= std::numeric_limits
<sal_uInt32
>::max() / mnHeight
)
242 m_pUserBuffer
= o3tl::make_shared_array
<sal_uInt8
>(mnBytesPerRow
* mnHeight
);
245 catch (std::bad_alloc
&) {}
249 SAL_WARN( "vcl.quartz", "bad_alloc: " << mnWidth
<< "x" << mnHeight
<< " (" << mnBytesPerRow
* mnHeight
<< " bytes)");
250 m_pUserBuffer
.reset();
254 return bool(m_pUserBuffer
);
257 void QuartzSalBitmap::ConvertBitmapData( sal_uInt32 nWidth
, sal_uInt32 nHeight
,
258 sal_uInt16 nDestBits
, sal_uInt32 nDestBytesPerRow
,
259 const BitmapPalette
& rDestPalette
, sal_uInt8
* pDestData
,
260 sal_uInt16 nSrcBits
, sal_uInt32 nSrcBytesPerRow
,
261 const BitmapPalette
& rSrcPalette
, sal_uInt8
* pSrcData
)
264 if( (nDestBytesPerRow
== nSrcBytesPerRow
) &&
265 (nDestBits
== nSrcBits
) && ((nSrcBits
!= 8) || (rDestPalette
.operator==( rSrcPalette
))) )
267 // simple case, same format, so just copy
268 memcpy( pDestData
, pSrcData
, nHeight
* nDestBytesPerRow
);
272 // try accelerated conversion if possible
273 // TODO: are other truecolor conversions except BGR->ARGB worth it?
274 bool bConverted
= false;
275 if( (nSrcBits
== 24) && (nDestBits
== 32) )
277 // TODO: extend bmpfast.cxx with a method that can be directly used here
278 BitmapBuffer aSrcBuf
;
279 aSrcBuf
.meFormat
= ScanlineFormat::N24BitTcBgr
;
280 aSrcBuf
.mpBits
= pSrcData
;
281 aSrcBuf
.mnBitCount
= nSrcBits
;
282 aSrcBuf
.mnScanlineSize
= nSrcBytesPerRow
;
283 BitmapBuffer aDstBuf
;
284 aDstBuf
.meFormat
= ScanlineFormat::N32BitTcArgb
;
285 aDstBuf
.mpBits
= pDestData
;
286 aDstBuf
.mnBitCount
= nDestBits
;
287 aDstBuf
.mnScanlineSize
= nDestBytesPerRow
;
289 aSrcBuf
.mnWidth
= aDstBuf
.mnWidth
= nWidth
;
290 aSrcBuf
.mnHeight
= aDstBuf
.mnHeight
= nHeight
;
292 SalTwoRect
aTwoRects(0, 0, mnWidth
, mnHeight
, 0, 0, mnWidth
, mnHeight
);
293 bConverted
= ::ImplFastBitmapConversion( aDstBuf
, aSrcBuf
, aTwoRects
);
298 // TODO: this implementation is for clarity, not for speed
300 auto pTarget
= vcl::bitmap::getScanlineTransformer(nDestBits
, rDestPalette
);
301 auto pSource
= vcl::bitmap::getScanlineTransformer(nSrcBits
, rSrcPalette
);
303 if (pTarget
&& pSource
)
305 sal_uInt32 nY
= nHeight
;
308 pTarget
->startLine(pDestData
);
309 pSource
->startLine(pSrcData
);
311 sal_uInt32 nX
= nWidth
;
314 pTarget
->writePixel(pSource
->readPixel());
316 pSrcData
+= nSrcBytesPerRow
;
317 pDestData
+= nDestBytesPerRow
;
323 Size
QuartzSalBitmap::GetSize() const
325 return Size( mnWidth
, mnHeight
);
328 sal_uInt16
QuartzSalBitmap::GetBitCount() const
344 pal_entry
const aImplSalSysPalEntryAry
[ 16 ] =
353 { 0x80, 0x80, 0x80 },
354 { 0xC0, 0xC0, 0xC0 },
364 static const BitmapPalette
& GetDefaultPalette( int mnBits
, bool bMonochrome
)
367 return Bitmap::GetGreyPalette( 1U << mnBits
);
369 // at this point we should provide some kind of default palette
370 // since all other platforms do so, too.
371 static bool bDefPalInit
= false;
372 static BitmapPalette aDefPalette256
;
373 static BitmapPalette aDefPalette2
;
377 aDefPalette256
.SetEntryCount( 256 );
378 aDefPalette2
.SetEntryCount( 2 );
382 for( i
= 0; i
< 16; i
++ )
384 aDefPalette256
[i
] = BitmapColor( aImplSalSysPalEntryAry
[i
].mnRed
,
385 aImplSalSysPalEntryAry
[i
].mnGreen
,
386 aImplSalSysPalEntryAry
[i
].mnBlue
);
389 aDefPalette2
[0] = BitmapColor( 0, 0, 0 );
390 aDefPalette2
[1] = BitmapColor( 0xff, 0xff, 0xff );
392 // own palette (6/6/6)
393 const int DITHER_PAL_STEPS
= 6;
394 const sal_uInt8 DITHER_PAL_DELTA
= 51;
396 sal_uInt8 nRed
, nGreen
, nBlue
;
397 for( nB
=0, nBlue
=0; nB
< DITHER_PAL_STEPS
; nB
++, nBlue
+= DITHER_PAL_DELTA
)
399 for( nG
=0, nGreen
=0; nG
< DITHER_PAL_STEPS
; nG
++, nGreen
+= DITHER_PAL_DELTA
)
401 for( nR
=0, nRed
=0; nR
< DITHER_PAL_STEPS
; nR
++, nRed
+= DITHER_PAL_DELTA
)
403 aDefPalette256
[ i
] = BitmapColor( nRed
, nGreen
, nBlue
);
410 // now fill in appropriate palette
413 case 1: return aDefPalette2
;
414 case 8: return aDefPalette256
;
418 const static BitmapPalette aEmptyPalette
;
419 return aEmptyPalette
;
422 BitmapBuffer
* QuartzSalBitmap::AcquireBuffer( BitmapAccessMode
/*nMode*/ )
424 // TODO: AllocateUserData();
428 BitmapBuffer
* pBuffer
= new BitmapBuffer
;
429 pBuffer
->mnWidth
= mnWidth
;
430 pBuffer
->mnHeight
= mnHeight
;
431 pBuffer
->maPalette
= maPalette
;
432 pBuffer
->mnScanlineSize
= mnBytesPerRow
;
433 pBuffer
->mpBits
= m_pUserBuffer
.get();
434 pBuffer
->mnBitCount
= mnBits
;
438 pBuffer
->meFormat
= ScanlineFormat::N1BitMsbPal
;
441 pBuffer
->meFormat
= ScanlineFormat::N8BitPal
;
444 pBuffer
->meFormat
= ScanlineFormat::N24BitTcBgr
;
448 pBuffer
->meFormat
= ScanlineFormat::N32BitTcArgb
;
449 ColorMaskElement
aRedMask(k32BitRedColorMask
);
450 aRedMask
.CalcMaskShift();
451 ColorMaskElement
aGreenMask(k32BitGreenColorMask
);
452 aGreenMask
.CalcMaskShift();
453 ColorMaskElement
aBlueMask(k32BitBlueColorMask
);
454 aBlueMask
.CalcMaskShift();
455 pBuffer
->maColorMask
= ColorMask(aRedMask
, aGreenMask
, aBlueMask
);
458 default: assert(false);
461 // some BitmapBuffer users depend on a complete palette
462 if( (mnBits
<= 8) && !maPalette
)
463 pBuffer
->maPalette
= GetDefaultPalette( mnBits
, true );
468 void QuartzSalBitmap::ReleaseBuffer( BitmapBuffer
* pBuffer
, BitmapAccessMode nMode
)
470 // invalidate graphic context if we have different data
471 if( nMode
== BitmapAccessMode::Write
)
473 maPalette
= pBuffer
->maPalette
;
474 if (maGraphicContext
.isSet())
478 InvalidateChecksum();
484 CGImageRef
QuartzSalBitmap::CreateCroppedImage( int nX
, int nY
, int nNewWidth
, int nNewHeight
) const
488 if (!maGraphicContext
.isSet())
490 if( !const_cast<QuartzSalBitmap
*>(this)->CreateContext() )
495 mxCachedImage
= CGBitmapContextCreateImage(maGraphicContext
.get());
498 CGImageRef xCroppedImage
= nullptr;
499 // short circuit if there is nothing to crop
500 if( !nX
&& !nY
&& (mnWidth
== nNewWidth
) && (mnHeight
== nNewHeight
) )
502 xCroppedImage
= mxCachedImage
;
503 CFRetain( xCroppedImage
);
507 nY
= mnHeight
- (nY
+ nNewHeight
); // adjust for y-mirrored context
508 const CGRect aCropRect
= { { static_cast<CGFloat
>(nX
), static_cast<CGFloat
>(nY
) }, { static_cast<CGFloat
>(nNewWidth
), static_cast<CGFloat
>(nNewHeight
) } };
509 xCroppedImage
= CGImageCreateWithImageInRect( mxCachedImage
, aCropRect
);
512 return xCroppedImage
;
515 static void CFRTLFree(void* /*info*/, const void* data
, size_t /*size*/)
517 std::free( const_cast<void*>(data
) );
520 CGImageRef
QuartzSalBitmap::CreateWithMask( const SalBitmap
& rMask
,
521 int nX
, int nY
, int nWidth
, int nHeight
) const
523 return CreateWithSalBitmapAndMask( *this, rMask
, nX
, nY
, nWidth
, nHeight
);
526 /** creates an image from the given rectangle, replacing all black pixels
527 with nMaskColor and make all other full transparent */
528 CGImageRef
QuartzSalBitmap::CreateColorMask( int nX
, int nY
, int nWidth
,
529 int nHeight
, Color nMaskColor
) const
531 CGImageRef xMask
= nullptr;
532 if (m_pUserBuffer
&& (nX
+ nWidth
<= mnWidth
) && (nY
+ nHeight
<= mnHeight
))
534 auto pSourcePixels
= vcl::bitmap::getScanlineTransformer(mnBits
, maPalette
);
535 // Don't allocate destination buffer if there is no scanline transformer
539 const sal_uInt32 nDestBytesPerRow
= nWidth
<< 2;
540 std::unique_ptr
<sal_uInt32
[]> pMaskBuffer(new (std::nothrow
) sal_uInt32
[ nHeight
* nDestBytesPerRow
/ 4] );
544 reinterpret_cast<sal_uInt8
*>(&nColor
)[0] = 0xff;
545 reinterpret_cast<sal_uInt8
*>(&nColor
)[1] = nMaskColor
.GetRed();
546 reinterpret_cast<sal_uInt8
*>(&nColor
)[2] = nMaskColor
.GetGreen();
547 reinterpret_cast<sal_uInt8
*>(&nColor
)[3] = nMaskColor
.GetBlue();
549 sal_uInt8
* pSource
= m_pUserBuffer
.get();
550 sal_uInt32
* pDest
= pMaskBuffer
.get();
551 // First to nY on y-axis, as that is our starting point (sub-image)
553 pSource
+= nY
* mnBytesPerRow
;
558 pSourcePixels
->startLine( pSource
);
559 pSourcePixels
->skipPixel(nX
); // Skip on x axis to nX
560 sal_uInt32 x
= nWidth
;
563 // Fix failure to generate the correct color mask
564 // OutputDevice::ImplDrawRotateText() draws black text but
565 // that will generate gray pixels due to antialiasing so
566 // count dark gray the same as black, light gray the same
567 // as white, and the rest as medium gray.
568 // The results are not smooth since LibreOffice appears to
569 // redraw these semi-transparent masks repeatedly without
570 // clearing the background so the semi-transparent pixels
571 // will grow darker with repeatedly redraws due to
572 // cumulative blending. But it is now better than before.
573 sal_uInt8 nAlpha
= 255 - pSourcePixels
->readPixel().GetRed();
574 sal_uInt32 nPremultColor
= nColor
;
583 reinterpret_cast<sal_uInt8
*>(&nPremultColor
)[0] /= 2;
584 reinterpret_cast<sal_uInt8
*>(&nPremultColor
)[1] /= 2;
585 reinterpret_cast<sal_uInt8
*>(&nPremultColor
)[2] /= 2;
586 reinterpret_cast<sal_uInt8
*>(&nPremultColor
)[3] /= 2;
589 *pDest
++ = nPremultColor
;
591 pSource
+= mnBytesPerRow
;
594 CGDataProviderRef
xDataProvider( CGDataProviderCreateWithData(nullptr, pMaskBuffer
.release(), nHeight
* nDestBytesPerRow
, &CFRTLFree
) );
595 xMask
= CGImageCreate(nWidth
, nHeight
, 8, 32, nDestBytesPerRow
, GetSalData()->mxRGBSpace
, kCGImageAlphaPremultipliedFirst
, xDataProvider
, nullptr, true, kCGRenderingIntentDefault
);
596 CFRelease(xDataProvider
);
602 /** QuartzSalBitmap::GetSystemData Get platform native image data from existing image
604 * @param rData struct BitmapSystemData, defined in vcl/inc/bitmap.hxx
605 * @return true if successful
607 bool QuartzSalBitmap::GetSystemData( BitmapSystemData
& rData
)
611 if (!maGraphicContext
.isSet())
614 if (maGraphicContext
.isSet())
618 if ((CGBitmapContextGetBitsPerPixel(maGraphicContext
.get()) == 32) &&
619 (CGBitmapContextGetBitmapInfo(maGraphicContext
.get()) & kCGBitmapByteOrderMask
) != kCGBitmapByteOrder32Host
)
622 * We need to hack things because VCL does not use kCGBitmapByteOrder32Host, while Cairo requires it.
624 * Not sure what the above comment means. We don't use Cairo on macOS or iOS.
626 * This whole if statement was originally (before 2011) inside #ifdef CAIRO. Did we use Cairo on Mac back then?
627 * Anyway, nowadays (since many years, I think) we don't, so should this if statement be dropped? Fun.
630 CGImageRef xImage
= CGBitmapContextCreateImage(maGraphicContext
.get());
632 // re-create the context with single change: include kCGBitmapByteOrder32Host flag.
633 CGContextHolder
aGraphicContextNew(CGBitmapContextCreate(CGBitmapContextGetData(maGraphicContext
.get()),
634 CGBitmapContextGetWidth(maGraphicContext
.get()),
635 CGBitmapContextGetHeight(maGraphicContext
.get()),
636 CGBitmapContextGetBitsPerComponent(maGraphicContext
.get()),
637 CGBitmapContextGetBytesPerRow(maGraphicContext
.get()),
638 CGBitmapContextGetColorSpace(maGraphicContext
.get()),
639 CGBitmapContextGetBitmapInfo(maGraphicContext
.get()) | kCGBitmapByteOrder32Host
));
640 CFRelease(maGraphicContext
.get());
642 // Needs to be flipped
643 aGraphicContextNew
.saveState();
644 CGContextTranslateCTM (aGraphicContextNew
.get(), 0, CGBitmapContextGetHeight(aGraphicContextNew
.get()));
645 CGContextScaleCTM (aGraphicContextNew
.get(), 1.0, -1.0);
647 CGContextDrawImage(aGraphicContextNew
.get(), CGRectMake( 0, 0, CGImageGetWidth(xImage
), CGImageGetHeight(xImage
)), xImage
);
650 CGContextRestoreGState( aGraphicContextNew
.get() );
651 CGImageRelease( xImage
);
652 maGraphicContext
= aGraphicContextNew
;
655 rData
.mnWidth
= mnWidth
;
656 rData
.mnHeight
= mnHeight
;
662 bool QuartzSalBitmap::ScalingSupported() const
667 bool QuartzSalBitmap::Scale( const double& /*rScaleX*/, const double& /*rScaleY*/, BmpScaleFlag
/*nScaleFlag*/ )
672 bool QuartzSalBitmap::Replace( const Color
& /*rSearchColor*/, const Color
& /*rReplaceColor*/, sal_uInt8
/*nTol*/ )
677 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */