diff options
author | Thorsten Wagner <thorsten.wagner.4@gmail.com> | 2021-05-19 17:47:58 +0200 |
---|---|---|
committer | Adolfo Jayme Barrientos <fitojb@ubuntu.com> | 2021-05-22 18:02:29 +0200 |
commit | 2cce064e6bb570361c845f7b3d82960f89c70bb1 (patch) | |
tree | a301056c4cd056d4e2606e906e6d29d446eaa9ce | |
parent | 77b505b0b9fd86d2bd8e82f2be7d40307dfd1493 (diff) |
tdf#142061 Add window scaling to XOR emulation on macOS
Window scaling for retina displays on macOS has been added to fix
tdf#138122 in commit 1a167625314bf36b735176ed488e6ba9b5e9b675
Missing window scaling for XOR emulation is added by this change.
Code modified for macOS is moved from quartz/salgidcommon.cxx to
osx/salmacos.cxx while original code is copied to ios/salios.cxx
to prevent modifications for iOS.
Change-Id: Ia8c52f9045379cc37d5aff1279650db0dddee8c0
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/115816
Tested-by: Adolfo Jayme Barrientos <fitojb@ubuntu.com>
Reviewed-by: Adolfo Jayme Barrientos <fitojb@ubuntu.com>
-rw-r--r-- | vcl/ios/salios.cxx | 147 | ||||
-rw-r--r-- | vcl/osx/salmacos.cxx | 152 | ||||
-rw-r--r-- | vcl/quartz/salgdicommon.cxx | 147 |
3 files changed, 299 insertions, 147 deletions
diff --git a/vcl/ios/salios.cxx b/vcl/ios/salios.cxx index ac11faff3072..335c14264c64 100644 --- a/vcl/ios/salios.cxx +++ b/vcl/ios/salios.cxx @@ -325,6 +325,153 @@ void AquaSalGraphics::SetVirDevGraphics(CGLayerHolder const & rLayer, CGContextR maShared.setState(); } +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+temp context + if( m_xMaskContext ) + { + // cleanup the mask context + CGContextRelease( m_xMaskContext ); + delete[] m_pMaskBuffer; + m_xMaskContext = nullptr; + m_pMaskBuffer = nullptr; + + // cleanup the temp context if needed + 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 CGBitmaps + 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; + } + nBytesPerRow *= nWidth; + m_nBufferLongs = (nHeight * nBytesPerRow + sizeof(sal_uLong)-1) / sizeof(sal_uLong); + + // create a XorMask context + m_pMaskBuffer = new sal_uLong[ m_nBufferLongs ]; + m_xMaskContext = CGBitmapContextCreate( m_pMaskBuffer, + nWidth, nHeight, + nBitsPerComponent, nBytesPerRow, + aCGColorSpace, aCGBmpInfo ); + SAL_WARN_IF( !m_xMaskContext, "vcl.quartz", "mask context creation failed" ); + + // reset the XOR mask to black + memset( m_pMaskBuffer, 0, m_nBufferLongs * sizeof(sal_uLong) ); + + // a bitmap context will be needed for manual XORing + // create one unless the target context is a bitmap context + if( nTargetDepth ) + { + m_pTempBuffer = static_cast<sal_uLong*>(CGBitmapContextGetData( m_xTargetContext )); + } + if( !m_pTempBuffer ) + { + // create a bitmap context matching to the target context + m_pTempBuffer = new sal_uLong[ m_nBufferLongs ]; + m_xTempContext = CGBitmapContextCreate( m_pTempBuffer, + nWidth, nHeight, + 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 the XorMask's XOR emulation a little + // NOTE: currently only enabled for monochrome contexts + if( aCGColorSpace == GetSalData()->mxGraySpace ) + { + CGContextSetBlendMode( m_xMaskContext, kCGBlendModeDifference ); + } + // initialize the transformation matrix to the drawing target + const CGAffineTransform aCTM = CGContextGetCTM( xTargetContext ); + CGContextConcatCTM( m_xMaskContext, aCTM ); + if( m_xTempContext ) + { + CGContextConcatCTM( m_xTempContext, aCTM ); + } + // initialize the default XorMask graphics state + CGContextSaveGState( m_xMaskContext ); +} + +bool XorEmulation::UpdateTarget() +{ + SAL_INFO( "vcl.quartz", "XorEmulation::UpdateTarget() this=" << this ); + + if( !IsEnabled() ) + { + return false; + } + // update the temp bitmap buffer if needed + if( m_xTempContext ) + { + SAL_WARN_IF( m_xTargetContext == nullptr, "vcl.quartz", "Target layer is NULL"); + CGContextDrawLayerAtPoint( m_xTempContext, CGPointZero, m_xTargetLayer ); + } + // do a manual XOR with the XorMask + // this approach suffices for simple color manipulations + // and also the complex-clipping-XOR-trick 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 the XOR results to the target context + if( m_xTempContext ) + { + CGImageRef xXorImage = CGBitmapContextCreateImage( m_xTempContext ); + const int nWidth = static_cast<int>(CGImageGetWidth( xXorImage )); + const int nHeight = static_cast<int>(CGImageGetHeight( xXorImage )); + // TODO: update minimal changerect + const CGRect aFullRect = CGRectMake(0, 0, nWidth, nHeight); + CGContextDrawImage( m_xTargetContext, aFullRect, xXorImage ); + CGImageRelease( xXorImage ); + } + + // reset the XorMask 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() diff --git a/vcl/osx/salmacos.cxx b/vcl/osx/salmacos.cxx index fa2520f056a6..bc5afba086b1 100644 --- a/vcl/osx/salmacos.cxx +++ b/vcl/osx/salmacos.cxx @@ -275,6 +275,158 @@ void AquaSalGraphics::SetVirDevGraphics(CGLayerHolder const &rLayer, CGContextRe " (" << 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() diff --git a/vcl/quartz/salgdicommon.cxx b/vcl/quartz/salgdicommon.cxx index fec61453367d..4061aefb4a40 100644 --- a/vcl/quartz/salgdicommon.cxx +++ b/vcl/quartz/salgdicommon.cxx @@ -284,151 +284,4 @@ XorEmulation::~XorEmulation() SetTarget( 0, 0, 0, nullptr, nullptr ); } -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+temp context - if( m_xMaskContext ) - { - // cleanup the mask context - CGContextRelease( m_xMaskContext ); - delete[] m_pMaskBuffer; - m_xMaskContext = nullptr; - m_pMaskBuffer = nullptr; - - // cleanup the temp context if needed - 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 CGBitmaps - 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; - } - nBytesPerRow *= nWidth; - m_nBufferLongs = (nHeight * nBytesPerRow + sizeof(sal_uLong)-1) / sizeof(sal_uLong); - - // create a XorMask context - m_pMaskBuffer = new sal_uLong[ m_nBufferLongs ]; - m_xMaskContext = CGBitmapContextCreate( m_pMaskBuffer, - nWidth, nHeight, - nBitsPerComponent, nBytesPerRow, - aCGColorSpace, aCGBmpInfo ); - SAL_WARN_IF( !m_xMaskContext, "vcl.quartz", "mask context creation failed" ); - - // reset the XOR mask to black - memset( m_pMaskBuffer, 0, m_nBufferLongs * sizeof(sal_uLong) ); - - // a bitmap context will be needed for manual XORing - // create one unless the target context is a bitmap context - if( nTargetDepth ) - { - m_pTempBuffer = static_cast<sal_uLong*>(CGBitmapContextGetData( m_xTargetContext )); - } - if( !m_pTempBuffer ) - { - // create a bitmap context matching to the target context - m_pTempBuffer = new sal_uLong[ m_nBufferLongs ]; - m_xTempContext = CGBitmapContextCreate( m_pTempBuffer, - nWidth, nHeight, - 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 the XorMask's XOR emulation a little - // NOTE: currently only enabled for monochrome contexts - if( aCGColorSpace == GetSalData()->mxGraySpace ) - { - CGContextSetBlendMode( m_xMaskContext, kCGBlendModeDifference ); - } - // initialize the transformation matrix to the drawing target - const CGAffineTransform aCTM = CGContextGetCTM( xTargetContext ); - CGContextConcatCTM( m_xMaskContext, aCTM ); - if( m_xTempContext ) - { - CGContextConcatCTM( m_xTempContext, aCTM ); - } - // initialize the default XorMask graphics state - CGContextSaveGState( m_xMaskContext ); -} - -bool XorEmulation::UpdateTarget() -{ - SAL_INFO( "vcl.quartz", "XorEmulation::UpdateTarget() this=" << this ); - - if( !IsEnabled() ) - { - return false; - } - // update the temp bitmap buffer if needed - if( m_xTempContext ) - { - SAL_WARN_IF( m_xTargetContext == nullptr, "vcl.quartz", "Target layer is NULL"); - CGContextDrawLayerAtPoint( m_xTempContext, CGPointZero, m_xTargetLayer ); - } - // do a manual XOR with the XorMask - // this approach suffices for simple color manipulations - // and also the complex-clipping-XOR-trick 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 the XOR results to the target context - if( m_xTempContext ) - { - CGImageRef xXorImage = CGBitmapContextCreateImage( m_xTempContext ); - const int nWidth = static_cast<int>(CGImageGetWidth( xXorImage )); - const int nHeight = static_cast<int>(CGImageGetHeight( xXorImage )); - // TODO: update minimal changerect - const CGRect aFullRect = CGRectMake(0, 0, nWidth, nHeight); - CGContextDrawImage( m_xTargetContext, aFullRect, xXorImage ); - CGImageRelease( xXorImage ); - } - - // reset the XorMask 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; -} - /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |