[Libreoffice-commits] core.git: vcl/inc vcl/quartz

Tomaž Vajngerl (via logerrit) logerrit at kemper.freedesktop.org
Tue May 21 23:35:35 UTC 2019


 vcl/inc/quartz/CGHelpers.hxx |   36 +++++++++++++
 vcl/quartz/salgdicommon.cxx  |   37 ++++++++++----
 vcl/quartz/salgdiutils.cxx   |  112 +++++++++++++++++++++++--------------------
 3 files changed, 124 insertions(+), 61 deletions(-)

New commits:
commit 959e8ae7ea33ce94dd80ee8ea172b6db64593873
Author:     Tomaž Vajngerl <tomaz.vajngerl at collabora.co.uk>
AuthorDate: Tue May 21 14:46:56 2019 +0900
Commit:     Tomaž Vajngerl <quikee at gmail.com>
CommitDate: Wed May 22 01:33:59 2019 +0200

    tdf#124271 use the bitmap context, handle scaling
    
    The problem with latest macOS versions is that creating a graphic
    context with window (NSGraphicsContext graphicsContextWithWindow:)
    only works when actually drawing and otherwise it returns null.
    This caused problems before in AquaVrtualDevice, but we also use
    this when creating a device backing storage. This interestingly
    caused slowdowns and eventual crash, but the backtrace looked
    very misterious as it didn't crash because of a nullptr, but
    it halted all drawing commands and it crashed because of that.
    
    This changes the graphic context with a bitmap context, as it
    was already done in VirtualDevice and use that instead. The
    problem with a bitmap context is that we need to handle HiDPI
    scaling by ourselves now.
    
    LayerHolder was extended to store the scaling information of the
    layer (and its underlaying bitmap context) and provides methods
    that get the size of the layer in pixels or in scaling independent
    points (which is just size in pixels multiplied by the scaling
    factor).
    
    An known issue is that VirtualDevice also needs to take scaling
    into account, which it currently doesn't, so the text is still
    blurry on a HiDPI screen, but that was already true previously
    and is something that will be done in a different change.
    
    Change-Id: I8e10c518ecba285125746bd20525c4cb5ca67279
    Reviewed-on: https://gerrit.libreoffice.org/72663
    Tested-by: Jenkins
    Reviewed-by: Tomaž Vajngerl <quikee at gmail.com>

diff --git a/vcl/inc/quartz/CGHelpers.hxx b/vcl/inc/quartz/CGHelpers.hxx
index f2d024dc9299..5d44e835d99b 100644
--- a/vcl/inc/quartz/CGHelpers.hxx
+++ b/vcl/inc/quartz/CGHelpers.hxx
@@ -22,22 +22,56 @@ class CGLayerHolder
 private:
     CGLayerRef mpLayer;
 
+    // Layer's scaling factor
+    float mfScale;
+
 public:
     CGLayerHolder()
         : mpLayer(nullptr)
+        , mfScale(1.0)
     {
     }
 
-    CGLayerHolder(CGLayerRef pLayer)
+    CGLayerHolder(CGLayerRef pLayer, float fScale = 1.0)
         : mpLayer(pLayer)
+        , mfScale(fScale)
     {
     }
 
+    // Just the size of the layer in pixels
+    CGSize getSizePixels() const
+    {
+        CGSize aSize;
+        if (mpLayer)
+        {
+            aSize = CGLayerGetSize(mpLayer);
+            SAL_INFO("vcl.cg", "CGLayerGetSize(" << mpLayer << ") = " << aSize);
+        }
+        return aSize;
+    }
+
+    // Size in points is size in pixels multiplied by the scaling factor
+    CGSize getSizePoints() const
+    {
+        CGSize aSize;
+        if (mpLayer)
+        {
+            const CGSize aLayerSize = getSizePixels();
+            aSize.width = aLayerSize.width / mfScale;
+            aSize.height = aLayerSize.height / mfScale;
+        }
+        return aSize;
+    }
+
     CGLayerRef get() const { return mpLayer; }
 
     bool isSet() const { return mpLayer != nullptr; }
 
     void set(CGLayerRef const& pLayer) { mpLayer = pLayer; }
+
+    float getScale() { return mfScale; }
+
+    void setScale(float fScale) { mfScale = fScale; }
 };
 
 class CGContextHolder
