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>
24 #include <sal/log.hxx>
26 #include <basegfx/polygon/b2dpolygon.hxx>
27 #include <basegfx/polygon/b2dpolygontools.hxx>
28 #include <basegfx/range/b2drectangle.hxx>
29 #include <basegfx/range/b2irange.hxx>
30 #include <basegfx/vector/b2ivector.hxx>
31 #include <vcl/svapp.hxx>
33 #include <quartz/salgdi.h>
34 #include <quartz/utils.h>
35 #include <osx/salframe.h>
36 #include <osx/saldata.hxx>
39 #include <tools/sk_app/mac/WindowContextFactory_mac.h>
40 #include <vcl/skia/SkiaHelper.hxx>
43 static bool bTotalScreenBounds
= false;
44 static NSRect aTotalScreenBounds
= NSZeroRect
;
46 // TODO: Scale will be set to 2.0f as default after implementation of full scaled display support . This will allow moving of
47 // windows between non retina and retina displays without blurry text and graphics. Static variables have to be removed thereafter.
49 // Currently scaled display support is not implemented for bitmaps. This will cause a slight performance degradation on displays
50 // with single precision. To preserve performance for now, window scaling is only activated if at least one display with double
51 // precision is present. Moving windows between displays is then possible without blurry text and graphics too. Adapting window
52 // scaling when displays are added while application is running is not supported.
54 static bool bWindowScaling
= false;
55 static float fWindowScale
= 1.0f
;
59 NSRect
getTotalScreenBounds()
61 if (!bTotalScreenBounds
)
63 aTotalScreenBounds
= NSZeroRect
;
65 NSArray
*aScreens
= [NSScreen screens
];
66 if (aScreens
!= nullptr)
68 for (NSScreen
*aScreen
: aScreens
)
70 // Calculate total screen bounds
71 NSRect aScreenFrame
= [aScreen frame
];
72 if (!NSIsEmptyRect(aScreenFrame
))
74 if (NSIsEmptyRect(aTotalScreenBounds
))
75 aTotalScreenBounds
= aScreenFrame
;
77 aTotalScreenBounds
= NSUnionRect( aScreenFrame
, aTotalScreenBounds
);
80 bTotalScreenBounds
= true;
83 return aTotalScreenBounds
;
86 void resetTotalScreenBounds()
88 bTotalScreenBounds
= false;
89 getTotalScreenBounds();
92 float getWindowScaling()
94 // Related: tdf#147342 Any changes to this function must be copied to the
95 // sk_app::GetBackingScaleFactor() function in the following file:
96 // workdir/UnpackedTarball/skia/tools/sk_app/mac/WindowContextFactory_mac.h
99 NSArray
*aScreens
= [NSScreen screens
];
100 if (aScreens
!= nullptr)
102 for (NSScreen
*aScreen
: aScreens
)
104 float fScale
= [aScreen backingScaleFactor
];
105 if (fScale
> fWindowScale
)
106 fWindowScale
= fScale
;
108 bWindowScaling
= true;
110 if( const char* env
= getenv("SAL_FORCE_HIDPI_SCALING"))
112 fWindowScale
= atof(env
);
113 bWindowScaling
= true;
119 void resetWindowScaling()
121 // Related: tdf#147342 Force recalculation of the window scaling but keep
122 // the previous window scaling as the minimum so that we don't lose the
123 // resolution in cached images if a HiDPI monitor is disconnected and
125 #if HAVE_FEATURE_SKIA
126 if ( SkiaHelper::isVCLSkiaEnabled() )
127 sk_app::ResetBackingScaleFactor();
129 bWindowScaling
= false;
134 void AquaSalGraphics::SetWindowGraphics( AquaSalFrame
* pFrame
)
136 maShared
.mpFrame
= pFrame
;
137 maShared
.mbWindow
= true;
138 maShared
.mbPrinter
= false;
139 maShared
.mbVirDev
= false;
140 mpBackend
->UpdateGeometryProvider(pFrame
);
143 void AquaSalGraphics::SetPrinterGraphics( CGContextRef xContext
, sal_Int32 nDPIX
, sal_Int32 nDPIY
)
145 maShared
.mbWindow
= false;
146 maShared
.mbPrinter
= true;
147 maShared
.mbVirDev
= false;
149 maShared
.maContextHolder
.set(xContext
);
153 // a previously set clip path is now invalid
154 maShared
.unsetClipPath();
156 if (maShared
.maContextHolder
.isSet())
158 CGContextSetFillColorSpace( maShared
.maContextHolder
.get(), GetSalData()->mxRGBSpace
);
159 CGContextSetStrokeColorSpace( maShared
.maContextHolder
.get(), GetSalData()->mxRGBSpace
);
160 CGContextSaveGState( maShared
.maContextHolder
.get() );
164 mpBackend
->UpdateGeometryProvider(nullptr);
167 void AquaSalGraphics::InvalidateContext()
171 CGContextRelease(maShared
.maContextHolder
.get());
172 CGContextRelease(maShared
.maBGContextHolder
.get());
173 CGContextRelease(maShared
.maCSContextHolder
.get());
175 maShared
.maContextHolder
.set(nullptr);
176 maShared
.maCSContextHolder
.set(nullptr);
177 maShared
.maBGContextHolder
.set(nullptr);
180 void AquaSalGraphics::UnsetState()
182 if (maShared
.maBGContextHolder
.isSet())
184 CGContextRelease(maShared
.maBGContextHolder
.get());
185 maShared
.maBGContextHolder
.set(nullptr);
187 if (maShared
.maCSContextHolder
.isSet())
189 CGContextRelease(maShared
.maCSContextHolder
.get());
190 maShared
.maBGContextHolder
.set(nullptr);
192 if (maShared
.maContextHolder
.isSet())
194 maShared
.maContextHolder
.restoreState();
195 maShared
.maContextHolder
.set(nullptr);
197 maShared
.unsetState();
201 * (re-)create the off-screen maLayer we render everything to if
202 * necessary: eg. not initialized yet, or it has an incorrect size.
204 bool AquaSharedAttributes::checkContext()
206 if (mbWindow
&& mpFrame
&& (mpFrame
->getNSWindow() || Application::IsBitmapRendering()))
208 const unsigned int nWidth
= mpFrame
->maGeometry
.width();
209 const unsigned int nHeight
= mpFrame
->maGeometry
.height();
210 const float fScale
= sal::aqua::getWindowScaling();
211 CGLayerRef rReleaseLayer
= nullptr;
213 // check if a new drawing context is needed (e.g. after a resize)
214 if( (unsigned(mnWidth
) != nWidth
) || (unsigned(mnHeight
) != nHeight
) )
218 // prepare to release the corresponding resources
221 rReleaseLayer
= maLayer
.get();
223 else if (maContextHolder
.isSet())
225 CGContextRelease(maContextHolder
.get());
227 CGContextRelease(maBGContextHolder
.get());
228 CGContextRelease(maCSContextHolder
.get());
230 maContextHolder
.set(nullptr);
231 maBGContextHolder
.set(nullptr);
232 maCSContextHolder
.set(nullptr);
233 maLayer
.set(nullptr);
236 if (!maContextHolder
.isSet())
238 const int nBitmapDepth
= 32;
240 float nScaledWidth
= mnWidth
* fScale
;
241 float nScaledHeight
= mnHeight
* fScale
;
243 const CGSize aLayerSize
= { static_cast<CGFloat
>(nScaledWidth
), static_cast<CGFloat
>(nScaledHeight
) };
245 const int nBytesPerRow
= (nBitmapDepth
* nScaledWidth
) / 8;
246 std::uint32_t nFlags
= std::uint32_t(kCGImageAlphaNoneSkipFirst
)
247 | std::uint32_t(kCGBitmapByteOrder32Host
);
248 maBGContextHolder
.set(CGBitmapContextCreate(
249 nullptr, nScaledWidth
, nScaledHeight
, 8, nBytesPerRow
, GetSalData()->mxRGBSpace
, nFlags
));
251 maLayer
.set(CGLayerCreateWithContext(maBGContextHolder
.get(), aLayerSize
, nullptr));
252 maLayer
.setScale(fScale
);
254 nFlags
= std::uint32_t(kCGImageAlphaPremultipliedFirst
)
255 | std::uint32_t(kCGBitmapByteOrder32Host
);
256 maCSContextHolder
.set(CGBitmapContextCreate(
257 nullptr, nScaledWidth
, nScaledHeight
, 8, nBytesPerRow
, GetSalData()->mxRGBSpace
, nFlags
));
259 CGContextRef xDrawContext
= CGLayerGetContext(maLayer
.get());
260 maContextHolder
= xDrawContext
;
264 // copy original layer to resized layer
265 if (maContextHolder
.isSet())
267 CGContextDrawLayerAtPoint(maContextHolder
.get(), CGPointZero
, rReleaseLayer
);
269 CGLayerRelease(rReleaseLayer
);
272 if (maContextHolder
.isSet())
274 CGContextTranslateCTM(maContextHolder
.get(), 0, nScaledHeight
);
275 CGContextScaleCTM(maContextHolder
.get(), 1.0, -1.0);
276 CGContextSetFillColorSpace(maContextHolder
.get(), GetSalData()->mxRGBSpace
);
277 CGContextSetStrokeColorSpace(maContextHolder
.get(), GetSalData()->mxRGBSpace
);
278 // apply a scale matrix so everything is auto-magically scaled
279 CGContextScaleCTM(maContextHolder
.get(), fScale
, fScale
);
280 maContextHolder
.saveState();
283 // re-enable XOR emulation for the new context
285 mpXorEmulation
->SetTarget(mnWidth
, mnHeight
, mnBitmapDepth
, maContextHolder
.get(), maLayer
.get());
290 SAL_WARN_IF(!maContextHolder
.isSet() && !mbPrinter
, "vcl", "<<<WARNING>>> AquaSalGraphics::CheckContext() FAILED!!!!");
292 return maContextHolder
.isSet();
296 * Blit the contents of our internal maLayer state to the
297 * associated window, if any; cf. drawRect event handling
300 void AquaSalGraphics::UpdateWindow( NSRect
& )
302 if (!maShared
.mpFrame
)
307 NSGraphicsContext
* pContext
= [NSGraphicsContext currentContext
];
308 if (maShared
.maLayer
.isSet() && pContext
!= nullptr)
310 CGContextHolder
rCGContextHolder([pContext CGContext
]);
312 rCGContextHolder
.saveState();
314 CGMutablePathRef rClip
= maShared
.mpFrame
->getClipPath();
317 CGContextBeginPath(rCGContextHolder
.get());
318 CGContextAddPath(rCGContextHolder
.get(), rClip
);
319 CGContextClip(rCGContextHolder
.get());
322 maShared
.applyXorContext();
324 const CGSize aSize
= maShared
.maLayer
.getSizePoints();
325 const CGRect aRect
= CGRectMake(0, 0, aSize
.width
, aSize
.height
);
326 const CGRect aRectPoints
= { CGPointZero
, maShared
.maLayer
.getSizePixels() };
327 CGContextSetBlendMode(maShared
.maCSContextHolder
.get(), kCGBlendModeCopy
);
328 CGContextDrawLayerInRect(maShared
.maCSContextHolder
.get(), aRectPoints
, maShared
.maLayer
.get());
330 CGImageRef img
= CGBitmapContextCreateImage(maShared
.maCSContextHolder
.get());
331 CGImageRef displayColorSpaceImage
= CGImageCreateCopyWithColorSpace(img
, [[maShared
.mpFrame
->getNSWindow() colorSpace
] CGColorSpace
]);
332 CGContextSetBlendMode(rCGContextHolder
.get(), kCGBlendModeCopy
);
333 CGContextDrawImage(rCGContextHolder
.get(), aRect
, displayColorSpaceImage
);
336 CGImageRelease(displayColorSpaceImage
);
338 rCGContextHolder
.restoreState();
342 SAL_WARN_IF(!maShared
.mpFrame
->mbInitShow
, "vcl", "UpdateWindow called on uneligible graphics");
346 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */