1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
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 // This file contains the macOS-specific versions of the functions which were touched in the commit
21 // to fix tdf#138122. The iOS-specific versions of these functions are kept (for now, when this
22 // comment is written) as they were before that commit in vcl/ios/salios.cxx.
24 #include <sal/config.h>
25 #include <sal/log.hxx>
26 #include <osl/diagnose.h>
28 #include <vcl/bitmap.hxx>
30 #include <quartz/salbmp.h>
31 #include <quartz/salgdi.h>
32 #include <quartz/salvd.h>
33 #include <quartz/utils.h>
35 #include <osx/saldata.hxx>
39 bool QuartzSalBitmap::Create(CGLayerHolder
const & rLayerHolder
, int nBitmapBits
, int nX
, int nY
, int nWidth
, int nHeight
, bool bFlipped
)
42 // TODO: Bitmaps from scaled layers are reverted to single precision. This is a workaround only unless bitmaps with precision of
43 // source layer are implemented.
45 SAL_WARN_IF(!rLayerHolder
.isSet(), "vcl", "QuartzSalBitmap::Create() from non-layered context");
47 // sanitize input parameters
58 CGSize aLayerSize
= CGLayerGetSize(rLayerHolder
.get());
59 const float fScale
= rLayerHolder
.getScale();
60 aLayerSize
.width
/= fScale
;
61 aLayerSize
.height
/= fScale
;
63 if( nWidth
>= static_cast<int>(aLayerSize
.width
) - nX
)
64 nWidth
= static_cast<int>(aLayerSize
.width
) - nX
;
66 if( nHeight
>= static_cast<int>(aLayerSize
.height
) - nY
)
67 nHeight
= static_cast<int>(aLayerSize
.height
) - nY
;
69 if( (nWidth
< 0) || (nHeight
< 0) )
72 // initialize properties
75 mnBits
= nBitmapBits
? nBitmapBits
: 32;
77 // initialize drawing context
80 // copy layer content into the bitmap buffer
81 const CGPoint aSrcPoint
= { static_cast<CGFloat
>(-nX
* fScale
), static_cast<CGFloat
>(-nY
* fScale
) };
82 if (maGraphicContext
.isSet())
86 CGContextTranslateCTM(maGraphicContext
.get(), 0, +mnHeight
);
87 CGContextScaleCTM(maGraphicContext
.get(), +1, -1);
89 maGraphicContext
.saveState();
90 CGContextScaleCTM(maGraphicContext
.get(), 1 / fScale
, 1 / fScale
);
91 CGContextDrawLayerAtPoint(maGraphicContext
.get(), aSrcPoint
, rLayerHolder
.get());
92 maGraphicContext
.restoreState();
97 // From salgdicommon.cxx
99 void AquaGraphicsBackend::copyBits(const SalTwoRect
&rPosAry
, SalGraphics
*pSrcGraphics
)
101 AquaSharedAttributes
* pSrcShared
= nullptr;
105 AquaSalGraphics
* pSrc
= static_cast<AquaSalGraphics
*>(pSrcGraphics
);
106 pSrcShared
= &pSrc
->getAquaGraphicsBackend()->mrShared
;
109 pSrcShared
= &mrShared
;
111 if (rPosAry
.mnSrcWidth
<= 0 || rPosAry
.mnSrcHeight
<= 0 || rPosAry
.mnDestWidth
<= 0 || rPosAry
.mnDestHeight
<= 0)
113 if (!mrShared
.maContextHolder
.isSet())
116 SAL_WARN_IF (!pSrcShared
->maLayer
.isSet(), "vcl.quartz", "AquaSalGraphics::copyBits() from non-layered graphics this=" << this);
118 // Layered graphics are copied by AquaSalGraphics::copyScaledArea() which is able to consider the layer's scaling.
120 if (pSrcShared
->maLayer
.isSet())
121 copyScaledArea(rPosAry
.mnDestX
, rPosAry
.mnDestY
, rPosAry
.mnSrcX
, rPosAry
.mnSrcY
,
122 rPosAry
.mnSrcWidth
, rPosAry
.mnSrcHeight
, pSrcShared
);
125 mrShared
.applyXorContext();
126 pSrcShared
->applyXorContext();
127 std::shared_ptr
<SalBitmap
> pBitmap
= pSrcGraphics
->GetImpl()->getBitmap(rPosAry
.mnSrcX
, rPosAry
.mnSrcY
,
128 rPosAry
.mnSrcWidth
, rPosAry
.mnSrcHeight
);
131 SalTwoRect
aPosAry(rPosAry
);
134 drawBitmap(aPosAry
, *pBitmap
);
139 void AquaGraphicsBackend::copyArea(tools::Long nDstX
, tools::Long nDstY
,tools::Long nSrcX
, tools::Long nSrcY
,
140 tools::Long nSrcWidth
, tools::Long nSrcHeight
, bool)
142 if (!mrShared
.maContextHolder
.isSet())
145 // Functionality is implemented in protected member function AquaSalGraphics::copyScaledArea() which requires an additional
146 // parameter of type SalGraphics to be used in AquaSalGraphics::copyBits() too.
148 copyScaledArea(nDstX
, nDstY
, nSrcX
, nSrcY
, nSrcWidth
, nSrcHeight
, &mrShared
);
151 void AquaGraphicsBackend::copyScaledArea(tools::Long nDstX
, tools::Long nDstY
,tools::Long nSrcX
, tools::Long nSrcY
,
152 tools::Long nSrcWidth
, tools::Long nSrcHeight
, AquaSharedAttributes
* pSrcShared
)
154 SAL_WARN_IF(!mrShared
.maLayer
.isSet(), "vcl.quartz",
155 "AquaSalGraphics::copyScaledArea() without graphics context or for non-layered graphics this=" << this);
157 if (!mrShared
.maContextHolder
.isSet() || !mrShared
.maLayer
.isSet())
160 // Determine scaled geometry of source and target area assuming source and target area have the same scale
162 float fScale
= mrShared
.maLayer
.getScale();
163 CGFloat nScaledSourceX
= nSrcX
* fScale
;
164 CGFloat nScaledSourceY
= nSrcY
* fScale
;
165 CGFloat nScaledTargetX
= nDstX
* fScale
;
166 CGFloat nScaledTargetY
= nDstY
* fScale
;
167 CGFloat nScaledSourceWidth
= nSrcWidth
* fScale
;
168 CGFloat nScaledSourceHeight
= nSrcHeight
* fScale
;
170 // Apply XOR context and get copy context from current graphics context or XOR context
172 mrShared
.applyXorContext();
173 mrShared
.maContextHolder
.saveState();
174 CGContextRef xCopyContext
= mrShared
.maContextHolder
.get();
175 if (mrShared
.mpXorEmulation
&& mrShared
.mpXorEmulation
->IsEnabled())
176 xCopyContext
= mrShared
.mpXorEmulation
->GetTargetContext();
178 // Set scale matrix of copy context to consider layer scaling
180 CGContextScaleCTM(xCopyContext
, 1 / fScale
, 1 / fScale
);
182 // Creating an additional layer is required for drawing with the required scale and extent at the drawing destination
185 CGLayerHolder
aSourceLayerHolder(pSrcShared
->maLayer
);
186 const CGSize aSourceSize
= CGSizeMake(nScaledSourceWidth
, nScaledSourceHeight
);
187 aSourceLayerHolder
.set(CGLayerCreateWithContext(xCopyContext
, aSourceSize
, nullptr));
188 const CGContextRef xSourceContext
= CGLayerGetContext(aSourceLayerHolder
.get());
189 CGPoint aSrcPoint
= CGPointMake(-nScaledSourceX
, -nScaledSourceY
);
190 if (pSrcShared
->isFlipped())
192 CGContextTranslateCTM(xSourceContext
, 0, nScaledSourceHeight
);
193 CGContextScaleCTM(xSourceContext
, 1, -1);
194 aSrcPoint
.y
= nScaledSourceY
+ nScaledSourceHeight
- mrShared
.mnHeight
* fScale
;
196 CGContextSetBlendMode(xSourceContext
, kCGBlendModeCopy
);
197 CGContextDrawLayerAtPoint(xSourceContext
, aSrcPoint
, pSrcShared
->maLayer
.get());
199 // Copy source area from additional layer to target area
201 const CGRect aTargetRect
= CGRectMake(nScaledTargetX
, nScaledTargetY
, nScaledSourceWidth
, nScaledSourceHeight
);
202 CGContextSetBlendMode(xCopyContext
, kCGBlendModeCopy
);
203 CGContextDrawLayerInRect(xCopyContext
, aTargetRect
, aSourceLayerHolder
.get());
205 // Housekeeping on exit
207 mrShared
.maContextHolder
.restoreState();
208 if (aSourceLayerHolder
.get() != mrShared
.maLayer
.get())
209 CGLayerRelease(aSourceLayerHolder
.get());
211 mrShared
.refreshRect(nDstX
, nDstY
, nSrcWidth
, nSrcHeight
);
214 void AquaSalGraphics::SetVirDevGraphics(CGLayerHolder
const &rLayer
, CGContextRef xContext
, int nBitmapDepth
)
216 SAL_INFO("vcl.quartz", "SetVirDevGraphics() this=" << this << " layer=" << rLayer
.get() << " context=" << xContext
);
218 // Set member variables
221 maShared
.mbWindow
= false;
222 maShared
.mbPrinter
= false;
223 maShared
.mbVirDev
= true;
224 maShared
.maLayer
= rLayer
;
225 maShared
.mnBitmapDepth
= nBitmapDepth
;
227 // Get size and scale from layer if set else from bitmap and sal::aqua::getWindowScaling(), which is used to determine
228 // scaling for direct graphics output too
232 if (maShared
.maLayer
.isSet())
234 maShared
.maContextHolder
.set(CGLayerGetContext(maShared
.maLayer
.get()));
235 aSize
= CGLayerGetSize(maShared
.maLayer
.get());
236 fScale
= maShared
.maLayer
.getScale();
240 maShared
.maContextHolder
.set(xContext
);
243 aSize
.width
= CGBitmapContextGetWidth(xContext
);
244 aSize
.height
= CGBitmapContextGetHeight(xContext
);
245 fScale
= sal::aqua::getWindowScaling();
247 maShared
.mnWidth
= aSize
.width
/ fScale
;
248 maShared
.mnHeight
= aSize
.height
/ fScale
;
250 // Set color space for fill and stroke
252 CGColorSpaceRef aColorSpace
= GetSalData()->mxRGBSpace
;
253 CGContextSetFillColorSpace(maShared
.maContextHolder
.get(), aColorSpace
);
254 CGContextSetStrokeColorSpace(maShared
.maContextHolder
.get(), aColorSpace
);
256 // Apply scale matrix to virtual device graphics
258 CGContextScaleCTM(maShared
.maContextHolder
.get(), fScale
, fScale
);
260 // Apply XOR emulation if required
262 if (maShared
.mpXorEmulation
)
264 maShared
.mpXorEmulation
->SetTarget(maShared
.mnWidth
, maShared
.mnHeight
, maShared
.mnBitmapDepth
, maShared
.maContextHolder
.get(), maShared
.maLayer
.get());
265 if (maShared
.mpXorEmulation
->IsEnabled())
266 maShared
.maContextHolder
.set(maShared
.mpXorEmulation
->GetMaskContext());
269 // Housekeeping on exit
271 maShared
.maContextHolder
.saveState();
274 SAL_INFO("vcl.quartz", "SetVirDevGraphics() this=" << this <<
275 " (" << maShared
.mnWidth
<< "x" << maShared
.mnHeight
<< ") fScale=" << fScale
<< " mnBitmapDepth=" << maShared
.mnBitmapDepth
);
278 void XorEmulation::SetTarget(int nWidth
, int nHeight
, int nTargetDepth
, CGContextRef xTargetContext
, CGLayerRef xTargetLayer
)
280 SAL_INFO("vcl.quartz", "XorEmulation::SetTarget() this=" << this <<
281 " (" << nWidth
<< "x" << nHeight
<< ") depth=" << nTargetDepth
<<
282 " context=" << xTargetContext
<< " layer=" << xTargetLayer
);
284 // Prepare to replace old mask and temporary context
288 CGContextRelease(m_xMaskContext
);
289 delete[] m_pMaskBuffer
;
290 m_xMaskContext
= nullptr;
291 m_pMaskBuffer
= nullptr;
294 CGContextRelease(m_xTempContext
);
295 delete[] m_pTempBuffer
;
296 m_xTempContext
= nullptr;
297 m_pTempBuffer
= nullptr;
301 // Return early if there is nothing more to do
306 // Retarget drawing operations to the XOR mask
308 m_xTargetLayer
= xTargetLayer
;
309 m_xTargetContext
= xTargetContext
;
311 // Prepare creation of matching bitmaps
313 CGColorSpaceRef aCGColorSpace
= GetSalData()->mxRGBSpace
;
314 CGBitmapInfo aCGBmpInfo
= kCGImageAlphaNoneSkipFirst
;
315 int nBitDepth
= nTargetDepth
;
318 int nBytesPerRow
= 4;
319 const size_t nBitsPerComponent
= (nBitDepth
== 16) ? 5 : 8;
322 aCGColorSpace
= GetSalData()->mxGraySpace
;
323 aCGBmpInfo
= kCGImageAlphaNone
;
326 float fScale
= sal::aqua::getWindowScaling();
327 size_t nScaledWidth
= nWidth
* fScale
;
328 size_t nScaledHeight
= nHeight
* fScale
;
329 nBytesPerRow
*= nScaledWidth
;
330 m_nBufferLongs
= (nScaledHeight
* nBytesPerRow
+ sizeof(sal_uLong
) - 1) / sizeof(sal_uLong
);
332 // Create XOR mask context
334 m_pMaskBuffer
= new sal_uLong
[m_nBufferLongs
];
335 m_xMaskContext
= CGBitmapContextCreate(m_pMaskBuffer
, nScaledWidth
, nScaledHeight
,
336 nBitsPerComponent
, nBytesPerRow
, aCGColorSpace
, aCGBmpInfo
);
337 SAL_WARN_IF(!m_xMaskContext
, "vcl.quartz", "mask context creation failed");
339 // Reset XOR mask to black
341 memset(m_pMaskBuffer
, 0, m_nBufferLongs
* sizeof(sal_uLong
));
343 // Create bitmap context for manual XOR unless target context is a bitmap context
346 m_pTempBuffer
= static_cast<sal_uLong
*>(CGBitmapContextGetData(m_xTargetContext
));
349 m_pTempBuffer
= new sal_uLong
[m_nBufferLongs
];
350 m_xTempContext
= CGBitmapContextCreate(m_pTempBuffer
, nScaledWidth
, nScaledHeight
,
351 nBitsPerComponent
, nBytesPerRow
, aCGColorSpace
, aCGBmpInfo
);
352 SAL_WARN_IF(!m_xTempContext
, "vcl.quartz", "temp context creation failed");
355 // Initialize XOR mask context for drawing
357 CGContextSetFillColorSpace(m_xMaskContext
, aCGColorSpace
);
358 CGContextSetStrokeColorSpace(m_xMaskContext
, aCGColorSpace
);
359 CGContextSetShouldAntialias(m_xMaskContext
, false);
361 // Improve XOR emulation for monochrome contexts
363 if (aCGColorSpace
== GetSalData()->mxGraySpace
)
364 CGContextSetBlendMode(m_xMaskContext
, kCGBlendModeDifference
);
366 // Initialize XOR mask transformation matrix and apply scale matrix to consider layer scaling
368 const CGAffineTransform aCTM
= CGContextGetCTM(xTargetContext
);
369 CGContextConcatCTM(m_xMaskContext
, aCTM
);
372 CGContextConcatCTM( m_xTempContext
, aCTM
);
373 CGContextScaleCTM(m_xTempContext
, 1 / fScale
, 1 / fScale
);
375 CGContextSaveGState(m_xMaskContext
);
378 bool XorEmulation::UpdateTarget()
380 SAL_INFO("vcl.quartz", "XorEmulation::UpdateTarget() this=" << this);
385 // Update temporary bitmap buffer
389 SAL_WARN_IF(m_xTargetContext
== nullptr, "vcl.quartz", "Target layer is NULL");
390 CGContextDrawLayerAtPoint(m_xTempContext
, CGPointZero
, m_xTargetLayer
);
393 // XOR using XOR mask (sufficient for simple color manipulations as well as for complex XOR clipping used in metafiles)
395 const sal_uLong
*pSrc
= m_pMaskBuffer
;
396 sal_uLong
*pDst
= m_pTempBuffer
;
397 for (int i
= m_nBufferLongs
; --i
>= 0;)
398 *(pDst
++) ^= *(pSrc
++);
400 // Write back XOR results to target context
404 CGImageRef xXorImage
= CGBitmapContextCreateImage(m_xTempContext
);
405 size_t nWidth
= CGImageGetWidth(xXorImage
);
406 size_t nHeight
= CGImageGetHeight(xXorImage
);
408 // Set scale matrix of target context to consider layer scaling and update target context
409 // TODO: Update minimal change rectangle
411 const CGRect aFullRect
= CGRectMake(0, 0, nWidth
, nHeight
);
412 CGContextSaveGState(m_xTargetContext
);
413 float fScale
= sal::aqua::getWindowScaling();
414 CGContextScaleCTM(m_xTargetContext
, 1 / fScale
, 1 / fScale
);
415 CGContextDrawImage(m_xTargetContext
, aFullRect
, xXorImage
);
416 CGContextRestoreGState(m_xTargetContext
);
417 CGImageRelease(xXorImage
);
420 // Reset XOR mask to black again
421 // TODO: Not needed for last update
423 memset(m_pMaskBuffer
, 0, m_nBufferLongs
* sizeof(sal_uLong
));
425 // TODO: Return FALSE if target was not changed
432 void AquaSalVirtualDevice::Destroy()
434 SAL_INFO( "vcl.virdev", "AquaSalVirtualDevice::Destroy() this=" << this << " mbForeignContext=" << mbForeignContext
);
436 if (mbForeignContext
)
438 // Do not delete mxContext that we have received from outside VCL
439 maLayer
.set(nullptr);
447 mpGraphics
->SetVirDevGraphics(nullptr, nullptr);
449 CGLayerRelease(maLayer
.get());
450 maLayer
.set(nullptr);
453 if (maBitmapContext
.isSet())
455 CGContextRelease(maBitmapContext
.get());
456 maBitmapContext
.set(nullptr);
460 bool AquaSalVirtualDevice::SetSize(tools::Long nDX
, tools::Long nDY
)
462 SAL_INFO("vcl.virdev", "AquaSalVirtualDevice::SetSize() this=" << this <<
463 " (" << nDX
<< "x" << nDY
<< ") mbForeignContext=" << (mbForeignContext
? "YES" : "NO"));
465 // Do not delete/resize graphics context if it has been received from outside VCL
467 if (mbForeignContext
)
470 // Do not delete/resize graphics context if no change of geometry has been requested
475 fScale
= maLayer
.getScale();
476 const CGSize aSize
= CGLayerGetSize(maLayer
.get());
477 if ((nDX
== aSize
.width
/ fScale
) && (nDY
== aSize
.height
/ fScale
))
481 // Destroy graphics context if change of geometry has been requested
485 // Prepare new graphics context for initialization, use scaling independent of prior graphics context calculated by
486 // sal::aqua::getWindowScaling(), which is used to determine scaling for direct graphics output too
490 fScale
= sal::aqua::getWindowScaling();
491 CGColorSpaceRef aColorSpace
;
493 if (mnBitmapDepth
&& (mnBitmapDepth
< 16))
496 aColorSpace
= GetSalData()->mxGraySpace
;
497 nFlags
= kCGImageAlphaNone
;
502 aColorSpace
= GetSalData()->mxRGBSpace
;
504 nFlags
= uint32_t(kCGImageAlphaNoneSkipFirst
) | uint32_t(kCGBitmapByteOrder32Host
);
507 // Allocate buffer for virtual device graphics as bitmap context to store graphics with highest required (scaled) resolution
509 size_t nScaledWidth
= mnWidth
* fScale
;
510 size_t nScaledHeight
= mnHeight
* fScale
;
511 size_t nBytesPerRow
= mnBitmapDepth
* nScaledWidth
/ 8;
512 maBitmapContext
.set(CGBitmapContextCreate(nullptr, nScaledWidth
, nScaledHeight
, 8, nBytesPerRow
, aColorSpace
, nFlags
));
514 SAL_INFO("vcl.virdev", "AquaSalVirtualDevice::SetSize() this=" << this <<
515 " fScale=" << fScale
<< " mnBitmapDepth=" << mnBitmapDepth
);
517 CGSize aLayerSize
= { static_cast<CGFloat
>(nScaledWidth
), static_cast<CGFloat
>(nScaledHeight
) };
518 maLayer
.set(CGLayerCreateWithContext(maBitmapContext
.get(), aLayerSize
, nullptr));
519 maLayer
.setScale(fScale
);
520 mpGraphics
->SetVirDevGraphics(maLayer
, CGLayerGetContext(maLayer
.get()), mnBitmapDepth
);
522 SAL_WARN_IF(!maBitmapContext
.isSet(), "vcl.quartz", "No context");
524 return maLayer
.isSet();
527 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */