summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>2019-05-21 14:46:56 +0900
committerTomaž Vajngerl <quikee@gmail.com>2019-05-22 01:33:59 +0200
commit959e8ae7ea33ce94dd80ee8ea172b6db64593873 (patch)
treec2bd09cd7407f3e830b7552fbeee6fe710e5c142
parent3b67ad5f11714d6bea83ec8511f7723dbd999c56 (diff)
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@gmail.com>
-rw-r--r--vcl/inc/quartz/CGHelpers.hxx36
-rw-r--r--vcl/quartz/salgdicommon.cxx37
-rw-r--r--vcl/quartz/salgdiutils.cxx112
3 files changed, 124 insertions, 61 deletions
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
{