Kaydet (Commit) 959e8ae7 authored tarafından Tomaž Vajngerl's avatar Tomaž Vajngerl Kaydeden (comit) Tomaž Vajngerl

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: 's avatarTomaž Vajngerl <quikee@gmail.com>
üst 3b67ad5f
......@@ -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
......
......@@ -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())
......
......@@ -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
{
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment