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 1a16762531

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>
This commit is contained in:
Thorsten Wagner 2021-05-19 17:47:58 +02:00 committed by Adolfo Jayme Barrientos
parent 77b505b0b9
commit 2cce064e6b
3 changed files with 299 additions and 147 deletions

View file

@ -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()

View file

@ -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()

View file

@ -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: */