summaryrefslogtreecommitdiff
path: root/vcl/source/bitmap
diff options
context:
space:
mode:
authorNoel Grandin <noelgrandin@gmail.com>2021-04-16 20:33:10 +0200
committerNoel Grandin <noel.grandin@collabora.co.uk>2023-07-25 08:38:12 +0200
commit81994cb2b8b32453a92bcb011830fcb884f22ff3 (patch)
treeae1750e92421ad2e0ec3f50351c3be6581841598 /vcl/source/bitmap
parentdabedcaf27b0af1e38a611b8d8e48444f848e01d (diff)
Convert internal vcl bitmap formats transparency->alpha (II)
(Second attempt at landing this) Image formats and graphics APIs use alpha, not transparency, so change our internal formats and data structures to work directly with alpha, so we don't need to modify data before we push it to graphics APIs. Add a couple of new Color constants to make the intention of the vcl code clearer. Notes (*) On macOS, tweaking the logic in CreateWithSalBitmapAndMask to more accurately reflect the requirements of the CGImageCreateWithMask function seems to fix some tests. (*) The vcl code does not properly support gradients with transparency. So the previous code was wrong, and this change is going to result in slightly different wrongness. Change-Id: I9e21c2e98d88ecfdc5f75db13bd1ffff7c38db98 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/114168 Tested-by: Jenkins Reviewed-by: Patrick Luby <plubius@neooffice.org> Reviewed-by: Noel Grandin <noel.grandin@collabora.co.uk>
Diffstat (limited to 'vcl/source/bitmap')
-rw-r--r--vcl/source/bitmap/BitmapAlphaClampFilter.cxx4
-rw-r--r--vcl/source/bitmap/BitmapEx.cxx48
-rw-r--r--vcl/source/bitmap/BitmapMaskToAlphaFilter.cxx8
-rw-r--r--vcl/source/bitmap/BitmapTools.cxx43
-rw-r--r--vcl/source/bitmap/alpha.cxx33
-rw-r--r--vcl/source/bitmap/bitmappaint.cxx235
-rw-r--r--vcl/source/bitmap/dibtools.cxx11
7 files changed, 308 insertions, 74 deletions
diff --git a/vcl/source/bitmap/BitmapAlphaClampFilter.cxx b/vcl/source/bitmap/BitmapAlphaClampFilter.cxx
index eb3245e2347c..820bdbdde34b 100644
--- a/vcl/source/bitmap/BitmapAlphaClampFilter.cxx
+++ b/vcl/source/bitmap/BitmapAlphaClampFilter.cxx
@@ -30,9 +30,9 @@ BitmapEx BitmapAlphaClampFilter::execute(BitmapEx const& rBitmapEx) const
for (sal_Int32 nX = 0; nX < sal_Int32(aSize.Width()); ++nX)
{
BitmapColor aBitmapAlphaValue(pWriteAlpha->GetPixelFromData(pScanAlpha, nX));
- if (aBitmapAlphaValue.GetIndex() > mcThreshold)
+ if ((255 - aBitmapAlphaValue.GetIndex()) > mcThreshold)
{
- aBitmapAlphaValue.SetIndex(255);
+ aBitmapAlphaValue.SetIndex(0);
pWriteAlpha->SetPixelOnData(pScanAlpha, nX, aBitmapAlphaValue);
}
}
diff --git a/vcl/source/bitmap/BitmapEx.cxx b/vcl/source/bitmap/BitmapEx.cxx
index ad4adca6319e..c9be55521f16 100644
--- a/vcl/source/bitmap/BitmapEx.cxx
+++ b/vcl/source/bitmap/BitmapEx.cxx
@@ -113,12 +113,20 @@ BitmapEx::BitmapEx( const Bitmap& rBmp, const Bitmap& rMask ) :
if (rMask.IsEmpty())
return;
+ assert(typeid(rMask) != typeid(AlphaMask)
+ && "If this mask is actually an AlphaMask, then it will be inverted unnecessarily "
+ "and the alpha channel will be wrong");
+
if( rMask.getPixelFormat() == vcl::PixelFormat::N8_BPP && rMask.HasGreyPalette8Bit() )
+ {
maAlphaMask = rMask;
+ maAlphaMask.Invert();
+ }
else if( rMask.getPixelFormat() == vcl::PixelFormat::N8_BPP )
{
BitmapEx aMaskEx(rMask);
BitmapFilter::Filter(aMaskEx, BitmapMonochromeFilter(255));
+ aMaskEx.Invert();
maAlphaMask = aMaskEx.GetBitmap();
}
else
@@ -127,6 +135,7 @@ BitmapEx::BitmapEx( const Bitmap& rBmp, const Bitmap& rMask ) :
SAL_WARN( "vcl", "BitmapEx: forced mask to monochrome");
BitmapEx aMaskEx(rMask);
BitmapFilter::Filter(aMaskEx, BitmapMonochromeFilter(255));
+ aMaskEx.Invert();
maAlphaMask = aMaskEx.GetBitmap();
}
@@ -154,7 +163,7 @@ BitmapEx::BitmapEx( const Bitmap& rBmp, const Color& rTransparentColor ) :
maBitmap ( rBmp ),
maBitmapSize ( maBitmap.GetSizePixel() )
{
- maAlphaMask = maBitmap.CreateMask( rTransparentColor );
+ maAlphaMask = maBitmap.CreateAlphaMask( rTransparentColor );
SAL_WARN_IF(rBmp.GetSizePixel() != maAlphaMask.GetSizePixel(), "vcl",
"BitmapEx::BitmapEx(): size mismatch for bitmap and alpha mask.");
@@ -332,14 +341,14 @@ bool BitmapEx::Rotate( Degree10 nAngle10, const Color& rFillColor )
}
if( bRet && !maAlphaMask.IsEmpty() )
- maAlphaMask.Rotate( nAngle10, COL_WHITE );
+ maAlphaMask.Rotate( nAngle10, COL_ALPHA_TRANSPARENT );
}
else
{
bRet = maBitmap.Rotate( nAngle10, rFillColor );
if( bRet && !maAlphaMask.IsEmpty() )
- maAlphaMask.Rotate( nAngle10, COL_WHITE );
+ maAlphaMask.Rotate( nAngle10, COL_ALPHA_TRANSPARENT );
}
SetSizePixel(maBitmap.GetSizePixel());
@@ -387,7 +396,7 @@ void BitmapEx::Expand( sal_Int32 nDX, sal_Int32 nDY, bool bExpandTransparent )
if( bRet && !maAlphaMask.IsEmpty() )
{
- Color aColor( bExpandTransparent ? COL_WHITE : COL_BLACK );
+ Color aColor( bExpandTransparent ? COL_ALPHA_TRANSPARENT : COL_ALPHA_OPAQUE );
maAlphaMask.Expand( nDX, nDY, &aColor );
}
@@ -427,8 +436,8 @@ bool BitmapEx::CopyPixel( const tools::Rectangle& rRectDst, const tools::Rectang
maAlphaMask.CopyPixel_AlphaOptimized( rRectDst, rRectSrc, &pBmpExSrc->maAlphaMask );
else
{
- sal_uInt8 cBlack = 0;
- std::optional<AlphaMask> pAlpha(std::in_place, GetSizePixel(), &cBlack);
+ sal_uInt8 nTransparencyOpaque = 0;
+ std::optional<AlphaMask> pAlpha(std::in_place, GetSizePixel(), &nTransparencyOpaque);
maAlphaMask = pAlpha->ImplGetBitmap();
pAlpha.reset();
@@ -437,8 +446,8 @@ bool BitmapEx::CopyPixel( const tools::Rectangle& rRectDst, const tools::Rectang
}
else if (IsAlpha())
{
- sal_uInt8 cBlack = 0;
- const AlphaMask aAlphaSrc(pBmpExSrc->GetSizePixel(), &cBlack);
+ sal_uInt8 nTransparencyOpaque = 0;
+ const AlphaMask aAlphaSrc(pBmpExSrc->GetSizePixel(), &nTransparencyOpaque);
maAlphaMask.CopyPixel( rRectDst, rRectSrc, &aAlphaSrc.ImplGetBitmap() );
}
@@ -581,7 +590,7 @@ sal_uInt8 BitmapEx::GetAlpha(sal_Int32 nX, sal_Int32 nY) const
if(pRead)
{
const BitmapColor aBitmapColor(pRead->GetPixel(nY, nX));
- nAlpha = 255 - aBitmapColor.GetIndex();
+ nAlpha = aBitmapColor.GetIndex();
}
}
return nAlpha;
@@ -599,7 +608,7 @@ Color BitmapEx::GetPixelColor(sal_Int32 nX, sal_Int32 nY) const
{
AlphaMask aAlpha = GetAlphaMask();
AlphaMask::ScopedReadAccess pAlphaReadAccess(aAlpha);
- aColor.SetAlpha(255 - pAlphaReadAccess->GetPixel(nY, nX).GetIndex());
+ aColor.SetAlpha(pAlphaReadAccess->GetPixel(nY, nX).GetIndex());
}
else if (maBitmap.getPixelFormat() != vcl::PixelFormat::N32_BPP)
{
@@ -1023,6 +1032,8 @@ BitmapEx createBlendFrame(
Color aColorBottomRight,
Color aColorBottomLeft)
{
+ // FIXME the call sites are actually passing in transparency
+ nAlpha = 255 - nAlpha;
BlendFrameCache* pBlendFrameCache = ImplGetBlendFrameCache();
if(pBlendFrameCache->m_aLastSize == rSize
@@ -1368,7 +1379,7 @@ void BitmapEx::ChangeColorAlpha( sal_uInt8 cIndexFrom, sal_Int8 nAlphaTo )
{
const sal_uInt8 cIndex = pReadAccess->GetPixelFromData( pScanlineRead, nX ).GetIndex();
if ( cIndex == cIndexFrom )
- pAlphaWriteAccess->SetPixelOnData( pScanline, nX, BitmapColor(255 - nAlphaTo) );
+ pAlphaWriteAccess->SetPixelOnData( pScanline, nX, BitmapColor(nAlphaTo) );
}
}
*this = BitmapEx( GetBitmap(), aAlphaMask );
@@ -1391,7 +1402,7 @@ void BitmapEx::AdjustTransparency(sal_uInt8 cTrans)
if( !pA )
return;
- sal_uLong nTrans = cTrans, nNewTrans;
+ sal_uLong nTrans = cTrans;
const tools::Long nWidth = pA->Width(), nHeight = pA->Height();
if( pA->GetScanlineFormat() == ScanlineFormat::N8BitPal )
@@ -1402,8 +1413,10 @@ void BitmapEx::AdjustTransparency(sal_uInt8 cTrans)
for( tools::Long nX = 0; nX < nWidth; nX++ )
{
- nNewTrans = nTrans + *pAScan;
- *pAScan++ = static_cast<sal_uInt8>( ( nNewTrans & 0xffffff00 ) ? 255 : nNewTrans );
+ sal_uLong nNewTrans = nTrans + (255 - *pAScan);
+ // clamp to 255
+ nNewTrans = ( nNewTrans & 0xffffff00 ) ? 255 : nNewTrans;
+ *pAScan++ = static_cast<sal_uInt8>( 255 - nNewTrans );
}
}
}
@@ -1416,8 +1429,11 @@ void BitmapEx::AdjustTransparency(sal_uInt8 cTrans)
Scanline pScanline = pA->GetScanline( nY );
for( tools::Long nX = 0; nX < nWidth; nX++ )
{
- nNewTrans = nTrans + pA->GetIndexFromData( pScanline, nX );
- aAlphaValue.SetIndex( static_cast<sal_uInt8>( ( nNewTrans & 0xffffff00 ) ? 255 : nNewTrans ) );
+ sal_uLong nNewTrans = nTrans + (255 - pA->GetIndexFromData( pScanline, nX ));
+ // clamp to 255
+ nNewTrans = ( nNewTrans & 0xffffff00 ) ? 255 : nNewTrans;
+ // convert back to alpha
+ aAlphaValue.SetIndex( static_cast<sal_uInt8>(255 - nNewTrans) );
pA->SetPixelOnData( pScanline, nX, aAlphaValue );
}
}
diff --git a/vcl/source/bitmap/BitmapMaskToAlphaFilter.cxx b/vcl/source/bitmap/BitmapMaskToAlphaFilter.cxx
index c9ec102d933a..c5f1cda1d9cb 100644
--- a/vcl/source/bitmap/BitmapMaskToAlphaFilter.cxx
+++ b/vcl/source/bitmap/BitmapMaskToAlphaFilter.cxx
@@ -14,7 +14,7 @@
#include <bitmap/BitmapMaskToAlphaFilter.hxx>
/**
- * Convert a 1-bit mask to an alpha layer
+ * Convert a 1-bit mask to an alpha bitmap
*/
BitmapEx BitmapMaskToAlphaFilter::execute(BitmapEx const& rBitmapEx) const
{
@@ -38,11 +38,11 @@ BitmapEx BitmapMaskToAlphaFilter::execute(BitmapEx const& rBitmapEx) const
{
BitmapColor aBmpColor = pRead->GetPixelFromData(pScanlineRead, nX);
if (aBmpColor == COL_BLACK)
- aBmpColor = COL_BLACK;
+ aBmpColor = COL_ALPHA_OPAQUE;
else if (aBmpColor == COL_WHITE)
- aBmpColor = COL_WHITE;
+ aBmpColor = COL_ALPHA_TRANSPARENT;
else if (aBmpColor == Color(0, 0, 1))
- aBmpColor = COL_WHITE;
+ aBmpColor = COL_ALPHA_TRANSPARENT;
else
assert(false);
pWrite->SetPixelOnData(pScanline, nX, aBmpColor);
diff --git a/vcl/source/bitmap/BitmapTools.cxx b/vcl/source/bitmap/BitmapTools.cxx
index beec528b2f0e..7caf1f12f328 100644
--- a/vcl/source/bitmap/BitmapTools.cxx
+++ b/vcl/source/bitmap/BitmapTools.cxx
@@ -205,7 +205,8 @@ BitmapEx CreateFromData(sal_uInt8 const *pData, sal_Int32 nWidth, sal_Int32 nHei
Scanline pMaskScanLine = xMaskAcc->GetScanline(y);
for (tools::Long x = 0; x < nWidth; ++x)
{
- const sal_uInt8 nValue = bReverseAlpha ? 0xff - *p : *p;
+ // FIXME this parameter is badly named
+ const sal_uInt8 nValue = bReverseAlpha ? *p : 0xff - *p;
xMaskAcc->SetPixelOnData(pMaskScanLine, x, BitmapColor(nValue));
p += 4;
}
@@ -271,11 +272,15 @@ BitmapEx CreateFromData( RawBitmap&& rawBitmap )
Scanline pMaskScanLine = xMaskAcc->GetScanline(y);
for (tools::Long x = 0; x < nWidth; ++x)
{
- xMaskAcc->SetPixelOnData(pMaskScanLine, x, BitmapColor(255 - *p));
+ xMaskAcc->SetPixelOnData(pMaskScanLine, x, BitmapColor(*p));
p += 4;
}
}
}
+
+ xMaskAcc.reset();
+ pWrite.reset();
+
if (nBitCount == 32)
return BitmapEx(aBmp, *pAlphaMask);
else
@@ -350,7 +355,7 @@ BitmapEx* CreateFromCairoSurface(Size aSize, cairo_surface_t * pSurface)
#endif
}
pRGBWrite->SetPixel( y, x, BitmapColor( nR, nG, nB ) );
- pMaskWrite->SetPixelIndex( y, x, 255 - nAlpha );
+ pMaskWrite->SetPixelIndex( y, x, nAlpha );
pPix++;
}
}
@@ -462,7 +467,7 @@ BitmapEx CanvasTransformBitmap( const BitmapEx& rBitmap,
if( nSrcX < 0 || nSrcX >= aBmpSize.Width() ||
nSrcY < 0 || nSrcY >= aBmpSize.Height() )
{
- pAlphaWriteAccess->SetPixelOnData( pScanAlpha, x, BitmapColor(255) );
+ pAlphaWriteAccess->SetPixelOnData( pScanAlpha, x, BitmapColor(0) );
}
else
{
@@ -486,11 +491,11 @@ BitmapEx CanvasTransformBitmap( const BitmapEx& rBitmap,
if( nSrcX < 0 || nSrcX >= aBmpSize.Width() ||
nSrcY < 0 || nSrcY >= aBmpSize.Height() )
{
- pAlphaWriteAccess->SetPixelOnData( pScanAlpha, x, BitmapColor(255) );
+ pAlphaWriteAccess->SetPixelOnData( pScanAlpha, x, BitmapColor(0) );
}
else
{
- pAlphaWriteAccess->SetPixelOnData( pScanAlpha, x, BitmapColor(0) );
+ pAlphaWriteAccess->SetPixelOnData( pScanAlpha, x, BitmapColor(255) );
pWriteAccess->SetPixelOnData( pScan, x, pReadAccess->GetPixel( nSrcY,
nSrcX ) );
}
@@ -521,6 +526,7 @@ void DrawAlphaBitmapAndAlphaGradient(BitmapEx & rBitmapEx, bool bFixedTransparen
}
{
+
AlphaScopedWriteAccess pOld(aOldMask);
assert(pOld && "Got no access to old alpha mask (!)");
@@ -536,8 +542,8 @@ void DrawAlphaBitmapAndAlphaGradient(BitmapEx & rBitmapEx, bool bFixedTransparen
Scanline pScanline = pOld->GetScanline( y );
for(tools::Long x(0); x < pOld->Width(); x++)
{
- const double fOpOld(1.0 - (pOld->GetIndexFromData(pScanline, x) * fFactor));
- const sal_uInt8 aCol(basegfx::fround((1.0 - (fOpOld * fOpNew)) * 255.0));
+ const double fOpOld(pOld->GetIndexFromData(pScanline, x) * fFactor);
+ const sal_uInt8 aCol(basegfx::fround((fOpOld * fOpNew) * 255.0));
pOld->SetPixelOnData(pScanline, x, BitmapColor(aCol));
}
@@ -557,9 +563,9 @@ void DrawAlphaBitmapAndAlphaGradient(BitmapEx & rBitmapEx, bool bFixedTransparen
Scanline pScanline = pOld->GetScanline( y );
for(tools::Long x(0); x < pOld->Width(); x++)
{
- const double fOpOld(1.0 - (pOld->GetIndexFromData(pScanline, x) * fFactor));
- const double fOpNew(1.0 - (pNew->GetIndexFromData(pScanline, x) * fFactor));
- const sal_uInt8 aCol(basegfx::fround((1.0 - (fOpOld * fOpNew)) * 255.0));
+ const double fOpOld(pOld->GetIndexFromData(pScanline, x) * fFactor);
+ const double fOpNew(pNew->GetIndexFromData(pScanline, x) * fFactor);
+ const sal_uInt8 aCol(basegfx::fround((fOpOld * fOpNew) * 255.0));
pOld->SetPixelOnData(pScanline, x, BitmapColor(aCol));
}
@@ -619,10 +625,9 @@ void DrawAndClipBitmap(const Point& rPos, const Size& rSize, const BitmapEx& rBi
const sal_uInt8 nIndR(pR->GetIndexFromData(pScanlineR, nX));
const sal_uInt8 nIndW(pW->GetIndexFromData(pScanlineW, nX));
- // these values represent transparency (0 == no, 255 == fully transparent),
- // so to blend these we have to multiply the inverse (opacity)
- // and re-invert the result to transparence
- const sal_uInt8 nCombined(0x00ff - (((0x00ff - nIndR) * (0x00ff - nIndW)) >> 8));
+ // these values represent alpha (255 == no, 0 == fully transparent),
+ // so to blend these we have to multiply
+ const sal_uInt8 nCombined((nIndR * nIndW) >> 8);
pW->SetPixelOnData(pScanlineW, nX, BitmapColor(nCombined));
}
@@ -672,7 +677,7 @@ static bool readAlpha( BitmapReadAccess const * pAlphaReadAcc, tools::Long nY, c
BitmapColor const& rColor(
pAlphaReadAcc->GetPaletteColor(*pReadScan));
pReadScan++;
- nAlpha = data[ nOff ] = 255 - rColor.GetIndex();
+ nAlpha = data[ nOff ] = rColor.GetIndex();
if( nAlpha != 255 )
bIsAlpha = true;
nOff += 4;
@@ -682,7 +687,7 @@ static bool readAlpha( BitmapReadAccess const * pAlphaReadAcc, tools::Long nY, c
SAL_INFO( "canvas.cairo", "fallback to GetColor for alpha - slow, format: " << static_cast<int>(pAlphaReadAcc->GetScanlineFormat()) );
for( nX = 0; nX < nWidth; nX++ )
{
- nAlpha = data[ nOff ] = 255 - pAlphaReadAcc->GetColor( nY, nX ).GetIndex();
+ nAlpha = data[ nOff ] = pAlphaReadAcc->GetColor( nY, nX ).GetIndex();
if( nAlpha != 255 )
bIsAlpha = true;
nOff += 4;
@@ -1027,7 +1032,7 @@ void CanvasCairoExtractBitmapData( BitmapEx const & aBmpEx, Bitmap & aBitmap, un
pRes[ nCurrPos++ ] = pReadAccess->GetColor( y, x ).GetRed();
pRes[ nCurrPos++ ] = pReadAccess->GetColor( y, x ).GetGreen();
pRes[ nCurrPos++ ] = pReadAccess->GetColor( y, x ).GetBlue();
- pRes[ nCurrPos++ ] = pAlphaReadAccess->GetIndexFromData( pScanlineReadAlpha, x );
+ pRes[ nCurrPos++ ] = 255 - pAlphaReadAccess->GetIndexFromData( pScanlineReadAlpha, x );
}
}
else
@@ -1217,7 +1222,7 @@ bool convertBitmap32To24Plus8(BitmapEx const & rInput, BitmapEx & rResult)
{
const BitmapColor aColor = pReadAccess->GetPixelFromData(aReadScan, nX);
BitmapColor aResultColor(aColor.GetRed(), aColor.GetGreen(), aColor.GetBlue());
- BitmapColor aResultColorAlpha(255 - aColor.GetAlpha(), 255 - aColor.GetAlpha(), 255 - aColor.GetAlpha());
+ BitmapColor aResultColorAlpha(aColor.GetAlpha(), aColor.GetAlpha(), aColor.GetAlpha());
pResultBitmapAccess->SetPixelOnData(aResultScan, nX, aResultColor);
pResultAlphaAccess->SetPixelOnData(aResultScanAlpha, nX, aResultColorAlpha);
diff --git a/vcl/source/bitmap/alpha.cxx b/vcl/source/bitmap/alpha.cxx
index f307dda63a28..8e082c695137 100644
--- a/vcl/source/bitmap/alpha.cxx
+++ b/vcl/source/bitmap/alpha.cxx
@@ -31,8 +31,11 @@ AlphaMask::AlphaMask() = default;
AlphaMask::AlphaMask( const Bitmap& rBitmap ) :
Bitmap( rBitmap )
{
- if( !rBitmap.IsEmpty() )
+ // no need to do any conversion if it is already an AlphaMask
+ if ( typeid(rBitmap) != typeid(AlphaMask) && !rBitmap.IsEmpty() )
Convert( BmpConversion::N8BitNoConversion );
+ assert( (IsEmpty() || getPixelFormat() == vcl::PixelFormat::N8_BPP) && "alpha bitmap should be 8bpp" );
+ assert( (IsEmpty() || HasGreyPalette8Bit()) && "alpha bitmap should have greyscale palette" );
}
AlphaMask::AlphaMask( const AlphaMask& ) = default;
@@ -43,7 +46,12 @@ AlphaMask::AlphaMask( const Size& rSizePixel, const sal_uInt8* pEraseTransparenc
: Bitmap(rSizePixel, vcl::PixelFormat::N8_BPP, &Bitmap::GetGreyPalette(256))
{
if( pEraseTransparency )
- Bitmap::Erase( Color( *pEraseTransparency, *pEraseTransparency, *pEraseTransparency ) );
+ {
+ sal_uInt8 nAlpha = 255 - *pEraseTransparency;
+ Bitmap::Erase( Color( nAlpha, nAlpha, nAlpha ) );
+ }
+ else
+ Bitmap::Erase( COL_ALPHA_OPAQUE );
}
AlphaMask::~AlphaMask() = default;
@@ -55,6 +63,9 @@ AlphaMask& AlphaMask::operator=( const Bitmap& rBitmap )
if( !rBitmap.IsEmpty() )
Convert( BmpConversion::N8BitNoConversion );
+ assert( getPixelFormat() == vcl::PixelFormat::N8_BPP && "alpha bitmap should be 8bpp" );
+ assert( HasGreyPalette8Bit() && "alpha bitmap should have greyscale palette" );
+
return *this;
}
@@ -70,7 +81,8 @@ Bitmap const & AlphaMask::GetBitmap() const
void AlphaMask::Erase( sal_uInt8 cTransparency )
{
- Bitmap::Erase( Color( cTransparency, cTransparency, cTransparency ) );
+ sal_uInt8 nAlpha = 255 - cTransparency;
+ Bitmap::Erase( Color( nAlpha, nAlpha, nAlpha ) );
}
void AlphaMask::BlendWith(const AlphaMask& rOther)
@@ -79,6 +91,8 @@ void AlphaMask::BlendWith(const AlphaMask& rOther)
if (xImpBmp->Create(*ImplGetSalBitmap()) && xImpBmp->AlphaBlendWith(*rOther.ImplGetSalBitmap()))
{
ImplSetSalBitmap(xImpBmp);
+ assert( getPixelFormat() == vcl::PixelFormat::N8_BPP && "alpha bitmap should be 8bpp" );
+ assert( HasGreyPalette8Bit() && "alpha bitmap should have greyscale palette" );
return;
}
Bitmap::ScopedReadAccess pOtherAcc(const_cast<AlphaMask&>(rOther));
@@ -101,11 +115,18 @@ void AlphaMask::BlendWith(const AlphaMask& rOther)
// Use sal_uInt16 for following multiplication
const sal_uInt16 nGrey1 = *scanline;
const sal_uInt16 nGrey2 = *otherScanline;
- *scanline = static_cast<sal_uInt8>(nGrey1 + nGrey2 - nGrey1 * nGrey2 / 255);
+ // Awkward calculation because the original used transparency, and to replicate
+ // the logic we need to translate into transparency, perform the original logic,
+ // then translate back to alpha.
+ auto tmp = 255 - ((255 - nGrey1) + (255 - nGrey2) - (255 - nGrey1) * (255 - nGrey2));
+ *scanline = static_cast<sal_uInt8>(tmp / 255);
++scanline;
++otherScanline;
}
}
+ pAcc.reset();
+ assert( getPixelFormat() == vcl::PixelFormat::N8_BPP && "alpha bitmap should be 8bpp" );
+ assert( HasGreyPalette8Bit() && "alpha bitmap should have greyscale palette" );
}
bool AlphaMask::hasAlpha() const
@@ -126,7 +147,7 @@ bool AlphaMask::hasAlpha() const
{
for (tools::Long x = 0; x < nWidth; ++x)
{
- if (0 != pAcc->GetColor(y, x).GetRed())
+ if (255 != pAcc->GetColor(y, x).GetRed())
{
return true;
}
@@ -143,6 +164,8 @@ void AlphaMask::ReleaseAccess( BitmapReadAccess* pAccess )
Bitmap::ReleaseAccess( pAccess );
Convert( BmpConversion::N8BitNoConversion );
}
+ assert( getPixelFormat() == vcl::PixelFormat::N8_BPP && "alpha bitmap should be 8bpp" );
+ assert( HasGreyPalette8Bit() && "alpha bitmap should have greyscale palette" );
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/bitmappaint.cxx b/vcl/source/bitmap/bitmappaint.cxx
index 0207bb813fe8..487e6d1a6ad6 100644
--- a/vcl/source/bitmap/bitmappaint.cxx
+++ b/vcl/source/bitmap/bitmappaint.cxx
@@ -60,38 +60,54 @@ bool Bitmap::Erase(const Color& rFillColor)
bool Bitmap::Invert()
{
- ScopedReadAccess pReadAcc(*this);
- if (!pReadAcc)
+ if (!mxSalBmp)
return false;
- if (pReadAcc->HasPalette())
+ // For alpha masks, we need to actually invert the underlying data
+ // or the optimisations elsewhere do not work right.
+ if (typeid(*this) != typeid(AlphaMask))
{
- BitmapScopedWriteAccess pWriteAcc(*this);
- BitmapPalette aBmpPal(pWriteAcc->GetPalette());
- const sal_uInt16 nCount = aBmpPal.GetEntryCount();
-
- for (sal_uInt16 i = 0; i < nCount; i++)
+ // We want to avoid using ScopedReadAccess until we really need
+ // it, because on Skia it triggers a GPU->RAM copy, which is very slow.
+ ScopedReadAccess pReadAcc(*this);
+ if (!pReadAcc)
+ return false;
+ if (pReadAcc->HasPalette())
{
- aBmpPal[i].Invert();
- }
+ BitmapScopedWriteAccess pWriteAcc(*this);
+ BitmapPalette aBmpPal(pWriteAcc->GetPalette());
+ const sal_uInt16 nCount = aBmpPal.GetEntryCount();
- pWriteAcc->SetPalette(aBmpPal);
+ for (sal_uInt16 i = 0; i < nCount; i++)
+ {
+ aBmpPal[i].Invert();
+ }
+
+ pWriteAcc->SetPalette(aBmpPal);
+ mxSalBmp->InvalidateChecksum();
+ return true;
+ }
}
- else if (!mxSalBmp->Invert()) // try optimised call first
+
+ // try optimised call, much faster on Skia
+ if (mxSalBmp->Invert())
{
- BitmapScopedWriteAccess pWriteAcc(*this);
- const tools::Long nWidth = pWriteAcc->Width();
- const tools::Long nHeight = pWriteAcc->Height();
+ mxSalBmp->InvalidateChecksum();
+ return true;
+ }
- for (tools::Long nY = 0; nY < nHeight; nY++)
+ BitmapScopedWriteAccess pWriteAcc(*this);
+ const tools::Long nWidth = pWriteAcc->Width();
+ const tools::Long nHeight = pWriteAcc->Height();
+
+ for (tools::Long nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ for (tools::Long nX = 0; nX < nWidth; nX++)
{
- Scanline pScanline = pWriteAcc->GetScanline(nY);
- for (tools::Long nX = 0; nX < nWidth; nX++)
- {
- BitmapColor aBmpColor = pWriteAcc->GetPixelFromData(pScanline, nX);
- aBmpColor.Invert();
- pWriteAcc->SetPixelOnData(pScanline, nX, aBmpColor);
- }
+ BitmapColor aBmpColor = pWriteAcc->GetPixelFromData(pScanline, nX);
+ aBmpColor.Invert();
+ pWriteAcc->SetPixelOnData(pScanline, nX, aBmpColor);
}
}
@@ -607,6 +623,174 @@ Bitmap Bitmap::CreateMask(const Color& rTransColor, sal_uInt8 nTol) const
return aNewBmp;
}
+AlphaMask Bitmap::CreateAlphaMask(const Color& rTransColor) const
+{
+ ScopedReadAccess pReadAcc(const_cast<Bitmap&>(*this));
+ if (!pReadAcc)
+ return AlphaMask();
+
+ // Historically LO used 1bpp masks, but 8bpp masks are much faster,
+ // better supported by hardware, and the memory savings are not worth
+ // it anymore.
+
+ if ((pReadAcc->GetScanlineFormat() == ScanlineFormat::N1BitMsbPal)
+ && pReadAcc->GetBestMatchingColor(COL_ALPHA_TRANSPARENT)
+ == pReadAcc->GetBestMatchingColor(rTransColor))
+ {
+ // if we're a 1 bit pixel already, and the transcolor matches the color that would replace it
+ // already, then just return a copy
+ return AlphaMask(*this);
+ }
+
+ AlphaMask aNewBmp(GetSizePixel());
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+ if (!pWriteAcc)
+ return AlphaMask();
+
+ const tools::Long nWidth = pReadAcc->Width();
+ const tools::Long nHeight = pReadAcc->Height();
+ const BitmapColor aOpaqueColor(pWriteAcc->GetBestMatchingColor(COL_ALPHA_OPAQUE));
+ const BitmapColor aTransparentColor(pWriteAcc->GetBestMatchingColor(COL_ALPHA_TRANSPARENT));
+
+ const BitmapColor aTest(pReadAcc->GetBestMatchingColor(rTransColor));
+
+ if (pReadAcc->GetScanlineFormat() == ScanlineFormat::N8BitPal)
+ {
+ // optimized for 8Bit source palette
+ const sal_uInt8 cTest = aTest.GetIndex();
+
+ for (tools::Long nY = 0; nY < nHeight; ++nY)
+ {
+ Scanline pSrc = pReadAcc->GetScanline(nY);
+ Scanline pDst = pWriteAcc->GetScanline(nY);
+ for (tools::Long nX = 0; nX < nWidth; ++nX)
+ {
+ if (cTest == pSrc[nX])
+ pDst[nX] = aTransparentColor.GetIndex();
+ else
+ pDst[nX] = aOpaqueColor.GetIndex();
+ }
+ }
+ }
+ else
+ {
+ // not optimized
+ for (tools::Long nY = 0; nY < nHeight; ++nY)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ Scanline pScanlineRead = pReadAcc->GetScanline(nY);
+ for (tools::Long nX = 0; nX < nWidth; ++nX)
+ {
+ if (aTest == pReadAcc->GetPixelFromData(pScanlineRead, nX))
+ pWriteAcc->SetPixelOnData(pScanline, nX, aTransparentColor);
+ else
+ pWriteAcc->SetPixelOnData(pScanline, nX, aOpaqueColor);
+ }
+ }
+ }
+
+ pWriteAcc.reset();
+ pReadAcc.reset();
+
+ aNewBmp.maPrefSize = maPrefSize;
+ aNewBmp.maPrefMapMode = maPrefMapMode;
+
+ return aNewBmp;
+}
+
+AlphaMask Bitmap::CreateAlphaMask(const Color& rTransColor, sal_uInt8 nTol) const
+{
+ if (nTol == 0)
+ return CreateAlphaMask(rTransColor);
+
+ ScopedReadAccess pReadAcc(const_cast<Bitmap&>(*this));
+ if (!pReadAcc)
+ return AlphaMask();
+
+ // Historically LO used 1bpp masks, but 8bpp masks are much faster,
+ // better supported by hardware, and the memory savings are not worth
+ // it anymore.
+ // TODO: Possibly remove the 1bpp code later.
+
+ AlphaMask aNewBmp(GetSizePixel());
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+ if (!pWriteAcc)
+ return AlphaMask();
+
+ const tools::Long nWidth = pReadAcc->Width();
+ const tools::Long nHeight = pReadAcc->Height();
+ const BitmapColor aOpaqueColor(pWriteAcc->GetBestMatchingColor(COL_ALPHA_OPAQUE));
+ const BitmapColor aTransparentColor(pWriteAcc->GetBestMatchingColor(COL_ALPHA_TRANSPARENT));
+
+ BitmapColor aCol;
+ tools::Long nR, nG, nB;
+ const tools::Long nMinR = MinMax<tools::Long>(rTransColor.GetRed() - nTol, 0, 255);
+ const tools::Long nMaxR = MinMax<tools::Long>(rTransColor.GetRed() + nTol, 0, 255);
+ const tools::Long nMinG = MinMax<tools::Long>(rTransColor.GetGreen() - nTol, 0, 255);
+ const tools::Long nMaxG = MinMax<tools::Long>(rTransColor.GetGreen() + nTol, 0, 255);
+ const tools::Long nMinB = MinMax<tools::Long>(rTransColor.GetBlue() - nTol, 0, 255);
+ const tools::Long nMaxB = MinMax<tools::Long>(rTransColor.GetBlue() + nTol, 0, 255);
+
+ if (pReadAcc->HasPalette())
+ {
+ for (tools::Long nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ Scanline pScanlineRead = pReadAcc->GetScanline(nY);
+ for (tools::Long nX = 0; nX < nWidth; nX++)
+ {
+ aCol = pReadAcc->GetPaletteColor(pReadAcc->GetIndexFromData(pScanlineRead, nX));
+ nR = aCol.GetRed();
+ nG = aCol.GetGreen();
+ nB = aCol.GetBlue();
+
+ if (nMinR <= nR && nMaxR >= nR && nMinG <= nG && nMaxG >= nG && nMinB <= nB
+ && nMaxB >= nB)
+ {
+ pWriteAcc->SetPixelOnData(pScanline, nX, aTransparentColor);
+ }
+ else
+ {
+ pWriteAcc->SetPixelOnData(pScanline, nX, aOpaqueColor);
+ }
+ }
+ }
+ }
+ else
+ {
+ for (tools::Long nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ Scanline pScanlineRead = pReadAcc->GetScanline(nY);
+ for (tools::Long nX = 0; nX < nWidth; nX++)
+ {
+ aCol = pReadAcc->GetPixelFromData(pScanlineRead, nX);
+ nR = aCol.GetRed();
+ nG = aCol.GetGreen();
+ nB = aCol.GetBlue();
+
+ if (nMinR <= nR && nMaxR >= nR && nMinG <= nG && nMaxG >= nG && nMinB <= nB
+ && nMaxB >= nB)
+ {
+ pWriteAcc->SetPixelOnData(pScanline, nX, aTransparentColor);
+ }
+ else
+ {
+ pWriteAcc->SetPixelOnData(pScanline, nX, aOpaqueColor);
+ }
+ }
+ }
+ }
+
+ pWriteAcc.reset();
+ pReadAcc.reset();
+
+ aNewBmp.maPrefSize = maPrefSize;
+ aNewBmp.maPrefMapMode = maPrefMapMode;
+
+ return aNewBmp;
+}
+
vcl::Region Bitmap::CreateRegion(const Color& rColor, const tools::Rectangle& rRect) const
{
tools::Rectangle aRect(rRect);
@@ -721,7 +905,7 @@ bool Bitmap::Replace(const AlphaMask& rAlpha, const Color& rMergeColor)
for (tools::Long nX = 0; nX < nWidth; nX++)
{
aCol = pAcc->GetColor(nY, nX);
- aCol.Merge(rMergeColor, 255 - pAlphaAcc->GetIndexFromData(pScanlineAlpha, nX));
+ aCol.Merge(rMergeColor, pAlphaAcc->GetIndexFromData(pScanlineAlpha, nX));
pNewAcc->SetPixelOnData(pScanline, nX, aCol);
}
}
@@ -965,8 +1149,7 @@ bool Bitmap::Blend(const AlphaMask& rAlpha, const Color& rBackgroundColor)
for (tools::Long nX = 0; nX < nWidth; ++nX)
{
BitmapColor aBmpColor = pAcc->GetPixelFromData(pScanline, nX);
- aBmpColor.Merge(rBackgroundColor,
- 255 - pAlphaAcc->GetIndexFromData(pScanlineAlpha, nX));
+ aBmpColor.Merge(rBackgroundColor, pAlphaAcc->GetIndexFromData(pScanlineAlpha, nX));
pAcc->SetPixelOnData(pScanline, nX, aBmpColor);
}
}
diff --git a/vcl/source/bitmap/dibtools.cxx b/vcl/source/bitmap/dibtools.cxx
index d8fa61362635..d6ac43a403da 100644
--- a/vcl/source/bitmap/dibtools.cxx
+++ b/vcl/source/bitmap/dibtools.cxx
@@ -1699,7 +1699,11 @@ bool ReadDIBV5(
AlphaMask& rTargetAlpha,
SvStream& rIStm)
{
- return ImplReadDIB(rTarget, &rTargetAlpha, rIStm, true);
+ bool rv = ImplReadDIB(rTarget, &rTargetAlpha, rIStm, true);
+ // convert transparency->alpha
+ if (rv)
+ rTargetAlpha.Invert();
+ return rv;
}
bool ReadRawDIB(
@@ -1747,7 +1751,10 @@ bool WriteDIBBitmapEx(
if(rSource.IsAlpha())
{
- return ImplWriteDIB(rSource.maAlphaMask, rOStm, true, true);
+ // invert the alpha because the other routines actually want transparency
+ AlphaMask tmpAlpha = rSource.maAlphaMask;
+ tmpAlpha.Invert();
+ return ImplWriteDIB(tmpAlpha, rOStm, true, true);
}
}