bump product version to 7.6.3.2-android
[LibreOffice.git] / vcl / osx / salmacos.cxx
blob700b252cf4f3fcb0dffeb009636ec4e87851936a
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
2 /*
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>
37 // From salbmp.cxx
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
48 if( nX < 0 ) {
49 nWidth += nX;
50 nX = 0;
53 if( nY < 0 ) {
54 nHeight += nY;
55 nY = 0;
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) )
70 nWidth = nHeight = 0;
72 // initialize properties
73 mnWidth = nWidth;
74 mnHeight = nHeight;
75 mnBits = nBitmapBits ? nBitmapBits : 32;
77 // initialize drawing context
78 CreateContext();
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())
84 if( bFlipped )
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();
94 return true;
97 // From salgdicommon.cxx
99 void AquaGraphicsBackend::copyBits(const SalTwoRect &rPosAry, SalGraphics *pSrcGraphics)
101 AquaSharedAttributes* pSrcShared = nullptr;
103 if (pSrcGraphics)
105 AquaSalGraphics* pSrc = static_cast<AquaSalGraphics*>(pSrcGraphics);
106 pSrcShared = &pSrc->getAquaGraphicsBackend()->GetShared();
108 else
109 pSrcShared = &mrShared;
111 if (rPosAry.mnSrcWidth <= 0 || rPosAry.mnSrcHeight <= 0 || rPosAry.mnDestWidth <= 0 || rPosAry.mnDestHeight <= 0)
112 return;
113 if (!mrShared.maContextHolder.isSet())
114 return;
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);
123 else
125 mrShared.applyXorContext();
126 pSrcShared->applyXorContext();
127 std::shared_ptr<SalBitmap> pBitmap = pSrcGraphics->GetImpl()->getBitmap(rPosAry.mnSrcX, rPosAry.mnSrcY,
128 rPosAry.mnSrcWidth, rPosAry.mnSrcHeight);
129 if (pBitmap)
131 SalTwoRect aPosAry(rPosAry);
132 aPosAry.mnSrcX = 0;
133 aPosAry.mnSrcY = 0;
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())
143 return;
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())
158 return;
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
183 // thereafter.
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(SalVirtualDevice* pVirDev, CGLayerHolder const &rLayer, CGContextRef xContext, int nBitmapDepth)
216 SAL_INFO("vcl.quartz", "SetVirDevGraphics() this=" << this << " layer=" << rLayer.get() << " context=" << xContext);
218 // Set member variables
220 InvalidateContext();
221 maShared.mbWindow = false;
222 maShared.mbPrinter = false;
223 maShared.mbVirDev = true;
224 maShared.maLayer = rLayer;
225 maShared.mnBitmapDepth = nBitmapDepth;
227 mpBackend->UpdateGeometryProvider(pVirDev);
229 // Get size and scale from layer if set else from bitmap and sal::aqua::getWindowScaling(), which is used to determine
230 // scaling for direct graphics output too
232 CGSize aSize;
233 float fScale;
234 if (maShared.maLayer.isSet())
236 maShared.maContextHolder.set(CGLayerGetContext(maShared.maLayer.get()));
237 aSize = CGLayerGetSize(maShared.maLayer.get());
238 fScale = maShared.maLayer.getScale();
240 else
242 maShared.maContextHolder.set(xContext);
243 if (!xContext)
244 return;
245 aSize.width = CGBitmapContextGetWidth(xContext);
246 aSize.height = CGBitmapContextGetHeight(xContext);
247 fScale = sal::aqua::getWindowScaling();
249 maShared.mnWidth = aSize.width / fScale;
250 maShared.mnHeight = aSize.height / fScale;
252 // Set color space for fill and stroke
254 CGColorSpaceRef aColorSpace = GetSalData()->mxRGBSpace;
255 CGContextSetFillColorSpace(maShared.maContextHolder.get(), aColorSpace);
256 CGContextSetStrokeColorSpace(maShared.maContextHolder.get(), aColorSpace);
258 // Apply scale matrix to virtual device graphics
260 CGContextScaleCTM(maShared.maContextHolder.get(), fScale, fScale);
262 // Apply XOR emulation if required
264 if (maShared.mpXorEmulation)
266 maShared.mpXorEmulation->SetTarget(maShared.mnWidth, maShared.mnHeight, maShared.mnBitmapDepth, maShared.maContextHolder.get(), maShared.maLayer.get());
267 if (maShared.mpXorEmulation->IsEnabled())
268 maShared.maContextHolder.set(maShared.mpXorEmulation->GetMaskContext());
271 // Housekeeping on exit
273 maShared.maContextHolder.saveState();
274 maShared.setState();
276 SAL_INFO("vcl.quartz", "SetVirDevGraphics() this=" << this <<
277 " (" << maShared.mnWidth << "x" << maShared.mnHeight << ") fScale=" << fScale << " mnBitmapDepth=" << maShared.mnBitmapDepth);
280 void XorEmulation::SetTarget(int nWidth, int nHeight, int nTargetDepth, CGContextRef xTargetContext, CGLayerRef xTargetLayer)
282 SAL_INFO("vcl.quartz", "XorEmulation::SetTarget() this=" << this <<
283 " (" << nWidth << "x" << nHeight << ") depth=" << nTargetDepth <<
284 " context=" << xTargetContext << " layer=" << xTargetLayer);
286 // Prepare to replace old mask and temporary context
288 if (m_xMaskContext)
290 CGContextRelease(m_xMaskContext);
291 delete[] m_pMaskBuffer;
292 m_xMaskContext = nullptr;
293 m_pMaskBuffer = nullptr;
294 if (m_xTempContext)
296 CGContextRelease(m_xTempContext);
297 delete[] m_pTempBuffer;
298 m_xTempContext = nullptr;
299 m_pTempBuffer = nullptr;
303 // Return early if there is nothing more to do
305 if (!xTargetContext)
306 return;
308 // Retarget drawing operations to the XOR mask
310 m_xTargetLayer = xTargetLayer;
311 m_xTargetContext = xTargetContext;
313 // Prepare creation of matching bitmaps
315 CGColorSpaceRef aCGColorSpace = GetSalData()->mxRGBSpace;
316 CGBitmapInfo aCGBmpInfo = kCGImageAlphaNoneSkipFirst;
317 int nBitDepth = nTargetDepth;
318 if (!nBitDepth)
319 nBitDepth = 32;
320 int nBytesPerRow = 4;
321 const size_t nBitsPerComponent = (nBitDepth == 16) ? 5 : 8;
322 if (nBitDepth <= 8)
324 aCGColorSpace = GetSalData()->mxGraySpace;
325 aCGBmpInfo = kCGImageAlphaNone;
326 nBytesPerRow = 1;
328 float fScale = sal::aqua::getWindowScaling();
329 size_t nScaledWidth = nWidth * fScale;
330 size_t nScaledHeight = nHeight * fScale;
331 nBytesPerRow *= nScaledWidth;
332 m_nBufferLongs = (nScaledHeight * nBytesPerRow + sizeof(sal_uLong) - 1) / sizeof(sal_uLong);
334 // Create XOR mask context
336 m_pMaskBuffer = new sal_uLong[m_nBufferLongs];
337 m_xMaskContext = CGBitmapContextCreate(m_pMaskBuffer, nScaledWidth, nScaledHeight,
338 nBitsPerComponent, nBytesPerRow, aCGColorSpace, aCGBmpInfo);
339 SAL_WARN_IF(!m_xMaskContext, "vcl.quartz", "mask context creation failed");
341 // Reset XOR mask to black
343 memset(m_pMaskBuffer, 0, m_nBufferLongs * sizeof(sal_uLong));
345 // Create bitmap context for manual XOR unless target context is a bitmap context
347 if (nTargetDepth)
348 m_pTempBuffer = static_cast<sal_uLong*>(CGBitmapContextGetData(m_xTargetContext));
349 if (!m_pTempBuffer)
351 m_pTempBuffer = new sal_uLong[m_nBufferLongs];
352 m_xTempContext = CGBitmapContextCreate(m_pTempBuffer, nScaledWidth, nScaledHeight,
353 nBitsPerComponent, nBytesPerRow, aCGColorSpace, aCGBmpInfo);
354 SAL_WARN_IF(!m_xTempContext, "vcl.quartz", "temp context creation failed");
357 // Initialize XOR mask context for drawing
359 CGContextSetFillColorSpace(m_xMaskContext, aCGColorSpace);
360 CGContextSetStrokeColorSpace(m_xMaskContext, aCGColorSpace);
361 CGContextSetShouldAntialias(m_xMaskContext, false);
363 // Improve XOR emulation for monochrome contexts
365 if (aCGColorSpace == GetSalData()->mxGraySpace)
366 CGContextSetBlendMode(m_xMaskContext, kCGBlendModeDifference);
368 // Initialize XOR mask transformation matrix and apply scale matrix to consider layer scaling
370 const CGAffineTransform aCTM = CGContextGetCTM(xTargetContext);
371 CGContextConcatCTM(m_xMaskContext, aCTM);
372 if (m_xTempContext)
374 CGContextConcatCTM( m_xTempContext, aCTM );
375 CGContextScaleCTM(m_xTempContext, 1 / fScale, 1 / fScale);
377 CGContextSaveGState(m_xMaskContext);
380 bool XorEmulation::UpdateTarget()
382 SAL_INFO("vcl.quartz", "XorEmulation::UpdateTarget() this=" << this);
384 if (!IsEnabled())
385 return false;
387 // Update temporary bitmap buffer
389 if (m_xTempContext)
391 SAL_WARN_IF(m_xTargetContext == nullptr, "vcl.quartz", "Target layer is NULL");
392 CGContextDrawLayerAtPoint(m_xTempContext, CGPointZero, m_xTargetLayer);
395 // XOR using XOR mask (sufficient for simple color manipulations as well as for complex XOR clipping used in metafiles)
397 const sal_uLong *pSrc = m_pMaskBuffer;
398 sal_uLong *pDst = m_pTempBuffer;
399 for (int i = m_nBufferLongs; --i >= 0;)
400 *(pDst++) ^= *(pSrc++);
402 // Write back XOR results to target context
404 if (m_xTempContext)
406 CGImageRef xXorImage = CGBitmapContextCreateImage(m_xTempContext);
407 size_t nWidth = CGImageGetWidth(xXorImage);
408 size_t nHeight = CGImageGetHeight(xXorImage);
410 // Set scale matrix of target context to consider layer scaling and update target context
411 // TODO: Update minimal change rectangle
413 const CGRect aFullRect = CGRectMake(0, 0, nWidth, nHeight);
414 CGContextSaveGState(m_xTargetContext);
415 float fScale = sal::aqua::getWindowScaling();
416 CGContextScaleCTM(m_xTargetContext, 1 / fScale, 1 / fScale);
417 CGContextDrawImage(m_xTargetContext, aFullRect, xXorImage);
418 CGContextRestoreGState(m_xTargetContext);
419 CGImageRelease(xXorImage);
422 // Reset XOR mask to black again
423 // TODO: Not needed for last update
425 memset(m_pMaskBuffer, 0, m_nBufferLongs * sizeof(sal_uLong));
427 // TODO: Return FALSE if target was not changed
429 return true;
432 // From salvd.cxx
434 void AquaSalVirtualDevice::Destroy()
436 SAL_INFO( "vcl.virdev", "AquaSalVirtualDevice::Destroy() this=" << this << " mbForeignContext=" << mbForeignContext );
438 if (mbForeignContext)
440 // Do not delete mxContext that we have received from outside VCL
441 maLayer.set(nullptr);
442 return;
445 if (maLayer.isSet())
447 if( mpGraphics )
449 mpGraphics->SetVirDevGraphics(this, nullptr, nullptr);
451 CGLayerRelease(maLayer.get());
452 maLayer.set(nullptr);
455 if (maBitmapContext.isSet())
457 CGContextRelease(maBitmapContext.get());
458 maBitmapContext.set(nullptr);
462 bool AquaSalVirtualDevice::SetSize(tools::Long nDX, tools::Long nDY)
464 SAL_INFO("vcl.virdev", "AquaSalVirtualDevice::SetSize() this=" << this <<
465 " (" << nDX << "x" << nDY << ") mbForeignContext=" << (mbForeignContext ? "YES" : "NO"));
467 // Do not delete/resize graphics context if it has been received from outside VCL
469 if (mbForeignContext)
470 return true;
472 // Do not delete/resize graphics context if no change of geometry has been requested
474 float fScale;
475 if (maLayer.isSet())
477 fScale = maLayer.getScale();
478 const CGSize aSize = CGLayerGetSize(maLayer.get());
479 if ((nDX == aSize.width / fScale) && (nDY == aSize.height / fScale))
480 return true;
483 // Destroy graphics context if change of geometry has been requested
485 Destroy();
487 // Prepare new graphics context for initialization, use scaling independent of prior graphics context calculated by
488 // sal::aqua::getWindowScaling(), which is used to determine scaling for direct graphics output too
490 mnWidth = nDX;
491 mnHeight = nDY;
492 fScale = sal::aqua::getWindowScaling();
493 CGColorSpaceRef aColorSpace;
494 uint32_t nFlags;
495 if (mnBitmapDepth && (mnBitmapDepth < 16))
497 mnBitmapDepth = 8;
498 aColorSpace = GetSalData()->mxGraySpace;
499 nFlags = kCGImageAlphaNone;
501 else
503 mnBitmapDepth = 32;
504 aColorSpace = GetSalData()->mxRGBSpace;
506 nFlags = uint32_t(kCGImageAlphaNoneSkipFirst) | uint32_t(kCGBitmapByteOrder32Host);
509 // Allocate buffer for virtual device graphics as bitmap context to store graphics with highest required (scaled) resolution
511 size_t nScaledWidth = mnWidth * fScale;
512 size_t nScaledHeight = mnHeight * fScale;
513 size_t nBytesPerRow = mnBitmapDepth * nScaledWidth / 8;
514 maBitmapContext.set(CGBitmapContextCreate(nullptr, nScaledWidth, nScaledHeight, 8, nBytesPerRow, aColorSpace, nFlags));
516 SAL_INFO("vcl.virdev", "AquaSalVirtualDevice::SetSize() this=" << this <<
517 " fScale=" << fScale << " mnBitmapDepth=" << mnBitmapDepth);
519 CGSize aLayerSize = { static_cast<CGFloat>(nScaledWidth), static_cast<CGFloat>(nScaledHeight) };
520 maLayer.set(CGLayerCreateWithContext(maBitmapContext.get(), aLayerSize, nullptr));
521 maLayer.setScale(fScale);
522 mpGraphics->SetVirDevGraphics(this, maLayer, CGLayerGetContext(maLayer.get()), mnBitmapDepth);
524 SAL_WARN_IF(!maBitmapContext.isSet(), "vcl.quartz", "No context");
526 return maLayer.isSet();
529 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */