Remove the special-casing in Bitmap::Invert (and fix)

and rather rely on the backends doing the right thing, which is
considerably faster.

Which uncovers a bug in the existing code - it is not legal
to simply invert the value when dealing with palette-based
images. Fix this by sharing some code with Bitmap::ReplaceMask.

Change-Id: I2ef340a9f251c8c7e27b68ab451ce85df07c1035
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/160332
Tested-by: Jenkins
Reviewed-by: Noel Grandin <noel.grandin@collabora.co.uk>
This commit is contained in:
Noel Grandin 2023-12-05 10:45:33 +02:00
parent a9ad36ae46
commit bb3157e38b
5 changed files with 140 additions and 72 deletions

View File

@ -77,6 +77,9 @@ public:
return mpBuffer ? mpBuffer->mnBitCount : 0;
}
/// Returns the BitmapColor (i.e. palette index) that is either an exact match
/// of the required color, or failing that, the entry that is the closest i.e. least error
/// as measured by Color::GetColorError.
BitmapColor GetBestMatchingColor(const BitmapColor& rBitmapColor) const
{
if (HasPalette())
@ -121,7 +124,13 @@ public:
return pBuffer->maPalette[nColor];
}
/// Returns the BitmapColor (i.e. palette index) that is either an exact match
/// of the required color, or failing that, the entry that is the closest i.e. least error
/// as measured by Color::GetColorError.
sal_uInt16 GetBestPaletteIndex(const BitmapColor& rBitmapColor) const;
/// Returns the BitmapColor (i.e. palette index) that is an exact match
/// of the required color. Returns SAL_MAX_UINT16 if nothing found.
sal_uInt16 GetMatchingPaletteIndex(const BitmapColor& rBitmapColor) const;
const ColorMask& GetColorMask() const
{

View File

@ -65,7 +65,13 @@ public:
const BitmapColor& operator[](sal_uInt16 nIndex) const;
BitmapColor& operator[](sal_uInt16 nIndex);
/// Returns the BitmapColor (i.e. palette index) that is either an exact match
/// of the required color, or failing that, the entry that is the closest i.e. least error
/// as measured by Color::GetColorError.
sal_uInt16 GetBestIndex(const BitmapColor& rCol) const;
/// Returns the BitmapColor (i.e. palette index) that is an exact match
/// of the required color. Returns SAL_MAX_UINT16 if nothing found.
sal_uInt16 GetMatchingIndex(const BitmapColor& rCol) const;
/// Returns true if the palette is 8-bit grey palette.
bool IsGreyPalette8Bit() const;

View File

@ -77,4 +77,10 @@ sal_uInt16 BitmapInfoAccess::GetBestPaletteIndex(const BitmapColor& rBitmapColor
return (HasPalette() ? pBuffer->maPalette.GetBestIndex(rBitmapColor) : 0);
}
sal_uInt16 BitmapInfoAccess::GetMatchingPaletteIndex(const BitmapColor& rBitmapColor) const
{
assert(HasPalette());
return mpBuffer->maPalette.GetMatchingIndex(rBitmapColor);
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

View File

@ -31,6 +31,12 @@
#include <algorithm>
#include <memory>
static BitmapColor UpdatePaletteForNewColor(BitmapScopedWriteAccess& pAcc,
const sal_uInt16 nActColors,
const sal_uInt16 nMaxColors, const tools::Long nHeight,
const tools::Long nWidth,
const BitmapColor& rWantedColor);
bool Bitmap::Erase(const Color& rFillColor)
{
if (IsEmpty())
@ -63,32 +69,6 @@ bool Bitmap::Invert()
if (!mxSalBmp)
return false;
// For alpha masks, we need to actually invert the underlying data
// or the optimisations elsewhere do not work right.
if (typeid(*this) != typeid(AlphaMask))
{
// 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())
{
BitmapScopedWriteAccess pWriteAcc(*this);
BitmapPalette aBmpPal(pWriteAcc->GetPalette());
const sal_uInt16 nCount = aBmpPal.GetEntryCount();
for (sal_uInt16 i = 0; i < nCount; i++)
{
aBmpPal[i].Invert();
}
pWriteAcc->SetPalette(aBmpPal);
mxSalBmp->InvalidateChecksum();
return true;
}
}
// try optimised call, much faster on Skia
if (mxSalBmp->Invert())
{
@ -100,17 +80,54 @@ bool Bitmap::Invert()
const tools::Long nWidth = pWriteAcc->Width();
const tools::Long nHeight = pWriteAcc->Height();
for (tools::Long nY = 0; nY < nHeight; nY++)
if (pWriteAcc->HasPalette())
{
Scanline pScanline = pWriteAcc->GetScanline(nY);
for (tools::Long nX = 0; nX < nWidth; nX++)
const sal_uInt16 nActColors = pWriteAcc->GetPaletteEntryCount();
const sal_uInt16 nMaxColors = 1 << pWriteAcc->GetBitCount();
if (pWriteAcc->GetPalette().IsGreyPalette8Bit())
{
BitmapColor aBmpColor = pWriteAcc->GetPixelFromData(pScanline, nX);
aBmpColor.Invert();
pWriteAcc->SetPixelOnData(pScanline, nX, aBmpColor);
for (tools::Long nY = 0; nY < nHeight; nY++)
{
Scanline pScanline = pWriteAcc->GetScanline(nY);
for (tools::Long nX = 0; nX < nWidth; nX++)
{
BitmapColor aBmpColor = pWriteAcc->GetPixelFromData(pScanline, nX);
aBmpColor.SetIndex(0xff - aBmpColor.GetIndex());
pWriteAcc->SetPixelOnData(pScanline, nX, aBmpColor);
}
}
}
else
{
for (tools::Long nY = 0; nY < nHeight; nY++)
{
Scanline pScanline = pWriteAcc->GetScanline(nY);
for (tools::Long nX = 0; nX < nWidth; nX++)
{
BitmapColor aBmpColor = pWriteAcc->GetPixelFromData(pScanline, nX);
aBmpColor = pWriteAcc->GetPaletteColor(aBmpColor.GetIndex());
aBmpColor.Invert();
BitmapColor aReplace = UpdatePaletteForNewColor(
pWriteAcc, nActColors, nMaxColors, nHeight, nWidth, aBmpColor);
pWriteAcc->SetPixelOnData(pScanline, nX, aReplace);
}
}
}
}
else
{
for (tools::Long nY = 0; nY < nHeight; nY++)
{
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);
}
}
}
mxSalBmp->InvalidateChecksum();
return true;
@ -902,45 +919,8 @@ bool Bitmap::ReplaceMask(const AlphaMask& rMask, const Color& rReplaceColor)
const sal_uInt16 nActColors = pAcc->GetPaletteEntryCount();
const sal_uInt16 nMaxColors = 1 << pAcc->GetBitCount();
// default to the nearest color
aReplace = pAcc->GetBestMatchingColor(rReplaceColor);
// for paletted images without a matching palette entry
// look for an unused palette entry (NOTE: expensive!)
if (pAcc->GetPaletteColor(aReplace.GetIndex()) != BitmapColor(rReplaceColor))
{
// if the palette has empty entries use the last one
if (nActColors < nMaxColors)
{
pAcc->SetPaletteEntryCount(nActColors + 1);
pAcc->SetPaletteColor(nActColors, rReplaceColor);
aReplace = BitmapColor(static_cast<sal_uInt8>(nActColors));
}
else
{
std::unique_ptr<bool[]> pFlags(new bool[nMaxColors]);
// Set all entries to false
std::fill(pFlags.get(), pFlags.get() + nMaxColors, false);
for (tools::Long nY = 0; nY < nHeight; nY++)
{
Scanline pScanline = pAcc->GetScanline(nY);
for (tools::Long nX = 0; nX < nWidth; nX++)
pFlags[pAcc->GetIndexFromData(pScanline, nX)] = true;
}
for (sal_uInt16 i = 0; i < nMaxColors; i++)
{
// Hurray, we do have an unused entry
if (!pFlags[i])
{
pAcc->SetPaletteColor(i, rReplaceColor);
aReplace = BitmapColor(static_cast<sal_uInt8>(i));
}
}
}
}
aReplace
= UpdatePaletteForNewColor(pAcc, nActColors, nMaxColors, nHeight, nWidth, aReplace);
}
else
aReplace = rReplaceColor;
@ -1196,4 +1176,51 @@ bool Bitmap::Blend(const AlphaMask& rAlpha, const Color& rBackgroundColor)
return true;
}
static BitmapColor UpdatePaletteForNewColor(BitmapScopedWriteAccess& pAcc,
const sal_uInt16 nActColors,
const sal_uInt16 nMaxColors, const tools::Long nHeight,
const tools::Long nWidth,
const BitmapColor& rWantedColor)
{
// default to the nearest color
sal_uInt16 aReplacePalIndex = pAcc->GetMatchingPaletteIndex(rWantedColor);
if (aReplacePalIndex != SAL_MAX_UINT16)
return BitmapColor(static_cast<sal_uInt8>(aReplacePalIndex));
// for paletted images without a matching palette entry
// if the palette has empty entries use the last one
if (nActColors < nMaxColors)
{
pAcc->SetPaletteEntryCount(nActColors + 1);
pAcc->SetPaletteColor(nActColors, rWantedColor);
return BitmapColor(static_cast<sal_uInt8>(nActColors));
}
// look for an unused palette entry (NOTE: expensive!)
std::unique_ptr<bool[]> pFlags(new bool[nMaxColors]);
// Set all entries to false
std::fill(pFlags.get(), pFlags.get() + nMaxColors, false);
for (tools::Long nY = 0; nY < nHeight; nY++)
{
Scanline pScanline = pAcc->GetScanline(nY);
for (tools::Long nX = 0; nX < nWidth; nX++)
pFlags[pAcc->GetIndexFromData(pScanline, nX)] = true;
}
for (sal_uInt16 i = 0; i < nMaxColors; i++)
{
// Hurray, we do have an unused entry
if (!pFlags[i])
{
pAcc->SetPaletteColor(i, rWantedColor);
return BitmapColor(static_cast<sal_uInt8>(i));
}
}
assert(false && "found nothing");
return BitmapColor(0);
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

View File

@ -147,6 +147,9 @@ BitmapColor& BitmapPalette::operator[](sal_uInt16 nIndex)
return mpImpl->GetBitmapData()[nIndex];
}
/// Returns the BitmapColor (i.e. palette index) that is either an exact match
/// of the required color, or failing that, the entry that is the closest i.e. least error
/// as measured by Color::GetColorError.
sal_uInt16 BitmapPalette::GetBestIndex(const BitmapColor& rCol) const
{
auto const& rBitmapColor = mpImpl->GetBitmapData();
@ -177,6 +180,23 @@ sal_uInt16 BitmapPalette::GetBestIndex(const BitmapColor& rCol) const
return nRetIndex;
}
/// Returns the BitmapColor (i.e. palette index) that is an exact match
/// of the required color. Returns SAL_MAX_UINT16 if nothing found.
sal_uInt16 BitmapPalette::GetMatchingIndex(const BitmapColor& rCol) const
{
auto const& rBitmapColor = mpImpl->GetBitmapData();
for (size_t j = 0; j < rBitmapColor.size(); ++j)
{
if (rCol == rBitmapColor[j])
{
return j;
}
}
return SAL_MAX_UINT16;
}
bool BitmapPalette::IsGreyPaletteAny() const
{
auto const& rBitmapColor = mpImpl->GetBitmapData();