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