tdf#138122 Add window scaling for retina displays on macOS

(1) Remove hack to make application look as if being linked against SDK 10.13

(2) Use quad storage size on virtual devices for displaying on retina displays thereafter

(3) Apply workaround to downsample bitmaps from scaled layers (to be implemented)

(4) Disable dark mode (to be implemented)

(5) Provide new environment variables:

VCL_MACOS_FORCE_WINDOW_SCALING:
window scaling on non retina displays

VCL_MACOS_FORCE_DARK_MODE:
enable dark mode (macOS 10.14, iOS 13 and newer)

VCL_MACOS_USE_SYSTEM_APPEARANCE:
use light mode or dark mode (macOS 10.14, iOS 13 and newer) as configured by system preferences

Change-Id: I99877cd62a98cb91bcbf27af62b043c31c5f5fc9
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/109072
Tested-by: Jenkins
Reviewed-by: Tor Lillqvist <tml@collabora.com>
This commit is contained in:
Thorsten Wagner
2021-01-17 19:21:15 +01:00
committed by Tor Lillqvist
parent 5bd90f7f58
commit 1a16762531
8 changed files with 264 additions and 297 deletions
-12
View File
@@ -23,18 +23,6 @@ $(eval $(call gb_Executable_add_cobjects,soffice_bin,\
desktop/source/app/main \
))
ifeq ($(OS),MACOSX)
# At least when building against SDK 10.15, changing the LC_VERSION_MIN_MACOSX load command's sdk
# value from 10.15 to "n/a" (i.e., 0.0.0) is necessary to avoid blurry text in the LO UI (see
# <https://github.com/llvm/llvm-project/commit/25ce33a6e4f3b13732c0f851e68390dc2acb9123>
# "[driver][darwin] Pass -platform_version flag to the linker instead of the
# -<platform>_version_min flag", clang/test/Driver/darwin-ld-platform-version-macos.c in particular,
# for the -platform_version that Clang passes by default to new-enough ld):
$(eval $(call gb_Executable_add_ldflags,soffice_bin, \
-Xlinker -platform_version -Xlinker macos -Xlinker $(MACOSX_DEPLOYMENT_TARGET) -Xlinker 0.0.0 \
))
endif
ifeq ($(OS),WNT)
$(eval $(call gb_Executable_set_targettype_gui,soffice_bin,NO))
+12 -6
View File
@@ -174,6 +174,7 @@ class AquaSalGraphics : public SalGraphics
#ifdef MACOSX
/// is this a window graphics
bool mbWindow;
bool mbWindowScaling;
#else // IOS
@@ -189,10 +190,10 @@ public:
bool IsPenVisible() const { return maLineColor.IsVisible(); }
bool IsBrushVisible() const { return maFillColor.IsVisible(); }
float GetWindowScaling();
void SetWindowGraphics( AquaSalFrame* pFrame );
void SetPrinterGraphics(
CGContextRef, sal_Int32 nRealDPIX, sal_Int32 nRealDPIY );
void SetVirDevGraphics(CGLayerHolder const & rLayer, CGContextRef, int nBitDepth = 0);
void SetPrinterGraphics(CGContextRef, sal_Int32 nRealDPIX, sal_Int32 nRealDPIY);
void SetVirDevGraphics(CGLayerHolder const &rLayer, CGContextRef, int nBitDepth = 0);
#ifdef MACOSX
void initResolution( NSWindow* );
void copyResolution( AquaSalGraphics& );
@@ -299,9 +300,14 @@ public:
virtual bool drawAlphaRect( tools::Long nX, tools::Long nY, tools::Long nWidth,
tools::Long nHeight, sal_uInt8 nTransparency ) override;
// native widget rendering methods that require mirroring
#ifdef MACOSX
protected:
virtual void copyScaledArea( tools::Long nDestX, tools::Long nDestY, tools::Long nSrcX, tools::Long nSrcY,
tools::Long nSrcWidth, tools::Long nSrcHeight, SalGraphics* pSrcGraphics );
// native widget rendering methods that require mirroring
#ifdef MACOSX
virtual bool isNativeControlSupported( ControlType nType, ControlPart nPart ) override;
virtual bool hitTestNativeControl( ControlType nType, ControlPart nPart, const tools::Rectangle& rControlRegion,
@@ -313,9 +319,9 @@ protected:
const ImplControlValue& aValue, const OUString& aCaption,
tools::Rectangle &rNativeBoundingRegion, tools::Rectangle &rNativeContentRegion ) override;
public:
#endif
public:
// get device resolution
virtual void GetResolution( sal_Int32& rDPIX, sal_Int32& rDPIY ) override;
// get the depth of the device
+15
View File
@@ -302,6 +302,21 @@ VCLPLUG_OSX_PUBLIC SalInstance* create_SalInstance()
// put cocoa into multithreaded mode
[NSThread detachNewThreadSelector:@selector(enableCocoaThreads:) toTarget:[[CocoaThreadEnabler alloc] init] withObject:nil];
// Dark mode is disabled as long as it is not implemented completely. For development purposes, it may be controlled by
// environment variables: VCL_MACOS_FORCE_DARK_MODE enable dark mode independent of system settings,
// VCL_MACOS_USE_SYSTEM_APPEARANCE to use system settings (light mode oder dark mode as configured within system preferences).
// TODO: After implementation of dark mode, this code has to be removed.
if (getenv("VCL_MACOS_FORCE_DARK_MODE"))
{
if (@available(macOS 10.14, iOS 13, *))
[NSApp setAppearance: [NSAppearance appearanceNamed: NSAppearanceNameDarkAqua]];
}
else
if (!getenv("VCL_MACOS_USE_SYSTEM_APPEARANCE"))
[NSApp setAppearance: [NSAppearance appearanceNamed: NSAppearanceNameAqua]];
// activate our delegate methods
[NSApp setDelegate: NSApp];
+15 -7
View File
@@ -71,6 +71,10 @@ QuartzSalBitmap::~QuartzSalBitmap()
bool QuartzSalBitmap::Create(CGLayerHolder const & rLayerHolder, int nBitmapBits, int nX, int nY, int nWidth, int nHeight, bool bFlipped)
{
// TODO: Bitmaps from scaled layers are reverted to single precision. This is a workaround only unless bitmaps with precision of
// source layer are implemented.
SAL_WARN_IF(!rLayerHolder.isSet(), "vcl", "QuartzSalBitmap::Create() from non-layered context");
// sanitize input parameters
@@ -84,7 +88,10 @@ bool QuartzSalBitmap::Create(CGLayerHolder const & rLayerHolder, int nBitmapBits
nY = 0;
}
const CGSize aLayerSize = CGLayerGetSize(rLayerHolder.get());
CGSize aLayerSize = CGLayerGetSize(rLayerHolder.get());
const float fScale = rLayerHolder.getScale();
aLayerSize.width /= fScale;
aLayerSize.height /= fScale;
if( nWidth >= static_cast<int>(aLayerSize.width) - nX )
nWidth = static_cast<int>(aLayerSize.width) - nX;
@@ -104,17 +111,18 @@ bool QuartzSalBitmap::Create(CGLayerHolder const & rLayerHolder, int nBitmapBits
CreateContext();
// copy layer content into the bitmap buffer
const CGPoint aSrcPoint = { static_cast<CGFloat>(-nX), static_cast<CGFloat>(-nY) };
if (maGraphicContext.isSet()) // remove warning
const CGPoint aSrcPoint = { static_cast<CGFloat>(-nX * fScale), static_cast<CGFloat>(-nY * fScale) };
if (maGraphicContext.isSet())
{
if( bFlipped )
{
CGContextTranslateCTM( maGraphicContext.get(), 0, +mnHeight );
CGContextScaleCTM( maGraphicContext.get(), +1, -1 );
CGContextTranslateCTM(maGraphicContext.get(), 0, +mnHeight);
CGContextScaleCTM(maGraphicContext.get(), +1, -1);
}
maGraphicContext.saveState();
CGContextScaleCTM(maGraphicContext.get(), 1 / fScale, 1 / fScale);
CGContextDrawLayerAtPoint(maGraphicContext.get(), aSrcPoint, rLayerHolder.get());
maGraphicContext.restoreState();
}
return true;
}
+8
View File
@@ -205,6 +205,14 @@ AquaSalGraphics::AquaSalGraphics()
, mbVirDev( false )
#ifdef MACOSX
, mbWindow( false )
// Window scaling independent from main display may be forced by setting VCL_MACOS_FORCE_WINDOW_SCALING environment variable. If
// unset window scaling from main display will be used. After implementation of full support of scaled displays window scaling
// will be set to 2.0f for macOS as default.
// TODO: After implementation of full support of scaled displays VCL_FORCE_WINDOW_SCALING control has to be removed.
, mbWindowScaling( getenv("VCL_MACOS_FORCE_WINDOW_SCALING") )
#else
, mbForeignContext( false )
#endif
+131 -190
View File
@@ -199,109 +199,6 @@ static void alignLinePoint( const Point* i_pIn, float& o_fX, float& o_fY )
o_fY = static_cast<float>(i_pIn->getY() ) + 0.5;
}
void AquaSalGraphics::copyBits( const SalTwoRect& rPosAry, SalGraphics *pSrcGraphics )
{
if( !pSrcGraphics )
{
pSrcGraphics = this;
}
//from unix salgdi2.cxx
//[FIXME] find a better way to prevent calc from crashing when width and height are negative
if( rPosAry.mnSrcWidth <= 0 ||
rPosAry.mnSrcHeight <= 0 ||
rPosAry.mnDestWidth <= 0 ||
rPosAry.mnDestHeight <= 0 )
{
return;
}
#ifdef IOS
// If called from idle layout, maContextHolder.get() is NULL, no idea what to do
if (!maContextHolder.isSet())
return;
#endif
// accelerate trivial operations
/*const*/ AquaSalGraphics* pSrc = static_cast<AquaSalGraphics*>(pSrcGraphics);
const bool bSameGraphics = (this == pSrc)
#ifdef MACOSX
|| (mbWindow && mpFrame && pSrc->mbWindow && (mpFrame == pSrc->mpFrame))
#endif
;
if( bSameGraphics &&
(rPosAry.mnSrcWidth == rPosAry.mnDestWidth) &&
(rPosAry.mnSrcHeight == rPosAry.mnDestHeight))
{
// short circuit if there is nothing to do
if( (rPosAry.mnSrcX == rPosAry.mnDestX) &&
(rPosAry.mnSrcY == rPosAry.mnDestY))
{
return;
}
// use copyArea() if source and destination context are identical
copyArea( rPosAry.mnDestX, rPosAry.mnDestY, rPosAry.mnSrcX, rPosAry.mnSrcY,
rPosAry.mnSrcWidth, rPosAry.mnSrcHeight, false/*bWindowInvalidate*/ );
return;
}
ApplyXorContext();
pSrc->ApplyXorContext();
SAL_WARN_IF (!pSrc->maLayer.isSet(), "vcl.quartz",
"AquaSalGraphics::copyBits() from non-layered graphics this=" << this);
const CGPoint aDstPoint = CGPointMake(+rPosAry.mnDestX - rPosAry.mnSrcX, rPosAry.mnDestY - rPosAry.mnSrcY);
if ((rPosAry.mnSrcWidth == rPosAry.mnDestWidth &&
rPosAry.mnSrcHeight == rPosAry.mnDestHeight) &&
(!mnBitmapDepth || (aDstPoint.x + pSrc->mnWidth) <= mnWidth)
&& pSrc->maLayer.isSet()) // workaround for a Quartz crash
{
// in XOR mode the drawing context is redirected to the XOR mask
// if source and target are identical then copyBits() paints onto the target context though
CGContextHolder aCopyContext = maContextHolder;
if( mpXorEmulation && mpXorEmulation->IsEnabled() )
{
if( pSrcGraphics == this )
{
aCopyContext.set(mpXorEmulation->GetTargetContext());
}
}
aCopyContext.saveState();
const CGRect aDstRect = CGRectMake(rPosAry.mnDestX, rPosAry.mnDestY, rPosAry.mnDestWidth, rPosAry.mnDestHeight);
CGContextClipToRect(aCopyContext.get(), aDstRect);
// draw at new destination
// NOTE: flipped drawing gets disabled for this, else the subimage would be drawn upside down
if( pSrc->IsFlipped() )
{
CGContextTranslateCTM( aCopyContext.get(), 0, +mnHeight );
CGContextScaleCTM( aCopyContext.get(), +1, -1 );
}
// TODO: pSrc->size() != this->size()
CGContextDrawLayerAtPoint(aCopyContext.get(), aDstPoint, pSrc->maLayer.get());
aCopyContext.restoreState();
// mark the destination rectangle as updated
RefreshRect( aDstRect );
}
else
{
std::shared_ptr<SalBitmap> pBitmap = pSrc->getBitmap( rPosAry.mnSrcX, rPosAry.mnSrcY,
rPosAry.mnSrcWidth, rPosAry.mnSrcHeight );
if( pBitmap )
{
SalTwoRect aPosAry( rPosAry );
aPosAry.mnSrcX = 0;
aPosAry.mnSrcY = 0;
drawBitmap( aPosAry, *pBitmap );
}
}
}
static void DrawPattern50( void*, CGContextRef rContext )
{
static const CGRect aRects[2] = { { {0,0}, { 2, 2 } }, { { 2, 2 }, { 2, 2 } } };
@@ -370,83 +267,115 @@ void AquaSalGraphics::ApplyXorContext()
}
}
void AquaSalGraphics::copyArea(
tools::Long nDstX, tools::Long nDstY,tools::Long nSrcX, tools::Long nSrcY,
tools::Long nSrcWidth, tools::Long nSrcHeight, bool /*bWindowInvalidate*/ )
void AquaSalGraphics::copyBits(const SalTwoRect &rPosAry, SalGraphics *pSrcGraphics)
{
SAL_WARN_IF (!maLayer.isSet(), "vcl.quartz",
"AquaSalGraphics::copyArea() for non-layered graphics this=" << this);
#ifdef IOS
if (!maLayer.isSet())
if (!pSrcGraphics)
pSrcGraphics = this;
AquaSalGraphics *pSrc = static_cast<AquaSalGraphics*>(pSrcGraphics);
if (rPosAry.mnSrcWidth <= 0 || rPosAry.mnSrcHeight <= 0 || rPosAry.mnDestWidth <= 0 || rPosAry.mnDestHeight <= 0)
return;
#endif
if (!maContextHolder.isSet())
return;
SAL_WARN_IF (!pSrc->maLayer.isSet(), "vcl.quartz", "AquaSalGraphics::copyBits() from non-layered graphics this=" << this);
// Layered graphics are copied by AquaSalGraphics::copyScaledArea() which is able to consider the layer's scaling.
if (pSrc->maLayer.isSet())
copyScaledArea(rPosAry.mnDestX, rPosAry.mnDestY, rPosAry.mnSrcX, rPosAry.mnSrcY,
rPosAry.mnSrcWidth, rPosAry.mnSrcHeight, pSrcGraphics);
else
{
ApplyXorContext();
pSrc->ApplyXorContext();
std::shared_ptr<SalBitmap> pBitmap = pSrc->getBitmap(rPosAry.mnSrcX, rPosAry.mnSrcY,
rPosAry.mnSrcWidth, rPosAry.mnSrcHeight);
if (pBitmap)
{
SalTwoRect aPosAry(rPosAry);
aPosAry.mnSrcX = 0;
aPosAry.mnSrcY = 0;
drawBitmap(aPosAry, *pBitmap);
}
}
}
void AquaSalGraphics::copyArea(tools::Long nDstX, tools::Long nDstY,tools::Long nSrcX, tools::Long nSrcY,
tools::Long nSrcWidth, tools::Long nSrcHeight, bool)
{
if (!maContextHolder.isSet())
return;
// Functionality is implemented in protected member function AquaSalGraphics::copyScaledArea() which requires an additional
// parameter of type SalGraphics to be used in AquaSalGraphics::copyBits() too.
copyScaledArea(nDstX, nDstY, nSrcX, nSrcY, nSrcWidth, nSrcHeight, this);
}
void AquaSalGraphics::copyScaledArea(tools::Long nDstX, tools::Long nDstY,tools::Long nSrcX, tools::Long nSrcY,
tools::Long nSrcWidth, tools::Long nSrcHeight, SalGraphics *pSrcGraphics)
{
if (!pSrcGraphics)
pSrcGraphics = this;
AquaSalGraphics *pSrc = static_cast<AquaSalGraphics*>(pSrcGraphics);
SAL_WARN_IF(!maLayer.isSet(), "vcl.quartz",
"AquaSalGraphics::copyScaledArea() without graphics context or for non-layered graphics this=" << this);
if (!maContextHolder.isSet() || !maLayer.isSet())
return;
// Determine scaled geometry of source and target area assuming source and target area have the same scale
float fScale = maLayer.getScale();
CGFloat nScaledSourceX = nSrcX * fScale;
CGFloat nScaledSourceY = nSrcY * fScale;
CGFloat nScaledTargetX = nDstX * fScale;
CGFloat nScaledTargetY = nDstY * fScale;
CGFloat nScaledSourceWidth = nSrcWidth * fScale;
CGFloat nScaledSourceHeight = nSrcHeight * fScale;
tools::Long nScaledSourceX = nSrcX * fScale;
tools::Long nScaledSourceY = nSrcY * fScale;
tools::Long nScaledTargetX = nDstX * fScale;
tools::Long nScaledTargetY = nDstY * fScale;
tools::Long nScaledSourceWidth = nSrcWidth * fScale;
tools::Long nScaledSourceHeight = nSrcHeight * fScale;
// Apply XOR context and get copy context from current graphics context or XOR context
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() )
{
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);
// Set scale matrix of copy context to consider layer scaling
// 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
CGContextScaleCTM(xCopyContext, 1 / fScale, 1 / fScale);
CGLayerHolder sSourceLayerHolder(maLayer);
// Creating an additional layer is required for drawing with the required scale and extent at the drawing destination
// thereafter.
CGLayerHolder aSourceLayerHolder(pSrc->maLayer);
const CGSize aSourceSize = CGSizeMake(nScaledSourceWidth, nScaledSourceHeight);
aSourceLayerHolder.set(CGLayerCreateWithContext(xCopyContext, aSourceSize, nullptr));
const CGContextRef xSourceContext = CGLayerGetContext(aSourceLayerHolder.get());
CGPoint aSrcPoint = CGPointMake(-nScaledSourceX, -nScaledSourceY);
if (pSrc->IsFlipped())
{
const CGSize aSrcSize = CGSizeMake(nScaledSourceWidth, nScaledSourceHeight);
sSourceLayerHolder.set(CGLayerCreateWithContext(xCopyContext, aSrcSize, nullptr));
const CGContextRef xSrcContext = CGLayerGetContext(sSourceLayerHolder.get());
CGPoint aSrcPoint = CGPointMake(-nScaledSourceX, -nScaledSourceY);
if( IsFlipped() )
{
CGContextTranslateCTM( xSrcContext, 0, +nScaledSourceHeight );
CGContextScaleCTM( xSrcContext, +1, -1 );
aSrcPoint.y = (nScaledSourceY + nScaledSourceHeight) - (mnHeight * fScale);
}
CGContextSetBlendMode(xSrcContext, kCGBlendModeCopy);
CGContextDrawLayerAtPoint(xSrcContext, aSrcPoint, maLayer.get());
CGContextTranslateCTM(xSourceContext, 0, nScaledSourceHeight);
CGContextScaleCTM(xSourceContext, 1, -1);
aSrcPoint.y = nScaledSourceY + nScaledSourceHeight - mnHeight * fScale;
}
CGContextSetBlendMode(xSourceContext, kCGBlendModeCopy);
CGContextDrawLayerAtPoint(xSourceContext, aSrcPoint, pSrc->maLayer.get());
// Copy source area from additional layer to traget area
// draw at new destination
const CGRect aTargetRect = CGRectMake(nScaledTargetX, nScaledTargetY, nScaledSourceWidth, nScaledSourceHeight);
CGContextSetBlendMode(xCopyContext, kCGBlendModeCopy);
CGContextDrawLayerInRect(xCopyContext, aTargetRect, sSourceLayerHolder.get());
CGContextDrawLayerInRect(xCopyContext, aTargetRect, aSourceLayerHolder.get());
// Housekeeping on exit
maContextHolder.restoreState();
// cleanup
if (sSourceLayerHolder.get() != maLayer.get())
{
CGLayerRelease(sSourceLayerHolder.get());
}
// mark the destination rectangle as updated
RefreshRect( nDstX, nDstY, nSrcWidth, nSrcHeight );
if (aSourceLayerHolder.get() != maLayer.get())
CGLayerRelease(aSourceLayerHolder.get());
RefreshRect(nDstX, nDstY, nSrcWidth, nSrcHeight);
}
#ifndef IOS
@@ -1897,62 +1826,74 @@ bool XorEmulation::UpdateTarget()
return true;
}
void AquaSalGraphics::SetVirDevGraphics(CGLayerHolder const & rLayer, CGContextRef xContext,
int nBitmapDepth)
void AquaSalGraphics::SetVirDevGraphics(CGLayerHolder const &rLayer, CGContextRef xContext, int nBitmapDepth)
{
SAL_INFO( "vcl.quartz", "SetVirDevGraphics() this=" << this << " layer=" << rLayer.get() << " context=" << xContext );
SAL_INFO("vcl.quartz", "SetVirDevGraphics() this=" << this << " layer=" << rLayer.get() << " context=" << xContext);
#ifndef IOS
// Set member variables
InvalidateContext();
mbWindow = false;
#endif
mbPrinter = false;
mbVirDev = true;
// set graphics properties
maLayer = rLayer;
maContextHolder.set(xContext);
mnBitmapDepth = nBitmapDepth;
#ifdef IOS
mbForeignContext = xContext != NULL;
#endif
// return early if the virdev is being destroyed
if( !xContext )
return;
// Get size and scale from layer if set else from bitmap and AquaSalGraphics::GetWindowScaling(), which is used to determine
// scaling for direct graphics output too
// get new graphics properties
if (!maLayer.isSet())
CGSize aSize;
float fScale;
if (maLayer.isSet())
{
mnWidth = CGBitmapContextGetWidth( maContextHolder.get() );
mnHeight = CGBitmapContextGetHeight( maContextHolder.get() );
maContextHolder.set(CGLayerGetContext(maLayer.get()));
aSize = CGLayerGetSize(maLayer.get());
fScale = maLayer.getScale();
}
else
{
const CGSize aSize = CGLayerGetSize(maLayer.get());
mnWidth = static_cast<int>(aSize.width);
mnHeight = static_cast<int>(aSize.height);
maContextHolder.set(xContext);
if (!xContext)
return;
aSize.width = CGBitmapContextGetWidth(xContext);
aSize.height = CGBitmapContextGetHeight(xContext);
fScale = GetWindowScaling();
}
mnWidth = aSize.width / fScale;
mnHeight = aSize.height / fScale;
// prepare graphics for drawing
const CGColorSpaceRef aCGColorSpace = GetSalData()->mxRGBSpace;
CGContextSetFillColorSpace( maContextHolder.get(), aCGColorSpace );
CGContextSetStrokeColorSpace( maContextHolder.get(), aCGColorSpace );
// Set color space for fill and stroke
// re-enable XorEmulation for the new context
if( mpXorEmulation )
CGColorSpaceRef aColorSpace = GetSalData()->mxRGBSpace;
CGContextSetFillColorSpace(maContextHolder.get(), aColorSpace);
CGContextSetStrokeColorSpace(maContextHolder.get(), aColorSpace);
// Apply scale matrix to virtual device graphics
CGContextScaleCTM(maContextHolder.get(), fScale, fScale);
// Apply XOR emulation if required
if (mpXorEmulation)
{
mpXorEmulation->SetTarget(mnWidth, mnHeight, mnBitmapDepth, maContextHolder.get(), maLayer.get());
if( mpXorEmulation->IsEnabled() )
{
if (mpXorEmulation->IsEnabled())
maContextHolder.set(mpXorEmulation->GetMaskContext());
}
}
// initialize stack of CGContext states
// Housekeeping on exit
maContextHolder.saveState();
SetState();
SAL_INFO("vcl.quartz", "SetVirDevGraphics() this=" << this <<
" (" << mnWidth << "x" << mnHeight << ") fScale=" << fScale << " mnBitmapDepth=" << mnBitmapDepth);
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
+34 -7
View File
@@ -35,6 +35,39 @@
#include <osx/salframe.h>
#include <osx/saldata.hxx>
float AquaSalGraphics::GetWindowScaling()
{
float fScale = 1.0f;
#ifdef MACOSX
// Window scaling independent from main display may be forced by setting VCL_MACOS_FORCE_WINDOW_SCALING environment variable
// whose setting is stored in mbWindowScaling. After implementation of full support of scaled displays window scaling will be
// set to 2.0f for macOS as default. This will allow moving of windows between non retina and retina displays without blurry
// text and graphics.
// TODO: After implementation of full support of scaled displays code has to be modified to set a scaling of 2.0f as default.
if (mbWindowScaling)
{
fScale = 2.0f;
return fScale;
}
#endif
AquaSalFrame *pSalFrame = mpFrame;
if (!pSalFrame)
pSalFrame = static_cast<AquaSalFrame *>(GetSalData()->mpInstance->anyFrame());
if (pSalFrame)
{
NSWindow *pNSWindow = pSalFrame->getNSWindow();
if (pNSWindow)
fScale = [pNSWindow backingScaleFactor];
}
return fScale;
}
void AquaSalGraphics::SetWindowGraphics( AquaSalFrame* pFrame )
{
mpFrame = pFrame;
@@ -116,13 +149,7 @@ 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];
const float fScale = GetWindowScaling();
CGLayerRef rReleaseLayer = nullptr;
// check if a new drawing context is needed (e.g. after a resize)
+49 -75
View File
@@ -104,6 +104,10 @@ AquaSalVirtualDevice::AquaSalVirtualDevice(
}
mpGraphics->SetVirDevGraphics(maLayer, pData->rCGContext);
SAL_INFO("vcl.virdev", "AquaSalVirtualDevice::AquaSalVirtualDevice() this=" << this <<
" (" << nDX << "x" << nDY << ") mbForeignContext=" << (mbForeignContext ? "YES" : "NO"));
}
else
{
@@ -180,8 +184,6 @@ void AquaSalVirtualDevice::Destroy()
if (maBitmapContext.isSet())
{
void* pRawData = CGBitmapContextGetData(maBitmapContext.get());
std::free(pRawData);
CGContextRelease(maBitmapContext.get());
maBitmapContext.set(nullptr);
}
@@ -202,106 +204,78 @@ void AquaSalVirtualDevice::ReleaseGraphics( SalGraphics* )
mbGraphicsUsed = false;
}
bool AquaSalVirtualDevice::SetSize( tools::Long nDX, tools::Long nDY )
bool AquaSalVirtualDevice::SetSize(tools::Long nDX, tools::Long nDY)
{
SAL_INFO( "vcl.virdev", "AquaSalVirtualDevice::SetSize() this=" << this <<
" (" << nDX << "x" << nDY << ") mbForeignContext=" << (mbForeignContext ? "YES" : "NO"));
SAL_INFO("vcl.virdev", "AquaSalVirtualDevice::SetSize() this=" << this <<
" (" << nDX << "x" << nDY << ") mbForeignContext=" << (mbForeignContext ? "YES" : "NO"));
if( mbForeignContext )
{
// Do not delete/resize mxContext that we have received from outside VCL
// Do not delete/resize graphics context if it has been received from outside VCL
if (mbForeignContext)
return true;
}
// Do not delete/resize graphics context if no change of geometry has been requested
float fScale;
if (maLayer.isSet())
{
fScale = maLayer.getScale();
const CGSize aSize = CGLayerGetSize(maLayer.get());
if( (nDX == aSize.width) && (nDY == aSize.height) )
{
// Yay, we do not have to do anything :)
if ((nDX == aSize.width / fScale) && (nDY == aSize.height / fScale))
return true;
}
}
// Destroy graphics context if change of geometry has been requested
Destroy();
// Prepare new graphics context for initialization, use scaling independent of prior graphics context calculated by
// AquaSalGraphics::GetWindowScaling(), which is used to determine scaling for direct graphics output too
mnWidth = nDX;
mnHeight = nDY;
// create a CGLayer matching to the intended virdev usage
CGContextHolder xCGContextHolder;
if( mnBitmapDepth && (mnBitmapDepth < 16) )
fScale = mpGraphics->GetWindowScaling();
CGColorSpaceRef aColorSpace;
uint32_t nFlags;
if (mnBitmapDepth && (mnBitmapDepth < 16))
{
mnBitmapDepth = 8; // TODO: are 1bit vdevs worth it?
const int nBytesPerRow = (mnBitmapDepth * nDX + 7) / 8;
void* pRawData = std::malloc( nBytesPerRow * nDY );
maBitmapContext.set(CGBitmapContextCreate( pRawData, nDX, nDY,
mnBitmapDepth, nBytesPerRow,
GetSalData()->mxGraySpace, kCGImageAlphaNone));
xCGContextHolder = maBitmapContext;
mnBitmapDepth = 8;
aColorSpace = GetSalData()->mxGraySpace;
nFlags = kCGImageAlphaNone;
}
else
{
#ifdef MACOSX
// default to a NSView target context
AquaSalFrame* pSalFrame = mpGraphics->getGraphicsFrame();
if( !pSalFrame || !AquaSalFrame::isAlive( pSalFrame ))
{
pSalFrame = static_cast<AquaSalFrame*>( GetSalData()->mpInstance->anyFrame() );
if ( pSalFrame )
// update the frame reference
mpGraphics->setGraphicsFrame( pSalFrame );
}
if( pSalFrame )
{
// #i91990#
NSWindow* pNSWindow = pSalFrame->getNSWindow();
if ( pNSWindow )
{
NSGraphicsContext* pNSContext = [NSGraphicsContext graphicsContextWithWindow: pNSWindow];
if( pNSContext )
{
xCGContextHolder.set([pNSContext CGContext]);
}
}
}
#endif
mnBitmapDepth = 32;
aColorSpace = GetSalData()->mxRGBSpace;
if (!xCGContextHolder.isSet())
{
// assert(Application::IsBitmapRendering());
mnBitmapDepth = 32;
const int nBytesPerRow = (mnBitmapDepth * nDX) / 8;
void* pRawData = std::malloc( nBytesPerRow * nDY );
#ifdef MACOSX
const int nFlags = kCGImageAlphaNoneSkipFirst;
nFlags = kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host;
#else
const int nFlags = kCGImageAlphaNoneSkipFirst | kCGImageByteOrder32Little;
nFlags = kCGImageAlphaNoneSkipFirst | kCGImageByteOrder32Little;
#endif
maBitmapContext.set(CGBitmapContextCreate(pRawData, nDX, nDY, 8, nBytesPerRow,
GetSalData()->mxRGBSpace, nFlags));
xCGContextHolder = maBitmapContext;
}
}
SAL_WARN_IF(!xCGContextHolder.isSet(), "vcl.quartz", "No context");
// Allocate buffer for virtual device graphics as bitmap context to store graphics with highest required (scaled) resolution
const CGSize aNewSize = { static_cast<CGFloat>(nDX), static_cast<CGFloat>(nDY) };
maLayer.set(CGLayerCreateWithContext(xCGContextHolder.get(), aNewSize, nullptr));
size_t nScaledWidth = mnWidth * fScale;
size_t nScaledHeight = mnHeight * fScale;
size_t nBytesPerRow = mnBitmapDepth * nScaledWidth / 8;
maBitmapContext.set(CGBitmapContextCreate(nullptr, nScaledWidth, nScaledHeight, 8, nBytesPerRow, aColorSpace, nFlags));
if (maLayer.isSet() && mpGraphics)
{
// get the matching Quartz context
CGContextRef xDrawContext = CGLayerGetContext( maLayer.get() );
SAL_INFO("vcl.virdev", "AquaSalVirtualDevice::SetSize() this=" << this <<
" fScale=" << fScale << " mnBitmapDepth=" << mnBitmapDepth);
// Here we pass the CGLayerRef that the CGLayerHolder maLayer holds as the first parameter
// to SetVirDevGraphics(). That parameter is of type CGLayerHolder, so what we actually pass
// is an implicitly constructed *separate* CGLayerHolder. Is that what we want? No idea.
// Possibly we could pass just maLayer as such? But doing that does not fix tdf#138122.
mpGraphics->SetVirDevGraphics(maLayer.get(), xDrawContext, mnBitmapDepth);
}
CGSize aLayerSize = { static_cast<CGFloat>(nScaledWidth), static_cast<CGFloat>(nScaledHeight) };
maLayer.set(CGLayerCreateWithContext(maBitmapContext.get(), aLayerSize, nullptr));
maLayer.setScale(fScale);
mpGraphics->SetVirDevGraphics(maLayer, CGLayerGetContext(maLayer.get()), mnBitmapDepth);
SAL_WARN_IF(!maBitmapContext.isSet(), "vcl.quartz", "No context");
return maLayer.isSet();
}