4924a9f73f
Change-Id: Iea920a34b11695aba32f15921018cf53f418d09a Reviewed-on: https://gerrit.libreoffice.org/c/core/+/174846 Tested-by: Jenkins Reviewed-by: Noel Grandin <noel.grandin@collabora.co.uk>
401 lines
14 KiB
C++
401 lines
14 KiB
C++
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
|
/*
|
|
* This file is part of the LibreOffice project.
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*
|
|
* This file incorporates work covered by the following license notice:
|
|
*
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed
|
|
* with this work for additional information regarding copyright
|
|
* ownership. The ASF licenses this file to you under the Apache
|
|
* License, Version 2.0 (the "License"); you may not use this file
|
|
* except in compliance with the License. You may obtain a copy of
|
|
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
|
|
*/
|
|
|
|
#include <sal/config.h>
|
|
|
|
#include <cstdint>
|
|
|
|
#include <sal/log.hxx>
|
|
|
|
#include <basegfx/polygon/b2dpolygon.hxx>
|
|
#include <basegfx/polygon/b2dpolygontools.hxx>
|
|
#include <basegfx/range/b2drectangle.hxx>
|
|
#include <basegfx/range/b2irange.hxx>
|
|
#include <basegfx/vector/b2ivector.hxx>
|
|
#include <vcl/svapp.hxx>
|
|
#include <vcl/skia/SkiaHelper.hxx>
|
|
|
|
#include <quartz/salgdi.h>
|
|
#include <quartz/utils.h>
|
|
#include <osx/salframe.h>
|
|
#include <osx/saldata.hxx>
|
|
|
|
#if HAVE_FEATURE_SKIA
|
|
#include <tools/window/mac/MacWindowInfo.h>
|
|
#include <skia/osx/gdiimpl.hxx>
|
|
#endif
|
|
|
|
static bool bTotalScreenBounds = false;
|
|
static NSRect aTotalScreenBounds = NSZeroRect;
|
|
|
|
// TODO: Scale will be set to 2.0f as default after implementation of full scaled display support . This will allow moving of
|
|
// windows between non retina and retina displays without blurry text and graphics. Static variables have to be removed thereafter.
|
|
|
|
// Currently scaled display support is not implemented for bitmaps. This will cause a slight performance degradation on displays
|
|
// with single precision. To preserve performance for now, window scaling is only activated if at least one display with double
|
|
// precision is present. Moving windows between displays is then possible without blurry text and graphics too. Adapting window
|
|
// scaling when displays are added while application is running is not supported.
|
|
|
|
static bool bWindowScaling = false;
|
|
static float fWindowScale = 1.0f;
|
|
|
|
namespace sal::aqua
|
|
{
|
|
NSRect getTotalScreenBounds()
|
|
{
|
|
if (!bTotalScreenBounds)
|
|
{
|
|
aTotalScreenBounds = NSZeroRect;
|
|
|
|
NSArray *aScreens = [NSScreen screens];
|
|
if (aScreens != nullptr)
|
|
{
|
|
for (NSScreen *aScreen : aScreens)
|
|
{
|
|
// Calculate total screen bounds
|
|
NSRect aScreenFrame = [aScreen frame];
|
|
if (!NSIsEmptyRect(aScreenFrame))
|
|
{
|
|
if (NSIsEmptyRect(aTotalScreenBounds))
|
|
aTotalScreenBounds = aScreenFrame;
|
|
else
|
|
aTotalScreenBounds = NSUnionRect( aScreenFrame, aTotalScreenBounds );
|
|
}
|
|
}
|
|
bTotalScreenBounds = true;
|
|
}
|
|
}
|
|
return aTotalScreenBounds;
|
|
}
|
|
|
|
void resetTotalScreenBounds()
|
|
{
|
|
bTotalScreenBounds = false;
|
|
getTotalScreenBounds();
|
|
}
|
|
|
|
float getWindowScaling()
|
|
{
|
|
// Related: tdf#147342 Any changes to this function must be copied to the
|
|
// sk_app::GetBackingScaleFactor() function in the following file:
|
|
// workdir/UnpackedTarball/skia/tools/sk_app/mac/WindowContextFactory_mac.h
|
|
if (!bWindowScaling)
|
|
{
|
|
NSArray *aScreens = [NSScreen screens];
|
|
if (aScreens != nullptr)
|
|
{
|
|
for (NSScreen *aScreen : aScreens)
|
|
{
|
|
float fScale = [aScreen backingScaleFactor];
|
|
if (fScale > fWindowScale)
|
|
fWindowScale = fScale;
|
|
}
|
|
bWindowScaling = true;
|
|
}
|
|
if( const char* env = getenv("SAL_FORCE_HIDPI_SCALING"))
|
|
{
|
|
fWindowScale = atof(env);
|
|
bWindowScaling = true;
|
|
}
|
|
}
|
|
return fWindowScale;
|
|
}
|
|
|
|
void resetWindowScaling()
|
|
{
|
|
// Related: tdf#147342 Force recalculation of the window scaling but keep
|
|
// the previous window scaling as the minimum so that we don't lose the
|
|
// resolution in cached images if a HiDPI monitor is disconnected and
|
|
// then reconnected.
|
|
#if HAVE_FEATURE_SKIA
|
|
if ( SkiaHelper::isVCLSkiaEnabled() )
|
|
skwindow::ResetBackingScaleFactor();
|
|
#endif
|
|
bWindowScaling = false;
|
|
getWindowScaling();
|
|
}
|
|
} // end aqua
|
|
|
|
void AquaSalGraphics::SetWindowGraphics( AquaSalFrame* pFrame )
|
|
{
|
|
maShared.mpFrame = pFrame;
|
|
maShared.mbWindow = true;
|
|
maShared.mbPrinter = false;
|
|
maShared.mbVirDev = false;
|
|
mpBackend->UpdateGeometryProvider(pFrame);
|
|
}
|
|
|
|
void AquaSalGraphics::SetPrinterGraphics( CGContextRef xContext, sal_Int32 nDPIX, sal_Int32 nDPIY )
|
|
{
|
|
maShared.mbWindow = false;
|
|
maShared.mbPrinter = true;
|
|
maShared.mbVirDev = false;
|
|
|
|
maShared.maContextHolder.set(xContext);
|
|
mnRealDPIX = nDPIX;
|
|
mnRealDPIY = nDPIY;
|
|
|
|
// a previously set clip path is now invalid
|
|
maShared.unsetClipPath();
|
|
|
|
if (maShared.maContextHolder.isSet())
|
|
{
|
|
CGContextSetFillColorSpace( maShared.maContextHolder.get(), GetSalData()->mxRGBSpace );
|
|
CGContextSetStrokeColorSpace( maShared.maContextHolder.get(), GetSalData()->mxRGBSpace );
|
|
CGContextSaveGState( maShared.maContextHolder.get() );
|
|
maShared.setState();
|
|
}
|
|
|
|
mpBackend->UpdateGeometryProvider(nullptr);
|
|
}
|
|
|
|
void AquaSalGraphics::InvalidateContext()
|
|
{
|
|
UnsetState();
|
|
|
|
CGContextRelease(maShared.maContextHolder.get());
|
|
CGContextRelease(maShared.maBGContextHolder.get());
|
|
CGContextRelease(maShared.maCSContextHolder.get());
|
|
|
|
maShared.maContextHolder.set(nullptr);
|
|
maShared.maCSContextHolder.set(nullptr);
|
|
maShared.maBGContextHolder.set(nullptr);
|
|
}
|
|
|
|
void AquaSalGraphics::UnsetState()
|
|
{
|
|
if (maShared.maBGContextHolder.isSet())
|
|
{
|
|
CGContextRelease(maShared.maBGContextHolder.get());
|
|
maShared.maBGContextHolder.set(nullptr);
|
|
}
|
|
if (maShared.maCSContextHolder.isSet())
|
|
{
|
|
CGContextRelease(maShared.maCSContextHolder.get());
|
|
maShared.maBGContextHolder.set(nullptr);
|
|
}
|
|
if (maShared.maContextHolder.isSet())
|
|
{
|
|
maShared.maContextHolder.restoreState();
|
|
maShared.maContextHolder.set(nullptr);
|
|
}
|
|
maShared.unsetState();
|
|
}
|
|
|
|
/**
|
|
* (re-)create the off-screen maLayer we render everything to if
|
|
* necessary: eg. not initialized yet, or it has an incorrect size.
|
|
*/
|
|
bool AquaSharedAttributes::checkContext()
|
|
{
|
|
if (mbWindow && mpFrame && (mpFrame->getNSWindow() || Application::IsBitmapRendering()))
|
|
{
|
|
const unsigned int nWidth = mpFrame->GetWidth();
|
|
const unsigned int nHeight = mpFrame->GetHeight();
|
|
const float fScale = sal::aqua::getWindowScaling();
|
|
CGLayerRef rReleaseLayer = nullptr;
|
|
|
|
// check if a new drawing context is needed (e.g. after a resize)
|
|
if( (unsigned(mnWidth) != nWidth) || (unsigned(mnHeight) != nHeight) )
|
|
{
|
|
mnWidth = nWidth;
|
|
mnHeight = nHeight;
|
|
// prepare to release the corresponding resources
|
|
if (maLayer.isSet())
|
|
{
|
|
rReleaseLayer = maLayer.get();
|
|
}
|
|
else if (maContextHolder.isSet())
|
|
{
|
|
CGContextRelease(maContextHolder.get());
|
|
}
|
|
CGContextRelease(maBGContextHolder.get());
|
|
CGContextRelease(maCSContextHolder.get());
|
|
|
|
maContextHolder.set(nullptr);
|
|
maBGContextHolder.set(nullptr);
|
|
maCSContextHolder.set(nullptr);
|
|
maLayer.set(nullptr);
|
|
}
|
|
|
|
// tdf#159175 no CGLayer is needed for an NSWindow when using Skia
|
|
if (SkiaHelper::isVCLSkiaEnabled() && mpFrame->getNSWindow())
|
|
return true;
|
|
|
|
if (!maContextHolder.isSet())
|
|
{
|
|
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;
|
|
std::uint32_t nFlags = std::uint32_t(kCGImageAlphaNoneSkipFirst)
|
|
| std::uint32_t(kCGBitmapByteOrder32Host);
|
|
maBGContextHolder.set(CGBitmapContextCreate(
|
|
nullptr, nScaledWidth, nScaledHeight, 8, nBytesPerRow, GetSalData()->mxRGBSpace, nFlags));
|
|
|
|
maLayer.set(CGLayerCreateWithContext(maBGContextHolder.get(), aLayerSize, nullptr));
|
|
maLayer.setScale(fScale);
|
|
|
|
nFlags = std::uint32_t(kCGImageAlphaPremultipliedFirst)
|
|
| std::uint32_t(kCGBitmapByteOrder32Host);
|
|
maCSContextHolder.set(CGBitmapContextCreate(
|
|
nullptr, nScaledWidth, nScaledHeight, 8, nBytesPerRow, GetSalData()->mxRGBSpace, nFlags));
|
|
|
|
CGContextRef xDrawContext = CGLayerGetContext(maLayer.get());
|
|
maContextHolder = xDrawContext;
|
|
|
|
if (rReleaseLayer)
|
|
{
|
|
// copy original layer to resized layer
|
|
if (maContextHolder.isSet())
|
|
{
|
|
CGContextDrawLayerAtPoint(maContextHolder.get(), CGPointZero, rReleaseLayer);
|
|
}
|
|
CGLayerRelease(rReleaseLayer);
|
|
}
|
|
|
|
if (maContextHolder.isSet())
|
|
{
|
|
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();
|
|
|
|
// re-enable XOR emulation for the new context
|
|
if (mpXorEmulation)
|
|
mpXorEmulation->SetTarget(mnWidth, mnHeight, mnBitmapDepth, maContextHolder.get(), maLayer.get());
|
|
}
|
|
}
|
|
}
|
|
|
|
SAL_WARN_IF(!maContextHolder.isSet() && !mbPrinter, "vcl", "<<<WARNING>>> AquaSalGraphics::CheckContext() FAILED!!!!");
|
|
|
|
return maContextHolder.isSet();
|
|
}
|
|
|
|
/**
|
|
* Blit the contents of our internal maLayer state to the
|
|
* associated window, if any; cf. drawRect event handling
|
|
* on the frame.
|
|
*/
|
|
void AquaSalGraphics::UpdateWindow( NSRect& rRect )
|
|
{
|
|
if (!maShared.mpFrame)
|
|
{
|
|
return;
|
|
}
|
|
|
|
NSGraphicsContext* pContext = [NSGraphicsContext currentContext];
|
|
if (!pContext)
|
|
{
|
|
SAL_WARN_IF(!maShared.mpFrame->mbInitShow, "vcl", "UpdateWindow called with no NSGraphicsContext");
|
|
return;
|
|
}
|
|
|
|
CGImageRef img = nullptr;
|
|
CGPoint aImageOrigin = CGPointMake(0, 0);
|
|
bool bImageFlipped = false;
|
|
#if HAVE_FEATURE_SKIA
|
|
if (SkiaHelper::isVCLSkiaEnabled())
|
|
{
|
|
// tdf#159175 no CGLayer is needed for an NSWindow when using Skia
|
|
// Get a CGImageRef directly from the Skia/Raster surface and draw
|
|
// that directly to the NSWindow.
|
|
// Note: Skia/Metal will always return a null CGImageRef since it
|
|
// draws directly to the NSWindow using the surface's CAMetalLayer.
|
|
AquaSkiaSalGraphicsImpl *pBackend = static_cast<AquaSkiaSalGraphicsImpl*>(mpBackend.get());
|
|
if (pBackend)
|
|
img = pBackend->createCGImageFromRasterSurface(rRect, aImageOrigin, bImageFlipped);
|
|
}
|
|
else
|
|
#else
|
|
(void)rRect;
|
|
#endif
|
|
if (maShared.maLayer.isSet())
|
|
{
|
|
maShared.applyXorContext();
|
|
|
|
const CGRect aRectPoints = { CGPointZero, maShared.maLayer.getSizePixels() };
|
|
CGContextSetBlendMode(maShared.maCSContextHolder.get(), kCGBlendModeCopy);
|
|
CGContextDrawLayerInRect(maShared.maCSContextHolder.get(), aRectPoints, maShared.maLayer.get());
|
|
|
|
img = CGBitmapContextCreateImage(maShared.maCSContextHolder.get());
|
|
}
|
|
|
|
if (img)
|
|
{
|
|
const float fScale = sal::aqua::getWindowScaling();
|
|
CGContextHolder rCGContextHolder([pContext CGContext]);
|
|
|
|
rCGContextHolder.saveState();
|
|
|
|
CGRect aRect = CGRectMake(aImageOrigin.x / fScale, aImageOrigin.y / fScale, CGImageGetWidth(img) / fScale, CGImageGetHeight(img) / fScale);
|
|
if (bImageFlipped)
|
|
{
|
|
// Related: tdf#155092 translate Y coordinate of flipped images
|
|
// When in live resize, the NSView's height may have changed before
|
|
// the surface has been resized. This causes flipped content
|
|
// to be drawn just above or below the top left corner of the view
|
|
// so translate the Y coordinate using the NSView's height.
|
|
// Use the NSView's bounds, not its frame, to properly handle
|
|
// any rotation and/or scaling that might have been already
|
|
// applied to the view.
|
|
NSView *pView = maShared.mpFrame->mpNSView;
|
|
if (pView)
|
|
aRect.origin.y = [pView bounds].size.height - aRect.origin.y - aRect.size.height;
|
|
else if (maShared.maLayer.isSet())
|
|
aRect.origin.y = maShared.maLayer.getSizePoints().height - aRect.origin.y - aRect.size.height;
|
|
}
|
|
|
|
CGMutablePathRef rClip = maShared.mpFrame->getClipPath();
|
|
if (rClip)
|
|
{
|
|
CGContextBeginPath(rCGContextHolder.get());
|
|
CGContextAddPath(rCGContextHolder.get(), rClip );
|
|
CGContextClip(rCGContextHolder.get());
|
|
}
|
|
|
|
CGContextSetBlendMode(rCGContextHolder.get(), kCGBlendModeCopy);
|
|
|
|
// tdf#163152 don't convert image's sRGB colorspace
|
|
// Converting the image's colorspace to match the window's
|
|
// colorspace causes more than an expected amount of color
|
|
// saturation so let the window's underlying CGContext handle
|
|
// any necessary colorspace conversion in CGContextDrawImage().
|
|
CGContextDrawImage(rCGContextHolder.get(), aRect, img);
|
|
|
|
rCGContextHolder.restoreState();
|
|
|
|
CGImageRelease(img);
|
|
}
|
|
else
|
|
{
|
|
SAL_WARN_IF(!maShared.mpFrame->mbInitShow, "vcl", "UpdateWindow called on uneligible graphics");
|
|
}
|
|
}
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|