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 iOS-specific versions of the functions which were touched in the commit to
21 // fix tdf#138122. The functions are here (for now) as they were before that commit. The
22 // macOS-specific versions of these functions are in vcl/osx/salmacos.cxx.
24 #include <sal/config.h>
25 #include <sal/log.hxx>
26 #include <osl/diagnose.h>
28 #include <vcl/bitmap.hxx>
30 #include <ios/iosinst.hxx>
31 #include <quartz/salbmp.h>
32 #include <quartz/salgdi.h>
33 #include <quartz/salvd.h>
34 #include <quartz/utils.h>
40 bool QuartzSalBitmap::Create(CGLayerHolder
const & rLayerHolder
, int nBitmapBits
, int nX
, int nY
, int nWidth
, int nHeight
, bool bFlipped
)
42 SAL_WARN_IF(!rLayerHolder
.isSet(), "vcl", "QuartzSalBitmap::Create() from non-layered context");
44 // sanitize input parameters
55 const CGSize aLayerSize
= CGLayerGetSize(rLayerHolder
.get());
57 if( nWidth
>= static_cast<int>(aLayerSize
.width
) - nX
)
58 nWidth
= static_cast<int>(aLayerSize
.width
) - nX
;
60 if( nHeight
>= static_cast<int>(aLayerSize
.height
) - nY
)
61 nHeight
= static_cast<int>(aLayerSize
.height
) - nY
;
63 if( (nWidth
< 0) || (nHeight
< 0) )
66 // initialize properties
69 mnBits
= nBitmapBits
? nBitmapBits
: 32;
71 // initialize drawing context
74 // copy layer content into the bitmap buffer
75 const CGPoint aSrcPoint
= { static_cast<CGFloat
>(-nX
), static_cast<CGFloat
>(-nY
) };
76 if (maGraphicContext
.isSet()) // remove warning
80 CGContextTranslateCTM( maGraphicContext
.get(), 0, +mnHeight
);
82 CGContextScaleCTM( maGraphicContext
.get(), +1, -1 );
85 CGContextDrawLayerAtPoint(maGraphicContext
.get(), aSrcPoint
, rLayerHolder
.get());
90 // From salgdicommon.cxx
92 void AquaGraphicsBackend::copyBits(const SalTwoRect
& rPosAry
, SalGraphics
*pSrcGraphics
)
94 //from unix salgdi2.cxx
95 //[FIXME] find a better way to prevent calc from crashing when width and height are negative
96 if( rPosAry
.mnSrcWidth
<= 0 ||
97 rPosAry
.mnSrcHeight
<= 0 ||
98 rPosAry
.mnDestWidth
<= 0 ||
99 rPosAry
.mnDestHeight
<= 0 )
104 // If called from idle layout, maContextHolder.get() is NULL, no idea what to do
105 if (!mrShared
.maContextHolder
.isSet())
108 AquaSharedAttributes
* pSrcShared
= nullptr;
112 AquaSalGraphics
* pSrc
= static_cast<AquaSalGraphics
*>(pSrcGraphics
);
113 pSrcShared
= &pSrc
->getAquaGraphicsBackend()->GetShared();
116 pSrcShared
= &mrShared
;
118 // accelerate trivial operations
119 const bool bSameGraphics
= (pSrcShared
== &mrShared
);
122 (rPosAry
.mnSrcWidth
== rPosAry
.mnDestWidth
) &&
123 (rPosAry
.mnSrcHeight
== rPosAry
.mnDestHeight
))
125 // short circuit if there is nothing to do
126 if( (rPosAry
.mnSrcX
== rPosAry
.mnDestX
) &&
127 (rPosAry
.mnSrcY
== rPosAry
.mnDestY
))
131 // use copyArea() if source and destination context are identical
132 copyArea( rPosAry
.mnDestX
, rPosAry
.mnDestY
, rPosAry
.mnSrcX
, rPosAry
.mnSrcY
,
133 rPosAry
.mnSrcWidth
, rPosAry
.mnSrcHeight
, false/*bWindowInvalidate*/ );
137 mrShared
.applyXorContext();
139 pSrcShared
->applyXorContext();
141 SAL_WARN_IF (!pSrcShared
->maLayer
.isSet(), "vcl.quartz",
142 "AquaSalGraphics::copyBits() from non-layered graphics this=" << this);
144 const CGPoint aDstPoint
= CGPointMake(+rPosAry
.mnDestX
- rPosAry
.mnSrcX
, rPosAry
.mnDestY
- rPosAry
.mnSrcY
);
145 if ((rPosAry
.mnSrcWidth
== rPosAry
.mnDestWidth
&&
146 rPosAry
.mnSrcHeight
== rPosAry
.mnDestHeight
) &&
147 (!mrShared
.mnBitmapDepth
|| (aDstPoint
.x
+ pSrcShared
->mnWidth
) <= mrShared
.mnWidth
)
148 && pSrcShared
->maLayer
.isSet()) // workaround for a Quartz crash
150 // in XOR mode the drawing context is redirected to the XOR mask
151 // if source and target are identical then copyBits() paints onto the target context though
152 CGContextHolder aCopyContext
= mrShared
.maContextHolder
;
153 if (mrShared
.mpXorEmulation
&& mrShared
.mpXorEmulation
->IsEnabled())
157 aCopyContext
.set(mrShared
.mpXorEmulation
->GetTargetContext());
160 aCopyContext
.saveState();
162 const CGRect aDstRect
= CGRectMake(rPosAry
.mnDestX
, rPosAry
.mnDestY
, rPosAry
.mnDestWidth
, rPosAry
.mnDestHeight
);
163 CGContextClipToRect(aCopyContext
.get(), aDstRect
);
165 // draw at new destination
166 // NOTE: flipped drawing gets disabled for this, else the subimage would be drawn upside down
167 if (pSrcShared
->isFlipped())
169 CGContextTranslateCTM(aCopyContext
.get(), 0, +mrShared
.mnHeight
);
170 CGContextScaleCTM(aCopyContext
.get(), +1, -1);
173 // TODO: pSrc->size() != this->size()
174 CGContextDrawLayerAtPoint(aCopyContext
.get(), aDstPoint
, pSrcShared
->maLayer
.get());
176 aCopyContext
.restoreState();
177 // mark the destination rectangle as updated
178 refreshRect(aDstRect
);
182 std::shared_ptr
<SalBitmap
> pBitmap
;
184 pBitmap
= pSrcGraphics
->GetImpl()->getBitmap(rPosAry
.mnSrcX
, rPosAry
.mnSrcY
, rPosAry
.mnSrcWidth
, rPosAry
.mnSrcHeight
);
186 pBitmap
= getBitmap(rPosAry
.mnSrcX
, rPosAry
.mnSrcY
, rPosAry
.mnSrcWidth
, rPosAry
.mnSrcHeight
);
190 SalTwoRect
aPosAry( rPosAry
);
193 drawBitmap(aPosAry
, *pBitmap
);
198 void AquaGraphicsBackend::copyArea(tools::Long nDstX
, tools::Long nDstY
,tools::Long nSrcX
, tools::Long nSrcY
,
199 tools::Long nSrcWidth
, tools::Long nSrcHeight
, bool /*bWindowInvalidate*/)
201 SAL_WARN_IF (!mrShared
.maLayer
.isSet(), "vcl.quartz",
202 "AquaSalGraphics::copyArea() for non-layered graphics this=" << this);
204 if (!mrShared
.maLayer
.isSet())
207 float fScale
= mrShared
.maLayer
.getScale();
209 tools::Long nScaledSourceX
= nSrcX
* fScale
;
210 tools::Long nScaledSourceY
= nSrcY
* fScale
;
212 tools::Long nScaledTargetX
= nDstX
* fScale
;
213 tools::Long nScaledTargetY
= nDstY
* fScale
;
215 tools::Long nScaledSourceWidth
= nSrcWidth
* fScale
;
216 tools::Long nScaledSourceHeight
= nSrcHeight
* fScale
;
218 mrShared
.applyXorContext();
220 mrShared
.maContextHolder
.saveState();
222 // in XOR mode the drawing context is redirected to the XOR mask
223 // copyArea() always works on the target context though
224 CGContextRef xCopyContext
= mrShared
.maContextHolder
.get();
226 if (mrShared
.mpXorEmulation
&& mrShared
.mpXorEmulation
->IsEnabled())
228 xCopyContext
= mrShared
.mpXorEmulation
->GetTargetContext();
231 // If we have a scaled layer, we need to revert the scaling or else
232 // it will interfere with the coordinate calculation
233 CGContextScaleCTM(xCopyContext
, 1.0 / fScale
, 1.0 / fScale
);
235 // drawing a layer onto its own context causes trouble on OSX => copy it first
236 // TODO: is it possible to get rid of this unneeded copy more often?
237 // e.g. on OSX>=10.5 only this situation causes problems:
238 // mnBitmapDepth && (aDstPoint.x + pSrc->mnWidth) > mnWidth
240 CGLayerHolder
sSourceLayerHolder(mrShared
.maLayer
);
242 const CGSize aSrcSize
= CGSizeMake(nScaledSourceWidth
, nScaledSourceHeight
);
243 sSourceLayerHolder
.set(CGLayerCreateWithContext(xCopyContext
, aSrcSize
, nullptr));
245 const CGContextRef xSrcContext
= CGLayerGetContext(sSourceLayerHolder
.get());
247 CGPoint aSrcPoint
= CGPointMake(-nScaledSourceX
, -nScaledSourceY
);
248 if (mrShared
.isFlipped())
250 CGContextTranslateCTM(xSrcContext
, 0, +nScaledSourceHeight
);
251 CGContextScaleCTM(xSrcContext
, +1, -1);
252 aSrcPoint
.y
= (nScaledSourceY
+ nScaledSourceHeight
) - (mrShared
.mnHeight
* fScale
);
254 CGContextSetBlendMode(xSrcContext
, kCGBlendModeCopy
);
256 CGContextDrawLayerAtPoint(xSrcContext
, aSrcPoint
, mrShared
.maLayer
.get());
259 // draw at new destination
260 const CGRect aTargetRect
= CGRectMake(nScaledTargetX
, nScaledTargetY
, nScaledSourceWidth
, nScaledSourceHeight
);
261 CGContextSetBlendMode(xCopyContext
, kCGBlendModeCopy
);
262 CGContextDrawLayerInRect(xCopyContext
, aTargetRect
, sSourceLayerHolder
.get());
264 mrShared
.maContextHolder
.restoreState();
267 if (sSourceLayerHolder
.get() != mrShared
.maLayer
.get())
269 CGLayerRelease(sSourceLayerHolder
.get());
272 // mark the destination rectangle as updated
273 mrShared
.refreshRect(nDstX
, nDstY
, nSrcWidth
, nSrcHeight
);
276 void AquaSalGraphics::SetVirDevGraphics(SalVirtualDevice
* pVirDev
, CGLayerHolder
const & rLayer
, CGContextRef xContext
,
279 SAL_INFO( "vcl.quartz", "SetVirDevGraphics() this=" << this << " layer=" << rLayer
.get() << " context=" << xContext
);
281 maShared
.mbPrinter
= false;
282 maShared
.mbVirDev
= true;
284 // set graphics properties
285 maShared
.maLayer
= rLayer
;
286 maShared
.maContextHolder
.set(xContext
);
288 maShared
.mnBitmapDepth
= nBitmapDepth
;
290 maShared
.mbForeignContext
= xContext
!= NULL
;
292 mpBackend
->UpdateGeometryProvider(pVirDev
);
294 // return early if the virdev is being destroyed
298 // get new graphics properties
299 if (!maShared
.maLayer
.isSet())
301 maShared
.mnWidth
= CGBitmapContextGetWidth(maShared
.maContextHolder
.get());
302 maShared
.mnHeight
= CGBitmapContextGetHeight(maShared
.maContextHolder
.get());
306 const CGSize aSize
= CGLayerGetSize(maShared
.maLayer
.get());
307 maShared
.mnWidth
= static_cast<int>(aSize
.width
);
308 maShared
.mnHeight
= static_cast<int>(aSize
.height
);
311 // prepare graphics for drawing
312 const CGColorSpaceRef aCGColorSpace
= GetSalData()->mxRGBSpace
;
313 CGContextSetFillColorSpace(maShared
.maContextHolder
.get(), aCGColorSpace
);
314 CGContextSetStrokeColorSpace(maShared
.maContextHolder
.get(), aCGColorSpace
);
316 // re-enable XorEmulation for the new context
317 if (maShared
.mpXorEmulation
)
319 maShared
.mpXorEmulation
->SetTarget(maShared
.mnWidth
, maShared
.mnHeight
, maShared
.mnBitmapDepth
, maShared
.maContextHolder
.get(), maShared
.maLayer
.get());
320 if (maShared
.mpXorEmulation
->IsEnabled())
322 maShared
.maContextHolder
.set(maShared
.mpXorEmulation
->GetMaskContext());
326 // initialize stack of CGContext states
327 maShared
.maContextHolder
.saveState();
331 void XorEmulation::SetTarget( int nWidth
, int nHeight
, int nTargetDepth
,
332 CGContextRef xTargetContext
, CGLayerRef xTargetLayer
)
334 SAL_INFO( "vcl.quartz", "XorEmulation::SetTarget() this=" << this <<
335 " (" << nWidth
<< "x" << nHeight
<< ") depth=" << nTargetDepth
<<
336 " context=" << xTargetContext
<< " layer=" << xTargetLayer
);
338 // prepare to replace old mask+temp context
341 // cleanup the mask context
342 CGContextRelease( m_xMaskContext
);
343 delete[] m_pMaskBuffer
;
344 m_xMaskContext
= nullptr;
345 m_pMaskBuffer
= nullptr;
347 // cleanup the temp context if needed
350 CGContextRelease( m_xTempContext
);
351 delete[] m_pTempBuffer
;
352 m_xTempContext
= nullptr;
353 m_pTempBuffer
= nullptr;
357 // return early if there is nothing more to do
358 if( !xTargetContext
)
362 // retarget drawing operations to the XOR mask
363 m_xTargetLayer
= xTargetLayer
;
364 m_xTargetContext
= xTargetContext
;
366 // prepare creation of matching CGBitmaps
367 CGColorSpaceRef aCGColorSpace
= GetSalData()->mxRGBSpace
;
368 CGBitmapInfo aCGBmpInfo
= kCGImageAlphaNoneSkipFirst
;
369 int nBitDepth
= nTargetDepth
;
374 int nBytesPerRow
= 4;
375 const size_t nBitsPerComponent
= (nBitDepth
== 16) ? 5 : 8;
378 aCGColorSpace
= GetSalData()->mxGraySpace
;
379 aCGBmpInfo
= kCGImageAlphaNone
;
382 nBytesPerRow
*= nWidth
;
383 m_nBufferLongs
= (nHeight
* nBytesPerRow
+ sizeof(sal_uLong
)-1) / sizeof(sal_uLong
);
385 // create a XorMask context
386 m_pMaskBuffer
= new sal_uLong
[ m_nBufferLongs
];
387 m_xMaskContext
= CGBitmapContextCreate( m_pMaskBuffer
,
389 nBitsPerComponent
, nBytesPerRow
,
390 aCGColorSpace
, aCGBmpInfo
);
391 SAL_WARN_IF( !m_xMaskContext
, "vcl.quartz", "mask context creation failed" );
393 // reset the XOR mask to black
394 memset( m_pMaskBuffer
, 0, m_nBufferLongs
* sizeof(sal_uLong
) );
396 // a bitmap context will be needed for manual XORing
397 // create one unless the target context is a bitmap context
400 m_pTempBuffer
= static_cast<sal_uLong
*>(CGBitmapContextGetData( m_xTargetContext
));
404 // create a bitmap context matching to the target context
405 m_pTempBuffer
= new sal_uLong
[ m_nBufferLongs
];
406 m_xTempContext
= CGBitmapContextCreate( m_pTempBuffer
,
408 nBitsPerComponent
, nBytesPerRow
,
409 aCGColorSpace
, aCGBmpInfo
);
410 SAL_WARN_IF( !m_xTempContext
, "vcl.quartz", "temp context creation failed" );
413 // initialize XOR mask context for drawing
414 CGContextSetFillColorSpace( m_xMaskContext
, aCGColorSpace
);
415 CGContextSetStrokeColorSpace( m_xMaskContext
, aCGColorSpace
);
416 CGContextSetShouldAntialias( m_xMaskContext
, false );
418 // improve the XorMask's XOR emulation a little
419 // NOTE: currently only enabled for monochrome contexts
420 if( aCGColorSpace
== GetSalData()->mxGraySpace
)
422 CGContextSetBlendMode( m_xMaskContext
, kCGBlendModeDifference
);
424 // initialize the transformation matrix to the drawing target
425 const CGAffineTransform aCTM
= CGContextGetCTM( xTargetContext
);
426 CGContextConcatCTM( m_xMaskContext
, aCTM
);
429 CGContextConcatCTM( m_xTempContext
, aCTM
);
431 // initialize the default XorMask graphics state
432 CGContextSaveGState( m_xMaskContext
);
435 bool XorEmulation::UpdateTarget()
437 SAL_INFO( "vcl.quartz", "XorEmulation::UpdateTarget() this=" << this );
443 // update the temp bitmap buffer if needed
446 SAL_WARN_IF( m_xTargetContext
== nullptr, "vcl.quartz", "Target layer is NULL");
447 CGContextDrawLayerAtPoint( m_xTempContext
, CGPointZero
, m_xTargetLayer
);
449 // do a manual XOR with the XorMask
450 // this approach suffices for simple color manipulations
451 // and also the complex-clipping-XOR-trick used in metafiles
452 const sal_uLong
* pSrc
= m_pMaskBuffer
;
453 sal_uLong
* pDst
= m_pTempBuffer
;
454 for( int i
= m_nBufferLongs
; --i
>= 0;)
456 *(pDst
++) ^= *(pSrc
++);
458 // write back the XOR results to the target context
461 CGImageRef xXorImage
= CGBitmapContextCreateImage( m_xTempContext
);
462 const int nWidth
= static_cast<int>(CGImageGetWidth( xXorImage
));
463 const int nHeight
= static_cast<int>(CGImageGetHeight( xXorImage
));
464 // TODO: update minimal changerect
465 const CGRect aFullRect
= CGRectMake(0, 0, nWidth
, nHeight
);
466 CGContextDrawImage( m_xTargetContext
, aFullRect
, xXorImage
);
467 CGImageRelease( xXorImage
);
470 // reset the XorMask to black again
471 // TODO: not needed for last update
472 memset( m_pMaskBuffer
, 0, m_nBufferLongs
* sizeof(sal_uLong
) );
474 // TODO: return FALSE if target was not changed
480 void AquaSalVirtualDevice::Destroy()
482 SAL_INFO( "vcl.virdev", "AquaSalVirtualDevice::Destroy() this=" << this << " mbForeignContext=" << mbForeignContext
);
484 if( mbForeignContext
)
486 // Do not delete mxContext that we have received from outside VCL
487 maLayer
.set(nullptr);
495 mpGraphics
->SetVirDevGraphics(this, nullptr, nullptr);
497 CGLayerRelease(maLayer
.get());
498 maLayer
.set(nullptr);
501 if (maBitmapContext
.isSet())
503 void* pRawData
= CGBitmapContextGetData(maBitmapContext
.get());
505 CGContextRelease(maBitmapContext
.get());
506 maBitmapContext
.set(nullptr);
510 bool AquaSalVirtualDevice::SetSize( tools::Long nDX
, tools::Long nDY
)
512 SAL_INFO( "vcl.virdev", "AquaSalVirtualDevice::SetSize() this=" << this <<
513 " (" << nDX
<< "x" << nDY
<< ") mbForeignContext=" << (mbForeignContext
? "YES" : "NO"));
515 if( mbForeignContext
)
517 // Do not delete/resize mxContext that we have received from outside VCL
523 const CGSize aSize
= CGLayerGetSize(maLayer
.get());
524 if( (nDX
== aSize
.width
) && (nDY
== aSize
.height
) )
526 // Yay, we do not have to do anything :)
536 // create a CGLayer matching to the intended virdev usage
537 CGContextHolder xCGContextHolder
;
538 if( mnBitmapDepth
&& (mnBitmapDepth
< 16) )
540 mnBitmapDepth
= 8; // TODO: are 1bit vdevs worth it?
541 const int nBytesPerRow
= (mnBitmapDepth
* nDX
+ 7) / 8;
543 void* pRawData
= std::malloc( nBytesPerRow
* nDY
);
544 maBitmapContext
.set(CGBitmapContextCreate( pRawData
, nDX
, nDY
,
545 mnBitmapDepth
, nBytesPerRow
,
546 GetSalData()->mxGraySpace
, kCGImageAlphaNone
));
547 xCGContextHolder
= maBitmapContext
;
551 if (!xCGContextHolder
.isSet())
553 // assert(Application::IsBitmapRendering());
556 const int nBytesPerRow
= (mnBitmapDepth
* nDX
) / 8;
557 void* pRawData
= std::malloc( nBytesPerRow
* nDY
);
558 const int nFlags
= kCGImageAlphaNoneSkipFirst
| kCGImageByteOrder32Little
;
559 maBitmapContext
.set(CGBitmapContextCreate(pRawData
, nDX
, nDY
, 8, nBytesPerRow
,
560 GetSalData()->mxRGBSpace
, nFlags
));
561 xCGContextHolder
= maBitmapContext
;
565 SAL_WARN_IF(!xCGContextHolder
.isSet(), "vcl.quartz", "No context");
567 const CGSize aNewSize
= { static_cast<CGFloat
>(nDX
), static_cast<CGFloat
>(nDY
) };
568 maLayer
.set(CGLayerCreateWithContext(xCGContextHolder
.get(), aNewSize
, nullptr));
570 if (maLayer
.isSet() && mpGraphics
)
572 // get the matching Quartz context
573 CGContextRef xDrawContext
= CGLayerGetContext( maLayer
.get() );
575 // Here we pass the CGLayerRef that the CGLayerHolder maLayer holds as the first parameter
576 // to SetVirDevGraphics(). That parameter is of type CGLayerHolder, so what we actually pass
577 // is an implicitly constructed *separate* CGLayerHolder. Is that what we want? No idea.
578 // Possibly we could pass just maLayer as such? But doing that does not fix tdf#138122.
579 mpGraphics
->SetVirDevGraphics(this, maLayer
.get(), xDrawContext
, mnBitmapDepth
);
582 return maLayer
.isSet();
585 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */