1
0
Fork 0
libreoffice/vcl/osx/salmacos.cxx
Daniel Baumann 8e63e14cf6
Adding upstream version 4:25.2.3.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-22 16:20:04 +02:00

536 lines
19 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
/*
* 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 .
*/
// This file contains the macOS-specific versions of the functions which were touched in the commit
// to fix tdf#138122. The iOS-specific versions of these functions are kept (for now, when this
// comment is written) as they were before that commit in vcl/ios/salios.cxx.
#include <sal/config.h>
#include <sal/log.hxx>
#include <osl/diagnose.h>
#include <vcl/bitmap.hxx>
#include <vcl/skia/SkiaHelper.hxx>
#include <quartz/salbmp.h>
#include <quartz/salgdi.h>
#include <quartz/salvd.h>
#include <quartz/utils.h>
#include <osx/saldata.hxx>
// From salbmp.cxx
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
if( nX < 0 ) {
nWidth += nX;
nX = 0;
}
if( nY < 0 ) {
nHeight += nY;
nY = 0;
}
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;
if( nHeight >= static_cast<int>(aLayerSize.height) - nY )
nHeight = static_cast<int>(aLayerSize.height) - nY;
if( (nWidth < 0) || (nHeight < 0) )
nWidth = nHeight = 0;
// initialize properties
mnWidth = nWidth;
mnHeight = nHeight;
mnBits = nBitmapBits ? nBitmapBits : 32;
// initialize drawing context
CreateContext();
// copy layer content into the bitmap buffer
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);
}
maGraphicContext.saveState();
CGContextScaleCTM(maGraphicContext.get(), 1 / fScale, 1 / fScale);
CGContextDrawLayerAtPoint(maGraphicContext.get(), aSrcPoint, rLayerHolder.get());
maGraphicContext.restoreState();
}
return true;
}
// From salgdicommon.cxx
void AquaGraphicsBackend::copyBits(const SalTwoRect &rPosAry, SalGraphics *pSrcGraphics)
{
AquaSharedAttributes* pSrcShared = nullptr;
if (pSrcGraphics)
{
AquaSalGraphics* pSrc = static_cast<AquaSalGraphics*>(pSrcGraphics);
pSrcShared = &pSrc->getAquaGraphicsBackend()->GetShared();
}
else
pSrcShared = &mrShared;
if (rPosAry.mnSrcWidth <= 0 || rPosAry.mnSrcHeight <= 0 || rPosAry.mnDestWidth <= 0 || rPosAry.mnDestHeight <= 0)
return;
if (!mrShared.maContextHolder.isSet())
return;
SAL_WARN_IF (!pSrcShared->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 (pSrcShared->maLayer.isSet())
copyScaledArea(rPosAry.mnDestX, rPosAry.mnDestY, rPosAry.mnSrcX, rPosAry.mnSrcY,
rPosAry.mnSrcWidth, rPosAry.mnSrcHeight, pSrcShared);
else
{
mrShared.applyXorContext();
pSrcShared->applyXorContext();
std::shared_ptr<SalBitmap> pBitmap = pSrcGraphics->GetImpl()->getBitmap(rPosAry.mnSrcX, rPosAry.mnSrcY,
rPosAry.mnSrcWidth, rPosAry.mnSrcHeight);
if (pBitmap)
{
SalTwoRect aPosAry(rPosAry);
aPosAry.mnSrcX = 0;
aPosAry.mnSrcY = 0;
drawBitmap(aPosAry, *pBitmap);
}
}
}
void AquaGraphicsBackend::copyArea(tools::Long nDstX, tools::Long nDstY,tools::Long nSrcX, tools::Long nSrcY,
tools::Long nSrcWidth, tools::Long nSrcHeight, bool)
{
if (!mrShared.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, &mrShared);
}
void AquaGraphicsBackend::copyScaledArea(tools::Long nDstX, tools::Long nDstY,tools::Long nSrcX, tools::Long nSrcY,
tools::Long nSrcWidth, tools::Long nSrcHeight, AquaSharedAttributes* pSrcShared)
{
SAL_WARN_IF(!mrShared.maLayer.isSet(), "vcl.quartz",
"AquaSalGraphics::copyScaledArea() without graphics context or for non-layered graphics this=" << this);
if (!mrShared.maContextHolder.isSet() || !mrShared.maLayer.isSet())
return;
// Determine scaled geometry of source and target area assuming source and target area have the same scale
float fScale = mrShared.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;
// Apply XOR context and get copy context from current graphics context or XOR context
mrShared.applyXorContext();
mrShared.maContextHolder.saveState();
CGContextRef xCopyContext = mrShared.maContextHolder.get();
if (mrShared.mpXorEmulation && mrShared.mpXorEmulation->IsEnabled())
xCopyContext = mrShared.mpXorEmulation->GetTargetContext();
// Set scale matrix of copy context to consider layer scaling
CGContextScaleCTM(xCopyContext, 1 / fScale, 1 / fScale);
// Creating an additional layer is required for drawing with the required scale and extent at the drawing destination
// thereafter.
CGLayerHolder aSourceLayerHolder(pSrcShared->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 (pSrcShared->isFlipped())
{
CGContextTranslateCTM(xSourceContext, 0, nScaledSourceHeight);
CGContextScaleCTM(xSourceContext, 1, -1);
aSrcPoint.y = nScaledSourceY + nScaledSourceHeight - mrShared.mnHeight * fScale;
}
CGContextSetBlendMode(xSourceContext, kCGBlendModeCopy);
CGContextDrawLayerAtPoint(xSourceContext, aSrcPoint, pSrcShared->maLayer.get());
// Copy source area from additional layer to target area
const CGRect aTargetRect = CGRectMake(nScaledTargetX, nScaledTargetY, nScaledSourceWidth, nScaledSourceHeight);
CGContextSetBlendMode(xCopyContext, kCGBlendModeCopy);
CGContextDrawLayerInRect(xCopyContext, aTargetRect, aSourceLayerHolder.get());
// Housekeeping on exit
mrShared.maContextHolder.restoreState();
if (aSourceLayerHolder.get() != mrShared.maLayer.get())
CGLayerRelease(aSourceLayerHolder.get());
mrShared.refreshRect(nDstX, nDstY, nSrcWidth, nSrcHeight);
}
void AquaSalGraphics::SetVirDevGraphics(SalVirtualDevice* pVirDev, CGLayerHolder const &rLayer, CGContextRef xContext, int nBitmapDepth)
{
SAL_INFO("vcl.quartz", "SetVirDevGraphics() this=" << this << " layer=" << rLayer.get() << " context=" << xContext);
// Set member variables
InvalidateContext();
maShared.mbWindow = false;
maShared.mbPrinter = false;
maShared.mbVirDev = true;
maShared.maLayer = rLayer;
maShared.mnBitmapDepth = nBitmapDepth;
mpBackend->UpdateGeometryProvider(pVirDev);
// Get size and scale from layer if set else from bitmap and sal::aqua::getWindowScaling(), which is used to determine
// scaling for direct graphics output too
CGSize aSize;
float fScale;
if (maShared.maLayer.isSet())
{
maShared.maContextHolder.set(CGLayerGetContext(maShared.maLayer.get()));
aSize = CGLayerGetSize(maShared.maLayer.get());
fScale = maShared.maLayer.getScale();
}
else
{
maShared.maContextHolder.set(xContext);
if (!xContext)
return;
aSize.width = CGBitmapContextGetWidth(xContext);
aSize.height = CGBitmapContextGetHeight(xContext);
fScale = sal::aqua::getWindowScaling();
}
maShared.mnWidth = aSize.width / fScale;
maShared.mnHeight = aSize.height / fScale;
// Set color space for fill and stroke
CGColorSpaceRef aColorSpace = GetSalData()->mxRGBSpace;
CGContextSetFillColorSpace(maShared.maContextHolder.get(), aColorSpace);
CGContextSetStrokeColorSpace(maShared.maContextHolder.get(), aColorSpace);
// Apply scale matrix to virtual device graphics
CGContextScaleCTM(maShared.maContextHolder.get(), fScale, fScale);
// Apply XOR emulation if required
if (maShared.mpXorEmulation)
{
maShared.mpXorEmulation->SetTarget(maShared.mnWidth, maShared.mnHeight, maShared.mnBitmapDepth, maShared.maContextHolder.get(), maShared.maLayer.get());
if (maShared.mpXorEmulation->IsEnabled())
maShared.maContextHolder.set(maShared.mpXorEmulation->GetMaskContext());
}
// Housekeeping on exit
maShared.maContextHolder.saveState();
maShared.setState();
SAL_INFO("vcl.quartz", "SetVirDevGraphics() this=" << this <<
" (" << maShared.mnWidth << "x" << maShared.mnHeight << ") fScale=" << fScale << " mnBitmapDepth=" << maShared.mnBitmapDepth);
}
void XorEmulation::SetTarget(int nWidth, int nHeight, int nTargetDepth, CGContextRef xTargetContext, CGLayerRef xTargetLayer)
{
SAL_INFO("vcl.quartz", "XorEmulation::SetTarget() this=" << this <<
" (" << nWidth << "x" << nHeight << ") depth=" << nTargetDepth <<
" context=" << xTargetContext << " layer=" << xTargetLayer);
// Prepare to replace old mask and temporary context
if (m_xMaskContext)
{
CGContextRelease(m_xMaskContext);
delete[] m_pMaskBuffer;
m_xMaskContext = nullptr;
m_pMaskBuffer = nullptr;
if (m_xTempContext)
{
CGContextRelease(m_xTempContext);
delete[] m_pTempBuffer;
m_xTempContext = nullptr;
m_pTempBuffer = nullptr;
}
}
// Return early if there is nothing more to do
if (!xTargetContext)
return;
// Retarget drawing operations to the XOR mask
m_xTargetLayer = xTargetLayer;
m_xTargetContext = xTargetContext;
// Prepare creation of matching bitmaps
CGColorSpaceRef aCGColorSpace = GetSalData()->mxRGBSpace;
CGBitmapInfo aCGBmpInfo = kCGImageAlphaNoneSkipFirst;
int nBitDepth = nTargetDepth;
if (!nBitDepth)
nBitDepth = 32;
int nBytesPerRow = 4;
const size_t nBitsPerComponent = (nBitDepth == 16) ? 5 : 8;
if (nBitDepth <= 8)
{
aCGColorSpace = GetSalData()->mxGraySpace;
aCGBmpInfo = kCGImageAlphaNone;
nBytesPerRow = 1;
}
float fScale = sal::aqua::getWindowScaling();
size_t nScaledWidth = nWidth * fScale;
size_t nScaledHeight = nHeight * fScale;
nBytesPerRow *= nScaledWidth;
m_nBufferLongs = (nScaledHeight * nBytesPerRow + sizeof(sal_uLong) - 1) / sizeof(sal_uLong);
// Create XOR mask context
m_pMaskBuffer = new sal_uLong[m_nBufferLongs];
m_xMaskContext = CGBitmapContextCreate(m_pMaskBuffer, nScaledWidth, nScaledHeight,
nBitsPerComponent, nBytesPerRow, aCGColorSpace, aCGBmpInfo);
SAL_WARN_IF(!m_xMaskContext, "vcl.quartz", "mask context creation failed");
// Reset XOR mask to black
memset(m_pMaskBuffer, 0, m_nBufferLongs * sizeof(sal_uLong));
// Create bitmap context for manual XOR unless target context is a bitmap context
if (nTargetDepth)
m_pTempBuffer = static_cast<sal_uLong*>(CGBitmapContextGetData(m_xTargetContext));
if (!m_pTempBuffer)
{
m_pTempBuffer = new sal_uLong[m_nBufferLongs];
m_xTempContext = CGBitmapContextCreate(m_pTempBuffer, nScaledWidth, nScaledHeight,
nBitsPerComponent, nBytesPerRow, aCGColorSpace, aCGBmpInfo);
SAL_WARN_IF(!m_xTempContext, "vcl.quartz", "temp context creation failed");
}
// Initialize XOR mask context for drawing
CGContextSetFillColorSpace(m_xMaskContext, aCGColorSpace);
CGContextSetStrokeColorSpace(m_xMaskContext, aCGColorSpace);
CGContextSetShouldAntialias(m_xMaskContext, false);
// Improve XOR emulation for monochrome contexts
if (aCGColorSpace == GetSalData()->mxGraySpace)
CGContextSetBlendMode(m_xMaskContext, kCGBlendModeDifference);
// Initialize XOR mask transformation matrix and apply scale matrix to consider layer scaling
const CGAffineTransform aCTM = CGContextGetCTM(xTargetContext);
CGContextConcatCTM(m_xMaskContext, aCTM);
if (m_xTempContext)
{
CGContextConcatCTM( m_xTempContext, aCTM );
CGContextScaleCTM(m_xTempContext, 1 / fScale, 1 / fScale);
}
CGContextSaveGState(m_xMaskContext);
}
bool XorEmulation::UpdateTarget()
{
SAL_INFO("vcl.quartz", "XorEmulation::UpdateTarget() this=" << this);
if (!IsEnabled())
return false;
// Update temporary bitmap buffer
if (m_xTempContext)
{
SAL_WARN_IF(m_xTargetContext == nullptr, "vcl.quartz", "Target layer is NULL");
CGContextDrawLayerAtPoint(m_xTempContext, CGPointZero, m_xTargetLayer);
}
// XOR using XOR mask (sufficient for simple color manipulations as well as for complex XOR clipping used in metafiles)
const sal_uLong *pSrc = m_pMaskBuffer;
sal_uLong *pDst = m_pTempBuffer;
for (int i = m_nBufferLongs; --i >= 0;)
*(pDst++) ^= *(pSrc++);
// Write back XOR results to target context
if (m_xTempContext)
{
CGImageRef xXorImage = CGBitmapContextCreateImage(m_xTempContext);
size_t nWidth = CGImageGetWidth(xXorImage);
size_t nHeight = CGImageGetHeight(xXorImage);
// Set scale matrix of target context to consider layer scaling and update target context
// TODO: Update minimal change rectangle
const CGRect aFullRect = CGRectMake(0, 0, nWidth, nHeight);
CGContextSaveGState(m_xTargetContext);
float fScale = sal::aqua::getWindowScaling();
CGContextScaleCTM(m_xTargetContext, 1 / fScale, 1 / fScale);
CGContextDrawImage(m_xTargetContext, aFullRect, xXorImage);
CGContextRestoreGState(m_xTargetContext);
CGImageRelease(xXorImage);
}
// Reset XOR mask to black again
// TODO: Not needed for last update
memset(m_pMaskBuffer, 0, m_nBufferLongs * sizeof(sal_uLong));
// TODO: Return FALSE if target was not changed
return true;
}
// From salvd.cxx
void AquaSalVirtualDevice::Destroy()
{
SAL_INFO( "vcl.virdev", "AquaSalVirtualDevice::Destroy() this=" << this << " mbForeignContext=" << mbForeignContext );
if (mbForeignContext)
{
// Do not delete mxContext that we have received from outside VCL
maLayer.set(nullptr);
return;
}
if (maLayer.isSet())
{
if( mpGraphics )
{
mpGraphics->SetVirDevGraphics(this, nullptr, nullptr);
}
CGLayerRelease(maLayer.get());
maLayer.set(nullptr);
}
if (maBitmapContext.isSet())
{
CGContextRelease(maBitmapContext.get());
maBitmapContext.set(nullptr);
}
}
bool AquaSalVirtualDevice::SetSize(tools::Long nDX, tools::Long nDY)
{
SAL_INFO("vcl.virdev", "AquaSalVirtualDevice::SetSize() this=" << this <<
" (" << nDX << "x" << nDY << ") mbForeignContext=" << (mbForeignContext ? "YES" : "NO"));
// 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 / 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
// sal::aqua::getWindowScaling(), which is used to determine scaling for direct graphics output too
mnWidth = nDX;
mnHeight = nDY;
fScale = sal::aqua::getWindowScaling();
CGColorSpaceRef aColorSpace;
uint32_t nFlags;
if (mnBitmapDepth && (mnBitmapDepth < 16))
{
mnBitmapDepth = 8;
aColorSpace = GetSalData()->mxGraySpace;
nFlags = kCGImageAlphaNone;
}
else
{
mnBitmapDepth = 32;
aColorSpace = GetSalData()->mxRGBSpace;
nFlags = uint32_t(kCGImageAlphaNoneSkipFirst) | uint32_t(kCGBitmapByteOrder32Host);
}
if (SkiaHelper::isVCLSkiaEnabled())
{
mpGraphics->SetVirDevGraphics(this, maLayer, nullptr, mnBitmapDepth);
return true;
}
// Allocate buffer for virtual device graphics as bitmap context to store graphics with highest required (scaled) resolution
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));
SAL_INFO("vcl.virdev", "AquaSalVirtualDevice::SetSize() this=" << this <<
" fScale=" << fScale << " mnBitmapDepth=" << mnBitmapDepth);
CGSize aLayerSize = { static_cast<CGFloat>(nScaledWidth), static_cast<CGFloat>(nScaledHeight) };
maLayer.set(CGLayerCreateWithContext(maBitmapContext.get(), aLayerSize, nullptr));
maLayer.setScale(fScale);
mpGraphics->SetVirDevGraphics(this, maLayer, CGLayerGetContext(maLayer.get()), mnBitmapDepth);
SAL_WARN_IF(!maBitmapContext.isSet(), "vcl.quartz", "No context");
return maLayer.isSet();
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */