xref: /core/vcl/quartz/salbmp.cxx (revision 4977c89e3e965c7f0c61ee4cd7a094d365ce4a62)
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
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/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
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 .
18  */
19 
20 #include <sal/config.h>
21 #include <sal/log.hxx>
22 #include <osl/diagnose.h>
23 
24 #include <cstddef>
25 #include <limits>
26 
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/Scanline.hxx>
35 
36 #include <bitmap/bmpfast.hxx>
37 #include <quartz/cgutils.h>
38 #include <quartz/salbmp.h>
39 #include <quartz/utils.h>
40 #include <bitmap/ScanlineTools.hxx>
41 
42 #ifdef MACOSX
43 #include <osx/saldata.hxx>
44 #else
45 #include <ios/iosinst.hxx>
46 #endif
47 
QuartzSalBitmap()48 QuartzSalBitmap::QuartzSalBitmap()
49   : mxCachedImage( nullptr )
50   , mnBits(0)
51   , mnWidth(0)
52   , mnHeight(0)
53   , mnBytesPerRow(0)
54 {
55 }
56 
~QuartzSalBitmap()57 QuartzSalBitmap::~QuartzSalBitmap()
58 {
59     doDestroy();
60 }
61 
Create(const Size & rSize,vcl::PixelFormat ePixelFormat,const BitmapPalette & rBitmapPalette)62 bool QuartzSalBitmap::Create( const Size& rSize, vcl::PixelFormat ePixelFormat, const BitmapPalette& rBitmapPalette )
63 {
64     if (ePixelFormat == vcl::PixelFormat::INVALID)
65         return false;
66 
67     maPalette = rBitmapPalette;
68     mnBits = vcl::pixelFormatBitCount(ePixelFormat);
69     mnWidth = rSize.Width();
70     mnHeight = rSize.Height();
71     return AllocateUserData();
72 }
73 
Create(const SalBitmap & rSalBmp)74 bool QuartzSalBitmap::Create( const SalBitmap& rSalBmp )
75 {
76     vcl::PixelFormat ePixelFormat = vcl::bitDepthToPixelFormat(rSalBmp.GetBitCount());
77     return Create( rSalBmp, ePixelFormat);
78 }
79 
Create(const SalBitmap & rSalBmp,SalGraphics * pGraphics)80 bool QuartzSalBitmap::Create( const SalBitmap& rSalBmp, SalGraphics* pGraphics )
81 {
82     vcl::PixelFormat ePixelFormat = vcl::PixelFormat::INVALID;
83     if (pGraphics)
84         ePixelFormat = vcl::bitDepthToPixelFormat(pGraphics->GetBitCount());
85     else
86         ePixelFormat = vcl::bitDepthToPixelFormat(rSalBmp.GetBitCount());
87 
88     return Create( rSalBmp, ePixelFormat);
89 }
90 
Create(const SalBitmap & rSalBmp,vcl::PixelFormat eNewPixelFormat)91 bool QuartzSalBitmap::Create( const SalBitmap& rSalBmp, vcl::PixelFormat eNewPixelFormat )
92 {
93     const QuartzSalBitmap& rSourceBitmap = static_cast<const QuartzSalBitmap&>(rSalBmp);
94 
95     if (eNewPixelFormat != vcl::PixelFormat::INVALID && rSourceBitmap.m_pUserBuffer)
96     {
97         mnBits = vcl::pixelFormatBitCount(eNewPixelFormat);
98         mnWidth = rSourceBitmap.mnWidth;
99         mnHeight = rSourceBitmap.mnHeight;
100         maPalette = rSourceBitmap.maPalette;
101 
102         if( AllocateUserData() )
103         {
104             ConvertBitmapData( mnWidth, mnHeight, mnBits, mnBytesPerRow, maPalette,
105                                m_pUserBuffer.get(), rSourceBitmap.mnBits,
106                                rSourceBitmap.mnBytesPerRow, rSourceBitmap.maPalette,
107                                rSourceBitmap.m_pUserBuffer.get() );
108             return true;
109         }
110     }
111     return false;
112 }
113 
Create(const css::uno::Reference<css::rendering::XBitmapCanvas> &,Size &)114 bool QuartzSalBitmap::Create( const css::uno::Reference< css::rendering::XBitmapCanvas >& /*xBitmapCanvas*/,
115                               Size& /*rSize*/ )
116 {
117     return false;
118 }
119 
Destroy()120 void QuartzSalBitmap::Destroy()
121 {
122     doDestroy();
123 }
124 
doDestroy()125 void QuartzSalBitmap::doDestroy()
126 {
127     DestroyContext();
128     m_pUserBuffer.reset();
129 }
130 
DestroyContext()131 void QuartzSalBitmap::DestroyContext()
132 {
133     if( mxCachedImage )
134     {
135         CGImageRelease( mxCachedImage );
136         mxCachedImage = nullptr;
137     }
138 
139     if (maGraphicContext.isSet())
140     {
141         CGContextRelease(maGraphicContext.get());
142         maGraphicContext.set(nullptr);
143         m_pContextBuffer.reset();
144     }
145 }
146 
CreateContext()147 bool QuartzSalBitmap::CreateContext()
148 {
149     DestroyContext();
150 
151     // prepare graphics context
152     // convert image from user input if available
153     const bool bSkipConversion = !m_pUserBuffer;
154     if( bSkipConversion )
155         AllocateUserData();
156 
157     // default to RGBA color space
158     CGColorSpaceRef aCGColorSpace = GetSalData()->mxRGBSpace;
159     CGBitmapInfo aCGBmpInfo = kCGImageAlphaNoneSkipFirst;
160 
161     // convert data into something accepted by CGBitmapContextCreate()
162     size_t bitsPerComponent = 8;
163     sal_uInt32 nContextBytesPerRow = mnBytesPerRow;
164     if( mnBits == 32 )
165     {
166         // no conversion needed for truecolor
167         m_pContextBuffer = m_pUserBuffer;
168     }
169     else if( mnBits == 8 && maPalette.IsGreyPalette8Bit() )
170     {
171         // no conversion needed for grayscale
172         m_pContextBuffer = m_pUserBuffer;
173         aCGColorSpace = GetSalData()->mxGraySpace;
174         aCGBmpInfo = kCGImageAlphaNone;
175         bitsPerComponent = mnBits;
176     }
177     // TODO: is special handling for 1bit input buffers worth it?
178     else
179     {
180         // convert user data to 32 bit
181         nContextBytesPerRow = mnWidth << 2;
182         try
183         {
184             m_pContextBuffer = o3tl::make_shared_array<sal_uInt8>(mnHeight * nContextBytesPerRow);
185 
186             if( !bSkipConversion )
187             {
188                 ConvertBitmapData( mnWidth, mnHeight,
189                                    32, nContextBytesPerRow, maPalette, m_pContextBuffer.get(),
190                                    mnBits, mnBytesPerRow, maPalette, m_pUserBuffer.get() );
191             }
192         }
193         catch( const std::bad_alloc& )
194         {
195             maGraphicContext.set(nullptr);
196         }
197     }
198 
199     if (m_pContextBuffer)
200     {
201         maGraphicContext.set(CGBitmapContextCreate(m_pContextBuffer.get(), mnWidth, mnHeight,
202                                                    bitsPerComponent, nContextBytesPerRow,
203                                                    aCGColorSpace, aCGBmpInfo));
204     }
205 
206     if (!maGraphicContext.isSet())
207         m_pContextBuffer.reset();
208 
209     return maGraphicContext.isSet();
210 }
211 
AllocateUserData()212 bool QuartzSalBitmap::AllocateUserData()
213 {
214     Destroy();
215 
216     if( mnWidth && mnHeight )
217     {
218         mnBytesPerRow =  0;
219 
220         switch( mnBits )
221         {
222         case 1:     mnBytesPerRow = (mnWidth + 7) >> 3; break;
223         case 8:     mnBytesPerRow = mnWidth; break;
224         case 24:    mnBytesPerRow = (mnWidth << 1) + mnWidth; break;
225         case 32:    mnBytesPerRow = mnWidth << 2; break;
226         default:
227             assert(false && "vcl::QuartzSalBitmap::AllocateUserData(), illegal bitcount!");
228         }
229     }
230 
231     bool alloc = false;
232     if (mnBytesPerRow != 0 &&
233         mnBytesPerRow <= std::numeric_limits<sal_uInt32>::max() / mnHeight)
234     {
235         try
236         {
237             m_pUserBuffer = o3tl::make_shared_array<sal_uInt8>(mnBytesPerRow * mnHeight);
238             alloc = true;
239         }
240         catch (std::bad_alloc &) {}
241     }
242     if (!alloc)
243     {
244         SAL_WARN( "vcl.quartz", "bad_alloc: " << mnWidth << "x" << mnHeight << " (" << mnBytesPerRow * mnHeight << " bytes)");
245         m_pUserBuffer.reset();
246         mnBytesPerRow = 0;
247     }
248 
249     return bool(m_pUserBuffer);
250 }
251 
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)252 void QuartzSalBitmap::ConvertBitmapData( sal_uInt32 nWidth, sal_uInt32 nHeight,
253                                          sal_uInt16 nDestBits, sal_uInt32 nDestBytesPerRow,
254                                          const BitmapPalette& rDestPalette, sal_uInt8* pDestData,
255                                          sal_uInt16 nSrcBits, sal_uInt32 nSrcBytesPerRow,
256                                          const BitmapPalette& rSrcPalette, sal_uInt8* pSrcData )
257 
258 {
259     if( (nDestBytesPerRow == nSrcBytesPerRow) &&
260         (nDestBits == nSrcBits) && ((nSrcBits != 8) || (rDestPalette.operator==( rSrcPalette ))) )
261     {
262         // simple case, same format, so just copy
263         memcpy( pDestData, pSrcData, nHeight * nDestBytesPerRow );
264         return;
265     }
266 
267     // try accelerated conversion if possible
268     // TODO: are other truecolor conversions except BGR->ARGB worth it?
269     bool bConverted = false;
270     if( (nSrcBits == 24) && (nDestBits == 32) )
271     {
272         // TODO: extend bmpfast.cxx with a method that can be directly used here
273         BitmapBuffer aSrcBuf;
274         aSrcBuf.meFormat = ScanlineFormat::N24BitTcBgr;
275         aSrcBuf.mpBits = pSrcData;
276         aSrcBuf.mnBitCount = nSrcBits;
277         aSrcBuf.mnScanlineSize = nSrcBytesPerRow;
278         BitmapBuffer aDstBuf;
279         aDstBuf.meFormat = ScanlineFormat::N32BitTcArgb;
280         aDstBuf.mpBits = pDestData;
281         aDstBuf.mnBitCount = nDestBits;
282         aDstBuf.mnScanlineSize = nDestBytesPerRow;
283 
284         aSrcBuf.mnWidth = aDstBuf.mnWidth = nWidth;
285         aSrcBuf.mnHeight = aDstBuf.mnHeight = nHeight;
286 
287         SalTwoRect aTwoRects(0, 0, mnWidth, mnHeight, 0, 0, mnWidth, mnHeight);
288         bConverted = ::ImplFastBitmapConversion( aDstBuf, aSrcBuf, aTwoRects );
289     }
290 
291     if( !bConverted )
292     {
293         // TODO: this implementation is for clarity, not for speed
294 
295         auto pTarget = vcl::bitmap::getScanlineTransformer(nDestBits, rDestPalette);
296         auto pSource = vcl::bitmap::getScanlineTransformer(nSrcBits, rSrcPalette);
297 
298         if (pTarget && pSource)
299         {
300             sal_uInt32 nY = nHeight;
301             while( nY-- )
302             {
303                 pTarget->startLine(pDestData);
304                 pSource->startLine(pSrcData);
305 
306                 sal_uInt32 nX = nWidth;
307                 while( nX-- )
308                 {
309                     pTarget->writePixel(pSource->readPixel());
310                 }
311                 pSrcData += nSrcBytesPerRow;
312                 pDestData += nDestBytesPerRow;
313             }
314         }
315     }
316 }
317 
GetSize() const318 Size QuartzSalBitmap::GetSize() const
319 {
320     return Size( mnWidth, mnHeight );
321 }
322 
GetBitCount() const323 sal_uInt16 QuartzSalBitmap::GetBitCount() const
324 {
325     return mnBits;
326 }
327 
328 namespace {
329 
330 struct pal_entry
331 {
332     sal_uInt8 mnRed;
333     sal_uInt8 mnGreen;
334     sal_uInt8 mnBlue;
335 };
336 
337 }
338 
339 pal_entry const aImplSalSysPalEntryAry[ 16 ] =
340 {
341 {    0,    0,    0 },
342 {    0,    0, 0x80 },
343 {    0, 0x80,    0 },
344 {    0, 0x80, 0x80 },
345 { 0x80,    0,    0 },
346 { 0x80,    0, 0x80 },
347 { 0x80, 0x80,    0 },
348 { 0x80, 0x80, 0x80 },
349 { 0xC0, 0xC0, 0xC0 },
350 {    0,    0, 0xFF },
351 {    0, 0xFF,    0 },
352 {    0, 0xFF, 0xFF },
353 { 0xFF,    0,    0 },
354 { 0xFF,    0, 0xFF },
355 { 0xFF, 0xFF,    0 },
356 { 0xFF, 0xFF, 0xFF }
357 };
358 
GetDefaultPalette(int mnBits,bool bMonochrome)359 static const BitmapPalette& GetDefaultPalette( int mnBits, bool bMonochrome )
360 {
361     if( bMonochrome )
362         return Bitmap::GetGreyPalette( 1U << mnBits );
363 
364     // at this point we should provide some kind of default palette
365     // since all other platforms do so, too.
366     static bool bDefPalInit = false;
367     static BitmapPalette aDefPalette256;
368     static BitmapPalette aDefPalette2;
369     if( ! bDefPalInit )
370     {
371         bDefPalInit = true;
372         aDefPalette256.SetEntryCount( 256 );
373         aDefPalette2.SetEntryCount( 2 );
374 
375         // Standard colors
376         unsigned int i;
377         for( i = 0; i < 16; i++ )
378         {
379             aDefPalette256[i] = BitmapColor( aImplSalSysPalEntryAry[i].mnRed,
380                                              aImplSalSysPalEntryAry[i].mnGreen,
381                                              aImplSalSysPalEntryAry[i].mnBlue );
382         }
383 
384         aDefPalette2[0] = BitmapColor( 0, 0, 0 );
385         aDefPalette2[1] = BitmapColor( 0xff, 0xff, 0xff );
386 
387         // own palette (6/6/6)
388         const int DITHER_PAL_STEPS = 6;
389         const sal_uInt8 DITHER_PAL_DELTA = 51;
390         int nB, nG, nR;
391         sal_uInt8 nRed, nGreen, nBlue;
392         for( nB=0, nBlue=0; nB < DITHER_PAL_STEPS; nB++, nBlue += DITHER_PAL_DELTA )
393         {
394             for( nG=0, nGreen=0; nG < DITHER_PAL_STEPS; nG++, nGreen += DITHER_PAL_DELTA )
395             {
396                 for( nR=0, nRed=0; nR < DITHER_PAL_STEPS; nR++, nRed += DITHER_PAL_DELTA )
397                 {
398                     aDefPalette256[ i ] = BitmapColor( nRed, nGreen, nBlue );
399                     i++;
400                 }
401             }
402         }
403     }
404 
405     // now fill in appropriate palette
406     switch( mnBits )
407     {
408     case 1: return aDefPalette2;
409     case 8: return aDefPalette256;
410     default: break;
411     }
412 
413     const static BitmapPalette aEmptyPalette;
414     return aEmptyPalette;
415 }
416 
AcquireBuffer(BitmapAccessMode)417 BitmapBuffer* QuartzSalBitmap::AcquireBuffer( BitmapAccessMode /*nMode*/ )
418 {
419     // TODO: AllocateUserData();
420     if (!m_pUserBuffer)
421         return nullptr;
422 
423     BitmapBuffer* pBuffer = new BitmapBuffer;
424     pBuffer->mnWidth = mnWidth;
425     pBuffer->mnHeight = mnHeight;
426     pBuffer->maPalette = maPalette;
427     pBuffer->mnScanlineSize = mnBytesPerRow;
428     pBuffer->mpBits = m_pUserBuffer.get();
429     pBuffer->mnBitCount = mnBits;
430     switch( mnBits )
431     {
432         case 1:
433             pBuffer->meFormat = ScanlineFormat::N1BitMsbPal;
434             break;
435         case 8:
436             pBuffer->meFormat = ScanlineFormat::N8BitPal;
437             break;
438         case 24:
439             pBuffer->meFormat = ScanlineFormat::N24BitTcBgr;
440             break;
441         case 32:
442             pBuffer->meFormat = ScanlineFormat::N32BitTcArgb;
443             break;
444         default: assert(false);
445     }
446 
447     // some BitmapBuffer users depend on a complete palette
448     if( (mnBits <= 8) && !maPalette )
449         pBuffer->maPalette = GetDefaultPalette( mnBits, true );
450 
451     return pBuffer;
452 }
453 
ReleaseBuffer(BitmapBuffer * pBuffer,BitmapAccessMode nMode)454 void QuartzSalBitmap::ReleaseBuffer( BitmapBuffer* pBuffer, BitmapAccessMode nMode )
455 {
456     // invalidate graphic context if we have different data
457     if( nMode == BitmapAccessMode::Write )
458     {
459         maPalette = pBuffer->maPalette;
460         if (maGraphicContext.isSet())
461         {
462             DestroyContext();
463         }
464         InvalidateChecksum();
465     }
466 
467     delete pBuffer;
468 }
469 
CreateCroppedImage(int nX,int nY,int nNewWidth,int nNewHeight) const470 CGImageRef QuartzSalBitmap::CreateCroppedImage( int nX, int nY, int nNewWidth, int nNewHeight ) const
471 {
472     if( !mxCachedImage )
473     {
474         if (!maGraphicContext.isSet())
475         {
476             if( !const_cast<QuartzSalBitmap*>(this)->CreateContext() )
477             {
478                 return nullptr;
479             }
480         }
481         mxCachedImage = CGBitmapContextCreateImage(maGraphicContext.get());
482     }
483 
484     CGImageRef xCroppedImage = nullptr;
485     // short circuit if there is nothing to crop
486     if( !nX && !nY && (mnWidth == nNewWidth) && (mnHeight == nNewHeight) )
487     {
488           xCroppedImage = mxCachedImage;
489           CFRetain( xCroppedImage );
490     }
491     else
492     {
493         nY = mnHeight - (nY + nNewHeight); // adjust for y-mirrored context
494         const CGRect aCropRect = { { static_cast<CGFloat>(nX), static_cast<CGFloat>(nY) }, { static_cast<CGFloat>(nNewWidth), static_cast<CGFloat>(nNewHeight) } };
495         xCroppedImage = CGImageCreateWithImageInRect( mxCachedImage, aCropRect );
496     }
497 
498     return xCroppedImage;
499 }
500 
CFRTLFree(void *,const void * data,size_t)501 static void CFRTLFree(void* /*info*/, const void* data, size_t /*size*/)
502 {
503     std::free( const_cast<void*>(data) );
504 }
505 
CreateWithMask(const SalBitmap & rMask,int nX,int nY,int nWidth,int nHeight) const506 CGImageRef QuartzSalBitmap::CreateWithMask( const SalBitmap& rMask,
507     int nX, int nY, int nWidth, int nHeight ) const
508 {
509     return CreateWithSalBitmapAndMask( *this, rMask, nX, nY, nWidth, nHeight );
510 }
511 
512 /** creates an image from the given rectangle, replacing all black pixels
513     with nMaskColor and make all other full transparent */
CreateColorMask(int nX,int nY,int nWidth,int nHeight,Color nMaskColor) const514 CGImageRef QuartzSalBitmap::CreateColorMask( int nX, int nY, int nWidth,
515                                              int nHeight, Color nMaskColor ) const
516 {
517     CGImageRef xMask = nullptr;
518     if (m_pUserBuffer && (nX + nWidth <= mnWidth) && (nY + nHeight <= mnHeight))
519     {
520         auto pSourcePixels = vcl::bitmap::getScanlineTransformer(mnBits, maPalette);
521         // Don't allocate destination buffer if there is no scanline transformer
522         if( !pSourcePixels )
523             return xMask;
524 
525         const sal_uInt32 nDestBytesPerRow = nWidth << 2;
526         std::unique_ptr<sal_uInt32[]> pMaskBuffer(new (std::nothrow) sal_uInt32[ nHeight * nDestBytesPerRow / 4] );
527         if( pMaskBuffer )
528         {
529             sal_uInt32 nColor;
530             reinterpret_cast<sal_uInt8*>(&nColor)[0] = 0xff;
531             reinterpret_cast<sal_uInt8*>(&nColor)[1] = nMaskColor.GetRed();
532             reinterpret_cast<sal_uInt8*>(&nColor)[2] = nMaskColor.GetGreen();
533             reinterpret_cast<sal_uInt8*>(&nColor)[3] = nMaskColor.GetBlue();
534 
535             sal_uInt8* pSource = m_pUserBuffer.get();
536             sal_uInt32* pDest = pMaskBuffer.get();
537             // First to nY on y-axis, as that is our starting point (sub-image)
538             if( nY )
539                 pSource += nY * mnBytesPerRow;
540 
541             int y = nHeight;
542             while( y-- )
543             {
544                 pSourcePixels->startLine( pSource );
545                 pSourcePixels->skipPixel(nX); // Skip on x axis to nX
546                 sal_uInt32 x = nWidth;
547                 while( x-- )
548                 {
549                     // Fix failure to generate the correct color mask
550                     // OutputDevice::ImplDrawRotateText() draws black text but
551                     // that will generate gray pixels due to antialiasing so
552                     // count dark gray the same as black, light gray the same
553                     // as white, and the rest as medium gray.
554                     // The results are not smooth since LibreOffice appears to
555                     // redraw these semi-transparent masks repeatedly without
556                     // clearing the background so the semi-transparent pixels
557                     // will grow darker with repeatedly redraws due to
558                     // cumulative blending. But it is now better than before.
559                     sal_uInt8 nAlpha = 255 - pSourcePixels->readPixel().GetRed();
560                     sal_uInt32 nPremultColor = nColor;
561                     if ( nAlpha < 192 )
562                     {
563                         if ( nAlpha < 64 )
564                         {
565                             nPremultColor = 0;
566                         }
567                         else
568                         {
569                             reinterpret_cast<sal_uInt8*>(&nPremultColor)[0] /= 2;
570                             reinterpret_cast<sal_uInt8*>(&nPremultColor)[1] /= 2;
571                             reinterpret_cast<sal_uInt8*>(&nPremultColor)[2] /= 2;
572                             reinterpret_cast<sal_uInt8*>(&nPremultColor)[3] /= 2;
573                         }
574                     }
575                     *pDest++ = nPremultColor;
576                 }
577                 pSource += mnBytesPerRow;
578             }
579 
580             CGDataProviderRef xDataProvider( CGDataProviderCreateWithData(nullptr, pMaskBuffer.release(), nHeight * nDestBytesPerRow, &CFRTLFree) );
581             xMask = CGImageCreate(nWidth, nHeight, 8, 32, nDestBytesPerRow, GetSalData()->mxRGBSpace, kCGImageAlphaPremultipliedFirst, xDataProvider, nullptr, true, kCGRenderingIntentDefault);
582             CFRelease(xDataProvider);
583         }
584     }
585     return xMask;
586 }
587 
588 /** QuartzSalBitmap::GetSystemData Get platform native image data from existing image
589  *
590  *  @param rData struct BitmapSystemData, defined in vcl/inc/bitmap.hxx
591  *  @return true if successful
592 **/
GetSystemData(BitmapSystemData & rData)593 bool QuartzSalBitmap::GetSystemData( BitmapSystemData& rData )
594 {
595     bool bRet = false;
596 
597     if (!maGraphicContext.isSet())
598         CreateContext();
599 
600     if (maGraphicContext.isSet())
601     {
602         bRet = true;
603 
604         if ((CGBitmapContextGetBitsPerPixel(maGraphicContext.get()) == 32) &&
605             (CGBitmapContextGetBitmapInfo(maGraphicContext.get()) & kCGBitmapByteOrderMask) != kCGBitmapByteOrder32Host)
606         {
607             /**
608              * We need to hack things because VCL does not use kCGBitmapByteOrder32Host, while Cairo requires it.
609              *
610              * Not sure what the above comment means. We don't use Cairo on macOS or iOS.
611              *
612              * This whole if statement was originally (before 2011) inside #ifdef CAIRO. Did we use Cairo on Mac back then?
613              * Anyway, nowadays (since many years, I think) we don't, so should this if statement be dropped? Fun.
614              */
615 
616             CGImageRef xImage = CGBitmapContextCreateImage(maGraphicContext.get());
617 
618             // re-create the context with single change: include kCGBitmapByteOrder32Host flag.
619             CGContextHolder aGraphicContextNew(CGBitmapContextCreate(CGBitmapContextGetData(maGraphicContext.get()),
620                                                                      CGBitmapContextGetWidth(maGraphicContext.get()),
621                                                                      CGBitmapContextGetHeight(maGraphicContext.get()),
622                                                                      CGBitmapContextGetBitsPerComponent(maGraphicContext.get()),
623                                                                      CGBitmapContextGetBytesPerRow(maGraphicContext.get()),
624                                                                      CGBitmapContextGetColorSpace(maGraphicContext.get()),
625                                                                      CGBitmapContextGetBitmapInfo(maGraphicContext.get()) | kCGBitmapByteOrder32Host));
626             CFRelease(maGraphicContext.get());
627 
628             // Needs to be flipped
629             aGraphicContextNew.saveState();
630             CGContextTranslateCTM (aGraphicContextNew.get(), 0, CGBitmapContextGetHeight(aGraphicContextNew.get()));
631             CGContextScaleCTM (aGraphicContextNew.get(), 1.0, -1.0);
632 
633             CGContextDrawImage(aGraphicContextNew.get(), CGRectMake( 0, 0, CGImageGetWidth(xImage), CGImageGetHeight(xImage)), xImage);
634 
635             // Flip back
636             CGContextRestoreGState( aGraphicContextNew.get() );
637             CGImageRelease( xImage );
638             maGraphicContext = aGraphicContextNew;
639         }
640 
641         rData.mnWidth = mnWidth;
642         rData.mnHeight = mnHeight;
643     }
644 
645     return bRet;
646 }
647 
ScalingSupported() const648 bool QuartzSalBitmap::ScalingSupported() const
649 {
650     return false;
651 }
652 
Scale(const double &,const double &,BmpScaleFlag)653 bool QuartzSalBitmap::Scale( const double& /*rScaleX*/, const double& /*rScaleY*/, BmpScaleFlag /*nScaleFlag*/ )
654 {
655     return false;
656 }
657 
Replace(const Color &,const Color &,sal_uInt8)658 bool QuartzSalBitmap::Replace( const Color& /*rSearchColor*/, const Color& /*rReplaceColor*/, sal_uInt8 /*nTol*/ )
659 {
660     return false;
661 }
662 
663 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
664