/* -*- 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 #include #include #include #include #include #include #include #include // 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(aLayerSize.width) - nX ) nWidth = static_cast(aLayerSize.width) - nX; if( nHeight >= static_cast(aLayerSize.height) - nY ) nHeight = static_cast(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(-nX * fScale), static_cast(-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(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 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(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); } // 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(nScaledWidth), static_cast(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: */