summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThorsten Wagner <thorsten.wagner.4@gmail.com>2021-06-28 22:36:56 +0200
committerAdolfo Jayme Barrientos <fitojb@ubuntu.com>2021-06-29 10:29:16 +0200
commita396dde0907ad87ac928c9a1b9f7640a73910b17 (patch)
treeba776309f47479645868615181fd12c371b6bc28
parente51ad28ba22105c60be246f02e7951b986f35150 (diff)
tdf#142061 Consider window scaling for XOR drawing on macOS
Change-Id: I4261334b6d2d2f34fe3452cc870aba6f88c4069e Reviewed-on: https://gerrit.libreoffice.org/c/core/+/118036 Reviewed-by: Adolfo Jayme Barrientos <fitojb@ubuntu.com> Tested-by: Adolfo Jayme Barrientos <fitojb@ubuntu.com>
-rw-r--r--vcl/ios/salios.cxx147
-rw-r--r--vcl/osx/salmacos.cxx152
-rw-r--r--vcl/quartz/salgdicommon.cxx147
3 files changed, 299 insertions, 147 deletions
diff --git a/vcl/ios/salios.cxx b/vcl/ios/salios.cxx
index d1552b8b0bbb..4c8c99b4c4a4 100644
--- a/vcl/ios/salios.cxx
+++ b/vcl/ios/salios.cxx
@@ -331,6 +331,153 @@ void AquaSalGraphics::SetVirDevGraphics(CGLayerHolder const & rLayer, CGContextR
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 24a4b5b5a4d6..e6573e101b61 100644
--- a/vcl/osx/salmacos.cxx
+++ b/vcl/osx/salmacos.cxx
@@ -285,6 +285,158 @@ void AquaSalGraphics::SetVirDevGraphics(CGLayerHolder const &rLayer, CGContextRe
" (" << mnWidth << "x" << mnHeight << ") fScale=" << fScale << " mnBitmapDepth=" << 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 = AquaSalGraphics::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 = AquaSalGraphics::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 bbfe185b73d2..e393659658d6 100644
--- a/vcl/quartz/salgdicommon.cxx
+++ b/vcl/quartz/salgdicommon.cxx
@@ -1544,151 +1544,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: */