diff --git a/vcl/quartz/salgdicommon.cxx b/vcl/quartz/salgdicommon.cxx
index dd35917cd949..05e84b98b810 100644
--- a/vcl/quartz/salgdicommon.cxx
+++ b/vcl/quartz/salgdicommon.cxx
@@ -483,48 +483,67 @@ void AquaSalGraphics::copyArea( long nDstX, long nDstY,long nSrcX, long nSrcY,
     if (!maLayer.isSet())
         return;
 #endif
+    float fScale = maLayer.getScale();
+
+    long nScaledSourceX = nSrcX * fScale;
+    long nScaledSourceY = nSrcY * fScale;
+
+    long nScaledTargetX = nDstX * fScale;
+    long nScaledTargetY = nDstY * fScale;
+
+    long nScaledSourceWidth = nSrcWidth * fScale;
+    long nScaledSourceHeight = nSrcHeight * fScale;
 
     ApplyXorContext();
 
+    maContextHolder.saveState();
+
     // in XOR mode the drawing context is redirected to the XOR mask
     // copyArea() always works on the target context though
     CGContextRef xCopyContext = maContextHolder.get();
+
     if( mpXorEmulation && mpXorEmulation->IsEnabled() )
     {
         xCopyContext = mpXorEmulation->GetTargetContext();
     }
+
+    // If we have a scaled layer, we need to revert the scaling or else
+    // it will interfere with the coordinate calculation
+    CGContextScaleCTM(xCopyContext, 1.0 / fScale, 1.0 / fScale);
+
     // drawing a layer onto its own context causes trouble on OSX => copy it first
     // TODO: is it possible to get rid of this unneeded copy more often?
     //       e.g. on OSX>=10.5 only this situation causes problems:
     //          mnBitmapDepth && (aDstPoint.x + pSrc->mnWidth) > mnWidth
 
-    CGLayerHolder sSourceLayerHolder(maLayer.get());
-    // TODO: if( mnBitmapDepth > 0 )
+    CGLayerHolder sSourceLayerHolder(maLayer);
     {
-        const CGSize aSrcSize = CGSizeMake(nSrcWidth, nSrcHeight);
+        const CGSize aSrcSize = CGSizeMake(nScaledSourceWidth, nScaledSourceHeight);
         sSourceLayerHolder.set(CGLayerCreateWithContext(xCopyContext, aSrcSize, nullptr));
         SAL_INFO( "vcl.cg", "CGLayerCreateWithContext(" << xCopyContext << "," << aSrcSize << ",NULL) = " << sSourceLayerHolder.get());
 
         const CGContextRef xSrcContext = CGLayerGetContext(sSourceLayerHolder.get());
         SAL_INFO( "vcl.cg", "CGLayerGetContext(" << sSourceLayerHolder.get() << ") = " << xSrcContext);
 
-        CGPoint aSrcPoint = CGPointMake(-nSrcX, -nSrcY);
+        CGPoint aSrcPoint = CGPointMake(-nScaledSourceX, -nScaledSourceY);
         if( IsFlipped() )
         {
             SAL_INFO( "vcl.cg", "CGContextTranslateCTM(" << xSrcContext << ",0," << nSrcHeight << ")" );
-            CGContextTranslateCTM( xSrcContext, 0, +nSrcHeight );
+            CGContextTranslateCTM( xSrcContext, 0, +nScaledSourceHeight );
             SAL_INFO( "vcl.cg", "CGContextScaleCTM(" << xSrcContext << ",+1,-1)" );
             CGContextScaleCTM( xSrcContext, +1, -1 );
-            aSrcPoint.y = (nSrcY + nSrcHeight) - mnHeight;
+            aSrcPoint.y = (nScaledSourceY + nScaledSourceHeight) - (mnHeight * fScale);
         }
         SAL_INFO( "vcl.cg", "CGContextDrawLayerAtPoint(" << xSrcContext << "," << aSrcPoint << "," << maLayer.get() << ")" );
         CGContextDrawLayerAtPoint(xSrcContext, aSrcPoint, maLayer.get());
     }
 
     // draw at new destination
-    const CGPoint aDstPoint = CGPointMake(+nDstX, +nDstY);
-    SAL_INFO( "vcl.cg", "CGContextDrawLayerAtPoint(" << xCopyContext << "," << aDstPoint << "," << sSourceLayerHolder.get() << ")" );
-    CGContextDrawLayerAtPoint(xCopyContext, aDstPoint, sSourceLayerHolder.get());
+    const CGRect aTargetRect = CGRectMake(nScaledTargetX, nScaledTargetY, nScaledSourceWidth, nScaledSourceHeight);
+    SAL_INFO( "vcl.cg", "CGContextDrawLayerInRect(" << xCopyContext << "," << aTargetRect << "," << sSourceLayerHolder.get() << ")" );
+    CGContextDrawLayerInRect(xCopyContext, aTargetRect, sSourceLayerHolder.get());
+
+    maContextHolder.restoreState();
 
     // cleanup
     if (sSourceLayerHolder.get() != maLayer.get())
diff --git a/vcl/quartz/salgdiutils.cxx b/vcl/quartz/salgdiutils.cxx
index 4c13ad788183..4b0db0c901e9 100644
--- a/vcl/quartz/salgdiutils.cxx
+++ b/vcl/quartz/salgdiutils.cxx
@@ -98,6 +98,12 @@ bool AquaSalGraphics::CheckContext()
         const unsigned int nWidth = mpFrame->maGeometry.nWidth;
         const unsigned int nHeight = mpFrame->maGeometry.nHeight;
 
+        // Let's get the window scaling factor if possible, or use 1.0
+        // as the scaling factor.
+        float fScale = 1.0f;
+        if (mpFrame->getNSWindow())
+            fScale = [mpFrame->getNSWindow() backingScaleFactor];
+
         CGLayerRef rReleaseLayer = nullptr;
 
         // check if a new drawing context is needed (e.g. after a resize)
@@ -107,7 +113,9 @@ bool AquaSalGraphics::CheckContext()
             mnHeight = nHeight;
             // prepare to release the corresponding resources
             if (maLayer.isSet())
+            {
                 rReleaseLayer = maLayer.get();
+            }
             else if (maContextHolder.isSet())
             {
                 SAL_INFO("vcl.cg", "CGContextRelease(" << maContextHolder.get() << ")");
@@ -119,50 +127,49 @@ bool AquaSalGraphics::CheckContext()
 
         if (!maContextHolder.isSet())
         {
-            if (mpFrame->getNSWindow())
-            {
-                const CGSize aLayerSize = { static_cast<CGFloat>(nWidth), static_cast<CGFloat>(nHeight) };
-                NSGraphicsContext* pNSGContext = [NSGraphicsContext graphicsContextWithWindow: mpFrame->getNSWindow()];
-                CGContextRef xCGContext = [pNSGContext CGContext];
-                maLayer.set(CGLayerCreateWithContext(xCGContext, aLayerSize, nullptr));
-                SAL_INFO("vcl.cg", "CGLayerCreateWithContext(" << xCGContext << "," << aLayerSize << ",NULL) = " << maLayer.get());
-                if (maLayer.isSet())
-                {
-                    maContextHolder.set(CGLayerGetContext(maLayer.get()));
-                    SAL_INFO( "vcl.cg", "CGLayerGetContext(" << maLayer.get() << ") = " << maContextHolder.get() );
-                }
+            const int nBitmapDepth = 32;
+
+            float nScaledWidth = mnWidth * fScale;
+            float nScaledHeight = mnHeight * fScale;
+
+            const CGSize aLayerSize = { static_cast<CGFloat>(nScaledWidth), static_cast<CGFloat>(nScaledHeight) };
+
+            const int nBytesPerRow = (nBitmapDepth * nScaledWidth) / 8;
+            void* pRawData = std::malloc(nBytesPerRow * nScaledHeight);
+#ifdef MACOSX
+        const int nFlags = kCGImageAlphaNoneSkipFirst;
+#else
+        const int nFlags = kCGImageAlphaNoneSkipFirst | kCGImageByteOrder32Little;
+#endif
+            CGContextHolder aContextHolder(CGBitmapContextCreate(
+                pRawData, nScaledWidth, nScaledHeight, 8, nBytesPerRow, GetSalData()->mxRGBSpace, nFlags));
+
+            maLayer.set(CGLayerCreateWithContext(aContextHolder.get(), aLayerSize, nullptr));
+            maLayer.setScale(fScale);
+
+            CGContextRef xDrawContext = CGLayerGetContext(maLayer.get());
+            maContextHolder = xDrawContext;
 
-                if (rReleaseLayer)
+            if (rReleaseLayer)
+            {
+                // copy original layer to resized layer
+                if (maContextHolder.isSet())
                 {
-                    // copy original layer to resized layer
-                    if (maContextHolder.isSet())
-                    {
-                        SAL_INFO("vcl.cg", "CGContextDrawLayerAtPoint(" << maContextHolder.get() << "," << CGPointZero << "," << rReleaseLayer << ")");
-                        CGContextDrawLayerAtPoint(maContextHolder.get(), CGPointZero, rReleaseLayer);
-                    }
-                    SAL_INFO("vcl.cg", "CGLayerRelease(" << rReleaseLayer << ")");
-                    CGLayerRelease(rReleaseLayer);
+                    SAL_INFO("vcl.cg", "CGContextDrawLayerAtPoint(" << maContextHolder.get() << "," << CGPointZero << "," << rReleaseLayer << ")");
+                    CGContextDrawLayerAtPoint(maContextHolder.get(), CGPointZero, rReleaseLayer);
                 }
-            }
-            else
-            {
-                assert(Application::IsBitmapRendering());
-                const int nBitmapDepth = 32;
-                const int nBytesPerRow = (nBitmapDepth * mnWidth) / 8;
-                void* pRawData = std::malloc(nBytesPerRow * mnHeight);
-                const int nFlags = kCGImageAlphaNoneSkipFirst;
-                maContextHolder.set(CGBitmapContextCreate(pRawData, mnWidth, mnHeight, 8, nBytesPerRow,
-                                                  GetSalData()->mxRGBSpace, nFlags));
-                SAL_INFO("vcl.cg", "CGBitmapContextCreate(" << mnWidth << "x" << mnHeight
-                                   << "x" << nBitmapDepth << ") = " << maContextHolder.get());
+                SAL_INFO("vcl.cg", "CGLayerRelease(" << rReleaseLayer << ")");
+                CGLayerRelease(rReleaseLayer);
             }
 
             if (maContextHolder.isSet())
             {
-                CGContextTranslateCTM(maContextHolder.get(), 0, nHeight);
+                CGContextTranslateCTM(maContextHolder.get(), 0, nScaledHeight);
                 CGContextScaleCTM(maContextHolder.get(), 1.0, -1.0);
                 CGContextSetFillColorSpace(maContextHolder.get(), GetSalData()->mxRGBSpace);
                 CGContextSetStrokeColorSpace(maContextHolder.get(), GetSalData()->mxRGBSpace);
+                // apply a scale matrix so everything is auto-magically scaled
+                CGContextScaleCTM(maContextHolder.get(), fScale, fScale);
                 maContextHolder.saveState();
                 SetState();
 
@@ -173,7 +180,8 @@ bool AquaSalGraphics::CheckContext()
         }
     }
 
-    SAL_WARN_IF( !maContextHolder.get() && !mbPrinter, "vcl", "<<<WARNING>>> AquaSalGraphics::CheckContext() FAILED!!!!" );
+    SAL_WARN_IF(!maContextHolder.isSet() && !mbPrinter, "vcl", "<<<WARNING>>> AquaSalGraphics::CheckContext() FAILED!!!!");
+
     return maContextHolder.isSet();
 }
 
@@ -201,28 +209,30 @@ void AquaSalGraphics::UpdateWindow( NSRect& )
     NSGraphicsContext* pContext = [NSGraphicsContext currentContext];
     if (maLayer.isSet() && pContext != nullptr)
     {
-        CGContextRef rCGContext = [pContext CGContext];
-        SAL_INFO( "vcl.cg", "[[NSGraphicsContext currentContext] CGContext] = " << rCGContext );
+        CGContextHolder rCGContextHolder([pContext CGContext]);
+        SAL_INFO("vcl.cg", "[[NSGraphicsContext currentContext] CGContext] = " << rCGContextHolder.get());
+
+        rCGContextHolder.saveState();
 
         CGMutablePathRef rClip = mpFrame->getClipPath();
-        if( rClip )
+        if (rClip)
         {
-            CGContextSaveGState( rCGContext );
-            SAL_INFO( "vcl.cg", "CGContextBeginPath(" << rCGContext << ")" );
-            CGContextBeginPath( rCGContext );
-            SAL_INFO( "vcl.cg", "CGContextAddPath(" << rCGContext << "," << rClip << ")" );
-            CGContextAddPath( rCGContext, rClip );
-            SAL_INFO( "vcl.cg", "CGContextClip(" << rCGContext << ")" );
-            CGContextClip( rCGContext );
+            CGContextBeginPath(rCGContextHolder.get());
+            SAL_INFO( "vcl.cg", "CGContextAddPath(" << rCGContextHolder.get() << "," << rClip << ")" );
+            CGContextAddPath(rCGContextHolder.get(), rClip );
+            SAL_INFO( "vcl.cg", "CGContextClip(" << rCGContextHolder.get() << ")" );
+            CGContextClip(rCGContextHolder.get());
         }
 
         ApplyXorContext();
-        SAL_INFO( "vcl.cg", "CGContextDrawLayerAtPoint(" << rCGContext << "," << CGPointZero << "," << maLayer.get() << ")" );
-        CGContextDrawLayerAtPoint( rCGContext, CGPointZero, maLayer.get() );
-        if( rClip ) // cleanup clipping
-        {
-            CGContextRestoreGState( rCGContext );
-        }
+
+        const CGSize aSize = maLayer.getSizePoints();
+        const CGRect aRect = CGRectMake(0, 0, aSize.width,  aSize.height);
+
+        SAL_INFO( "vcl.cg", "CGContextDrawLayerInRect(" << rCGContextHolder.get() << "," << aRect << "," << maLayer.get() << ")" );
+        CGContextDrawLayerInRect(rCGContextHolder.get(), aRect, maLayer.get());
+
+        rCGContextHolder.restoreState();
     }
     else
     {


More information about the Libreoffice-commits mailing list