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
22 #include <cstdint>
23
24 #include <sal/log.hxx>
25
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>
32
33 #include <quartz/salgdi.h>
34 #include <quartz/utils.h>
35 #include <osx/salframe.h>
36 #include <osx/saldata.hxx>
37
38 // TODO: Scale will be set to 2.0f as default after implementation of full scaled display support . This will allow moving of
39 // windows between non retina and retina displays without blurry text and graphics. Static variables have to be removed thereafter.
40
41 // Currently scaled display support is not implemented for bitmaps. This will cause a slight performance degradation on displays
42 // with single precision. To preserve performance for now, window scaling is only activated if at least one display with double
43 // precision is present. Moving windows between displays is then possible without blurry text and graphics too. Adapting window
44 // scaling when displays are added while application is running is not supported.
45
46 static bool bWindowScaling = false;
47 static float fWindowScale = 1.0f;
48
49 namespace sal::aqua
50 {
getWindowScaling()51 float getWindowScaling()
52 {
53 if (!bWindowScaling)
54 {
55 NSArray *aScreens = [NSScreen screens];
56 if (aScreens != nullptr)
57 {
58 int nScreens = [aScreens count];
59 for (int i = 0; i < nScreens; i++)
60 {
61 float fScale = [[aScreens objectAtIndex:i] backingScaleFactor];
62 if (fScale > fWindowScale)
63 fWindowScale = fScale;
64 }
65 bWindowScaling = true;
66 }
67 }
68 return fWindowScale;
69 }
70 } // end aqua
71
SetWindowGraphics(AquaSalFrame * pFrame)72 void AquaSalGraphics::SetWindowGraphics( AquaSalFrame* pFrame )
73 {
74 maShared.mpFrame = pFrame;
75 maShared.mbWindow = true;
76 maShared.mbPrinter = false;
77 maShared.mbVirDev = false;
78 }
79
SetPrinterGraphics(CGContextRef xContext,sal_Int32 nDPIX,sal_Int32 nDPIY)80 void AquaSalGraphics::SetPrinterGraphics( CGContextRef xContext, sal_Int32 nDPIX, sal_Int32 nDPIY )
81 {
82 maShared.mbWindow = false;
83 maShared.mbPrinter = true;
84 maShared.mbVirDev = false;
85
86 maShared.maContextHolder.set(xContext);
87 mnRealDPIX = nDPIX;
88 mnRealDPIY = nDPIY;
89
90 // a previously set clip path is now invalid
91 maShared.unsetClipPath();
92
93 if (maShared.maContextHolder.isSet())
94 {
95 CGContextSetFillColorSpace( maShared.maContextHolder.get(), GetSalData()->mxRGBSpace );
96 CGContextSetStrokeColorSpace( maShared.maContextHolder.get(), GetSalData()->mxRGBSpace );
97 CGContextSaveGState( maShared.maContextHolder.get() );
98 maShared.setState();
99 }
100 }
101
InvalidateContext()102 void AquaSalGraphics::InvalidateContext()
103 {
104 UnsetState();
105
106 CGContextRelease(maShared.maContextHolder.get());
107 CGContextRelease(maShared.maBGContextHolder.get());
108 CGContextRelease(maShared.maCSContextHolder.get());
109
110 maShared.maContextHolder.set(nullptr);
111 maShared.maCSContextHolder.set(nullptr);
112 maShared.maBGContextHolder.set(nullptr);
113 }
114
UnsetState()115 void AquaSalGraphics::UnsetState()
116 {
117 if (maShared.maBGContextHolder.isSet())
118 {
119 CGContextRelease(maShared.maBGContextHolder.get());
120 maShared.maBGContextHolder.set(nullptr);
121 }
122 if (maShared.maCSContextHolder.isSet())
123 {
124 CGContextRelease(maShared.maCSContextHolder.get());
125 maShared.maBGContextHolder.set(nullptr);
126 }
127 if (maShared.maContextHolder.isSet())
128 {
129 maShared.maContextHolder.restoreState();
130 maShared.maContextHolder.set(nullptr);
131 }
132 maShared.unsetState();
133 }
134
135 /**
136 * (re-)create the off-screen maLayer we render everything to if
137 * necessary: eg. not initialized yet, or it has an incorrect size.
138 */
checkContext()139 bool AquaSharedAttributes::checkContext()
140 {
141 if (mbWindow && mpFrame && (mpFrame->getNSWindow() || Application::IsBitmapRendering()))
142 {
143 const unsigned int nWidth = mpFrame->maGeometry.nWidth;
144 const unsigned int nHeight = mpFrame->maGeometry.nHeight;
145 const float fScale = sal::aqua::getWindowScaling();
146 CGLayerRef rReleaseLayer = nullptr;
147
148 // check if a new drawing context is needed (e.g. after a resize)
149 if( (unsigned(mnWidth) != nWidth) || (unsigned(mnHeight) != nHeight) )
150 {
151 mnWidth = nWidth;
152 mnHeight = nHeight;
153 // prepare to release the corresponding resources
154 if (maLayer.isSet())
155 {
156 rReleaseLayer = maLayer.get();
157 }
158 else if (maContextHolder.isSet())
159 {
160 CGContextRelease(maContextHolder.get());
161 }
162 CGContextRelease(maBGContextHolder.get());
163 CGContextRelease(maCSContextHolder.get());
164
165 maContextHolder.set(nullptr);
166 maBGContextHolder.set(nullptr);
167 maCSContextHolder.set(nullptr);
168 maLayer.set(nullptr);
169 }
170
171 if (!maContextHolder.isSet())
172 {
173 const int nBitmapDepth = 32;
174
175 float nScaledWidth = mnWidth * fScale;
176 float nScaledHeight = mnHeight * fScale;
177
178 const CGSize aLayerSize = { static_cast<CGFloat>(nScaledWidth), static_cast<CGFloat>(nScaledHeight) };
179
180 const int nBytesPerRow = (nBitmapDepth * nScaledWidth) / 8;
181 std::uint32_t nFlags = std::uint32_t(kCGImageAlphaNoneSkipFirst)
182 | std::uint32_t(kCGBitmapByteOrder32Host);
183 maBGContextHolder.set(CGBitmapContextCreate(
184 nullptr, nScaledWidth, nScaledHeight, 8, nBytesPerRow, GetSalData()->mxRGBSpace, nFlags));
185
186 maLayer.set(CGLayerCreateWithContext(maBGContextHolder.get(), aLayerSize, nullptr));
187 maLayer.setScale(fScale);
188
189 nFlags = std::uint32_t(kCGImageAlphaPremultipliedFirst)
190 | std::uint32_t(kCGBitmapByteOrder32Host);
191 maCSContextHolder.set(CGBitmapContextCreate(
192 nullptr, nScaledWidth, nScaledHeight, 8, nBytesPerRow, GetSalData()->mxRGBSpace, nFlags));
193
194 CGContextRef xDrawContext = CGLayerGetContext(maLayer.get());
195 maContextHolder = xDrawContext;
196
197 if (rReleaseLayer)
198 {
199 // copy original layer to resized layer
200 if (maContextHolder.isSet())
201 {
202 CGContextDrawLayerAtPoint(maContextHolder.get(), CGPointZero, rReleaseLayer);
203 }
204 CGLayerRelease(rReleaseLayer);
205 }
206
207 if (maContextHolder.isSet())
208 {
209 CGContextTranslateCTM(maContextHolder.get(), 0, nScaledHeight);
210 CGContextScaleCTM(maContextHolder.get(), 1.0, -1.0);
211 CGContextSetFillColorSpace(maContextHolder.get(), GetSalData()->mxRGBSpace);
212 CGContextSetStrokeColorSpace(maContextHolder.get(), GetSalData()->mxRGBSpace);
213 // apply a scale matrix so everything is auto-magically scaled
214 CGContextScaleCTM(maContextHolder.get(), fScale, fScale);
215 maContextHolder.saveState();
216 setState();
217
218 // re-enable XOR emulation for the new context
219 if (mpXorEmulation)
220 mpXorEmulation->SetTarget(mnWidth, mnHeight, mnBitmapDepth, maContextHolder.get(), maLayer.get());
221 }
222 }
223 }
224
225 SAL_WARN_IF(!maContextHolder.isSet() && !mbPrinter, "vcl", "<<<WARNING>>> AquaSalGraphics::CheckContext() FAILED!!!!");
226
227 return maContextHolder.isSet();
228 }
229
230 /**
231 * Blit the contents of our internal maLayer state to the
232 * associated window, if any; cf. drawRect event handling
233 * on the frame.
234 */
UpdateWindow(NSRect &)235 void AquaSalGraphics::UpdateWindow( NSRect& )
236 {
237 if (!maShared.mpFrame)
238 {
239 return;
240 }
241
242 NSGraphicsContext* pContext = [NSGraphicsContext currentContext];
243 if (maShared.maLayer.isSet() && pContext != nullptr)
244 {
245 CGContextHolder rCGContextHolder([pContext CGContext]);
246
247 rCGContextHolder.saveState();
248
249 CGMutablePathRef rClip = maShared.mpFrame->getClipPath();
250 if (rClip)
251 {
252 CGContextBeginPath(rCGContextHolder.get());
253 CGContextAddPath(rCGContextHolder.get(), rClip );
254 CGContextClip(rCGContextHolder.get());
255 }
256
257 maShared.applyXorContext();
258
259 const CGSize aSize = maShared.maLayer.getSizePoints();
260 const CGRect aRect = CGRectMake(0, 0, aSize.width, aSize.height);
261 const CGRect aRectPoints = { CGPointZero, maShared.maLayer.getSizePixels() };
262 CGContextSetBlendMode(maShared.maCSContextHolder.get(), kCGBlendModeCopy);
263 CGContextDrawLayerInRect(maShared.maCSContextHolder.get(), aRectPoints, maShared.maLayer.get());
264
265 CGImageRef img = CGBitmapContextCreateImage(maShared.maCSContextHolder.get());
266 CGImageRef displayColorSpaceImage = CGImageCreateCopyWithColorSpace(img, [[maShared.mpFrame->getNSWindow() colorSpace] CGColorSpace]);
267 CGContextSetBlendMode(rCGContextHolder.get(), kCGBlendModeCopy);
268 CGContextDrawImage(rCGContextHolder.get(), aRect, displayColorSpaceImage);
269
270 CGImageRelease(img);
271 CGImageRelease(displayColorSpaceImage);
272
273 rCGContextHolder.restoreState();
274 }
275 else
276 {
277 SAL_WARN_IF(!maShared.mpFrame->mbInitShow, "vcl", "UpdateWindow called on uneligible graphics");
278 }
279 }
280
281 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
282