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:
parent
77b505b0b9
commit
2cce064e6b
3 changed files with 299 additions and 147 deletions
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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: */
|
||||
|
|
Loading…
Reference in a new issue