summaryrefslogtreecommitdiffstats
path: root/vcl/source/bitmap
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
commit267c6f2ac71f92999e969232431ba04678e7437e (patch)
tree358c9467650e1d0a1d7227a21dac2e3d08b622b2 /vcl/source/bitmap
parentInitial commit. (diff)
downloadlibreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz
libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vcl/source/bitmap')
-rw-r--r--vcl/source/bitmap/BitmapAlphaClampFilter.cxx44
-rw-r--r--vcl/source/bitmap/BitmapBasicMorphologyFilter.cxx370
-rw-r--r--vcl/source/bitmap/BitmapColorQuantizationFilter.cxx200
-rw-r--r--vcl/source/bitmap/BitmapColorizeFilter.cxx92
-rw-r--r--vcl/source/bitmap/BitmapConvolutionMatrixFilter.cxx199
-rw-r--r--vcl/source/bitmap/BitmapDisabledImageFilter.cxx60
-rw-r--r--vcl/source/bitmap/BitmapDuoToneFilter.cxx59
-rw-r--r--vcl/source/bitmap/BitmapEmbossGreyFilter.cxx136
-rw-r--r--vcl/source/bitmap/BitmapEx.cxx1523
-rw-r--r--vcl/source/bitmap/BitmapFastScaleFilter.cxx129
-rw-r--r--vcl/source/bitmap/BitmapFilterStackBlur.cxx658
-rw-r--r--vcl/source/bitmap/BitmapGaussianSeparableBlurFilter.cxx215
-rw-r--r--vcl/source/bitmap/BitmapInfoAccess.cxx91
-rw-r--r--vcl/source/bitmap/BitmapInterpolateScaleFilter.cxx245
-rw-r--r--vcl/source/bitmap/BitmapLightenFilter.cxx67
-rw-r--r--vcl/source/bitmap/BitmapMaskToAlphaFilter.cxx58
-rw-r--r--vcl/source/bitmap/BitmapMedianFilter.cxx204
-rw-r--r--vcl/source/bitmap/BitmapMonochromeFilter.cxx87
-rw-r--r--vcl/source/bitmap/BitmapMosaicFilter.cxx183
-rw-r--r--vcl/source/bitmap/BitmapPopArtFilter.cxx96
-rw-r--r--vcl/source/bitmap/BitmapReadAccess.cxx534
-rw-r--r--vcl/source/bitmap/BitmapScaleConvolutionFilter.cxx381
-rw-r--r--vcl/source/bitmap/BitmapScaleSuperFilter.cxx1046
-rw-r--r--vcl/source/bitmap/BitmapSeparableUnsharpenFilter.cxx75
-rw-r--r--vcl/source/bitmap/BitmapSepiaFilter.cxx98
-rw-r--r--vcl/source/bitmap/BitmapShadowFilter.cxx47
-rw-r--r--vcl/source/bitmap/BitmapSimpleColorQuantizationFilter.cxx88
-rw-r--r--vcl/source/bitmap/BitmapSmoothenFilter.cxx32
-rw-r--r--vcl/source/bitmap/BitmapSobelGreyFilter.cxx154
-rw-r--r--vcl/source/bitmap/BitmapSolarizeFilter.cxx70
-rw-r--r--vcl/source/bitmap/BitmapSymmetryCheck.cxx83
-rw-r--r--vcl/source/bitmap/BitmapTools.cxx1302
-rw-r--r--vcl/source/bitmap/BitmapWriteAccess.cxx399
-rw-r--r--vcl/source/bitmap/Octree.cxx280
-rw-r--r--vcl/source/bitmap/alpha.cxx209
-rw-r--r--vcl/source/bitmap/bitmap.cxx1681
-rw-r--r--vcl/source/bitmap/bitmapfilter.cxx58
-rw-r--r--vcl/source/bitmap/bitmappaint.cxx1226
-rw-r--r--vcl/source/bitmap/bitmappalette.cxx242
-rw-r--r--vcl/source/bitmap/bmpfast.cxx823
-rw-r--r--vcl/source/bitmap/dibtools.cxx1769
-rw-r--r--vcl/source/bitmap/floyd.hxx177
-rw-r--r--vcl/source/bitmap/impvect.cxx996
-rw-r--r--vcl/source/bitmap/impvect.hxx32
-rw-r--r--vcl/source/bitmap/salbmp.cxx335
45 files changed, 16853 insertions, 0 deletions
diff --git a/vcl/source/bitmap/BitmapAlphaClampFilter.cxx b/vcl/source/bitmap/BitmapAlphaClampFilter.cxx
new file mode 100644
index 0000000000..d36261b2eb
--- /dev/null
+++ b/vcl/source/bitmap/BitmapAlphaClampFilter.cxx
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#include <vcl/bitmapex.hxx>
+#include <vcl/BitmapAlphaClampFilter.hxx>
+#include <vcl/BitmapWriteAccess.hxx>
+
+BitmapEx BitmapAlphaClampFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ if (!rBitmapEx.IsAlpha())
+ return rBitmapEx;
+
+ AlphaMask aBitmapAlpha(rBitmapEx.GetAlphaMask());
+ {
+ BitmapScopedWriteAccess pWriteAlpha(aBitmapAlpha);
+ const Size aSize(rBitmapEx.GetSizePixel());
+
+ for (sal_Int32 nY = 0; nY < sal_Int32(aSize.Height()); ++nY)
+ {
+ Scanline pScanAlpha = pWriteAlpha->GetScanline(nY);
+
+ for (sal_Int32 nX = 0; nX < sal_Int32(aSize.Width()); ++nX)
+ {
+ BitmapColor aBitmapAlphaValue(pWriteAlpha->GetPixelFromData(pScanAlpha, nX));
+ if ((255 - aBitmapAlphaValue.GetIndex()) > mcThreshold)
+ {
+ aBitmapAlphaValue.SetIndex(0);
+ pWriteAlpha->SetPixelOnData(pScanAlpha, nX, aBitmapAlphaValue);
+ }
+ }
+ }
+ }
+
+ return BitmapEx(rBitmapEx.GetBitmap(), aBitmapAlpha);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapBasicMorphologyFilter.cxx b/vcl/source/bitmap/BitmapBasicMorphologyFilter.cxx
new file mode 100644
index 0000000000..e4285cb53a
--- /dev/null
+++ b/vcl/source/bitmap/BitmapBasicMorphologyFilter.cxx
@@ -0,0 +1,370 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#include <sal/config.h>
+
+#include <comphelper/threadpool.hxx>
+#include <sal/log.hxx>
+#include <vcl/BitmapBasicMorphologyFilter.hxx>
+#include <vcl/BitmapWriteAccess.hxx>
+
+#include <algorithm>
+
+/* TODO: Use round kernel instead of square one.
+ This would make the result more natural, e.g. not making rounded square out of circle.
+ */
+
+namespace
+{
+struct FilterSharedData
+{
+ BitmapScopedReadAccess& mpReadAccess;
+ BitmapScopedWriteAccess& mpWriteAccess;
+ sal_Int32 mnRadius;
+ sal_uInt8 mnOutsideVal;
+ Color maOutsideColor;
+
+ FilterSharedData(BitmapScopedReadAccess& rReadAccess, BitmapScopedWriteAccess& rWriteAccess,
+ sal_Int32 nRadius, sal_uInt8 nOutsideVal)
+ : mpReadAccess(rReadAccess)
+ , mpWriteAccess(rWriteAccess)
+ , mnRadius(nRadius)
+ , mnOutsideVal(nOutsideVal)
+ , maOutsideColor(ColorTransparency, nOutsideVal, nOutsideVal, nOutsideVal, nOutsideVal)
+ {
+ }
+};
+
+// Black is foreground, white is background
+
+struct ErodeOp
+{
+ static sal_uInt8 apply(sal_uInt8 v1, sal_uInt8 v2) { return std::max(v1, v2); }
+ static constexpr sal_uInt8 initVal = 0;
+};
+
+struct DilateOp
+{
+ static sal_uInt8 apply(sal_uInt8 v1, sal_uInt8 v2) { return std::min(v1, v2); }
+ static constexpr sal_uInt8 initVal = SAL_MAX_UINT8;
+};
+
+// 8 bit per channel case
+
+template <typename MorphologyOp, int nComponentWidth> struct Value
+{
+ static constexpr int nWidthBytes = nComponentWidth / 8;
+ static_assert(nWidthBytes * 8 == nComponentWidth);
+
+ sal_uInt8 aResult[nWidthBytes];
+
+ // If we are at the start or at the end of the line, consider outside value
+ Value(FilterSharedData const& rShared, bool bLookOutside)
+ {
+ std::fill_n(aResult, nWidthBytes,
+ bLookOutside ? rShared.mnOutsideVal : MorphologyOp::initVal);
+ }
+
+ void apply(BitmapScopedReadAccess& pReadAccess, sal_Int32 x, sal_Int32 y,
+ sal_uInt8* pHint = nullptr)
+ {
+ sal_uInt8* pSource = (pHint ? pHint : pReadAccess->GetScanline(y)) + nWidthBytes * x;
+ std::transform(pSource, pSource + nWidthBytes, aResult, aResult, MorphologyOp::apply);
+ }
+
+ void copy(BitmapScopedWriteAccess& pWriteAccess, sal_Int32 x, sal_Int32 y,
+ sal_uInt8* pHint = nullptr)
+ {
+ sal_uInt8* pDest = (pHint ? pHint : pWriteAccess->GetScanline(y)) + nWidthBytes * x;
+ std::copy_n(aResult, nWidthBytes, pDest);
+ }
+};
+
+// Partial specializations for nComponentWidth == 0, using access' GetColor/SetPixel
+
+template <typename MorphologyOp> struct Value<MorphologyOp, 0>
+{
+ static constexpr Color initColor{ ColorTransparency, MorphologyOp::initVal,
+ MorphologyOp::initVal, MorphologyOp::initVal,
+ MorphologyOp::initVal };
+
+ Color aResult;
+
+ // If we are at the start or at the end of the line, consider outside value
+ Value(FilterSharedData const& rShared, bool bLookOutside)
+ : aResult(bLookOutside ? rShared.maOutsideColor : initColor)
+ {
+ }
+
+ void apply(const BitmapScopedReadAccess& pReadAccess, sal_Int32 x, sal_Int32 y,
+ sal_uInt8* /*pHint*/ = nullptr)
+ {
+ const auto& rSource = pReadAccess->GetColor(y, x);
+ aResult = Color(ColorAlpha, MorphologyOp::apply(rSource.GetAlpha(), aResult.GetAlpha()),
+ MorphologyOp::apply(rSource.GetRed(), aResult.GetRed()),
+ MorphologyOp::apply(rSource.GetGreen(), aResult.GetGreen()),
+ MorphologyOp::apply(rSource.GetBlue(), aResult.GetBlue()));
+ }
+
+ void copy(BitmapScopedWriteAccess& pWriteAccess, sal_Int32 x, sal_Int32 y,
+ sal_uInt8* /*pHint*/ = nullptr)
+ {
+ pWriteAccess->SetPixel(y, x, aResult);
+ }
+};
+
+bool GetMinMax(sal_Int32 nCenter, sal_Int32 nRadius, sal_Int32 nMaxLimit, sal_Int32& nMin,
+ sal_Int32& nMax)
+{
+ nMin = nCenter - nRadius;
+ nMax = nCenter + nRadius;
+ bool bLookOutside = false;
+ if (nMin < 0)
+ {
+ bLookOutside = true;
+ nMin = 0;
+ }
+ if (nMax > nMaxLimit)
+ {
+ bLookOutside = true;
+ nMax = nMaxLimit;
+ }
+ return bLookOutside;
+}
+
+template <typename MorphologyOp, int nComponentWidth> struct pass
+{
+ static void Horizontal(FilterSharedData const& rShared, const sal_Int32 nStart,
+ const sal_Int32 nEnd)
+ {
+ BitmapScopedReadAccess& pReadAccess = rShared.mpReadAccess;
+ BitmapScopedWriteAccess& pWriteAccess = rShared.mpWriteAccess;
+
+ const sal_Int32 nLastIndex = pReadAccess->Width() - 1;
+
+ for (sal_Int32 y = nStart; y <= nEnd; y++)
+ {
+ // Optimization
+ sal_uInt8* const pSourceHint = pReadAccess->GetScanline(y);
+ sal_uInt8* const pDestHint = pWriteAccess->GetScanline(y);
+ for (sal_Int32 x = 0; x <= nLastIndex; x++)
+ {
+ // This processes [nRadius * 2 + 1] pixels of source per resulting pixel
+ // TODO: try to optimize this to not process same pixels repeatedly
+ sal_Int32 iMin, iMax;
+ const bool bLookOutside = GetMinMax(x, rShared.mnRadius, nLastIndex, iMin, iMax);
+ Value<MorphologyOp, nComponentWidth> aResult(rShared, bLookOutside);
+ for (sal_Int32 i = iMin; i <= iMax; ++i)
+ aResult.apply(pReadAccess, i, y, pSourceHint);
+
+ aResult.copy(pWriteAccess, x, y, pDestHint);
+ }
+ }
+ }
+
+ static void Vertical(FilterSharedData const& rShared, const sal_Int32 nStart,
+ const sal_Int32 nEnd)
+ {
+ BitmapScopedReadAccess& pReadAccess = rShared.mpReadAccess;
+ BitmapScopedWriteAccess& pWriteAccess = rShared.mpWriteAccess;
+
+ const sal_Int32 nLastIndex = pReadAccess->Height() - 1;
+
+ for (sal_Int32 x = nStart; x <= nEnd; x++)
+ {
+ for (sal_Int32 y = 0; y <= nLastIndex; y++)
+ {
+ // This processes [nRadius * 2 + 1] pixels of source per resulting pixel
+ // TODO: try to optimize this to not process same pixels repeatedly
+ sal_Int32 iMin, iMax;
+ const bool bLookOutside = GetMinMax(y, rShared.mnRadius, nLastIndex, iMin, iMax);
+ Value<MorphologyOp, nComponentWidth> aResult(rShared, bLookOutside);
+ for (sal_Int32 i = iMin; i <= iMax; ++i)
+ aResult.apply(pReadAccess, x, i);
+
+ aResult.copy(pWriteAccess, x, y);
+ }
+ }
+ }
+};
+
+typedef void (*passFn)(FilterSharedData const& rShared, sal_Int32 nStart, sal_Int32 nEnd);
+
+class FilterTask : public comphelper::ThreadTask
+{
+ passFn mpFunction;
+ FilterSharedData& mrShared;
+ sal_Int32 mnStart;
+ sal_Int32 mnEnd;
+
+public:
+ explicit FilterTask(const std::shared_ptr<comphelper::ThreadTaskTag>& pTag, passFn pFunction,
+ FilterSharedData& rShared, sal_Int32 nStart, sal_Int32 nEnd)
+ : comphelper::ThreadTask(pTag)
+ , mpFunction(pFunction)
+ , mrShared(rShared)
+ , mnStart(nStart)
+ , mnEnd(nEnd)
+ {
+ }
+
+ virtual void doWork() override { mpFunction(mrShared, mnStart, mnEnd); }
+};
+
+constexpr sal_Int32 nThreadStrip = 16;
+
+template <typename MorphologyOp, int nComponentWidth>
+void runFilter(Bitmap& rBitmap, const sal_Int32 nRadius, const bool bParallel,
+ bool bUseValueOutside, sal_uInt8 nValueOutside)
+{
+ using myPass = pass<MorphologyOp, nComponentWidth>;
+ const sal_uInt8 nOutsideVal = bUseValueOutside ? nValueOutside : MorphologyOp::initVal;
+ if (bParallel)
+ {
+ try
+ {
+ comphelper::ThreadPool& rShared = comphelper::ThreadPool::getSharedOptimalPool();
+ auto pTag = comphelper::ThreadPool::createThreadTaskTag();
+
+ {
+ BitmapScopedReadAccess pReadAccess(rBitmap);
+ BitmapScopedWriteAccess pWriteAccess(rBitmap);
+ FilterSharedData aSharedData(pReadAccess, pWriteAccess, nRadius, nOutsideVal);
+
+ const sal_Int32 nLastIndex = pReadAccess->Height() - 1;
+ sal_Int32 nStripStart = 0;
+ for (; nStripStart < nLastIndex - nThreadStrip; nStripStart += nThreadStrip)
+ {
+ sal_Int32 nStripEnd = nStripStart + nThreadStrip - 1;
+ auto pTask(std::make_unique<FilterTask>(pTag, myPass::Horizontal, aSharedData,
+ nStripStart, nStripEnd));
+ rShared.pushTask(std::move(pTask));
+ }
+ // Do the last (or the only) strip in main thread without threading overhead
+ myPass::Horizontal(aSharedData, nStripStart, nLastIndex);
+ rShared.waitUntilDone(pTag);
+ }
+ {
+ BitmapScopedReadAccess pReadAccess(rBitmap);
+ BitmapScopedWriteAccess pWriteAccess(rBitmap);
+ FilterSharedData aSharedData(pReadAccess, pWriteAccess, nRadius, nOutsideVal);
+
+ const sal_Int32 nLastIndex = pReadAccess->Width() - 1;
+ sal_Int32 nStripStart = 0;
+ for (; nStripStart < nLastIndex - nThreadStrip; nStripStart += nThreadStrip)
+ {
+ sal_Int32 nStripEnd = nStripStart + nThreadStrip - 1;
+ auto pTask(std::make_unique<FilterTask>(pTag, myPass::Vertical, aSharedData,
+ nStripStart, nStripEnd));
+ rShared.pushTask(std::move(pTask));
+ }
+ // Do the last (or the only) strip in main thread without threading overhead
+ myPass::Vertical(aSharedData, nStripStart, nLastIndex);
+ rShared.waitUntilDone(pTag);
+ }
+ }
+ catch (...)
+ {
+ SAL_WARN("vcl.gdi", "threaded bitmap blurring failed");
+ }
+ }
+ else
+ {
+ {
+ BitmapScopedReadAccess pReadAccess(rBitmap);
+ BitmapScopedWriteAccess pWriteAccess(rBitmap);
+ FilterSharedData aSharedData(pReadAccess, pWriteAccess, nRadius, nOutsideVal);
+ sal_Int32 nFirstIndex = 0;
+ sal_Int32 nLastIndex = pReadAccess->Height() - 1;
+ myPass::Horizontal(aSharedData, nFirstIndex, nLastIndex);
+ }
+ {
+ BitmapScopedReadAccess pReadAccess(rBitmap);
+ BitmapScopedWriteAccess pWriteAccess(rBitmap);
+ FilterSharedData aSharedData(pReadAccess, pWriteAccess, nRadius, nOutsideVal);
+ sal_Int32 nFirstIndex = 0;
+ sal_Int32 nLastIndex = pReadAccess->Width() - 1;
+ myPass::Vertical(aSharedData, nFirstIndex, nLastIndex);
+ }
+ }
+}
+
+template <int nComponentWidth>
+void runFilter(Bitmap& rBitmap, BasicMorphologyOp op, sal_Int32 nRadius, bool bUseValueOutside,
+ sal_uInt8 nValueOutside)
+{
+ const bool bParallel = true;
+
+ if (op == BasicMorphologyOp::erode)
+ runFilter<ErodeOp, nComponentWidth>(rBitmap, nRadius, bParallel, bUseValueOutside,
+ nValueOutside);
+ else if (op == BasicMorphologyOp::dilate)
+ runFilter<DilateOp, nComponentWidth>(rBitmap, nRadius, bParallel, bUseValueOutside,
+ nValueOutside);
+}
+
+} // end anonymous namespace
+
+BitmapBasicMorphologyFilter::BitmapBasicMorphologyFilter(BasicMorphologyOp op, sal_Int32 nRadius)
+ : m_eOp(op)
+ , m_nRadius(nRadius)
+{
+}
+
+BitmapBasicMorphologyFilter::BitmapBasicMorphologyFilter(BasicMorphologyOp op, sal_Int32 nRadius,
+ sal_uInt8 nValueOutside)
+ : m_eOp(op)
+ , m_nRadius(nRadius)
+ , m_nValueOutside(nValueOutside)
+ , m_bUseValueOutside(true)
+{
+}
+
+BitmapBasicMorphologyFilter::~BitmapBasicMorphologyFilter() = default;
+
+BitmapEx BitmapBasicMorphologyFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ Bitmap result = filter(rBitmapEx.GetBitmap());
+ return BitmapEx(result, rBitmapEx.GetAlphaMask());
+}
+
+Bitmap BitmapBasicMorphologyFilter::filter(Bitmap const& rBitmap) const
+{
+ Bitmap bitmapCopy(rBitmap);
+ ScanlineFormat nScanlineFormat;
+ {
+ BitmapScopedReadAccess pReadAccess(bitmapCopy);
+ nScanlineFormat = pReadAccess ? pReadAccess->GetScanlineFormat() : ScanlineFormat::NONE;
+ }
+
+ switch (nScanlineFormat)
+ {
+ case ScanlineFormat::N24BitTcRgb:
+ case ScanlineFormat::N24BitTcBgr:
+ runFilter<24>(bitmapCopy, m_eOp, m_nRadius, m_bUseValueOutside, m_nValueOutside);
+ break;
+ case ScanlineFormat::N32BitTcMask:
+ case ScanlineFormat::N32BitTcBgra:
+ runFilter<32>(bitmapCopy, m_eOp, m_nRadius, m_bUseValueOutside, m_nValueOutside);
+ break;
+ case ScanlineFormat::N8BitPal:
+ runFilter<8>(bitmapCopy, m_eOp, m_nRadius, m_bUseValueOutside, m_nValueOutside);
+ break;
+ // TODO: handle 1-bit images
+ default:
+ // Use access' GetColor/SetPixel fallback
+ runFilter<0>(bitmapCopy, m_eOp, m_nRadius, m_bUseValueOutside, m_nValueOutside);
+ break;
+ }
+
+ return bitmapCopy;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapColorQuantizationFilter.cxx b/vcl/source/bitmap/BitmapColorQuantizationFilter.cxx
new file mode 100644
index 0000000000..9014bfe5dd
--- /dev/null
+++ b/vcl/source/bitmap/BitmapColorQuantizationFilter.cxx
@@ -0,0 +1,200 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#include <sal/config.h>
+
+#include <vcl/bitmap.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/BitmapColorQuantizationFilter.hxx>
+#include <vcl/BitmapWriteAccess.hxx>
+
+#include <algorithm>
+#include <cstdlib>
+
+BitmapEx BitmapColorQuantizationFilter::execute(BitmapEx const& aBitmapEx) const
+{
+ Bitmap aBitmap = aBitmapEx.GetBitmap();
+
+ if (vcl::numberOfColors(aBitmap.getPixelFormat()) <= sal_Int64(mnNewColorCount))
+ return BitmapEx(aBitmap);
+
+ BitmapScopedReadAccess pRAcc(aBitmap);
+ if (!pRAcc)
+ return BitmapEx();
+
+ auto const cappedNewColorCount = std::min(mnNewColorCount, sal_uInt16(256));
+
+ const sal_uInt32 nValidBits = 4;
+ const sal_uInt32 nRightShiftBits = 8 - nValidBits;
+ const sal_uInt32 nLeftShiftBits1 = nValidBits;
+ const sal_uInt32 nLeftShiftBits2 = nValidBits << 1;
+ const sal_uInt32 nColorsPerComponent = 1 << nValidBits;
+ const sal_uInt32 nColorOffset = 256 / nColorsPerComponent;
+ const sal_uInt32 nTotalColors = nColorsPerComponent * nColorsPerComponent * nColorsPerComponent;
+ const sal_Int32 nWidth = pRAcc->Width();
+ const sal_Int32 nHeight = pRAcc->Height();
+ std::unique_ptr<PopularColorCount[]> pCountTable(new PopularColorCount[nTotalColors]);
+
+ memset(pCountTable.get(), 0, nTotalColors * sizeof(PopularColorCount));
+
+ for (sal_Int32 nR = 0, nIndex = 0; nR < 256; nR += nColorOffset)
+ {
+ for (sal_Int32 nG = 0; nG < 256; nG += nColorOffset)
+ {
+ for (sal_Int32 nB = 0; nB < 256; nB += nColorOffset)
+ {
+ pCountTable[nIndex].mnIndex = nIndex;
+ nIndex++;
+ }
+ }
+ }
+
+ if (pRAcc->HasPalette())
+ {
+ for (sal_Int32 nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanlineRead = pRAcc->GetScanline(nY);
+ for (sal_Int32 nX = 0; nX < nWidth; nX++)
+ {
+ const BitmapColor& rCol
+ = pRAcc->GetPaletteColor(pRAcc->GetIndexFromData(pScanlineRead, nX));
+ pCountTable[((static_cast<sal_uInt32>(rCol.GetRed()) >> nRightShiftBits)
+ << nLeftShiftBits2)
+ | ((static_cast<sal_uInt32>(rCol.GetGreen()) >> nRightShiftBits)
+ << nLeftShiftBits1)
+ | (static_cast<sal_uInt32>(rCol.GetBlue()) >> nRightShiftBits)]
+ .mnCount++;
+ }
+ }
+ }
+ else
+ {
+ for (sal_Int32 nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanlineRead = pRAcc->GetScanline(nY);
+ for (sal_Int32 nX = 0; nX < nWidth; nX++)
+ {
+ const BitmapColor aCol(pRAcc->GetPixelFromData(pScanlineRead, nX));
+ pCountTable[((static_cast<sal_uInt32>(aCol.GetRed()) >> nRightShiftBits)
+ << nLeftShiftBits2)
+ | ((static_cast<sal_uInt32>(aCol.GetGreen()) >> nRightShiftBits)
+ << nLeftShiftBits1)
+ | (static_cast<sal_uInt32>(aCol.GetBlue()) >> nRightShiftBits)]
+ .mnCount++;
+ }
+ }
+ }
+
+ BitmapPalette aNewPal(cappedNewColorCount);
+
+ std::qsort(pCountTable.get(), nTotalColors, sizeof(PopularColorCount),
+ [](const void* p1, const void* p2) {
+ int nRet;
+
+ if (static_cast<PopularColorCount const*>(p1)->mnCount
+ < static_cast<PopularColorCount const*>(p2)->mnCount)
+ nRet = 1;
+ else if (static_cast<PopularColorCount const*>(p1)->mnCount
+ == static_cast<PopularColorCount const*>(p2)->mnCount)
+ nRet = 0;
+ else
+ nRet = -1;
+
+ return nRet;
+ });
+
+ for (sal_uInt16 n = 0; n < cappedNewColorCount; n++)
+ {
+ const PopularColorCount& rPop = pCountTable[n];
+ aNewPal[n] = BitmapColor(
+ static_cast<sal_uInt8>((rPop.mnIndex >> nLeftShiftBits2) << nRightShiftBits),
+ static_cast<sal_uInt8>(((rPop.mnIndex >> nLeftShiftBits1) & (nColorsPerComponent - 1))
+ << nRightShiftBits),
+ static_cast<sal_uInt8>((rPop.mnIndex & (nColorsPerComponent - 1)) << nRightShiftBits));
+ }
+
+ Bitmap aNewBmp(aBitmap.GetSizePixel(), vcl::PixelFormat::N8_BPP, &aNewPal);
+ BitmapScopedWriteAccess pWAcc(aNewBmp);
+ if (!pWAcc)
+ return BitmapEx();
+
+ BitmapColor aDstCol(sal_uInt8(0));
+ std::unique_ptr<sal_uInt8[]> pIndexMap(new sal_uInt8[nTotalColors]);
+
+ for (sal_Int32 nR = 0, nIndex = 0; nR < 256; nR += nColorOffset)
+ {
+ for (sal_Int32 nG = 0; nG < 256; nG += nColorOffset)
+ {
+ for (sal_Int32 nB = 0; nB < 256; nB += nColorOffset)
+ {
+ pIndexMap[nIndex++] = static_cast<sal_uInt8>(aNewPal.GetBestIndex(
+ BitmapColor(static_cast<sal_uInt8>(nR), static_cast<sal_uInt8>(nG),
+ static_cast<sal_uInt8>(nB))));
+ }
+ }
+ }
+
+ if (pRAcc->HasPalette())
+ {
+ for (sal_Int32 nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pWAcc->GetScanline(nY);
+ Scanline pScanlineRead = pRAcc->GetScanline(nY);
+ for (sal_Int32 nX = 0; nX < nWidth; nX++)
+ {
+ const BitmapColor& rCol
+ = pRAcc->GetPaletteColor(pRAcc->GetIndexFromData(pScanlineRead, nX));
+ aDstCol.SetIndex(
+ pIndexMap[((static_cast<sal_uInt32>(rCol.GetRed()) >> nRightShiftBits)
+ << nLeftShiftBits2)
+ | ((static_cast<sal_uInt32>(rCol.GetGreen()) >> nRightShiftBits)
+ << nLeftShiftBits1)
+ | (static_cast<sal_uInt32>(rCol.GetBlue()) >> nRightShiftBits)]);
+ pWAcc->SetPixelOnData(pScanline, nX, aDstCol);
+ }
+ }
+ }
+ else
+ {
+ for (sal_Int32 nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pWAcc->GetScanline(nY);
+ Scanline pScanlineRead = pRAcc->GetScanline(nY);
+
+ for (sal_Int32 nX = 0; nX < nWidth; nX++)
+ {
+ const BitmapColor aCol(pRAcc->GetPixelFromData(pScanlineRead, nX));
+ aDstCol.SetIndex(
+ pIndexMap[((static_cast<sal_uInt32>(aCol.GetRed()) >> nRightShiftBits)
+ << nLeftShiftBits2)
+ | ((static_cast<sal_uInt32>(aCol.GetGreen()) >> nRightShiftBits)
+ << nLeftShiftBits1)
+ | (static_cast<sal_uInt32>(aCol.GetBlue()) >> nRightShiftBits)]);
+ pWAcc->SetPixelOnData(pScanline, nX, aDstCol);
+ }
+ }
+ }
+
+ pWAcc.reset();
+ pCountTable.reset();
+ pRAcc.reset();
+
+ const MapMode aMap(aBitmap.GetPrefMapMode());
+ const Size aSize(aBitmap.GetPrefSize());
+
+ aBitmap = aNewBmp;
+
+ aBitmap.SetPrefMapMode(aMap);
+ aBitmap.SetPrefSize(aSize);
+
+ return BitmapEx(aBitmap);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapColorizeFilter.cxx b/vcl/source/bitmap/BitmapColorizeFilter.cxx
new file mode 100644
index 0000000000..21e996193e
--- /dev/null
+++ b/vcl/source/bitmap/BitmapColorizeFilter.cxx
@@ -0,0 +1,92 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#include <tools/color.hxx>
+#include <tools/helpers.hxx>
+
+#include <vcl/BitmapWriteAccess.hxx>
+#include <bitmap/BitmapColorizeFilter.hxx>
+
+BitmapEx BitmapColorizeFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ Bitmap aBitmap = rBitmapEx.GetBitmap();
+ BitmapScopedWriteAccess pWriteAccess(aBitmap);
+
+ if (!pWriteAccess)
+ return rBitmapEx;
+
+ BitmapColor aBitmapColor;
+ const sal_Int32 nW = pWriteAccess->Width();
+ const sal_Int32 nH = pWriteAccess->Height();
+ std::vector<sal_uInt8> aMapR(256);
+ std::vector<sal_uInt8> aMapG(256);
+ std::vector<sal_uInt8> aMapB(256);
+ sal_Int32 nX;
+ sal_Int32 nY;
+
+ const sal_uInt8 cR = maColor.GetRed();
+ const sal_uInt8 cG = maColor.GetGreen();
+ const sal_uInt8 cB = maColor.GetBlue();
+
+ for (nX = 0; nX < 256; ++nX)
+ {
+ aMapR[nX] = std::clamp((nX + cR) / 2, sal_Int32(0), sal_Int32(255));
+ aMapG[nX] = std::clamp((nX + cG) / 2, sal_Int32(0), sal_Int32(255));
+ aMapB[nX] = std::clamp((nX + cB) / 2, sal_Int32(0), sal_Int32(255));
+ }
+
+ if (pWriteAccess->HasPalette())
+ {
+ for (sal_uInt16 i = 0, nCount = pWriteAccess->GetPaletteEntryCount(); i < nCount; i++)
+ {
+ const BitmapColor& rCol = pWriteAccess->GetPaletteColor(i);
+ aBitmapColor.SetRed(aMapR[rCol.GetRed()]);
+ aBitmapColor.SetGreen(aMapG[rCol.GetGreen()]);
+ aBitmapColor.SetBlue(aMapB[rCol.GetBlue()]);
+ pWriteAccess->SetPaletteColor(i, aBitmapColor);
+ }
+ }
+ else if (pWriteAccess->GetScanlineFormat() == ScanlineFormat::N24BitTcBgr)
+ {
+ for (nY = 0; nY < nH; ++nY)
+ {
+ Scanline pScan = pWriteAccess->GetScanline(nY);
+
+ for (nX = 0; nX < nW; ++nX)
+ {
+ *pScan = aMapB[*pScan];
+ pScan++;
+ *pScan = aMapG[*pScan];
+ pScan++;
+ *pScan = aMapR[*pScan];
+ pScan++;
+ }
+ }
+ }
+ else
+ {
+ for (nY = 0; nY < nH; ++nY)
+ {
+ Scanline pScanline = pWriteAccess->GetScanline(nY);
+ for (nX = 0; nX < nW; ++nX)
+ {
+ aBitmapColor = pWriteAccess->GetPixelFromData(pScanline, nX);
+ aBitmapColor.SetRed(aMapR[aBitmapColor.GetRed()]);
+ aBitmapColor.SetGreen(aMapG[aBitmapColor.GetGreen()]);
+ aBitmapColor.SetBlue(aMapB[aBitmapColor.GetBlue()]);
+ pWriteAccess->SetPixelOnData(pScanline, nX, aBitmapColor);
+ }
+ }
+ }
+
+ return rBitmapEx;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapConvolutionMatrixFilter.cxx b/vcl/source/bitmap/BitmapConvolutionMatrixFilter.cxx
new file mode 100644
index 0000000000..4a0d3d89a8
--- /dev/null
+++ b/vcl/source/bitmap/BitmapConvolutionMatrixFilter.cxx
@@ -0,0 +1,199 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#include <tools/helpers.hxx>
+
+#include <vcl/bitmap.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/BitmapConvolutionMatrixFilter.hxx>
+#include <vcl/BitmapSharpenFilter.hxx>
+#include <vcl/BitmapWriteAccess.hxx>
+
+#include <array>
+
+BitmapEx BitmapConvolutionMatrixFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ Bitmap aBitmap(rBitmapEx.GetBitmap());
+
+ const sal_Int32 nDivisor = 8;
+ BitmapScopedReadAccess pReadAcc(aBitmap);
+ if (!pReadAcc)
+ return BitmapEx();
+
+ Bitmap aNewBmp(aBitmap.GetSizePixel(), vcl::PixelFormat::N24_BPP);
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+ if (!pWriteAcc)
+ return BitmapEx();
+
+ const sal_Int32 nWidth = pWriteAcc->Width(), nWidth2 = nWidth + 2;
+ const sal_Int32 nHeight = pWriteAcc->Height(), nHeight2 = nHeight + 2;
+ std::unique_ptr<sal_Int32[]> pColm(new sal_Int32[nWidth2]);
+ std::unique_ptr<sal_Int32[]> pRows(new sal_Int32[nHeight2]);
+ std::unique_ptr<BitmapColor[]> pColRow1(new BitmapColor[nWidth2]);
+ std::unique_ptr<BitmapColor[]> pColRow2(new BitmapColor[nWidth2]);
+ std::unique_ptr<BitmapColor[]> pColRow3(new BitmapColor[nWidth2]);
+ BitmapColor* pRowTmp1 = pColRow1.get();
+ BitmapColor* pRowTmp2 = pColRow2.get();
+ BitmapColor* pRowTmp3 = pColRow3.get();
+ BitmapColor* pColor;
+ sal_Int32 nY, nX, i, nSumR, nSumG, nSumB, nMatrixVal, nTmp;
+ std::array<std::array<sal_Int32, 256>, 9> aKoeff;
+ sal_Int32* pTmp;
+
+ // create LUT of products of matrix value and possible color component values
+ for (nY = 0; nY < 9; nY++)
+ {
+ for (nX = nTmp = 0, nMatrixVal = mrMatrix[nY]; nX < 256; nX++, nTmp += nMatrixVal)
+ {
+ aKoeff[nY][nX] = nTmp;
+ }
+ }
+
+ // create column LUT
+ for (i = 0; i < nWidth2; i++)
+ {
+ pColm[i] = (i > 0) ? (i - 1) : 0;
+ }
+
+ pColm[nWidth + 1] = pColm[nWidth];
+
+ // create row LUT
+ for (i = 0; i < nHeight2; i++)
+ {
+ pRows[i] = (i > 0) ? (i - 1) : 0;
+ }
+
+ pRows[nHeight + 1] = pRows[nHeight];
+
+ // read first three rows of bitmap color
+ for (i = 0; i < nWidth2; i++)
+ {
+ pColRow1[i] = pReadAcc->GetColor(pRows[0], pColm[i]);
+ pColRow2[i] = pReadAcc->GetColor(pRows[1], pColm[i]);
+ pColRow3[i] = pReadAcc->GetColor(pRows[2], pColm[i]);
+ }
+
+ // do convolution
+ for (nY = 0; nY < nHeight;)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ for (nX = 0; nX < nWidth; nX++)
+ {
+ // first row
+ pTmp = aKoeff[0].data();
+ pColor = pRowTmp1 + nX;
+ nSumR = pTmp[pColor->GetRed()];
+ nSumG = pTmp[pColor->GetGreen()];
+ nSumB = pTmp[pColor->GetBlue()];
+
+ pTmp = aKoeff[1].data();
+ nSumR += pTmp[(++pColor)->GetRed()];
+ nSumG += pTmp[pColor->GetGreen()];
+ nSumB += pTmp[pColor->GetBlue()];
+
+ pTmp = aKoeff[2].data();
+ nSumR += pTmp[(++pColor)->GetRed()];
+ nSumG += pTmp[pColor->GetGreen()];
+ nSumB += pTmp[pColor->GetBlue()];
+
+ // second row
+ pTmp = aKoeff[3].data();
+ pColor = pRowTmp2 + nX;
+ nSumR += pTmp[pColor->GetRed()];
+ nSumG += pTmp[pColor->GetGreen()];
+ nSumB += pTmp[pColor->GetBlue()];
+
+ pTmp = aKoeff[4].data();
+ nSumR += pTmp[(++pColor)->GetRed()];
+ nSumG += pTmp[pColor->GetGreen()];
+ nSumB += pTmp[pColor->GetBlue()];
+
+ pTmp = aKoeff[5].data();
+ nSumR += pTmp[(++pColor)->GetRed()];
+ nSumG += pTmp[pColor->GetGreen()];
+ nSumB += pTmp[pColor->GetBlue()];
+
+ // third row
+ pTmp = aKoeff[6].data();
+ pColor = pRowTmp3 + nX;
+ nSumR += pTmp[pColor->GetRed()];
+ nSumG += pTmp[pColor->GetGreen()];
+ nSumB += pTmp[pColor->GetBlue()];
+
+ pTmp = aKoeff[7].data();
+ nSumR += pTmp[(++pColor)->GetRed()];
+ nSumG += pTmp[pColor->GetGreen()];
+ nSumB += pTmp[pColor->GetBlue()];
+
+ pTmp = aKoeff[8].data();
+ nSumR += pTmp[(++pColor)->GetRed()];
+ nSumG += pTmp[pColor->GetGreen()];
+ nSumB += pTmp[pColor->GetBlue()];
+
+ // calculate destination color
+ pWriteAcc->SetPixelOnData(
+ pScanline, nX,
+ BitmapColor(static_cast<sal_uInt8>(
+ std::clamp(nSumR / nDivisor, sal_Int32(0), sal_Int32(255))),
+ static_cast<sal_uInt8>(
+ std::clamp(nSumG / nDivisor, sal_Int32(0), sal_Int32(255))),
+ static_cast<sal_uInt8>(
+ std::clamp(nSumB / nDivisor, sal_Int32(0), sal_Int32(255)))));
+ }
+
+ if (++nY < nHeight)
+ {
+ if (pRowTmp1 == pColRow1.get())
+ {
+ pRowTmp1 = pColRow2.get();
+ pRowTmp2 = pColRow3.get();
+ pRowTmp3 = pColRow1.get();
+ }
+ else if (pRowTmp1 == pColRow2.get())
+ {
+ pRowTmp1 = pColRow3.get();
+ pRowTmp2 = pColRow1.get();
+ pRowTmp3 = pColRow2.get();
+ }
+ else
+ {
+ pRowTmp1 = pColRow1.get();
+ pRowTmp2 = pColRow2.get();
+ pRowTmp3 = pColRow3.get();
+ }
+
+ for (i = 0; i < nWidth2; i++)
+ {
+ pRowTmp3[i] = pReadAcc->GetColor(pRows[nY + 2], pColm[i]);
+ }
+ }
+ }
+
+ pWriteAcc.reset();
+ pReadAcc.reset();
+
+ const MapMode aMap(aBitmap.GetPrefMapMode());
+ const Size aPrefSize(aBitmap.GetPrefSize());
+
+ aBitmap = aNewBmp;
+
+ aBitmap.SetPrefMapMode(aMap);
+ aBitmap.SetPrefSize(aPrefSize);
+
+ return BitmapEx(aBitmap);
+}
+
+const sal_Int32 g_SharpenMatrix[] = { -1, -1, -1, -1, 16, -1, -1, -1, -1 };
+
+BitmapSharpenFilter::BitmapSharpenFilter()
+ : BitmapConvolutionMatrixFilter(g_SharpenMatrix)
+{
+}
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapDisabledImageFilter.cxx b/vcl/source/bitmap/BitmapDisabledImageFilter.cxx
new file mode 100644
index 0000000000..e38c450d79
--- /dev/null
+++ b/vcl/source/bitmap/BitmapDisabledImageFilter.cxx
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#include <vcl/BitmapWriteAccess.hxx>
+#include <bitmap/BitmapDisabledImageFilter.hxx>
+
+BitmapEx BitmapDisabledImageFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ const Size aSize(rBitmapEx.GetSizePixel());
+
+ // keep disable image at same depth as original where possible, otherwise
+ // use 8 bit
+ auto ePixelFormat = rBitmapEx.getPixelFormat();
+ if (sal_uInt16(ePixelFormat) < 8)
+ ePixelFormat = vcl::PixelFormat::N8_BPP;
+
+ const BitmapPalette* pPal
+ = vcl::isPalettePixelFormat(ePixelFormat) ? &Bitmap::GetGreyPalette(256) : nullptr;
+ Bitmap aGrey(aSize, ePixelFormat, pPal);
+ BitmapScopedWriteAccess pGrey(aGrey);
+
+ BitmapEx aReturnBitmap;
+ Bitmap aReadBitmap(rBitmapEx.GetBitmap());
+ BitmapScopedReadAccess pRead(aReadBitmap);
+ if (pRead && pGrey)
+ {
+ for (sal_Int32 nY = 0; nY < sal_Int32(aSize.Height()); ++nY)
+ {
+ Scanline pGreyScan = pGrey->GetScanline(nY);
+ Scanline pReadScan = pRead->GetScanline(nY);
+
+ for (sal_Int32 nX = 0; nX < sal_Int32(aSize.Width()); ++nX)
+ {
+ // Get the luminance from RGB color and remap the value from 0-255 to 160-224
+ const BitmapColor aColor = pRead->GetPixelFromData(pReadScan, nX);
+ sal_uInt8 nLum(aColor.GetLuminance() / 4 + 160);
+ BitmapColor aGreyValue(ColorAlpha, nLum, nLum, nLum, aColor.GetAlpha());
+ pGrey->SetPixelOnData(pGreyScan, nX, aGreyValue);
+ }
+ }
+ }
+
+ if (rBitmapEx.IsAlpha())
+ {
+ aReturnBitmap = BitmapEx(aGrey, rBitmapEx.GetAlphaMask());
+ }
+ else
+ aReturnBitmap = BitmapEx(aGrey);
+
+ return aReturnBitmap;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapDuoToneFilter.cxx b/vcl/source/bitmap/BitmapDuoToneFilter.cxx
new file mode 100644
index 0000000000..c9cc10b1d1
--- /dev/null
+++ b/vcl/source/bitmap/BitmapDuoToneFilter.cxx
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#include <vcl/bitmap.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/BitmapDuoToneFilter.hxx>
+#include <vcl/BitmapWriteAccess.hxx>
+
+static sal_uInt8 lcl_getDuotoneColorComponent(sal_uInt8 base, sal_uInt16 color1, sal_uInt16 color2)
+{
+ color2 = color2 * base / 0xFF;
+ color1 = color1 * (0xFF - base) / 0xFF;
+
+ return static_cast<sal_uInt8>(color1 + color2);
+}
+
+BitmapEx BitmapDuoToneFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ Bitmap aBitmap(rBitmapEx.GetBitmap());
+
+ const sal_Int32 nWidth = aBitmap.GetSizePixel().Width();
+ const sal_Int32 nHeight = aBitmap.GetSizePixel().Height();
+
+ Bitmap aResultBitmap(aBitmap.GetSizePixel(), vcl::PixelFormat::N24_BPP);
+ BitmapScopedReadAccess pReadAcc(aBitmap);
+ BitmapScopedWriteAccess pWriteAcc(aResultBitmap);
+ const BitmapColor aColorOne(mnColorOne);
+ const BitmapColor aColorTwo(mnColorTwo);
+
+ for (sal_Int32 x = 0; x < nWidth; x++)
+ {
+ for (sal_Int32 y = 0; y < nHeight; y++)
+ {
+ BitmapColor aColor = pReadAcc->GetColor(y, x);
+ sal_uInt8 nLuminance = aColor.GetLuminance();
+ BitmapColor aResultColor(
+ lcl_getDuotoneColorComponent(nLuminance, aColorOne.GetRed(), aColorTwo.GetRed()),
+ lcl_getDuotoneColorComponent(nLuminance, aColorOne.GetGreen(),
+ aColorTwo.GetGreen()),
+ lcl_getDuotoneColorComponent(nLuminance, aColorOne.GetBlue(), aColorTwo.GetBlue()));
+ pWriteAcc->SetPixel(y, x, aResultColor);
+ }
+ }
+
+ pWriteAcc.reset();
+ pReadAcc.reset();
+ aBitmap.ReassignWithSize(aResultBitmap);
+
+ return BitmapEx(aBitmap);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapEmbossGreyFilter.cxx b/vcl/source/bitmap/BitmapEmbossGreyFilter.cxx
new file mode 100644
index 0000000000..6fa5dd5c12
--- /dev/null
+++ b/vcl/source/bitmap/BitmapEmbossGreyFilter.cxx
@@ -0,0 +1,136 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#include <sal/config.h>
+
+#include <tools/helpers.hxx>
+
+#include <vcl/bitmap.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/BitmapEmbossGreyFilter.hxx>
+#include <vcl/BitmapWriteAccess.hxx>
+
+#include <algorithm>
+
+BitmapEx BitmapEmbossGreyFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ Bitmap aBitmap(rBitmapEx.GetBitmap());
+
+ if (!aBitmap.ImplMakeGreyscales())
+ return BitmapEx();
+
+ BitmapScopedReadAccess pReadAcc(aBitmap);
+ if (!pReadAcc)
+ return BitmapEx();
+
+ Bitmap aNewBmp(aBitmap.GetSizePixel(), vcl::PixelFormat::N8_BPP, &pReadAcc->GetPalette());
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+ if (!pWriteAcc)
+ return BitmapEx();
+
+ BitmapColor aGrey(sal_uInt8(0));
+ const sal_Int32 nWidth = pWriteAcc->Width();
+ const sal_Int32 nHeight = pWriteAcc->Height();
+ const double fAzim = toRadians(mnAzimuthAngle);
+ const double fElev = toRadians(mnElevationAngle);
+ std::vector<sal_Int32> pHMap(nWidth + 2);
+ std::vector<sal_Int32> pVMap(nHeight + 2);
+ const double nLx = cos(fAzim) * cos(fElev) * 255.0;
+ const double nLy = sin(fAzim) * cos(fElev) * 255.0;
+ const double nLz = sin(fElev) * 255.0;
+ const double nNz = 6 * 255.0 / 4;
+ const double nNzLz = nNz * nLz;
+ const sal_uInt8 cLz = FRound(std::clamp(nLz, 0.0, 255.0));
+
+ // fill mapping tables
+ pHMap[0] = 0;
+
+ for (sal_Int32 nX = 1; nX <= nWidth; nX++)
+ {
+ pHMap[nX] = nX - 1;
+ }
+
+ pHMap[nWidth + 1] = nWidth - 1;
+
+ pVMap[0] = 0;
+
+ for (sal_Int32 nY = 1; nY <= nHeight; nY++)
+ {
+ pVMap[nY] = nY - 1;
+ }
+
+ pVMap[nHeight + 1] = nHeight - 1;
+
+ for (sal_Int32 nY = 0; nY < nHeight; nY++)
+ {
+ sal_Int32 nGrey11 = pReadAcc->GetPixel(pVMap[nY], pHMap[0]).GetIndex();
+ sal_Int32 nGrey12 = pReadAcc->GetPixel(pVMap[nY], pHMap[1]).GetIndex();
+ sal_Int32 nGrey13 = pReadAcc->GetPixel(pVMap[nY], pHMap[2]).GetIndex();
+ sal_Int32 nGrey21 = pReadAcc->GetPixel(pVMap[nY + 1], pHMap[0]).GetIndex();
+ sal_Int32 nGrey22 = pReadAcc->GetPixel(pVMap[nY + 1], pHMap[1]).GetIndex();
+ sal_Int32 nGrey23 = pReadAcc->GetPixel(pVMap[nY + 1], pHMap[2]).GetIndex();
+ sal_Int32 nGrey31 = pReadAcc->GetPixel(pVMap[nY + 2], pHMap[0]).GetIndex();
+ sal_Int32 nGrey32 = pReadAcc->GetPixel(pVMap[nY + 2], pHMap[1]).GetIndex();
+ sal_Int32 nGrey33 = pReadAcc->GetPixel(pVMap[nY + 2], pHMap[2]).GetIndex();
+
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ for (sal_Int32 nX = 0; nX < nWidth; nX++)
+ {
+ const sal_Int32 nNx = nGrey11 + nGrey21 + nGrey31 - nGrey13 - nGrey23 - nGrey33;
+ const sal_Int32 nNy = nGrey31 + nGrey32 + nGrey33 - nGrey11 - nGrey12 - nGrey13;
+
+ if (!nNx && !nNy)
+ {
+ aGrey.SetIndex(cLz);
+ }
+ else if (double nDotL = nNx * nLx + nNy * nLy + nNzLz; nDotL < 0)
+ {
+ aGrey.SetIndex(0);
+ }
+ else
+ {
+ const double fGrey = nDotL / std::hypot(nNx, nNy, nNz);
+ aGrey.SetIndex(FRound(std::clamp(fGrey, 0.0, 255.0)));
+ }
+
+ pWriteAcc->SetPixelOnData(pScanline, nX, aGrey);
+
+ if (nX < (nWidth - 1))
+ {
+ const sal_Int32 nNextX = pHMap[nX + 3];
+
+ nGrey11 = nGrey12;
+ nGrey12 = nGrey13;
+ nGrey13 = pReadAcc->GetPixel(pVMap[nY], nNextX).GetIndex();
+ nGrey21 = nGrey22;
+ nGrey22 = nGrey23;
+ nGrey23 = pReadAcc->GetPixel(pVMap[nY + 1], nNextX).GetIndex();
+ nGrey31 = nGrey32;
+ nGrey32 = nGrey33;
+ nGrey33 = pReadAcc->GetPixel(pVMap[nY + 2], nNextX).GetIndex();
+ }
+ }
+ }
+
+ pWriteAcc.reset();
+ pReadAcc.reset();
+
+ const MapMode aMap(aBitmap.GetPrefMapMode());
+ const Size aPrefSize(aBitmap.GetPrefSize());
+
+ aBitmap = aNewBmp;
+
+ aBitmap.SetPrefMapMode(aMap);
+ aBitmap.SetPrefSize(aPrefSize);
+
+ return BitmapEx(aBitmap);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapEx.cxx b/vcl/source/bitmap/BitmapEx.cxx
new file mode 100644
index 0000000000..40feacbf66
--- /dev/null
+++ b/vcl/source/bitmap/BitmapEx.cxx
@@ -0,0 +1,1523 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/log.hxx>
+#include <rtl/crc.h>
+#include <rtl/math.hxx>
+#include <o3tl/underlyingenumvalue.hxx>
+#include <osl/diagnose.h>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <basegfx/color/bcolormodifier.hxx>
+
+#include <vcl/ImageTree.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/alpha.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/BitmapMonochromeFilter.hxx>
+
+// BitmapEx::Create
+#include <salbmp.hxx>
+#include <salinst.hxx>
+#include <svdata.hxx>
+#include <vcl/BitmapWriteAccess.hxx>
+#include <bitmap/BitmapMaskToAlphaFilter.hxx>
+
+#include <o3tl/any.hxx>
+#include <tools/stream.hxx>
+#include <vcl/filter/PngImageWriter.hxx>
+
+#include <com/sun/star/beans/XFastPropertySet.hpp>
+
+#include <memory>
+
+using namespace ::com::sun::star;
+
+BitmapEx::BitmapEx()
+{
+}
+
+BitmapEx::BitmapEx( const BitmapEx& ) = default;
+
+BitmapEx::BitmapEx( const BitmapEx& rBitmapEx, Point aSrc, Size aSize )
+{
+ if( rBitmapEx.IsEmpty() || aSize.IsEmpty() )
+ return;
+
+ maBitmap = Bitmap(aSize, rBitmapEx.maBitmap.getPixelFormat());
+ SetSizePixel(aSize);
+ if( rBitmapEx.IsAlpha() )
+ maAlphaMask = AlphaMask( aSize );
+
+ tools::Rectangle aDestRect( Point( 0, 0 ), aSize );
+ tools::Rectangle aSrcRect( aSrc, aSize );
+ CopyPixel( aDestRect, aSrcRect, rBitmapEx );
+}
+
+BitmapEx::BitmapEx(Size aSize, vcl::PixelFormat ePixelFormat)
+{
+ maBitmap = Bitmap(aSize, ePixelFormat);
+ SetSizePixel(aSize);
+}
+
+BitmapEx::BitmapEx( const OUString& rIconName )
+{
+ loadFromIconTheme( rIconName );
+}
+
+void BitmapEx::loadFromIconTheme( const OUString& rIconName )
+{
+ bool bSuccess;
+ OUString aIconTheme;
+
+ try
+ {
+ aIconTheme = Application::GetSettings().GetStyleSettings().DetermineIconTheme();
+ bSuccess = ImageTree::get().loadImage(rIconName, aIconTheme, *this, true);
+ }
+ catch (...)
+ {
+ bSuccess = false;
+ }
+
+ SAL_WARN_IF( !bSuccess, "vcl", "BitmapEx::BitmapEx(): could not load image " << rIconName << " via icon theme " << aIconTheme);
+}
+
+BitmapEx::BitmapEx( const Bitmap& rBmp ) :
+ maBitmap ( rBmp ),
+ maBitmapSize ( maBitmap.GetSizePixel() )
+{
+}
+
+BitmapEx::BitmapEx( const Bitmap& rBmp, const Bitmap& rMask ) :
+ maBitmap ( rBmp ),
+ maBitmapSize ( maBitmap.GetSizePixel() )
+{
+ 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
+ {
+ // convert to alpha bitmap
+ SAL_WARN( "vcl", "BitmapEx: forced mask to monochrome");
+ BitmapEx aMaskEx(rMask);
+ BitmapFilter::Filter(aMaskEx, BitmapMonochromeFilter(255));
+ aMaskEx.Invert();
+ maAlphaMask = aMaskEx.GetBitmap();
+ }
+
+ if (!maBitmap.IsEmpty() && maBitmap.GetSizePixel() != maAlphaMask.GetSizePixel())
+ {
+ OSL_ENSURE(false, "Mask size differs from Bitmap size, corrected Mask (!)");
+ maAlphaMask.Scale(maBitmap.GetSizePixel(), BmpScaleFlag::Fast);
+ }
+}
+
+BitmapEx::BitmapEx( const Bitmap& rBmp, const AlphaMask& rAlphaMask ) :
+ maBitmap ( rBmp ),
+ maAlphaMask ( rAlphaMask ),
+ maBitmapSize ( maBitmap.GetSizePixel() )
+{
+ if (!maBitmap.IsEmpty() && !maAlphaMask.IsEmpty() && maBitmap.GetSizePixel() != maAlphaMask.GetSizePixel())
+ {
+ OSL_ENSURE(false, "Alpha size differs from Bitmap size, corrected Mask (!)");
+ maAlphaMask.Scale(rBmp.GetSizePixel(), BmpScaleFlag::Fast);
+ }
+}
+
+
+BitmapEx::BitmapEx( const Bitmap& rBmp, const Color& rTransparentColor ) :
+ maBitmap ( rBmp ),
+ maBitmapSize ( maBitmap.GetSizePixel() )
+{
+ maAlphaMask = maBitmap.CreateAlphaMask( rTransparentColor );
+
+ SAL_WARN_IF(rBmp.GetSizePixel() != maAlphaMask.GetSizePixel(), "vcl",
+ "BitmapEx::BitmapEx(): size mismatch for bitmap and alpha mask.");
+}
+
+
+BitmapEx& BitmapEx::operator=( const BitmapEx& ) = default;
+
+bool BitmapEx::operator==( const BitmapEx& rBitmapEx ) const
+{
+ if (GetSizePixel() != rBitmapEx.GetSizePixel())
+ return false;
+
+ if (maBitmap != rBitmapEx.maBitmap)
+ return false;
+
+ return maAlphaMask == rBitmapEx.maAlphaMask;
+}
+
+bool BitmapEx::IsEmpty() const
+{
+ return( maBitmap.IsEmpty() && maAlphaMask.IsEmpty() );
+}
+
+void BitmapEx::SetEmpty()
+{
+ maBitmap.SetEmpty();
+ maAlphaMask.SetEmpty();
+}
+
+void BitmapEx::Clear()
+{
+ SetEmpty();
+}
+
+void BitmapEx::ClearAlpha()
+{
+ maAlphaMask.SetEmpty();
+}
+
+bool BitmapEx::IsAlpha() const
+{
+ return !maAlphaMask.IsEmpty();
+}
+
+const Bitmap& BitmapEx::GetBitmap() const
+{
+ return maBitmap;
+}
+
+Bitmap BitmapEx::GetBitmap( Color aTransparentReplaceColor ) const
+{
+ Bitmap aRetBmp( maBitmap );
+
+ if( !maAlphaMask.IsEmpty() )
+ {
+ aRetBmp.Replace( maAlphaMask, aTransparentReplaceColor );
+ }
+
+ return aRetBmp;
+}
+
+sal_Int64 BitmapEx::GetSizeBytes() const
+{
+ sal_Int64 nSizeBytes = maBitmap.GetSizeBytes();
+
+ if( !maAlphaMask.IsEmpty() )
+ nSizeBytes += maAlphaMask.GetSizeBytes();
+
+ return nSizeBytes;
+}
+
+BitmapChecksum BitmapEx::GetChecksum() const
+{
+ BitmapChecksum nCrc = maBitmap.GetChecksum();
+
+ if( !maAlphaMask.IsEmpty() )
+ {
+ BitmapChecksumOctetArray aBCOA;
+ BCToBCOA( maAlphaMask.GetChecksum(), aBCOA );
+ nCrc = rtl_crc32( nCrc, aBCOA, BITMAP_CHECKSUM_SIZE );
+ }
+
+ return nCrc;
+}
+
+void BitmapEx::SetSizePixel(const Size& rNewSize)
+{
+ maBitmapSize = rNewSize;
+}
+
+bool BitmapEx::Invert()
+{
+ bool bRet = false;
+
+ if (!maBitmap.IsEmpty())
+ bRet = maBitmap.Invert();
+
+ return bRet;
+}
+
+bool BitmapEx::Mirror( BmpMirrorFlags nMirrorFlags )
+{
+ bool bRet = false;
+
+ if( !maBitmap.IsEmpty() )
+ {
+ bRet = maBitmap.Mirror( nMirrorFlags );
+
+ if( bRet && !maAlphaMask.IsEmpty() )
+ maAlphaMask.Mirror( nMirrorFlags );
+ }
+
+ return bRet;
+}
+
+bool BitmapEx::Scale( const double& rScaleX, const double& rScaleY, BmpScaleFlag nScaleFlag )
+{
+ bool bRet = false;
+
+ if( !maBitmap.IsEmpty() )
+ {
+ bRet = maBitmap.Scale( rScaleX, rScaleY, nScaleFlag );
+
+ if( bRet && !maAlphaMask.IsEmpty() )
+ {
+ maAlphaMask.Scale( rScaleX, rScaleY, nScaleFlag );
+ }
+
+ SetSizePixel(maBitmap.GetSizePixel());
+
+ SAL_WARN_IF( !maAlphaMask.IsEmpty() && maBitmap.GetSizePixel() != maAlphaMask.GetSizePixel(), "vcl",
+ "BitmapEx::Scale(): size mismatch for bitmap and alpha mask." );
+ }
+
+ return bRet;
+}
+
+bool BitmapEx::Scale( const Size& rNewSize, BmpScaleFlag nScaleFlag )
+{
+ bool bRet;
+
+ if (GetSizePixel().Width() && GetSizePixel().Height()
+ && (rNewSize.Width() != GetSizePixel().Width()
+ || rNewSize.Height() != GetSizePixel().Height() ) )
+ {
+ bRet = Scale( static_cast<double>(rNewSize.Width()) / GetSizePixel().Width(),
+ static_cast<double>(rNewSize.Height()) / GetSizePixel().Height(),
+ nScaleFlag );
+ }
+ else
+ {
+ bRet = true;
+ }
+
+ return bRet;
+}
+
+bool BitmapEx::Rotate( Degree10 nAngle10, const Color& rFillColor )
+{
+ bool bRet = false;
+
+ if( !maBitmap.IsEmpty() )
+ {
+ const bool bTransRotate = ( COL_TRANSPARENT == rFillColor );
+
+ if( bTransRotate )
+ {
+ bRet = maBitmap.Rotate( nAngle10, COL_BLACK );
+
+ if( maAlphaMask.IsEmpty() )
+ {
+ maAlphaMask = Bitmap(GetSizePixel(), vcl::PixelFormat::N8_BPP, &Bitmap::GetGreyPalette(256));
+ maAlphaMask.Erase( 0 );
+ }
+
+ if( bRet && !maAlphaMask.IsEmpty() )
+ maAlphaMask.Rotate( nAngle10, COL_ALPHA_TRANSPARENT );
+ }
+ else
+ {
+ bRet = maBitmap.Rotate( nAngle10, rFillColor );
+
+ if( bRet && !maAlphaMask.IsEmpty() )
+ maAlphaMask.Rotate( nAngle10, COL_ALPHA_TRANSPARENT );
+ }
+
+ SetSizePixel(maBitmap.GetSizePixel());
+
+ SAL_WARN_IF(!maAlphaMask.IsEmpty() && maBitmap.GetSizePixel() != maAlphaMask.GetSizePixel(), "vcl",
+ "BitmapEx::Rotate(): size mismatch for bitmap and alpha mask.");
+ }
+
+ return bRet;
+}
+
+bool BitmapEx::Crop( const tools::Rectangle& rRectPixel )
+{
+ bool bRet = false;
+
+ if( !maBitmap.IsEmpty() )
+ {
+ bRet = maBitmap.Crop( rRectPixel );
+
+ if( bRet && !maAlphaMask.IsEmpty() )
+ maAlphaMask.Crop( rRectPixel );
+
+ SetSizePixel(maBitmap.GetSizePixel());
+
+ SAL_WARN_IF(!maAlphaMask.IsEmpty() && maBitmap.GetSizePixel() != maAlphaMask.GetSizePixel(), "vcl",
+ "BitmapEx::Crop(): size mismatch for bitmap and alpha mask.");
+ }
+
+ return bRet;
+}
+
+bool BitmapEx::Convert( BmpConversion eConversion )
+{
+ return !maBitmap.IsEmpty() && maBitmap.Convert( eConversion );
+}
+
+void BitmapEx::Expand( sal_Int32 nDX, sal_Int32 nDY, bool bExpandTransparent )
+{
+ bool bRet = false;
+
+ if( maBitmap.IsEmpty() )
+ return;
+
+ bRet = maBitmap.Expand( nDX, nDY );
+
+ if( bRet && !maAlphaMask.IsEmpty() )
+ {
+ Color aColor( bExpandTransparent ? COL_ALPHA_TRANSPARENT : COL_ALPHA_OPAQUE );
+ maAlphaMask.Expand( nDX, nDY, &aColor );
+ }
+
+ SetSizePixel(maBitmap.GetSizePixel());
+
+ SAL_WARN_IF(!maAlphaMask.IsEmpty() && maBitmap.GetSizePixel() != maAlphaMask.GetSizePixel(), "vcl",
+ "BitmapEx::Expand(): size mismatch for bitmap and alpha mask.");
+}
+
+bool BitmapEx::CopyPixel( const tools::Rectangle& rRectDst, const tools::Rectangle& rRectSrc )
+{
+ if( maBitmap.IsEmpty() )
+ return false;
+
+ bool bRet = maBitmap.CopyPixel( rRectDst, rRectSrc );
+
+ if( bRet && !maAlphaMask.IsEmpty() )
+ maAlphaMask.CopyPixel( rRectDst, rRectSrc );
+
+ return bRet;
+}
+
+bool BitmapEx::CopyPixel( const tools::Rectangle& rRectDst, const tools::Rectangle& rRectSrc,
+ const BitmapEx& rBmpExSrc )
+{
+ if( maBitmap.IsEmpty() )
+ return false;
+
+ if (!maBitmap.CopyPixel( rRectDst, rRectSrc, rBmpExSrc.maBitmap ))
+ return false;
+
+ if( rBmpExSrc.IsAlpha() )
+ {
+ if( IsAlpha() )
+ // cast to use the optimized AlphaMask::CopyPixel
+ maAlphaMask.CopyPixel_AlphaOptimized( rRectDst, rRectSrc, rBmpExSrc.maAlphaMask );
+ else
+ {
+ sal_uInt8 nTransparencyOpaque = 0;
+ maAlphaMask = AlphaMask(GetSizePixel(), &nTransparencyOpaque);
+ maAlphaMask.CopyPixel( rRectDst, rRectSrc, rBmpExSrc.maAlphaMask );
+ }
+ }
+ else if (IsAlpha())
+ {
+ sal_uInt8 nTransparencyOpaque = 0;
+ const AlphaMask aAlphaSrc(rBmpExSrc.GetSizePixel(), &nTransparencyOpaque);
+
+ maAlphaMask.CopyPixel( rRectDst, rRectSrc, aAlphaSrc );
+ }
+
+ return true;
+}
+
+bool BitmapEx::Erase( const Color& rFillColor )
+{
+ bool bRet = false;
+
+ if( !maBitmap.IsEmpty() )
+ {
+ bRet = maBitmap.Erase( rFillColor );
+
+ if( bRet && !maAlphaMask.IsEmpty() )
+ {
+ // Respect transparency on fill color
+ if( rFillColor.IsTransparent() )
+ maAlphaMask.Erase( 255 - rFillColor.GetAlpha() );
+ else
+ maAlphaMask.Erase( 0 );
+ }
+ }
+
+ return bRet;
+}
+
+void BitmapEx::Replace( const Color& rSearchColor, const Color& rReplaceColor )
+{
+ if (!maBitmap.IsEmpty())
+ maBitmap.Replace( rSearchColor, rReplaceColor );
+}
+
+void BitmapEx::Replace( const Color* pSearchColors, const Color* pReplaceColors, size_t nColorCount )
+{
+ if (!maBitmap.IsEmpty())
+ maBitmap.Replace( pSearchColors, pReplaceColors, nColorCount, /*pTols*/nullptr );
+}
+
+bool BitmapEx::Adjust( short nLuminancePercent, short nContrastPercent,
+ short nChannelRPercent, short nChannelGPercent, short nChannelBPercent,
+ double fGamma, bool bInvert, bool msoBrightness )
+{
+ return !maBitmap.IsEmpty() && maBitmap.Adjust( nLuminancePercent, nContrastPercent,
+ nChannelRPercent, nChannelGPercent, nChannelBPercent,
+ fGamma, bInvert, msoBrightness );
+}
+
+void BitmapEx::Draw( OutputDevice* pOutDev, const Point& rDestPt ) const
+{
+ pOutDev->DrawBitmapEx( rDestPt, *this );
+}
+
+void BitmapEx::Draw( OutputDevice* pOutDev,
+ const Point& rDestPt, const Size& rDestSize ) const
+{
+ pOutDev->DrawBitmapEx( rDestPt, rDestSize, *this );
+}
+
+BitmapEx BitmapEx:: AutoScaleBitmap(BitmapEx const & aBitmap, const tools::Long aStandardSize)
+{
+ Point aEmptyPoint(0,0);
+ double imgposX = 0;
+ double imgposY = 0;
+ BitmapEx aRet = aBitmap;
+ double imgOldWidth = aRet.GetSizePixel().Width();
+ double imgOldHeight = aRet.GetSizePixel().Height();
+
+ if (imgOldWidth >= aStandardSize || imgOldHeight >= aStandardSize)
+ {
+ sal_Int32 imgNewWidth = 0;
+ sal_Int32 imgNewHeight = 0;
+ if (imgOldWidth >= imgOldHeight)
+ {
+ imgNewWidth = aStandardSize;
+ imgNewHeight = sal_Int32(imgOldHeight / (imgOldWidth / aStandardSize) + 0.5);
+ imgposX = 0;
+ imgposY = (aStandardSize - (imgOldHeight / (imgOldWidth / aStandardSize) + 0.5)) / 2 + 0.5;
+ }
+ else
+ {
+ imgNewHeight = aStandardSize;
+ imgNewWidth = sal_Int32(imgOldWidth / (imgOldHeight / aStandardSize) + 0.5);
+ imgposY = 0;
+ imgposX = (aStandardSize - (imgOldWidth / (imgOldHeight / aStandardSize) + 0.5)) / 2 + 0.5;
+ }
+
+ Size aScaledSize( imgNewWidth, imgNewHeight );
+ aRet.Scale( aScaledSize, BmpScaleFlag::BestQuality );
+ }
+ else
+ {
+ imgposX = (aStandardSize - imgOldWidth) / 2 + 0.5;
+ imgposY = (aStandardSize - imgOldHeight) / 2 + 0.5;
+ }
+
+ Size aStdSize( aStandardSize, aStandardSize );
+ tools::Rectangle aRect(aEmptyPoint, aStdSize );
+
+ ScopedVclPtrInstance< VirtualDevice > aVirDevice(*Application::GetDefaultDevice());
+ aVirDevice->SetOutputSizePixel( aStdSize );
+ aVirDevice->SetFillColor( COL_TRANSPARENT );
+ aVirDevice->SetLineColor( COL_TRANSPARENT );
+
+ // Draw a rect into virDevice
+ aVirDevice->DrawRect( aRect );
+ Point aPointPixel( static_cast<tools::Long>(imgposX), static_cast<tools::Long>(imgposY) );
+ aVirDevice->DrawBitmapEx( aPointPixel, aRet );
+ aRet = aVirDevice->GetBitmapEx( aEmptyPoint, aStdSize );
+
+ return aRet;
+}
+
+sal_uInt8 BitmapEx::GetAlpha(sal_Int32 nX, sal_Int32 nY) const
+{
+ if(maBitmap.IsEmpty())
+ return 0;
+
+ if (nX < 0 || nX >= GetSizePixel().Width() || nY < 0 || nY >= GetSizePixel().Height())
+ return 0;
+
+ if (maBitmap.getPixelFormat() == vcl::PixelFormat::N32_BPP)
+ return GetPixelColor(nX, nY).GetAlpha();
+
+ sal_uInt8 nAlpha(0);
+ if (maAlphaMask.IsEmpty())
+ {
+ // Not transparent, ergo all covered
+ nAlpha = 255;
+ }
+ else
+ {
+ BitmapScopedReadAccess pRead(maAlphaMask);
+ if(pRead)
+ {
+ const BitmapColor aBitmapColor(pRead->GetPixel(nY, nX));
+ nAlpha = aBitmapColor.GetIndex();
+ }
+ }
+ return nAlpha;
+}
+
+
+Color BitmapEx::GetPixelColor(sal_Int32 nX, sal_Int32 nY) const
+{
+ BitmapScopedReadAccess pReadAccess( maBitmap );
+ assert(pReadAccess);
+
+ BitmapColor aColor = pReadAccess->GetColor(nY, nX);
+
+ if (IsAlpha())
+ {
+ AlphaMask aAlpha = GetAlphaMask();
+ BitmapScopedReadAccess pAlphaReadAccess(aAlpha);
+ aColor.SetAlpha(pAlphaReadAccess->GetPixel(nY, nX).GetIndex());
+ }
+ else if (maBitmap.getPixelFormat() != vcl::PixelFormat::N32_BPP)
+ {
+ aColor.SetAlpha(255);
+ }
+ return aColor;
+}
+
+// Shift alpha transparent pixels between cppcanvas/ implementations
+// and vcl in a generally grotesque and under-performing fashion
+bool BitmapEx::Create( const css::uno::Reference< css::rendering::XBitmapCanvas > &xBitmapCanvas,
+ const Size &rSize )
+{
+ uno::Reference< beans::XFastPropertySet > xFastPropertySet( xBitmapCanvas, uno::UNO_QUERY );
+ if( xFastPropertySet )
+ {
+ // 0 means get BitmapEx
+ uno::Any aAny = xFastPropertySet->getFastPropertyValue( 0 );
+ std::unique_ptr<BitmapEx> xBitmapEx(reinterpret_cast<BitmapEx*>(*o3tl::doAccess<sal_Int64>(aAny)));
+ if( xBitmapEx )
+ {
+ *this = *xBitmapEx;
+ return true;
+ }
+ }
+
+ std::shared_ptr<SalBitmap> pSalBmp;
+ std::shared_ptr<SalBitmap> pSalMask;
+
+ pSalBmp = ImplGetSVData()->mpDefInst->CreateSalBitmap();
+
+ Size aLocalSize(rSize);
+ if( pSalBmp->Create( xBitmapCanvas, aLocalSize ) )
+ {
+ pSalMask = ImplGetSVData()->mpDefInst->CreateSalBitmap();
+ if ( pSalMask->Create( xBitmapCanvas, aLocalSize, true ) )
+ {
+ *this = BitmapEx(Bitmap(pSalBmp), Bitmap(pSalMask) );
+ return true;
+ }
+ else
+ {
+ *this = BitmapEx(Bitmap(pSalBmp));
+ return true;
+ }
+ }
+
+ return false;
+}
+
+namespace
+{
+ Bitmap impTransformBitmap(
+ const Bitmap& rSource,
+ const Size& rDestinationSize,
+ const basegfx::B2DHomMatrix& rTransform,
+ bool bSmooth)
+ {
+ Bitmap aDestination(rDestinationSize, vcl::PixelFormat::N24_BPP);
+ BitmapScopedWriteAccess xWrite(aDestination);
+
+ if(xWrite)
+ {
+ BitmapScopedReadAccess xRead(rSource);
+
+ if (xRead)
+ {
+ const Size aDestinationSizePixel(aDestination.GetSizePixel());
+
+ // tdf#157795 set color to black outside of bitmap bounds
+ // Due to commit 81994cb2b8b32453a92bcb011830fcb884f22ff3,
+ // transparent areas are now black instead of white.
+ const BitmapColor aOutside(0x0, 0x0, 0x0);
+
+ for(tools::Long y(0); y < aDestinationSizePixel.getHeight(); y++)
+ {
+ Scanline pScanline = xWrite->GetScanline( y );
+ for(tools::Long x(0); x < aDestinationSizePixel.getWidth(); x++)
+ {
+ const basegfx::B2DPoint aSourceCoor(rTransform * basegfx::B2DPoint(x, y));
+
+ if(bSmooth)
+ {
+ xWrite->SetPixelOnData(
+ pScanline,
+ x,
+ xRead->GetInterpolatedColorWithFallback(
+ aSourceCoor.getY(),
+ aSourceCoor.getX(),
+ aOutside));
+ }
+ else
+ {
+ // this version does the correct <= 0.0 checks, so no need
+ // to do the static_cast< sal_Int32 > self and make an error
+ xWrite->SetPixelOnData(
+ pScanline,
+ x,
+ xRead->GetColorWithFallback(
+ aSourceCoor.getY(),
+ aSourceCoor.getX(),
+ aOutside));
+ }
+ }
+ }
+ }
+ }
+ xWrite.reset();
+
+ rSource.AdaptBitCount(aDestination);
+
+ return aDestination;
+ }
+
+ /// Decides if rTransformation needs smoothing or not (e.g. 180 deg rotation doesn't need it).
+ bool implTransformNeedsSmooth(const basegfx::B2DHomMatrix& rTransformation)
+ {
+ basegfx::B2DVector aScale, aTranslate;
+ double fRotate, fShearX;
+ rTransformation.decompose(aScale, aTranslate, fRotate, fShearX);
+ if (aScale != basegfx::B2DVector(1, 1))
+ {
+ return true;
+ }
+
+ fRotate = fmod( fRotate, 2 * M_PI );
+ if (fRotate < 0)
+ {
+ fRotate += 2 * M_PI;
+ }
+ if (!rtl::math::approxEqual(fRotate, 0)
+ && !rtl::math::approxEqual(fRotate, M_PI_2)
+ && !rtl::math::approxEqual(fRotate, M_PI)
+ && !rtl::math::approxEqual(fRotate, 3 * M_PI_2))
+ {
+ return true;
+ }
+
+ if (!rtl::math::approxEqual(fShearX, 0))
+ {
+ return true;
+ }
+
+ return false;
+ }
+} // end of anonymous namespace
+
+BitmapEx BitmapEx::TransformBitmapEx(
+ double fWidth,
+ double fHeight,
+ const basegfx::B2DHomMatrix& rTransformation) const
+{
+ if(fWidth <= 1 || fHeight <= 1)
+ return BitmapEx();
+
+ // force destination to 24 bit, we want to smooth output
+ const Size aDestinationSize(basegfx::fround(fWidth), basegfx::fround(fHeight));
+ bool bSmooth = implTransformNeedsSmooth(rTransformation);
+ const Bitmap aDestination(impTransformBitmap(GetBitmap(), aDestinationSize, rTransformation, bSmooth));
+
+ // create mask
+ if(IsAlpha())
+ {
+ const Bitmap aAlpha(impTransformBitmap(GetAlphaMask().GetBitmap(), aDestinationSize, rTransformation, bSmooth));
+ return BitmapEx(aDestination, AlphaMask(aAlpha));
+ }
+
+ return BitmapEx(aDestination);
+}
+
+BitmapEx BitmapEx::getTransformed(
+ const basegfx::B2DHomMatrix& rTransformation,
+ const basegfx::B2DRange& rVisibleRange,
+ double fMaximumArea) const
+{
+ BitmapEx aRetval;
+
+ if(IsEmpty())
+ return aRetval;
+
+ const sal_uInt32 nSourceWidth(GetSizePixel().Width());
+ const sal_uInt32 nSourceHeight(GetSizePixel().Height());
+
+ if(!nSourceWidth || !nSourceHeight)
+ return aRetval;
+
+ // Get aOutlineRange
+ basegfx::B2DRange aOutlineRange(0.0, 0.0, 1.0, 1.0);
+
+ aOutlineRange.transform(rTransformation);
+
+ // create visible range from it by moving from relative to absolute
+ basegfx::B2DRange aVisibleRange(rVisibleRange);
+
+ aVisibleRange.transform(
+ basegfx::utils::createScaleTranslateB2DHomMatrix(
+ aOutlineRange.getRange(),
+ aOutlineRange.getMinimum()));
+
+ // get target size (which is visible range's size)
+ double fWidth(aVisibleRange.getWidth());
+ double fHeight(aVisibleRange.getHeight());
+
+ if(fWidth < 1.0 || fHeight < 1.0)
+ {
+ return aRetval;
+ }
+
+ // test if discrete size (pixel) maybe too big and limit it
+ const double fArea(fWidth * fHeight);
+ const bool bNeedToReduce(basegfx::fTools::more(fArea, fMaximumArea));
+ double fReduceFactor(1.0);
+
+ if(bNeedToReduce)
+ {
+ fReduceFactor = sqrt(fMaximumArea / fArea);
+ fWidth *= fReduceFactor;
+ fHeight *= fReduceFactor;
+ }
+
+ // Build complete transform from source pixels to target pixels.
+ // Start by scaling from source pixel size to unit coordinates
+ basegfx::B2DHomMatrix aTransform(
+ basegfx::utils::createScaleB2DHomMatrix(
+ 1.0 / nSourceWidth,
+ 1.0 / nSourceHeight));
+
+ // multiply with given transform which leads from unit coordinates inside
+ // aOutlineRange
+ aTransform = rTransformation * aTransform;
+
+ // subtract top-left of absolute VisibleRange
+ aTransform.translate(
+ -aVisibleRange.getMinX(),
+ -aVisibleRange.getMinY());
+
+ // scale to target pixels (if needed)
+ if(bNeedToReduce)
+ {
+ aTransform.scale(fReduceFactor, fReduceFactor);
+ }
+
+ // invert to get transformation from target pixel coordinates to source pixels
+ aTransform.invert();
+
+ // create bitmap using source, destination and linear back-transformation
+ aRetval = TransformBitmapEx(fWidth, fHeight, aTransform);
+
+ return aRetval;
+}
+
+BitmapEx BitmapEx::ModifyBitmapEx(const basegfx::BColorModifierStack& rBColorModifierStack) const
+{
+ Bitmap aChangedBitmap(GetBitmap());
+ bool bDone(false);
+
+ for(sal_uInt32 a(rBColorModifierStack.count()); a && !bDone; )
+ {
+ const basegfx::BColorModifierSharedPtr& rModifier = rBColorModifierStack.getBColorModifier(--a);
+ const basegfx::BColorModifier_replace* pReplace = dynamic_cast< const basegfx::BColorModifier_replace* >(rModifier.get());
+
+ if(pReplace)
+ {
+ // complete replace
+ if(IsAlpha())
+ {
+ // clear bitmap with dest color
+ if (vcl::isPalettePixelFormat(aChangedBitmap.getPixelFormat()))
+ {
+ // For e.g. 8bit Bitmaps, the nearest color to the given erase color is
+ // determined and used -> this may be different from what is wanted here.
+ // Better create a new bitmap with the needed color explicitly.
+ BitmapScopedReadAccess xReadAccess(aChangedBitmap);
+ OSL_ENSURE(xReadAccess, "Got no Bitmap ReadAccess ?!?");
+
+ if(xReadAccess)
+ {
+ BitmapPalette aNewPalette(xReadAccess->GetPalette());
+ aNewPalette[0] = BitmapColor(Color(pReplace->getBColor()));
+ aChangedBitmap = Bitmap(
+ aChangedBitmap.GetSizePixel(),
+ aChangedBitmap.getPixelFormat(),
+ &aNewPalette);
+ }
+ }
+ aChangedBitmap.Erase(Color(pReplace->getBColor()));
+ }
+ else
+ {
+ // erase bitmap, caller will know to paint direct
+ aChangedBitmap.SetEmpty();
+ }
+
+ bDone = true;
+ }
+ else
+ {
+ BitmapScopedWriteAccess xContent(aChangedBitmap);
+
+ if(xContent)
+ {
+ const double fConvertColor(1.0 / 255.0);
+
+ if(xContent->HasPalette())
+ {
+ const sal_uInt16 nCount(xContent->GetPaletteEntryCount());
+
+ for(sal_uInt16 b(0); b < nCount; b++)
+ {
+ const BitmapColor& rCol = xContent->GetPaletteColor(b);
+ const basegfx::BColor aBSource(
+ rCol.GetRed() * fConvertColor,
+ rCol.GetGreen() * fConvertColor,
+ rCol.GetBlue() * fConvertColor);
+ const basegfx::BColor aBDest(rModifier->getModifiedColor(aBSource));
+ xContent->SetPaletteColor(b, BitmapColor(Color(aBDest)));
+ }
+ }
+ else if(ScanlineFormat::N24BitTcBgr == xContent->GetScanlineFormat())
+ {
+ for(tools::Long y(0); y < xContent->Height(); y++)
+ {
+ Scanline pScan = xContent->GetScanline(y);
+
+ for(tools::Long x(0); x < xContent->Width(); x++)
+ {
+ const basegfx::BColor aBSource(
+ *(pScan + 2)* fConvertColor,
+ *(pScan + 1) * fConvertColor,
+ *pScan * fConvertColor);
+ const basegfx::BColor aBDest(rModifier->getModifiedColor(aBSource));
+ *pScan++ = static_cast< sal_uInt8 >(aBDest.getBlue() * 255.0);
+ *pScan++ = static_cast< sal_uInt8 >(aBDest.getGreen() * 255.0);
+ *pScan++ = static_cast< sal_uInt8 >(aBDest.getRed() * 255.0);
+ }
+ }
+ }
+ else if(ScanlineFormat::N24BitTcRgb == xContent->GetScanlineFormat())
+ {
+ for(tools::Long y(0); y < xContent->Height(); y++)
+ {
+ Scanline pScan = xContent->GetScanline(y);
+
+ for(tools::Long x(0); x < xContent->Width(); x++)
+ {
+ const basegfx::BColor aBSource(
+ *pScan * fConvertColor,
+ *(pScan + 1) * fConvertColor,
+ *(pScan + 2) * fConvertColor);
+ const basegfx::BColor aBDest(rModifier->getModifiedColor(aBSource));
+ *pScan++ = static_cast< sal_uInt8 >(aBDest.getRed() * 255.0);
+ *pScan++ = static_cast< sal_uInt8 >(aBDest.getGreen() * 255.0);
+ *pScan++ = static_cast< sal_uInt8 >(aBDest.getBlue() * 255.0);
+ }
+ }
+ }
+ else
+ {
+ for(tools::Long y(0); y < xContent->Height(); y++)
+ {
+ Scanline pScanline = xContent->GetScanline( y );
+ for(tools::Long x(0); x < xContent->Width(); x++)
+ {
+ const BitmapColor aBMCol(xContent->GetColor(y, x));
+ const basegfx::BColor aBSource(
+ static_cast<double>(aBMCol.GetRed()) * fConvertColor,
+ static_cast<double>(aBMCol.GetGreen()) * fConvertColor,
+ static_cast<double>(aBMCol.GetBlue()) * fConvertColor);
+ const basegfx::BColor aBDest(rModifier->getModifiedColor(aBSource));
+
+ xContent->SetPixelOnData(pScanline, x, BitmapColor(Color(aBDest)));
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if(aChangedBitmap.IsEmpty())
+ {
+ return BitmapEx();
+ }
+ else
+ {
+ if(IsAlpha())
+ {
+ return BitmapEx(aChangedBitmap, GetAlphaMask());
+ }
+ else
+ {
+ return BitmapEx(aChangedBitmap);
+ }
+ }
+}
+
+BitmapEx createBlendFrame(
+ const Size& rSize,
+ sal_uInt8 nAlpha,
+ Color aColorTopLeft,
+ Color aColorBottomRight)
+{
+ const sal_uInt32 nW(rSize.Width());
+ const sal_uInt32 nH(rSize.Height());
+
+ if(nW || nH)
+ {
+ Color aColTopRight(aColorTopLeft);
+ Color aColBottomLeft(aColorTopLeft);
+ const sal_uInt32 nDE(nW + nH);
+
+ aColTopRight.Merge(aColorBottomRight, 255 - sal_uInt8((nW * 255) / nDE));
+ aColBottomLeft.Merge(aColorBottomRight, 255 - sal_uInt8((nH * 255) / nDE));
+
+ return createBlendFrame(rSize, nAlpha, aColorTopLeft, aColTopRight, aColorBottomRight, aColBottomLeft);
+ }
+
+ return BitmapEx();
+}
+
+BitmapEx createBlendFrame(
+ const Size& rSize,
+ sal_uInt8 nAlpha,
+ Color aColorTopLeft,
+ Color aColorTopRight,
+ Color aColorBottomRight,
+ Color aColorBottomLeft)
+{
+ // FIXME the call sites are actually passing in transparency
+ nAlpha = 255 - nAlpha;
+ BlendFrameCache* pBlendFrameCache = ImplGetBlendFrameCache();
+
+ if(pBlendFrameCache->m_aLastSize == rSize
+ && pBlendFrameCache->m_nLastAlpha == nAlpha
+ && pBlendFrameCache->m_aLastColorTopLeft == aColorTopLeft
+ && pBlendFrameCache->m_aLastColorTopRight == aColorTopRight
+ && pBlendFrameCache->m_aLastColorBottomRight == aColorBottomRight
+ && pBlendFrameCache->m_aLastColorBottomLeft == aColorBottomLeft)
+ {
+ return pBlendFrameCache->m_aLastResult;
+ }
+
+ pBlendFrameCache->m_aLastSize = rSize;
+ pBlendFrameCache->m_nLastAlpha = nAlpha;
+ pBlendFrameCache->m_aLastColorTopLeft = aColorTopLeft;
+ pBlendFrameCache->m_aLastColorTopRight = aColorTopRight;
+ pBlendFrameCache->m_aLastColorBottomRight = aColorBottomRight;
+ pBlendFrameCache->m_aLastColorBottomLeft = aColorBottomLeft;
+ pBlendFrameCache->m_aLastResult.Clear();
+
+ const tools::Long nW(rSize.Width());
+ const tools::Long nH(rSize.Height());
+
+ if(nW > 1 && nH > 1)
+ {
+ sal_uInt8 aEraseTrans(0xff);
+ Bitmap aContent(rSize, vcl::PixelFormat::N24_BPP);
+ AlphaMask aAlpha(rSize, &aEraseTrans);
+
+ aContent.Erase(COL_BLACK);
+
+ BitmapScopedWriteAccess pContent(aContent);
+ BitmapScopedWriteAccess pAlpha(aAlpha);
+
+ if(pContent && pAlpha)
+ {
+ tools::Long x(0);
+ tools::Long y(0);
+ Scanline pScanContent = pContent->GetScanline( 0 );
+ Scanline pScanAlpha = pContent->GetScanline( 0 );
+
+ // x == 0, y == 0, top-left corner
+ pContent->SetPixelOnData(pScanContent, 0, aColorTopLeft);
+ pAlpha->SetPixelOnData(pScanAlpha, 0, BitmapColor(nAlpha));
+
+ // y == 0, top line left to right
+ for(x = 1; x < nW - 1; x++)
+ {
+ Color aMix(aColorTopLeft);
+
+ aMix.Merge(aColorTopRight, 255 - sal_uInt8((x * 255) / nW));
+ pContent->SetPixelOnData(pScanContent, x, aMix);
+ pAlpha->SetPixelOnData(pScanAlpha, x, BitmapColor(nAlpha));
+ }
+
+ // x == nW - 1, y == 0, top-right corner
+ // #i123690# Caution! When nW is 1, x == nW is possible (!)
+ if(x < nW)
+ {
+ pContent->SetPixelOnData(pScanContent, x, aColorTopRight);
+ pAlpha->SetPixelOnData(pScanAlpha, x, BitmapColor(nAlpha));
+ }
+
+ // x == 0 and nW - 1, left and right line top-down
+ for(y = 1; y < nH - 1; y++)
+ {
+ pScanContent = pContent->GetScanline( y );
+ pScanAlpha = pContent->GetScanline( y );
+ Color aMixA(aColorTopLeft);
+
+ aMixA.Merge(aColorBottomLeft, 255 - sal_uInt8((y * 255) / nH));
+ pContent->SetPixelOnData(pScanContent, 0, aMixA);
+ pAlpha->SetPixelOnData(pScanAlpha, 0, BitmapColor(nAlpha));
+
+ // #i123690# Caution! When nW is 1, x == nW is possible (!)
+ if(x < nW)
+ {
+ Color aMixB(aColorTopRight);
+
+ aMixB.Merge(aColorBottomRight, 255 - sal_uInt8((y * 255) / nH));
+ pContent->SetPixelOnData(pScanContent, x, aMixB);
+ pAlpha->SetPixelOnData(pScanAlpha, x, BitmapColor(nAlpha));
+ }
+ }
+
+ // #i123690# Caution! When nH is 1, y == nH is possible (!)
+ if(y < nH)
+ {
+ // x == 0, y == nH - 1, bottom-left corner
+ pContent->SetPixelOnData(pScanContent, 0, aColorBottomLeft);
+ pAlpha->SetPixelOnData(pScanAlpha, 0, BitmapColor(nAlpha));
+
+ // y == nH - 1, bottom line left to right
+ for(x = 1; x < nW - 1; x++)
+ {
+ Color aMix(aColorBottomLeft);
+
+ aMix.Merge(aColorBottomRight, 255 - sal_uInt8(((x - 0)* 255) / nW));
+ pContent->SetPixelOnData(pScanContent, x, aMix);
+ pAlpha->SetPixelOnData(pScanAlpha, x, BitmapColor(nAlpha));
+ }
+
+ // x == nW - 1, y == nH - 1, bottom-right corner
+ // #i123690# Caution! When nW is 1, x == nW is possible (!)
+ if(x < nW)
+ {
+ pContent->SetPixelOnData(pScanContent, x, aColorBottomRight);
+ pAlpha->SetPixelOnData(pScanAlpha, x, BitmapColor(nAlpha));
+ }
+ }
+
+ pContent.reset();
+ pAlpha.reset();
+
+ pBlendFrameCache->m_aLastResult = BitmapEx(aContent, aAlpha);
+ }
+ }
+
+ return pBlendFrameCache->m_aLastResult;
+}
+
+void BitmapEx::Replace(const Color& rSearchColor,
+ const Color& rReplaceColor,
+ sal_uInt8 nTolerance)
+{
+ maBitmap.Replace(rSearchColor, rReplaceColor, nTolerance);
+}
+
+void BitmapEx::Replace( const Color* pSearchColors,
+ const Color* pReplaceColors,
+ size_t nColorCount,
+ sal_uInt8 const * pTols )
+{
+ maBitmap.Replace( pSearchColors, pReplaceColors, nColorCount, pTols );
+}
+
+void BitmapEx::ReplaceTransparency(const Color& rColor)
+{
+ if( IsAlpha() )
+ {
+ maBitmap.Replace( GetAlphaMask(), rColor );
+ maAlphaMask = Bitmap();
+ maBitmapSize = maBitmap.GetSizePixel();
+ }
+}
+
+static Bitmap DetectEdges( const Bitmap& rBmp )
+{
+ constexpr sal_uInt8 cEdgeDetectThreshold = 128;
+ const Size aSize( rBmp.GetSizePixel() );
+
+ if( ( aSize.Width() <= 2 ) || ( aSize.Height() <= 2 ) )
+ return rBmp;
+
+ Bitmap aWorkBmp( rBmp );
+
+ if( !aWorkBmp.Convert( BmpConversion::N8BitGreys ) )
+ return rBmp;
+
+ ScopedVclPtr<VirtualDevice> pVirDev(VclPtr<VirtualDevice>::Create());
+ pVirDev->SetOutputSizePixel(aSize);
+ BitmapScopedReadAccess pReadAcc(aWorkBmp);
+ if( !pReadAcc )
+ return rBmp;
+
+ const tools::Long nWidth = aSize.Width();
+ const tools::Long nWidth2 = nWidth - 2;
+ const tools::Long nHeight = aSize.Height();
+ const tools::Long nHeight2 = nHeight - 2;
+ const tools::Long lThres2 = static_cast<tools::Long>(cEdgeDetectThreshold) * cEdgeDetectThreshold;
+ tools::Long nSum1;
+ tools::Long nSum2;
+ tools::Long lGray;
+
+ // initialize border with white pixels
+ pVirDev->SetLineColor( COL_WHITE );
+ pVirDev->DrawLine( Point(), Point( nWidth - 1, 0L ) );
+ pVirDev->DrawLine( Point( nWidth - 1, 0L ), Point( nWidth - 1, nHeight - 1 ) );
+ pVirDev->DrawLine( Point( nWidth - 1, nHeight - 1 ), Point( 0L, nHeight - 1 ) );
+ pVirDev->DrawLine( Point( 0, nHeight - 1 ), Point() );
+
+ for( tools::Long nY = 0, nY1 = 1, nY2 = 2; nY < nHeight2; nY++, nY1++, nY2++ )
+ {
+ Scanline pScanlineRead = pReadAcc->GetScanline( nY );
+ Scanline pScanlineRead1 = pReadAcc->GetScanline( nY1 );
+ Scanline pScanlineRead2 = pReadAcc->GetScanline( nY2 );
+ for( tools::Long nX = 0, nXDst = 1, nXTmp; nX < nWidth2; nX++, nXDst++ )
+ {
+ nXTmp = nX;
+
+ nSum2 = pReadAcc->GetIndexFromData( pScanlineRead, nXTmp++ );
+ nSum1 = -nSum2;
+ nSum2 += static_cast<tools::Long>(pReadAcc->GetIndexFromData( pScanlineRead, nXTmp++ )) << 1;
+ lGray = pReadAcc->GetIndexFromData( pScanlineRead, nXTmp );
+ nSum1 += lGray;
+ nSum2 += lGray;
+
+ nSum1 += static_cast<tools::Long>(pReadAcc->GetIndexFromData( pScanlineRead1, nXTmp )) << 1;
+ nXTmp -= 2;
+ nSum1 -= static_cast<tools::Long>(pReadAcc->GetIndexFromData( pScanlineRead1, nXTmp )) << 1;
+
+ lGray = -static_cast<tools::Long>(pReadAcc->GetIndexFromData( pScanlineRead2, nXTmp++ ));
+ nSum1 += lGray;
+ nSum2 += lGray;
+ nSum2 -= static_cast<tools::Long>(pReadAcc->GetIndexFromData( pScanlineRead2, nXTmp++ )) << 1;
+ lGray = static_cast<tools::Long>(pReadAcc->GetIndexFromData( pScanlineRead2, nXTmp ));
+ nSum1 += lGray;
+ nSum2 -= lGray;
+
+ if( ( nSum1 * nSum1 + nSum2 * nSum2 ) < lThres2 )
+ pVirDev->DrawPixel( Point(nXDst, nY), COL_WHITE );
+ else
+ pVirDev->DrawPixel( Point(nXDst, nY), COL_BLACK );
+ }
+ }
+
+ pReadAcc.reset();
+
+ Bitmap aRetBmp = pVirDev->GetBitmap(Point(0,0), aSize);
+
+ if( aRetBmp.IsEmpty() )
+ aRetBmp = rBmp;
+ else
+ {
+ aRetBmp.SetPrefMapMode( rBmp.GetPrefMapMode() );
+ aRetBmp.SetPrefSize( rBmp.GetPrefSize() );
+ }
+
+ return aRetBmp;
+}
+
+/** Get contours in image */
+tools::Polygon BitmapEx::GetContour( bool bContourEdgeDetect,
+ const tools::Rectangle* pWorkRectPixel )
+{
+ Bitmap aWorkBmp;
+ tools::Polygon aRetPoly;
+ tools::Rectangle aWorkRect( Point(), maBitmap.GetSizePixel() );
+
+ if( pWorkRectPixel )
+ aWorkRect.Intersection( *pWorkRectPixel );
+
+ aWorkRect.Normalize();
+
+ if( ( aWorkRect.GetWidth() > 4 ) && ( aWorkRect.GetHeight() > 4 ) )
+ {
+ // if the flag is set, we need to detect edges
+ if( bContourEdgeDetect )
+ aWorkBmp = DetectEdges( maBitmap );
+ else
+ aWorkBmp = maBitmap;
+
+ BitmapScopedReadAccess pAcc(aWorkBmp);
+
+ const tools::Long nWidth = pAcc ? pAcc->Width() : 0;
+ const tools::Long nHeight = pAcc ? pAcc->Height() : 0;
+
+ if (pAcc && nWidth && nHeight)
+ {
+ const Size& rPrefSize = aWorkBmp.GetPrefSize();
+ const double fFactorX = static_cast<double>(rPrefSize.Width()) / nWidth;
+ const double fFactorY = static_cast<double>(rPrefSize.Height()) / nHeight;
+ const tools::Long nStartX1 = aWorkRect.Left() + 1;
+ const tools::Long nEndX1 = aWorkRect.Right();
+ const tools::Long nStartX2 = nEndX1 - 1;
+ const tools::Long nStartY1 = aWorkRect.Top() + 1;
+ const tools::Long nEndY1 = aWorkRect.Bottom();
+ std::unique_ptr<Point[]> pPoints1;
+ std::unique_ptr<Point[]> pPoints2;
+ tools::Long nX, nY;
+ sal_uInt16 nPolyPos = 0;
+ const BitmapColor aBlack = pAcc->GetBestMatchingColor( COL_BLACK );
+
+ pPoints1.reset(new Point[ nHeight ]);
+ pPoints2.reset(new Point[ nHeight ]);
+
+ for ( nY = nStartY1; nY < nEndY1; nY++ )
+ {
+ nX = nStartX1;
+ Scanline pScanline = pAcc->GetScanline( nY );
+
+ // scan row from left to right
+ while( nX < nEndX1 )
+ {
+ if( aBlack == pAcc->GetPixelFromData( pScanline, nX ) )
+ {
+ pPoints1[ nPolyPos ] = Point( nX, nY );
+ nX = nStartX2;
+
+ // this loop always breaks eventually as there is at least one pixel
+ while( true )
+ {
+ if( aBlack == pAcc->GetPixelFromData( pScanline, nX ) )
+ {
+ pPoints2[ nPolyPos ] = Point( nX, nY );
+ break;
+ }
+
+ nX--;
+ }
+
+ nPolyPos++;
+ break;
+ }
+
+ nX++;
+ }
+ }
+
+ const sal_uInt16 nNewSize1 = nPolyPos << 1;
+
+ aRetPoly = tools::Polygon( nPolyPos, pPoints1.get() );
+ aRetPoly.SetSize( nNewSize1 + 1 );
+ aRetPoly[ nNewSize1 ] = aRetPoly[ 0 ];
+
+ for( sal_uInt16 j = nPolyPos; nPolyPos < nNewSize1; )
+ aRetPoly[ nPolyPos++ ] = pPoints2[ --j ];
+
+ if( ( fFactorX != 0. ) && ( fFactorY != 0. ) )
+ aRetPoly.Scale( fFactorX, fFactorY );
+ }
+ }
+
+ return aRetPoly;
+}
+
+void BitmapEx::ChangeColorAlpha( sal_uInt8 cIndexFrom, sal_Int8 nAlphaTo )
+{
+ AlphaMask aAlphaMask(GetAlphaMask());
+ BitmapScopedWriteAccess pAlphaWriteAccess(aAlphaMask);
+ BitmapScopedReadAccess pReadAccess(maBitmap);
+ assert( pReadAccess.get() && pAlphaWriteAccess.get() );
+ if ( !(pReadAccess.get() && pAlphaWriteAccess.get()) )
+ return;
+
+ for ( tools::Long nY = 0; nY < pReadAccess->Height(); nY++ )
+ {
+ Scanline pScanline = pAlphaWriteAccess->GetScanline( nY );
+ Scanline pScanlineRead = pReadAccess->GetScanline( nY );
+ for ( tools::Long nX = 0; nX < pReadAccess->Width(); nX++ )
+ {
+ const sal_uInt8 cIndex = pReadAccess->GetPixelFromData( pScanlineRead, nX ).GetIndex();
+ if ( cIndex == cIndexFrom )
+ pAlphaWriteAccess->SetPixelOnData( pScanline, nX, BitmapColor(nAlphaTo) );
+ }
+ }
+ *this = BitmapEx( GetBitmap(), aAlphaMask );
+}
+
+void BitmapEx::AdjustTransparency(sal_uInt8 cTrans)
+{
+ AlphaMask aAlpha;
+
+ if (!IsAlpha())
+ {
+ aAlpha = AlphaMask(GetSizePixel(), &cTrans);
+ }
+ else
+ {
+ aAlpha = GetAlphaMask();
+ BitmapScopedWriteAccess pA(aAlpha);
+ assert(pA);
+
+ if( !pA )
+ return;
+
+ sal_uLong nTrans = cTrans;
+ const tools::Long nWidth = pA->Width(), nHeight = pA->Height();
+
+ if( pA->GetScanlineFormat() == ScanlineFormat::N8BitPal )
+ {
+ for( tools::Long nY = 0; nY < nHeight; nY++ )
+ {
+ Scanline pAScan = pA->GetScanline( nY );
+
+ for( tools::Long nX = 0; nX < nWidth; nX++ )
+ {
+ sal_uLong nNewTrans = nTrans + (255 - *pAScan);
+ // clamp to 255
+ nNewTrans = ( nNewTrans & 0xffffff00 ) ? 255 : nNewTrans;
+ *pAScan++ = static_cast<sal_uInt8>( 255 - nNewTrans );
+ }
+ }
+ }
+ else
+ {
+ BitmapColor aAlphaValue( 0 );
+
+ for( tools::Long nY = 0; nY < nHeight; nY++ )
+ {
+ Scanline pScanline = pA->GetScanline( nY );
+ for( tools::Long nX = 0; nX < nWidth; nX++ )
+ {
+ 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 );
+ }
+ }
+ }
+ }
+ *this = BitmapEx( GetBitmap(), aAlpha );
+}
+
+void BitmapEx::CombineMaskOr(Color maskColor, sal_uInt8 nTol)
+{
+ AlphaMask aNewMask = maBitmap.CreateAlphaMask( maskColor, nTol );
+ if ( IsAlpha() )
+ aNewMask.AlphaCombineOr( maAlphaMask );
+ maAlphaMask = aNewMask;
+}
+
+/**
+ * Retrieves the color model data we need for the XImageConsumer stuff.
+ */
+void BitmapEx::GetColorModel(css::uno::Sequence< sal_Int32 >& rRGBPalette,
+ sal_uInt32& rnRedMask, sal_uInt32& rnGreenMask, sal_uInt32& rnBlueMask, sal_uInt32& rnAlphaMask, sal_uInt32& rnTransparencyIndex,
+ sal_uInt32& rnWidth, sal_uInt32& rnHeight, sal_uInt8& rnBitCount)
+{
+ BitmapScopedReadAccess pReadAccess( maBitmap );
+ assert( pReadAccess );
+
+ if( pReadAccess->HasPalette() )
+ {
+ sal_uInt16 nPalCount = pReadAccess->GetPaletteEntryCount();
+
+ if( nPalCount )
+ {
+ rRGBPalette = css::uno::Sequence< sal_Int32 >( nPalCount + 1 );
+
+ sal_Int32* pTmp = rRGBPalette.getArray();
+
+ for( sal_uInt32 i = 0; i < nPalCount; i++, pTmp++ )
+ {
+ const BitmapColor& rCol = pReadAccess->GetPaletteColor( static_cast<sal_uInt16>(i) );
+
+ *pTmp = static_cast<sal_Int32>(rCol.GetRed()) << sal_Int32(24);
+ *pTmp |= static_cast<sal_Int32>(rCol.GetGreen()) << sal_Int32(16);
+ *pTmp |= static_cast<sal_Int32>(rCol.GetBlue()) << sal_Int32(8);
+ *pTmp |= sal_Int32(0x000000ffL);
+ }
+
+ if( IsAlpha() )
+ {
+ // append transparent entry
+ *pTmp = sal_Int32(0xffffff00L);
+ rnTransparencyIndex = nPalCount;
+ nPalCount++;
+ }
+ else
+ rnTransparencyIndex = 0;
+ }
+ }
+ else
+ {
+ rnRedMask = 0xff000000UL;
+ rnGreenMask = 0x00ff0000UL;
+ rnBlueMask = 0x0000ff00UL;
+ rnAlphaMask = 0x000000ffUL;
+ rnTransparencyIndex = 0;
+ }
+
+ rnWidth = pReadAccess->Width();
+ rnHeight = pReadAccess->Height();
+ rnBitCount = pReadAccess->GetBitCount();
+}
+
+void BitmapEx::DumpAsPng(const char* pFileName) const
+{
+ OUString sPath;
+ if (pFileName)
+ {
+ sPath = OUString::fromUtf8(pFileName);
+ }
+ else if (const char* pEnv = std::getenv("VCL_DUMP_BMP_PATH"))
+ {
+ sPath = OUString::fromUtf8(pEnv);
+ }
+ else
+ {
+ sPath = "file:///tmp/bitmap.png";
+ }
+ SvFileStream aStream(sPath, StreamMode::STD_READWRITE | StreamMode::TRUNC);
+ assert(aStream.good());
+ vcl::PngImageWriter aWriter(aStream);
+ aWriter.write(*this);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapFastScaleFilter.cxx b/vcl/source/bitmap/BitmapFastScaleFilter.cxx
new file mode 100644
index 0000000000..431211369f
--- /dev/null
+++ b/vcl/source/bitmap/BitmapFastScaleFilter.cxx
@@ -0,0 +1,129 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/log.hxx>
+#include <tools/helpers.hxx>
+
+#include <vcl/bitmapex.hxx>
+#include <vcl/BitmapWriteAccess.hxx>
+
+#include <bitmap/BitmapFastScaleFilter.hxx>
+
+BitmapEx BitmapFastScaleFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ SAL_INFO("vcl.gdi", "BitmapFastScaleFilter::execute()");
+
+ Bitmap aBitmap(rBitmapEx.GetBitmap());
+
+ const Size aSizePix(aBitmap.GetSizePixel());
+ const sal_Int32 nNewWidth = FRound(aSizePix.Width() * mfScaleX);
+ const sal_Int32 nNewHeight = FRound(aSizePix.Height() * mfScaleY);
+ bool bRet = false;
+
+ SAL_INFO("vcl.gdi", "New width: " << nNewWidth << "\nNew height: " << nNewHeight);
+
+ if (nNewWidth > 0 && nNewHeight > 0)
+ {
+ BitmapScopedReadAccess pReadAcc(aBitmap);
+
+ if (pReadAcc)
+ {
+ Bitmap aNewBmp(Size(nNewWidth, nNewHeight), aBitmap.getPixelFormat(),
+ &pReadAcc->GetPalette());
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+
+ if (pWriteAcc)
+ {
+ const sal_Int32 nScanlineSize = pWriteAcc->GetScanlineSize();
+ const sal_Int32 nNewHeight1 = nNewHeight - 1;
+
+ if (nNewWidth && nNewHeight)
+ {
+ const double nWidth = pReadAcc->Width();
+ const double nHeight = pReadAcc->Height();
+ std::unique_ptr<sal_Int32[]> pLutX(new sal_Int32[nNewWidth]);
+ std::unique_ptr<sal_Int32[]> pLutY(new sal_Int32[nNewHeight]);
+
+ for (sal_Int32 nX = 0; nX < nNewWidth; nX++)
+ {
+ pLutX[nX] = sal_Int32(nX * nWidth / nNewWidth);
+ }
+
+ for (sal_Int32 nY = 0; nY < nNewHeight; nY++)
+ {
+ pLutY[nY] = sal_Int32(nY * nHeight / nNewHeight);
+ }
+
+ sal_Int32 nActY = 0;
+ while (nActY < nNewHeight)
+ {
+ sal_Int32 nMapY = pLutY[nActY];
+ Scanline pScanline = pWriteAcc->GetScanline(nActY);
+ Scanline pScanlineRead = pReadAcc->GetScanline(nMapY);
+
+ for (sal_Int32 nX = 0; nX < nNewWidth; nX++)
+ {
+ pWriteAcc->SetPixelOnData(
+ pScanline, nX,
+ pReadAcc->GetPixelFromData(pScanlineRead, pLutX[nX]));
+ }
+
+ while ((nActY < nNewHeight1) && (pLutY[nActY + 1] == nMapY))
+ {
+ memcpy(pWriteAcc->GetScanline(nActY + 1), pWriteAcc->GetScanline(nActY),
+ nScanlineSize);
+ nActY++;
+ }
+ nActY++;
+ }
+
+ bRet = true;
+ }
+
+ pWriteAcc.reset();
+ }
+ pReadAcc.reset();
+
+ if (bRet)
+ {
+ aBitmap.ReassignWithSize(aNewBmp);
+ SAL_INFO("vcl.gdi", "Bitmap size: " << aBitmap.GetSizePixel());
+ }
+ else
+ {
+ SAL_WARN("vcl.gdi", "no resize");
+ }
+ }
+ }
+
+ AlphaMask aMask(rBitmapEx.GetAlphaMask());
+
+ if (bRet && !aMask.IsEmpty())
+ bRet = aMask.Scale(maSize, BmpScaleFlag::Fast);
+
+ SAL_WARN_IF(!aMask.IsEmpty() && aBitmap.GetSizePixel() != aMask.GetSizePixel(), "vcl",
+ "BitmapEx::Scale(): size mismatch for bitmap and alpha mask.");
+
+ if (bRet)
+ return BitmapEx(aBitmap, aMask);
+
+ return BitmapEx();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapFilterStackBlur.cxx b/vcl/source/bitmap/BitmapFilterStackBlur.cxx
new file mode 100644
index 0000000000..6e3b1ef555
--- /dev/null
+++ b/vcl/source/bitmap/BitmapFilterStackBlur.cxx
@@ -0,0 +1,658 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#include <vcl/BitmapFilterStackBlur.hxx>
+#include <vcl/BitmapWriteAccess.hxx>
+#include <sal/log.hxx>
+
+#include <comphelper/threadpool.hxx>
+
+namespace
+{
+const sal_Int16 constMultiplyTable[255]
+ = { 512, 512, 456, 512, 328, 456, 335, 512, 405, 328, 271, 456, 388, 335, 292, 512, 454,
+ 405, 364, 328, 298, 271, 496, 456, 420, 388, 360, 335, 312, 292, 273, 512, 482, 454,
+ 428, 405, 383, 364, 345, 328, 312, 298, 284, 271, 259, 496, 475, 456, 437, 420, 404,
+ 388, 374, 360, 347, 335, 323, 312, 302, 292, 282, 273, 265, 512, 497, 482, 468, 454,
+ 441, 428, 417, 405, 394, 383, 373, 364, 354, 345, 337, 328, 320, 312, 305, 298, 291,
+ 284, 278, 271, 265, 259, 507, 496, 485, 475, 465, 456, 446, 437, 428, 420, 412, 404,
+ 396, 388, 381, 374, 367, 360, 354, 347, 341, 335, 329, 323, 318, 312, 307, 302, 297,
+ 292, 287, 282, 278, 273, 269, 265, 261, 512, 505, 497, 489, 482, 475, 468, 461, 454,
+ 447, 441, 435, 428, 422, 417, 411, 405, 399, 394, 389, 383, 378, 373, 368, 364, 359,
+ 354, 350, 345, 341, 337, 332, 328, 324, 320, 316, 312, 309, 305, 301, 298, 294, 291,
+ 287, 284, 281, 278, 274, 271, 268, 265, 262, 259, 257, 507, 501, 496, 491, 485, 480,
+ 475, 470, 465, 460, 456, 451, 446, 442, 437, 433, 428, 424, 420, 416, 412, 408, 404,
+ 400, 396, 392, 388, 385, 381, 377, 374, 370, 367, 363, 360, 357, 354, 350, 347, 344,
+ 341, 338, 335, 332, 329, 326, 323, 320, 318, 315, 312, 310, 307, 304, 302, 299, 297,
+ 294, 292, 289, 287, 285, 282, 280, 278, 275, 273, 271, 269, 267, 265, 263, 261, 259 };
+
+const sal_Int16 constShiftTable[255]
+ = { 9, 11, 12, 13, 13, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 17, 17, 17, 17, 17, 17, 17,
+ 18, 18, 18, 18, 18, 18, 18, 18, 18, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
+ 19, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
+ 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23,
+ 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
+ 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
+ 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24 };
+
+struct BlurSharedData
+{
+ BitmapReadAccess* mpReadAccess;
+ BitmapWriteAccess* mpWriteAccess;
+ sal_Int32 mnRadius;
+ sal_Int32 mnComponentWidth;
+ sal_Int32 mnDiv;
+ sal_Int32 mnColorChannels;
+
+ BlurSharedData(BitmapReadAccess* pReadAccess, BitmapWriteAccess* pWriteAccess,
+ sal_Int32 aRadius, sal_Int32 nComponentWidth, sal_Int32 nColorChannels)
+ : mpReadAccess(pReadAccess)
+ , mpWriteAccess(pWriteAccess)
+ , mnRadius(aRadius)
+ , mnComponentWidth(nComponentWidth)
+ , mnDiv(aRadius + aRadius + 1)
+ , mnColorChannels(nColorChannels)
+ {
+ }
+};
+
+struct BlurArrays
+{
+ BlurSharedData maShared;
+
+ std::vector<sal_uInt8> maStackBuffer;
+ std::vector<sal_Int32> maPositionTable;
+ std::vector<sal_Int32> maWeightTable;
+
+ std::vector<sal_Int32> mnSumVector;
+ std::vector<sal_Int32> mnInSumVector;
+ std::vector<sal_Int32> mnOutSumVector;
+
+ BlurArrays(BlurSharedData const& rShared)
+ : maShared(rShared)
+ , maStackBuffer(maShared.mnDiv * maShared.mnComponentWidth)
+ , maPositionTable(maShared.mnDiv)
+ , maWeightTable(maShared.mnDiv)
+ , mnSumVector(maShared.mnColorChannels)
+ , mnInSumVector(maShared.mnColorChannels)
+ , mnOutSumVector(maShared.mnColorChannels)
+ {
+ }
+
+ void initializeWeightAndPositions(sal_Int32 nLastIndex)
+ {
+ for (sal_Int32 i = 0; i < maShared.mnDiv; i++)
+ {
+ maPositionTable[i] = std::clamp(i - maShared.mnRadius, sal_Int32(0), nLastIndex);
+ maWeightTable[i] = maShared.mnRadius + 1 - std::abs(i - maShared.mnRadius);
+ }
+ }
+
+ sal_Int32 getMultiplyValue() const
+ {
+ return static_cast<sal_Int32>(constMultiplyTable[maShared.mnRadius]);
+ }
+
+ sal_Int32 getShiftValue() const
+ {
+ return static_cast<sal_Int32>(constShiftTable[maShared.mnRadius]);
+ }
+};
+
+typedef void (*BlurRangeFn)(BlurSharedData const& rShared, sal_Int32 nStartY, sal_Int32 nEndY);
+
+class BlurTask : public comphelper::ThreadTask
+{
+ BlurRangeFn mpBlurFunction;
+ BlurSharedData& mrShared;
+ sal_Int32 mnStartY;
+ sal_Int32 mnEndY;
+
+public:
+ explicit BlurTask(const std::shared_ptr<comphelper::ThreadTaskTag>& pTag,
+ BlurRangeFn pBlurFunction, BlurSharedData& rShared, sal_Int32 nStartY,
+ sal_Int32 nEndY)
+ : comphelper::ThreadTask(pTag)
+ , mpBlurFunction(pBlurFunction)
+ , mrShared(rShared)
+ , mnStartY(nStartY)
+ , mnEndY(nEndY)
+ {
+ }
+
+ virtual void doWork() override { mpBlurFunction(mrShared, mnStartY, mnEndY); }
+};
+
+struct SumFunction24
+{
+ static inline void add(sal_Int32*& pValue1, sal_Int32 nConstant)
+ {
+ pValue1[0] += nConstant;
+ pValue1[1] += nConstant;
+ pValue1[2] += nConstant;
+ }
+
+ static inline void set(sal_Int32*& pValue1, sal_Int32 nConstant)
+ {
+ pValue1[0] = nConstant;
+ pValue1[1] = nConstant;
+ pValue1[2] = nConstant;
+ }
+
+ static inline void add(sal_Int32*& pValue1, const sal_uInt8* pValue2)
+ {
+ pValue1[0] += pValue2[0];
+ pValue1[1] += pValue2[1];
+ pValue1[2] += pValue2[2];
+ }
+
+ static inline void add(sal_Int32*& pValue1, const sal_Int32* pValue2)
+ {
+ pValue1[0] += pValue2[0];
+ pValue1[1] += pValue2[1];
+ pValue1[2] += pValue2[2];
+ }
+
+ static inline void sub(sal_Int32*& pValue1, const sal_uInt8* pValue2)
+ {
+ pValue1[0] -= pValue2[0];
+ pValue1[1] -= pValue2[1];
+ pValue1[2] -= pValue2[2];
+ }
+
+ static inline void sub(sal_Int32*& pValue1, const sal_Int32* pValue2)
+ {
+ pValue1[0] -= pValue2[0];
+ pValue1[1] -= pValue2[1];
+ pValue1[2] -= pValue2[2];
+ }
+
+ static inline void assignPtr(sal_uInt8*& pValue1, const sal_uInt8* pValue2)
+ {
+ pValue1[0] = pValue2[0];
+ pValue1[1] = pValue2[1];
+ pValue1[2] = pValue2[2];
+ }
+
+ static inline void assignMulAndShr(sal_uInt8*& result, const sal_Int32* sum, sal_Int32 multiply,
+ sal_Int32 shift)
+ {
+ result[0] = (multiply * sum[0]) >> shift;
+ result[1] = (multiply * sum[1]) >> shift;
+ result[2] = (multiply * sum[2]) >> shift;
+ }
+};
+
+struct SumFunction8
+{
+ static inline void add(sal_Int32*& pValue1, sal_Int32 nConstant) { pValue1[0] += nConstant; }
+
+ static inline void set(sal_Int32*& pValue1, sal_Int32 nConstant) { pValue1[0] = nConstant; }
+
+ static inline void add(sal_Int32*& pValue1, const sal_uInt8* pValue2)
+ {
+ pValue1[0] += pValue2[0];
+ }
+
+ static inline void add(sal_Int32*& pValue1, const sal_Int32* pValue2)
+ {
+ pValue1[0] += pValue2[0];
+ }
+
+ static inline void sub(sal_Int32*& pValue1, const sal_uInt8* pValue2)
+ {
+ pValue1[0] -= pValue2[0];
+ }
+
+ static inline void sub(sal_Int32*& pValue1, const sal_Int32* pValue2)
+ {
+ pValue1[0] -= pValue2[0];
+ }
+
+ static inline void assignPtr(sal_uInt8*& pValue1, const sal_uInt8* pValue2)
+ {
+ pValue1[0] = pValue2[0];
+ }
+
+ static inline void assignMulAndShr(sal_uInt8*& result, const sal_Int32* sum, sal_Int32 multiply,
+ sal_Int32 shift)
+ {
+ result[0] = (multiply * sum[0]) >> shift;
+ }
+};
+
+template <typename SumFunction>
+void stackBlurHorizontal(BlurSharedData const& rShared, sal_Int32 nStart, sal_Int32 nEnd)
+{
+ BitmapReadAccess* pReadAccess = rShared.mpReadAccess;
+ BitmapWriteAccess* pWriteAccess = rShared.mpWriteAccess;
+
+ BlurArrays aArrays(rShared);
+
+ sal_uInt8* pStack = aArrays.maStackBuffer.data();
+ sal_uInt8* pStackPtr;
+
+ sal_Int32 nWidth = pReadAccess->Width();
+ sal_Int32 nLastIndexX = nWidth - 1;
+
+ sal_Int32 nMultiplyValue = aArrays.getMultiplyValue();
+ sal_Int32 nShiftValue = aArrays.getShiftValue();
+
+ sal_Int32 nRadius = rShared.mnRadius;
+ sal_Int32 nComponentWidth = rShared.mnComponentWidth;
+ sal_Int32 nDiv = rShared.mnDiv;
+
+ Scanline pSourcePointer;
+ Scanline pDestinationPointer;
+
+ sal_Int32 nXPosition;
+ sal_Int32 nStackIndex;
+ sal_Int32 nStackIndexStart;
+ sal_Int32 nWeight;
+
+ aArrays.initializeWeightAndPositions(nLastIndexX);
+
+ sal_Int32* nSum = aArrays.mnSumVector.data();
+ sal_Int32* nInSum = aArrays.mnInSumVector.data();
+ sal_Int32* nOutSum = aArrays.mnOutSumVector.data();
+
+ sal_Int32* pPositionPointer = aArrays.maPositionTable.data();
+ sal_Int32* pWeightPointer = aArrays.maWeightTable.data();
+
+ for (sal_Int32 y = nStart; y <= nEnd; y++)
+ {
+ SumFunction::set(nSum, 0);
+ SumFunction::set(nInSum, 0);
+ SumFunction::set(nOutSum, 0);
+
+ // Pre-initialize blur data for first pixel.
+ // aArrays.maPositionTable contains values like (for radius of 5): [0,0,0,0,0,0,1,2,3,4,5],
+ // which are used as pixels indices in the current row that we use to prepare information
+ // for the first pixel; aArrays.maWeightTable has [1,2,3,4,5,6,5,4,3,2,1]. Before looking at
+ // the first row pixel, we pretend to have processed fake previous pixels, as if the row was
+ // extended to the left with the same color as that of the first pixel.
+ for (sal_Int32 i = 0; i < nDiv; i++)
+ {
+ pSourcePointer = pReadAccess->GetScanline(y) + nComponentWidth * pPositionPointer[i];
+
+ pStackPtr = &pStack[nComponentWidth * i];
+
+ SumFunction::assignPtr(pStackPtr, pSourcePointer);
+
+ nWeight = pWeightPointer[i];
+
+ SumFunction::add(nSum, pSourcePointer[0] * nWeight);
+
+ if (i - nRadius > 0)
+ {
+ SumFunction::add(nInSum, pSourcePointer);
+ }
+ else
+ {
+ SumFunction::add(nOutSum, pSourcePointer);
+ }
+ }
+
+ nStackIndex = nRadius;
+ nXPosition = std::min(nRadius, nLastIndexX);
+
+ pSourcePointer = pReadAccess->GetScanline(y) + nComponentWidth * nXPosition;
+
+ for (sal_Int32 x = 0; x < nWidth; x++)
+ {
+ pDestinationPointer = pWriteAccess->GetScanline(y) + nComponentWidth * x;
+
+ SumFunction::assignMulAndShr(pDestinationPointer, nSum, nMultiplyValue, nShiftValue);
+
+ SumFunction::sub(nSum, nOutSum);
+
+ nStackIndexStart = nStackIndex + nDiv - nRadius;
+ if (nStackIndexStart >= nDiv)
+ {
+ nStackIndexStart -= nDiv;
+ }
+ pStackPtr = &pStack[nComponentWidth * nStackIndexStart];
+
+ SumFunction::sub(nOutSum, pStackPtr);
+
+ if (nXPosition < nLastIndexX)
+ {
+ nXPosition++;
+ pSourcePointer = pReadAccess->GetScanline(y) + nComponentWidth * nXPosition;
+ }
+
+ SumFunction::assignPtr(pStackPtr, pSourcePointer);
+
+ SumFunction::add(nInSum, pSourcePointer);
+
+ SumFunction::add(nSum, nInSum);
+
+ nStackIndex++;
+ if (nStackIndex >= nDiv)
+ {
+ nStackIndex = 0;
+ }
+
+ pStackPtr = &pStack[nStackIndex * nComponentWidth];
+
+ SumFunction::add(nOutSum, pStackPtr);
+ SumFunction::sub(nInSum, pStackPtr);
+ }
+ }
+}
+
+template <typename SumFunction>
+void stackBlurVertical(BlurSharedData const& rShared, sal_Int32 nStart, sal_Int32 nEnd)
+{
+ BitmapReadAccess* pReadAccess = rShared.mpReadAccess;
+ BitmapWriteAccess* pWriteAccess = rShared.mpWriteAccess;
+
+ BlurArrays aArrays(rShared);
+
+ sal_uInt8* pStack = aArrays.maStackBuffer.data();
+ sal_uInt8* pStackPtr;
+
+ sal_Int32 nHeight = pReadAccess->Height();
+ sal_Int32 nLastIndexY = nHeight - 1;
+
+ sal_Int32 nMultiplyValue = aArrays.getMultiplyValue();
+ sal_Int32 nShiftValue = aArrays.getShiftValue();
+
+ sal_Int32 nRadius = rShared.mnRadius;
+ sal_Int32 nComponentWidth = rShared.mnComponentWidth;
+ sal_Int32 nDiv = rShared.mnDiv;
+
+ Scanline pSourcePointer;
+ Scanline pDestinationPointer;
+
+ sal_Int32 nYPosition;
+ sal_Int32 nStackIndex;
+ sal_Int32 nStackIndexStart;
+ sal_Int32 nWeight;
+
+ aArrays.initializeWeightAndPositions(nLastIndexY);
+
+ sal_Int32* nSum = aArrays.mnSumVector.data();
+ sal_Int32* nInSum = aArrays.mnInSumVector.data();
+ sal_Int32* nOutSum = aArrays.mnOutSumVector.data();
+ sal_Int32* pPositionPointer = aArrays.maPositionTable.data();
+ sal_Int32* pWeightPointer = aArrays.maWeightTable.data();
+
+ for (sal_Int32 x = nStart; x <= nEnd; x++)
+ {
+ SumFunction::set(nSum, 0);
+ SumFunction::set(nInSum, 0);
+ SumFunction::set(nOutSum, 0);
+
+ // Pre-initialize blur data for first pixel.
+ // aArrays.maPositionTable contains values like (for radius of 5): [0,0,0,0,0,0,1,2,3,4,5],
+ // which are used as pixels indices in the current column that we use to prepare information
+ // for the first pixel; aArrays.maWeightTable has [1,2,3,4,5,6,5,4,3,2,1]. Before looking at
+ // the first column pixels, we pretend to have processed fake previous pixels, as if the
+ // column was extended to the top with the same color as that of the first pixel.
+ for (sal_Int32 i = 0; i < nDiv; i++)
+ {
+ pSourcePointer = pReadAccess->GetScanline(pPositionPointer[i]) + nComponentWidth * x;
+
+ pStackPtr = &pStack[nComponentWidth * i];
+
+ SumFunction::assignPtr(pStackPtr, pSourcePointer);
+
+ nWeight = pWeightPointer[i];
+
+ SumFunction::add(nSum, pSourcePointer[0] * nWeight);
+
+ if (i - nRadius > 0)
+ {
+ SumFunction::add(nInSum, pSourcePointer);
+ }
+ else
+ {
+ SumFunction::add(nOutSum, pSourcePointer);
+ }
+ }
+
+ nStackIndex = nRadius;
+ nYPosition = std::min(nRadius, nLastIndexY);
+
+ pSourcePointer = pReadAccess->GetScanline(nYPosition) + nComponentWidth * x;
+
+ for (sal_Int32 y = 0; y < nHeight; y++)
+ {
+ pDestinationPointer = pWriteAccess->GetScanline(y) + nComponentWidth * x;
+
+ SumFunction::assignMulAndShr(pDestinationPointer, nSum, nMultiplyValue, nShiftValue);
+
+ SumFunction::sub(nSum, nOutSum);
+
+ nStackIndexStart = nStackIndex + nDiv - nRadius;
+ if (nStackIndexStart >= nDiv)
+ {
+ nStackIndexStart -= nDiv;
+ }
+ pStackPtr = &pStack[nComponentWidth * nStackIndexStart];
+
+ SumFunction::sub(nOutSum, pStackPtr);
+
+ if (nYPosition < nLastIndexY)
+ {
+ nYPosition++;
+ pSourcePointer = pReadAccess->GetScanline(nYPosition) + nComponentWidth * x;
+ }
+
+ SumFunction::assignPtr(pStackPtr, pSourcePointer);
+ SumFunction::add(nInSum, pSourcePointer);
+ SumFunction::add(nSum, nInSum);
+
+ nStackIndex++;
+ if (nStackIndex >= nDiv)
+ {
+ nStackIndex = 0;
+ }
+
+ pStackPtr = &pStack[nStackIndex * nComponentWidth];
+
+ SumFunction::add(nOutSum, pStackPtr);
+ SumFunction::sub(nInSum, pStackPtr);
+ }
+ }
+}
+
+constexpr sal_Int32 nThreadStrip = 16;
+
+void runStackBlur(Bitmap& rBitmap, const sal_Int32 nRadius, const sal_Int32 nComponentWidth,
+ const sal_Int32 nColorChannels, BlurRangeFn pBlurHorizontalFn,
+ BlurRangeFn pBlurVerticalFn, const bool bParallel)
+{
+ if (bParallel)
+ {
+ try
+ {
+ comphelper::ThreadPool& rShared = comphelper::ThreadPool::getSharedOptimalPool();
+ auto pTag = comphelper::ThreadPool::createThreadTaskTag();
+
+ {
+ BitmapScopedReadAccess pReadAccess(rBitmap);
+ BitmapScopedWriteAccess pWriteAccess(rBitmap);
+ BlurSharedData aSharedData(pReadAccess.get(), pWriteAccess.get(), nRadius,
+ nComponentWidth, nColorChannels);
+
+ const sal_Int32 nFirstIndex = 0;
+ const sal_Int32 nLastIndex = pReadAccess->Height() - 1;
+
+ vcl::bitmap::generateStripRanges<nThreadStrip>(
+ nFirstIndex, nLastIndex,
+ [&](sal_Int32 const nStart, sal_Int32 const nEnd, bool const bLast) {
+ if (!bLast)
+ {
+ auto pTask(std::make_unique<BlurTask>(pTag, pBlurHorizontalFn,
+ aSharedData, nStart, nEnd));
+ rShared.pushTask(std::move(pTask));
+ }
+ else
+ pBlurHorizontalFn(aSharedData, nStart, nEnd);
+ });
+ rShared.waitUntilDone(pTag);
+ }
+ {
+ BitmapScopedReadAccess pReadAccess(rBitmap);
+ BitmapScopedWriteAccess pWriteAccess(rBitmap);
+ BlurSharedData aSharedData(pReadAccess.get(), pWriteAccess.get(), nRadius,
+ nComponentWidth, nColorChannels);
+
+ const sal_Int32 nFirstIndex = 0;
+ const sal_Int32 nLastIndex = pReadAccess->Width() - 1;
+
+ vcl::bitmap::generateStripRanges<nThreadStrip>(
+ nFirstIndex, nLastIndex,
+ [&](sal_Int32 const nStart, sal_Int32 const nEnd, bool const bLast) {
+ if (!bLast)
+ {
+ auto pTask(std::make_unique<BlurTask>(pTag, pBlurVerticalFn,
+ aSharedData, nStart, nEnd));
+ rShared.pushTask(std::move(pTask));
+ }
+ else
+ pBlurVerticalFn(aSharedData, nStart, nEnd);
+ });
+
+ rShared.waitUntilDone(pTag);
+ }
+ }
+ catch (...)
+ {
+ SAL_WARN("vcl.gdi", "threaded bitmap blurring failed");
+ }
+ }
+ else
+ {
+ {
+ BitmapScopedReadAccess pReadAccess(rBitmap);
+ BitmapScopedWriteAccess pWriteAccess(rBitmap);
+ BlurSharedData aSharedData(pReadAccess.get(), pWriteAccess.get(), nRadius,
+ nComponentWidth, nColorChannels);
+ sal_Int32 nFirstIndex = 0;
+ sal_Int32 nLastIndex = pReadAccess->Height() - 1;
+ pBlurHorizontalFn(aSharedData, nFirstIndex, nLastIndex);
+ }
+ {
+ BitmapScopedReadAccess pReadAccess(rBitmap);
+ BitmapScopedWriteAccess pWriteAccess(rBitmap);
+ BlurSharedData aSharedData(pReadAccess.get(), pWriteAccess.get(), nRadius,
+ nComponentWidth, nColorChannels);
+ sal_Int32 nFirstIndex = 0;
+ sal_Int32 nLastIndex = pReadAccess->Width() - 1;
+ pBlurVerticalFn(aSharedData, nFirstIndex, nLastIndex);
+ }
+ }
+}
+
+void stackBlur24(Bitmap& rBitmap, sal_Int32 nRadius, sal_Int32 nComponentWidth)
+{
+ const bool bParallel = true;
+ // Limit radius
+ nRadius = std::clamp<sal_Int32>(nRadius, 2, 254);
+ const sal_Int32 nColorChannels = 3; // 3 color channel
+
+ BlurRangeFn pBlurHorizontalFn = stackBlurHorizontal<SumFunction24>;
+ BlurRangeFn pBlurVerticalFn = stackBlurVertical<SumFunction24>;
+
+ runStackBlur(rBitmap, nRadius, nComponentWidth, nColorChannels, pBlurHorizontalFn,
+ pBlurVerticalFn, bParallel);
+}
+
+void stackBlur8(Bitmap& rBitmap, sal_Int32 nRadius, sal_Int32 nComponentWidth)
+{
+ const bool bParallel = true;
+ // Limit radius
+ nRadius = std::clamp<sal_Int32>(nRadius, 2, 254);
+ const sal_Int32 nColorChannels = 1; // 1 color channel
+
+ BlurRangeFn pBlurHorizontalFn = stackBlurHorizontal<SumFunction8>;
+ BlurRangeFn pBlurVerticalFn = stackBlurVertical<SumFunction8>;
+
+ runStackBlur(rBitmap, nRadius, nComponentWidth, nColorChannels, pBlurHorizontalFn,
+ pBlurVerticalFn, bParallel);
+}
+
+} // end anonymous namespace
+
+/**
+ * Implementation of stack blur - a fast Gaussian blur approximation.
+ * nRadius - blur radius, valid values are between 2 and 254
+ * bExtend - extend the bitmap in all directions by the radius
+ *
+ * Stack Blur Algorithm by Mario Klingemann <mario@quasimondo.com>
+ * (http://www.quasimondo.com/StackBlurForCanvas/StackBlurDemo.html)
+ *
+ * Additionally references and implementations:
+ * - Blur.js by Jacob Kelley
+ * (http://www.blurjs.com)
+ * - BlurEffectForAndroidDesign by Nicolas Pomepuy
+ * (https://github.com/PomepuyN/BlurEffectForAndroidDesign)
+ * - StackBluriOS by Thomas Landspurg
+ * (https://github.com/tomsoft1/StackBluriOS)
+ * - stackblur.cpp by Benjamin Yates
+ * (https://gist.github.com/benjamin9999/3809142)
+ * - stack blur in fog 2D graphic library by Petr Kobalicek
+ * (https://code.google.com/p/fog/)
+ *
+ */
+BitmapFilterStackBlur::BitmapFilterStackBlur(sal_Int32 nRadius)
+ : mnRadius(nRadius)
+{
+}
+
+BitmapFilterStackBlur::~BitmapFilterStackBlur() {}
+
+BitmapEx BitmapFilterStackBlur::execute(BitmapEx const& rBitmapEx) const
+{
+ Bitmap aBitmap = rBitmapEx.GetBitmap();
+ Bitmap result = filter(aBitmap);
+ return BitmapEx(result, rBitmapEx.GetAlphaMask());
+}
+
+Bitmap BitmapFilterStackBlur::filter(Bitmap const& rBitmap) const
+{
+ Bitmap bitmapCopy(rBitmap);
+ ScanlineFormat nScanlineFormat;
+ {
+ BitmapScopedReadAccess pReadAccess(bitmapCopy);
+ nScanlineFormat = pReadAccess ? pReadAccess->GetScanlineFormat() : ScanlineFormat::NONE;
+ }
+
+ if (nScanlineFormat == ScanlineFormat::N24BitTcRgb
+ || nScanlineFormat == ScanlineFormat::N24BitTcBgr
+ || nScanlineFormat == ScanlineFormat::N32BitTcMask
+ || nScanlineFormat == ScanlineFormat::N32BitTcBgra)
+ {
+ int nComponentWidth = (nScanlineFormat == ScanlineFormat::N32BitTcMask
+ || nScanlineFormat == ScanlineFormat::N32BitTcBgra)
+ ? 4
+ : 3;
+
+ stackBlur24(bitmapCopy, mnRadius, nComponentWidth);
+ }
+ else if (nScanlineFormat == ScanlineFormat::N8BitPal)
+ {
+ int nComponentWidth = 1;
+
+ stackBlur8(bitmapCopy, mnRadius, nComponentWidth);
+ }
+
+ return bitmapCopy;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapGaussianSeparableBlurFilter.cxx b/vcl/source/bitmap/BitmapGaussianSeparableBlurFilter.cxx
new file mode 100644
index 0000000000..d1bc188c95
--- /dev/null
+++ b/vcl/source/bitmap/BitmapGaussianSeparableBlurFilter.cxx
@@ -0,0 +1,215 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#include <tools/helpers.hxx>
+
+#include <vcl/bitmap.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/BitmapGaussianSeparableBlurFilter.hxx>
+#include <vcl/BitmapWriteAccess.hxx>
+
+BitmapEx BitmapGaussianSeparableBlurFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ Bitmap aBitmap(rBitmapEx.GetBitmap());
+
+ const sal_Int32 nWidth = aBitmap.GetSizePixel().Width();
+ const sal_Int32 nHeight = aBitmap.GetSizePixel().Height();
+
+ // Prepare Blur Vector
+ int aNumberOfContributions;
+ std::vector<double> aBlurVector(makeBlurKernel(mfRadius, aNumberOfContributions));
+ std::vector<double> aWeights;
+ std::vector<int> aPixels;
+ std::vector<int> aCounts;
+
+ // Do horizontal filtering
+ blurContributions(nWidth, aNumberOfContributions, aBlurVector, aWeights, aPixels, aCounts);
+
+ BitmapScopedReadAccess pReadAcc(aBitmap);
+
+ // switch coordinates as convolution pass transposes result
+ Bitmap aNewBitmap(Size(nHeight, nWidth), vcl::PixelFormat::N24_BPP);
+
+ bool bResult = convolutionPass(aBitmap, aNewBitmap, pReadAcc.get(), aNumberOfContributions,
+ aWeights.data(), aPixels.data(), aCounts.data());
+
+ // Cleanup
+ pReadAcc.reset();
+ aWeights.clear();
+ aPixels.clear();
+ aCounts.clear();
+
+ if (!bResult)
+ {
+ aBlurVector.clear();
+ }
+ else
+ {
+ // Swap current bitmap with new bitmap
+ aBitmap.ReassignWithSize(aNewBitmap);
+
+ // Do vertical filtering
+ blurContributions(nHeight, aNumberOfContributions, aBlurVector, aWeights, aPixels, aCounts);
+
+ pReadAcc = aBitmap;
+ aNewBitmap = Bitmap(Size(nWidth, nHeight), vcl::PixelFormat::N24_BPP);
+ bResult = convolutionPass(aBitmap, aNewBitmap, pReadAcc.get(), aNumberOfContributions,
+ aWeights.data(), aPixels.data(), aCounts.data());
+
+ // Cleanup
+ pReadAcc.reset();
+ aWeights.clear();
+ aCounts.clear();
+ aPixels.clear();
+ aBlurVector.clear();
+
+ if (bResult)
+ aBitmap.ReassignWithSize(aNewBitmap); // swap current bitmap with new bitmap
+ }
+
+ if (bResult)
+ return BitmapEx(aBitmap);
+
+ return BitmapEx();
+}
+
+bool BitmapGaussianSeparableBlurFilter::convolutionPass(const Bitmap& rBitmap, Bitmap& aNewBitmap,
+ BitmapReadAccess const* pReadAcc,
+ int aNumberOfContributions,
+ const double* pWeights, int const* pPixels,
+ const int* pCount)
+{
+ if (!pReadAcc)
+ return false;
+
+ BitmapScopedWriteAccess pWriteAcc(aNewBitmap);
+ if (!pWriteAcc)
+ return false;
+
+ const int nHeight = rBitmap.GetSizePixel().Height();
+ assert(rBitmap.GetSizePixel().Height() == aNewBitmap.GetSizePixel().Width());
+ const int nWidth = rBitmap.GetSizePixel().Width();
+ assert(rBitmap.GetSizePixel().Width() == aNewBitmap.GetSizePixel().Height());
+
+ BitmapColor aColor;
+ double aValueRed, aValueGreen, aValueBlue;
+ double aSum, aWeight;
+ int aBaseIndex, aIndex;
+
+ for (int nSourceY = 0; nSourceY < nHeight; ++nSourceY)
+ {
+ for (int nSourceX = 0; nSourceX < nWidth; ++nSourceX)
+ {
+ aBaseIndex = nSourceX * aNumberOfContributions;
+ aSum = aValueRed = aValueGreen = aValueBlue = 0.0;
+
+ for (int j = 0; j < pCount[nSourceX]; ++j)
+ {
+ aIndex = aBaseIndex + j;
+ aWeight = pWeights[aIndex];
+ aSum += aWeight;
+
+ aColor = pReadAcc->GetColor(nSourceY, pPixels[aIndex]);
+
+ aValueRed += aWeight * aColor.GetRed();
+ aValueGreen += aWeight * aColor.GetGreen();
+ aValueBlue += aWeight * aColor.GetBlue();
+ }
+
+ BitmapColor aResultColor(
+ static_cast<sal_uInt8>(std::clamp(aValueRed / aSum, 0.0, 255.0)),
+ static_cast<sal_uInt8>(std::clamp(aValueGreen / aSum, 0.0, 255.0)),
+ static_cast<sal_uInt8>(std::clamp(aValueBlue / aSum, 0.0, 255.0)));
+
+ int nDestX = nSourceY;
+ int nDestY = nSourceX;
+
+ pWriteAcc->SetPixel(nDestY, nDestX, aResultColor);
+ }
+ }
+ return true;
+}
+
+std::vector<double> BitmapGaussianSeparableBlurFilter::makeBlurKernel(const double radius,
+ int& rows)
+{
+ int intRadius = static_cast<int>(radius + 1.0);
+ rows = intRadius * 2 + 1;
+ std::vector<double> matrix(rows);
+
+ double sigma = radius / 3;
+ double radius2 = radius * radius;
+ int index = 0;
+ for (int row = -intRadius; row <= intRadius; row++)
+ {
+ double distance = row * row;
+ if (distance > radius2)
+ {
+ matrix[index] = 0.0;
+ }
+ else
+ {
+ matrix[index] = exp(-distance / (2.0 * sigma * sigma)) / sqrt(2.0 * M_PI * sigma);
+ }
+ index++;
+ }
+ return matrix;
+}
+
+void BitmapGaussianSeparableBlurFilter::blurContributions(
+ const int aSize, const int aNumberOfContributions, const std::vector<double>& rBlurVector,
+ std::vector<double>& rWeights, std::vector<int>& rPixels, std::vector<int>& rCounts)
+{
+ rWeights.resize(aSize * aNumberOfContributions);
+ rPixels.resize(aSize * aNumberOfContributions);
+ rCounts.resize(aSize);
+
+ int aLeft, aRight, aCurrentCount, aPixelIndex;
+ double aWeight;
+
+ for (int i = 0; i < aSize; i++)
+ {
+ aLeft = i - aNumberOfContributions / 2;
+ aRight = i + aNumberOfContributions / 2;
+ aCurrentCount = 0;
+ for (int j = aLeft; j <= aRight; j++)
+ {
+ aWeight = rBlurVector[aCurrentCount];
+
+ // Mirror edges
+ if (j < 0)
+ {
+ aPixelIndex = -j;
+ }
+ else if (j >= aSize)
+ {
+ aPixelIndex = (aSize - j) + aSize - 1;
+ }
+ else
+ {
+ aPixelIndex = j;
+ }
+
+ // Edge case for small bitmaps
+ if (aPixelIndex < 0 || aPixelIndex >= aSize)
+ {
+ aWeight = 0.0;
+ }
+
+ rWeights[i * aNumberOfContributions + aCurrentCount] = aWeight;
+ rPixels[i * aNumberOfContributions + aCurrentCount] = aPixelIndex;
+
+ aCurrentCount++;
+ }
+ rCounts[i] = aCurrentCount;
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapInfoAccess.cxx b/vcl/source/bitmap/BitmapInfoAccess.cxx
new file mode 100644
index 0000000000..817d5187c3
--- /dev/null
+++ b/vcl/source/bitmap/BitmapInfoAccess.cxx
@@ -0,0 +1,91 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <vcl/BitmapInfoAccess.hxx>
+
+#include <salbmp.hxx>
+#include <svdata.hxx>
+#include <salinst.hxx>
+
+BitmapInfoAccess::BitmapInfoAccess(const AlphaMask& rBitmap, BitmapAccessMode nMode)
+ : BitmapInfoAccess(rBitmap.GetBitmap(), nMode)
+{
+}
+
+BitmapInfoAccess::BitmapInfoAccess(const Bitmap& rBitmap, BitmapAccessMode nMode)
+ : mpBuffer(nullptr)
+ , mnAccessMode(nMode)
+{
+ std::shared_ptr<SalBitmap> xImpBmp = rBitmap.ImplGetSalBitmap();
+
+ if (!xImpBmp)
+ return;
+
+ if (mnAccessMode == BitmapAccessMode::Write)
+ {
+ xImpBmp->DropScaledCache();
+
+ if (xImpBmp.use_count() > 2)
+ {
+ xImpBmp.reset();
+ const_cast<Bitmap&>(rBitmap).ImplMakeUnique();
+ xImpBmp = rBitmap.ImplGetSalBitmap();
+ }
+ }
+
+ mpBuffer = xImpBmp->AcquireBuffer(mnAccessMode);
+
+ if (!mpBuffer)
+ {
+ std::shared_ptr<SalBitmap> xNewImpBmp(ImplGetSVData()->mpDefInst->CreateSalBitmap());
+ if (xNewImpBmp->Create(*xImpBmp, rBitmap.getPixelFormat()))
+ {
+ xImpBmp = xNewImpBmp;
+ const_cast<Bitmap&>(rBitmap).ImplSetSalBitmap(xImpBmp);
+ mpBuffer = xImpBmp->AcquireBuffer(mnAccessMode);
+ }
+ }
+
+ maBitmap = rBitmap;
+}
+
+BitmapInfoAccess::~BitmapInfoAccess()
+{
+ std::shared_ptr<SalBitmap> xImpBmp = maBitmap.ImplGetSalBitmap();
+
+ if (mpBuffer && xImpBmp)
+ {
+ xImpBmp->ReleaseBuffer(mpBuffer, mnAccessMode);
+ }
+}
+
+sal_uInt16 BitmapInfoAccess::GetBestPaletteIndex(const BitmapColor& rBitmapColor) const
+{
+ const BitmapBuffer* pBuffer = mpBuffer;
+
+ 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: */
diff --git a/vcl/source/bitmap/BitmapInterpolateScaleFilter.cxx b/vcl/source/bitmap/BitmapInterpolateScaleFilter.cxx
new file mode 100644
index 0000000000..c0c866b53d
--- /dev/null
+++ b/vcl/source/bitmap/BitmapInterpolateScaleFilter.cxx
@@ -0,0 +1,245 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <tools/helpers.hxx>
+#include <osl/diagnose.h>
+
+#include <vcl/bitmapex.hxx>
+
+#include <vcl/BitmapWriteAccess.hxx>
+#include <bitmap/BitmapFastScaleFilter.hxx>
+#include <bitmap/BitmapInterpolateScaleFilter.hxx>
+
+BitmapEx BitmapInterpolateScaleFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ Bitmap aBitmap(rBitmapEx.GetBitmap());
+
+ const Size aSizePix(aBitmap.GetSizePixel());
+ const sal_Int32 nNewWidth = FRound(aSizePix.Width() * mfScaleX);
+ const sal_Int32 nNewHeight = FRound(aSizePix.Height() * mfScaleY);
+ bool bRet = false;
+
+ if ((nNewWidth > 1) && (nNewHeight > 1))
+ {
+ BitmapScopedReadAccess pReadAcc(aBitmap);
+ if (pReadAcc)
+ {
+ sal_Int32 nWidth = pReadAcc->Width();
+ sal_Int32 nHeight = pReadAcc->Height();
+ Bitmap aNewBmp(Size(nNewWidth, nHeight), vcl::PixelFormat::N24_BPP);
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+
+ if (pWriteAcc)
+ {
+ if (1 == nWidth)
+ {
+ for (sal_Int32 nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanlineRead = pReadAcc->GetScanline(nY);
+ BitmapColor aCol0;
+ if (pReadAcc->HasPalette())
+ {
+ aCol0 = pReadAcc->GetPaletteColor(
+ pReadAcc->GetIndexFromData(pScanlineRead, 0));
+ }
+ else
+ {
+ aCol0 = pReadAcc->GetPixelFromData(pScanlineRead, 0);
+ }
+
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ for (sal_Int32 nX = 0; nX < nNewWidth; nX++)
+ {
+ pWriteAcc->SetPixelOnData(pScanline, nX, aCol0);
+ }
+ }
+ }
+ else
+ {
+ const sal_Int32 nNewWidth1 = nNewWidth - 1;
+ const sal_Int32 nWidth1 = pReadAcc->Width() - 1;
+ const double fRevScaleX = static_cast<double>(nWidth1) / nNewWidth1;
+
+ std::unique_ptr<sal_Int32[]> pLutInt(new sal_Int32[nNewWidth]);
+ std::unique_ptr<sal_Int32[]> pLutFrac(new sal_Int32[nNewWidth]);
+
+ for (sal_Int32 nX = 0, nTemp = nWidth - 2; nX < nNewWidth; nX++)
+ {
+ double fTemp = nX * fRevScaleX;
+ pLutInt[nX]
+ = std::clamp(static_cast<sal_Int32>(fTemp), sal_Int32(0), nTemp);
+ fTemp -= pLutInt[nX];
+ pLutFrac[nX] = static_cast<sal_Int32>(fTemp * 1024.);
+ }
+
+ for (sal_Int32 nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanlineRead = pReadAcc->GetScanline(nY);
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ for (sal_Int32 nX = 0; nX < nNewWidth; nX++)
+ {
+ sal_Int32 nTemp = pLutInt[nX];
+
+ BitmapColor aCol0, aCol1;
+ if (pReadAcc->HasPalette())
+ {
+ aCol0 = pReadAcc->GetPaletteColor(
+ pReadAcc->GetIndexFromData(pScanlineRead, nTemp++));
+ aCol1 = pReadAcc->GetPaletteColor(
+ pReadAcc->GetIndexFromData(pScanlineRead, nTemp));
+ }
+ else
+ {
+ aCol0 = pReadAcc->GetPixelFromData(pScanlineRead, nTemp++);
+ aCol1 = pReadAcc->GetPixelFromData(pScanlineRead, nTemp);
+ }
+
+ nTemp = pLutFrac[nX];
+
+ sal_Int32 lXR0 = aCol0.GetRed();
+ sal_Int32 lXG0 = aCol0.GetGreen();
+ sal_Int32 lXB0 = aCol0.GetBlue();
+ sal_Int32 lXR1 = aCol1.GetRed() - lXR0;
+ sal_Int32 lXG1 = aCol1.GetGreen() - lXG0;
+ sal_Int32 lXB1 = aCol1.GetBlue() - lXB0;
+
+ aCol0.SetRed(
+ static_cast<sal_uInt8>((lXR1 * nTemp + (lXR0 << 10)) >> 10));
+ aCol0.SetGreen(
+ static_cast<sal_uInt8>((lXG1 * nTemp + (lXG0 << 10)) >> 10));
+ aCol0.SetBlue(
+ static_cast<sal_uInt8>((lXB1 * nTemp + (lXB0 << 10)) >> 10));
+
+ pWriteAcc->SetPixelOnData(pScanline, nX, aCol0);
+ }
+ }
+ }
+
+ bRet = true;
+ }
+
+ pReadAcc.reset();
+ pWriteAcc.reset();
+
+ if (bRet)
+ {
+ bRet = false;
+ const Bitmap aOriginal(aBitmap);
+ aBitmap = aNewBmp;
+ aNewBmp = Bitmap(Size(nNewWidth, nNewHeight), vcl::PixelFormat::N24_BPP);
+ pReadAcc = aBitmap;
+ pWriteAcc = aNewBmp;
+
+ if (pReadAcc && pWriteAcc)
+ {
+ // after 1st step, bitmap *is* 24bit format (see above)
+ OSL_ENSURE(!pReadAcc->HasPalette(), "OOps, somehow ImplScaleInterpolate "
+ "in-between format has palette, should not "
+ "happen (!)");
+
+ if (1 == nHeight)
+ {
+ for (sal_Int32 nX = 0; nX < nNewWidth; nX++)
+ {
+ BitmapColor aCol0 = pReadAcc->GetPixel(0, nX);
+
+ for (sal_Int32 nY = 0; nY < nNewHeight; nY++)
+ {
+ pWriteAcc->SetPixel(nY, nX, aCol0);
+ }
+ }
+ }
+ else
+ {
+ const sal_Int32 nNewHeight1 = nNewHeight - 1;
+ const sal_Int32 nHeight1 = pReadAcc->Height() - 1;
+ const double fRevScaleY = static_cast<double>(nHeight1) / nNewHeight1;
+
+ std::unique_ptr<sal_Int32[]> pLutInt(new sal_Int32[nNewHeight]);
+ std::unique_ptr<sal_Int32[]> pLutFrac(new sal_Int32[nNewHeight]);
+
+ for (sal_Int32 nY = 0, nTemp = nHeight - 2; nY < nNewHeight; nY++)
+ {
+ double fTemp = nY * fRevScaleY;
+ pLutInt[nY]
+ = std::clamp(static_cast<sal_Int32>(fTemp), sal_Int32(0), nTemp);
+ fTemp -= pLutInt[nY];
+ pLutFrac[nY] = static_cast<sal_Int32>(fTemp * 1024.);
+ }
+
+ for (sal_Int32 nX = 0; nX < nNewWidth; nX++)
+ {
+ for (sal_Int32 nY = 0; nY < nNewHeight; nY++)
+ {
+ sal_Int32 nTemp = pLutInt[nY];
+
+ BitmapColor aCol0 = pReadAcc->GetPixel(nTemp++, nX);
+ BitmapColor aCol1 = pReadAcc->GetPixel(nTemp, nX);
+
+ nTemp = pLutFrac[nY];
+
+ sal_Int32 lXR0 = aCol0.GetRed();
+ sal_Int32 lXG0 = aCol0.GetGreen();
+ sal_Int32 lXB0 = aCol0.GetBlue();
+ sal_Int32 lXR1 = aCol1.GetRed() - lXR0;
+ sal_Int32 lXG1 = aCol1.GetGreen() - lXG0;
+ sal_Int32 lXB1 = aCol1.GetBlue() - lXB0;
+
+ aCol0.SetRed(
+ static_cast<sal_uInt8>((lXR1 * nTemp + (lXR0 << 10)) >> 10));
+ aCol0.SetGreen(
+ static_cast<sal_uInt8>((lXG1 * nTemp + (lXG0 << 10)) >> 10));
+ aCol0.SetBlue(
+ static_cast<sal_uInt8>((lXB1 * nTemp + (lXB0 << 10)) >> 10));
+
+ pWriteAcc->SetPixel(nY, nX, aCol0);
+ }
+ }
+ }
+
+ bRet = true;
+ }
+
+ pReadAcc.reset();
+ pWriteAcc.reset();
+
+ if (bRet)
+ {
+ aOriginal.AdaptBitCount(aNewBmp);
+ aBitmap = aNewBmp;
+ }
+ }
+ }
+ }
+
+ if (!bRet)
+ {
+ // fallback to fast scale filter
+ BitmapEx aBmpEx(aBitmap);
+ bRet = BitmapFilter::Filter(aBmpEx, BitmapFastScaleFilter(mfScaleX, mfScaleY));
+ aBitmap = aBmpEx.GetBitmap();
+ }
+
+ if (bRet)
+ return BitmapEx(aBitmap);
+
+ return BitmapEx();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapLightenFilter.cxx b/vcl/source/bitmap/BitmapLightenFilter.cxx
new file mode 100644
index 0000000000..a5c4f71e63
--- /dev/null
+++ b/vcl/source/bitmap/BitmapLightenFilter.cxx
@@ -0,0 +1,67 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#include <basegfx/color/bcolortools.hxx>
+
+#include <vcl/BitmapWriteAccess.hxx>
+#include <bitmap/BitmapLightenFilter.hxx>
+
+BitmapEx BitmapLightenFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ const Size aSize(rBitmapEx.GetSizePixel());
+
+ Bitmap aBitmap(rBitmapEx.GetBitmap());
+ Bitmap aDarkBitmap(aSize, vcl::PixelFormat::N24_BPP);
+
+ BitmapScopedReadAccess pRead(aBitmap);
+ BitmapScopedWriteAccess pWrite(aDarkBitmap);
+
+ if (pRead && pWrite)
+ {
+ for (sal_Int32 nY = 0; nY < sal_Int32(aSize.Height()); ++nY)
+ {
+ Scanline pScanline = pWrite->GetScanline(nY);
+ Scanline pScanlineRead = pRead->GetScanline(nY);
+ for (sal_Int32 nX = 0; nX < sal_Int32(aSize.Width()); ++nX)
+ {
+ BitmapColor aBmpColor
+ = pRead->HasPalette()
+ ? pRead->GetPaletteColor(pRead->GetIndexFromData(pScanlineRead, nX))
+ : pRead->GetPixelFromData(pScanlineRead, nX);
+ aBmpColor.Invert();
+ basegfx::BColor aBColor(aBmpColor.getBColor());
+ aBColor = basegfx::utils::rgb2hsl(aBColor);
+
+ double fHue = aBColor.getRed();
+ fHue += 180.0;
+
+ while (fHue > 360.0)
+ {
+ fHue -= 360.0;
+ }
+
+ aBColor.setRed(fHue);
+
+ aBColor = basegfx::utils::hsl2rgb(aBColor);
+ aBmpColor.SetRed((aBColor.getRed() * 255.0) + 0.5);
+ aBmpColor.SetGreen((aBColor.getGreen() * 255.0) + 0.5);
+ aBmpColor.SetBlue((aBColor.getBlue() * 255.0) + 0.5);
+
+ pWrite->SetPixelOnData(pScanline, nX, aBmpColor);
+ }
+ }
+ }
+ pWrite.reset();
+ pRead.reset();
+
+ return BitmapEx(aDarkBitmap, rBitmapEx.GetAlphaMask());
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapMaskToAlphaFilter.cxx b/vcl/source/bitmap/BitmapMaskToAlphaFilter.cxx
new file mode 100644
index 0000000000..acae8f516e
--- /dev/null
+++ b/vcl/source/bitmap/BitmapMaskToAlphaFilter.cxx
@@ -0,0 +1,58 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#include <basegfx/color/bcolortools.hxx>
+
+#include <vcl/BitmapWriteAccess.hxx>
+#include <bitmap/BitmapMaskToAlphaFilter.hxx>
+
+/**
+ * Convert a 1-bit mask to an alpha bitmap
+ */
+BitmapEx BitmapMaskToAlphaFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ const Size aSize(rBitmapEx.GetSizePixel());
+
+ Bitmap aBitmap(rBitmapEx.GetBitmap());
+ Bitmap aOutBitmap(aSize, vcl::PixelFormat::N8_BPP, &Bitmap::GetGreyPalette(256));
+
+ BitmapScopedReadAccess pRead(aBitmap);
+ BitmapScopedWriteAccess pWrite(aOutBitmap);
+
+ if (pRead && pWrite)
+ {
+ assert(pRead->HasPalette() && "only supposed to be called with 1-bit mask");
+ assert(pRead->GetPaletteEntryCount() == 2);
+ for (sal_Int32 nY = 0; nY < sal_Int32(aSize.Height()); ++nY)
+ {
+ Scanline pScanline = pWrite->GetScanline(nY);
+ Scanline pScanlineRead = pRead->GetScanline(nY);
+ for (sal_Int32 nX = 0; nX < sal_Int32(aSize.Width()); ++nX)
+ {
+ BitmapColor aBmpColor = pRead->GetPixelFromData(pScanlineRead, nX);
+ if (aBmpColor == COL_BLACK)
+ aBmpColor = COL_ALPHA_OPAQUE;
+ else if (aBmpColor == COL_WHITE)
+ aBmpColor = COL_ALPHA_TRANSPARENT;
+ else if (aBmpColor == Color(0, 0, 1))
+ aBmpColor = COL_ALPHA_TRANSPARENT;
+ else
+ assert(false);
+ pWrite->SetPixelOnData(pScanline, nX, aBmpColor);
+ }
+ }
+ }
+ pWrite.reset();
+ pRead.reset();
+
+ return BitmapEx(aOutBitmap);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapMedianFilter.cxx b/vcl/source/bitmap/BitmapMedianFilter.cxx
new file mode 100644
index 0000000000..222451d0a9
--- /dev/null
+++ b/vcl/source/bitmap/BitmapMedianFilter.cxx
@@ -0,0 +1,204 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#include <vcl/bitmap.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/BitmapMedianFilter.hxx>
+#include <vcl/BitmapWriteAccess.hxx>
+
+#define S2(a, b) \
+ { \
+ sal_Int32 t; \
+ if ((t = b - a) < 0) \
+ { \
+ a += t; \
+ b -= t; \
+ } \
+ }
+#define MN3(a, b, c) \
+ S2(a, b); \
+ S2(a, c);
+#define MX3(a, b, c) \
+ S2(b, c); \
+ S2(a, c);
+#define MNMX3(a, b, c) \
+ MX3(a, b, c); \
+ S2(a, b);
+#define MNMX4(a, b, c, d) \
+ S2(a, b); \
+ S2(c, d); \
+ S2(a, c); \
+ S2(b, d);
+#define MNMX5(a, b, c, d, e) \
+ S2(a, b); \
+ S2(c, d); \
+ MN3(a, c, e); \
+ MX3(b, d, e);
+#define MNMX6(a, b, c, d, e, f) \
+ S2(a, d); \
+ S2(b, e); \
+ S2(c, f); \
+ MN3(a, b, c); \
+ MX3(d, e, f);
+
+BitmapEx BitmapMedianFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ Bitmap aBitmap(rBitmapEx.GetBitmap());
+
+ BitmapScopedReadAccess pReadAcc(aBitmap);
+ if (!pReadAcc)
+ return BitmapEx();
+
+ Bitmap aNewBmp(aBitmap.GetSizePixel(), vcl::PixelFormat::N24_BPP);
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+ if (!pWriteAcc)
+ return BitmapEx();
+
+ const sal_Int32 nWidth = pWriteAcc->Width(), nWidth2 = nWidth + 2;
+ const sal_Int32 nHeight = pWriteAcc->Height(), nHeight2 = nHeight + 2;
+ std::unique_ptr<sal_Int32[]> pColm(new sal_Int32[nWidth2]);
+ std::unique_ptr<sal_Int32[]> pRows(new sal_Int32[nHeight2]);
+ std::unique_ptr<BitmapColor[]> pColRow1(new BitmapColor[nWidth2]);
+ std::unique_ptr<BitmapColor[]> pColRow2(new BitmapColor[nWidth2]);
+ std::unique_ptr<BitmapColor[]> pColRow3(new BitmapColor[nWidth2]);
+ BitmapColor* pRowTmp1 = pColRow1.get();
+ BitmapColor* pRowTmp2 = pColRow2.get();
+ BitmapColor* pRowTmp3 = pColRow3.get();
+ BitmapColor* pColor;
+ sal_Int32 nY, nX, i;
+ sal_Int32 nR1, nR2, nR3, nR4, nR5, nR6, nR7, nR8, nR9;
+ sal_Int32 nG1, nG2, nG3, nG4, nG5, nG6, nG7, nG8, nG9;
+ sal_Int32 nB1, nB2, nB3, nB4, nB5, nB6, nB7, nB8, nB9;
+
+ // create column LUT
+ for (i = 0; i < nWidth2; i++)
+ pColm[i] = (i > 0) ? (i - 1) : 0;
+
+ pColm[nWidth + 1] = pColm[nWidth];
+
+ // create row LUT
+ for (i = 0; i < nHeight2; i++)
+ pRows[i] = (i > 0) ? (i - 1) : 0;
+
+ pRows[nHeight + 1] = pRows[nHeight];
+
+ // read first three rows of bitmap color
+ if (nHeight2 > 2)
+ {
+ for (i = 0; i < nWidth2; i++)
+ {
+ pColRow1[i] = pReadAcc->GetColor(pRows[0], pColm[i]);
+ pColRow2[i] = pReadAcc->GetColor(pRows[1], pColm[i]);
+ pColRow3[i] = pReadAcc->GetColor(pRows[2], pColm[i]);
+ }
+ }
+
+ // do median filtering
+ for (nY = 0; nY < nHeight;)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ for (nX = 0; nX < nWidth; nX++)
+ {
+ pColor = pRowTmp1 + nX;
+ nR1 = pColor->GetRed();
+ nG1 = pColor->GetGreen();
+ nB1 = pColor->GetBlue();
+ nR2 = (++pColor)->GetRed();
+ nG2 = pColor->GetGreen();
+ nB2 = pColor->GetBlue();
+ nR3 = (++pColor)->GetRed();
+ nG3 = pColor->GetGreen();
+ nB3 = pColor->GetBlue();
+
+ pColor = pRowTmp2 + nX;
+ nR4 = pColor->GetRed();
+ nG4 = pColor->GetGreen();
+ nB4 = pColor->GetBlue();
+ nR5 = (++pColor)->GetRed();
+ nG5 = pColor->GetGreen();
+ nB5 = pColor->GetBlue();
+ nR6 = (++pColor)->GetRed();
+ nG6 = pColor->GetGreen();
+ nB6 = pColor->GetBlue();
+
+ pColor = pRowTmp3 + nX;
+ nR7 = pColor->GetRed();
+ nG7 = pColor->GetGreen();
+ nB7 = pColor->GetBlue();
+ nR8 = (++pColor)->GetRed();
+ nG8 = pColor->GetGreen();
+ nB8 = pColor->GetBlue();
+ nR9 = (++pColor)->GetRed();
+ nG9 = pColor->GetGreen();
+ nB9 = pColor->GetBlue();
+
+ MNMX6(nR1, nR2, nR3, nR4, nR5, nR6);
+ MNMX5(nR7, nR2, nR3, nR4, nR5);
+ MNMX4(nR8, nR2, nR3, nR4);
+ MNMX3(nR9, nR2, nR3);
+
+ MNMX6(nG1, nG2, nG3, nG4, nG5, nG6);
+ MNMX5(nG7, nG2, nG3, nG4, nG5);
+ MNMX4(nG8, nG2, nG3, nG4);
+ MNMX3(nG9, nG2, nG3);
+
+ MNMX6(nB1, nB2, nB3, nB4, nB5, nB6);
+ MNMX5(nB7, nB2, nB3, nB4, nB5);
+ MNMX4(nB8, nB2, nB3, nB4);
+ MNMX3(nB9, nB2, nB3);
+
+ // set destination color
+ pWriteAcc->SetPixelOnData(pScanline, nX,
+ BitmapColor(static_cast<sal_uInt8>(nR2),
+ static_cast<sal_uInt8>(nG2),
+ static_cast<sal_uInt8>(nB2)));
+ }
+
+ if (++nY < nHeight)
+ {
+ if (pRowTmp1 == pColRow1.get())
+ {
+ pRowTmp1 = pColRow2.get();
+ pRowTmp2 = pColRow3.get();
+ pRowTmp3 = pColRow1.get();
+ }
+ else if (pRowTmp1 == pColRow2.get())
+ {
+ pRowTmp1 = pColRow3.get();
+ pRowTmp2 = pColRow1.get();
+ pRowTmp3 = pColRow2.get();
+ }
+ else
+ {
+ pRowTmp1 = pColRow1.get();
+ pRowTmp2 = pColRow2.get();
+ pRowTmp3 = pColRow3.get();
+ }
+
+ for (i = 0; i < nWidth2; i++)
+ pRowTmp3[i] = pReadAcc->GetColor(pRows[nY + 2], pColm[i]);
+ }
+ }
+
+ pWriteAcc.reset();
+ pReadAcc.reset();
+
+ const MapMode aMap(aBitmap.GetPrefMapMode());
+ const Size aPrefSize(aBitmap.GetPrefSize());
+
+ aBitmap = aNewBmp;
+
+ aBitmap.SetPrefMapMode(aMap);
+ aBitmap.SetPrefSize(aPrefSize);
+
+ return BitmapEx(aBitmap);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapMonochromeFilter.cxx b/vcl/source/bitmap/BitmapMonochromeFilter.cxx
new file mode 100644
index 0000000000..00f7b99ef1
--- /dev/null
+++ b/vcl/source/bitmap/BitmapMonochromeFilter.cxx
@@ -0,0 +1,87 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#include <vcl/bitmap.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/BitmapMonochromeFilter.hxx>
+#include <vcl/BitmapWriteAccess.hxx>
+
+BitmapEx BitmapMonochromeFilter::execute(BitmapEx const& aBitmapEx) const
+{
+ Bitmap aBitmap = aBitmapEx.GetBitmap();
+ BitmapScopedReadAccess pReadAcc(aBitmap);
+ if (!pReadAcc)
+ return BitmapEx();
+
+ Bitmap aNewBmp(aBitmap.GetSizePixel(), vcl::PixelFormat::N8_BPP, &Bitmap::GetGreyPalette(256));
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+ if (!pWriteAcc)
+ return BitmapEx();
+
+ const BitmapColor aBlack(pWriteAcc->GetBestMatchingColor(COL_BLACK));
+ const BitmapColor aWhite(pWriteAcc->GetBestMatchingColor(COL_WHITE));
+ const sal_Int32 nWidth = pWriteAcc->Width();
+ const sal_Int32 nHeight = pWriteAcc->Height();
+
+ if (pReadAcc->HasPalette())
+ {
+ for (sal_Int32 nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ Scanline pScanlineRead = pReadAcc->GetScanline(nY);
+ for (sal_Int32 nX = 0; nX < nWidth; nX++)
+ {
+ const sal_uInt8 cIndex = pReadAcc->GetIndexFromData(pScanlineRead, nX);
+ if (pReadAcc->GetPaletteColor(cIndex).GetLuminance() >= mcThreshold)
+ {
+ pWriteAcc->SetPixelOnData(pScanline, nX, aWhite);
+ }
+ else
+ {
+ pWriteAcc->SetPixelOnData(pScanline, nX, aBlack);
+ }
+ }
+ }
+ }
+ else
+ {
+ for (sal_Int32 nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ Scanline pScanlineRead = pReadAcc->GetScanline(nY);
+ for (sal_Int32 nX = 0; nX < nWidth; nX++)
+ {
+ if (pReadAcc->GetPixelFromData(pScanlineRead, nX).GetLuminance() >= mcThreshold)
+ {
+ pWriteAcc->SetPixelOnData(pScanline, nX, aWhite);
+ }
+ else
+ {
+ pWriteAcc->SetPixelOnData(pScanline, nX, aBlack);
+ }
+ }
+ }
+ }
+
+ pWriteAcc.reset();
+ pReadAcc.reset();
+
+ const MapMode aMap(aBitmap.GetPrefMapMode());
+ const Size aSize(aBitmap.GetPrefSize());
+
+ aBitmap = aNewBmp;
+
+ aBitmap.SetPrefMapMode(aMap);
+ aBitmap.SetPrefSize(aSize);
+
+ return BitmapEx(aBitmap);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapMosaicFilter.cxx b/vcl/source/bitmap/BitmapMosaicFilter.cxx
new file mode 100644
index 0000000000..0f89b23b9a
--- /dev/null
+++ b/vcl/source/bitmap/BitmapMosaicFilter.cxx
@@ -0,0 +1,183 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#include <vcl/bitmap.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/BitmapMosaicFilter.hxx>
+#include <vcl/BitmapWriteAccess.hxx>
+
+BitmapEx BitmapMosaicFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ Bitmap aBitmap(rBitmapEx.GetBitmap());
+
+ bool bRet = false;
+
+ if (mnTileWidth > 1 || mnTileHeight > 1)
+ {
+ std::optional<Bitmap> pNewBmp;
+ BitmapScopedReadAccess pReadAcc;
+ BitmapScopedWriteAccess pWriteAcc;
+
+ if (!isPalettePixelFormat(aBitmap.getPixelFormat()))
+ {
+ pReadAcc = aBitmap;
+ pWriteAcc = aBitmap;
+ }
+ else
+ {
+ pNewBmp.emplace(aBitmap.GetSizePixel(), vcl::PixelFormat::N24_BPP);
+ pReadAcc = aBitmap;
+ pWriteAcc = *pNewBmp;
+ }
+
+ bool bConditionsMet = false;
+ sal_Int32 nWidth(0);
+ sal_Int32 nHeight(0);
+ if (pReadAcc && pWriteAcc)
+ {
+ nWidth = pReadAcc->Width();
+ nHeight = pReadAcc->Height();
+ bConditionsMet = (nWidth > 0 && nHeight > 0);
+ }
+
+ if (bConditionsMet)
+ {
+ BitmapColor aCol;
+ sal_Int32 nX, nY, nX1, nX2, nY1, nY2, nSumR, nSumG, nSumB;
+ double fArea_1;
+
+ nY1 = 0;
+ nY2 = mnTileHeight - 1;
+
+ if (nY2 >= nHeight)
+ nY2 = nHeight - 1;
+
+ do
+ {
+ nX1 = 0;
+ nX2 = mnTileWidth - 1;
+
+ if (nX2 >= nWidth)
+ nX2 = nWidth - 1;
+
+ fArea_1 = 1.0 / ((nX2 - nX1 + 1) * (nY2 - nY1 + 1));
+
+ if (!pNewBmp)
+ {
+ do
+ {
+ for (nY = nY1, nSumR = nSumG = nSumB = 0; nY <= nY2; nY++)
+ {
+ Scanline pScanlineRead = pReadAcc->GetScanline(nY);
+ for (nX = nX1; nX <= nX2; nX++)
+ {
+ aCol = pReadAcc->GetPixelFromData(pScanlineRead, nX);
+ nSumR += aCol.GetRed();
+ nSumG += aCol.GetGreen();
+ nSumB += aCol.GetBlue();
+ }
+ }
+
+ aCol.SetRed(static_cast<sal_uInt8>(nSumR * fArea_1));
+ aCol.SetGreen(static_cast<sal_uInt8>(nSumG * fArea_1));
+ aCol.SetBlue(static_cast<sal_uInt8>(nSumB * fArea_1));
+
+ for (nY = nY1; nY <= nY2; nY++)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ for (nX = nX1; nX <= nX2; nX++)
+ pWriteAcc->SetPixelOnData(pScanline, nX, aCol);
+ }
+
+ nX1 += mnTileWidth;
+ nX2 += mnTileWidth;
+
+ if (nX2 >= nWidth)
+ {
+ nX2 = nWidth - 1;
+ fArea_1 = 1.0 / ((nX2 - nX1 + 1) * (nY2 - nY1 + 1));
+ }
+ } while (nX1 < nWidth);
+ }
+ else
+ {
+ do
+ {
+ for (nY = nY1, nSumR = nSumG = nSumB = 0; nY <= nY2; nY++)
+ {
+ Scanline pScanlineRead = pReadAcc->GetScanline(nY);
+ for (nX = nX1; nX <= nX2; nX++)
+ {
+ const BitmapColor& rCol = pReadAcc->GetPaletteColor(
+ pReadAcc->GetIndexFromData(pScanlineRead, nX));
+ nSumR += rCol.GetRed();
+ nSumG += rCol.GetGreen();
+ nSumB += rCol.GetBlue();
+ }
+ }
+
+ aCol.SetRed(static_cast<sal_uInt8>(nSumR * fArea_1));
+ aCol.SetGreen(static_cast<sal_uInt8>(nSumG * fArea_1));
+ aCol.SetBlue(static_cast<sal_uInt8>(nSumB * fArea_1));
+
+ for (nY = nY1; nY <= nY2; nY++)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ for (nX = nX1; nX <= nX2; nX++)
+ pWriteAcc->SetPixelOnData(pScanline, nX, aCol);
+ }
+
+ nX1 += mnTileWidth;
+ nX2 += mnTileWidth;
+
+ if (nX2 >= nWidth)
+ {
+ nX2 = nWidth - 1;
+ fArea_1 = 1.0 / ((nX2 - nX1 + 1) * (nY2 - nY1 + 1));
+ }
+ } while (nX1 < nWidth);
+ }
+
+ nY1 += mnTileHeight;
+ nY2 += mnTileHeight;
+
+ if (nY2 >= nHeight)
+ nY2 = nHeight - 1;
+
+ } while (nY1 < nHeight);
+
+ bRet = true;
+ }
+
+ pReadAcc.reset();
+ pWriteAcc.reset();
+
+ if (pNewBmp)
+ {
+ if (bRet)
+ {
+ const MapMode aMap(aBitmap.GetPrefMapMode());
+ const Size aPrefSize(aBitmap.GetPrefSize());
+
+ aBitmap = *pNewBmp;
+
+ aBitmap.SetPrefMapMode(aMap);
+ aBitmap.SetPrefSize(aPrefSize);
+ }
+ }
+ }
+
+ if (bRet)
+ return BitmapEx(aBitmap);
+
+ return BitmapEx();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapPopArtFilter.cxx b/vcl/source/bitmap/BitmapPopArtFilter.cxx
new file mode 100644
index 0000000000..ee2cf716c9
--- /dev/null
+++ b/vcl/source/bitmap/BitmapPopArtFilter.cxx
@@ -0,0 +1,96 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#include <tools/solar.h>
+#include <vcl/bitmap.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/BitmapPopArtFilter.hxx>
+#include <vcl/BitmapWriteAccess.hxx>
+
+BitmapEx BitmapPopArtFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ Bitmap aBitmap(rBitmapEx.GetBitmap());
+
+ bool bRet = isPalettePixelFormat(aBitmap.getPixelFormat())
+ || aBitmap.Convert(BmpConversion::N8BitColors);
+
+ if (bRet)
+ {
+ bRet = false;
+
+ BitmapScopedWriteAccess pWriteAcc(aBitmap);
+
+ if (pWriteAcc)
+ {
+ const sal_Int32 nWidth = pWriteAcc->Width();
+ const sal_Int32 nHeight = pWriteAcc->Height();
+ const sal_uInt16 nEntryCount = 1 << pWriteAcc->GetBitCount();
+ sal_uInt16 n = 0;
+ std::vector<PopArtEntry> aPopArtTable(nEntryCount);
+
+ for (n = 0; n < nEntryCount; n++)
+ {
+ PopArtEntry& rEntry = aPopArtTable[n];
+ rEntry.mnIndex = n;
+ rEntry.mnCount = 0;
+ }
+
+ // get pixel count for each palette entry
+ for (sal_Int32 nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ for (sal_Int32 nX = 0; nX < nWidth; nX++)
+ {
+ aPopArtTable[pWriteAcc->GetIndexFromData(pScanline, nX)].mnCount++;
+ assert(aPopArtTable[pWriteAcc->GetIndexFromData(pScanline, nX)].mnCount != 0);
+ }
+ }
+
+ // sort table
+ std::sort(aPopArtTable.begin(), aPopArtTable.end(),
+ [](const PopArtEntry& lhs, const PopArtEntry& rhs) {
+ return lhs.mnCount > rhs.mnCount;
+ });
+
+ // get last used entry
+ sal_uInt16 nFirstEntry;
+ sal_uInt16 nLastEntry = 0;
+
+ for (n = 0; n < nEntryCount; n++)
+ {
+ if (aPopArtTable[n].mnCount)
+ nLastEntry = n;
+ }
+
+ // rotate palette (one entry)
+ const BitmapColor aFirstCol(pWriteAcc->GetPaletteColor(aPopArtTable[0].mnIndex));
+
+ for (nFirstEntry = 0; nFirstEntry < nLastEntry; nFirstEntry++)
+ {
+ pWriteAcc->SetPaletteColor(
+ aPopArtTable[nFirstEntry].mnIndex,
+ pWriteAcc->GetPaletteColor(aPopArtTable[nFirstEntry + 1].mnIndex));
+ }
+
+ pWriteAcc->SetPaletteColor(aPopArtTable[nLastEntry].mnIndex, aFirstCol);
+
+ // cleanup
+ pWriteAcc.reset();
+ bRet = true;
+ }
+ }
+
+ if (bRet)
+ return BitmapEx(aBitmap);
+
+ return BitmapEx();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapReadAccess.cxx b/vcl/source/bitmap/BitmapReadAccess.cxx
new file mode 100644
index 0000000000..5dc3c944d9
--- /dev/null
+++ b/vcl/source/bitmap/BitmapReadAccess.cxx
@@ -0,0 +1,534 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <vcl/BitmapReadAccess.hxx>
+#include <vcl/BitmapTools.hxx>
+
+#include <salbmp.hxx>
+#include <svdata.hxx>
+#include <salinst.hxx>
+
+BitmapReadAccess::BitmapReadAccess(const AlphaMask& rBitmap, BitmapAccessMode nMode)
+ : BitmapReadAccess(rBitmap.GetBitmap(), nMode)
+{
+}
+
+BitmapReadAccess::BitmapReadAccess(const Bitmap& rBitmap, BitmapAccessMode nMode)
+ : BitmapInfoAccess(rBitmap, nMode)
+ , mFncGetPixel(nullptr)
+ , mFncSetPixel(nullptr)
+{
+ if (!mpBuffer)
+ return;
+
+ const std::shared_ptr<SalBitmap>& xImpBmp = rBitmap.ImplGetSalBitmap();
+ if (!xImpBmp)
+ return;
+
+ maColorMask = mpBuffer->maColorMask;
+
+ mFncGetPixel = GetPixelFunction(mpBuffer->mnFormat);
+ mFncSetPixel = SetPixelFunction(mpBuffer->mnFormat);
+
+ if (!mFncGetPixel || !mFncSetPixel)
+ {
+ xImpBmp->ReleaseBuffer(mpBuffer, mnAccessMode);
+ mpBuffer = nullptr;
+ }
+}
+
+BitmapReadAccess::~BitmapReadAccess() {}
+
+bool Bitmap32IsPreMultipled() { return ImplGetSVData()->mpDefInst->supportsBitmap32(); }
+
+FncGetPixel BitmapReadAccess::GetPixelFunction(ScanlineFormat nFormat)
+{
+ switch (RemoveScanline(nFormat))
+ {
+ case ScanlineFormat::N1BitMsbPal:
+ return GetPixelForN1BitMsbPal;
+ case ScanlineFormat::N8BitPal:
+ return GetPixelForN8BitPal;
+ case ScanlineFormat::N24BitTcBgr:
+ return GetPixelForN24BitTcBgr;
+ case ScanlineFormat::N24BitTcRgb:
+ return GetPixelForN24BitTcRgb;
+ case ScanlineFormat::N32BitTcAbgr:
+ if (Bitmap32IsPreMultipled())
+ return GetPixelForN32BitTcAbgr;
+ else
+ return GetPixelForN32BitTcXbgr;
+ case ScanlineFormat::N32BitTcArgb:
+ if (Bitmap32IsPreMultipled())
+ return GetPixelForN32BitTcArgb;
+ else
+ return GetPixelForN32BitTcXrgb;
+ case ScanlineFormat::N32BitTcBgra:
+ if (Bitmap32IsPreMultipled())
+ return GetPixelForN32BitTcBgra;
+ else
+ return GetPixelForN32BitTcBgrx;
+ case ScanlineFormat::N32BitTcRgba:
+ if (Bitmap32IsPreMultipled())
+ return GetPixelForN32BitTcRgba;
+ else
+ return GetPixelForN32BitTcRgbx;
+ case ScanlineFormat::N32BitTcMask:
+ return GetPixelForN32BitTcMask;
+
+ default:
+ return nullptr;
+ }
+}
+
+FncSetPixel BitmapReadAccess::SetPixelFunction(ScanlineFormat nFormat)
+{
+ switch (RemoveScanline(nFormat))
+ {
+ case ScanlineFormat::N1BitMsbPal:
+ return SetPixelForN1BitMsbPal;
+ case ScanlineFormat::N8BitPal:
+ return SetPixelForN8BitPal;
+ case ScanlineFormat::N24BitTcBgr:
+ return SetPixelForN24BitTcBgr;
+ case ScanlineFormat::N24BitTcRgb:
+ return SetPixelForN24BitTcRgb;
+ case ScanlineFormat::N32BitTcAbgr:
+ if (Bitmap32IsPreMultipled())
+ return SetPixelForN32BitTcAbgr;
+ else
+ return SetPixelForN32BitTcXbgr;
+ case ScanlineFormat::N32BitTcArgb:
+ if (Bitmap32IsPreMultipled())
+ return SetPixelForN32BitTcArgb;
+ else
+ return SetPixelForN32BitTcXrgb;
+ case ScanlineFormat::N32BitTcBgra:
+ if (Bitmap32IsPreMultipled())
+ return SetPixelForN32BitTcBgra;
+ else
+ return SetPixelForN32BitTcBgrx;
+ case ScanlineFormat::N32BitTcRgba:
+ if (Bitmap32IsPreMultipled())
+ return SetPixelForN32BitTcRgba;
+ else
+ return SetPixelForN32BitTcRgbx;
+ case ScanlineFormat::N32BitTcMask:
+ return SetPixelForN32BitTcMask;
+
+ default:
+ return nullptr;
+ }
+}
+
+BitmapColor BitmapReadAccess::GetInterpolatedColorWithFallback(double fY, double fX,
+ const BitmapColor& rFallback) const
+{
+ // ask directly doubles >= 0.0 here to avoid rounded values of 0 at small negative
+ // double values, e.g. static_cast< sal_Int32 >(-0.25) is 0, not -1, but *has* to be outside (!)
+ if (mpBuffer && fX >= 0.0 && fY >= 0.0)
+ {
+ const sal_Int64 nX(static_cast<sal_Int64>(fX));
+ const sal_Int64 nY(static_cast<sal_Int64>(fY));
+
+ if (nX < mpBuffer->mnWidth && nY < mpBuffer->mnHeight)
+ {
+ // get base-return value from inside pixel
+ BitmapColor aRetval(GetColor(nY, nX));
+
+ // calculate deltas and indices for neighbour accesses
+ sal_Int16 nDeltaX((fX - (nX + 0.5)) * 255.0); // [-255 .. 255]
+ sal_Int16 nDeltaY((fY - (nY + 0.5)) * 255.0); // [-255 .. 255]
+ sal_Int16 nIndX(0);
+ sal_Int16 nIndY(0);
+
+ if (nDeltaX > 0)
+ {
+ nIndX = nX + 1;
+ }
+ else
+ {
+ nIndX = nX - 1;
+ nDeltaX = -nDeltaX;
+ }
+
+ if (nDeltaY > 0)
+ {
+ nIndY = nY + 1;
+ }
+ else
+ {
+ nIndY = nY - 1;
+ nDeltaY = -nDeltaY;
+ }
+
+ // get right/left neighbour
+ BitmapColor aXCol(rFallback);
+
+ if (nDeltaX && nIndX >= 0 && nIndX < mpBuffer->mnWidth)
+ {
+ aXCol = GetColor(nY, nIndX);
+ }
+
+ // get top/bottom neighbour
+ BitmapColor aYCol(rFallback);
+
+ if (nDeltaY && nIndY >= 0 && nIndY < mpBuffer->mnHeight)
+ {
+ aYCol = GetColor(nIndY, nX);
+ }
+
+ // get one of four edge neighbours
+ BitmapColor aXYCol(rFallback);
+
+ if (nDeltaX && nDeltaY && nIndX >= 0 && nIndY >= 0 && nIndX < mpBuffer->mnWidth
+ && nIndY < mpBuffer->mnHeight)
+ {
+ aXYCol = GetColor(nIndY, nIndX);
+ }
+
+ // merge return value with right/left neighbour
+ if (aXCol != aRetval)
+ {
+ aRetval.Merge(aXCol, 255 - nDeltaX);
+ }
+
+ // merge top/bottom neighbour with edge
+ if (aYCol != aXYCol)
+ {
+ aYCol.Merge(aXYCol, 255 - nDeltaX);
+ }
+
+ // merge return value with already merged top/bottom neighbour
+ if (aRetval != aYCol)
+ {
+ aRetval.Merge(aYCol, 255 - nDeltaY);
+ }
+
+ return aRetval;
+ }
+ }
+
+ return rFallback;
+}
+
+BitmapColor BitmapReadAccess::GetColorWithFallback(double fY, double fX,
+ const BitmapColor& rFallback) const
+{
+ // ask directly doubles >= 0.0 here to avoid rounded values of 0 at small negative
+ // double values, e.g. static_cast< sal_Int32 >(-0.25) is 0, not -1, but *has* to be outside (!)
+ if (mpBuffer && fX >= 0.0 && fY >= 0.0)
+ {
+ const sal_Int32 nX(static_cast<sal_Int32>(fX));
+ const sal_Int32 nY(static_cast<sal_Int32>(fY));
+
+ if (nX < mpBuffer->mnWidth && nY < mpBuffer->mnHeight)
+ {
+ return GetColor(nY, nX);
+ }
+ }
+
+ return rFallback;
+}
+
+BitmapColor BitmapReadAccess::GetPixelForN1BitMsbPal(ConstScanline pScanline, tools::Long nX,
+ const ColorMask&)
+{
+ return BitmapColor(pScanline[nX >> 3] & (1 << (7 - (nX & 7))) ? 1 : 0);
+}
+
+void BitmapReadAccess::SetPixelForN1BitMsbPal(const Scanline pScanline, tools::Long nX,
+ const BitmapColor& rBitmapColor, const ColorMask&)
+{
+ sal_uInt8& rByte = pScanline[nX >> 3];
+
+ if (rBitmapColor.GetIndex() & 1)
+ rByte |= 1 << (7 - (nX & 7));
+ else
+ rByte &= ~(1 << (7 - (nX & 7)));
+}
+
+BitmapColor BitmapReadAccess::GetPixelForN8BitPal(ConstScanline pScanline, tools::Long nX,
+ const ColorMask&)
+{
+ return BitmapColor(pScanline[nX]);
+}
+
+void BitmapReadAccess::SetPixelForN8BitPal(Scanline pScanline, tools::Long nX,
+ const BitmapColor& rBitmapColor, const ColorMask&)
+{
+ pScanline[nX] = rBitmapColor.GetIndex();
+}
+
+BitmapColor BitmapReadAccess::GetPixelForN24BitTcBgr(ConstScanline pScanline, tools::Long nX,
+ const ColorMask&)
+{
+ BitmapColor aBitmapColor;
+
+ pScanline = pScanline + nX * 3;
+ aBitmapColor.SetBlue(*pScanline++);
+ aBitmapColor.SetGreen(*pScanline++);
+ aBitmapColor.SetRed(*pScanline);
+
+ return aBitmapColor;
+}
+
+void BitmapReadAccess::SetPixelForN24BitTcBgr(Scanline pScanline, tools::Long nX,
+ const BitmapColor& rBitmapColor, const ColorMask&)
+{
+ pScanline = pScanline + nX * 3;
+ *pScanline++ = rBitmapColor.GetBlue();
+ *pScanline++ = rBitmapColor.GetGreen();
+ *pScanline = rBitmapColor.GetRed();
+}
+
+BitmapColor BitmapReadAccess::GetPixelForN24BitTcRgb(ConstScanline pScanline, tools::Long nX,
+ const ColorMask&)
+{
+ BitmapColor aBitmapColor;
+
+ pScanline = pScanline + nX * 3;
+ aBitmapColor.SetRed(*pScanline++);
+ aBitmapColor.SetGreen(*pScanline++);
+ aBitmapColor.SetBlue(*pScanline);
+
+ return aBitmapColor;
+}
+
+void BitmapReadAccess::SetPixelForN24BitTcRgb(Scanline pScanline, tools::Long nX,
+ const BitmapColor& rBitmapColor, const ColorMask&)
+{
+ pScanline = pScanline + nX * 3;
+ *pScanline++ = rBitmapColor.GetRed();
+ *pScanline++ = rBitmapColor.GetGreen();
+ *pScanline = rBitmapColor.GetBlue();
+}
+
+BitmapColor BitmapReadAccess::GetPixelForN32BitTcAbgr(ConstScanline pScanline, tools::Long nX,
+ const ColorMask&)
+{
+ pScanline = pScanline + nX * 4;
+
+ sal_uInt8 a = *pScanline++;
+ sal_uInt8 b = *pScanline++;
+ sal_uInt8 g = *pScanline++;
+ sal_uInt8 r = *pScanline;
+
+ return BitmapColor(ColorAlpha, vcl::bitmap::unpremultiply(r, a),
+ vcl::bitmap::unpremultiply(g, a), vcl::bitmap::unpremultiply(b, a), a);
+}
+
+BitmapColor BitmapReadAccess::GetPixelForN32BitTcXbgr(ConstScanline pScanline, tools::Long nX,
+ const ColorMask&)
+{
+ BitmapColor aBitmapColor;
+
+ pScanline = pScanline + (nX << 2) + 1;
+ aBitmapColor.SetBlue(*pScanline++);
+ aBitmapColor.SetGreen(*pScanline++);
+ aBitmapColor.SetRed(*pScanline);
+
+ return aBitmapColor;
+}
+
+void BitmapReadAccess::SetPixelForN32BitTcAbgr(Scanline pScanline, tools::Long nX,
+ const BitmapColor& rBitmapColor, const ColorMask&)
+{
+ pScanline = pScanline + nX * 4;
+
+ sal_uInt8 alpha = rBitmapColor.GetAlpha();
+ *pScanline++ = alpha;
+ *pScanline++ = vcl::bitmap::premultiply(rBitmapColor.GetBlue(), alpha);
+ *pScanline++ = vcl::bitmap::premultiply(rBitmapColor.GetGreen(), alpha);
+ *pScanline = vcl::bitmap::premultiply(rBitmapColor.GetRed(), alpha);
+}
+
+void BitmapReadAccess::SetPixelForN32BitTcXbgr(Scanline pScanline, tools::Long nX,
+ const BitmapColor& rBitmapColor, const ColorMask&)
+{
+ pScanline = pScanline + (nX << 2);
+ *pScanline++ = 0xFF;
+ *pScanline++ = rBitmapColor.GetBlue();
+ *pScanline++ = rBitmapColor.GetGreen();
+ *pScanline = rBitmapColor.GetRed();
+}
+
+BitmapColor BitmapReadAccess::GetPixelForN32BitTcArgb(ConstScanline pScanline, tools::Long nX,
+ const ColorMask&)
+{
+ pScanline = pScanline + nX * 4;
+
+ sal_uInt8 a = *pScanline++;
+ sal_uInt8 r = *pScanline++;
+ sal_uInt8 g = *pScanline++;
+ sal_uInt8 b = *pScanline;
+
+ return BitmapColor(ColorAlpha, vcl::bitmap::unpremultiply(r, a),
+ vcl::bitmap::unpremultiply(g, a), vcl::bitmap::unpremultiply(b, a), a);
+}
+
+BitmapColor BitmapReadAccess::GetPixelForN32BitTcXrgb(ConstScanline pScanline, tools::Long nX,
+ const ColorMask&)
+{
+ BitmapColor aBitmapColor;
+
+ pScanline = pScanline + (nX << 2) + 1;
+ aBitmapColor.SetRed(*pScanline++);
+ aBitmapColor.SetGreen(*pScanline++);
+ aBitmapColor.SetBlue(*pScanline);
+
+ return aBitmapColor;
+}
+
+void BitmapReadAccess::SetPixelForN32BitTcArgb(Scanline pScanline, tools::Long nX,
+ const BitmapColor& rBitmapColor, const ColorMask&)
+{
+ pScanline = pScanline + nX * 4;
+
+ sal_uInt8 alpha = rBitmapColor.GetAlpha();
+ *pScanline++ = alpha;
+ *pScanline++ = vcl::bitmap::premultiply(rBitmapColor.GetRed(), alpha);
+ *pScanline++ = vcl::bitmap::premultiply(rBitmapColor.GetGreen(), alpha);
+ *pScanline = vcl::bitmap::premultiply(rBitmapColor.GetBlue(), alpha);
+}
+
+void BitmapReadAccess::SetPixelForN32BitTcXrgb(Scanline pScanline, tools::Long nX,
+ const BitmapColor& rBitmapColor, const ColorMask&)
+{
+ pScanline = pScanline + (nX << 2);
+ *pScanline++ = 0xFF;
+ *pScanline++ = rBitmapColor.GetRed();
+ *pScanline++ = rBitmapColor.GetGreen();
+ *pScanline = rBitmapColor.GetBlue();
+}
+
+BitmapColor BitmapReadAccess::GetPixelForN32BitTcBgra(ConstScanline pScanline, tools::Long nX,
+ const ColorMask&)
+{
+ pScanline = pScanline + nX * 4;
+
+ sal_uInt8 b = *pScanline++;
+ sal_uInt8 g = *pScanline++;
+ sal_uInt8 r = *pScanline++;
+ sal_uInt8 a = *pScanline;
+
+ return BitmapColor(ColorAlpha, vcl::bitmap::unpremultiply(r, a),
+ vcl::bitmap::unpremultiply(g, a), vcl::bitmap::unpremultiply(b, a), a);
+}
+
+BitmapColor BitmapReadAccess::GetPixelForN32BitTcBgrx(ConstScanline pScanline, tools::Long nX,
+ const ColorMask&)
+{
+ BitmapColor aBitmapColor;
+
+ pScanline = pScanline + (nX << 2);
+ aBitmapColor.SetBlue(*pScanline++);
+ aBitmapColor.SetGreen(*pScanline++);
+ aBitmapColor.SetRed(*pScanline);
+
+ return aBitmapColor;
+}
+
+void BitmapReadAccess::SetPixelForN32BitTcBgra(Scanline pScanline, tools::Long nX,
+ const BitmapColor& rBitmapColor, const ColorMask&)
+{
+ pScanline = pScanline + nX * 4;
+
+ sal_uInt8 alpha = rBitmapColor.GetAlpha();
+ *pScanline++ = vcl::bitmap::premultiply(rBitmapColor.GetBlue(), alpha);
+ *pScanline++ = vcl::bitmap::premultiply(rBitmapColor.GetGreen(), alpha);
+ *pScanline++ = vcl::bitmap::premultiply(rBitmapColor.GetRed(), alpha);
+ *pScanline = alpha;
+}
+
+void BitmapReadAccess::SetPixelForN32BitTcBgrx(Scanline pScanline, tools::Long nX,
+ const BitmapColor& rBitmapColor, const ColorMask&)
+{
+ pScanline = pScanline + (nX << 2);
+ *pScanline++ = rBitmapColor.GetBlue();
+ *pScanline++ = rBitmapColor.GetGreen();
+ *pScanline++ = rBitmapColor.GetRed();
+ *pScanline = 0xFF;
+}
+
+BitmapColor BitmapReadAccess::GetPixelForN32BitTcRgba(ConstScanline pScanline, tools::Long nX,
+ const ColorMask&)
+{
+ pScanline = pScanline + nX * 4;
+
+ sal_uInt8 r = *pScanline++;
+ sal_uInt8 g = *pScanline++;
+ sal_uInt8 b = *pScanline++;
+ sal_uInt8 a = *pScanline;
+
+ return BitmapColor(ColorAlpha, vcl::bitmap::unpremultiply(r, a),
+ vcl::bitmap::unpremultiply(g, a), vcl::bitmap::unpremultiply(b, a), a);
+}
+
+BitmapColor BitmapReadAccess::GetPixelForN32BitTcRgbx(ConstScanline pScanline, tools::Long nX,
+ const ColorMask&)
+{
+ BitmapColor aBitmapColor;
+
+ pScanline = pScanline + (nX << 2);
+ aBitmapColor.SetRed(*pScanline++);
+ aBitmapColor.SetGreen(*pScanline++);
+ aBitmapColor.SetBlue(*pScanline);
+
+ return aBitmapColor;
+}
+
+void BitmapReadAccess::SetPixelForN32BitTcRgba(Scanline pScanline, tools::Long nX,
+ const BitmapColor& rBitmapColor, const ColorMask&)
+{
+ pScanline = pScanline + nX * 4;
+
+ sal_uInt8 alpha = rBitmapColor.GetAlpha();
+ *pScanline++ = vcl::bitmap::premultiply(rBitmapColor.GetRed(), alpha);
+ *pScanline++ = vcl::bitmap::premultiply(rBitmapColor.GetGreen(), alpha);
+ *pScanline++ = vcl::bitmap::premultiply(rBitmapColor.GetBlue(), alpha);
+ *pScanline = alpha;
+}
+
+void BitmapReadAccess::SetPixelForN32BitTcRgbx(Scanline pScanline, tools::Long nX,
+ const BitmapColor& rBitmapColor, const ColorMask&)
+{
+ pScanline = pScanline + (nX << 2);
+ *pScanline++ = rBitmapColor.GetRed();
+ *pScanline++ = rBitmapColor.GetGreen();
+ *pScanline++ = rBitmapColor.GetBlue();
+ *pScanline = 0xFF;
+}
+
+BitmapColor BitmapReadAccess::GetPixelForN32BitTcMask(ConstScanline pScanline, tools::Long nX,
+ const ColorMask& rMask)
+{
+ BitmapColor aColor;
+ rMask.GetColorFor32Bit(aColor, pScanline + (nX << 2));
+ return aColor;
+}
+
+void BitmapReadAccess::SetPixelForN32BitTcMask(Scanline pScanline, tools::Long nX,
+ const BitmapColor& rBitmapColor,
+ const ColorMask& rMask)
+{
+ rMask.SetColorFor32Bit(rBitmapColor, pScanline + (nX << 2));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapScaleConvolutionFilter.cxx b/vcl/source/bitmap/BitmapScaleConvolutionFilter.cxx
new file mode 100644
index 0000000000..27df45f7ba
--- /dev/null
+++ b/vcl/source/bitmap/BitmapScaleConvolutionFilter.cxx
@@ -0,0 +1,381 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <osl/diagnose.h>
+#include <tools/helpers.hxx>
+
+#include <vcl/BitmapWriteAccess.hxx>
+#include <bitmap/BitmapScaleConvolutionFilter.hxx>
+
+#include <algorithm>
+#include <memory>
+
+namespace vcl
+{
+
+namespace
+{
+
+void ImplCalculateContributions(
+ const sal_Int32 aSourceSize,
+ const sal_Int32 aDestinationSize,
+ sal_Int32& aNumberOfContributions,
+ std::vector<sal_Int16>& rWeights,
+ std::vector<sal_Int32>& rPixels,
+ std::vector<sal_Int32>& rCounts,
+ const Kernel& aKernel)
+{
+ const double fSamplingRadius(aKernel.GetWidth());
+ const double fScale(aDestinationSize / static_cast< double >(aSourceSize));
+ const double fScaledRadius((fScale < 1.0) ? fSamplingRadius / fScale : fSamplingRadius);
+ const double fFilterFactor(std::min(fScale, 1.0));
+
+ aNumberOfContributions = (sal_Int32(fabs(ceil(fScaledRadius))) * 2) + 1;
+ const sal_Int32 nAllocSize(aDestinationSize * aNumberOfContributions);
+ rWeights.resize(nAllocSize);
+ rPixels.resize(nAllocSize);
+ rCounts.resize(aDestinationSize);
+
+ for(sal_Int32 i(0); i < aDestinationSize; i++)
+ {
+ const sal_Int32 aIndex(i * aNumberOfContributions);
+ const double aCenter(i / fScale);
+ const sal_Int32 aLeft(static_cast< sal_Int32 >(floor(aCenter - fScaledRadius)));
+ const sal_Int32 aRight(static_cast< sal_Int32 >(ceil(aCenter + fScaledRadius)));
+ sal_Int32 aCurrentCount(0);
+
+ for(sal_Int32 j(aLeft); j <= aRight; j++)
+ {
+ const double aWeight(aKernel.Calculate(fFilterFactor * (aCenter - static_cast< double>(j))));
+
+ // Reduce calculations with ignoring weights of 0.0
+ if(fabs(aWeight) < 0.0001)
+ {
+ continue;
+ }
+
+ // Handling on edges
+ const sal_Int32 aPixelIndex(std::clamp(j, sal_Int32(0), aSourceSize - 1));
+ const sal_Int32 nIndex(aIndex + aCurrentCount);
+
+ // scale the weight by 255 since we're converting from float to int
+ rWeights[nIndex] = aWeight * 255;
+ rPixels[nIndex] = aPixelIndex;
+
+ aCurrentCount++;
+ }
+
+ rCounts[i] = aCurrentCount;
+ }
+}
+
+bool ImplScaleConvolutionHor(Bitmap& rSource, Bitmap& rTarget, const double& rScaleX, const Kernel& aKernel)
+{
+ // Do horizontal filtering
+ OSL_ENSURE(rScaleX > 0.0, "Error in scaling: Mirror given in non-mirror-capable method (!)");
+ const sal_Int32 nWidth(rSource.GetSizePixel().Width());
+ const sal_Int32 nNewWidth(FRound(nWidth * rScaleX));
+
+ if(nWidth == nNewWidth)
+ {
+ return true;
+ }
+
+ BitmapScopedReadAccess pReadAcc(rSource);
+
+ if(pReadAcc)
+ {
+ std::vector<sal_Int16> aWeights;
+ std::vector<sal_Int32> aPixels;
+ std::vector<sal_Int32> aCounts;
+ sal_Int32 aNumberOfContributions(0);
+
+ const sal_Int32 nHeight(rSource.GetSizePixel().Height());
+ ImplCalculateContributions(nWidth, nNewWidth, aNumberOfContributions, aWeights, aPixels, aCounts, aKernel);
+ rTarget = Bitmap(Size(nNewWidth, nHeight), vcl::PixelFormat::N24_BPP);
+ BitmapScopedWriteAccess pWriteAcc(rTarget);
+ bool bResult(pWriteAcc);
+
+ if(bResult)
+ {
+ for(sal_Int32 y(0); y < nHeight; y++)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline( y );
+ Scanline pScanlineRead = pReadAcc->GetScanline( y );
+ for(sal_Int32 x(0); x < nNewWidth; x++)
+ {
+ const sal_Int32 aBaseIndex(x * aNumberOfContributions);
+ sal_Int32 aSum(0);
+ sal_Int32 aValueRed(0);
+ sal_Int32 aValueGreen(0);
+ sal_Int32 aValueBlue(0);
+
+ for(sal_Int32 j(0); j < aCounts[x]; j++)
+ {
+ const sal_Int32 aIndex(aBaseIndex + j);
+ const sal_Int16 aWeight(aWeights[aIndex]);
+ BitmapColor aColor;
+
+ aSum += aWeight;
+
+ if(pReadAcc->HasPalette())
+ {
+ aColor = pReadAcc->GetPaletteColor(pReadAcc->GetIndexFromData(pScanlineRead, aPixels[aIndex]));
+ }
+ else
+ {
+ aColor = pReadAcc->GetPixelFromData(pScanlineRead, aPixels[aIndex]);
+ }
+
+ aValueRed += aWeight * aColor.GetRed();
+ aValueGreen += aWeight * aColor.GetGreen();
+ aValueBlue += aWeight * aColor.GetBlue();
+ }
+
+ assert(aSum != 0);
+
+ const BitmapColor aResultColor(
+ static_cast< sal_uInt8 >(std::clamp< sal_Int32 >(aValueRed / aSum, 0, 255)),
+ static_cast< sal_uInt8 >(std::clamp< sal_Int32 >(aValueGreen / aSum, 0, 255)),
+ static_cast< sal_uInt8 >(std::clamp< sal_Int32 >(aValueBlue / aSum, 0, 255)));
+
+ pWriteAcc->SetPixelOnData(pScanline, x, aResultColor);
+ }
+ }
+
+ pWriteAcc.reset();
+ }
+
+ aWeights.clear();
+ aCounts.clear();
+ aPixels.clear();
+
+ if(bResult)
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool ImplScaleConvolutionVer(Bitmap& rSource, Bitmap& rTarget, const double& rScaleY, const Kernel& aKernel)
+{
+ // Do vertical filtering
+ OSL_ENSURE(rScaleY > 0.0, "Error in scaling: Mirror given in non-mirror-capable method (!)");
+ const sal_Int32 nHeight(rSource.GetSizePixel().Height());
+ const sal_Int32 nNewHeight(FRound(nHeight * rScaleY));
+
+ if(nHeight == nNewHeight)
+ {
+ return true;
+ }
+
+ BitmapScopedReadAccess pReadAcc(rSource);
+ if(!pReadAcc)
+ return false;
+
+ std::vector<sal_Int16> aWeights;
+ std::vector<sal_Int32> aPixels;
+ std::vector<sal_Int32> aCounts;
+ sal_Int32 aNumberOfContributions(0);
+
+ const sal_Int32 nWidth(rSource.GetSizePixel().Width());
+ ImplCalculateContributions(nHeight, nNewHeight, aNumberOfContributions, aWeights, aPixels, aCounts, aKernel);
+ rTarget = Bitmap(Size(nWidth, nNewHeight), vcl::PixelFormat::N24_BPP);
+ BitmapScopedWriteAccess pWriteAcc(rTarget);
+ if(!pWriteAcc)
+ return false;
+
+ std::vector<BitmapColor> aScanline(nHeight);
+ for(sal_Int32 x(0); x < nWidth; x++)
+ {
+ for(sal_Int32 y(0); y < nHeight; y++)
+ if(pReadAcc->HasPalette())
+ aScanline[y] = pReadAcc->GetPaletteColor(pReadAcc->GetPixelIndex(y, x));
+ else
+ aScanline[y] = pReadAcc->GetPixel(y, x);
+ for(sal_Int32 y(0); y < nNewHeight; y++)
+ {
+ const sal_Int32 aBaseIndex(y * aNumberOfContributions);
+ sal_Int32 aSum(0);
+ sal_Int32 aValueRed(0);
+ sal_Int32 aValueGreen(0);
+ sal_Int32 aValueBlue(0);
+
+ for(sal_Int32 j(0); j < aCounts[y]; j++)
+ {
+ const sal_Int32 aIndex(aBaseIndex + j);
+ const sal_Int16 aWeight(aWeights[aIndex]);
+ aSum += aWeight;
+ const BitmapColor & aColor = aScanline[aPixels[aIndex]];
+ aValueRed += aWeight * aColor.GetRed();
+ aValueGreen += aWeight * aColor.GetGreen();
+ aValueBlue += aWeight * aColor.GetBlue();
+ }
+
+ assert(aSum != 0);
+
+ const BitmapColor aResultColor(
+ static_cast< sal_uInt8 >(std::clamp< sal_Int32 >(aValueRed / aSum, 0, 255)),
+ static_cast< sal_uInt8 >(std::clamp< sal_Int32 >(aValueGreen / aSum, 0, 255)),
+ static_cast< sal_uInt8 >(std::clamp< sal_Int32 >(aValueBlue / aSum, 0, 255)));
+
+ if(pWriteAcc->HasPalette())
+ {
+ pWriteAcc->SetPixelIndex(y, x, static_cast< sal_uInt8 >(pWriteAcc->GetBestPaletteIndex(aResultColor)));
+ }
+ else
+ {
+ pWriteAcc->SetPixel(y, x, aResultColor);
+ }
+ }
+ }
+
+ aWeights.clear();
+ aCounts.clear();
+ aPixels.clear();
+
+ return true;
+}
+
+bool ImplScaleConvolution(Bitmap& rBitmap, const double& rScaleX, const double& rScaleY, const Kernel& aKernel)
+{
+ const bool bMirrorHor(rScaleX < 0.0);
+ const bool bMirrorVer(rScaleY < 0.0);
+ const double fScaleX(bMirrorHor ? -rScaleX : rScaleX);
+ const double fScaleY(bMirrorVer ? -rScaleY : rScaleY);
+ const sal_Int32 nWidth(rBitmap.GetSizePixel().Width());
+ const sal_Int32 nHeight(rBitmap.GetSizePixel().Height());
+ const sal_Int32 nNewWidth(FRound(nWidth * fScaleX));
+ const sal_Int32 nNewHeight(FRound(nHeight * fScaleY));
+ const bool bScaleHor(nWidth != nNewWidth);
+ const bool bScaleVer(nHeight != nNewHeight);
+ const bool bMirror(bMirrorHor || bMirrorVer);
+
+ if (!bMirror && !bScaleHor && !bScaleVer)
+ {
+ return true;
+ }
+
+ bool bResult(true);
+ BmpMirrorFlags nMirrorFlags(BmpMirrorFlags::NONE);
+ bool bMirrorAfter(false);
+
+ if (bMirror)
+ {
+ if(bMirrorHor)
+ {
+ nMirrorFlags |= BmpMirrorFlags::Horizontal;
+ }
+
+ if(bMirrorVer)
+ {
+ nMirrorFlags |= BmpMirrorFlags::Vertical;
+ }
+
+ const sal_Int32 nStartSize(nWidth * nHeight);
+ const sal_Int32 nEndSize(nNewWidth * nNewHeight);
+
+ bMirrorAfter = nStartSize > nEndSize;
+
+ if(!bMirrorAfter)
+ {
+ bResult = rBitmap.Mirror(nMirrorFlags);
+ }
+ }
+
+ Bitmap aResult;
+
+ if (bResult)
+ {
+ const sal_Int32 nInBetweenSizeHorFirst(nHeight * nNewWidth);
+ const sal_Int32 nInBetweenSizeVerFirst(nNewHeight * nWidth);
+ Bitmap aSource(rBitmap);
+
+ if(nInBetweenSizeHorFirst < nInBetweenSizeVerFirst)
+ {
+ if(bScaleHor)
+ {
+ bResult = ImplScaleConvolutionHor(aSource, aResult, fScaleX, aKernel);
+ }
+
+ if(bResult && bScaleVer)
+ {
+ if(bScaleHor)
+ {
+ // copy partial result, independent of color depth
+ aSource = aResult;
+ }
+
+ bResult = ImplScaleConvolutionVer(aSource, aResult, fScaleY, aKernel);
+ }
+ }
+ else
+ {
+ if(bScaleVer)
+ {
+ bResult = ImplScaleConvolutionVer(aSource, aResult, fScaleY, aKernel);
+ }
+
+ if(bResult && bScaleHor)
+ {
+ if(bScaleVer)
+ {
+ // copy partial result, independent of color depth
+ aSource = aResult;
+ }
+
+ bResult = ImplScaleConvolutionHor(aSource, aResult, fScaleX, aKernel);
+ }
+ }
+ }
+
+ if(bResult && bMirrorAfter)
+ {
+ bResult = aResult.Mirror(nMirrorFlags);
+ }
+
+ if(bResult)
+ {
+ rBitmap.AdaptBitCount(aResult);
+ rBitmap = aResult;
+ }
+
+ return bResult;
+}
+
+} // end anonymous namespace
+
+BitmapEx BitmapScaleConvolutionFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ bool bRetval = false;
+ Bitmap aBitmap(rBitmapEx.GetBitmap());
+
+ bRetval = ImplScaleConvolution(aBitmap, mrScaleX, mrScaleY, *mxKernel);
+
+ if (bRetval)
+ return BitmapEx(aBitmap);
+
+ return BitmapEx();
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapScaleSuperFilter.cxx b/vcl/source/bitmap/BitmapScaleSuperFilter.cxx
new file mode 100644
index 0000000000..3c844c690e
--- /dev/null
+++ b/vcl/source/bitmap/BitmapScaleSuperFilter.cxx
@@ -0,0 +1,1046 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <comphelper/profilezone.hxx>
+#include <comphelper/threadpool.hxx>
+#include <tools/helpers.hxx>
+
+#include <vcl/BitmapWriteAccess.hxx>
+#include <bitmap/BitmapScaleSuperFilter.hxx>
+
+#include <algorithm>
+#include <memory>
+#include <svdata.hxx>
+#include <sal/log.hxx>
+
+/*
+A scaling algorithm that uses bilinear if not downscaling too much,
+and averaging otherwise (bilinear would produce poor results for big downscaling).
+
+By default the combination of two filters is used: bilinear and averaging algorithm.
+Bilinear filtering is used for bitmap enlarging and shrinking till factor 0.6. Below
+this bilinear gives bad results because of limited sampling. For such cases averaging
+is used which is a simple algorithm for shrinking. In averaging the algorithm
+calculates the average of samples which result is the new pixel.
+*/
+
+namespace {
+
+constexpr int MAP_PRECISION = 7;
+
+typedef sal_Int32 BilinearWeightType;
+
+constexpr BilinearWeightType lclMaxWeight()
+{
+ return BilinearWeightType(1) << MAP_PRECISION;
+}
+
+constexpr sal_uInt8 MAP(sal_uInt8 cVal0, sal_uInt8 cVal1, BilinearWeightType nFrac)
+{
+ return sal_uInt8(((BilinearWeightType(cVal0) << MAP_PRECISION) + nFrac * (BilinearWeightType(cVal1) - BilinearWeightType(cVal0))) >> MAP_PRECISION);
+}
+
+struct ScaleContext
+{
+ BitmapReadAccess* mpSrc;
+ BitmapWriteAccess* mpDest;
+ sal_Int32 mnDestW;
+ bool mbHMirr;
+ bool mbVMirr;
+ std::vector<sal_Int32> maMapIX;
+ std::vector<sal_Int32> maMapIY;
+ std::vector<BilinearWeightType> maMapFX;
+ std::vector<BilinearWeightType> maMapFY;
+
+ ScaleContext( BitmapReadAccess *pSrc,
+ BitmapWriteAccess *pDest,
+ sal_Int32 nSrcW, sal_Int32 nDestW,
+ sal_Int32 nSrcH, sal_Int32 nDestH,
+ bool bHMirr, bool bVMirr)
+ : mpSrc(pSrc)
+ , mpDest(pDest)
+ , mnDestW(nDestW)
+ , mbHMirr(bHMirr)
+ , mbVMirr(bVMirr)
+ , maMapIX(nDestW)
+ , maMapIY(nDestH)
+ , maMapFX(nDestW)
+ , maMapFY(nDestH)
+ {
+ generateMap(nSrcW, nDestW, bHMirr, maMapIX, maMapFX);
+ generateMap(nSrcH, nDestH, bVMirr, maMapIY, maMapFY);
+ }
+
+ static void generateMap(sal_Int32 nSourceLength, sal_Int32 nDestinationLength, bool bMirrored,
+ std::vector<sal_Int32> & rMapIX, std::vector<BilinearWeightType> & rMapFX)
+ {
+ const double fRevScale = (nDestinationLength > 1) ? double(nSourceLength - 1) / (nDestinationLength - 1) : 0.0;
+
+ sal_Int32 nTemp = nSourceLength - 2;
+ sal_Int32 nTempX = nSourceLength - 1;
+
+ for (sal_Int32 i = 0; i < nDestinationLength; i++)
+ {
+ double fTemp = i * fRevScale;
+ if (bMirrored)
+ fTemp = nTempX - fTemp;
+ rMapIX[i] = std::clamp(sal_Int32(fTemp), sal_Int32(0), nTemp);
+ rMapFX[i] = BilinearWeightType((fTemp - rMapIX[i]) * (BilinearWeightType(1) << MAP_PRECISION));
+ }
+ }
+};
+
+constexpr sal_Int32 constScaleThreadStrip = 32;
+
+typedef void (*ScaleRangeFn)(const ScaleContext & rContext, sal_Int32 nStartY, sal_Int32 nEndY);
+
+template <size_t nSize> struct ScaleFunc
+{
+ // for scale down
+
+ static inline void generateSumRows(Scanline& pTmpX, std::array<int, nSize>& sumRows)
+ {
+ for (int& n : sumRows)
+ n += (*pTmpX++) << MAP_PRECISION;
+ }
+
+ static inline void generateSumRows(BilinearWeightType const nWeightX, Scanline& pTmpX,
+ std::array<int, nSize>& sumRows)
+ {
+ for (int& n : sumRows)
+ n += (nWeightX * (*pTmpX++));
+ }
+
+ static inline void generateSumRows(BilinearWeightType const nTotalWeightX,
+ std::array<int, nSize>& sumRows)
+ {
+ for (int& n : sumRows)
+ n /= nTotalWeightX;
+ }
+
+ static inline void generateSumNumbers(BilinearWeightType const nWeightY,
+ std::array<int, nSize>& sumRows,
+ std::array<int, nSize>& sumNumbers)
+ {
+ std::transform(sumRows.begin(), sumRows.end(), sumNumbers.begin(), sumNumbers.begin(),
+ [nWeightY](int n1, int n2) { return nWeightY * n1 + n2; });
+ }
+
+ static inline void generateSumNumbers(BilinearWeightType const nTotalWeightY,
+ std::array<int, nSize>& sumNumbers)
+ {
+ for (int& n : sumNumbers)
+ n /= nTotalWeightY;
+ }
+
+ static inline void calculateDestination(Scanline& pScanDest, std::array<int, nSize>& sumNumbers)
+ {
+ pScanDest = std::copy(sumNumbers.begin(), sumNumbers.end(), pScanDest);
+ }
+
+ // for scale up
+
+ static inline void generateComponent(Scanline pColorPtr0, Scanline pColorPtr1,
+ BilinearWeightType const nTempFX,
+ std::array<sal_uInt8, nSize>& nComponents)
+ {
+ for (sal_uInt8& rComponent : nComponents)
+ rComponent = MAP(*pColorPtr0++, *pColorPtr1++, nTempFX);
+ }
+
+ static inline void calculateDestination(Scanline& pScanDest, BilinearWeightType const nTempFY,
+ const std::array<sal_uInt8, nSize>& nComponents1,
+ const std::array<sal_uInt8, nSize>& nComponents2)
+ {
+ pScanDest = std::transform(
+ nComponents1.begin(), nComponents1.end(), nComponents2.begin(), pScanDest,
+ [nTempFY](sal_uInt8 c1, sal_uInt8 c2) { return MAP(c1, c2, nTempFY); });
+ }
+};
+
+template <int nColorBits>
+void scaleDown (const ScaleContext &rCtx, sal_Int32 nStartY, sal_Int32 nEndY)
+{
+ comphelper::ProfileZone pz("BitmapScaleSuperFilter::scaleDown");
+ constexpr int nColorComponents = nColorBits / 8;
+ static_assert(nColorComponents * 8 == nColorBits, "nColorBits must be divisible by 8");
+ using ScaleFunction = ScaleFunc<nColorComponents>;
+ const sal_Int32 nStartX = 0;
+ const sal_Int32 nEndX = rCtx.mnDestW - 1;
+
+ for (sal_Int32 nY = nStartY; nY <= nEndY; nY++)
+ {
+ sal_Int32 nTop = rCtx.mbVMirr ? (nY + 1) : nY;
+ sal_Int32 nBottom = rCtx.mbVMirr ? nY : (nY + 1);
+
+ sal_Int32 nLineStart;
+ sal_Int32 nLineRange;
+ if (nY == nEndY)
+ {
+ nLineStart = rCtx.maMapIY[nY];
+ nLineRange = 0;
+ }
+ else
+ {
+ nLineStart = rCtx.maMapIY[nTop];
+ nLineRange = (rCtx.maMapIY[nBottom] == rCtx.maMapIY[nTop]) ?
+ 1 : (rCtx.maMapIY[nBottom] - rCtx.maMapIY[nTop]);
+ }
+
+ Scanline pScanDest = rCtx.mpDest->GetScanline(nY);
+ for (sal_Int32 nX = nStartX; nX <= nEndX; nX++)
+ {
+ sal_Int32 nLeft = rCtx.mbHMirr ? (nX + 1) : nX;
+ sal_Int32 nRight = rCtx.mbHMirr ? nX : (nX + 1);
+
+ sal_Int32 nRowStart;
+ sal_Int32 nRowRange;
+ if (nX == nEndX)
+ {
+ nRowStart = rCtx.maMapIX[nX];
+ nRowRange = 0;
+ }
+ else
+ {
+ nRowStart = rCtx.maMapIX[nLeft];
+ nRowRange = (rCtx.maMapIX[nRight] == rCtx.maMapIX[nLeft]) ?
+ 1 : (rCtx.maMapIX[nRight] - rCtx.maMapIX[nLeft]);
+ }
+
+ std::array<int, nColorComponents> sumNumbers{}; // zero-initialize
+ BilinearWeightType nTotalWeightY = 0;
+
+ for (sal_Int32 i = 0; i<= nLineRange; i++)
+ {
+ Scanline pTmpY = rCtx.mpSrc->GetScanline(nLineStart + i);
+ Scanline pTmpX = pTmpY + nColorComponents * nRowStart;
+
+ std::array<int, nColorComponents> sumRows{}; // zero-initialize
+ BilinearWeightType nTotalWeightX = 0;
+
+ for (sal_Int32 j = 0; j <= nRowRange; j++)
+ {
+ if (nX == nEndX)
+ {
+ ScaleFunction::generateSumRows(pTmpX, sumRows);
+ nTotalWeightX += lclMaxWeight();
+ }
+ else if(j == 0)
+ {
+ BilinearWeightType nWeightX = lclMaxWeight() - rCtx.maMapFX[nLeft];
+ ScaleFunction::generateSumRows(nWeightX, pTmpX, sumRows);
+ nTotalWeightX += nWeightX;
+ }
+ else if ( nRowRange == j )
+ {
+ BilinearWeightType nWeightX = rCtx.maMapFX[ nRight ] ;
+ ScaleFunction::generateSumRows(nWeightX, pTmpX, sumRows);
+ nTotalWeightX += nWeightX;
+ }
+ else
+ {
+ ScaleFunction::generateSumRows(pTmpX, sumRows);
+ nTotalWeightX += lclMaxWeight();
+ }
+ }
+
+ BilinearWeightType nWeightY = lclMaxWeight();
+ if (nY == nEndY)
+ nWeightY = lclMaxWeight();
+ else if (i == 0)
+ nWeightY = lclMaxWeight() - rCtx.maMapFY[nTop];
+ else if (nLineRange == 1)
+ nWeightY = rCtx.maMapFY[nTop];
+ else if (nLineRange == i)
+ nWeightY = rCtx.maMapFY[nBottom];
+
+ if (nTotalWeightX)
+ {
+ ScaleFunction::generateSumRows(nTotalWeightX, sumRows);
+ }
+ ScaleFunction::generateSumNumbers(nWeightY, sumRows, sumNumbers);
+ nTotalWeightY += nWeightY;
+
+ }
+
+ if (nTotalWeightY)
+ {
+ ScaleFunction::generateSumNumbers(nTotalWeightY, sumNumbers);
+ }
+
+ // Write the calculated color components to the destination
+ ScaleFunction::calculateDestination(pScanDest, sumNumbers);
+ }
+ }
+}
+
+template <int nColorBits>
+void scaleUp(const ScaleContext &rCtx, sal_Int32 nStartY, sal_Int32 nEndY)
+{
+ comphelper::ProfileZone pz("BitmapScaleSuperFilter::scaleUp");
+ constexpr int nColorComponents = nColorBits / 8;
+ static_assert(nColorComponents * 8 == nColorBits, "nColorBits must be divisible by 8");
+ using ScaleFunction = ScaleFunc<nColorComponents>;
+ const sal_Int32 nStartX = 0;
+ const sal_Int32 nEndX = rCtx.mnDestW - 1;
+
+ for (sal_Int32 nY = nStartY; nY <= nEndY; nY++)
+ {
+ sal_Int32 nTempY = rCtx.maMapIY[nY];
+ BilinearWeightType nTempFY = rCtx.maMapFY[nY];
+
+ Scanline pLine0 = rCtx.mpSrc->GetScanline(nTempY+0);
+ Scanline pLine1 = rCtx.mpSrc->GetScanline(nTempY+1);
+ Scanline pScanDest = rCtx.mpDest->GetScanline(nY);
+
+ std::array<sal_uInt8, nColorComponents> nComponents1; // no need to initialize since it's
+ std::array<sal_uInt8, nColorComponents> nComponents2; // initialized in generateComponent
+
+ Scanline pColorPtr0;
+ Scanline pColorPtr1;
+
+ for (sal_Int32 nX = nStartX; nX <= nEndX; nX++)
+ {
+ sal_Int32 nTempX = rCtx.maMapIX[nX];
+ BilinearWeightType nTempFX = rCtx.maMapFX[nX];
+
+ pColorPtr0 = pLine0 + nTempX * nColorComponents;
+ pColorPtr1 = pColorPtr0 + nColorComponents;
+
+ ScaleFunction::generateComponent(pColorPtr0, pColorPtr1, nTempFX, nComponents1);
+
+ pColorPtr0 = pLine1 + nTempX * nColorComponents;
+ pColorPtr1 = pColorPtr0 + nColorComponents;
+
+ ScaleFunction::generateComponent(pColorPtr0, pColorPtr1, nTempFX, nComponents2);
+ ScaleFunction::calculateDestination(pScanDest, nTempFY, nComponents1, nComponents2);
+ }
+ }
+}
+
+class ScaleTask : public comphelper::ThreadTask
+{
+ ScaleRangeFn mpScaleRangeFunction;
+ const ScaleContext& mrContext;
+ sal_Int32 mnStartY;
+ sal_Int32 mnEndY;
+
+public:
+ explicit ScaleTask(const std::shared_ptr<comphelper::ThreadTaskTag>& pTag,
+ ScaleRangeFn pScaleRangeFunction,
+ const ScaleContext& rContext,
+ sal_Int32 nStartY, sal_Int32 nEndY)
+ : comphelper::ThreadTask(pTag)
+ , mpScaleRangeFunction(pScaleRangeFunction)
+ , mrContext(rContext)
+ , mnStartY(nStartY)
+ , mnEndY(nEndY)
+ {}
+
+ virtual void doWork() override
+ {
+ mpScaleRangeFunction(mrContext, mnStartY, mnEndY);
+ }
+};
+
+void scaleUpPalette8bit(const ScaleContext &rCtx, sal_Int32 nStartY, sal_Int32 nEndY)
+{
+ const sal_Int32 nStartX = 0, nEndX = rCtx.mnDestW - 1;
+
+ for( sal_Int32 nY = nStartY; nY <= nEndY; nY++ )
+ {
+ sal_Int32 nTempY = rCtx.maMapIY[ nY ];
+ BilinearWeightType nTempFY = rCtx.maMapFY[ nY ];
+ Scanline pLine0 = rCtx.mpSrc->GetScanline( nTempY );
+ Scanline pLine1 = rCtx.mpSrc->GetScanline( ++nTempY );
+ Scanline pScanDest = rCtx.mpDest->GetScanline( nY );
+
+ for(sal_Int32 nX = nStartX, nXDst = 0; nX <= nEndX; nX++ )
+ {
+ sal_Int32 nTempX = rCtx.maMapIX[ nX ];
+ BilinearWeightType nTempFX = rCtx.maMapFX[ nX ];
+
+ const BitmapColor& rCol0 = rCtx.mpSrc->GetPaletteColor( pLine0[ nTempX ] );
+ const BitmapColor& rCol2 = rCtx.mpSrc->GetPaletteColor( pLine1[ nTempX ] );
+ const BitmapColor& rCol1 = rCtx.mpSrc->GetPaletteColor( pLine0[ ++nTempX ] );
+ const BitmapColor& rCol3 = rCtx.mpSrc->GetPaletteColor( pLine1[ nTempX ] );
+
+ sal_uInt8 cR0 = MAP( rCol0.GetRed(), rCol1.GetRed(), nTempFX );
+ sal_uInt8 cG0 = MAP( rCol0.GetGreen(), rCol1.GetGreen(), nTempFX );
+ sal_uInt8 cB0 = MAP( rCol0.GetBlue(), rCol1.GetBlue(), nTempFX );
+
+ sal_uInt8 cR1 = MAP( rCol2.GetRed(), rCol3.GetRed(), nTempFX );
+ sal_uInt8 cG1 = MAP( rCol2.GetGreen(), rCol3.GetGreen(), nTempFX );
+ sal_uInt8 cB1 = MAP( rCol2.GetBlue(), rCol3.GetBlue(), nTempFX );
+
+ BitmapColor aColRes( MAP( cR0, cR1, nTempFY ),
+ MAP( cG0, cG1, nTempFY ),
+ MAP( cB0, cB1, nTempFY ) );
+ rCtx.mpDest->SetPixelOnData( pScanDest, nXDst++, aColRes );
+ }
+ }
+}
+
+void scaleUpPaletteGeneral(const ScaleContext &rCtx, sal_Int32 nStartY, sal_Int32 nEndY)
+{
+ const sal_Int32 nStartX = 0, nEndX = rCtx.mnDestW - 1;
+
+ for( sal_Int32 nY = nStartY; nY <= nEndY; nY++ )
+ {
+ sal_Int32 nTempY = rCtx.maMapIY[ nY ];
+ BilinearWeightType nTempFY = rCtx.maMapFY[ nY ];
+ Scanline pScanline = rCtx.mpDest->GetScanline( nY );
+
+ for( sal_Int32 nX = nStartX, nXDst = 0; nX <= nEndX; nX++ )
+ {
+ sal_Int32 nTempX = rCtx.maMapIX[ nX ];
+ BilinearWeightType nTempFX = rCtx.maMapFX[ nX ];
+
+ BitmapColor aCol0 = rCtx.mpSrc->GetPaletteColor( rCtx.mpSrc->GetPixelIndex( nTempY, nTempX ) );
+ BitmapColor aCol1 = rCtx.mpSrc->GetPaletteColor( rCtx.mpSrc->GetPixelIndex( nTempY, ++nTempX ) );
+ sal_uInt8 cR0 = MAP( aCol0.GetRed(), aCol1.GetRed(), nTempFX );
+ sal_uInt8 cG0 = MAP( aCol0.GetGreen(), aCol1.GetGreen(), nTempFX );
+ sal_uInt8 cB0 = MAP( aCol0.GetBlue(), aCol1.GetBlue(), nTempFX );
+
+ aCol1 = rCtx.mpSrc->GetPaletteColor( rCtx.mpSrc->GetPixelIndex( ++nTempY, nTempX ) );
+ aCol0 = rCtx.mpSrc->GetPaletteColor( rCtx.mpSrc->GetPixelIndex( nTempY--, --nTempX ) );
+ sal_uInt8 cR1 = MAP( aCol0.GetRed(), aCol1.GetRed(), nTempFX );
+ sal_uInt8 cG1 = MAP( aCol0.GetGreen(), aCol1.GetGreen(), nTempFX );
+ sal_uInt8 cB1 = MAP( aCol0.GetBlue(), aCol1.GetBlue(), nTempFX );
+
+ BitmapColor aColRes( MAP( cR0, cR1, nTempFY ),
+ MAP( cG0, cG1, nTempFY ),
+ MAP( cB0, cB1, nTempFY ) );
+ rCtx.mpDest->SetPixelOnData( pScanline, nXDst++, aColRes );
+ }
+ }
+}
+
+void scaleUpNonPaletteGeneral(const ScaleContext &rCtx, sal_Int32 nStartY, sal_Int32 nEndY)
+{
+ const sal_Int32 nStartX = 0, nEndX = rCtx.mnDestW - 1;
+
+ for( sal_Int32 nY = nStartY; nY <= nEndY; nY++ )
+ {
+ sal_Int32 nTempY = rCtx.maMapIY[ nY ];
+ BilinearWeightType nTempFY = rCtx.maMapFY[ nY ];
+ Scanline pScanDest = rCtx.mpDest->GetScanline( nY );
+
+ for( sal_Int32 nX = nStartX, nXDst = 0; nX <= nEndX; nX++ )
+ {
+ sal_Int32 nTempX = rCtx.maMapIX[ nX ];
+ BilinearWeightType nTempFX = rCtx.maMapFX[ nX ];
+
+ BitmapColor aCol0 = rCtx.mpSrc->GetPixel( nTempY, nTempX );
+ BitmapColor aCol1 = rCtx.mpSrc->GetPixel( nTempY, ++nTempX );
+ sal_uInt8 cR0 = MAP( aCol0.GetRed(), aCol1.GetRed(), nTempFX );
+ sal_uInt8 cG0 = MAP( aCol0.GetGreen(), aCol1.GetGreen(), nTempFX );
+ sal_uInt8 cB0 = MAP( aCol0.GetBlue(), aCol1.GetBlue(), nTempFX );
+
+ aCol1 = rCtx.mpSrc->GetPixel( ++nTempY, nTempX );
+ aCol0 = rCtx.mpSrc->GetPixel( nTempY--, --nTempX );
+ sal_uInt8 cR1 = MAP( aCol0.GetRed(), aCol1.GetRed(), nTempFX );
+ sal_uInt8 cG1 = MAP( aCol0.GetGreen(), aCol1.GetGreen(), nTempFX );
+ sal_uInt8 cB1 = MAP( aCol0.GetBlue(), aCol1.GetBlue(), nTempFX );
+
+ BitmapColor aColRes( MAP( cR0, cR1, nTempFY ),
+ MAP( cG0, cG1, nTempFY ),
+ MAP( cB0, cB1, nTempFY ) );
+ rCtx.mpDest->SetPixelOnData( pScanDest, nXDst++, aColRes );
+ }
+ }
+}
+
+void scaleDownPalette8bit(const ScaleContext &rCtx, sal_Int32 nStartY, sal_Int32 nEndY)
+{
+ const sal_Int32 nStartX = 0, nEndX = rCtx.mnDestW - 1;
+
+ for( sal_Int32 nY = nStartY; nY <= nEndY; nY++ )
+ {
+ sal_Int32 nTop = rCtx.mbVMirr ? ( nY + 1 ) : nY;
+ sal_Int32 nBottom = rCtx.mbVMirr ? nY : ( nY + 1 ) ;
+
+ sal_Int32 nLineStart, nLineRange;
+ if( nY == nEndY )
+ {
+ nLineStart = rCtx.maMapIY[ nY ];
+ nLineRange = 0;
+ }
+ else
+ {
+ nLineStart = rCtx.maMapIY[ nTop ] ;
+ nLineRange = ( rCtx.maMapIY[ nBottom ] == rCtx.maMapIY[ nTop ] ) ? 1 :( rCtx.maMapIY[ nBottom ] - rCtx.maMapIY[ nTop ] );
+ }
+
+ Scanline pScanDest = rCtx.mpDest->GetScanline( nY );
+ for( sal_Int32 nX = nStartX , nXDst = 0; nX <= nEndX; nX++ )
+ {
+ sal_Int32 nLeft = rCtx.mbHMirr ? ( nX + 1 ) : nX;
+ sal_Int32 nRight = rCtx.mbHMirr ? nX : ( nX + 1 ) ;
+
+ sal_Int32 nRowStart;
+ sal_Int32 nRowRange;
+ if( nX == nEndX )
+ {
+ nRowStart = rCtx.maMapIX[ nX ];
+ nRowRange = 0;
+ }
+ else
+ {
+ nRowStart = rCtx.maMapIX[ nLeft ];
+ nRowRange = ( rCtx.maMapIX[ nRight ] == rCtx.maMapIX[ nLeft ] )? 1 : ( rCtx.maMapIX[ nRight ] - rCtx.maMapIX[ nLeft ] );
+ }
+
+ int nSumR = 0;
+ int nSumG = 0;
+ int nSumB = 0;
+ BilinearWeightType nTotalWeightY = 0;
+
+ for(sal_Int32 i = 0; i<= nLineRange; i++)
+ {
+ Scanline pTmpY = rCtx.mpSrc->GetScanline( nLineStart + i );
+ int nSumRowR = 0;
+ int nSumRowG = 0;
+ int nSumRowB = 0;
+ BilinearWeightType nTotalWeightX = 0;
+
+ for(sal_Int32 j = 0; j <= nRowRange; j++)
+ {
+ const BitmapColor& rCol = rCtx.mpSrc->GetPaletteColor( pTmpY[ nRowStart + j ] );
+
+ if(nX == nEndX )
+ {
+ nSumRowB += rCol.GetBlue() << MAP_PRECISION;
+ nSumRowG += rCol.GetGreen() << MAP_PRECISION;
+ nSumRowR += rCol.GetRed() << MAP_PRECISION;
+ nTotalWeightX += lclMaxWeight();
+ }
+ else if( j == 0 )
+ {
+ BilinearWeightType nWeightX = lclMaxWeight() - rCtx.maMapFX[ nLeft ];
+ nSumRowB += ( nWeightX *rCol.GetBlue()) ;
+ nSumRowG += ( nWeightX *rCol.GetGreen()) ;
+ nSumRowR += ( nWeightX *rCol.GetRed()) ;
+ nTotalWeightX += nWeightX;
+ }
+ else if ( nRowRange == j )
+ {
+ BilinearWeightType nWeightX = rCtx.maMapFX[ nRight ] ;
+ nSumRowB += ( nWeightX *rCol.GetBlue() );
+ nSumRowG += ( nWeightX *rCol.GetGreen() );
+ nSumRowR += ( nWeightX *rCol.GetRed() );
+ nTotalWeightX += nWeightX;
+ }
+ else
+ {
+ nSumRowB += rCol.GetBlue() << MAP_PRECISION;
+ nSumRowG += rCol.GetGreen() << MAP_PRECISION;
+ nSumRowR += rCol.GetRed() << MAP_PRECISION;
+ nTotalWeightX += lclMaxWeight();
+ }
+ }
+
+ BilinearWeightType nWeightY = lclMaxWeight();
+ if( nY == nEndY )
+ nWeightY = lclMaxWeight();
+ else if( i == 0 )
+ nWeightY = lclMaxWeight() - rCtx.maMapFY[ nTop ];
+ else if( nLineRange == 1 )
+ nWeightY = rCtx.maMapFY[ nTop ];
+ else if ( nLineRange == i )
+ nWeightY = rCtx.maMapFY[ nBottom ];
+
+ if (nTotalWeightX)
+ {
+ nSumRowB /= nTotalWeightX;
+ nSumRowG /= nTotalWeightX;
+ nSumRowR /= nTotalWeightX;
+ }
+
+ nSumB += nWeightY * nSumRowB;
+ nSumG += nWeightY * nSumRowG;
+ nSumR += nWeightY * nSumRowR;
+ nTotalWeightY += nWeightY;
+ }
+
+ if (nTotalWeightY)
+ {
+ nSumR /= nTotalWeightY;
+ nSumG /= nTotalWeightY;
+ nSumB /= nTotalWeightY;
+ }
+
+ BitmapColor aColRes(static_cast<sal_uInt8>(nSumR), static_cast<sal_uInt8>(nSumG), static_cast<sal_uInt8>(nSumB));
+ rCtx.mpDest->SetPixelOnData( pScanDest, nXDst++, aColRes );
+ }
+ }
+}
+
+void scaleDownPaletteGeneral(const ScaleContext &rCtx, sal_Int32 nStartY, sal_Int32 nEndY)
+{
+ const sal_Int32 nStartX = 0, nEndX = rCtx.mnDestW - 1;
+
+ for( sal_Int32 nY = nStartY; nY <= nEndY; nY++ )
+ {
+ sal_Int32 nTop = rCtx.mbVMirr ? ( nY + 1 ) : nY;
+ sal_Int32 nBottom = rCtx.mbVMirr ? nY : ( nY + 1 ) ;
+
+ sal_Int32 nLineStart, nLineRange;
+ if( nY ==nEndY )
+ {
+ nLineStart = rCtx.maMapIY[ nY ];
+ nLineRange = 0;
+ }
+ else
+ {
+ nLineStart = rCtx.maMapIY[ nTop ] ;
+ nLineRange = ( rCtx.maMapIY[ nBottom ] == rCtx.maMapIY[ nTop ] ) ? 1 :( rCtx.maMapIY[ nBottom ] - rCtx.maMapIY[ nTop ] );
+ }
+
+ Scanline pScanDest = rCtx.mpDest->GetScanline( nY );
+ for( sal_Int32 nX = nStartX , nXDst = 0; nX <= nEndX; nX++ )
+ {
+ sal_Int32 nLeft = rCtx.mbHMirr ? ( nX + 1 ) : nX;
+ sal_Int32 nRight = rCtx.mbHMirr ? nX : ( nX + 1 ) ;
+
+ sal_Int32 nRowStart, nRowRange;
+ if( nX == nEndX )
+ {
+ nRowStart = rCtx.maMapIX[ nX ];
+ nRowRange = 0;
+ }
+ else
+ {
+ nRowStart = rCtx.maMapIX[ nLeft ];
+ nRowRange = ( rCtx.maMapIX[ nRight ] == rCtx.maMapIX[ nLeft ] )? 1 : ( rCtx.maMapIX[ nRight ] - rCtx.maMapIX[ nLeft ] );
+ }
+
+ int nSumR = 0;
+ int nSumG = 0;
+ int nSumB = 0;
+ BilinearWeightType nTotalWeightY = 0;
+
+ for(sal_Int32 i = 0; i<= nLineRange; i++)
+ {
+ int nSumRowR = 0;
+ int nSumRowG = 0;
+ int nSumRowB = 0;
+ BilinearWeightType nTotalWeightX = 0;
+
+ Scanline pScanlineSrc = rCtx.mpSrc->GetScanline( nLineStart + i );
+ for(sal_Int32 j = 0; j <= nRowRange; j++)
+ {
+ BitmapColor aCol0 = rCtx.mpSrc->GetPaletteColor ( rCtx.mpSrc->GetIndexFromData( pScanlineSrc, nRowStart + j ) );
+
+ if(nX == nEndX )
+ {
+
+ nSumRowB += aCol0.GetBlue() << MAP_PRECISION;
+ nSumRowG += aCol0.GetGreen() << MAP_PRECISION;
+ nSumRowR += aCol0.GetRed() << MAP_PRECISION;
+ nTotalWeightX += lclMaxWeight();
+ }
+ else if( j == 0 )
+ {
+
+ BilinearWeightType nWeightX = lclMaxWeight() - rCtx.maMapFX[ nLeft ];
+ nSumRowB += ( nWeightX *aCol0.GetBlue()) ;
+ nSumRowG += ( nWeightX *aCol0.GetGreen()) ;
+ nSumRowR += ( nWeightX *aCol0.GetRed()) ;
+ nTotalWeightX += nWeightX;
+ }
+ else if ( nRowRange == j )
+ {
+
+ BilinearWeightType nWeightX = rCtx.maMapFX[ nRight ] ;
+ nSumRowB += ( nWeightX *aCol0.GetBlue() );
+ nSumRowG += ( nWeightX *aCol0.GetGreen() );
+ nSumRowR += ( nWeightX *aCol0.GetRed() );
+ nTotalWeightX += nWeightX;
+ }
+ else
+ {
+
+ nSumRowB += aCol0.GetBlue() << MAP_PRECISION;
+ nSumRowG += aCol0.GetGreen() << MAP_PRECISION;
+ nSumRowR += aCol0.GetRed() << MAP_PRECISION;
+ nTotalWeightX += lclMaxWeight();
+ }
+ }
+
+ sal_Int32 nWeightY = lclMaxWeight();
+ if( nY == nEndY )
+ nWeightY = lclMaxWeight();
+ else if( i == 0 )
+ nWeightY = lclMaxWeight() - rCtx.maMapFY[ nTop ];
+ else if( nLineRange == 1 )
+ nWeightY = rCtx.maMapFY[ nTop ];
+ else if ( nLineRange == i )
+ nWeightY = rCtx.maMapFY[ nBottom ];
+
+ if (nTotalWeightX)
+ {
+ nSumRowB /= nTotalWeightX;
+ nSumRowG /= nTotalWeightX;
+ nSumRowR /= nTotalWeightX;
+ }
+
+ nSumB += nWeightY * nSumRowB;
+ nSumG += nWeightY * nSumRowG;
+ nSumR += nWeightY * nSumRowR;
+ nTotalWeightY += nWeightY;
+ }
+
+ if (nTotalWeightY)
+ {
+ nSumR /= nTotalWeightY;
+ nSumG /= nTotalWeightY;
+ nSumB /= nTotalWeightY;
+ }
+
+ BitmapColor aColRes(static_cast<sal_uInt8>(nSumR), static_cast<sal_uInt8>(nSumG), static_cast<sal_uInt8>(nSumB));
+ rCtx.mpDest->SetPixelOnData( pScanDest, nXDst++, aColRes );
+ }
+ }
+}
+
+void scaleDownNonPaletteGeneral(const ScaleContext &rCtx, sal_Int32 nStartY, sal_Int32 nEndY)
+{
+ const sal_Int32 nStartX = 0, nEndX = rCtx.mnDestW - 1;
+
+ for( sal_Int32 nY = nStartY; nY <= nEndY; nY++ )
+ {
+ sal_Int32 nTop = rCtx.mbVMirr ? ( nY + 1 ) : nY;
+ sal_Int32 nBottom = rCtx.mbVMirr ? nY : ( nY + 1 ) ;
+
+ sal_Int32 nLineStart, nLineRange;
+ if( nY ==nEndY )
+ {
+ nLineStart = rCtx.maMapIY[ nY ];
+ nLineRange = 0;
+ }
+ else
+ {
+ nLineStart = rCtx.maMapIY[ nTop ] ;
+ nLineRange = ( rCtx.maMapIY[ nBottom ] == rCtx.maMapIY[ nTop ] ) ? 1 :( rCtx.maMapIY[ nBottom ] - rCtx.maMapIY[ nTop ] );
+ }
+
+ Scanline pScanDest = rCtx.mpDest->GetScanline( nY );
+ for( sal_Int32 nX = nStartX , nXDst = 0; nX <= nEndX; nX++ )
+ {
+ sal_Int32 nLeft = rCtx.mbHMirr ? ( nX + 1 ) : nX;
+ sal_Int32 nRight = rCtx.mbHMirr ? nX : ( nX + 1 ) ;
+
+ sal_Int32 nRowStart, nRowRange;
+ if( nX == nEndX )
+ {
+ nRowStart = rCtx.maMapIX[ nX ];
+ nRowRange = 0;
+ }
+ else
+ {
+ nRowStart = rCtx.maMapIX[ nLeft ];
+ nRowRange = ( rCtx.maMapIX[ nRight ] == rCtx.maMapIX[ nLeft ] )? 1 : ( rCtx.maMapIX[ nRight ] - rCtx.maMapIX[ nLeft ] );
+ }
+
+ int nSumR = 0;
+ int nSumG = 0;
+ int nSumB = 0;
+ BilinearWeightType nTotalWeightY = 0;
+
+ for(sal_Int32 i = 0; i<= nLineRange; i++)
+ {
+ int nSumRowR = 0;
+ int nSumRowG = 0;
+ int nSumRowB = 0;
+ BilinearWeightType nTotalWeightX = 0;
+
+ Scanline pScanlineSrc = rCtx.mpSrc->GetScanline( nLineStart + i );
+ for(sal_Int32 j = 0; j <= nRowRange; j++)
+ {
+ BitmapColor aCol0 = rCtx.mpSrc->GetPixelFromData( pScanlineSrc, nRowStart + j );
+
+ if(nX == nEndX )
+ {
+
+ nSumRowB += aCol0.GetBlue() << MAP_PRECISION;
+ nSumRowG += aCol0.GetGreen() << MAP_PRECISION;
+ nSumRowR += aCol0.GetRed() << MAP_PRECISION;
+ nTotalWeightX += lclMaxWeight();
+ }
+ else if( j == 0 )
+ {
+
+ BilinearWeightType nWeightX = lclMaxWeight() - rCtx.maMapFX[ nLeft ];
+ nSumRowB += ( nWeightX *aCol0.GetBlue()) ;
+ nSumRowG += ( nWeightX *aCol0.GetGreen()) ;
+ nSumRowR += ( nWeightX *aCol0.GetRed()) ;
+ nTotalWeightX += nWeightX;
+ }
+ else if ( nRowRange == j )
+ {
+
+ BilinearWeightType nWeightX = rCtx.maMapFX[ nRight ] ;
+ nSumRowB += ( nWeightX *aCol0.GetBlue() );
+ nSumRowG += ( nWeightX *aCol0.GetGreen() );
+ nSumRowR += ( nWeightX *aCol0.GetRed() );
+ nTotalWeightX += nWeightX;
+ }
+ else
+ {
+ nSumRowB += aCol0.GetBlue() << MAP_PRECISION;
+ nSumRowG += aCol0.GetGreen() << MAP_PRECISION;
+ nSumRowR += aCol0.GetRed() << MAP_PRECISION;
+ nTotalWeightX += lclMaxWeight();
+ }
+ }
+
+ BilinearWeightType nWeightY = lclMaxWeight();
+ if( nY == nEndY )
+ nWeightY = lclMaxWeight();
+ else if( i == 0 )
+ nWeightY = lclMaxWeight() - rCtx.maMapFY[ nTop ];
+ else if( nLineRange == 1 )
+ nWeightY = rCtx.maMapFY[ nTop ];
+ else if ( nLineRange == i )
+ nWeightY = rCtx.maMapFY[ nBottom ];
+
+ if (nTotalWeightX)
+ {
+ nSumRowB /= nTotalWeightX;
+ nSumRowG /= nTotalWeightX;
+ nSumRowR /= nTotalWeightX;
+ }
+
+ nSumB += nWeightY * nSumRowB;
+ nSumG += nWeightY * nSumRowG;
+ nSumR += nWeightY * nSumRowR;
+ nTotalWeightY += nWeightY;
+ }
+
+ if (nTotalWeightY)
+ {
+ nSumR /= nTotalWeightY;
+ nSumG /= nTotalWeightY;
+ nSumB /= nTotalWeightY;
+ }
+
+ BitmapColor aColRes(static_cast<sal_uInt8>(nSumR), static_cast<sal_uInt8>(nSumG), static_cast<sal_uInt8>(nSumB));
+ rCtx.mpDest->SetPixelOnData( pScanDest, nXDst++, aColRes );
+ }
+ }
+}
+
+} // end anonymous namespace
+
+BitmapScaleSuperFilter::BitmapScaleSuperFilter(const double& rScaleX, const double& rScaleY) :
+ mrScaleX(rScaleX),
+ mrScaleY(rScaleY)
+{}
+
+BitmapScaleSuperFilter::~BitmapScaleSuperFilter()
+{}
+
+BitmapEx BitmapScaleSuperFilter::execute(BitmapEx const& rBitmap) const
+{
+ Bitmap aBitmap(rBitmap.GetBitmap());
+ bool bRet = false;
+
+ const Size aSizePix(rBitmap.GetSizePixel());
+
+ bool bHMirr = mrScaleX < 0;
+ bool bVMirr = mrScaleY < 0;
+
+ double fScaleX = std::fabs(mrScaleX);
+ double fScaleY = std::fabs(mrScaleY);
+
+ const sal_Int32 nDstW = FRound(aSizePix.Width() * fScaleX);
+ const sal_Int32 nDstH = FRound(aSizePix.Height() * fScaleY);
+
+ constexpr double fScaleThresh = 0.6;
+
+ if (nDstW <= 1 || nDstH <= 1)
+ return BitmapEx();
+
+ // check cache for a previously scaled version of this
+ ScaleCacheKey aKey(aBitmap.ImplGetSalBitmap().get(),
+ Size(nDstW, nDstH));
+
+ ImplSVData* pSVData = ImplGetSVData();
+ auto& rCache = pSVData->maGDIData.maScaleCache;
+ auto aFind = rCache.find(aKey);
+ if (aFind != rCache.end())
+ {
+ if (aFind->second.GetSizePixel().Width() == nDstW && aFind->second.GetSizePixel().Height() == nDstH)
+ return aFind->second;
+ else
+ SAL_WARN("vcl.gdi", "Error: size mismatch in scale cache");
+ }
+
+ {
+ BitmapScopedReadAccess pReadAccess(aBitmap);
+
+ // If source format is less than 24BPP, use 24BPP
+ auto eSourcePixelFormat = aBitmap.getPixelFormat();
+ auto ePixelFormat = eSourcePixelFormat;
+ if (sal_uInt16(eSourcePixelFormat) < 24)
+ ePixelFormat = vcl::PixelFormat::N24_BPP;
+
+ Bitmap aOutBmp(Size(nDstW, nDstH), ePixelFormat);
+ Size aOutSize = aOutBmp.GetSizePixel();
+ auto eTargetPixelFormat = aOutBmp.getPixelFormat();
+
+ if (!aOutSize.Width() || !aOutSize.Height())
+ {
+ SAL_WARN("vcl.gdi", "bmp creation failed");
+ return BitmapEx();
+ }
+
+ BitmapScopedWriteAccess pWriteAccess(aOutBmp);
+
+ const sal_Int32 nEndY = nDstH - 1;
+
+ if (pReadAccess && pWriteAccess)
+ {
+ ScaleRangeFn pScaleRangeFn;
+ const ScaleContext aContext( pReadAccess.get(),
+ pWriteAccess.get(),
+ pReadAccess->Width(),
+ pWriteAccess->Width(),
+ pReadAccess->Height(),
+ pWriteAccess->Height(),
+ bVMirr, bHMirr );
+
+ bool bScaleUp = fScaleX >= fScaleThresh && fScaleY >= fScaleThresh;
+ // If we have a source bitmap with a palette the scaling converts
+ // from up to 8 bit image -> 24 bit non-palette, which is then
+ // adapted back to the same type as original.
+ if (pReadAccess->HasPalette())
+ {
+ switch( pReadAccess->GetScanlineFormat() )
+ {
+ case ScanlineFormat::N8BitPal:
+ pScaleRangeFn = bScaleUp ? scaleUpPalette8bit
+ : scaleDownPalette8bit;
+ break;
+ default:
+ pScaleRangeFn = bScaleUp ? scaleUpPaletteGeneral
+ : scaleDownPaletteGeneral;
+ break;
+ }
+ }
+ // Here we know that we are dealing with a non-palette source bitmap.
+ // The target is either 24 or 32 bit, depending on the image and
+ // the capabilities of the backend. If for some reason the destination
+ // is not the same bit-depth as the source, then we can't use
+ // a fast path, so we always need to process with a general scaler.
+ else if (eSourcePixelFormat != eTargetPixelFormat)
+ {
+ pScaleRangeFn = bScaleUp ? scaleUpNonPaletteGeneral : scaleDownNonPaletteGeneral;
+ }
+ // If we get here then we can only use a fast path, but let's
+ // still keep the fallback to the general scaler alive.
+ else
+ {
+ switch( pReadAccess->GetScanlineFormat() )
+ {
+ case ScanlineFormat::N24BitTcBgr:
+ case ScanlineFormat::N24BitTcRgb:
+ pScaleRangeFn = bScaleUp ? scaleUp<24> : scaleDown<24>;
+ break;
+ case ScanlineFormat::N32BitTcRgba:
+ case ScanlineFormat::N32BitTcBgra:
+ case ScanlineFormat::N32BitTcArgb:
+ case ScanlineFormat::N32BitTcAbgr:
+ pScaleRangeFn = bScaleUp ? scaleUp<32> : scaleDown<32>;
+ break;
+ default:
+ pScaleRangeFn = bScaleUp ? scaleUpNonPaletteGeneral
+ : scaleDownNonPaletteGeneral;
+ break;
+ }
+ }
+
+ // We want to thread - only if there is a lot of work to do:
+ // We work hard when there is a large destination image, or
+ // A large source image.
+ bool bHorizontalWork = pReadAccess->Height() >= 512 && pReadAccess->Width() >= 512;
+ bool bUseThreads = true;
+ const sal_Int32 nStartY = 0;
+
+ static bool bDisableThreadedScaling = getenv ("VCL_NO_THREAD_SCALE");
+ if (bDisableThreadedScaling || !bHorizontalWork)
+ {
+ SAL_INFO("vcl.gdi", "Scale in main thread");
+ bUseThreads = false;
+ }
+
+ if (bUseThreads)
+ {
+ try
+ {
+ // partition and queue work
+ comphelper::ThreadPool &rShared = comphelper::ThreadPool::getSharedOptimalPool();
+ std::shared_ptr<comphelper::ThreadTaskTag> pTag = comphelper::ThreadPool::createThreadTaskTag();
+
+ vcl::bitmap::generateStripRanges<constScaleThreadStrip>(nStartY, nEndY,
+ [&] (sal_Int32 const nStart, sal_Int32 const nEnd, bool const bLast)
+ {
+ if (!bLast)
+ {
+ auto pTask(std::make_unique<ScaleTask>(pTag, pScaleRangeFn, aContext, nStart, nEnd));
+ rShared.pushTask(std::move(pTask));
+ }
+ else
+ pScaleRangeFn(aContext, nStart, nEnd);
+ });
+ rShared.waitUntilDone(pTag);
+ SAL_INFO("vcl.gdi", "All threaded scaling tasks complete");
+ }
+ catch (...)
+ {
+ SAL_WARN("vcl.gdi", "threaded bitmap scaling failed");
+ bUseThreads = false;
+ }
+ }
+
+ if (!bUseThreads)
+ pScaleRangeFn( aContext, nStartY, nEndY );
+
+ pWriteAccess.reset();
+ bRet = true;
+ aBitmap.AdaptBitCount(aOutBmp);
+ aBitmap = aOutBmp;
+ }
+ }
+
+ if (bRet)
+ {
+ tools::Rectangle aRect(Point(0, 0), Point(nDstW, nDstH));
+ aBitmap.Crop(aRect);
+ BitmapEx aRet(aBitmap);
+ rCache.insert(std::make_pair(aKey, aRet));
+ return aRet;
+ }
+
+ return BitmapEx();
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapSeparableUnsharpenFilter.cxx b/vcl/source/bitmap/BitmapSeparableUnsharpenFilter.cxx
new file mode 100644
index 0000000000..a65cd6ee56
--- /dev/null
+++ b/vcl/source/bitmap/BitmapSeparableUnsharpenFilter.cxx
@@ -0,0 +1,75 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#include <tools/helpers.hxx>
+
+#include <vcl/bitmap.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/BitmapGaussianSeparableBlurFilter.hxx>
+#include <vcl/BitmapSeparableUnsharpenFilter.hxx>
+#include <vcl/BitmapWriteAccess.hxx>
+
+BitmapEx BitmapSeparableUnsharpenFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ Bitmap aBitmap(rBitmapEx.GetBitmap());
+
+ const sal_Int32 nWidth = aBitmap.GetSizePixel().Width();
+ const sal_Int32 nHeight = aBitmap.GetSizePixel().Height();
+
+ Bitmap aBlur(aBitmap);
+ BitmapEx aBlurEx(aBlur);
+
+ BitmapFilter::Filter(aBlurEx, BitmapGaussianSeparableBlurFilter(-mfRadius));
+ aBlur = aBlurEx.GetBitmap();
+
+ // Amount of unsharpening effect on image - currently set to a fixed value
+ double aAmount = 2.0;
+
+ Bitmap aResultBitmap(Size(nWidth, nHeight), vcl::PixelFormat::N24_BPP);
+
+ BitmapScopedReadAccess pReadAccBlur(aBlur);
+ BitmapScopedReadAccess pReadAcc(aBitmap);
+ BitmapScopedWriteAccess pWriteAcc(aResultBitmap);
+
+ BitmapColor aColor, aColorBlur;
+
+ // For all pixels in original image subtract pixels values from blurred image
+ for (sal_Int32 y = 0; y < nHeight; y++)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(y);
+ for (sal_Int32 x = 0; x < nWidth; x++)
+ {
+ aColorBlur = pReadAccBlur->GetColor(y, x);
+ aColor = pReadAcc->GetColor(y, x);
+
+ BitmapColor aResultColor(
+ static_cast<sal_uInt8>(
+ std::clamp(aColor.GetRed() + (aColor.GetRed() - aColorBlur.GetRed()) * aAmount,
+ 0.0, 255.0)),
+ static_cast<sal_uInt8>(std::clamp(
+ aColor.GetGreen() + (aColor.GetGreen() - aColorBlur.GetGreen()) * aAmount, 0.0,
+ 255.0)),
+ static_cast<sal_uInt8>(std::clamp(
+ aColor.GetBlue() + (aColor.GetBlue() - aColorBlur.GetBlue()) * aAmount, 0.0,
+ 255.0)));
+
+ pWriteAcc->SetPixelOnData(pScanline, x, aResultColor);
+ }
+ }
+
+ pWriteAcc.reset();
+ pReadAcc.reset();
+ pReadAccBlur.reset();
+ aBitmap.ReassignWithSize(aResultBitmap);
+
+ return BitmapEx(aBitmap);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapSepiaFilter.cxx b/vcl/source/bitmap/BitmapSepiaFilter.cxx
new file mode 100644
index 0000000000..fdc5a8a722
--- /dev/null
+++ b/vcl/source/bitmap/BitmapSepiaFilter.cxx
@@ -0,0 +1,98 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#include <sal/config.h>
+
+#include <vcl/bitmap.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/BitmapSepiaFilter.hxx>
+#include <vcl/BitmapWriteAccess.hxx>
+
+#include <algorithm>
+
+BitmapEx BitmapSepiaFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ Bitmap aBitmap(rBitmapEx.GetBitmap());
+ BitmapScopedReadAccess pReadAcc(aBitmap);
+ if (!pReadAcc)
+ return BitmapEx();
+
+ const sal_Int32 nSepia
+ = 10000 - 100 * std::clamp(mnSepiaPercent, sal_uInt16(0), sal_uInt16(100));
+ BitmapPalette aSepiaPal(256);
+
+ for (sal_uInt16 i = 0; i < 256; i++)
+ {
+ BitmapColor& rCol = aSepiaPal[i];
+ const sal_uInt8 cSepiaValue = static_cast<sal_uInt8>(nSepia * i / 10000);
+
+ rCol.SetRed(static_cast<sal_uInt8>(i));
+ rCol.SetGreen(cSepiaValue);
+ rCol.SetBlue(cSepiaValue);
+ }
+
+ Bitmap aNewBmp(aBitmap.GetSizePixel(), vcl::PixelFormat::N8_BPP, &aSepiaPal);
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+ if (!pWriteAcc)
+ return BitmapEx();
+
+ BitmapColor aCol(sal_uInt8(0));
+ const sal_Int32 nWidth = pWriteAcc->Width();
+ const sal_Int32 nHeight = pWriteAcc->Height();
+
+ if (pReadAcc->HasPalette())
+ {
+ const sal_uInt16 nPalCount = pReadAcc->GetPaletteEntryCount();
+ std::unique_ptr<sal_uInt8[]> pIndexMap(new sal_uInt8[nPalCount]);
+ for (sal_uInt16 i = 0; i < nPalCount; i++)
+ {
+ pIndexMap[i] = pReadAcc->GetPaletteColor(i).GetLuminance();
+ }
+
+ for (sal_Int32 nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ Scanline pScanlineRead = pReadAcc->GetScanline(nY);
+ for (sal_Int32 nX = 0; nX < nWidth; nX++)
+ {
+ aCol.SetIndex(pIndexMap[pReadAcc->GetIndexFromData(pScanlineRead, nX)]);
+ pWriteAcc->SetPixelOnData(pScanline, nX, aCol);
+ }
+ }
+ }
+ else
+ {
+ for (sal_Int32 nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ Scanline pScanlineRead = pReadAcc->GetScanline(nY);
+ for (sal_Int32 nX = 0; nX < nWidth; nX++)
+ {
+ aCol.SetIndex(pReadAcc->GetPixelFromData(pScanlineRead, nX).GetLuminance());
+ pWriteAcc->SetPixelOnData(pScanline, nX, aCol);
+ }
+ }
+ }
+
+ pWriteAcc.reset();
+ pReadAcc.reset();
+
+ const MapMode aMap(aBitmap.GetPrefMapMode());
+ const Size aPrefSize(aBitmap.GetPrefSize());
+
+ aBitmap = aNewBmp;
+
+ aBitmap.SetPrefMapMode(aMap);
+ aBitmap.SetPrefSize(aPrefSize);
+
+ return BitmapEx(aBitmap);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapShadowFilter.cxx b/vcl/source/bitmap/BitmapShadowFilter.cxx
new file mode 100644
index 0000000000..76da85b3bf
--- /dev/null
+++ b/vcl/source/bitmap/BitmapShadowFilter.cxx
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#include <vcl/bitmapex.hxx>
+#include <vcl/BitmapColor.hxx>
+#include <vcl/BitmapShadowFilter.hxx>
+#include <vcl/BitmapWriteAccess.hxx>
+
+BitmapEx BitmapShadowFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ BitmapEx aBitmapEx(rBitmapEx);
+ BitmapScopedWriteAccess pWriteAccess(const_cast<Bitmap&>(aBitmapEx.GetBitmap()));
+
+ if (!pWriteAccess)
+ return rBitmapEx;
+
+ for (sal_Int32 y(0); y < sal_Int32(pWriteAccess->Height()); y++)
+ {
+ Scanline pScanline = pWriteAccess->GetScanline(y);
+
+ for (sal_Int32 x(0); x < sal_Int32(pWriteAccess->Width()); x++)
+ {
+ const BitmapColor aColor = pWriteAccess->GetColor(y, x);
+ sal_uInt16 nLuminance(static_cast<sal_uInt16>(aColor.GetLuminance()) + 1);
+ const BitmapColor aDestColor(
+ static_cast<sal_uInt8>(
+ (nLuminance * static_cast<sal_uInt16>(maShadowColor.GetRed())) >> 8),
+ static_cast<sal_uInt8>(
+ (nLuminance * static_cast<sal_uInt16>(maShadowColor.GetGreen())) >> 8),
+ static_cast<sal_uInt8>(
+ (nLuminance * static_cast<sal_uInt16>(maShadowColor.GetBlue())) >> 8));
+
+ pWriteAccess->SetPixelOnData(pScanline, x, aDestColor);
+ }
+ }
+
+ return aBitmapEx;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapSimpleColorQuantizationFilter.cxx b/vcl/source/bitmap/BitmapSimpleColorQuantizationFilter.cxx
new file mode 100644
index 0000000000..4dc045be2d
--- /dev/null
+++ b/vcl/source/bitmap/BitmapSimpleColorQuantizationFilter.cxx
@@ -0,0 +1,88 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#include <vcl/bitmap.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/BitmapSimpleColorQuantizationFilter.hxx>
+#include <vcl/BitmapWriteAccess.hxx>
+#include <bitmap/Octree.hxx>
+
+BitmapEx BitmapSimpleColorQuantizationFilter::execute(BitmapEx const& aBitmapEx) const
+{
+ Bitmap aBitmap = aBitmapEx.GetBitmap();
+
+ if (vcl::numberOfColors(aBitmap.getPixelFormat()) <= sal_Int64(mnNewColorCount))
+ return BitmapEx(aBitmap);
+
+ Bitmap aNewBmp;
+ BitmapScopedReadAccess pRAcc(aBitmap);
+ if (!pRAcc)
+ return BitmapEx();
+
+ const sal_uInt16 nColorCount = std::min(mnNewColorCount, sal_uInt16(256));
+ auto ePixelFormat = vcl::PixelFormat::N8_BPP;
+
+ Octree aOct(*pRAcc, nColorCount);
+ const BitmapPalette& rPal = aOct.GetPalette();
+
+ aNewBmp = Bitmap(aBitmap.GetSizePixel(), ePixelFormat, &rPal);
+ BitmapScopedWriteAccess pWAcc(aNewBmp);
+ if (!pWAcc)
+ return BitmapEx();
+
+ const sal_Int32 nWidth = pRAcc->Width();
+ const sal_Int32 nHeight = pRAcc->Height();
+
+ if (pRAcc->HasPalette())
+ {
+ for (sal_Int32 nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pWAcc->GetScanline(nY);
+ Scanline pScanlineRead = pRAcc->GetScanline(nY);
+ for (sal_Int32 nX = 0; nX < nWidth; nX++)
+ {
+ auto c = pRAcc->GetPaletteColor(pRAcc->GetIndexFromData(pScanlineRead, nX));
+ pWAcc->SetPixelOnData(
+ pScanline, nX,
+ BitmapColor(static_cast<sal_uInt8>(aOct.GetBestPaletteIndex(c))));
+ }
+ }
+ }
+ else
+ {
+ for (sal_Int32 nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pWAcc->GetScanline(nY);
+ Scanline pScanlineRead = pRAcc->GetScanline(nY);
+ for (sal_Int32 nX = 0; nX < nWidth; nX++)
+ {
+ auto c = pRAcc->GetPixelFromData(pScanlineRead, nX);
+ pWAcc->SetPixelOnData(
+ pScanline, nX,
+ BitmapColor(static_cast<sal_uInt8>(aOct.GetBestPaletteIndex(c))));
+ }
+ }
+ }
+
+ pWAcc.reset();
+ pRAcc.reset();
+
+ const MapMode aMap(aBitmap.GetPrefMapMode());
+ const Size aSize(aBitmap.GetPrefSize());
+
+ aBitmap = aNewBmp;
+
+ aBitmap.SetPrefMapMode(aMap);
+ aBitmap.SetPrefSize(aSize);
+
+ return BitmapEx(aBitmap);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapSmoothenFilter.cxx b/vcl/source/bitmap/BitmapSmoothenFilter.cxx
new file mode 100644
index 0000000000..e9c135f8ec
--- /dev/null
+++ b/vcl/source/bitmap/BitmapSmoothenFilter.cxx
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#include <vcl/bitmapex.hxx>
+#include <vcl/BitmapGaussianSeparableBlurFilter.hxx>
+#include <vcl/BitmapSeparableUnsharpenFilter.hxx>
+#include <vcl/BitmapSmoothenFilter.hxx>
+
+BitmapEx BitmapSmoothenFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ BitmapEx aBitmapEx(rBitmapEx);
+ bool bRet = false;
+
+ if (mfRadius > 0.0) // Blur for positive values of mnRadius
+ bRet = BitmapFilter::Filter(aBitmapEx, BitmapGaussianSeparableBlurFilter(mfRadius));
+ else if (mfRadius < 0.0) // Unsharpen mask for negative values of mnRadius
+ bRet = BitmapFilter::Filter(aBitmapEx, BitmapSeparableUnsharpenFilter(mfRadius));
+
+ if (bRet)
+ return aBitmapEx;
+
+ return BitmapEx();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapSobelGreyFilter.cxx b/vcl/source/bitmap/BitmapSobelGreyFilter.cxx
new file mode 100644
index 0000000000..c1c6822757
--- /dev/null
+++ b/vcl/source/bitmap/BitmapSobelGreyFilter.cxx
@@ -0,0 +1,154 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#include <sal/config.h>
+
+#include <vcl/bitmap.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/BitmapSobelGreyFilter.hxx>
+#include <vcl/BitmapWriteAccess.hxx>
+
+#include <algorithm>
+
+BitmapEx BitmapSobelGreyFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ Bitmap aBitmap(rBitmapEx.GetBitmap());
+
+ if (!aBitmap.ImplMakeGreyscales())
+ return BitmapEx();
+
+ BitmapScopedReadAccess pReadAcc(aBitmap);
+ if (!pReadAcc)
+ return BitmapEx();
+
+ Bitmap aNewBmp(aBitmap.GetSizePixel(), vcl::PixelFormat::N8_BPP, &pReadAcc->GetPalette());
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+ if (!pWriteAcc)
+ return BitmapEx();
+
+ BitmapColor aGrey(sal_uInt8(0));
+ const sal_Int32 nWidth = pWriteAcc->Width();
+ const sal_Int32 nHeight = pWriteAcc->Height();
+ const sal_Int32 nMask111 = -1, nMask121 = 0, nMask131 = 1;
+ const sal_Int32 nMask211 = -2, nMask221 = 0, nMask231 = 2;
+ const sal_Int32 nMask311 = -1, nMask321 = 0, nMask331 = 1;
+ const sal_Int32 nMask112 = 1, nMask122 = 2, nMask132 = 1;
+ const sal_Int32 nMask212 = 0, nMask222 = 0, nMask232 = 0;
+ const sal_Int32 nMask312 = -1, nMask322 = -2, nMask332 = -1;
+ sal_Int32 nGrey11, nGrey12, nGrey13;
+ sal_Int32 nGrey21, nGrey22, nGrey23;
+ sal_Int32 nGrey31, nGrey32, nGrey33;
+ std::unique_ptr<long[]> pHMap(new long[nWidth + 2]);
+ std::unique_ptr<long[]> pVMap(new long[nHeight + 2]);
+ sal_Int32 nX, nY, nSum1, nSum2;
+
+ // fill mapping tables
+ pHMap[0] = 0;
+
+ for (nX = 1; nX <= nWidth; nX++)
+ {
+ pHMap[nX] = nX - 1;
+ }
+
+ pHMap[nWidth + 1] = nWidth - 1;
+
+ pVMap[0] = 0;
+
+ for (nY = 1; nY <= nHeight; nY++)
+ {
+ pVMap[nY] = nY - 1;
+ }
+
+ pVMap[nHeight + 1] = nHeight - 1;
+
+ for (nY = 0; nY < nHeight; nY++)
+ {
+ nGrey11 = pReadAcc->GetPixel(pVMap[nY], pHMap[0]).GetIndex();
+ nGrey12 = pReadAcc->GetPixel(pVMap[nY], pHMap[1]).GetIndex();
+ nGrey13 = pReadAcc->GetPixel(pVMap[nY], pHMap[2]).GetIndex();
+ nGrey21 = pReadAcc->GetPixel(pVMap[nY + 1], pHMap[0]).GetIndex();
+ nGrey22 = pReadAcc->GetPixel(pVMap[nY + 1], pHMap[1]).GetIndex();
+ nGrey23 = pReadAcc->GetPixel(pVMap[nY + 1], pHMap[2]).GetIndex();
+ nGrey31 = pReadAcc->GetPixel(pVMap[nY + 2], pHMap[0]).GetIndex();
+ nGrey32 = pReadAcc->GetPixel(pVMap[nY + 2], pHMap[1]).GetIndex();
+ nGrey33 = pReadAcc->GetPixel(pVMap[nY + 2], pHMap[2]).GetIndex();
+
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ for (nX = 0; nX < nWidth; nX++)
+ {
+ nSum1 = nSum2 = 0;
+
+ nSum1 += nMask111 * nGrey11;
+ nSum2 += nMask112 * nGrey11;
+
+ nSum1 += nMask121 * nGrey12;
+ nSum2 += nMask122 * nGrey12;
+
+ nSum1 += nMask131 * nGrey13;
+ nSum2 += nMask132 * nGrey13;
+
+ nSum1 += nMask211 * nGrey21;
+ nSum2 += nMask212 * nGrey21;
+
+ nSum1 += nMask221 * nGrey22;
+ nSum2 += nMask222 * nGrey22;
+
+ nSum1 += nMask231 * nGrey23;
+ nSum2 += nMask232 * nGrey23;
+
+ nSum1 += nMask311 * nGrey31;
+ nSum2 += nMask312 * nGrey31;
+
+ nSum1 += nMask321 * nGrey32;
+ nSum2 += nMask322 * nGrey32;
+
+ nSum1 += nMask331 * nGrey33;
+ nSum2 += nMask332 * nGrey33;
+
+ nSum1 = static_cast<sal_Int32>(std::hypot(nSum1, nSum2));
+
+ aGrey.SetIndex(
+ ~static_cast<sal_uInt8>(std::clamp(nSum1, sal_Int32(0), sal_Int32(255))));
+ pWriteAcc->SetPixelOnData(pScanline, nX, aGrey);
+
+ if (nX < (nWidth - 1))
+ {
+ const sal_Int32 nNextX = pHMap[nX + 3];
+
+ nGrey11 = nGrey12;
+ nGrey12 = nGrey13;
+ nGrey13 = pReadAcc->GetPixel(pVMap[nY], nNextX).GetIndex();
+ nGrey21 = nGrey22;
+ nGrey22 = nGrey23;
+ nGrey23 = pReadAcc->GetPixel(pVMap[nY + 1], nNextX).GetIndex();
+ nGrey31 = nGrey32;
+ nGrey32 = nGrey33;
+ nGrey33 = pReadAcc->GetPixel(pVMap[nY + 2], nNextX).GetIndex();
+ }
+ }
+ }
+
+ pHMap.reset();
+ pVMap.reset();
+ pWriteAcc.reset();
+ pReadAcc.reset();
+
+ const MapMode aMap(aBitmap.GetPrefMapMode());
+ const Size aPrefSize(aBitmap.GetPrefSize());
+
+ aBitmap = aNewBmp;
+
+ aBitmap.SetPrefMapMode(aMap);
+ aBitmap.SetPrefSize(aPrefSize);
+
+ return BitmapEx(aBitmap);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapSolarizeFilter.cxx b/vcl/source/bitmap/BitmapSolarizeFilter.cxx
new file mode 100644
index 0000000000..ca2485a7f4
--- /dev/null
+++ b/vcl/source/bitmap/BitmapSolarizeFilter.cxx
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#include <vcl/bitmap.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/BitmapSolarizeFilter.hxx>
+#include <vcl/BitmapWriteAccess.hxx>
+
+BitmapEx BitmapSolarizeFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ Bitmap aBitmap(rBitmapEx.GetBitmap());
+ bool bRet = false;
+ BitmapScopedWriteAccess pWriteAcc(aBitmap);
+
+ if (pWriteAcc)
+ {
+ if (pWriteAcc->HasPalette())
+ {
+ const BitmapPalette& rPal = pWriteAcc->GetPalette();
+
+ for (sal_uInt16 i = 0, nCount = rPal.GetEntryCount(); i < nCount; i++)
+ {
+ if (rPal[i].GetLuminance() >= mcSolarGreyThreshold)
+ {
+ BitmapColor aCol(rPal[i]);
+ aCol.Invert();
+ pWriteAcc->SetPaletteColor(i, aCol);
+ }
+ }
+ }
+ else
+ {
+ BitmapColor aCol;
+ const sal_Int32 nWidth = pWriteAcc->Width();
+ const sal_Int32 nHeight = pWriteAcc->Height();
+
+ for (sal_Int32 nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ for (sal_Int32 nX = 0; nX < nWidth; nX++)
+ {
+ aCol = pWriteAcc->GetPixelFromData(pScanline, nX);
+
+ if (aCol.GetLuminance() >= mcSolarGreyThreshold)
+ {
+ aCol.Invert();
+ pWriteAcc->SetPixelOnData(pScanline, nX, aCol);
+ }
+ }
+ }
+ }
+
+ pWriteAcc.reset();
+ bRet = true;
+ }
+
+ if (bRet)
+ return BitmapEx(aBitmap);
+
+ return BitmapEx();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapSymmetryCheck.cxx b/vcl/source/bitmap/BitmapSymmetryCheck.cxx
new file mode 100644
index 0000000000..26e035e8da
--- /dev/null
+++ b/vcl/source/bitmap/BitmapSymmetryCheck.cxx
@@ -0,0 +1,83 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#include <vcl/BitmapReadAccess.hxx>
+
+#include <BitmapSymmetryCheck.hxx>
+
+BitmapSymmetryCheck::BitmapSymmetryCheck()
+{}
+
+bool BitmapSymmetryCheck::check(Bitmap& rBitmap)
+{
+ BitmapScopedReadAccess aReadAccess(rBitmap);
+ return checkImpl(aReadAccess.get());
+}
+
+bool BitmapSymmetryCheck::checkImpl(BitmapReadAccess const * pReadAccess)
+{
+ tools::Long nHeight = pReadAccess->Height();
+ tools::Long nWidth = pReadAccess->Width();
+
+ tools::Long nHeightHalf = nHeight / 2;
+ tools::Long nWidthHalf = nWidth / 2;
+
+ bool bHeightEven = (nHeight % 2) == 0;
+ bool bWidthEven = (nWidth % 2) == 0;
+
+ for (tools::Long y = 0; y < nHeightHalf; ++y)
+ {
+ Scanline pScanlineRead = pReadAccess->GetScanline( y );
+ Scanline pScanlineRead2 = pReadAccess->GetScanline( nHeight - y - 1 );
+ for (tools::Long x = 0; x < nWidthHalf; ++x)
+ {
+ if (pReadAccess->GetPixelFromData(pScanlineRead, x) != pReadAccess->GetPixelFromData(pScanlineRead2, x))
+ {
+ return false;
+ }
+ if (pReadAccess->GetPixelFromData(pScanlineRead, x) != pReadAccess->GetPixelFromData(pScanlineRead, nWidth - x - 1))
+ {
+ return false;
+ }
+ if (pReadAccess->GetPixelFromData(pScanlineRead, x) != pReadAccess->GetPixelFromData(pScanlineRead2, nWidth - x - 1))
+ {
+ return false;
+ }
+ }
+ }
+
+ if (bWidthEven)
+ {
+ for (tools::Long y = 0; y < nHeightHalf; ++y)
+ {
+ if (pReadAccess->GetPixel(y, nWidthHalf) != pReadAccess->GetPixel(nHeight - y - 1, nWidthHalf))
+ {
+ return false;
+ }
+ }
+ }
+
+ if (bHeightEven)
+ {
+ Scanline pScanlineRead = pReadAccess->GetScanline( nHeightHalf );
+ for (tools::Long x = 0; x < nWidthHalf; ++x)
+ {
+ if (pReadAccess->GetPixelFromData(pScanlineRead, x) != pReadAccess->GetPixelFromData(pScanlineRead, nWidth - x - 1))
+ {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapTools.cxx b/vcl/source/bitmap/BitmapTools.cxx
new file mode 100644
index 0000000000..ee0134a900
--- /dev/null
+++ b/vcl/source/bitmap/BitmapTools.cxx
@@ -0,0 +1,1302 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#include <sal/config.h>
+
+#include <array>
+#include <utility>
+
+#include <tools/helpers.hxx>
+#include <vcl/BitmapTools.hxx>
+
+#include <sal/log.hxx>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/seqstream.hxx>
+#include <vcl/canvastools.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+
+#include <com/sun/star/graphic/SvgTools.hpp>
+#include <com/sun/star/graphic/Primitive2DTools.hpp>
+
+#include <drawinglayer/primitive2d/baseprimitive2d.hxx>
+
+#include <com/sun/star/rendering/XIntegerReadOnlyBitmap.hpp>
+
+#include <vcl/dibtools.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/virdev.hxx>
+#if ENABLE_CAIRO_CANVAS
+#include <cairo.h>
+#endif
+#include <comphelper/diagnose_ex.hxx>
+#include <tools/fract.hxx>
+#include <tools/stream.hxx>
+#include <vcl/BitmapWriteAccess.hxx>
+
+using namespace css;
+
+using drawinglayer::primitive2d::Primitive2DSequence;
+using drawinglayer::primitive2d::Primitive2DReference;
+
+namespace vcl::bitmap
+{
+
+BitmapEx loadFromName(const OUString& rFileName, const ImageLoadFlags eFlags)
+{
+ bool bSuccess = true;
+ OUString aIconTheme;
+ BitmapEx aBitmapEx;
+ try
+ {
+ aIconTheme = Application::GetSettings().GetStyleSettings().DetermineIconTheme();
+ ImageTree::get().loadImage(rFileName, aIconTheme, aBitmapEx, true, eFlags);
+ }
+ catch (...)
+ {
+ bSuccess = false;
+ }
+
+ SAL_WARN_IF(!bSuccess, "vcl", "vcl::bitmap::loadFromName : could not load image " << rFileName << " via icon theme " << aIconTheme);
+
+ return aBitmapEx;
+}
+
+void loadFromSvg(SvStream& rStream, const OUString& sPath, BitmapEx& rBitmapEx, double fScalingFactor)
+{
+ uno::Reference<uno::XComponentContext> xContext(comphelper::getProcessComponentContext());
+ const uno::Reference<graphic::XSvgParser> xSvgParser = graphic::SvgTools::create(xContext);
+
+ std::size_t nSize = rStream.remainingSize();
+ std::vector<sal_Int8> aBuffer(nSize + 1);
+ rStream.ReadBytes(aBuffer.data(), nSize);
+ aBuffer[nSize] = 0;
+
+ uno::Sequence<sal_Int8> aData(aBuffer.data(), nSize + 1);
+ uno::Reference<io::XInputStream> aInputStream(new comphelper::SequenceInputStream(aData));
+
+ const Primitive2DSequence aPrimitiveSequence = xSvgParser->getDecomposition(aInputStream, sPath);
+
+ if (!aPrimitiveSequence.hasElements())
+ return;
+
+ uno::Sequence<beans::PropertyValue> aViewParameters;
+
+ geometry::RealRectangle2D aRealRect;
+ basegfx::B2DRange aRange;
+ for (css::uno::Reference<css::graphic::XPrimitive2D> const & xReference : aPrimitiveSequence)
+ {
+ if (xReference.is())
+ {
+ aRealRect = xReference->getRange(aViewParameters);
+ aRange.expand(basegfx::B2DRange(aRealRect.X1, aRealRect.Y1, aRealRect.X2, aRealRect.Y2));
+ }
+ }
+
+ aRealRect.X1 = aRange.getMinX();
+ aRealRect.Y1 = aRange.getMinY();
+ aRealRect.X2 = aRange.getMaxX();
+ aRealRect.Y2 = aRange.getMaxY();
+
+ double nDPI = 96 * fScalingFactor;
+
+ const css::uno::Reference<css::graphic::XPrimitive2DRenderer> xPrimitive2DRenderer = css::graphic::Primitive2DTools::create(xContext);
+ const css::uno::Reference<css::rendering::XBitmap> xBitmap(
+ xPrimitive2DRenderer->rasterize(aPrimitiveSequence, aViewParameters, nDPI, nDPI, aRealRect, 256*256));
+
+ if (xBitmap.is())
+ {
+ const css::uno::Reference<css::rendering::XIntegerReadOnlyBitmap> xIntBmp(xBitmap, uno::UNO_QUERY_THROW);
+ rBitmapEx = vcl::unotools::bitmapExFromXBitmap(xIntBmp);
+ }
+
+}
+
+/** Copy block of image data into the bitmap.
+ Assumes that the Bitmap has been constructed with the desired size.
+
+ @param pData
+ The block of data to copy
+ @param nStride
+ The number of bytes in a scanline, must >= (width * nBitCount / 8)
+ @param bReversColors
+ In case the endianness of pData is wrong, you could reverse colors
+*/
+BitmapEx CreateFromData(sal_uInt8 const *pData, sal_Int32 nWidth, sal_Int32 nHeight,
+ sal_Int32 nStride, sal_Int8 nBitCount,
+ bool bReversColors, bool bReverseAlpha)
+{
+ assert(nStride >= (nWidth * nBitCount / 8));
+ assert(nBitCount == 1 || nBitCount == 8 || nBitCount == 24 || nBitCount == 32);
+
+ PixelFormat ePixelFormat;
+ if (nBitCount == 1)
+ ePixelFormat = PixelFormat::N8_BPP; // we convert 1-bit input data to 8-bit format
+ else if (nBitCount == 8)
+ ePixelFormat = PixelFormat::N8_BPP;
+ else if (nBitCount == 24)
+ ePixelFormat = PixelFormat::N24_BPP;
+ else if (nBitCount == 32)
+ ePixelFormat = PixelFormat::N32_BPP;
+ else
+ std::abort();
+ Bitmap aBmp;
+ if (nBitCount == 1)
+ {
+ BitmapPalette aBiLevelPalette { COL_BLACK, COL_WHITE };
+ aBmp = Bitmap(Size(nWidth, nHeight), PixelFormat::N8_BPP, &aBiLevelPalette);
+ }
+ else
+ aBmp = Bitmap(Size(nWidth, nHeight), ePixelFormat);
+
+ BitmapScopedWriteAccess pWrite(aBmp);
+ assert(pWrite.get());
+ if( !pWrite )
+ return BitmapEx();
+ std::optional<AlphaMask> pAlphaMask;
+ BitmapScopedWriteAccess xMaskAcc;
+ if (nBitCount == 32)
+ {
+ pAlphaMask.emplace( Size(nWidth, nHeight) );
+ xMaskAcc = *pAlphaMask;
+ }
+ if (nBitCount == 1)
+ {
+ for( tools::Long y = 0; y < nHeight; ++y )
+ {
+ sal_uInt8 const *p = pData + y * nStride / 8;
+ Scanline pScanline = pWrite->GetScanline(y);
+ for (tools::Long x = 0; x < nWidth; ++x)
+ {
+ int bitIndex = (y * nStride + x) % 8;
+
+ pWrite->SetPixelOnData(pScanline, x, BitmapColor((*p >> bitIndex) & 1));
+ }
+ }
+ }
+ else
+ {
+ for( tools::Long y = 0; y < nHeight; ++y )
+ {
+ sal_uInt8 const *p = pData + (y * nStride);
+ Scanline pScanline = pWrite->GetScanline(y);
+ for (tools::Long x = 0; x < nWidth; ++x)
+ {
+ BitmapColor col;
+ if (nBitCount == 8)
+ col = BitmapColor( *p );
+ else if ( bReversColors )
+ col = BitmapColor( p[2], p[1], p[0] );
+ else
+ col = BitmapColor( p[0], p[1], p[2] );
+ pWrite->SetPixelOnData(pScanline, x, col);
+ p += nBitCount/8;
+ }
+ if (nBitCount == 32)
+ {
+ p = pData + (y * nStride) + 3;
+ Scanline pMaskScanLine = xMaskAcc->GetScanline(y);
+ for (tools::Long x = 0; x < nWidth; ++x)
+ {
+ // FIXME this parameter is badly named
+ const sal_uInt8 nValue = bReverseAlpha ? *p : 0xff - *p;
+ xMaskAcc->SetPixelOnData(pMaskScanLine, x, BitmapColor(nValue));
+ p += 4;
+ }
+ }
+ }
+ }
+ // Avoid further bitmap use with unfinished write access
+ pWrite.reset();
+ xMaskAcc.reset();
+ if (nBitCount == 32)
+ return BitmapEx(aBmp, *pAlphaMask);
+ else
+ return BitmapEx(aBmp);
+}
+
+/** Copy block of image data into the bitmap.
+ Assumes that the Bitmap has been constructed with the desired size.
+*/
+BitmapEx CreateFromData( RawBitmap&& rawBitmap )
+{
+ auto nBitCount = rawBitmap.GetBitCount();
+ assert( nBitCount == 24 || nBitCount == 32);
+
+ auto ePixelFormat = vcl::PixelFormat::INVALID;
+
+ if (nBitCount == 24)
+ ePixelFormat = vcl::PixelFormat::N24_BPP;
+ else if (nBitCount == 32)
+ ePixelFormat = vcl::PixelFormat::N32_BPP;
+
+ assert(ePixelFormat != vcl::PixelFormat::INVALID);
+
+ Bitmap aBmp(rawBitmap.maSize, ePixelFormat);
+
+ BitmapScopedWriteAccess pWrite(aBmp);
+ assert(pWrite.get());
+ if( !pWrite )
+ return BitmapEx();
+ std::optional<AlphaMask> pAlphaMask;
+ BitmapScopedWriteAccess xMaskAcc;
+ if (nBitCount == 32)
+ {
+ pAlphaMask.emplace( rawBitmap.maSize );
+ xMaskAcc = *pAlphaMask;
+ }
+
+ auto nHeight = rawBitmap.maSize.getHeight();
+ auto nWidth = rawBitmap.maSize.getWidth();
+ auto nStride = nWidth * nBitCount / 8;
+ for( tools::Long y = 0; y < nHeight; ++y )
+ {
+ sal_uInt8 const *p = rawBitmap.mpData.get() + (y * nStride);
+ Scanline pScanline = pWrite->GetScanline(y);
+ for (tools::Long x = 0; x < nWidth; ++x)
+ {
+ BitmapColor col(p[0], p[1], p[2]);
+ pWrite->SetPixelOnData(pScanline, x, col);
+ p += nBitCount/8;
+ }
+ if (nBitCount == 32)
+ {
+ p = rawBitmap.mpData.get() + (y * nStride) + 3;
+ Scanline pMaskScanLine = xMaskAcc->GetScanline(y);
+ for (tools::Long x = 0; x < nWidth; ++x)
+ {
+ xMaskAcc->SetPixelOnData(pMaskScanLine, x, BitmapColor(*p));
+ p += 4;
+ }
+ }
+ }
+
+ xMaskAcc.reset();
+ pWrite.reset();
+
+ if (nBitCount == 32)
+ return BitmapEx(aBmp, *pAlphaMask);
+ else
+ return BitmapEx(aBmp);
+}
+
+#if ENABLE_CAIRO_CANVAS
+BitmapEx* CreateFromCairoSurface(Size aSize, cairo_surface_t * pSurface)
+{
+ // FIXME: if we could teach VCL/ about cairo handles, life could
+ // be significantly better here perhaps.
+
+ cairo_surface_t *pPixels = cairo_surface_create_similar_image(pSurface,
+ CAIRO_FORMAT_ARGB32, aSize.Width(), aSize.Height());
+ cairo_t *pCairo = cairo_create( pPixels );
+ if( !pPixels || !pCairo || cairo_status(pCairo) != CAIRO_STATUS_SUCCESS )
+ return nullptr;
+
+ // suck ourselves from the X server to this buffer so then we can fiddle with
+ // Alpha to turn it into the ultra-lame vcl required format and then push it
+ // all back again later at vast expense [ urgh ]
+ cairo_set_source_surface( pCairo, pSurface, 0, 0 );
+ cairo_set_operator( pCairo, CAIRO_OPERATOR_SOURCE );
+ cairo_paint( pCairo );
+
+ Bitmap aRGB(aSize, vcl::PixelFormat::N24_BPP);
+ ::AlphaMask aMask( aSize );
+
+ BitmapScopedWriteAccess pRGBWrite(aRGB);
+ assert(pRGBWrite);
+ if (!pRGBWrite)
+ return nullptr;
+
+ BitmapScopedWriteAccess pMaskWrite(aMask);
+ assert(pMaskWrite);
+ if (!pMaskWrite)
+ return nullptr;
+
+ cairo_surface_flush(pPixels);
+ unsigned char *pSrc = cairo_image_surface_get_data( pPixels );
+ unsigned int nStride = cairo_image_surface_get_stride( pPixels );
+#if !ENABLE_WASM_STRIP_PREMULTIPLY
+ vcl::bitmap::lookup_table const & unpremultiply_table = vcl::bitmap::get_unpremultiply_table();
+#endif
+ for( tools::Long y = 0; y < aSize.Height(); y++ )
+ {
+ sal_uInt32 *pPix = reinterpret_cast<sal_uInt32 *>(pSrc + nStride * y);
+ for( tools::Long x = 0; x < aSize.Width(); x++ )
+ {
+#if defined OSL_BIGENDIAN
+ sal_uInt8 nB = (*pPix >> 24);
+ sal_uInt8 nG = (*pPix >> 16) & 0xff;
+ sal_uInt8 nR = (*pPix >> 8) & 0xff;
+ sal_uInt8 nAlpha = *pPix & 0xff;
+#else
+ sal_uInt8 nAlpha = (*pPix >> 24);
+ sal_uInt8 nR = (*pPix >> 16) & 0xff;
+ sal_uInt8 nG = (*pPix >> 8) & 0xff;
+ sal_uInt8 nB = *pPix & 0xff;
+#endif
+ if( nAlpha != 0 && nAlpha != 255 )
+ {
+ // Cairo uses pre-multiplied alpha - we do not => re-multiply
+#if ENABLE_WASM_STRIP_PREMULTIPLY
+ nR = vcl::bitmap::unpremultiply(nAlpha, nR);
+ nG = vcl::bitmap::unpremultiply(nAlpha, nG);
+ nB = vcl::bitmap::unpremultiply(nAlpha, nB);
+#else
+ nR = unpremultiply_table[nAlpha][nR];
+ nG = unpremultiply_table[nAlpha][nG];
+ nB = unpremultiply_table[nAlpha][nB];
+#endif
+ }
+ pRGBWrite->SetPixel( y, x, BitmapColor( nR, nG, nB ) );
+ pMaskWrite->SetPixelIndex( y, x, nAlpha );
+ pPix++;
+ }
+ }
+
+ // ignore potential errors above. will get caller a
+ // uniformly white bitmap, but not that there would
+ // be error handling in calling code ...
+ ::BitmapEx *pBitmapEx = new ::BitmapEx( aRGB, aMask );
+
+ cairo_destroy( pCairo );
+ cairo_surface_destroy( pPixels );
+ return pBitmapEx;
+}
+#endif
+
+BitmapEx CanvasTransformBitmap( const BitmapEx& rBitmap,
+ const ::basegfx::B2DHomMatrix& rTransform,
+ ::basegfx::B2DRectangle const & rDestRect,
+ ::basegfx::B2DHomMatrix const & rLocalTransform )
+{
+ const Size aBmpSize( rBitmap.GetSizePixel() );
+ Bitmap aSrcBitmap( rBitmap.GetBitmap() );
+ Bitmap aSrcAlpha;
+
+ // differentiate mask and alpha channel (on-off
+ // vs. multi-level transparency)
+ if( rBitmap.IsAlpha() )
+ {
+ aSrcAlpha = rBitmap.GetAlphaMask().GetBitmap();
+ }
+
+ BitmapScopedReadAccess pReadAccess( aSrcBitmap );
+ BitmapScopedReadAccess pAlphaReadAccess;
+ if (rBitmap.IsAlpha())
+ pAlphaReadAccess = aSrcAlpha;
+
+ if( !pReadAccess || (!pAlphaReadAccess && rBitmap.IsAlpha()) )
+ {
+ // TODO(E2): Error handling!
+ ENSURE_OR_THROW( false,
+ "transformBitmap(): could not access source bitmap" );
+ }
+
+ // mapping table, to translate pAlphaReadAccess' pixel
+ // values into destination alpha values (needed e.g. for
+ // paletted 1-bit masks).
+ sal_uInt8 aAlphaMap[256];
+
+ if( rBitmap.IsAlpha() )
+ {
+ // source already has alpha channel - 1:1 mapping,
+ // i.e. aAlphaMap[0]=0,...,aAlphaMap[255]=255.
+ sal_uInt8 val=0;
+ sal_uInt8* pCur=aAlphaMap;
+ sal_uInt8* const pEnd=&aAlphaMap[256];
+ while(pCur != pEnd)
+ *pCur++ = val++;
+ }
+ // else: mapping table is not used
+
+ const Size aDestBmpSize( ::basegfx::fround( rDestRect.getWidth() ),
+ ::basegfx::fround( rDestRect.getHeight() ) );
+
+ if( aDestBmpSize.IsEmpty() )
+ return BitmapEx();
+
+ Bitmap aDstBitmap(aDestBmpSize, aSrcBitmap.getPixelFormat(), &pReadAccess->GetPalette());
+ Bitmap aDstAlpha( AlphaMask( aDestBmpSize ).GetBitmap() );
+
+ {
+ // just to be on the safe side: let the
+ // ScopedAccessors get destructed before
+ // copy-constructing the resulting bitmap. This will
+ // rule out the possibility that cached accessor data
+ // is not yet written back.
+ BitmapScopedWriteAccess pWriteAccess( aDstBitmap );
+ BitmapScopedWriteAccess pAlphaWriteAccess( aDstAlpha );
+
+
+ if( pWriteAccess.get() != nullptr &&
+ pAlphaWriteAccess.get() != nullptr &&
+ rTransform.isInvertible() )
+ {
+ // we're doing inverse mapping here, i.e. mapping
+ // points from the destination bitmap back to the
+ // source
+ ::basegfx::B2DHomMatrix aTransform( rLocalTransform );
+ aTransform.invert();
+
+ // for the time being, always read as ARGB
+ for( tools::Long y=0; y<aDestBmpSize.Height(); ++y )
+ {
+ // differentiate mask and alpha channel (on-off
+ // vs. multi-level transparency)
+ if( rBitmap.IsAlpha() )
+ {
+ Scanline pScan = pWriteAccess->GetScanline( y );
+ Scanline pScanAlpha = pAlphaWriteAccess->GetScanline( y );
+ // Handling alpha and mask just the same...
+ for( tools::Long x=0; x<aDestBmpSize.Width(); ++x )
+ {
+ ::basegfx::B2DPoint aPoint(x,y);
+ aPoint *= aTransform;
+
+ const int nSrcX( ::basegfx::fround( aPoint.getX() ) );
+ const int nSrcY( ::basegfx::fround( aPoint.getY() ) );
+ if( nSrcX < 0 || nSrcX >= aBmpSize.Width() ||
+ nSrcY < 0 || nSrcY >= aBmpSize.Height() )
+ {
+ pAlphaWriteAccess->SetPixelOnData( pScanAlpha, x, BitmapColor(0) );
+ }
+ else
+ {
+ const sal_uInt8 cAlphaIdx = pAlphaReadAccess->GetPixelIndex( nSrcY, nSrcX );
+ pAlphaWriteAccess->SetPixelOnData( pScanAlpha, x, BitmapColor(aAlphaMap[ cAlphaIdx ]) );
+ pWriteAccess->SetPixelOnData( pScan, x, pReadAccess->GetPixel( nSrcY, nSrcX ) );
+ }
+ }
+ }
+ else
+ {
+ Scanline pScan = pWriteAccess->GetScanline( y );
+ Scanline pScanAlpha = pAlphaWriteAccess->GetScanline( y );
+ for( tools::Long x=0; x<aDestBmpSize.Width(); ++x )
+ {
+ ::basegfx::B2DPoint aPoint(x,y);
+ aPoint *= aTransform;
+
+ const int nSrcX( ::basegfx::fround( aPoint.getX() ) );
+ const int nSrcY( ::basegfx::fround( aPoint.getY() ) );
+ if( nSrcX < 0 || nSrcX >= aBmpSize.Width() ||
+ nSrcY < 0 || nSrcY >= aBmpSize.Height() )
+ {
+ pAlphaWriteAccess->SetPixelOnData( pScanAlpha, x, BitmapColor(0) );
+ }
+ else
+ {
+ pAlphaWriteAccess->SetPixelOnData( pScanAlpha, x, BitmapColor(255) );
+ pWriteAccess->SetPixelOnData( pScan, x, pReadAccess->GetPixel( nSrcY,
+ nSrcX ) );
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ // TODO(E2): Error handling!
+ ENSURE_OR_THROW( false,
+ "transformBitmap(): could not access bitmap" );
+ }
+ }
+
+ return BitmapEx(aDstBitmap, AlphaMask(aDstAlpha));
+}
+
+
+void DrawAlphaBitmapAndAlphaGradient(BitmapEx & rBitmapEx, bool bFixedTransparence, float fTransparence, AlphaMask & rNewMask)
+{
+ // mix existing and new alpha mask
+ AlphaMask aOldMask;
+
+ if(rBitmapEx.IsAlpha())
+ {
+ aOldMask = rBitmapEx.GetAlphaMask();
+ }
+
+ {
+
+ BitmapScopedWriteAccess pOld(aOldMask);
+
+ assert(pOld && "Got no access to old alpha mask (!)");
+
+ const double fFactor(1.0 / 255.0);
+
+ if(bFixedTransparence)
+ {
+ const double fOpNew(1.0 - fTransparence);
+
+ for(tools::Long y(0); y < pOld->Height(); y++)
+ {
+ Scanline pScanline = pOld->GetScanline( y );
+ for(tools::Long x(0); x < pOld->Width(); x++)
+ {
+ const double fOpOld(pOld->GetIndexFromData(pScanline, x) * fFactor);
+ const sal_uInt8 aCol(basegfx::fround((fOpOld * fOpNew) * 255.0));
+
+ pOld->SetPixelOnData(pScanline, x, BitmapColor(aCol));
+ }
+ }
+ }
+ else
+ {
+ BitmapScopedReadAccess pNew(rNewMask);
+
+ assert(pNew && "Got no access to new alpha mask (!)");
+
+ assert(pOld->Width() == pNew->Width() && pOld->Height() == pNew->Height() &&
+ "Alpha masks have different sizes (!)");
+
+ for(tools::Long y(0); y < pOld->Height(); y++)
+ {
+ Scanline pScanline = pOld->GetScanline( y );
+ for(tools::Long x(0); x < pOld->Width(); x++)
+ {
+ 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));
+ }
+ }
+ }
+
+ }
+
+ // apply combined bitmap as mask
+ rBitmapEx = BitmapEx(rBitmapEx.GetBitmap(), aOldMask);
+}
+
+
+void DrawAndClipBitmap(const Point& rPos, const Size& rSize, const BitmapEx& rBitmap, BitmapEx & aBmpEx, basegfx::B2DPolyPolygon const & rClipPath)
+{
+ ScopedVclPtrInstance< VirtualDevice > pVDev;
+ MapMode aMapMode( MapUnit::Map100thMM );
+ aMapMode.SetOrigin( Point( -rPos.X(), -rPos.Y() ) );
+ const Size aOutputSizePixel( pVDev->LogicToPixel( rSize, aMapMode ) );
+ const Size aSizePixel( rBitmap.GetSizePixel() );
+ if ( aOutputSizePixel.Width() && aOutputSizePixel.Height() )
+ {
+ aMapMode.SetScaleX( Fraction( aSizePixel.Width(), aOutputSizePixel.Width() ) );
+ aMapMode.SetScaleY( Fraction( aSizePixel.Height(), aOutputSizePixel.Height() ) );
+ }
+ pVDev->SetMapMode( aMapMode );
+ pVDev->SetOutputSizePixel( aSizePixel );
+ pVDev->SetFillColor( COL_BLACK );
+ const tools::PolyPolygon aClip( rClipPath );
+ pVDev->DrawPolyPolygon( aClip );
+
+ // #i50672# Extract whole VDev content (to match size of rBitmap)
+ pVDev->EnableMapMode( false );
+ const Bitmap aVDevMask(pVDev->GetBitmap(Point(), aSizePixel));
+
+ if(aBmpEx.IsAlpha())
+ {
+ // bitmap already uses a Mask or Alpha, we need to blend that with
+ // the new masking in pVDev.
+ // need to blend in AlphaMask quality (8Bit)
+ AlphaMask fromVDev(aVDevMask);
+ AlphaMask fromBmpEx(aBmpEx.GetAlphaMask());
+ BitmapScopedReadAccess pR(fromVDev);
+ BitmapScopedWriteAccess pW(fromBmpEx);
+
+ if(pR && pW)
+ {
+ const tools::Long nWidth(std::min(pR->Width(), pW->Width()));
+ const tools::Long nHeight(std::min(pR->Height(), pW->Height()));
+
+ for(tools::Long nY(0); nY < nHeight; nY++)
+ {
+ Scanline pScanlineR = pR->GetScanline( nY );
+ Scanline pScanlineW = pW->GetScanline( nY );
+ for(tools::Long nX(0); nX < nWidth; nX++)
+ {
+ const sal_uInt8 nIndR(pR->GetIndexFromData(pScanlineR, nX));
+ const sal_uInt8 nIndW(pW->GetIndexFromData(pScanlineW, nX));
+
+ // 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));
+ }
+ }
+ }
+
+ pR.reset();
+ pW.reset();
+ aBmpEx = BitmapEx(aBmpEx.GetBitmap(), fromBmpEx);
+ }
+ else
+ {
+ // no mask yet, create and add new mask. For better quality, use Alpha,
+ // this allows the drawn mask being processed with AntiAliasing (AAed)
+ aBmpEx = BitmapEx(rBitmap.GetBitmap(), aVDevMask);
+ }
+}
+
+
+css::uno::Sequence< sal_Int8 > GetMaskDIB(BitmapEx const & aBmpEx)
+{
+ if ( aBmpEx.IsAlpha() )
+ {
+ SvMemoryStream aMem;
+ WriteDIB(aBmpEx.GetAlphaMask().GetBitmap(), aMem, false, true);
+ return css::uno::Sequence< sal_Int8 >( static_cast<sal_Int8 const *>(aMem.GetData()), aMem.Tell() );
+ }
+
+ return css::uno::Sequence< sal_Int8 >();
+}
+
+static bool readAlpha( BitmapReadAccess const * pAlphaReadAcc, tools::Long nY, const tools::Long nWidth, unsigned char* data, tools::Long nOff )
+{
+ bool bIsAlpha = false;
+ tools::Long nX;
+ int nAlpha;
+ Scanline pReadScan;
+
+ nOff += 3;
+
+ switch( pAlphaReadAcc->GetScanlineFormat() )
+ {
+ case ScanlineFormat::N8BitPal:
+ pReadScan = pAlphaReadAcc->GetScanline( nY );
+ for( nX = 0; nX < nWidth; nX++ )
+ {
+ BitmapColor const& rColor(
+ pAlphaReadAcc->GetPaletteColor(*pReadScan));
+ pReadScan++;
+ nAlpha = data[ nOff ] = rColor.GetIndex();
+ if( nAlpha != 255 )
+ bIsAlpha = true;
+ nOff += 4;
+ }
+ break;
+ default:
+ 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 ] = pAlphaReadAcc->GetColor( nY, nX ).GetIndex();
+ if( nAlpha != 255 )
+ bIsAlpha = true;
+ nOff += 4;
+ }
+ }
+
+ return bIsAlpha;
+}
+
+
+
+/**
+ * @param data will be filled with alpha data, if xBitmap is alpha/transparent image
+ * @param bHasAlpha will be set to true if resulting surface has alpha
+ **/
+void CanvasCairoExtractBitmapData( BitmapEx const & aBmpEx, Bitmap & aBitmap, unsigned char*& data, bool& bHasAlpha, tools::Long& rnWidth, tools::Long& rnHeight )
+{
+ AlphaMask aAlpha = aBmpEx.GetAlphaMask();
+
+ BitmapScopedReadAccess pBitmapReadAcc( aBitmap );
+ BitmapScopedReadAccess pAlphaReadAcc;
+ const tools::Long nWidth = rnWidth = pBitmapReadAcc->Width();
+ const tools::Long nHeight = rnHeight = pBitmapReadAcc->Height();
+ tools::Long nX, nY;
+ bool bIsAlpha = false;
+
+ if( aBmpEx.IsAlpha() )
+ pAlphaReadAcc = aAlpha;
+
+ data = static_cast<unsigned char*>(malloc( nWidth*nHeight*4 ));
+
+ tools::Long nOff = 0;
+ ::Color aColor;
+ unsigned int nAlpha = 255;
+
+#if !ENABLE_WASM_STRIP_PREMULTIPLY
+ vcl::bitmap::lookup_table const & premultiply_table = vcl::bitmap::get_premultiply_table();
+#endif
+ for( nY = 0; nY < nHeight; nY++ )
+ {
+ ::Scanline pReadScan;
+
+ switch( pBitmapReadAcc->GetScanlineFormat() )
+ {
+ case ScanlineFormat::N8BitPal:
+ pReadScan = pBitmapReadAcc->GetScanline( nY );
+ if( pAlphaReadAcc )
+ if( readAlpha( pAlphaReadAcc.get(), nY, nWidth, data, nOff ) )
+ bIsAlpha = true;
+
+ for( nX = 0; nX < nWidth; nX++ )
+ {
+#ifdef OSL_BIGENDIAN
+ if( pAlphaReadAcc )
+ nAlpha = data[ nOff++ ];
+ else
+ nAlpha = data[ nOff++ ] = 255;
+#else
+ if( pAlphaReadAcc )
+ nAlpha = data[ nOff + 3 ];
+ else
+ nAlpha = data[ nOff + 3 ] = 255;
+#endif
+ aColor = pBitmapReadAcc->GetPaletteColor(*pReadScan++);
+
+#ifdef OSL_BIGENDIAN
+#if ENABLE_WASM_STRIP_PREMULTIPLY
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, aColor.GetRed());
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, aColor.GetGreen());
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, aColor.GetBlue());
+#else
+ data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetRed()];
+ data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetGreen()];
+ data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetBlue()];
+#endif
+#else
+#if ENABLE_WASM_STRIP_PREMULTIPLY
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, aColor.GetBlue());
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, aColor.GetGreen());
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, aColor.GetRed());
+#else
+ data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetBlue()];
+ data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetGreen()];
+ data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetRed()];
+#endif
+ nOff++;
+#endif
+ }
+ break;
+ case ScanlineFormat::N24BitTcBgr:
+ pReadScan = pBitmapReadAcc->GetScanline( nY );
+ if( pAlphaReadAcc )
+ if( readAlpha( pAlphaReadAcc.get(), nY, nWidth, data, nOff ) )
+ bIsAlpha = true;
+
+ for( nX = 0; nX < nWidth; nX++ )
+ {
+#ifdef OSL_BIGENDIAN
+ if( pAlphaReadAcc )
+ nAlpha = data[ nOff ];
+ else
+ nAlpha = data[ nOff ] = 255;
+#if ENABLE_WASM_STRIP_PREMULTIPLY
+ data[ nOff + 3 ] = vcl::bitmap::premultiply(nAlpha, *pReadScan++);
+ data[ nOff + 2 ] = vcl::bitmap::premultiply(nAlpha, *pReadScan++);
+ data[ nOff + 1 ] = vcl::bitmap::premultiply(nAlpha, *pReadScan++);
+#else
+ data[ nOff + 3 ] = premultiply_table[nAlpha][*pReadScan++];
+ data[ nOff + 2 ] = premultiply_table[nAlpha][*pReadScan++];
+ data[ nOff + 1 ] = premultiply_table[nAlpha][*pReadScan++];
+#endif
+ nOff += 4;
+#else
+ if( pAlphaReadAcc )
+ nAlpha = data[ nOff + 3 ];
+ else
+ nAlpha = data[ nOff + 3 ] = 255;
+#if ENABLE_WASM_STRIP_PREMULTIPLY
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, *pReadScan++);
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, *pReadScan++);
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, *pReadScan++);
+#else
+ data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++];
+ data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++];
+ data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++];
+#endif
+ nOff++;
+#endif
+ }
+ break;
+ case ScanlineFormat::N24BitTcRgb:
+ pReadScan = pBitmapReadAcc->GetScanline( nY );
+ if( pAlphaReadAcc )
+ if( readAlpha( pAlphaReadAcc.get(), nY, nWidth, data, nOff ) )
+ bIsAlpha = true;
+
+ for( nX = 0; nX < nWidth; nX++ )
+ {
+#ifdef OSL_BIGENDIAN
+ if( pAlphaReadAcc )
+ nAlpha = data[ nOff++ ];
+ else
+ nAlpha = data[ nOff++ ] = 255;
+#if ENABLE_WASM_STRIP_PREMULTIPLY
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, *pReadScan++);
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, *pReadScan++);
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, *pReadScan++);
+#else
+ data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++];
+ data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++];
+ data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++];
+#endif
+#else
+ if( pAlphaReadAcc )
+ nAlpha = data[ nOff + 3 ];
+ else
+ nAlpha = data[ nOff + 3 ] = 255;
+#if ENABLE_WASM_STRIP_PREMULTIPLY
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, pReadScan[ 2 ]);
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, pReadScan[ 1 ]);
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, pReadScan[ 0 ]);
+#else
+ data[ nOff++ ] = premultiply_table[nAlpha][pReadScan[ 2 ]];
+ data[ nOff++ ] = premultiply_table[nAlpha][pReadScan[ 1 ]];
+ data[ nOff++ ] = premultiply_table[nAlpha][pReadScan[ 0 ]];
+#endif
+ pReadScan += 3;
+ nOff++;
+#endif
+ }
+ break;
+ case ScanlineFormat::N32BitTcBgra:
+ pReadScan = pBitmapReadAcc->GetScanline( nY );
+ if( pAlphaReadAcc )
+ if( readAlpha( pAlphaReadAcc.get(), nY, nWidth, data, nOff ) )
+ bIsAlpha = true;
+
+ for( nX = 0; nX < nWidth; nX++ )
+ {
+#ifdef OSL_BIGENDIAN
+ if( pAlphaReadAcc )
+ nAlpha = data[ nOff++ ];
+ else
+ nAlpha = data[ nOff++ ] = 255;
+#if ENABLE_WASM_STRIP_PREMULTIPLY
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, pReadScan[ 2 ]);
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, pReadScan[ 1 ]);
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, pReadScan[ 0 ]);
+#else
+ data[ nOff++ ] = premultiply_table[nAlpha][pReadScan[ 2 ]];
+ data[ nOff++ ] = premultiply_table[nAlpha][pReadScan[ 1 ]];
+ data[ nOff++ ] = premultiply_table[nAlpha][pReadScan[ 0 ]];
+#endif
+ pReadScan += 4;
+#else
+ if( pAlphaReadAcc )
+ nAlpha = data[ nOff + 3 ];
+ else
+ nAlpha = data[ nOff + 3 ] = 255;
+#if ENABLE_WASM_STRIP_PREMULTIPLY
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, *pReadScan++);
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, *pReadScan++);
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, *pReadScan++);
+#else
+ data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++];
+ data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++];
+ data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++];
+#endif
+ pReadScan++;
+ nOff++;
+#endif
+ }
+ break;
+ case ScanlineFormat::N32BitTcRgba:
+ pReadScan = pBitmapReadAcc->GetScanline( nY );
+ if( pAlphaReadAcc )
+ if( readAlpha( pAlphaReadAcc.get(), nY, nWidth, data, nOff ) )
+ bIsAlpha = true;
+
+ for( nX = 0; nX < nWidth; nX++ )
+ {
+#ifdef OSL_BIGENDIAN
+ if( pAlphaReadAcc )
+ nAlpha = data[ nOff ++ ];
+ else
+ nAlpha = data[ nOff ++ ] = 255;
+#if ENABLE_WASM_STRIP_PREMULTIPLY
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, *pReadScan++);
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, *pReadScan++);
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, *pReadScan++);
+#else
+ data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++];
+ data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++];
+ data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++];
+#endif
+ pReadScan++;
+#else
+ if( pAlphaReadAcc )
+ nAlpha = data[ nOff + 3 ];
+ else
+ nAlpha = data[ nOff + 3 ] = 255;
+#if ENABLE_WASM_STRIP_PREMULTIPLY
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, pReadScan[ 2 ]);
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, pReadScan[ 1 ]);
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, pReadScan[ 0 ]);
+#else
+ data[ nOff++ ] = premultiply_table[nAlpha][pReadScan[ 2 ]];
+ data[ nOff++ ] = premultiply_table[nAlpha][pReadScan[ 1 ]];
+ data[ nOff++ ] = premultiply_table[nAlpha][pReadScan[ 0 ]];
+#endif
+ pReadScan += 4;
+ nOff++;
+#endif
+ }
+ break;
+ default:
+ SAL_INFO( "canvas.cairo", "fallback to GetColor - slow, format: " << static_cast<int>(pBitmapReadAcc->GetScanlineFormat()) );
+
+ if( pAlphaReadAcc )
+ if( readAlpha( pAlphaReadAcc.get(), nY, nWidth, data, nOff ) )
+ bIsAlpha = true;
+
+ for( nX = 0; nX < nWidth; nX++ )
+ {
+ aColor = pBitmapReadAcc->GetColor( nY, nX );
+
+ // cairo need premultiplied color values
+ // TODO(rodo) handle endianness
+#ifdef OSL_BIGENDIAN
+ if( pAlphaReadAcc )
+ nAlpha = data[ nOff++ ];
+ else
+ nAlpha = data[ nOff++ ] = 255;
+#if ENABLE_WASM_STRIP_PREMULTIPLY
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, aColor.GetRed());
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, aColor.GetGreen());
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, aColor.GetBlue());
+#else
+ data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetRed()];
+ data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetGreen()];
+ data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetBlue()];
+#endif
+#else
+ if( pAlphaReadAcc )
+ nAlpha = data[ nOff + 3 ];
+ else
+ nAlpha = data[ nOff + 3 ] = 255;
+#if ENABLE_WASM_STRIP_PREMULTIPLY
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, aColor.GetBlue());
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, aColor.GetGreen());
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, aColor.GetRed());
+#else
+ data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetBlue()];
+ data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetGreen()];
+ data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetRed()];
+#endif
+ nOff ++;
+#endif
+ }
+ }
+ }
+
+ bHasAlpha = bIsAlpha;
+
+}
+
+ uno::Sequence< sal_Int8 > CanvasExtractBitmapData(BitmapEx const & rBitmapEx, const geometry::IntegerRectangle2D& rect)
+ {
+ Bitmap aBitmap( rBitmapEx.GetBitmap() );
+ Bitmap aAlpha( rBitmapEx.GetAlphaMask().GetBitmap() );
+
+ BitmapScopedReadAccess pReadAccess( aBitmap );
+ BitmapScopedReadAccess pAlphaReadAccess;
+ if (!aAlpha.IsEmpty())
+ pAlphaReadAccess = aAlpha;
+
+ assert( pReadAccess );
+
+ // TODO(F1): Support more formats.
+ const Size aBmpSize( aBitmap.GetSizePixel() );
+
+ // for the time being, always return as BGRA
+ uno::Sequence< sal_Int8 > aRes( 4*aBmpSize.Width()*aBmpSize.Height() );
+ sal_Int8* pRes = aRes.getArray();
+
+ int nCurrPos(0);
+ for( tools::Long y=rect.Y1;
+ y<aBmpSize.Height() && y<rect.Y2;
+ ++y )
+ {
+ if( pAlphaReadAccess.get() != nullptr )
+ {
+ Scanline pScanlineReadAlpha = pAlphaReadAccess->GetScanline( y );
+ for( tools::Long x=rect.X1;
+ x<aBmpSize.Width() && x<rect.X2;
+ ++x )
+ {
+ pRes[ nCurrPos++ ] = pReadAccess->GetColor( y, x ).GetRed();
+ pRes[ nCurrPos++ ] = pReadAccess->GetColor( y, x ).GetGreen();
+ pRes[ nCurrPos++ ] = pReadAccess->GetColor( y, x ).GetBlue();
+ pRes[ nCurrPos++ ] = 255 - pAlphaReadAccess->GetIndexFromData( pScanlineReadAlpha, x );
+ }
+ }
+ else
+ {
+ for( tools::Long x=rect.X1;
+ x<aBmpSize.Width() && x<rect.X2;
+ ++x )
+ {
+ pRes[ nCurrPos++ ] = pReadAccess->GetColor( y, x ).GetRed();
+ pRes[ nCurrPos++ ] = pReadAccess->GetColor( y, x ).GetGreen();
+ pRes[ nCurrPos++ ] = pReadAccess->GetColor( y, x ).GetBlue();
+ pRes[ nCurrPos++ ] = sal_uInt8(255);
+ }
+ }
+ }
+ return aRes;
+ }
+
+ BitmapEx createHistorical8x8FromArray(std::array<sal_uInt8,64> const & pArray, Color aColorPix, Color aColorBack)
+ {
+ BitmapPalette aPalette(2);
+
+ aPalette[0] = BitmapColor(aColorBack);
+ aPalette[1] = BitmapColor(aColorPix);
+
+ Bitmap aBitmap(Size(8, 8), vcl::PixelFormat::N8_BPP, &aPalette);
+ BitmapScopedWriteAccess pContent(aBitmap);
+
+ for(sal_uInt16 a(0); a < 8; a++)
+ {
+ for(sal_uInt16 b(0); b < 8; b++)
+ {
+ if(pArray[(a * 8) + b])
+ {
+ pContent->SetPixelIndex(a, b, 1);
+ }
+ else
+ {
+ pContent->SetPixelIndex(a, b, 0);
+ }
+ }
+ }
+
+ return BitmapEx(aBitmap);
+ }
+
+ bool isHistorical8x8(const BitmapEx& rBitmapEx, Color& o_rBack, Color& o_rFront)
+ {
+ bool bRet(false);
+
+ if(!rBitmapEx.IsAlpha())
+ {
+ Bitmap aBitmap(rBitmapEx.GetBitmap());
+
+ if(8 == aBitmap.GetSizePixel().Width() && 8 == aBitmap.GetSizePixel().Height())
+ {
+ // Historical 1bpp images are getting really historical,
+ // even to the point that e.g. the png loader actually loads
+ // them as RGB. But the pattern code in svx relies on this
+ // assumption that any 2-color 1bpp bitmap is a pattern, and so it would
+ // get confused by RGB. Try to detect if this image is really
+ // just two colors and say it's a pattern bitmap if so.
+ BitmapScopedReadAccess access(aBitmap);
+ o_rBack = access->GetColor(0,0);
+ bool foundSecondColor = false;;
+ for(tools::Long y = 0; y < access->Height(); ++y)
+ for(tools::Long x = 0; x < access->Width(); ++x)
+ {
+ if(!foundSecondColor)
+ {
+ if( access->GetColor(y,x) != o_rBack )
+ {
+ o_rFront = access->GetColor(y,x);
+ foundSecondColor = true;
+ // Hard to know which of the two colors is the background,
+ // select the lighter one.
+ if( o_rFront.GetLuminance() > o_rBack.GetLuminance())
+ std::swap( o_rFront, o_rBack );
+ }
+ }
+ else
+ {
+ if( access->GetColor(y,x) != o_rBack && access->GetColor(y,x) != o_rFront)
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ return bRet;
+ }
+
+#if ENABLE_WASM_STRIP_PREMULTIPLY
+ sal_uInt8 unpremultiply(sal_uInt8 c, sal_uInt8 a)
+ {
+ return (a == 0) ? 0 : (c * 255 + a / 2) / a;
+ }
+
+ sal_uInt8 premultiply(sal_uInt8 c, sal_uInt8 a)
+ {
+ return (c * a + 127) / 255;
+ }
+#else
+ sal_uInt8 unpremultiply(sal_uInt8 c, sal_uInt8 a)
+ {
+ return get_unpremultiply_table()[a][c];
+ }
+
+ static constexpr sal_uInt8 unpremultiplyImpl(sal_uInt8 c, sal_uInt8 a)
+ {
+ return (a == 0) ? 0 : (c * 255 + a / 2) / a;
+ }
+
+ sal_uInt8 premultiply(sal_uInt8 c, sal_uInt8 a)
+ {
+ return get_premultiply_table()[a][c];
+ }
+
+ static constexpr sal_uInt8 premultiplyImpl(sal_uInt8 c, sal_uInt8 a)
+ {
+ return (c * a + 127) / 255;
+ }
+
+ template<int... Is> static constexpr std::array<sal_uInt8, 256> make_unpremultiply_table_row_(
+ int a, std::integer_sequence<int, Is...>)
+ {
+ return {unpremultiplyImpl(Is, a)...};
+ }
+
+ template<int... Is> static constexpr lookup_table make_unpremultiply_table_(
+ std::integer_sequence<int, Is...>)
+ {
+ return {make_unpremultiply_table_row_(Is, std::make_integer_sequence<int, 256>{})...};
+ }
+
+ lookup_table const & get_unpremultiply_table()
+ {
+ static constexpr auto unpremultiply_table = make_unpremultiply_table_(
+ std::make_integer_sequence<int, 256>{});
+ return unpremultiply_table;
+ }
+
+ template<int... Is> static constexpr std::array<sal_uInt8, 256> make_premultiply_table_row_(
+ int a, std::integer_sequence<int, Is...>)
+ {
+ return {premultiplyImpl(Is, a)...};
+ }
+
+ template<int... Is> static constexpr lookup_table make_premultiply_table_(
+ std::integer_sequence<int, Is...>)
+ {
+ return {make_premultiply_table_row_(Is, std::make_integer_sequence<int, 256>{})...};
+ }
+
+ lookup_table const & get_premultiply_table()
+ {
+ static constexpr auto premultiply_table = make_premultiply_table_(
+ std::make_integer_sequence<int, 256>{});
+ return premultiply_table;
+ }
+#endif
+
+bool convertBitmap32To24Plus8(BitmapEx const & rInput, BitmapEx & rResult)
+{
+ Bitmap aBitmap(rInput.GetBitmap());
+ if (aBitmap.getPixelFormat() != vcl::PixelFormat::N32_BPP)
+ return false;
+
+ Size aSize = aBitmap.GetSizePixel();
+ Bitmap aResultBitmap(aSize, vcl::PixelFormat::N24_BPP);
+ AlphaMask aResultAlpha(aSize);
+ {
+ BitmapScopedWriteAccess pResultBitmapAccess(aResultBitmap);
+ BitmapScopedWriteAccess pResultAlphaAccess(aResultAlpha);
+
+ BitmapScopedReadAccess pReadAccess(aBitmap);
+
+ for (tools::Long nY = 0; nY < aSize.Height(); ++nY)
+ {
+ Scanline aResultScan = pResultBitmapAccess->GetScanline(nY);
+ Scanline aResultScanAlpha = pResultAlphaAccess->GetScanline(nY);
+
+ Scanline aReadScan = pReadAccess->GetScanline(nY);
+
+ for (tools::Long nX = 0; nX < aSize.Width(); ++nX)
+ {
+ const BitmapColor aColor = pReadAccess->GetPixelFromData(aReadScan, nX);
+ BitmapColor aResultColor(aColor.GetRed(), aColor.GetGreen(), aColor.GetBlue());
+ BitmapColor aResultColorAlpha(aColor.GetAlpha(), aColor.GetAlpha(), aColor.GetAlpha());
+
+ pResultBitmapAccess->SetPixelOnData(aResultScan, nX, aResultColor);
+ pResultAlphaAccess->SetPixelOnData(aResultScanAlpha, nX, aResultColorAlpha);
+ }
+ }
+ }
+ if (rInput.IsAlpha())
+ rResult = BitmapEx(aResultBitmap, rInput.GetAlphaMask());
+ else
+ rResult = BitmapEx(aResultBitmap, aResultAlpha);
+ return true;
+}
+
+Bitmap GetDownsampledBitmap(Size const& rDstSizeTwip, Point const& rSrcPt, Size const& rSrcSz,
+ Bitmap const& rBmp, tools::Long nMaxBmpDPIX, tools::Long nMaxBmpDPIY)
+{
+ Bitmap aBmp(rBmp);
+
+ if (!aBmp.IsEmpty())
+ {
+ const tools::Rectangle aBmpRect( Point(), aBmp.GetSizePixel() );
+ tools::Rectangle aSrcRect( rSrcPt, rSrcSz );
+
+ // do cropping if necessary
+ if( aSrcRect.Intersection( aBmpRect ) != aBmpRect )
+ {
+ if( !aSrcRect.IsEmpty() )
+ aBmp.Crop( aSrcRect );
+ else
+ aBmp.SetEmpty();
+ }
+
+ if( !aBmp.IsEmpty() )
+ {
+ // do downsampling if necessary
+ // #103209# Normalize size (mirroring has to happen outside of this method)
+ Size aDstSizeTwip(std::abs(rDstSizeTwip.Width()), std::abs(rDstSizeTwip.Height()));
+
+ const Size aBmpSize( aBmp.GetSizePixel() );
+ const double fBmpPixelX = aBmpSize.Width();
+ const double fBmpPixelY = aBmpSize.Height();
+ const double fMaxPixelX
+ = o3tl::convert<double>(aDstSizeTwip.Width(), o3tl::Length::twip, o3tl::Length::in)
+ * nMaxBmpDPIX;
+ const double fMaxPixelY
+ = o3tl::convert<double>(aDstSizeTwip.Height(), o3tl::Length::twip, o3tl::Length::in)
+ * nMaxBmpDPIY;
+
+ // check, if the bitmap DPI exceeds the maximum DPI (allow 4 pixel rounding tolerance)
+ if (((fBmpPixelX > (fMaxPixelX + 4)) ||
+ (fBmpPixelY > (fMaxPixelY + 4))) &&
+ (fBmpPixelY > 0.0) && (fMaxPixelY > 0.0))
+ {
+ // do scaling
+ Size aNewBmpSize;
+ const double fBmpWH = fBmpPixelX / fBmpPixelY;
+ const double fMaxWH = fMaxPixelX / fMaxPixelY;
+
+ if (fBmpWH < fMaxWH)
+ {
+ aNewBmpSize.setWidth(FRound(fMaxPixelY * fBmpWH));
+ aNewBmpSize.setHeight(FRound(fMaxPixelY));
+ }
+ else if (fBmpWH > 0.0)
+ {
+ aNewBmpSize.setWidth(FRound(fMaxPixelX));
+ aNewBmpSize.setHeight(FRound(fMaxPixelX / fBmpWH));
+ }
+
+ if( aNewBmpSize.Width() && aNewBmpSize.Height() )
+ aBmp.Scale(aNewBmpSize);
+ else
+ aBmp.SetEmpty();
+ }
+ }
+ }
+
+ return aBmp;
+}
+
+} // end vcl::bitmap
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapWriteAccess.cxx b/vcl/source/bitmap/BitmapWriteAccess.cxx
new file mode 100644
index 0000000000..610cb2cc26
--- /dev/null
+++ b/vcl/source/bitmap/BitmapWriteAccess.cxx
@@ -0,0 +1,399 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+#include <sal/log.hxx>
+#include <tools/debug.hxx>
+
+#include <vcl/BitmapWriteAccess.hxx>
+#include <bitmap/bmpfast.hxx>
+
+BitmapWriteAccess::BitmapWriteAccess(Bitmap& rBitmap)
+ : BitmapReadAccess(rBitmap, BitmapAccessMode::Write)
+{
+}
+
+BitmapWriteAccess::BitmapWriteAccess(AlphaMask& rBitmap)
+ : BitmapReadAccess(rBitmap, BitmapAccessMode::Write)
+{
+}
+
+BitmapWriteAccess::~BitmapWriteAccess() {}
+
+void BitmapWriteAccess::CopyScanline(tools::Long nY, const BitmapReadAccess& rReadAcc)
+{
+ assert(nY >= 0 && nY < mpBuffer->mnHeight && "y-coordinate in destination out of range!");
+ SAL_WARN_IF(nY >= rReadAcc.Height(), "vcl", "y-coordinate in source out of range!");
+ SAL_WARN_IF((!HasPalette() || !rReadAcc.HasPalette())
+ && (HasPalette() || rReadAcc.HasPalette()),
+ "vcl", "No copying possible between palette bitmap and TC bitmap!");
+
+ if ((GetScanlineFormat() == rReadAcc.GetScanlineFormat())
+ && (GetScanlineSize() >= rReadAcc.GetScanlineSize()))
+ {
+ memcpy(GetScanline(nY), rReadAcc.GetScanline(nY), rReadAcc.GetScanlineSize());
+ }
+ else
+ {
+ tools::Long nWidth = std::min(mpBuffer->mnWidth, rReadAcc.Width());
+ if (!ImplFastCopyScanline(nY, *ImplGetBitmapBuffer(), *rReadAcc.ImplGetBitmapBuffer()))
+ {
+ Scanline pScanline = GetScanline(nY);
+ Scanline pScanlineRead = rReadAcc.GetScanline(nY);
+ for (tools::Long nX = 0; nX < nWidth; nX++)
+ SetPixelOnData(pScanline, nX, rReadAcc.GetPixelFromData(pScanlineRead, nX));
+ }
+ }
+}
+
+void BitmapWriteAccess::CopyScanline(tools::Long nY, ConstScanline aSrcScanline,
+ ScanlineFormat nSrcScanlineFormat, sal_uInt32 nSrcScanlineSize)
+{
+ const ScanlineFormat nFormat = RemoveScanline(nSrcScanlineFormat);
+
+ assert(nY >= 0 && nY < mpBuffer->mnHeight && "y-coordinate in destination out of range!");
+ DBG_ASSERT((HasPalette() && nFormat <= ScanlineFormat::N8BitPal)
+ || (!HasPalette() && nFormat > ScanlineFormat::N8BitPal),
+ "No copying possible between palette and non palette scanlines!");
+
+ const sal_uInt32 nCount = std::min(GetScanlineSize(), nSrcScanlineSize);
+
+ if (!nCount)
+ return;
+
+ if (GetScanlineFormat() == RemoveScanline(nSrcScanlineFormat))
+ memcpy(GetScanline(nY), aSrcScanline, nCount);
+ else
+ {
+ if (ImplFastCopyScanline(nY, *ImplGetBitmapBuffer(), aSrcScanline, nSrcScanlineFormat,
+ nSrcScanlineSize))
+ return;
+
+ DBG_ASSERT(nFormat != ScanlineFormat::N32BitTcMask,
+ "No support for pixel formats with color masks yet!");
+ FncGetPixel pFncGetPixel;
+ switch (nFormat)
+ {
+ case ScanlineFormat::N1BitMsbPal:
+ pFncGetPixel = GetPixelForN1BitMsbPal;
+ break;
+ case ScanlineFormat::N8BitPal:
+ pFncGetPixel = GetPixelForN8BitPal;
+ break;
+ case ScanlineFormat::N24BitTcBgr:
+ pFncGetPixel = GetPixelForN24BitTcBgr;
+ break;
+ case ScanlineFormat::N24BitTcRgb:
+ pFncGetPixel = GetPixelForN24BitTcRgb;
+ break;
+ case ScanlineFormat::N32BitTcAbgr:
+ if (Bitmap32IsPreMultipled())
+ pFncGetPixel = GetPixelForN32BitTcAbgr;
+ else
+ pFncGetPixel = GetPixelForN32BitTcXbgr;
+ break;
+ case ScanlineFormat::N32BitTcArgb:
+ if (Bitmap32IsPreMultipled())
+ pFncGetPixel = GetPixelForN32BitTcArgb;
+ else
+ pFncGetPixel = GetPixelForN32BitTcXrgb;
+ break;
+ case ScanlineFormat::N32BitTcBgra:
+ if (Bitmap32IsPreMultipled())
+ pFncGetPixel = GetPixelForN32BitTcBgra;
+ else
+ pFncGetPixel = GetPixelForN32BitTcBgrx;
+ break;
+ case ScanlineFormat::N32BitTcRgba:
+ if (Bitmap32IsPreMultipled())
+ pFncGetPixel = GetPixelForN32BitTcRgba;
+ else
+ pFncGetPixel = GetPixelForN32BitTcRgbx;
+ break;
+ case ScanlineFormat::N32BitTcMask:
+ pFncGetPixel = GetPixelForN32BitTcMask;
+ break;
+
+ default:
+ assert(false);
+ pFncGetPixel = nullptr;
+ break;
+ }
+
+ if (pFncGetPixel)
+ {
+ const ColorMask aDummyMask;
+ Scanline pScanline = GetScanline(nY);
+ for (tools::Long nX = 0, nWidth = mpBuffer->mnWidth; nX < nWidth; ++nX)
+ SetPixelOnData(pScanline, nX, pFncGetPixel(aSrcScanline, nX, aDummyMask));
+ }
+ }
+}
+
+void BitmapWriteAccess::SetLineColor(const Color& rColor)
+{
+ if (rColor.GetAlpha() == 0)
+ {
+ mpLineColor.reset();
+ }
+ else
+ {
+ if (HasPalette())
+ {
+ mpLineColor = BitmapColor(static_cast<sal_uInt8>(GetBestPaletteIndex(rColor)));
+ }
+ else
+ {
+ mpLineColor = BitmapColor(rColor);
+ }
+ }
+}
+
+void BitmapWriteAccess::SetFillColor() { mpFillColor.reset(); }
+
+void BitmapWriteAccess::SetFillColor(const Color& rColor)
+{
+ if (rColor.GetAlpha() == 0)
+ {
+ mpFillColor.reset();
+ }
+ else
+ {
+ if (HasPalette())
+ {
+ mpFillColor = BitmapColor(static_cast<sal_uInt8>(GetBestPaletteIndex(rColor)));
+ }
+ else
+ {
+ mpFillColor = BitmapColor(rColor);
+ }
+ }
+}
+
+void BitmapWriteAccess::Erase(const Color& rColor)
+{
+ // convert the color format from RGB to palette index if needed
+ // TODO: provide and use Erase( BitmapColor& method)
+ BitmapColor aColor = rColor;
+ if (HasPalette())
+ {
+ aColor = BitmapColor(static_cast<sal_uInt8>(GetBestPaletteIndex(rColor)));
+ }
+
+ // try fast bitmap method first
+ if (ImplFastEraseBitmap(*mpBuffer, aColor))
+ return;
+
+ tools::Rectangle aRect(Point(), maBitmap.GetSizePixel());
+ if (aRect.IsEmpty())
+ return;
+ // clear the bitmap by filling the first line pixel by pixel,
+ // then dup the first line over each other line
+ Scanline pFirstScanline = GetScanline(0);
+ const tools::Long nEndX = aRect.Right();
+ for (tools::Long nX = 0; nX <= nEndX; ++nX)
+ SetPixelOnData(pFirstScanline, nX, rColor);
+ const auto nScanlineSize = GetScanlineSize();
+ const tools::Long nEndY = aRect.Bottom();
+ for (tools::Long nY = 1; nY <= nEndY; nY++)
+ {
+ Scanline pDestScanline = GetScanline(nY);
+ memcpy(pDestScanline, pFirstScanline, nScanlineSize);
+ }
+}
+
+void BitmapWriteAccess::DrawLine(const Point& rStart, const Point& rEnd)
+{
+ if (!mpLineColor)
+ return;
+
+ const BitmapColor& rLineColor = *mpLineColor;
+ tools::Long nX, nY;
+
+ if (rStart.X() == rEnd.X())
+ {
+ // Vertical Line
+ const tools::Long nEndY = rEnd.Y();
+
+ nX = rStart.X();
+ nY = rStart.Y();
+
+ if (nEndY > nY)
+ {
+ for (; nY <= nEndY; nY++)
+ SetPixel(nY, nX, rLineColor);
+ }
+ else
+ {
+ for (; nY >= nEndY; nY--)
+ SetPixel(nY, nX, rLineColor);
+ }
+ }
+ else if (rStart.Y() == rEnd.Y())
+ {
+ // Horizontal Line
+ const tools::Long nEndX = rEnd.X();
+
+ nX = rStart.X();
+ nY = rStart.Y();
+
+ if (nEndX > nX)
+ {
+ for (; nX <= nEndX; nX++)
+ SetPixel(nY, nX, rLineColor);
+ }
+ else
+ {
+ for (; nX >= nEndX; nX--)
+ SetPixel(nY, nX, rLineColor);
+ }
+ }
+ else
+ {
+ const tools::Long nDX = std::abs(rEnd.X() - rStart.X());
+ const tools::Long nDY = std::abs(rEnd.Y() - rStart.Y());
+ tools::Long nX1;
+ tools::Long nY1;
+ tools::Long nX2;
+ tools::Long nY2;
+
+ if (nDX >= nDY)
+ {
+ if (rStart.X() < rEnd.X())
+ {
+ nX1 = rStart.X();
+ nY1 = rStart.Y();
+ nX2 = rEnd.X();
+ nY2 = rEnd.Y();
+ }
+ else
+ {
+ nX1 = rEnd.X();
+ nY1 = rEnd.Y();
+ nX2 = rStart.X();
+ nY2 = rStart.Y();
+ }
+
+ const tools::Long nDYX = (nDY - nDX) << 1;
+ const tools::Long nDY2 = nDY << 1;
+ tools::Long nD = nDY2 - nDX;
+ bool bPos = nY1 < nY2;
+
+ for (nX = nX1, nY = nY1; nX <= nX2; nX++)
+ {
+ SetPixel(nY, nX, rLineColor);
+
+ if (nD < 0)
+ nD += nDY2;
+ else
+ {
+ nD += nDYX;
+
+ if (bPos)
+ nY++;
+ else
+ nY--;
+ }
+ }
+ }
+ else
+ {
+ if (rStart.Y() < rEnd.Y())
+ {
+ nX1 = rStart.X();
+ nY1 = rStart.Y();
+ nX2 = rEnd.X();
+ nY2 = rEnd.Y();
+ }
+ else
+ {
+ nX1 = rEnd.X();
+ nY1 = rEnd.Y();
+ nX2 = rStart.X();
+ nY2 = rStart.Y();
+ }
+
+ const tools::Long nDYX = (nDX - nDY) << 1;
+ const tools::Long nDY2 = nDX << 1;
+ tools::Long nD = nDY2 - nDY;
+ bool bPos = nX1 < nX2;
+
+ for (nX = nX1, nY = nY1; nY <= nY2; nY++)
+ {
+ SetPixel(nY, nX, rLineColor);
+
+ if (nD < 0)
+ nD += nDY2;
+ else
+ {
+ nD += nDYX;
+
+ if (bPos)
+ nX++;
+ else
+ nX--;
+ }
+ }
+ }
+ }
+}
+
+void BitmapWriteAccess::FillRect(const tools::Rectangle& rRect)
+{
+ if (!mpFillColor)
+ return;
+
+ const BitmapColor& rFillColor = *mpFillColor;
+ tools::Rectangle aRect(Point(), maBitmap.GetSizePixel());
+
+ aRect.Intersection(rRect);
+
+ if (aRect.IsEmpty())
+ return;
+
+ const tools::Long nStartX = rRect.Left();
+ const tools::Long nStartY = rRect.Top();
+ const tools::Long nEndX = rRect.Right();
+ const tools::Long nEndY = rRect.Bottom();
+
+ for (tools::Long nY = nStartY; nY <= nEndY; nY++)
+ {
+ Scanline pScanline = GetScanline(nY);
+ for (tools::Long nX = nStartX; nX <= nEndX; nX++)
+ {
+ SetPixelOnData(pScanline, nX, rFillColor);
+ }
+ }
+}
+
+void BitmapWriteAccess::DrawRect(const tools::Rectangle& rRect)
+{
+ if (mpFillColor)
+ FillRect(rRect);
+
+ if (mpLineColor && (!mpFillColor || (*mpFillColor != *mpLineColor)))
+ {
+ DrawLine(rRect.TopLeft(), rRect.TopRight());
+ DrawLine(rRect.TopRight(), rRect.BottomRight());
+ DrawLine(rRect.BottomRight(), rRect.BottomLeft());
+ DrawLine(rRect.BottomLeft(), rRect.TopLeft());
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/Octree.cxx b/vcl/source/bitmap/Octree.cxx
new file mode 100644
index 0000000000..98ad8c9fcf
--- /dev/null
+++ b/vcl/source/bitmap/Octree.cxx
@@ -0,0 +1,280 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <vcl/BitmapReadAccess.hxx>
+
+#include <bitmap/Octree.hxx>
+
+namespace
+{
+constexpr size_t OCTREE_BITS = 5;
+constexpr size_t OCTREE_BITS_1 = 10;
+
+constexpr sal_uLong gnBits = 8 - OCTREE_BITS;
+
+} // end anonymous namespace
+
+Octree::Octree(const BitmapReadAccess& rReadAcc, sal_uLong nColors)
+ : mnLeafCount(0)
+ , mnLevel(0)
+ , mpReduce(OCTREE_BITS + 1, nullptr)
+ , mnPalIndex(0)
+{
+ const BitmapReadAccess* pAccess = &rReadAcc;
+ sal_uLong nMax(nColors);
+
+ if (!*pAccess)
+ return;
+
+ const tools::Long nWidth = pAccess->Width();
+ const tools::Long nHeight = pAccess->Height();
+
+ if (pAccess->HasPalette())
+ {
+ for (tools::Long nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pAccess->GetScanline(nY);
+ for (tools::Long nX = 0; nX < nWidth; nX++)
+ {
+ mnLevel = 0;
+ add(pTree, pAccess->GetPaletteColor(pAccess->GetIndexFromData(pScanline, nX)));
+
+ while (mnLeafCount > nMax)
+ reduce();
+ }
+ }
+ }
+ else
+ {
+ for (tools::Long nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pAccess->GetScanline(nY);
+ for (tools::Long nX = 0; nX < nWidth; nX++)
+ {
+ mnLevel = 0;
+ add(pTree, pAccess->GetPixelFromData(pScanline, nX));
+
+ while (mnLeafCount > nMax)
+ reduce();
+ }
+ }
+ }
+}
+
+Octree::~Octree() {}
+
+void Octree::add(std::unique_ptr<OctreeNode>& rpNode, BitmapColor const& color)
+{
+ // possibly generate new nodes
+ if (!rpNode)
+ {
+ rpNode.reset(new OctreeNode);
+ rpNode->bLeaf = (OCTREE_BITS == mnLevel);
+
+ if (rpNode->bLeaf)
+ mnLeafCount++;
+ else
+ {
+ rpNode->pNext = mpReduce[mnLevel];
+ mpReduce[mnLevel] = rpNode.get();
+ }
+ }
+
+ if (rpNode->bLeaf)
+ {
+ rpNode->nCount++;
+ rpNode->nRed += color.GetRed();
+ rpNode->nGreen += color.GetGreen();
+ rpNode->nBlue += color.GetBlue();
+ }
+ else
+ {
+ const sal_uLong nShift = 7 - mnLevel;
+ const sal_uInt8 cMask = 0x80 >> mnLevel;
+ const sal_uLong nIndex = (((color.GetRed() & cMask) >> nShift) << 2)
+ | (((color.GetGreen() & cMask) >> nShift) << 1)
+ | ((color.GetBlue() & cMask) >> nShift);
+
+ mnLevel++;
+ add(rpNode->pChild[nIndex], color);
+ }
+}
+
+void Octree::reduce()
+{
+ OctreeNode* pNode;
+ sal_uLong nRedSum = 0;
+ sal_uLong nGreenSum = 0;
+ sal_uLong nBlueSum = 0;
+ sal_uLong nChildren = 0;
+
+ sal_uLong nIndex = OCTREE_BITS - 1;
+ while (nIndex > 0 && !mpReduce[nIndex])
+ {
+ nIndex--;
+ }
+
+ pNode = mpReduce[nIndex];
+ mpReduce[nIndex] = pNode->pNext;
+
+ for (unsigned int i = 0; i < 8; i++)
+ {
+ if (pNode->pChild[i])
+ {
+ OctreeNode* pChild = pNode->pChild[i].get();
+
+ nRedSum += pChild->nRed;
+ nGreenSum += pChild->nGreen;
+ nBlueSum += pChild->nBlue;
+ pNode->nCount += pChild->nCount;
+
+ pNode->pChild[i].reset();
+ nChildren++;
+ }
+ }
+
+ pNode->bLeaf = true;
+ pNode->nRed = nRedSum;
+ pNode->nGreen = nGreenSum;
+ pNode->nBlue = nBlueSum;
+ mnLeafCount -= --nChildren;
+}
+
+void Octree::CreatePalette(OctreeNode* pNode)
+{
+ if (pNode->bLeaf)
+ {
+ pNode->nPalIndex = mnPalIndex;
+ maPalette[mnPalIndex++] = BitmapColor(sal_uInt8(double(pNode->nRed) / pNode->nCount),
+ sal_uInt8(double(pNode->nGreen) / pNode->nCount),
+ sal_uInt8(double(pNode->nBlue) / pNode->nCount));
+ }
+ else
+ {
+ for (auto const& i : pNode->pChild)
+ {
+ if (i)
+ {
+ CreatePalette(i.get());
+ }
+ }
+ }
+}
+
+void Octree::GetPalIndex(const OctreeNode* pNode, BitmapColor const& color)
+{
+ if (pNode->bLeaf)
+ mnPalIndex = pNode->nPalIndex;
+ else
+ {
+ const sal_uLong nShift = 7 - mnLevel;
+ const sal_uInt8 cMask = 0x80 >> mnLevel;
+ mnLevel++;
+ const sal_uLong nIndex = (((color.GetRed() & cMask) >> nShift) << 2)
+ | (((color.GetGreen() & cMask) >> nShift) << 1)
+ | ((color.GetBlue() & cMask) >> nShift);
+
+ GetPalIndex(pNode->pChild[nIndex].get(), color);
+ }
+}
+
+const BitmapPalette& Octree::GetPalette()
+{
+ maPalette.SetEntryCount(sal_uInt16(mnLeafCount));
+ mnPalIndex = 0;
+ CreatePalette(pTree.get());
+ return maPalette;
+}
+
+sal_uInt16 Octree::GetBestPaletteIndex(const BitmapColor& rColor)
+{
+ mnPalIndex = 65535;
+ mnLevel = 0;
+ GetPalIndex(pTree.get(), rColor);
+ return mnPalIndex;
+}
+
+constexpr int nColorMax = 1 << OCTREE_BITS;
+
+InverseColorMap::InverseColorMap(const BitmapPalette& rPal)
+{
+ const unsigned long xsqr = 1 << (gnBits << 1);
+ const unsigned long xsqr2 = xsqr << 1;
+ const int nColors = rPal.GetEntryCount();
+ const tools::Long x = 1 << gnBits;
+ const tools::Long x2 = x >> 1;
+ sal_uLong r, g, b;
+ tools::Long rxx, gxx, bxx;
+
+ ImplCreateBuffers();
+
+ for (int nIndex = 0; nIndex < nColors; nIndex++)
+ {
+ const BitmapColor& rColor = rPal[static_cast<sal_uInt16>(nIndex)];
+ const tools::Long cRed = rColor.GetRed();
+ const tools::Long cGreen = rColor.GetGreen();
+ const tools::Long cBlue = rColor.GetBlue();
+
+ tools::Long rdist = cRed - x2;
+ tools::Long gdist = cGreen - x2;
+ tools::Long bdist = cBlue - x2;
+ rdist = rdist * rdist + gdist * gdist + bdist * bdist;
+
+ const tools::Long crinc = (xsqr - (cRed << gnBits)) << 1;
+ const tools::Long cginc = (xsqr - (cGreen << gnBits)) << 1;
+ const tools::Long cbinc = (xsqr - (cBlue << gnBits)) << 1;
+
+ sal_uLong* cdp = reinterpret_cast<sal_uLong*>(mpBuffer.data());
+ sal_uInt8* crgbp = mpMap.data();
+
+ for (r = 0, rxx = crinc; r < nColorMax; rdist += rxx, r++, rxx += xsqr2)
+ {
+ for (g = 0, gdist = rdist, gxx = cginc; g < nColorMax; gdist += gxx, g++, gxx += xsqr2)
+ {
+ for (b = 0, bdist = gdist, bxx = cbinc; b < nColorMax;
+ bdist += bxx, b++, cdp++, crgbp++, bxx += xsqr2)
+ if (!nIndex || static_cast<tools::Long>(*cdp) > bdist)
+ {
+ *cdp = bdist;
+ *crgbp = static_cast<sal_uInt8>(nIndex);
+ }
+ }
+ }
+ }
+}
+
+InverseColorMap::~InverseColorMap() {}
+
+void InverseColorMap::ImplCreateBuffers()
+{
+ const sal_uLong nCount = nColorMax * nColorMax * nColorMax;
+ const sal_uLong nSize = nCount * sizeof(sal_uLong);
+
+ mpMap.resize(nCount, 0x00);
+ mpBuffer.resize(nSize, 0xff);
+}
+
+sal_uInt16 InverseColorMap::GetBestPaletteIndex(const BitmapColor& rColor)
+{
+ return mpMap[((static_cast<sal_uLong>(rColor.GetRed()) >> gnBits) << OCTREE_BITS_1)
+ | ((static_cast<sal_uLong>(rColor.GetGreen()) >> gnBits) << OCTREE_BITS)
+ | (static_cast<sal_uLong>(rColor.GetBlue()) >> gnBits)];
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/alpha.cxx b/vcl/source/bitmap/alpha.cxx
new file mode 100644
index 0000000000..634fc3cdd6
--- /dev/null
+++ b/vcl/source/bitmap/alpha.cxx
@@ -0,0 +1,209 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <tools/color.hxx>
+#include <vcl/alpha.hxx>
+
+#include <vcl/BitmapWriteAccess.hxx>
+#include <salinst.hxx>
+#include <svdata.hxx>
+#include <salbmp.hxx>
+#include <sal/log.hxx>
+#if HAVE_FEATURE_SKIA
+#include <vcl/skia/SkiaHelper.hxx>
+#endif
+
+
+AlphaMask::AlphaMask() = default;
+
+AlphaMask::AlphaMask( const Bitmap& rBitmap ) :
+ maBitmap( rBitmap )
+{
+ if ( !rBitmap.IsEmpty() )
+ maBitmap.Convert( BmpConversion::N8BitNoConversion );
+#if HAVE_FEATURE_SKIA
+ // Related tdf#156866 force snapshot of alpha mask when using Skia
+ // In release builds, tdf#156629 and tdf#156630 reappear in many
+ // cases because a BitmapInfoAccess is in a debug block. So, instead
+ // of relying on other code to a create a BitmapInfoAccess instance,
+ // create one here to force the alpha mask to handle any pending
+ // scaling and make the alpha mask immutable.
+ else if ( SkiaHelper::isVCLSkiaEnabled() )
+ BitmapInfoAccess aInfoAccess( maBitmap );
+#endif
+ assert( (IsEmpty() || maBitmap.getPixelFormat() == vcl::PixelFormat::N8_BPP) && "alpha bitmap should be 8bpp" );
+ assert( (IsEmpty() || maBitmap.HasGreyPalette8Bit()) && "alpha bitmap should have greyscale palette" );
+}
+
+AlphaMask::AlphaMask( const AlphaMask& ) = default;
+
+AlphaMask::AlphaMask( AlphaMask&& ) = default;
+
+AlphaMask::AlphaMask( const Size& rSizePixel, const sal_uInt8* pEraseTransparency )
+ : maBitmap(rSizePixel, vcl::PixelFormat::N8_BPP, &Bitmap::GetGreyPalette(256))
+{
+ if( pEraseTransparency )
+ {
+ sal_uInt8 nAlpha = 255 - *pEraseTransparency;
+ maBitmap.Erase( Color( nAlpha, nAlpha, nAlpha ) );
+ }
+ else
+ maBitmap.Erase( COL_ALPHA_OPAQUE );
+}
+
+AlphaMask::~AlphaMask() = default;
+
+AlphaMask& AlphaMask::operator=( const Bitmap& rBitmap )
+{
+ maBitmap = rBitmap;
+
+ if( !rBitmap.IsEmpty() )
+ maBitmap.Convert( BmpConversion::N8BitNoConversion );
+
+ assert( maBitmap.getPixelFormat() == vcl::PixelFormat::N8_BPP && "alpha bitmap should be 8bpp" );
+ assert( maBitmap.HasGreyPalette8Bit() && "alpha bitmap should have greyscale palette" );
+
+ return *this;
+}
+
+void AlphaMask::Erase( sal_uInt8 cTransparency )
+{
+ sal_uInt8 nAlpha = 255 - cTransparency;
+ maBitmap.Erase( Color( nAlpha, nAlpha, nAlpha ) );
+}
+
+void AlphaMask::BlendWith(const AlphaMask& rOther)
+{
+ std::shared_ptr<SalBitmap> xImpBmp(ImplGetSVData()->mpDefInst->CreateSalBitmap());
+ if (xImpBmp->Create(*maBitmap.ImplGetSalBitmap()) && xImpBmp->AlphaBlendWith(*rOther.maBitmap.ImplGetSalBitmap()))
+ {
+ maBitmap.ImplSetSalBitmap(xImpBmp);
+ assert( maBitmap.getPixelFormat() == vcl::PixelFormat::N8_BPP && "alpha bitmap should be 8bpp" );
+ assert( maBitmap.HasGreyPalette8Bit() && "alpha bitmap should have greyscale palette" );
+ return;
+ }
+ BitmapScopedReadAccess pOtherAcc(rOther);
+ BitmapScopedWriteAccess pAcc(*this);
+ assert (pOtherAcc && pAcc && pOtherAcc->GetBitCount() == 8 && pAcc->GetBitCount() == 8 && "cannot BlendWith this combination");
+ if (!(pOtherAcc && pAcc && pOtherAcc->GetBitCount() == 8 && pAcc->GetBitCount() == 8))
+ {
+ SAL_WARN("vcl", "cannot BlendWith this combination");
+ return;
+ }
+
+ const tools::Long nHeight = std::min(pOtherAcc->Height(), pAcc->Height());
+ const tools::Long nWidth = std::min(pOtherAcc->Width(), pAcc->Width());
+ for (tools::Long y = 0; y < nHeight; ++y)
+ {
+ Scanline scanline = pAcc->GetScanline( y );
+ ConstScanline otherScanline = pOtherAcc->GetScanline( y );
+ for (tools::Long x = 0; x < nWidth; ++x)
+ {
+ // Use sal_uInt16 for following multiplication
+ const sal_uInt16 nGrey1 = *scanline;
+ const sal_uInt16 nGrey2 = *otherScanline;
+ // 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.
+ // The original looked like:
+ // auto tmp = nGrey1 + nGrey2 - (nGrey1 * nGrey2 / 255)
+ // which, when converted to using alpha looks like
+ // auto tmp = 255 - ((255 - nGrey1) + (255 - nGrey2) - (255 - nGrey1) * (255 - nGrey2) / 255);
+ // which then simplifies to:
+ auto tmp = nGrey1 * nGrey2 / 255;
+ *scanline = static_cast<sal_uInt8>(tmp);
+ ++scanline;
+ ++otherScanline;
+ }
+ }
+ pAcc.reset();
+ assert( maBitmap.getPixelFormat() == vcl::PixelFormat::N8_BPP && "alpha bitmap should be 8bpp" );
+ assert( maBitmap.HasGreyPalette8Bit() && "alpha bitmap should have greyscale palette" );
+}
+
+bool AlphaMask::hasAlpha() const
+{
+ // no content, no alpha
+ if(IsEmpty())
+ return false;
+
+ BitmapScopedReadAccess pAcc(*this);
+ const tools::Long nHeight(pAcc->Height());
+ const tools::Long nWidth(pAcc->Width());
+
+ // no content, no alpha
+ if(0 == nHeight || 0 == nWidth)
+ return false;
+
+ for (tools::Long y = 0; y < nHeight; ++y)
+ {
+ for (tools::Long x = 0; x < nWidth; ++x)
+ {
+ if (255 != pAcc->GetColor(y, x).GetRed())
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+bool AlphaMask::AlphaCombineOr(const AlphaMask& rMask)
+{
+ BitmapScopedReadAccess pMaskAcc(rMask);
+ BitmapScopedWriteAccess pAcc(*this);
+
+ if (!pMaskAcc || !pAcc)
+ return false;
+
+ assert (pMaskAcc->GetBitCount() == 8 && pAcc->GetBitCount() == 8);
+
+ const tools::Long nWidth = std::min(pMaskAcc->Width(), pAcc->Width());
+ const tools::Long nHeight = std::min(pMaskAcc->Height(), pAcc->Height());
+
+ for (tools::Long nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pAcc->GetScanline(nY);
+ ConstScanline pScanlineMask = pMaskAcc->GetScanline(nY);
+ for (tools::Long nX = 0; nX < nWidth; nX++)
+ {
+ if (*pScanlineMask != 255 || *pScanline != 255)
+ *pScanline = 0;
+ else
+ *pScanline = 255;
+ ++pScanline;
+ ++pScanlineMask;
+ }
+ }
+
+ return true;
+}
+
+bool AlphaMask::Invert()
+{
+ if (IsEmpty())
+ return false;
+ bool b = maBitmap.Invert();
+ assert( maBitmap.getPixelFormat() == vcl::PixelFormat::N8_BPP && "alpha bitmap should be 8bpp" );
+ assert( maBitmap.HasGreyPalette8Bit() && "alpha bitmap should have greyscale palette" );
+ return b;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/bitmap.cxx b/vcl/source/bitmap/bitmap.cxx
new file mode 100644
index 0000000000..53b30d0b31
--- /dev/null
+++ b/vcl/source/bitmap/bitmap.cxx
@@ -0,0 +1,1681 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <config_features.h>
+
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+#include <tools/helpers.hxx>
+
+#include <utility>
+#include <vcl/bitmap.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/outdev.hxx>
+
+#include <svdata.hxx>
+#include <salinst.hxx>
+#include <salbmp.hxx>
+#if HAVE_FEATURE_SKIA
+#include <vcl/skia/SkiaHelper.hxx>
+#endif
+#include <vcl/BitmapMonochromeFilter.hxx>
+
+#include <bitmap/BitmapScaleSuperFilter.hxx>
+#include <bitmap/BitmapScaleConvolutionFilter.hxx>
+#include <bitmap/BitmapFastScaleFilter.hxx>
+#include <bitmap/BitmapInterpolateScaleFilter.hxx>
+#include <vcl/BitmapWriteAccess.hxx>
+#include <bitmap/impoctree.hxx>
+#include <bitmap/Octree.hxx>
+
+#include "impvect.hxx"
+#include "floyd.hxx"
+
+#include <math.h>
+#include <algorithm>
+#include <memory>
+
+#ifdef DBG_UTIL
+#include <cstdlib>
+#include <tools/stream.hxx>
+#include <vcl/graphicfilter.hxx>
+#endif
+
+Bitmap::Bitmap()
+{
+}
+
+Bitmap::Bitmap(const Bitmap& rBitmap)
+ : mxSalBmp(rBitmap.mxSalBmp)
+ , maPrefMapMode(rBitmap.maPrefMapMode)
+ , maPrefSize(rBitmap.maPrefSize)
+{
+}
+
+Bitmap::Bitmap(std::shared_ptr<SalBitmap> pSalBitmap)
+ : mxSalBmp(std::move(pSalBitmap))
+ , maPrefMapMode(MapMode(MapUnit::MapPixel))
+ , maPrefSize(mxSalBmp->GetSize())
+{
+}
+
+Bitmap::Bitmap( const Size& rSizePixel, vcl::PixelFormat ePixelFormat, const BitmapPalette* pPal )
+{
+ if (!(rSizePixel.Width() && rSizePixel.Height()))
+ return;
+
+ switch (ePixelFormat)
+ {
+ case vcl::PixelFormat::N8_BPP:
+ {
+ static const BitmapPalette aPalN8_BPP = [] {
+ BitmapPalette aPal(1 << sal_uInt16(vcl::PixelFormat::N8_BPP));
+ aPal[ 0 ] = COL_BLACK;
+ aPal[ 1 ] = COL_BLUE;
+ aPal[ 2 ] = COL_GREEN;
+ aPal[ 3 ] = COL_CYAN;
+ aPal[ 4 ] = COL_RED;
+ aPal[ 5 ] = COL_MAGENTA;
+ aPal[ 6 ] = COL_BROWN;
+ aPal[ 7 ] = COL_GRAY;
+ aPal[ 8 ] = COL_LIGHTGRAY;
+ aPal[ 9 ] = COL_LIGHTBLUE;
+ aPal[ 10 ] = COL_LIGHTGREEN;
+ aPal[ 11 ] = COL_LIGHTCYAN;
+ aPal[ 12 ] = COL_LIGHTRED;
+ aPal[ 13 ] = COL_LIGHTMAGENTA;
+ aPal[ 14 ] = COL_YELLOW;
+ aPal[ 15 ] = COL_WHITE;
+
+ // Create dither palette
+ sal_uInt16 nActCol = 16;
+
+ for( sal_uInt16 nB = 0; nB < 256; nB += 51 )
+ for( sal_uInt16 nG = 0; nG < 256; nG += 51 )
+ for( sal_uInt16 nR = 0; nR < 256; nR += 51 )
+ aPal[ nActCol++ ] = BitmapColor( static_cast<sal_uInt8>(nR), static_cast<sal_uInt8>(nG), static_cast<sal_uInt8>(nB) );
+
+ // Set standard Office colors
+ aPal[ nActCol++ ] = BitmapColor( 0, 184, 255 );
+ return aPal;
+ }();
+ if (!pPal)
+ pPal = &aPalN8_BPP;
+ break;
+ }
+ default:
+ {
+ static const BitmapPalette aPalEmpty;
+ if (!pPal || !vcl::isPalettePixelFormat(ePixelFormat))
+ pPal = &aPalEmpty;
+ break;
+ }
+ }
+
+ mxSalBmp = ImplGetSVData()->mpDefInst->CreateSalBitmap();
+ mxSalBmp->Create(rSizePixel, ePixelFormat, *pPal);
+}
+
+#ifdef DBG_UTIL
+
+namespace
+{
+void savePNG(const OUString& sWhere, const Bitmap& rBmp)
+{
+ SvFileStream aStream(sWhere, StreamMode::WRITE | StreamMode::TRUNC);
+ GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
+ rFilter.compressAsPNG(BitmapEx(rBmp), aStream);
+}
+}
+
+#endif
+
+Bitmap::~Bitmap()
+{
+#ifdef DBG_UTIL
+ // VCL_DUMP_BMP_PATH should be like C:/path/ or ~/path/
+ static const OUString sDumpPath(OUString::createFromAscii(std::getenv("VCL_DUMP_BMP_PATH")));
+ // Stepping into the dtor of a bitmap you need, and setting the volatile variable to true in
+ // debugger, would dump the bitmap in question
+ static volatile bool save(false);
+ if (!sDumpPath.isEmpty() && save)
+ {
+ save = false;
+ savePNG(sDumpPath + "BitmapDump.png", *this);
+ }
+#endif
+}
+
+namespace
+{
+template <size_t N>
+constexpr std::enable_if_t<255 % (N - 1) == 0, std::array<BitmapColor, N>> getGreyscalePalette()
+{
+ const int step = 255 / (N - 1);
+ std::array<BitmapColor, N> a;
+ for (size_t i = 0; i < N; ++i)
+ a[i] = BitmapColor(i * step, i * step, i * step);
+ return a;
+}
+}
+
+const BitmapPalette& Bitmap::GetGreyPalette( int nEntries )
+{
+ // Create greyscale palette with 2, 4, 16 or 256 entries
+ switch (nEntries)
+ {
+ case 2:
+ {
+ static const BitmapPalette aGreyPalette2 = getGreyscalePalette<2>();
+ return aGreyPalette2;
+ }
+ case 4:
+ {
+ static const BitmapPalette aGreyPalette4 = getGreyscalePalette<4>();
+ return aGreyPalette4;
+ }
+ case 16:
+ {
+ static const BitmapPalette aGreyPalette16 = getGreyscalePalette<16>();
+ return aGreyPalette16;
+ }
+ case 256:
+ {
+ static const BitmapPalette aGreyPalette256 = getGreyscalePalette<256>();
+ return aGreyPalette256;
+ }
+ }
+ OSL_FAIL("Bitmap::GetGreyPalette: invalid entry count (2/4/16/256 allowed)");
+ return GetGreyPalette(2);
+}
+
+Bitmap& Bitmap::operator=( const Bitmap& rBitmap )
+{
+ if (this == &rBitmap)
+ return *this;
+
+ maPrefSize = rBitmap.maPrefSize;
+ maPrefMapMode = rBitmap.maPrefMapMode;
+ mxSalBmp = rBitmap.mxSalBmp;
+
+ return *this;
+}
+
+Bitmap& Bitmap::operator=( Bitmap&& rBitmap ) noexcept
+{
+ maPrefSize = std::move(rBitmap.maPrefSize);
+ maPrefMapMode = std::move(rBitmap.maPrefMapMode);
+ mxSalBmp = std::move(rBitmap.mxSalBmp);
+
+ return *this;
+}
+
+bool Bitmap::operator==( const Bitmap& rBmp ) const
+{
+ if (rBmp.mxSalBmp == mxSalBmp) // Includes both are nullptr
+ return true;
+ if (!rBmp.mxSalBmp || !mxSalBmp)
+ return false;
+ if (rBmp.mxSalBmp->GetSize() != mxSalBmp->GetSize() ||
+ rBmp.mxSalBmp->GetBitCount() != mxSalBmp->GetBitCount())
+ return false;
+ BitmapChecksum aChecksum1 = rBmp.mxSalBmp->GetChecksum();
+ BitmapChecksum aChecksum2 = mxSalBmp->GetChecksum();
+ // If the bitmaps can't calculate a checksum, best to regard them as different.
+ if (aChecksum1 == 0 || aChecksum2 == 0)
+ return false;
+ return aChecksum1 == aChecksum2;
+}
+
+void Bitmap::SetEmpty()
+{
+ maPrefMapMode = MapMode();
+ maPrefSize = Size();
+ mxSalBmp.reset();
+}
+
+Size Bitmap::GetSizePixel() const
+{
+ return( mxSalBmp ? mxSalBmp->GetSize() : Size() );
+}
+
+vcl::PixelFormat Bitmap::getPixelFormat() const
+{
+ if (!mxSalBmp)
+ return vcl::PixelFormat::INVALID;
+
+ sal_uInt16 nBitCount = mxSalBmp->GetBitCount();
+ if (nBitCount <= 8)
+ return vcl::PixelFormat::N8_BPP;
+ if (nBitCount <= 24)
+ return vcl::PixelFormat::N24_BPP;
+ if (nBitCount <= 32)
+ return vcl::PixelFormat::N32_BPP;
+
+ return vcl::PixelFormat::INVALID;
+}
+
+bool Bitmap::HasGreyPaletteAny() const
+{
+ bool bRet = false;
+
+ BitmapScopedInfoAccess pIAcc(*this);
+
+ if( pIAcc )
+ {
+ bRet = pIAcc->HasPalette() && pIAcc->GetPalette().IsGreyPaletteAny();
+ }
+
+ return bRet;
+}
+
+bool Bitmap::HasGreyPalette8Bit() const
+{
+ bool bRet = false;
+ BitmapScopedInfoAccess pIAcc(*this);
+
+ if( pIAcc )
+ {
+ bRet = pIAcc->HasPalette() && pIAcc->GetPalette().IsGreyPalette8Bit();
+ }
+
+ return bRet;
+}
+
+BitmapChecksum Bitmap::GetChecksum() const
+{
+ if( !mxSalBmp )
+ return 0;
+
+ BitmapChecksum nRet = mxSalBmp->GetChecksum();
+ if (!nRet)
+ {
+ // nRet == 0 => probably, we were not able to acquire
+ // the buffer in SalBitmap::updateChecksum;
+ // so, we need to update the imp bitmap for this bitmap instance
+ // as we do in BitmapInfoAccess::ImplCreate
+ std::shared_ptr<SalBitmap> xNewImpBmp(ImplGetSVData()->mpDefInst->CreateSalBitmap());
+ if (xNewImpBmp->Create(*mxSalBmp, getPixelFormat()))
+ {
+ Bitmap* pThis = const_cast<Bitmap*>(this);
+ pThis->mxSalBmp = xNewImpBmp;
+ nRet = mxSalBmp->GetChecksum();
+ }
+ }
+
+ return nRet;
+}
+
+void Bitmap::ImplMakeUnique()
+{
+ if (mxSalBmp && mxSalBmp.use_count() > 1)
+ {
+ std::shared_ptr<SalBitmap> xOldImpBmp = mxSalBmp;
+ mxSalBmp = ImplGetSVData()->mpDefInst->CreateSalBitmap();
+ (void)mxSalBmp->Create(*xOldImpBmp);
+ }
+}
+
+void Bitmap::ReassignWithSize(const Bitmap& rBitmap)
+{
+ const Size aOldSizePix(GetSizePixel());
+ const Size aNewSizePix(rBitmap.GetSizePixel());
+ const MapMode aOldMapMode(maPrefMapMode);
+ Size aNewPrefSize;
+
+ if ((aOldSizePix != aNewSizePix) && aOldSizePix.Width() && aOldSizePix.Height())
+ {
+ aNewPrefSize.setWidth(FRound(maPrefSize.Width() * aNewSizePix.Width() / aOldSizePix.Width()));
+ aNewPrefSize.setHeight(FRound(maPrefSize.Height() * aNewSizePix.Height() / aOldSizePix.Height()));
+ }
+ else
+ {
+ aNewPrefSize = maPrefSize;
+ }
+
+ *this = rBitmap;
+
+ maPrefSize = aNewPrefSize;
+ maPrefMapMode = aOldMapMode;
+}
+
+void Bitmap::ImplSetSalBitmap(const std::shared_ptr<SalBitmap>& xImpBmp)
+{
+ mxSalBmp = xImpBmp;
+}
+
+bool Bitmap::Crop( const tools::Rectangle& rRectPixel )
+{
+ const Size aSizePix( GetSizePixel() );
+ tools::Rectangle aRect( rRectPixel );
+
+ aRect.Intersection( tools::Rectangle( Point(), aSizePix ) );
+
+ if( aRect.IsEmpty() || aSizePix == aRect.GetSize())
+ return false;
+
+ BitmapScopedReadAccess pReadAcc(*this);
+ if( !pReadAcc )
+ return false;
+
+ const tools::Rectangle aNewRect( Point(), aRect.GetSize() );
+ Bitmap aNewBmp(aNewRect.GetSize(), getPixelFormat(), &pReadAcc->GetPalette());
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+ if( !pWriteAcc )
+ return false;
+
+ const tools::Long nOldX = aRect.Left();
+ const tools::Long nOldY = aRect.Top();
+ const tools::Long nNewWidth = aNewRect.GetWidth();
+ const tools::Long nNewHeight = aNewRect.GetHeight();
+
+ for( tools::Long nY = 0, nY2 = nOldY; nY < nNewHeight; nY++, nY2++ )
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ Scanline pScanlineRead = pReadAcc->GetScanline(nY2);
+ for( tools::Long nX = 0, nX2 = nOldX; nX < nNewWidth; nX++, nX2++ )
+ pWriteAcc->SetPixelOnData( pScanline, nX, pReadAcc->GetPixelFromData( pScanlineRead, nX2 ) );
+ }
+
+ pWriteAcc.reset();
+ pReadAcc.reset();
+
+ ReassignWithSize( aNewBmp );
+
+ return true;
+};
+
+bool Bitmap::CopyPixel( const tools::Rectangle& rRectDst,
+ const tools::Rectangle& rRectSrc )
+{
+ const Size aSizePix( GetSizePixel() );
+ tools::Rectangle aRectDst( rRectDst );
+
+ aRectDst.Intersection( tools::Rectangle( Point(), aSizePix ) );
+
+ if( aRectDst.IsEmpty() )
+ return false;
+
+ tools::Rectangle aRectSrc( rRectSrc );
+
+ aRectSrc.Intersection( tools::Rectangle( Point(), aSizePix ) );
+
+ if( aRectSrc.IsEmpty() || ( aRectSrc == aRectDst ) )
+ return false;
+
+ BitmapScopedWriteAccess pWriteAcc(*this);
+ if( !pWriteAcc )
+ return false;
+
+ const tools::Long nWidth = std::min( aRectSrc.GetWidth(), aRectDst.GetWidth() );
+ const tools::Long nHeight = std::min( aRectSrc.GetHeight(), aRectDst.GetHeight() );
+ const tools::Long nSrcX = aRectSrc.Left();
+ const tools::Long nSrcY = aRectSrc.Top();
+ const tools::Long nSrcEndX1 = nSrcX + nWidth - 1;
+ const tools::Long nSrcEndY1 = nSrcY + nHeight - 1;
+ const tools::Long nDstX = aRectDst.Left();
+ const tools::Long nDstY = aRectDst.Top();
+ const tools::Long nDstEndX1 = nDstX + nWidth - 1;
+ const tools::Long nDstEndY1 = nDstY + nHeight - 1;
+
+ if( ( nDstX <= nSrcX ) && ( nDstY <= nSrcY ) )
+ {
+ for( tools::Long nY = nSrcY, nYN = nDstY; nY <= nSrcEndY1; nY++, nYN++ )
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nYN);
+ Scanline pScanlineSrc = pWriteAcc->GetScanline(nY);
+ for( tools::Long nX = nSrcX, nXN = nDstX; nX <= nSrcEndX1; nX++, nXN++ )
+ pWriteAcc->SetPixelOnData( pScanline, nXN, pWriteAcc->GetPixelFromData( pScanlineSrc, nX ) );
+ }
+ }
+ else if( ( nDstX <= nSrcX ) && ( nDstY >= nSrcY ) )
+ {
+ for( tools::Long nY = nSrcEndY1, nYN = nDstEndY1; nY >= nSrcY; nY--, nYN-- )
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nYN);
+ Scanline pScanlineSrc = pWriteAcc->GetScanline(nY);
+ for( tools::Long nX = nSrcX, nXN = nDstX; nX <= nSrcEndX1; nX++, nXN++ )
+ pWriteAcc->SetPixelOnData( pScanline, nXN, pWriteAcc->GetPixelFromData( pScanlineSrc, nX ) );
+ }
+ }
+ else if( ( nDstX >= nSrcX ) && ( nDstY <= nSrcY ) )
+ {
+ for( tools::Long nY = nSrcY, nYN = nDstY; nY <= nSrcEndY1; nY++, nYN++ )
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nYN);
+ Scanline pScanlineSrc = pWriteAcc->GetScanline(nY);
+ for( tools::Long nX = nSrcEndX1, nXN = nDstEndX1; nX >= nSrcX; nX--, nXN-- )
+ pWriteAcc->SetPixelOnData( pScanline, nXN, pWriteAcc->GetPixelFromData( pScanlineSrc, nX ) );
+ }
+ }
+ else
+ {
+ for( tools::Long nY = nSrcEndY1, nYN = nDstEndY1; nY >= nSrcY; nY--, nYN-- )
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nYN);
+ Scanline pScanlineSrc = pWriteAcc->GetScanline(nY);
+ for( tools::Long nX = nSrcEndX1, nXN = nDstEndX1; nX >= nSrcX; nX--, nXN-- )
+ pWriteAcc->SetPixelOnData( pScanline, nXN, pWriteAcc->GetPixelFromData( pScanlineSrc, nX ) );
+ }
+ }
+
+ return true;
+}
+
+bool Bitmap::CopyPixel( const tools::Rectangle& rRectDst,
+ const tools::Rectangle& rRectSrc, const Bitmap& rBmpSrc )
+{
+ const Size aSizePix( GetSizePixel() );
+ tools::Rectangle aRectDst( rRectDst );
+
+ aRectDst.Intersection( tools::Rectangle( Point(), aSizePix ) );
+
+ if( aRectDst.IsEmpty() )
+ return false;
+
+ if( rBmpSrc.mxSalBmp == mxSalBmp ) // if self-copy
+ return CopyPixel(rRectDst, rRectSrc);
+
+ Bitmap* pSrc = &const_cast<Bitmap&>(rBmpSrc);
+ const Size aCopySizePix( pSrc->GetSizePixel() );
+ tools::Rectangle aRectSrc( rRectSrc );
+ const sal_uInt16 nSrcBitCount = vcl::pixelFormatBitCount(rBmpSrc.getPixelFormat());
+ const sal_uInt16 nDstBitCount = vcl::pixelFormatBitCount(getPixelFormat());
+
+ if( nSrcBitCount > nDstBitCount )
+ {
+ int nNextIndex = 0;
+
+ if (nSrcBitCount == 24)
+ Convert( BmpConversion::N24Bit );
+ else if (nSrcBitCount == 8)
+ {
+ Convert( BmpConversion::N8BitColors );
+ nNextIndex = 16;
+ }
+ else if (nSrcBitCount == 4)
+ {
+ assert(false);
+ }
+
+ if( nNextIndex )
+ {
+ BitmapScopedReadAccess pSrcAcc(*pSrc);
+ BitmapScopedWriteAccess pDstAcc(*this);
+
+ if( pSrcAcc && pDstAcc )
+ {
+ const int nSrcCount = pSrcAcc->GetPaletteEntryCount();
+ const int nDstCount = 1 << nDstBitCount;
+
+ for (int i = 0; ( i < nSrcCount ) && ( nNextIndex < nDstCount ); ++i)
+ {
+ const BitmapColor& rSrcCol = pSrcAcc->GetPaletteColor( static_cast<sal_uInt16>(i) );
+
+ bool bFound = false;
+
+ for (int j = 0; j < nDstCount; ++j)
+ {
+ if( rSrcCol == pDstAcc->GetPaletteColor( static_cast<sal_uInt16>(j) ) )
+ {
+ bFound = true;
+ break;
+ }
+ }
+
+ if( !bFound )
+ pDstAcc->SetPaletteColor( static_cast<sal_uInt16>(nNextIndex++), rSrcCol );
+ }
+ }
+ }
+ }
+
+ aRectSrc.Intersection( tools::Rectangle( Point(), aCopySizePix ) );
+
+ if( aRectSrc.IsEmpty() )
+ return false;
+
+ BitmapScopedReadAccess pReadAcc(*pSrc);
+ if( !pReadAcc )
+ return false;
+
+ BitmapScopedWriteAccess pWriteAcc(*this);
+ if( !pWriteAcc )
+ return false;
+
+ const tools::Long nWidth = std::min( aRectSrc.GetWidth(), aRectDst.GetWidth() );
+ const tools::Long nHeight = std::min( aRectSrc.GetHeight(), aRectDst.GetHeight() );
+ const tools::Long nSrcEndX = aRectSrc.Left() + nWidth;
+ const tools::Long nSrcEndY = aRectSrc.Top() + nHeight;
+ tools::Long nDstY = aRectDst.Top();
+
+ if( pReadAcc->HasPalette() && pWriteAcc->HasPalette() )
+ {
+ const sal_uInt16 nCount = pReadAcc->GetPaletteEntryCount();
+ std::unique_ptr<sal_uInt8[]> pMap(new sal_uInt8[ nCount ]);
+
+ // Create index map for the color table, as the bitmap should be copied
+ // retaining it's color information relatively well
+ for( sal_uInt16 i = 0; i < nCount; i++ )
+ pMap[ i ] = static_cast<sal_uInt8>(pWriteAcc->GetBestPaletteIndex( pReadAcc->GetPaletteColor( i ) ));
+
+ for( tools::Long nSrcY = aRectSrc.Top(); nSrcY < nSrcEndY; nSrcY++, nDstY++ )
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nDstY);
+ Scanline pScanlineRead = pReadAcc->GetScanline(nSrcY);
+ for( tools::Long nSrcX = aRectSrc.Left(), nDstX = aRectDst.Left(); nSrcX < nSrcEndX; nSrcX++, nDstX++ )
+ pWriteAcc->SetPixelOnData( pScanline, nDstX, BitmapColor( pMap[ pReadAcc->GetIndexFromData( pScanlineRead, nSrcX ) ] ));
+ }
+ }
+ else if( pReadAcc->HasPalette() )
+ {
+ for( tools::Long nSrcY = aRectSrc.Top(); nSrcY < nSrcEndY; nSrcY++, nDstY++ )
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nDstY);
+ Scanline pScanlineRead = pReadAcc->GetScanline(nSrcY);
+ for( tools::Long nSrcX = aRectSrc.Left(), nDstX = aRectDst.Left(); nSrcX < nSrcEndX; nSrcX++, nDstX++ )
+ pWriteAcc->SetPixelOnData( pScanline, nDstX, pReadAcc->GetPaletteColor( pReadAcc->GetIndexFromData( pScanlineRead, nSrcX ) ) );
+ }
+ }
+ else
+ for( tools::Long nSrcY = aRectSrc.Top(); nSrcY < nSrcEndY; nSrcY++, nDstY++ )
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nDstY);
+ Scanline pScanlineRead = pReadAcc->GetScanline(nSrcY);
+ for( tools::Long nSrcX = aRectSrc.Left(), nDstX = aRectDst.Left(); nSrcX < nSrcEndX; nSrcX++, nDstX++ )
+ pWriteAcc->SetPixelOnData( pScanline, nDstX, pReadAcc->GetPixelFromData( pScanlineRead, nSrcX ) );
+ }
+
+ bool bRet = ( nWidth > 0 ) && ( nHeight > 0 );
+
+ return bRet;
+}
+
+bool Bitmap::CopyPixel_AlphaOptimized( const tools::Rectangle& rRectDst, const tools::Rectangle& rRectSrc )
+{
+ assert(HasGreyPalette8Bit());
+ // Note: this code is copied from Bitmap::CopyPixel but avoids any palette lookups
+ // This optimization is possible because the palettes of AlphaMasks are always identical (8bit GreyPalette, see ctor)
+ const Size aSizePix( GetSizePixel() );
+ tools::Rectangle aRectDst( rRectDst );
+
+ aRectDst.Intersection( tools::Rectangle( Point(), aSizePix ) );
+
+ if( aRectDst.IsEmpty() )
+ return false;
+
+ tools::Rectangle aRectSrc( rRectSrc );
+ aRectSrc.Intersection( tools::Rectangle( Point(), aSizePix ) );
+ if( aRectSrc.IsEmpty() || ( aRectSrc == aRectDst ) )
+ return false;
+
+ BitmapScopedWriteAccess pWriteAcc(*this);
+ if( !pWriteAcc )
+ return false;
+
+ const tools::Long nWidth = std::min( aRectSrc.GetWidth(), aRectDst.GetWidth() );
+ const tools::Long nHeight = std::min( aRectSrc.GetHeight(), aRectDst.GetHeight() );
+ const tools::Long nSrcX = aRectSrc.Left();
+ const tools::Long nSrcY = aRectSrc.Top();
+ const tools::Long nSrcEndX1 = nSrcX + nWidth - 1;
+ const tools::Long nSrcEndY1 = nSrcY + nHeight - 1;
+ const tools::Long nDstX = aRectDst.Left();
+ const tools::Long nDstY = aRectDst.Top();
+ const tools::Long nDstEndX1 = nDstX + nWidth - 1;
+ const tools::Long nDstEndY1 = nDstY + nHeight - 1;
+
+ if( ( nDstX <= nSrcX ) && ( nDstY <= nSrcY ) )
+ {
+ for( tools::Long nY = nSrcY, nYN = nDstY; nY <= nSrcEndY1; nY++, nYN++ )
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nYN);
+ Scanline pScanlineSrc = pWriteAcc->GetScanline(nY);
+ for( tools::Long nX = nSrcX, nXN = nDstX; nX <= nSrcEndX1; nX++, nXN++ )
+ pWriteAcc->SetPixelOnData( pScanline, nXN, pWriteAcc->GetPixelFromData( pScanlineSrc, nX ) );
+ }
+ }
+ else if( ( nDstX <= nSrcX ) && ( nDstY >= nSrcY ) )
+ {
+ for( tools::Long nY = nSrcEndY1, nYN = nDstEndY1; nY >= nSrcY; nY--, nYN-- )
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nYN);
+ Scanline pScanlineSrc = pWriteAcc->GetScanline(nY);
+ for( tools::Long nX = nSrcX, nXN = nDstX; nX <= nSrcEndX1; nX++, nXN++ )
+ pWriteAcc->SetPixelOnData( pScanline, nXN, pWriteAcc->GetPixelFromData( pScanlineSrc, nX ) );
+ }
+ }
+ else if( ( nDstX >= nSrcX ) && ( nDstY <= nSrcY ) )
+ {
+ for( tools::Long nY = nSrcY, nYN = nDstY; nY <= nSrcEndY1; nY++, nYN++ )
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nYN);
+ Scanline pScanlineSrc = pWriteAcc->GetScanline(nY);
+ for( tools::Long nX = nSrcEndX1, nXN = nDstEndX1; nX >= nSrcX; nX--, nXN-- )
+ pWriteAcc->SetPixelOnData( pScanline, nXN, pWriteAcc->GetPixelFromData( pScanlineSrc, nX ) );
+ }
+ }
+ else
+ {
+ for( tools::Long nY = nSrcEndY1, nYN = nDstEndY1; nY >= nSrcY; nY--, nYN-- )
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nYN);
+ Scanline pScanlineSrc = pWriteAcc->GetScanline(nY);
+ for( tools::Long nX = nSrcEndX1, nXN = nDstEndX1; nX >= nSrcX; nX--, nXN-- )
+ pWriteAcc->SetPixelOnData( pScanline, nXN, pWriteAcc->GetPixelFromData( pScanlineSrc, nX ) );
+ }
+ }
+
+ return true;
+}
+
+bool Bitmap::CopyPixel_AlphaOptimized( const tools::Rectangle& rRectDst, const tools::Rectangle& rRectSrc,
+ const AlphaMask& rBmpSrc )
+{
+ assert(HasGreyPalette8Bit());
+ assert(rBmpSrc.GetBitmap().HasGreyPalette8Bit());
+ // Note: this code is copied from Bitmap::CopyPixel but avoids any palette lookups
+ // This optimization is possible because the palettes of AlphaMasks are always identical (8bit GreyPalette, see ctor)
+ const Size aSizePix( GetSizePixel() );
+ tools::Rectangle aRectDst( rRectDst );
+
+ aRectDst.Intersection( tools::Rectangle( Point(), aSizePix ) );
+
+ if( aRectDst.IsEmpty() )
+ return false;
+
+ if( rBmpSrc.GetBitmap().mxSalBmp == mxSalBmp ) // self-copy
+ return CopyPixel_AlphaOptimized(rRectDst, rRectSrc);
+
+ Bitmap* pSrc = &const_cast<Bitmap&>(rBmpSrc.GetBitmap());
+ const Size aCopySizePix( pSrc->GetSizePixel() );
+ tools::Rectangle aRectSrc( rRectSrc );
+
+ aRectSrc.Intersection( tools::Rectangle( Point(), aCopySizePix ) );
+ if( aRectSrc.IsEmpty() )
+ return false;
+
+ BitmapScopedReadAccess pReadAcc(*pSrc);
+ if( !pReadAcc )
+ return false;
+
+ BitmapScopedWriteAccess pWriteAcc(*this);
+ if( !pWriteAcc )
+ return false;
+
+ const tools::Long nWidth = std::min( aRectSrc.GetWidth(), aRectDst.GetWidth() );
+ const tools::Long nHeight = std::min( aRectSrc.GetHeight(), aRectDst.GetHeight() );
+ const tools::Long nSrcEndX = aRectSrc.Left() + nWidth;
+ const tools::Long nSrcEndY = aRectSrc.Top() + nHeight;
+ tools::Long nDstY = aRectDst.Top();
+
+ for( tools::Long nSrcY = aRectSrc.Top(); nSrcY < nSrcEndY; nSrcY++, nDstY++)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nDstY);
+ Scanline pScanlineRead = pReadAcc->GetScanline(nSrcY);
+ for( tools::Long nSrcX = aRectSrc.Left(), nDstX = aRectDst.Left(); nSrcX < nSrcEndX; nSrcX++, nDstX++ )
+ pWriteAcc->SetPixelOnData( pScanline, nDstX, pReadAcc->GetPixelFromData( pScanlineRead, nSrcX ) );
+ }
+
+ bool bRet = ( nWidth > 0 ) && ( nHeight > 0 );
+
+ return bRet;
+}
+
+bool Bitmap::Expand( sal_Int32 nDX, sal_Int32 nDY, const Color* pInitColor )
+{
+ if( !nDX && !nDY )
+ return false;
+
+ const Size aSizePixel( GetSizePixel() );
+ const tools::Long nWidth = aSizePixel.Width();
+ const tools::Long nHeight = aSizePixel.Height();
+ const Size aNewSize( nWidth + nDX, nHeight + nDY );
+ BitmapScopedReadAccess pReadAcc(*this);
+ if( !pReadAcc )
+ return false;
+
+ BitmapPalette aBmpPal( pReadAcc->GetPalette() );
+ Bitmap aNewBmp(aNewSize, getPixelFormat(), &aBmpPal);
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+ if( !pWriteAcc )
+ return false;
+
+ BitmapColor aColor;
+ const tools::Long nNewX = nWidth;
+ const tools::Long nNewY = nHeight;
+ const tools::Long nNewWidth = pWriteAcc->Width();
+ const tools::Long nNewHeight = pWriteAcc->Height();
+ tools::Long nX;
+ tools::Long nY;
+
+ if( pInitColor )
+ aColor = pWriteAcc->GetBestMatchingColor( *pInitColor );
+
+ for( nY = 0; nY < nHeight; nY++ )
+ {
+ pWriteAcc->CopyScanline( nY, *pReadAcc );
+
+ if( pInitColor && nDX )
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ for( nX = nNewX; nX < nNewWidth; nX++ )
+ pWriteAcc->SetPixelOnData( pScanline, nX, aColor );
+ }
+ }
+
+ if( pInitColor && nDY )
+ for( nY = nNewY; nY < nNewHeight; nY++ )
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ for( nX = 0; nX < nNewWidth; nX++ )
+ pWriteAcc->SetPixelOnData( pScanline, nX, aColor );
+ }
+
+ pWriteAcc.reset();
+ pReadAcc.reset();
+
+ ReassignWithSize(aNewBmp);
+
+ return true;
+}
+
+Bitmap Bitmap::CreateDisplayBitmap( OutputDevice* pDisplay ) const
+{
+ Bitmap aDispBmp( *this );
+
+ SalGraphics* pDispGraphics = pDisplay->GetGraphics();
+
+ if( mxSalBmp && pDispGraphics )
+ {
+ std::shared_ptr<SalBitmap> xImpDispBmp(ImplGetSVData()->mpDefInst->CreateSalBitmap());
+ if (xImpDispBmp->Create(*mxSalBmp, pDispGraphics))
+ aDispBmp.ImplSetSalBitmap(xImpDispBmp);
+ }
+
+ return aDispBmp;
+}
+
+bool Bitmap::GetSystemData( BitmapSystemData& rData ) const
+{
+ return mxSalBmp && mxSalBmp->GetSystemData(rData);
+}
+
+
+bool Bitmap::Convert( BmpConversion eConversion )
+{
+ // try to convert in backend
+ if (mxSalBmp)
+ {
+ // avoid large chunk of obsolete and hopefully rarely used conversions.
+ if (eConversion == BmpConversion::N8BitNoConversion)
+ {
+ if (mxSalBmp->GetBitCount() == 8 && HasGreyPalette8Bit())
+ return true;
+ std::shared_ptr<SalBitmap> xImpBmp(ImplGetSVData()->mpDefInst->CreateSalBitmap());
+ // frequently used conversion for creating alpha masks
+ if (xImpBmp->Create(*mxSalBmp) && xImpBmp->InterpretAs8Bit())
+ {
+ ImplSetSalBitmap(xImpBmp);
+ SAL_INFO( "vcl.opengl", "Ref count: " << mxSalBmp.use_count() );
+ return true;
+ }
+ }
+ if (eConversion == BmpConversion::N8BitGreys)
+ {
+ std::shared_ptr<SalBitmap> xImpBmp(ImplGetSVData()->mpDefInst->CreateSalBitmap());
+ if (xImpBmp->Create(*mxSalBmp) && xImpBmp->ConvertToGreyscale())
+ {
+ ImplSetSalBitmap(xImpBmp);
+ SAL_INFO( "vcl.opengl", "Ref count: " << mxSalBmp.use_count() );
+ return true;
+ }
+ }
+ }
+
+ const sal_uInt16 nBitCount = vcl::pixelFormatBitCount(getPixelFormat());
+ bool bRet = false;
+
+ switch( eConversion )
+ {
+ case BmpConversion::N1BitThreshold:
+ {
+ BitmapEx aBmpEx(*this);
+ bRet = BitmapFilter::Filter(aBmpEx, BitmapMonochromeFilter(128));
+ *this = aBmpEx.GetBitmap();
+ }
+ break;
+
+ case BmpConversion::N8BitGreys:
+ case BmpConversion::N8BitNoConversion:
+ bRet = ImplMakeGreyscales();
+ break;
+
+ case BmpConversion::N8BitColors:
+ {
+ if( nBitCount < 8 )
+ bRet = ImplConvertUp(vcl::PixelFormat::N8_BPP);
+ else if( nBitCount > 8 )
+ bRet = ImplConvertDown8BPP();
+ else
+ bRet = true;
+ }
+ break;
+
+ case BmpConversion::N8BitTrans:
+ {
+ Color aTrans( BMP_COL_TRANS );
+
+ if( nBitCount < 8 )
+ bRet = ImplConvertUp(vcl::PixelFormat::N8_BPP, &aTrans );
+ else
+ bRet = ImplConvertDown8BPP(&aTrans );
+ }
+ break;
+
+ case BmpConversion::N24Bit:
+ {
+ if( nBitCount < 24 )
+ bRet = ImplConvertUp(vcl::PixelFormat::N24_BPP);
+ else
+ bRet = true;
+ }
+ break;
+
+ case BmpConversion::N32Bit:
+ {
+ if( nBitCount < 32 )
+ bRet = ImplConvertUp(vcl::PixelFormat::N32_BPP);
+ else
+ bRet = true;
+ }
+ break;
+
+ default:
+ OSL_FAIL( "Bitmap::Convert(): Unsupported conversion" );
+ break;
+ }
+
+ return bRet;
+}
+
+bool Bitmap::ImplMakeGreyscales()
+{
+ BitmapScopedReadAccess pReadAcc(*this);
+ if( !pReadAcc )
+ return false;
+
+ const BitmapPalette& rPal = GetGreyPalette(256);
+ sal_uLong nShift = 0;
+ bool bPalDiffers = !pReadAcc->HasPalette() || ( rPal.GetEntryCount() != pReadAcc->GetPaletteEntryCount() );
+
+ if( !bPalDiffers )
+ bPalDiffers = ( rPal != pReadAcc->GetPalette() );
+ if( !bPalDiffers )
+ return true;
+
+ const auto ePixelFormat = vcl::PixelFormat::N8_BPP;
+ Bitmap aNewBmp(GetSizePixel(), ePixelFormat, &rPal );
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+ if( !pWriteAcc )
+ return false;
+
+ const tools::Long nWidth = pWriteAcc->Width();
+ const tools::Long nHeight = pWriteAcc->Height();
+
+ 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++ )
+ {
+ const sal_uInt8 cIndex = pReadAcc->GetIndexFromData( pScanlineRead, nX );
+ pWriteAcc->SetPixelOnData( pScanline, nX,
+ BitmapColor(pReadAcc->GetPaletteColor( cIndex ).GetLuminance() >> nShift) );
+ }
+ }
+ }
+ else if( pReadAcc->GetScanlineFormat() == ScanlineFormat::N24BitTcBgr &&
+ pWriteAcc->GetScanlineFormat() == ScanlineFormat::N8BitPal )
+ {
+ nShift += 8;
+
+ for( tools::Long nY = 0; nY < nHeight; nY++ )
+ {
+ Scanline pReadScan = pReadAcc->GetScanline( nY );
+ Scanline pWriteScan = pWriteAcc->GetScanline( nY );
+
+ for( tools::Long nX = 0; nX < nWidth; nX++ )
+ {
+ const sal_uLong nB = *pReadScan++;
+ const sal_uLong nG = *pReadScan++;
+ const sal_uLong nR = *pReadScan++;
+
+ *pWriteScan++ = static_cast<sal_uInt8>( ( nB * 28UL + nG * 151UL + nR * 77UL ) >> nShift );
+ }
+ }
+ }
+ else if( pReadAcc->GetScanlineFormat() == ScanlineFormat::N24BitTcRgb &&
+ pWriteAcc->GetScanlineFormat() == ScanlineFormat::N8BitPal )
+ {
+ nShift += 8;
+
+ for( tools::Long nY = 0; nY < nHeight; nY++ )
+ {
+ Scanline pReadScan = pReadAcc->GetScanline( nY );
+ Scanline pWriteScan = pWriteAcc->GetScanline( nY );
+
+ for( tools::Long nX = 0; nX < nWidth; nX++ )
+ {
+ const sal_uLong nR = *pReadScan++;
+ const sal_uLong nG = *pReadScan++;
+ const sal_uLong nB = *pReadScan++;
+
+ *pWriteScan++ = static_cast<sal_uInt8>( ( nB * 28UL + nG * 151UL + nR * 77UL ) >> nShift );
+ }
+ }
+ }
+ 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++ )
+ pWriteAcc->SetPixelOnData( pScanline, nX, BitmapColor(pReadAcc->GetPixelFromData( pScanlineRead, nX ).GetLuminance() >> nShift) );
+ }
+ }
+
+ pWriteAcc.reset();
+ pReadAcc.reset();
+
+ const MapMode aMap( maPrefMapMode );
+ const Size aSize( maPrefSize );
+
+ *this = aNewBmp;
+
+ maPrefMapMode = aMap;
+ maPrefSize = aSize;
+
+ return true;
+}
+
+bool Bitmap::ImplConvertUp(vcl::PixelFormat ePixelFormat, Color const * pExtColor)
+{
+ SAL_WARN_IF(ePixelFormat <= getPixelFormat(), "vcl", "New pixel format must be greater!" );
+
+ BitmapScopedReadAccess pReadAcc(*this);
+ if (!pReadAcc)
+ return false;
+
+ BitmapPalette aPalette;
+ Bitmap aNewBmp(GetSizePixel(), ePixelFormat, pReadAcc->HasPalette() ? &pReadAcc->GetPalette() : &aPalette);
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+ if (!pWriteAcc)
+ return false;
+
+ const tools::Long nWidth = pWriteAcc->Width();
+ const tools::Long nHeight = pWriteAcc->Height();
+
+ if (pWriteAcc->HasPalette())
+ {
+ const BitmapPalette& rOldPalette = pReadAcc->GetPalette();
+ const sal_uInt16 nOldCount = rOldPalette.GetEntryCount();
+ assert(nOldCount <= (1 << vcl::pixelFormatBitCount(getPixelFormat())));
+
+ aPalette.SetEntryCount(1 << vcl::pixelFormatBitCount(ePixelFormat));
+
+ for (sal_uInt16 i = 0; i < nOldCount; i++)
+ aPalette[i] = rOldPalette[i];
+
+ if (pExtColor)
+ aPalette[aPalette.GetEntryCount() - 1] = *pExtColor;
+
+ pWriteAcc->SetPalette(aPalette);
+
+ 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++)
+ {
+ pWriteAcc->SetPixelOnData(pScanline, nX, pReadAcc->GetPixelFromData(pScanlineRead, nX));
+ }
+ }
+ }
+ else
+ {
+ 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++)
+ {
+ pWriteAcc->SetPixelOnData(pScanline, nX, pReadAcc->GetPaletteColor(pReadAcc->GetIndexFromData(pScanlineRead, nX)));
+ }
+ }
+ }
+ 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++)
+ {
+ pWriteAcc->SetPixelOnData(pScanline, nX, pReadAcc->GetPixelFromData(pScanlineRead, nX));
+ }
+ }
+ }
+ }
+
+ const MapMode aMap(maPrefMapMode);
+ const Size aSize(maPrefSize);
+
+ *this = aNewBmp;
+
+ maPrefMapMode = aMap;
+ maPrefSize = aSize;
+
+ return true;
+}
+
+bool Bitmap::ImplConvertDown8BPP(Color const * pExtColor)
+{
+ SAL_WARN_IF(vcl::PixelFormat::N8_BPP > getPixelFormat(), "vcl", "New pixelformat must be lower ( or equal when pExtColor is set )!");
+
+ BitmapScopedReadAccess pReadAcc(*this);
+ if (!pReadAcc)
+ return false;
+
+ BitmapPalette aPalette;
+ Bitmap aNewBmp(GetSizePixel(), vcl::PixelFormat::N8_BPP, &aPalette);
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+ if (!pWriteAcc)
+ return false;
+
+ sal_Int16 nNewBitCount = sal_Int16(vcl::PixelFormat::N8_BPP);
+ const sal_uInt16 nCount = 1 << nNewBitCount;
+ const tools::Long nWidth = pWriteAcc->Width();
+ const tools::Long nWidth1 = nWidth - 1;
+ const tools::Long nHeight = pWriteAcc->Height();
+ Octree aOctree(*pReadAcc, pExtColor ? (nCount - 1) : nCount);
+ aPalette = aOctree.GetPalette();
+ InverseColorMap aColorMap(aPalette);
+ BitmapColor aColor;
+ ImpErrorQuad aErrQuad;
+ std::vector<ImpErrorQuad> aErrQuad1(nWidth);
+ std::vector<ImpErrorQuad> aErrQuad2(nWidth);
+ ImpErrorQuad* pQLine1 = aErrQuad1.data();
+ ImpErrorQuad* pQLine2 = nullptr;
+ tools::Long nYTmp = 0;
+ sal_uInt8 cIndex;
+ bool bQ1 = true;
+
+ if (pExtColor)
+ {
+ aPalette.SetEntryCount(aPalette.GetEntryCount() + 1);
+ aPalette[aPalette.GetEntryCount() - 1] = *pExtColor;
+ }
+
+ // set Black/White always, if we have enough space
+ if (aPalette.GetEntryCount() < (nCount - 1))
+ {
+ aPalette.SetEntryCount(aPalette.GetEntryCount() + 2);
+ aPalette[aPalette.GetEntryCount() - 2] = COL_BLACK;
+ aPalette[aPalette.GetEntryCount() - 1] = COL_WHITE;
+ }
+
+ pWriteAcc->SetPalette(aPalette);
+
+ for (tools::Long nY = 0; nY < std::min(nHeight, tools::Long(2)); nY++, nYTmp++)
+ {
+ pQLine2 = !nY ? aErrQuad1.data() : aErrQuad2.data();
+ Scanline pScanlineRead = pReadAcc->GetScanline(nYTmp);
+ for (tools::Long nX = 0; nX < nWidth; nX++)
+ {
+ if (pReadAcc->HasPalette())
+ pQLine2[nX] = pReadAcc->GetPaletteColor(pReadAcc->GetIndexFromData(pScanlineRead, nX));
+ else
+ pQLine2[nX] = pReadAcc->GetPixelFromData(pScanlineRead, nX);
+ }
+ }
+
+ assert(pQLine2 || nHeight == 0);
+
+ for (tools::Long nY = 0; nY < nHeight; nY++, nYTmp++)
+ {
+ // first pixel in the line
+ cIndex = static_cast<sal_uInt8>(aColorMap.GetBestPaletteIndex(pQLine1[0].ImplGetColor()));
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ pWriteAcc->SetPixelOnData(pScanline, 0, BitmapColor(cIndex));
+
+ tools::Long nX;
+ for (nX = 1; nX < nWidth1; nX++)
+ {
+ aColor = pQLine1[nX].ImplGetColor();
+ cIndex = static_cast<sal_uInt8>(aColorMap.GetBestPaletteIndex(aColor));
+ aErrQuad = (ImpErrorQuad(aColor) -= pWriteAcc->GetPaletteColor(cIndex));
+ pQLine1[++nX].ImplAddColorError7(aErrQuad);
+ pQLine2[nX--].ImplAddColorError1(aErrQuad);
+ pQLine2[nX--].ImplAddColorError5(aErrQuad);
+ pQLine2[nX++].ImplAddColorError3(aErrQuad);
+ pWriteAcc->SetPixelOnData(pScanline, nX, BitmapColor(cIndex));
+ }
+
+ // Last RowPixel
+ if (nX < nWidth)
+ {
+ cIndex = static_cast<sal_uInt8>(aColorMap.GetBestPaletteIndex(pQLine1[nWidth1].ImplGetColor()));
+ pWriteAcc->SetPixelOnData(pScanline, nX, BitmapColor(cIndex));
+ }
+
+ // Refill/copy row buffer
+ pQLine1 = pQLine2;
+ bQ1 = !bQ1;
+ pQLine2 = bQ1 ? aErrQuad2.data() : aErrQuad1.data();
+
+ if (nYTmp < nHeight)
+ {
+ Scanline pScanlineRead = pReadAcc->GetScanline(nYTmp);
+ for (nX = 0; nX < nWidth; nX++)
+ {
+ if (pReadAcc->HasPalette())
+ pQLine2[nX] = pReadAcc->GetPaletteColor(pReadAcc->GetIndexFromData(pScanlineRead, nX));
+ else
+ pQLine2[nX] = pReadAcc->GetPixelFromData(pScanlineRead, nX);
+ }
+ }
+ }
+
+ pWriteAcc.reset();
+
+ const MapMode aMap(maPrefMapMode);
+ const Size aSize(maPrefSize);
+
+ *this = aNewBmp;
+
+ maPrefMapMode = aMap;
+ maPrefSize = aSize;
+
+ return true;
+}
+
+bool Bitmap::Scale( const double& rScaleX, const double& rScaleY, BmpScaleFlag nScaleFlag )
+{
+ if(basegfx::fTools::equalZero(rScaleX) || basegfx::fTools::equalZero(rScaleY))
+ {
+ // no scale
+ return true;
+ }
+
+ if(basegfx::fTools::equal(rScaleX, 1.0) && basegfx::fTools::equal(rScaleY, 1.0))
+ {
+ // no scale
+ return true;
+ }
+
+ const auto eStartPixelFormat = getPixelFormat();
+
+ if (mxSalBmp && mxSalBmp->ScalingSupported())
+ {
+ // implementation specific scaling
+ std::shared_ptr<SalBitmap> xImpBmp(ImplGetSVData()->mpDefInst->CreateSalBitmap());
+ if (xImpBmp->Create(*mxSalBmp) && xImpBmp->Scale(rScaleX, rScaleY, nScaleFlag))
+ {
+ ImplSetSalBitmap(xImpBmp);
+ SAL_INFO( "vcl.opengl", "Ref count: " << mxSalBmp.use_count() );
+ maPrefMapMode = MapMode( MapUnit::MapPixel );
+ maPrefSize = xImpBmp->GetSize();
+ return true;
+ }
+ }
+
+ BitmapEx aBmpEx(*this);
+ bool bRetval(false);
+
+ switch(nScaleFlag)
+ {
+ case BmpScaleFlag::Default:
+ if (GetSizePixel().Width() < 2 || GetSizePixel().Height() < 2)
+ bRetval = BitmapFilter::Filter(aBmpEx, BitmapFastScaleFilter(rScaleX, rScaleY));
+ else
+ bRetval = BitmapFilter::Filter(aBmpEx, BitmapScaleSuperFilter(rScaleX, rScaleY));
+ break;
+
+ case BmpScaleFlag::Fast:
+ case BmpScaleFlag::NearestNeighbor:
+ bRetval = BitmapFilter::Filter(aBmpEx, BitmapFastScaleFilter(rScaleX, rScaleY));
+ break;
+
+ case BmpScaleFlag::Interpolate:
+ bRetval = BitmapFilter::Filter(aBmpEx, BitmapInterpolateScaleFilter(rScaleX, rScaleY));
+ break;
+
+ case BmpScaleFlag::BestQuality:
+ case BmpScaleFlag::Lanczos:
+ bRetval = BitmapFilter::Filter(aBmpEx, vcl::BitmapScaleLanczos3Filter(rScaleX, rScaleY));
+ break;
+
+ case BmpScaleFlag::BiCubic:
+ bRetval = BitmapFilter::Filter(aBmpEx, vcl::BitmapScaleBicubicFilter(rScaleX, rScaleY));
+ break;
+
+ case BmpScaleFlag::BiLinear:
+ bRetval = BitmapFilter::Filter(aBmpEx, vcl::BitmapScaleBilinearFilter(rScaleX, rScaleY));
+ break;
+ }
+
+ if (bRetval)
+ *this = aBmpEx.GetBitmap();
+
+ OSL_ENSURE(!bRetval || eStartPixelFormat == getPixelFormat(), "Bitmap::Scale has changed the ColorDepth, this should *not* happen (!)");
+ return bRetval;
+}
+
+bool Bitmap::Scale( const Size& rNewSize, BmpScaleFlag nScaleFlag )
+{
+ const Size aSize( GetSizePixel() );
+ bool bRet;
+
+ if( aSize.Width() && aSize.Height() )
+ {
+ bRet = Scale( static_cast<double>(rNewSize.Width()) / aSize.Width(),
+ static_cast<double>(rNewSize.Height()) / aSize.Height(),
+ nScaleFlag );
+ }
+ else
+ bRet = true;
+
+ return bRet;
+}
+
+bool Bitmap::HasFastScale()
+{
+#if HAVE_FEATURE_SKIA
+ if( SkiaHelper::isVCLSkiaEnabled() && SkiaHelper::renderMethodToUse() != SkiaHelper::RenderRaster)
+ return true;
+#endif
+ return false;
+}
+
+void Bitmap::AdaptBitCount(Bitmap& rNew) const
+{
+ // aNew is the result of some operation; adapt it's BitCount to the original (this)
+ if (getPixelFormat() == rNew.getPixelFormat())
+ return;
+
+ switch (getPixelFormat())
+ {
+ case vcl::PixelFormat::N8_BPP:
+ {
+ if(HasGreyPaletteAny())
+ {
+ rNew.Convert(BmpConversion::N8BitGreys);
+ }
+ else
+ {
+ rNew.Convert(BmpConversion::N8BitColors);
+ }
+ break;
+ }
+ case vcl::PixelFormat::N24_BPP:
+ {
+ rNew.Convert(BmpConversion::N24Bit);
+ break;
+ }
+ case vcl::PixelFormat::N32_BPP:
+ {
+ rNew.Convert(BmpConversion::N32Bit);
+ break;
+ }
+ case vcl::PixelFormat::INVALID:
+ {
+ SAL_WARN("vcl", "Can't adapt the pixelformat as it is invalid.");
+ break;
+ }
+ }
+}
+
+static void shiftColors(sal_Int32* pColorArray, const BitmapScopedReadAccess& pReadAcc)
+{
+ Scanline pScanlineRead = pReadAcc->GetScanline(0); // Why always 0?
+ for (tools::Long n = 0; n < pReadAcc->Width(); ++n)
+ {
+ const BitmapColor& rColor = pReadAcc->GetColorFromData(pScanlineRead, n);
+ *pColorArray++ = static_cast<sal_Int32>(rColor.GetBlue()) << 12;
+ *pColorArray++ = static_cast<sal_Int32>(rColor.GetGreen()) << 12;
+ *pColorArray++ = static_cast<sal_Int32>(rColor.GetRed()) << 12;
+ }
+}
+
+bool Bitmap::Dither()
+{
+ const Size aSize( GetSizePixel() );
+ if( aSize.Width() == 1 || aSize.Height() == 1 )
+ return true;
+ if( ( aSize.Width() <= 3 ) || ( aSize.Height() <= 2 ) )
+ return false;
+
+ BitmapScopedReadAccess pReadAcc(*this);
+ Bitmap aNewBmp(GetSizePixel(), vcl::PixelFormat::N8_BPP);
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+ if( !pReadAcc || !pWriteAcc )
+ return false;
+
+ tools::Long nWidth = pReadAcc->Width();
+ tools::Long nWidth1 = nWidth - 1;
+ tools::Long nHeight = pReadAcc->Height();
+ tools::Long nW = nWidth * 3;
+ tools::Long nW2 = nW - 3;
+ std::unique_ptr<sal_Int32[]> p1(new sal_Int32[ nW ]);
+ std::unique_ptr<sal_Int32[]> p2(new sal_Int32[ nW ]);
+ sal_Int32* p1T = p1.get();
+ sal_Int32* p2T = p2.get();
+ shiftColors(p2T, pReadAcc);
+ for( tools::Long nYAcc = 0; nYAcc < nHeight; nYAcc++ )
+ {
+ std::swap(p1T, p2T);
+ if (nYAcc < nHeight - 1)
+ shiftColors(p2T, pReadAcc);
+
+ auto CalcError = [](tools::Long n)
+ {
+ n = std::clamp<tools::Long>(n >> 12, 0, 255);
+ return std::pair(FloydErrMap[n], FloydMap[n]);
+ };
+
+ auto CalcErrors = [&](tools::Long n)
+ { return std::tuple_cat(CalcError(p1T[n]), CalcError(p1T[n + 1]), CalcError(p1T[n + 2])); };
+
+ auto CalcT = [](sal_Int32* dst, const int* src, int b, int g, int r)
+ {
+ dst[0] += src[b];
+ dst[1] += src[g];
+ dst[2] += src[r];
+ };
+
+ auto Calc1 = [&](int x, int b, int g, int r) { CalcT(p2T + x + 3, FloydError1, b, g, r); };
+ auto Calc3 = [&](int x, int b, int g, int r) { CalcT(p2T + x - 3, FloydError3, b, g, r); };
+ auto Calc5 = [&](int x, int b, int g, int r) { CalcT(p2T + x, FloydError5, b, g, r); };
+ auto Calc7 = [&](int x, int b, int g, int r) { CalcT(p1T + x + 3, FloydError7, b, g, r); };
+
+ Scanline pScanline = pWriteAcc->GetScanline(nYAcc);
+ // Examine first Pixel separately
+ {
+ auto [nBErr, nBC, nGErr, nGC, nRErr, nRC] = CalcErrors(0);
+ Calc1(0, nBErr, nGErr, nRErr);
+ Calc5(0, nBErr, nGErr, nRErr);
+ Calc7(0, nBErr, nGErr, nRErr);
+ pWriteAcc->SetPixelOnData( pScanline, 0, BitmapColor(static_cast<sal_uInt8>(nVCLBLut[ nBC ] + nVCLGLut[nGC ] + nVCLRLut[nRC ])) );
+ }
+ // Get middle Pixels using a loop
+ for ( tools::Long nX = 3, nXAcc = 1; nX < nW2; nX += 3, nXAcc++ )
+ {
+ auto [nBErr, nBC, nGErr, nGC, nRErr, nRC] = CalcErrors(nX);
+ Calc1(nX, nBErr, nGErr, nRErr);
+ Calc3(nX, nBErr, nGErr, nRErr);
+ Calc5(nX, nBErr, nGErr, nRErr);
+ Calc7(nX, nBErr, nGErr, nRErr);
+ pWriteAcc->SetPixelOnData( pScanline, nXAcc, BitmapColor(static_cast<sal_uInt8>(nVCLBLut[ nBC ] + nVCLGLut[nGC ] + nVCLRLut[nRC ])) );
+ }
+ // Treat last Pixel separately
+ {
+ auto [nBErr, nBC, nGErr, nGC, nRErr, nRC] = CalcErrors(nW2);
+ Calc3(nW2, nBErr, nGErr, nRErr);
+ Calc5(nW2, nBErr, nGErr, nRErr);
+ pWriteAcc->SetPixelOnData( pScanline, nWidth1, BitmapColor(static_cast<sal_uInt8>(nVCLBLut[ nBC ] + nVCLGLut[nGC ] + nVCLRLut[nRC ])) );
+ }
+ }
+ pReadAcc.reset();
+ pWriteAcc.reset();
+ const MapMode aMap( maPrefMapMode );
+ const Size aPrefSize( maPrefSize );
+ *this = aNewBmp;
+ maPrefMapMode = aMap;
+ maPrefSize = aPrefSize;
+ return true;
+}
+
+void Bitmap::Vectorize( GDIMetaFile& rMtf, sal_uInt8 cReduce, const Link<tools::Long,void>* pProgress )
+{
+ ImplVectorizer::ImplVectorize( *this, rMtf, cReduce, pProgress );
+}
+
+bool Bitmap::Adjust( short nLuminancePercent, short nContrastPercent,
+ short nChannelRPercent, short nChannelGPercent, short nChannelBPercent,
+ double fGamma, bool bInvert, bool msoBrightness )
+{
+ // nothing to do => return quickly
+ if( !nLuminancePercent && !nContrastPercent &&
+ !nChannelRPercent && !nChannelGPercent && !nChannelBPercent &&
+ ( fGamma == 1.0 ) && !bInvert )
+ {
+ return true;
+ }
+
+ BitmapScopedWriteAccess pAcc(*this);
+ if( !pAcc )
+ return false;
+
+ BitmapColor aCol;
+ const tools::Long nW = pAcc->Width();
+ const tools::Long nH = pAcc->Height();
+ std::unique_ptr<sal_uInt8[]> cMapR(new sal_uInt8[ 256 ]);
+ std::unique_ptr<sal_uInt8[]> cMapG(new sal_uInt8[ 256 ]);
+ std::unique_ptr<sal_uInt8[]> cMapB(new sal_uInt8[ 256 ]);
+ double fM, fROff, fGOff, fBOff, fOff;
+
+ // calculate slope
+ if( nContrastPercent >= 0 )
+ fM = 128.0 / ( 128.0 - 1.27 * std::clamp( nContrastPercent, short(0), short(100) ) );
+ else
+ fM = ( 128.0 + 1.27 * std::clamp( nContrastPercent, short(-100), short(0) ) ) / 128.0;
+
+ if(!msoBrightness)
+ // total offset = luminance offset + contrast offset
+ fOff = std::clamp( nLuminancePercent, short(-100), short(100) ) * 2.55 + 128.0 - fM * 128.0;
+ else
+ fOff = std::clamp( nLuminancePercent, short(-100), short(100) ) * 2.55;
+
+ // channel offset = channel offset + total offset
+ fROff = nChannelRPercent * 2.55 + fOff;
+ fGOff = nChannelGPercent * 2.55 + fOff;
+ fBOff = nChannelBPercent * 2.55 + fOff;
+
+ // calculate gamma value
+ fGamma = ( fGamma <= 0.0 || fGamma > 10.0 ) ? 1.0 : ( 1.0 / fGamma );
+ const bool bGamma = ( fGamma != 1.0 );
+
+ // create mapping table
+ for( tools::Long nX = 0; nX < 256; nX++ )
+ {
+ if(!msoBrightness)
+ {
+ cMapR[ nX ] = FRound( std::clamp( nX * fM + fROff, 0.0, 255.0 ) );
+ cMapG[ nX ] = FRound( std::clamp( nX * fM + fGOff, 0.0, 255.0 ) );
+ cMapB[ nX ] = FRound( std::clamp( nX * fM + fBOff, 0.0, 255.0 ) );
+ }
+ else
+ {
+ // LO simply uses (in a somewhat optimized form) "newcolor = (oldcolor-128)*contrast+brightness+128"
+ // as the formula, i.e. contrast first, brightness afterwards. MSOffice, for whatever weird reason,
+ // use neither first, but apparently it applies half of brightness before contrast and half afterwards.
+ cMapR[ nX ] = FRound( std::clamp( (nX+fROff/2-128) * fM + 128 + fROff/2, 0.0, 255.0 ) );
+ cMapG[ nX ] = FRound( std::clamp( (nX+fGOff/2-128) * fM + 128 + fGOff/2, 0.0, 255.0 ) );
+ cMapB[ nX ] = FRound( std::clamp( (nX+fBOff/2-128) * fM + 128 + fBOff/2, 0.0, 255.0 ) );
+ }
+ if( bGamma )
+ {
+ cMapR[ nX ] = GAMMA( cMapR[ nX ], fGamma );
+ cMapG[ nX ] = GAMMA( cMapG[ nX ], fGamma );
+ cMapB[ nX ] = GAMMA( cMapB[ nX ], fGamma );
+ }
+
+ if( bInvert )
+ {
+ cMapR[ nX ] = ~cMapR[ nX ];
+ cMapG[ nX ] = ~cMapG[ nX ];
+ cMapB[ nX ] = ~cMapB[ nX ];
+ }
+ }
+
+ // do modifying
+ if( pAcc->HasPalette() )
+ {
+ BitmapColor aNewCol;
+
+ for( sal_uInt16 i = 0, nCount = pAcc->GetPaletteEntryCount(); i < nCount; i++ )
+ {
+ const BitmapColor& rCol = pAcc->GetPaletteColor( i );
+ aNewCol.SetRed( cMapR[ rCol.GetRed() ] );
+ aNewCol.SetGreen( cMapG[ rCol.GetGreen() ] );
+ aNewCol.SetBlue( cMapB[ rCol.GetBlue() ] );
+ pAcc->SetPaletteColor( i, aNewCol );
+ }
+ }
+ else if( pAcc->GetScanlineFormat() == ScanlineFormat::N24BitTcBgr )
+ {
+ for( tools::Long nY = 0; nY < nH; nY++ )
+ {
+ Scanline pScan = pAcc->GetScanline( nY );
+
+ for( tools::Long nX = 0; nX < nW; nX++ )
+ {
+ *pScan = cMapB[ *pScan ]; pScan++;
+ *pScan = cMapG[ *pScan ]; pScan++;
+ *pScan = cMapR[ *pScan ]; pScan++;
+ }
+ }
+ }
+ else if( pAcc->GetScanlineFormat() == ScanlineFormat::N24BitTcRgb )
+ {
+ for( tools::Long nY = 0; nY < nH; nY++ )
+ {
+ Scanline pScan = pAcc->GetScanline( nY );
+
+ for( tools::Long nX = 0; nX < nW; nX++ )
+ {
+ *pScan = cMapR[ *pScan ]; pScan++;
+ *pScan = cMapG[ *pScan ]; pScan++;
+ *pScan = cMapB[ *pScan ]; pScan++;
+ }
+ }
+ }
+ else
+ {
+ for( tools::Long nY = 0; nY < nH; nY++ )
+ {
+ Scanline pScanline = pAcc->GetScanline(nY);
+ for( tools::Long nX = 0; nX < nW; nX++ )
+ {
+ aCol = pAcc->GetPixelFromData( pScanline, nX );
+ aCol.SetRed( cMapR[ aCol.GetRed() ] );
+ aCol.SetGreen( cMapG[ aCol.GetGreen() ] );
+ aCol.SetBlue( cMapB[ aCol.GetBlue() ] );
+ pAcc->SetPixelOnData( pScanline, nX, aCol );
+ }
+ }
+ }
+
+ pAcc.reset();
+
+ return true;
+}
+
+namespace
+{
+inline sal_uInt8 backBlendAlpha(sal_uInt16 alpha, sal_uInt16 srcCol, sal_uInt16 startCol)
+{
+ const sal_uInt16 nAlpha((alpha * startCol) / 255);
+ if(srcCol > nAlpha)
+ {
+ return static_cast<sal_uInt8>(((srcCol - nAlpha) * 255) / (255 - nAlpha));
+ }
+
+ return 0;
+}
+}
+
+void Bitmap::RemoveBlendedStartColor(
+ const Color& rStartColor,
+ const AlphaMask& rAlphaMask)
+{
+ // no content, done
+ if(IsEmpty())
+ return;
+
+ BitmapScopedWriteAccess pAcc(*this);
+ const tools::Long nHeight(pAcc->Height());
+ const tools::Long nWidth(pAcc->Width());
+
+ // no content, done
+ if(0 == nHeight || 0 == nWidth)
+ return;
+
+ BitmapScopedReadAccess pAlphaAcc(rAlphaMask);
+
+ // inequal sizes of content and alpha, avoid change (maybe assert?)
+ if(pAlphaAcc->Height() != nHeight || pAlphaAcc->Width() != nWidth)
+ return;
+
+ // prepare local values as sal_uInt16 to avoid multiple conversions
+ const sal_uInt16 nStartColRed(rStartColor.GetRed());
+ const sal_uInt16 nStartColGreen(rStartColor.GetGreen());
+ const sal_uInt16 nStartColBlue(rStartColor.GetBlue());
+
+ for (tools::Long y = 0; y < nHeight; ++y)
+ {
+ for (tools::Long x = 0; x < nWidth; ++x)
+ {
+ // get alpha value
+ const sal_uInt8 nAlpha8(pAlphaAcc->GetColor(y, x).GetRed());
+
+ // not or completely transparent, no adaptation needed
+ if(0 == nAlpha8 || 255 == nAlpha8)
+ continue;
+
+ // prepare local value as sal_uInt16 to avoid multiple conversions
+ const sal_uInt16 nAlpha16(static_cast<sal_uInt16>(nAlpha8));
+
+ // get source color
+ BitmapColor aColor(pAcc->GetColor(y, x));
+
+ // modify/blend back source color
+ aColor.SetRed(backBlendAlpha(nAlpha16, static_cast<sal_uInt16>(aColor.GetRed()), nStartColRed));
+ aColor.SetGreen(backBlendAlpha(nAlpha16, static_cast<sal_uInt16>(aColor.GetGreen()), nStartColGreen));
+ aColor.SetBlue(backBlendAlpha(nAlpha16, static_cast<sal_uInt16>(aColor.GetBlue()), nStartColBlue));
+
+ // write result back
+ pAcc->SetPixel(y, x, aColor);
+ }
+ }
+}
+
+const basegfx::SystemDependentDataHolder* Bitmap::accessSystemDependentDataHolder() const
+{
+ if(!mxSalBmp)
+ return nullptr;
+ return mxSalBmp->accessSystemDependentDataHolder();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/bitmapfilter.cxx b/vcl/source/bitmap/bitmapfilter.cxx
new file mode 100644
index 0000000000..63ccd2b130
--- /dev/null
+++ b/vcl/source/bitmap/bitmapfilter.cxx
@@ -0,0 +1,58 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#include <vcl/BitmapFilter.hxx>
+#include <vcl/animate/Animation.hxx>
+
+#include <sal/log.hxx>
+
+BitmapFilter::BitmapFilter() {}
+
+BitmapFilter::~BitmapFilter() {}
+
+bool BitmapFilter::Filter(BitmapEx& rBmpEx, BitmapFilter const& rFilter)
+{
+ BitmapEx aTmpBmpEx(rFilter.execute(rBmpEx));
+
+ if (aTmpBmpEx.IsEmpty())
+ {
+ SAL_WARN("vcl.gdi", "Bitmap filter failed " << typeid(rFilter).name());
+ return false;
+ }
+
+ rBmpEx = aTmpBmpEx;
+ return true;
+}
+
+bool BitmapFilter::Filter(Animation& rAnimation, BitmapFilter const& rFilter)
+{
+ SAL_WARN_IF(rAnimation.IsInAnimation(), "vcl", "Animation modified while it is animated");
+
+ bool bRet = false;
+
+ if (!rAnimation.IsInAnimation() && !rAnimation.Count())
+ {
+ bRet = true;
+
+ std::vector<std::unique_ptr<AnimationFrame>>& aList = rAnimation.GetAnimationFrames();
+ for (size_t i = 0, n = aList.size(); (i < n) && bRet; ++i)
+ {
+ bRet = BitmapFilter::Filter(aList[i]->maBitmapEx, rFilter);
+ }
+
+ BitmapEx aBmpEx(rAnimation.GetBitmapEx());
+ BitmapFilter::Filter(aBmpEx, rFilter);
+ rAnimation.SetBitmapEx(aBmpEx);
+ }
+
+ return bRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/bitmappaint.cxx b/vcl/source/bitmap/bitmappaint.cxx
new file mode 100644
index 0000000000..cc3674ad31
--- /dev/null
+++ b/vcl/source/bitmap/bitmappaint.cxx
@@ -0,0 +1,1226 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <tools/poly.hxx>
+#include <tools/helpers.hxx>
+
+#include <vcl/bitmap.hxx>
+#include <vcl/alpha.hxx>
+
+#include <vcl/BitmapWriteAccess.hxx>
+#include <salbmp.hxx>
+#include <svdata.hxx>
+#include <salinst.hxx>
+
+#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())
+ return true;
+
+ // implementation specific replace
+ std::shared_ptr<SalBitmap> xImpBmp(ImplGetSVData()->mpDefInst->CreateSalBitmap());
+ if (xImpBmp->Create(*mxSalBmp) && xImpBmp->Erase(rFillColor))
+ {
+ ImplSetSalBitmap(xImpBmp);
+ maPrefMapMode = MapMode(MapUnit::MapPixel);
+ maPrefSize = xImpBmp->GetSize();
+ return true;
+ }
+
+ BitmapScopedWriteAccess pWriteAcc(*this);
+ bool bRet = false;
+
+ if (pWriteAcc)
+ {
+ pWriteAcc->Erase(rFillColor);
+ bRet = true;
+ }
+
+ return bRet;
+}
+
+bool Bitmap::Invert()
+{
+ if (!mxSalBmp)
+ return false;
+
+ // try optimised call, much faster on Skia
+ if (mxSalBmp->Invert())
+ {
+ mxSalBmp->InvalidateChecksum();
+ return true;
+ }
+
+ BitmapScopedWriteAccess pWriteAcc(*this);
+ const tools::Long nWidth = pWriteAcc->Width();
+ const tools::Long nHeight = pWriteAcc->Height();
+
+ if (pWriteAcc->HasPalette())
+ {
+ const sal_uInt16 nActColors = pWriteAcc->GetPaletteEntryCount();
+ const sal_uInt16 nMaxColors = 1 << pWriteAcc->GetBitCount();
+
+ if (pWriteAcc->GetPalette().IsGreyPalette8Bit())
+ {
+ 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;
+}
+
+namespace
+{
+// Put each scanline's content horizontally mirrored into the other one.
+// (optimized version accessing pixel values directly).
+template <int bitCount>
+void mirrorScanlines(Scanline scanline1, Scanline scanline2, tools::Long nWidth)
+{
+ constexpr int byteCount = bitCount / 8;
+ Scanline pos1 = scanline1;
+ Scanline pos2 = scanline2 + (nWidth - 1) * byteCount; // last in second scanline
+ sal_uInt8 tmp[byteCount];
+ for (tools::Long i = 0; i < nWidth; ++i)
+ {
+ memcpy(tmp, pos1, byteCount);
+ memcpy(pos1, pos2, byteCount);
+ memcpy(pos2, tmp, byteCount);
+ pos1 += byteCount;
+ pos2 -= byteCount;
+ }
+}
+}
+
+bool Bitmap::Mirror(BmpMirrorFlags nMirrorFlags)
+{
+ bool bHorz(nMirrorFlags & BmpMirrorFlags::Horizontal);
+ bool bVert(nMirrorFlags & BmpMirrorFlags::Vertical);
+ bool bRet = false;
+
+ if (bHorz && !bVert)
+ {
+ BitmapScopedWriteAccess pAcc(*this);
+
+ if (pAcc)
+ {
+ const tools::Long nWidth = pAcc->Width();
+ const tools::Long nHeight = pAcc->Height();
+ const tools::Long nWidth1 = nWidth - 1;
+ const tools::Long nWidth_2 = nWidth / 2;
+ const tools::Long nSecondHalf = nWidth - nWidth_2;
+
+ switch (pAcc->GetBitCount())
+ {
+ // Special-case these, swap the halves of scanlines while mirroring them.
+ case 32:
+ for (tools::Long nY = 0; nY < nHeight; nY++)
+ mirrorScanlines<32>(pAcc->GetScanline(nY),
+ pAcc->GetScanline(nY) + 4 * nSecondHalf, nWidth_2);
+ break;
+ case 24:
+ for (tools::Long nY = 0; nY < nHeight; nY++)
+ mirrorScanlines<24>(pAcc->GetScanline(nY),
+ pAcc->GetScanline(nY) + 3 * nSecondHalf, nWidth_2);
+ break;
+ case 8:
+ for (tools::Long nY = 0; nY < nHeight; nY++)
+ mirrorScanlines<8>(pAcc->GetScanline(nY),
+ pAcc->GetScanline(nY) + nSecondHalf, nWidth_2);
+ break;
+ default:
+ for (tools::Long nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pAcc->GetScanline(nY);
+ for (tools::Long nX = 0, nOther = nWidth1; nX < nWidth_2; nX++, nOther--)
+ {
+ const BitmapColor aTemp(pAcc->GetPixelFromData(pScanline, nX));
+
+ pAcc->SetPixelOnData(pScanline, nX,
+ pAcc->GetPixelFromData(pScanline, nOther));
+ pAcc->SetPixelOnData(pScanline, nOther, aTemp);
+ }
+ }
+ }
+
+ pAcc.reset();
+ bRet = true;
+ }
+ }
+ else if (bVert && !bHorz)
+ {
+ BitmapScopedWriteAccess pAcc(*this);
+
+ if (pAcc)
+ {
+ const tools::Long nScanSize = pAcc->GetScanlineSize();
+ std::unique_ptr<sal_uInt8[]> pBuffer(new sal_uInt8[nScanSize]);
+ const tools::Long nHeight = pAcc->Height();
+ const tools::Long nHeight1 = nHeight - 1;
+ const tools::Long nHeight_2 = nHeight >> 1;
+
+ for (tools::Long nY = 0, nOther = nHeight1; nY < nHeight_2; nY++, nOther--)
+ {
+ memcpy(pBuffer.get(), pAcc->GetScanline(nY), nScanSize);
+ memcpy(pAcc->GetScanline(nY), pAcc->GetScanline(nOther), nScanSize);
+ memcpy(pAcc->GetScanline(nOther), pBuffer.get(), nScanSize);
+ }
+
+ pAcc.reset();
+ bRet = true;
+ }
+ }
+ else if (bHorz && bVert)
+ {
+ BitmapScopedWriteAccess pAcc(*this);
+
+ if (pAcc)
+ {
+ const tools::Long nWidth = pAcc->Width();
+ const tools::Long nWidth1 = nWidth - 1;
+ const tools::Long nHeight = pAcc->Height();
+ tools::Long nHeight_2 = nHeight / 2;
+ const tools::Long nWidth_2 = nWidth / 2;
+ const tools::Long nSecondHalf = nWidth - nWidth_2;
+
+ switch (pAcc->GetBitCount())
+ {
+ case 32:
+ for (tools::Long nY = 0, nOtherY = nHeight - 1; nY < nHeight_2; nY++, nOtherY--)
+ mirrorScanlines<32>(pAcc->GetScanline(nY), pAcc->GetScanline(nOtherY),
+ nWidth);
+ if (nHeight & 1)
+ mirrorScanlines<32>(pAcc->GetScanline(nHeight_2),
+ pAcc->GetScanline(nHeight_2) + 4 * nSecondHalf,
+ nWidth_2);
+ break;
+ case 24:
+ for (tools::Long nY = 0, nOtherY = nHeight - 1; nY < nHeight_2; nY++, nOtherY--)
+ mirrorScanlines<24>(pAcc->GetScanline(nY), pAcc->GetScanline(nOtherY),
+ nWidth);
+ if (nHeight & 1)
+ mirrorScanlines<24>(pAcc->GetScanline(nHeight_2),
+ pAcc->GetScanline(nHeight_2) + 3 * nSecondHalf,
+ nWidth_2);
+ break;
+ case 8:
+ for (tools::Long nY = 0, nOtherY = nHeight - 1; nY < nHeight_2; nY++, nOtherY--)
+ mirrorScanlines<8>(pAcc->GetScanline(nY), pAcc->GetScanline(nOtherY),
+ nWidth);
+ if (nHeight & 1)
+ mirrorScanlines<8>(pAcc->GetScanline(nHeight_2),
+ pAcc->GetScanline(nHeight_2) + nSecondHalf, nWidth_2);
+ break;
+ default:
+ for (tools::Long nY = 0, nOtherY = nHeight - 1; nY < nHeight_2; nY++, nOtherY--)
+ {
+ Scanline pScanline = pAcc->GetScanline(nY);
+ Scanline pScanlineOther = pAcc->GetScanline(nOtherY);
+ for (tools::Long nX = 0, nOtherX = nWidth1; nX < nWidth; nX++, nOtherX--)
+ {
+ const BitmapColor aTemp(pAcc->GetPixelFromData(pScanline, nX));
+
+ pAcc->SetPixelOnData(pScanline, nX,
+ pAcc->GetPixelFromData(pScanlineOther, nOtherX));
+ pAcc->SetPixelOnData(pScanlineOther, nOtherX, aTemp);
+ }
+ }
+
+ // if necessary, also mirror the middle line horizontally
+ if (nHeight & 1)
+ {
+ Scanline pScanline = pAcc->GetScanline(nHeight_2);
+ for (tools::Long nX = 0, nOtherX = nWidth1; nX < nWidth_2; nX++, nOtherX--)
+ {
+ const BitmapColor aTemp(pAcc->GetPixelFromData(pScanline, nX));
+ pAcc->SetPixelOnData(pScanline, nX,
+ pAcc->GetPixelFromData(pScanline, nOtherX));
+ pAcc->SetPixelOnData(pScanline, nOtherX, aTemp);
+ }
+ }
+ }
+
+ pAcc.reset();
+ bRet = true;
+ }
+ }
+ else
+ bRet = true;
+
+ return bRet;
+}
+
+bool Bitmap::Rotate(Degree10 nAngle10, const Color& rFillColor)
+{
+ nAngle10 %= 3600_deg10;
+ nAngle10 = (nAngle10 < 0_deg10) ? (Degree10(3599) + nAngle10) : nAngle10;
+
+ if (!nAngle10)
+ return true;
+ if (nAngle10 == 1800_deg10)
+ return Mirror(BmpMirrorFlags::Horizontal | BmpMirrorFlags::Vertical);
+
+ BitmapScopedReadAccess pReadAcc(*this);
+ if (!pReadAcc)
+ return false;
+
+ Bitmap aRotatedBmp;
+ bool bRet = false;
+ const Size aSizePix(GetSizePixel());
+
+ if (nAngle10 == 900_deg10 || nAngle10 == 2700_deg10)
+ {
+ const Size aNewSizePix(aSizePix.Height(), aSizePix.Width());
+ Bitmap aNewBmp(aNewSizePix, getPixelFormat(), &pReadAcc->GetPalette());
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+
+ if (pWriteAcc)
+ {
+ const tools::Long nWidth = aSizePix.Width();
+ const tools::Long nWidth1 = nWidth - 1;
+ const tools::Long nHeight = aSizePix.Height();
+ const tools::Long nHeight1 = nHeight - 1;
+ const tools::Long nNewWidth = aNewSizePix.Width();
+ const tools::Long nNewHeight = aNewSizePix.Height();
+
+ if (nAngle10 == 900_deg10)
+ {
+ for (tools::Long nY = 0, nOtherX = nWidth1; nY < nNewHeight; nY++, nOtherX--)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ for (tools::Long nX = 0, nOtherY = 0; nX < nNewWidth; nX++)
+ {
+ pWriteAcc->SetPixelOnData(pScanline, nX,
+ pReadAcc->GetPixel(nOtherY++, nOtherX));
+ }
+ }
+ }
+ else if (nAngle10 == 2700_deg10)
+ {
+ for (tools::Long nY = 0, nOtherX = 0; nY < nNewHeight; nY++, nOtherX++)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ for (tools::Long nX = 0, nOtherY = nHeight1; nX < nNewWidth; nX++)
+ {
+ pWriteAcc->SetPixelOnData(pScanline, nX,
+ pReadAcc->GetPixel(nOtherY--, nOtherX));
+ }
+ }
+ }
+
+ pWriteAcc.reset();
+ }
+
+ aRotatedBmp = aNewBmp;
+ }
+ else
+ {
+ Point aTmpPoint;
+ tools::Rectangle aTmpRectangle(aTmpPoint, aSizePix);
+ tools::Polygon aPoly(aTmpRectangle);
+ aPoly.Rotate(aTmpPoint, nAngle10);
+
+ tools::Rectangle aNewBound(aPoly.GetBoundRect());
+ const Size aNewSizePix(aNewBound.GetSize());
+ Bitmap aNewBmp(aNewSizePix, getPixelFormat(), &pReadAcc->GetPalette());
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+
+ if (pWriteAcc)
+ {
+ const BitmapColor aFillColor(pWriteAcc->GetBestMatchingColor(rFillColor));
+ const double fCosAngle = cos(toRadians(nAngle10));
+ const double fSinAngle = sin(toRadians(nAngle10));
+ const double fXMin = aNewBound.Left();
+ const double fYMin = aNewBound.Top();
+ const sal_Int32 nWidth = aSizePix.Width();
+ const sal_Int32 nHeight = aSizePix.Height();
+ const sal_Int32 nNewWidth = aNewSizePix.Width();
+ const sal_Int32 nNewHeight = aNewSizePix.Height();
+ // we store alternating values of cos/sin. We do this instead of
+ // separate arrays to improve cache hit.
+ std::unique_ptr<sal_Int32[]> pCosSinX(new sal_Int32[nNewWidth * 2]);
+ std::unique_ptr<sal_Int32[]> pCosSinY(new sal_Int32[nNewHeight * 2]);
+
+ for (sal_Int32 nIdx = 0, nX = 0; nX < nNewWidth; nX++)
+ {
+ const double fTmp = (fXMin + nX) * 64;
+
+ pCosSinX[nIdx++] = std::round(fCosAngle * fTmp);
+ pCosSinX[nIdx++] = std::round(fSinAngle * fTmp);
+ }
+
+ for (sal_Int32 nIdx = 0, nY = 0; nY < nNewHeight; nY++)
+ {
+ const double fTmp = (fYMin + nY) * 64;
+
+ pCosSinY[nIdx++] = std::round(fCosAngle * fTmp);
+ pCosSinY[nIdx++] = std::round(fSinAngle * fTmp);
+ }
+
+ for (sal_Int32 nCosSinYIdx = 0, nY = 0; nY < nNewHeight; nY++)
+ {
+ sal_Int32 nCosY = pCosSinY[nCosSinYIdx++];
+ sal_Int32 nSinY = pCosSinY[nCosSinYIdx++];
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+
+ for (sal_Int32 nCosSinXIdx = 0, nX = 0; nX < nNewWidth; nX++)
+ {
+ sal_Int32 nRotX = (pCosSinX[nCosSinXIdx++] - nSinY) >> 6;
+ sal_Int32 nRotY = (pCosSinX[nCosSinXIdx++] + nCosY) >> 6;
+
+ if ((nRotX > -1) && (nRotX < nWidth) && (nRotY > -1) && (nRotY < nHeight))
+ {
+ pWriteAcc->SetPixelOnData(pScanline, nX, pReadAcc->GetPixel(nRotY, nRotX));
+ }
+ else
+ {
+ pWriteAcc->SetPixelOnData(pScanline, nX, aFillColor);
+ }
+ }
+ }
+
+ pWriteAcc.reset();
+ }
+
+ aRotatedBmp = aNewBmp;
+ }
+
+ pReadAcc.reset();
+
+ bRet = !aRotatedBmp.IsEmpty();
+ if (bRet)
+ ReassignWithSize(aRotatedBmp);
+
+ return bRet;
+};
+
+Bitmap Bitmap::CreateMask(const Color& rTransColor) const
+{
+ BitmapScopedReadAccess pReadAcc(*this);
+ if (!pReadAcc)
+ return Bitmap();
+
+ // 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.
+
+ if ((pReadAcc->GetScanlineFormat() == ScanlineFormat::N1BitMsbPal)
+ && pReadAcc->GetBestMatchingColor(COL_WHITE) == 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 *this;
+ }
+
+ auto ePixelFormat = vcl::PixelFormat::N8_BPP;
+ Bitmap aNewBmp(GetSizePixel(), ePixelFormat, &Bitmap::GetGreyPalette(256));
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+ if (!pWriteAcc)
+ return Bitmap();
+
+ const tools::Long nWidth = pReadAcc->Width();
+ const tools::Long nHeight = pReadAcc->Height();
+ const BitmapColor aBlack(pWriteAcc->GetBestMatchingColor(COL_BLACK));
+ const BitmapColor aWhite(pWriteAcc->GetBestMatchingColor(COL_WHITE));
+
+ const BitmapColor aTest(pReadAcc->GetBestMatchingColor(rTransColor));
+
+ if (pWriteAcc->GetScanlineFormat() == pReadAcc->GetScanlineFormat() && aWhite.GetIndex() == 1
+ && (pReadAcc->GetScanlineFormat() == ScanlineFormat::N1BitMsbPal))
+ {
+ for (tools::Long nY = 0; nY < nHeight; ++nY)
+ {
+ Scanline pSrc = pReadAcc->GetScanline(nY);
+ Scanline pDst = pWriteAcc->GetScanline(nY);
+ assert(pWriteAcc->GetScanlineSize() == pReadAcc->GetScanlineSize());
+ const tools::Long nScanlineSize = pWriteAcc->GetScanlineSize();
+ for (tools::Long nX = 0; nX < nScanlineSize; ++nX)
+ pDst[nX] = ~pSrc[nX];
+ }
+ }
+ else 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] = aWhite.GetIndex();
+ else
+ pDst[nX] = aBlack.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, aWhite);
+ else
+ pWriteAcc->SetPixelOnData(pScanline, nX, aBlack);
+ }
+ }
+ }
+
+ pWriteAcc.reset();
+ pReadAcc.reset();
+
+ aNewBmp.maPrefSize = maPrefSize;
+ aNewBmp.maPrefMapMode = maPrefMapMode;
+
+ return aNewBmp;
+}
+
+Bitmap Bitmap::CreateMask(const Color& rTransColor, sal_uInt8 nTol) const
+{
+ if (nTol == 0)
+ return CreateMask(rTransColor);
+
+ BitmapScopedReadAccess pReadAcc(*this);
+ if (!pReadAcc)
+ return Bitmap();
+
+ // 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.
+
+ auto ePixelFormat = vcl::PixelFormat::N8_BPP;
+ Bitmap aNewBmp(GetSizePixel(), ePixelFormat, &Bitmap::GetGreyPalette(256));
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+ if (!pWriteAcc)
+ return Bitmap();
+
+ const tools::Long nWidth = pReadAcc->Width();
+ const tools::Long nHeight = pReadAcc->Height();
+ const BitmapColor aBlack(pWriteAcc->GetBestMatchingColor(COL_BLACK));
+ const BitmapColor aWhite(pWriteAcc->GetBestMatchingColor(COL_WHITE));
+
+ BitmapColor aCol;
+ tools::Long nR, nG, nB;
+ const tools::Long nMinR = std::clamp<tools::Long>(rTransColor.GetRed() - nTol, 0, 255);
+ const tools::Long nMaxR = std::clamp<tools::Long>(rTransColor.GetRed() + nTol, 0, 255);
+ const tools::Long nMinG = std::clamp<tools::Long>(rTransColor.GetGreen() - nTol, 0, 255);
+ const tools::Long nMaxG = std::clamp<tools::Long>(rTransColor.GetGreen() + nTol, 0, 255);
+ const tools::Long nMinB = std::clamp<tools::Long>(rTransColor.GetBlue() - nTol, 0, 255);
+ const tools::Long nMaxB = std::clamp<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, aWhite);
+ }
+ else
+ {
+ pWriteAcc->SetPixelOnData(pScanline, nX, aBlack);
+ }
+ }
+ }
+ }
+ 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, aWhite);
+ }
+ else
+ {
+ pWriteAcc->SetPixelOnData(pScanline, nX, aBlack);
+ }
+ }
+ }
+ }
+
+ pWriteAcc.reset();
+ pReadAcc.reset();
+
+ aNewBmp.maPrefSize = maPrefSize;
+ aNewBmp.maPrefMapMode = maPrefMapMode;
+
+ return aNewBmp;
+}
+
+AlphaMask Bitmap::CreateAlphaMask(const Color& rTransColor) const
+{
+ BitmapScopedReadAccess pReadAcc(*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.SetPrefSize(maPrefSize);
+ aNewBmp.SetPrefMapMode(maPrefMapMode);
+
+ return aNewBmp;
+}
+
+AlphaMask Bitmap::CreateAlphaMask(const Color& rTransColor, sal_uInt8 nTol) const
+{
+ if (nTol == 0)
+ return CreateAlphaMask(rTransColor);
+
+ BitmapScopedReadAccess pReadAcc(*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 = std::clamp<tools::Long>(rTransColor.GetRed() - nTol, 0, 255);
+ const tools::Long nMaxR = std::clamp<tools::Long>(rTransColor.GetRed() + nTol, 0, 255);
+ const tools::Long nMinG = std::clamp<tools::Long>(rTransColor.GetGreen() - nTol, 0, 255);
+ const tools::Long nMaxG = std::clamp<tools::Long>(rTransColor.GetGreen() + nTol, 0, 255);
+ const tools::Long nMinB = std::clamp<tools::Long>(rTransColor.GetBlue() - nTol, 0, 255);
+ const tools::Long nMaxB = std::clamp<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.SetPrefSize(maPrefSize);
+ aNewBmp.SetPrefMapMode(maPrefMapMode);
+
+ return aNewBmp;
+}
+
+vcl::Region Bitmap::CreateRegion(const Color& rColor, const tools::Rectangle& rRect) const
+{
+ tools::Rectangle aRect(rRect);
+ BitmapScopedReadAccess pReadAcc(*this);
+
+ aRect.Intersection(tools::Rectangle(Point(), GetSizePixel()));
+ aRect.Normalize();
+
+ if (!pReadAcc)
+ return vcl::Region(aRect);
+
+ vcl::Region aRegion;
+ const tools::Long nLeft = aRect.Left();
+ const tools::Long nTop = aRect.Top();
+ const tools::Long nRight = aRect.Right();
+ const tools::Long nBottom = aRect.Bottom();
+ const BitmapColor aMatch(pReadAcc->GetBestMatchingColor(rColor));
+
+ std::vector<tools::Long> aLine;
+ tools::Long nYStart(nTop);
+ tools::Long nY(nTop);
+
+ for (; nY <= nBottom; nY++)
+ {
+ std::vector<tools::Long> aNewLine;
+ tools::Long nX(nLeft);
+ Scanline pScanlineRead = pReadAcc->GetScanline(nY);
+
+ for (; nX <= nRight;)
+ {
+ while ((nX <= nRight) && (aMatch != pReadAcc->GetPixelFromData(pScanlineRead, nX)))
+ nX++;
+
+ if (nX <= nRight)
+ {
+ aNewLine.push_back(nX);
+
+ while ((nX <= nRight) && (aMatch == pReadAcc->GetPixelFromData(pScanlineRead, nX)))
+ {
+ nX++;
+ }
+
+ aNewLine.push_back(nX - 1);
+ }
+ }
+
+ if (aNewLine != aLine)
+ {
+ // need to write aLine, it's different from the next line
+ if (!aLine.empty())
+ {
+ tools::Rectangle aSubRect;
+
+ // enter y values and proceed ystart
+ aSubRect.SetTop(nYStart);
+ aSubRect.SetBottom(nY ? nY - 1 : 0);
+
+ for (size_t a(0); a < aLine.size();)
+ {
+ aSubRect.SetLeft(aLine[a++]);
+ aSubRect.SetRight(aLine[a++]);
+ aRegion.Union(aSubRect);
+ }
+ }
+
+ // copy line as new line
+ aLine = aNewLine;
+ nYStart = nY;
+ }
+ }
+
+ // write last line if used
+ if (!aLine.empty())
+ {
+ tools::Rectangle aSubRect;
+
+ // enter y values
+ aSubRect.SetTop(nYStart);
+ aSubRect.SetBottom(nY ? nY - 1 : 0);
+
+ for (size_t a(0); a < aLine.size();)
+ {
+ aSubRect.SetLeft(aLine[a++]);
+ aSubRect.SetRight(aLine[a++]);
+ aRegion.Union(aSubRect);
+ }
+ }
+
+ pReadAcc.reset();
+
+ return aRegion;
+}
+
+bool Bitmap::ReplaceMask(const AlphaMask& rMask, const Color& rReplaceColor)
+{
+ BitmapScopedReadAccess pMaskAcc(rMask);
+ BitmapScopedWriteAccess pAcc(*this);
+
+ if (!pMaskAcc || !pAcc)
+ return false;
+
+ const tools::Long nWidth = std::min(pMaskAcc->Width(), pAcc->Width());
+ const tools::Long nHeight = std::min(pMaskAcc->Height(), pAcc->Height());
+ const BitmapColor aMaskWhite(pMaskAcc->GetBestMatchingColor(COL_WHITE));
+ BitmapColor aReplace;
+
+ if (pAcc->HasPalette())
+ {
+ const sal_uInt16 nActColors = pAcc->GetPaletteEntryCount();
+ const sal_uInt16 nMaxColors = 1 << pAcc->GetBitCount();
+
+ aReplace = UpdatePaletteForNewColor(pAcc, nActColors, nMaxColors, nHeight, nWidth,
+ BitmapColor(rReplaceColor));
+ }
+ else
+ aReplace = rReplaceColor;
+
+ for (tools::Long nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pAcc->GetScanline(nY);
+ Scanline pScanlineMask = pMaskAcc->GetScanline(nY);
+ for (tools::Long nX = 0; nX < nWidth; nX++)
+ {
+ if (pMaskAcc->GetPixelFromData(pScanlineMask, nX) == aMaskWhite)
+ pAcc->SetPixelOnData(pScanline, nX, aReplace);
+ }
+ }
+
+ return true;
+}
+
+bool Bitmap::Replace(const AlphaMask& rAlpha, const Color& rMergeColor)
+{
+ Bitmap aNewBmp(GetSizePixel(), vcl::PixelFormat::N24_BPP);
+ BitmapScopedReadAccess pAcc(*this);
+ BitmapScopedReadAccess pAlphaAcc(rAlpha);
+ BitmapScopedWriteAccess pNewAcc(aNewBmp);
+
+ if (!pAcc || !pAlphaAcc || !pNewAcc)
+ return false;
+
+ BitmapColor aCol;
+ const tools::Long nWidth = std::min(pAlphaAcc->Width(), pAcc->Width());
+ const tools::Long nHeight = std::min(pAlphaAcc->Height(), pAcc->Height());
+
+ for (tools::Long nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pNewAcc->GetScanline(nY);
+ Scanline pScanlineAlpha = pAlphaAcc->GetScanline(nY);
+ for (tools::Long nX = 0; nX < nWidth; nX++)
+ {
+ aCol = pAcc->GetColor(nY, nX);
+ aCol.Merge(rMergeColor, pAlphaAcc->GetIndexFromData(pScanlineAlpha, nX));
+ pNewAcc->SetPixelOnData(pScanline, nX, aCol);
+ }
+ }
+
+ pAcc.reset();
+ pAlphaAcc.reset();
+ pNewAcc.reset();
+
+ const MapMode aMap(maPrefMapMode);
+ const Size aSize(maPrefSize);
+
+ *this = aNewBmp;
+
+ maPrefMapMode = aMap;
+ maPrefSize = aSize;
+
+ return true;
+}
+
+bool Bitmap::Replace(const Color& rSearchColor, const Color& rReplaceColor, sal_uInt8 nTol)
+{
+ if (mxSalBmp)
+ {
+ // implementation specific replace
+ std::shared_ptr<SalBitmap> xImpBmp(ImplGetSVData()->mpDefInst->CreateSalBitmap());
+ if (xImpBmp->Create(*mxSalBmp) && xImpBmp->Replace(rSearchColor, rReplaceColor, nTol))
+ {
+ ImplSetSalBitmap(xImpBmp);
+ maPrefMapMode = MapMode(MapUnit::MapPixel);
+ maPrefSize = xImpBmp->GetSize();
+ return true;
+ }
+ }
+
+ BitmapScopedWriteAccess pAcc(*this);
+ if (!pAcc)
+ return false;
+
+ const tools::Long nMinR = std::clamp<tools::Long>(rSearchColor.GetRed() - nTol, 0, 255);
+ const tools::Long nMaxR = std::clamp<tools::Long>(rSearchColor.GetRed() + nTol, 0, 255);
+ const tools::Long nMinG = std::clamp<tools::Long>(rSearchColor.GetGreen() - nTol, 0, 255);
+ const tools::Long nMaxG = std::clamp<tools::Long>(rSearchColor.GetGreen() + nTol, 0, 255);
+ const tools::Long nMinB = std::clamp<tools::Long>(rSearchColor.GetBlue() - nTol, 0, 255);
+ const tools::Long nMaxB = std::clamp<tools::Long>(rSearchColor.GetBlue() + nTol, 0, 255);
+
+ if (pAcc->HasPalette())
+ {
+ for (sal_uInt16 i = 0, nPalCount = pAcc->GetPaletteEntryCount(); i < nPalCount; i++)
+ {
+ const BitmapColor& rCol = pAcc->GetPaletteColor(i);
+
+ if (nMinR <= rCol.GetRed() && nMaxR >= rCol.GetRed() && nMinG <= rCol.GetGreen()
+ && nMaxG >= rCol.GetGreen() && nMinB <= rCol.GetBlue() && nMaxB >= rCol.GetBlue())
+ {
+ pAcc->SetPaletteColor(i, rReplaceColor);
+ }
+ }
+ }
+ else
+ {
+ BitmapColor aCol;
+ const BitmapColor aReplace(pAcc->GetBestMatchingColor(rReplaceColor));
+
+ for (tools::Long nY = 0, nHeight = pAcc->Height(); nY < nHeight; nY++)
+ {
+ Scanline pScanline = pAcc->GetScanline(nY);
+ for (tools::Long nX = 0, nWidth = pAcc->Width(); nX < nWidth; nX++)
+ {
+ aCol = pAcc->GetPixelFromData(pScanline, nX);
+
+ if (nMinR <= aCol.GetRed() && nMaxR >= aCol.GetRed() && nMinG <= aCol.GetGreen()
+ && nMaxG >= aCol.GetGreen() && nMinB <= aCol.GetBlue()
+ && nMaxB >= aCol.GetBlue())
+ {
+ pAcc->SetPixelOnData(pScanline, nX, aReplace);
+ }
+ }
+ }
+ }
+
+ pAcc.reset();
+
+ return true;
+}
+
+bool Bitmap::Replace(const Color* pSearchColors, const Color* pReplaceColors, size_t nColorCount,
+ sal_uInt8 const* pTols)
+{
+ BitmapScopedWriteAccess pAcc(*this);
+ if (!pAcc)
+ return false;
+
+ std::vector<sal_uInt8> aMinR(nColorCount);
+ std::vector<sal_uInt8> aMaxR(nColorCount);
+ std::vector<sal_uInt8> aMinG(nColorCount);
+ std::vector<sal_uInt8> aMaxG(nColorCount);
+ std::vector<sal_uInt8> aMinB(nColorCount);
+ std::vector<sal_uInt8> aMaxB(nColorCount);
+
+ if (pTols)
+ {
+ for (size_t i = 0; i < nColorCount; ++i)
+ {
+ const Color& rCol = pSearchColors[i];
+ const sal_uInt8 nTol = pTols[i];
+
+ aMinR[i] = std::clamp(rCol.GetRed() - nTol, 0, 255);
+ aMaxR[i] = std::clamp(rCol.GetRed() + nTol, 0, 255);
+ aMinG[i] = std::clamp(rCol.GetGreen() - nTol, 0, 255);
+ aMaxG[i] = std::clamp(rCol.GetGreen() + nTol, 0, 255);
+ aMinB[i] = std::clamp(rCol.GetBlue() - nTol, 0, 255);
+ aMaxB[i] = std::clamp(rCol.GetBlue() + nTol, 0, 255);
+ }
+ }
+ else
+ {
+ for (size_t i = 0; i < nColorCount; ++i)
+ {
+ const Color& rCol = pSearchColors[i];
+
+ aMinR[i] = rCol.GetRed();
+ aMaxR[i] = rCol.GetRed();
+ aMinG[i] = rCol.GetGreen();
+ aMaxG[i] = rCol.GetGreen();
+ aMinB[i] = rCol.GetBlue();
+ aMaxB[i] = rCol.GetBlue();
+ }
+ }
+
+ if (pAcc->HasPalette())
+ {
+ for (sal_uInt16 nEntry = 0, nPalCount = pAcc->GetPaletteEntryCount(); nEntry < nPalCount;
+ nEntry++)
+ {
+ const BitmapColor& rCol = pAcc->GetPaletteColor(nEntry);
+
+ for (size_t i = 0; i < nColorCount; ++i)
+ {
+ if (aMinR[i] <= rCol.GetRed() && aMaxR[i] >= rCol.GetRed()
+ && aMinG[i] <= rCol.GetGreen() && aMaxG[i] >= rCol.GetGreen()
+ && aMinB[i] <= rCol.GetBlue() && aMaxB[i] >= rCol.GetBlue())
+ {
+ pAcc->SetPaletteColor(nEntry, pReplaceColors[i]);
+ break;
+ }
+ }
+ }
+ }
+ else
+ {
+ std::vector<BitmapColor> aReplaces(nColorCount);
+
+ for (size_t i = 0; i < nColorCount; ++i)
+ aReplaces[i] = pAcc->GetBestMatchingColor(pReplaceColors[i]);
+
+ for (tools::Long nY = 0, nHeight = pAcc->Height(); nY < nHeight; nY++)
+ {
+ Scanline pScanline = pAcc->GetScanline(nY);
+ for (tools::Long nX = 0, nWidth = pAcc->Width(); nX < nWidth; nX++)
+ {
+ BitmapColor aCol = pAcc->GetPixelFromData(pScanline, nX);
+
+ for (size_t i = 0; i < nColorCount; ++i)
+ {
+ if (aMinR[i] <= aCol.GetRed() && aMaxR[i] >= aCol.GetRed()
+ && aMinG[i] <= aCol.GetGreen() && aMaxG[i] >= aCol.GetGreen()
+ && aMinB[i] <= aCol.GetBlue() && aMaxB[i] >= aCol.GetBlue())
+ {
+ pAcc->SetPixelOnData(pScanline, nX, aReplaces[i]);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ pAcc.reset();
+
+ return true;
+}
+
+// TODO: Have a look at OutputDevice::ImplDrawAlpha() for some
+// optimizations. Might even consolidate the code here and there.
+bool Bitmap::Blend(const AlphaMask& rAlpha, const Color& rBackgroundColor)
+{
+ // Convert to a truecolor bitmap, if we're a paletted one. There's room for tradeoff decision here,
+ // maybe later for an overload (or a flag)
+ if (vcl::isPalettePixelFormat(getPixelFormat()))
+ Convert(BmpConversion::N24Bit);
+
+ BitmapScopedReadAccess pAlphaAcc(rAlpha);
+
+ BitmapScopedWriteAccess pAcc(*this);
+
+ if (!pAlphaAcc || !pAcc)
+ return false;
+
+ const tools::Long nWidth = std::min(pAlphaAcc->Width(), pAcc->Width());
+ const tools::Long nHeight = std::min(pAlphaAcc->Height(), pAcc->Height());
+
+ for (tools::Long nY = 0; nY < nHeight; ++nY)
+ {
+ Scanline pScanline = pAcc->GetScanline(nY);
+ Scanline pScanlineAlpha = pAlphaAcc->GetScanline(nY);
+ for (tools::Long nX = 0; nX < nWidth; ++nX)
+ {
+ BitmapColor aBmpColor = pAcc->GetPixelFromData(pScanline, nX);
+ aBmpColor.Merge(rBackgroundColor, pAlphaAcc->GetIndexFromData(pScanlineAlpha, nX));
+ pAcc->SetPixelOnData(pScanline, nX, aBmpColor);
+ }
+ }
+
+ 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: */
diff --git a/vcl/source/bitmap/bitmappalette.cxx b/vcl/source/bitmap/bitmappalette.cxx
new file mode 100644
index 0000000000..43eae34754
--- /dev/null
+++ b/vcl/source/bitmap/bitmappalette.cxx
@@ -0,0 +1,242 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <config_features.h>
+
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+#include <rtl/crc.h>
+#include <tools/helpers.hxx>
+
+#include <vcl/BitmapPalette.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/outdev.hxx>
+
+#include <svdata.hxx>
+#include <salinst.hxx>
+
+class ImplBitmapPalette
+{
+public:
+ ImplBitmapPalette(std::initializer_list<BitmapColor> aBitmapColor)
+ : maBitmapColor(aBitmapColor)
+ {
+ }
+ ImplBitmapPalette(const BitmapColor* first, const BitmapColor* last)
+ : maBitmapColor(first, last)
+ {
+ }
+ ImplBitmapPalette() {}
+ ImplBitmapPalette(sal_uInt16 nCount)
+ : maBitmapColor(nCount)
+ {
+ }
+ std::vector<BitmapColor>& GetBitmapData() { return maBitmapColor; }
+ const std::vector<BitmapColor>& GetBitmapData() const { return maBitmapColor; }
+ bool operator==(const ImplBitmapPalette& rBitmapPalette) const
+ {
+ return maBitmapColor == rBitmapPalette.maBitmapColor;
+ }
+
+private:
+ std::vector<BitmapColor> maBitmapColor;
+};
+
+namespace
+{
+BitmapPalette::ImplType& GetGlobalDefault()
+{
+ static BitmapPalette::ImplType gDefault;
+ return gDefault;
+}
+}
+
+BitmapPalette::BitmapPalette()
+ : mpImpl(GetGlobalDefault())
+{
+}
+
+BitmapPalette::BitmapPalette(const BitmapPalette& rOther)
+ : mpImpl(rOther.mpImpl)
+{
+}
+
+BitmapPalette::BitmapPalette(BitmapPalette&& rOther) noexcept
+ : mpImpl(std::move(rOther.mpImpl))
+{
+}
+
+BitmapPalette::BitmapPalette(std::initializer_list<BitmapColor> aBitmapColor)
+ : mpImpl(aBitmapColor)
+{
+}
+
+BitmapPalette::BitmapPalette(const BitmapColor* first, const BitmapColor* last)
+ : mpImpl({ first, last })
+{
+}
+
+BitmapPalette::BitmapPalette(sal_uInt16 nCount)
+ : mpImpl(nCount)
+{
+}
+
+BitmapPalette::~BitmapPalette() {}
+
+BitmapPalette& BitmapPalette::operator=(const BitmapPalette& rOther)
+{
+ mpImpl = rOther.mpImpl;
+ return *this;
+}
+
+BitmapPalette& BitmapPalette::operator=(BitmapPalette&& rOther) noexcept
+{
+ mpImpl = std::move(rOther.mpImpl);
+ return *this;
+}
+
+const BitmapColor* BitmapPalette::ImplGetColorBuffer() const
+{
+ return mpImpl->GetBitmapData().data();
+}
+
+BitmapColor* BitmapPalette::ImplGetColorBuffer() { return mpImpl->GetBitmapData().data(); }
+
+BitmapChecksum BitmapPalette::GetChecksum() const
+{
+ auto const& rBitmapData = mpImpl->GetBitmapData();
+ return rtl_crc32(0, rBitmapData.data(), rBitmapData.size() * sizeof(BitmapColor));
+}
+
+bool BitmapPalette::operator==(const BitmapPalette& rOther) const
+{
+ return mpImpl == rOther.mpImpl;
+}
+
+bool BitmapPalette::operator!() const { return mpImpl->GetBitmapData().empty(); }
+
+sal_uInt16 BitmapPalette::GetEntryCount() const { return mpImpl->GetBitmapData().size(); }
+
+void BitmapPalette::SetEntryCount(sal_uInt16 nCount) { mpImpl->GetBitmapData().resize(nCount); }
+
+const BitmapColor& BitmapPalette::operator[](sal_uInt16 nIndex) const
+{
+ assert(nIndex < mpImpl->GetBitmapData().size() && "Palette index is out of range");
+ return mpImpl->GetBitmapData()[nIndex];
+}
+
+BitmapColor& BitmapPalette::operator[](sal_uInt16 nIndex)
+{
+ assert(nIndex < mpImpl->GetBitmapData().size() && "Palette index is out of range");
+ 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();
+ sal_uInt16 nRetIndex = 0;
+
+ if (!rBitmapColor.empty())
+ {
+ for (size_t j = 0; j < rBitmapColor.size(); ++j)
+ {
+ if (rCol == rBitmapColor[j])
+ {
+ return j;
+ }
+ }
+
+ sal_uInt16 nLastErr = SAL_MAX_UINT16;
+ for (size_t i = 0; i < rBitmapColor.size(); ++i)
+ {
+ const sal_uInt16 nActErr = rCol.GetColorError(rBitmapColor[i]);
+ if (nActErr < nLastErr)
+ {
+ nLastErr = nActErr;
+ nRetIndex = i;
+ }
+ }
+ }
+
+ 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();
+ const int nEntryCount = GetEntryCount();
+ if (!nEntryCount) // NOTE: an empty palette means 1:1 mapping
+ return true;
+ // See above: only certain entry values will result in a valid call to GetGreyPalette
+ if (nEntryCount == 2 || nEntryCount == 4 || nEntryCount == 16 || nEntryCount == 256)
+ {
+ const BitmapPalette& rGreyPalette = Bitmap::GetGreyPalette(nEntryCount);
+ if (rGreyPalette == *this)
+ return true;
+ }
+
+ bool bRet = false;
+ // TODO: is it worth to compare the entries for the general case?
+ if (nEntryCount == 2)
+ {
+ const BitmapColor& rCol0(rBitmapColor[0]);
+ const BitmapColor& rCol1(rBitmapColor[1]);
+ bRet = rCol0.GetRed() == rCol0.GetGreen() && rCol0.GetRed() == rCol0.GetBlue()
+ && rCol1.GetRed() == rCol1.GetGreen() && rCol1.GetRed() == rCol1.GetBlue();
+ }
+ return bRet;
+}
+
+bool BitmapPalette::IsGreyPalette8Bit() const
+{
+ auto const& rBitmapColor = mpImpl->GetBitmapData();
+ const int nEntryCount = GetEntryCount();
+ if (!nEntryCount) // NOTE: an empty palette means 1:1 mapping
+ return true;
+ if (nEntryCount != 256)
+ return false;
+ for (sal_uInt16 i = 0; i < 256; ++i)
+ {
+ if (rBitmapColor[i] != BitmapColor(i, i, i))
+ return false;
+ }
+ return true;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/bmpfast.cxx b/vcl/source/bitmap/bmpfast.cxx
new file mode 100644
index 0000000000..53de074adc
--- /dev/null
+++ b/vcl/source/bitmap/bmpfast.cxx
@@ -0,0 +1,823 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <vcl/salgtype.hxx>
+
+#include <vcl/BitmapWriteAccess.hxx>
+#include <bitmap/bmpfast.hxx>
+
+#include <sal/log.hxx>
+
+typedef unsigned char PIXBYTE;
+
+namespace {
+
+class BasePixelPtr
+{
+public:
+ explicit BasePixelPtr( PIXBYTE* p = nullptr ) : mpPixel( p ) {}
+ void SetRawPtr( PIXBYTE* pRawPtr ) { mpPixel = pRawPtr; }
+ void AddByteOffset( int nByteOffset ) { mpPixel += nByteOffset; }
+
+protected:
+ PIXBYTE* mpPixel;
+};
+
+template <ScanlineFormat PIXFMT>
+class TrueColorPixelPtr : public BasePixelPtr
+{
+public:
+ PIXBYTE GetRed() const;
+ PIXBYTE GetGreen() const;
+ PIXBYTE GetBlue() const;
+ PIXBYTE GetAlpha() const;
+
+ void SetColor( PIXBYTE r, PIXBYTE g, PIXBYTE b ) const;
+ void SetAlpha( PIXBYTE a ) const;
+};
+
+// template specializations for truecolor pixel formats
+template <>
+class TrueColorPixelPtr<ScanlineFormat::N24BitTcRgb> : public BasePixelPtr
+{
+public:
+ void operator++() { mpPixel += 3; }
+
+ PIXBYTE GetRed() const { return mpPixel[0]; }
+ PIXBYTE GetGreen() const { return mpPixel[1]; }
+ PIXBYTE GetBlue() const { return mpPixel[2]; }
+ static PIXBYTE GetAlpha() { return 255; }
+ static void SetAlpha( PIXBYTE ) {}
+
+ void SetColor( PIXBYTE r, PIXBYTE g, PIXBYTE b ) const
+ {
+ mpPixel[0] = r;
+ mpPixel[1] = g;
+ mpPixel[2] = b;
+ }
+};
+
+template <>
+class TrueColorPixelPtr<ScanlineFormat::N24BitTcBgr> : public BasePixelPtr
+{
+public:
+ void operator++() { mpPixel += 3; }
+
+ PIXBYTE GetRed() const { return mpPixel[2]; }
+ PIXBYTE GetGreen() const { return mpPixel[1]; }
+ PIXBYTE GetBlue() const { return mpPixel[0]; }
+ static PIXBYTE GetAlpha() { return 255; }
+ static void SetAlpha( PIXBYTE ) {}
+
+ void SetColor( PIXBYTE r, PIXBYTE g, PIXBYTE b ) const
+ {
+ mpPixel[0] = b;
+ mpPixel[1] = g;
+ mpPixel[2] = r;
+ }
+};
+
+template <>
+class TrueColorPixelPtr<ScanlineFormat::N32BitTcArgb> : public BasePixelPtr
+{
+public:
+ void operator++() { mpPixel += 4; }
+
+ PIXBYTE GetRed() const { return mpPixel[1]; }
+ PIXBYTE GetGreen() const { return mpPixel[2]; }
+ PIXBYTE GetBlue() const { return mpPixel[3]; }
+ PIXBYTE GetAlpha() const { return mpPixel[0]; }
+ void SetAlpha( PIXBYTE a ) const { mpPixel[0] = a; }
+
+ void SetColor( PIXBYTE r, PIXBYTE g, PIXBYTE b ) const
+ {
+ mpPixel[1] = r;
+ mpPixel[2] = g;
+ mpPixel[3] = b;
+ }
+};
+
+template <>
+class TrueColorPixelPtr<ScanlineFormat::N32BitTcAbgr> : public BasePixelPtr
+{
+public:
+ void operator++() { mpPixel += 4; }
+
+ PIXBYTE GetRed() const { return mpPixel[3]; }
+ PIXBYTE GetGreen() const { return mpPixel[2]; }
+ PIXBYTE GetBlue() const { return mpPixel[1]; }
+ PIXBYTE GetAlpha() const { return mpPixel[0]; }
+ void SetAlpha( PIXBYTE a ) const { mpPixel[0] = a; }
+
+ void SetColor( PIXBYTE r, PIXBYTE g, PIXBYTE b ) const
+ {
+ mpPixel[1] = b;
+ mpPixel[2] = g;
+ mpPixel[3] = r;
+ }
+};
+
+template <>
+class TrueColorPixelPtr<ScanlineFormat::N32BitTcRgba> : public BasePixelPtr
+{
+public:
+ void operator++() { mpPixel += 4; }
+
+ PIXBYTE GetRed() const { return mpPixel[0]; }
+ PIXBYTE GetGreen() const { return mpPixel[1]; }
+ PIXBYTE GetBlue() const { return mpPixel[2]; }
+ PIXBYTE GetAlpha() const { return mpPixel[3]; }
+ void SetAlpha( PIXBYTE a ) const{ mpPixel[3] = a; }
+
+ void SetColor( PIXBYTE r, PIXBYTE g, PIXBYTE b ) const
+ {
+ mpPixel[0] = r;
+ mpPixel[1] = g;
+ mpPixel[2] = b;
+ }
+};
+
+template <>
+class TrueColorPixelPtr<ScanlineFormat::N32BitTcBgra> : public BasePixelPtr
+{
+public:
+ void operator++() { mpPixel += 4; }
+
+ PIXBYTE GetRed() const { return mpPixel[2]; }
+ PIXBYTE GetGreen() const { return mpPixel[1]; }
+ PIXBYTE GetBlue() const { return mpPixel[0]; }
+ PIXBYTE GetAlpha() const { return mpPixel[3]; }
+ void SetAlpha( PIXBYTE a ) const{ mpPixel[3] = a; }
+
+ void SetColor( PIXBYTE r, PIXBYTE g, PIXBYTE b ) const
+ {
+ mpPixel[0] = b;
+ mpPixel[1] = g;
+ mpPixel[2] = r;
+ }
+};
+
+// This assumes the content uses the grayscale palette (needs to be checked
+// by code allowing the use of the format).
+// Only reading color is implemented, since e.g. 24bpp input couldn't be
+// easily guaranteed to be grayscale.
+template <>
+class TrueColorPixelPtr<ScanlineFormat::N8BitPal> : public BasePixelPtr
+{
+public:
+ void operator++() { mpPixel += 1; }
+
+ PIXBYTE GetRed() const { return mpPixel[0]; }
+ PIXBYTE GetGreen() const { return mpPixel[0]; }
+ PIXBYTE GetBlue() const { return mpPixel[0]; }
+ static PIXBYTE GetAlpha() { return 255; }
+};
+
+}
+
+// converting truecolor formats
+template <ScanlineFormat SRCFMT, ScanlineFormat DSTFMT>
+static void ImplConvertPixel( const TrueColorPixelPtr<DSTFMT>& rDst,
+ const TrueColorPixelPtr<SRCFMT>& rSrc )
+{
+ rDst.SetColor( rSrc.GetRed(), rSrc.GetGreen(), rSrc.GetBlue() );
+ rDst.SetAlpha( rSrc.GetAlpha() );
+}
+
+template <ScanlineFormat SRCFMT, ScanlineFormat DSTFMT>
+static void ImplConvertLine( const TrueColorPixelPtr<DSTFMT>& rDst,
+ const TrueColorPixelPtr<SRCFMT>& rSrc, int nPixelCount )
+{
+ TrueColorPixelPtr<DSTFMT> aDst( rDst );
+ TrueColorPixelPtr<SRCFMT> aSrc( rSrc );
+ while( --nPixelCount >= 0 )
+ {
+ ImplConvertPixel( aDst, aSrc );
+ ++aSrc;
+ ++aDst;
+ }
+}
+
+// alpha blending truecolor pixels
+template <ScanlineFormat SRCFMT, ScanlineFormat DSTFMT>
+static void ImplBlendPixels( const TrueColorPixelPtr<DSTFMT>& rDst,
+ const TrueColorPixelPtr<SRCFMT>& rSrc, unsigned nAlphaVal )
+{
+ static const unsigned nAlphaShift = 8;
+ if( !nAlphaVal )
+ ImplConvertPixel( rDst, rSrc );
+ else if( nAlphaVal != ~(~0U << nAlphaShift) )
+ {
+ int nR = rDst.GetRed();
+ int nS = rSrc.GetRed();
+ nR = nS + (((nR - nS) * nAlphaVal) >> nAlphaShift);
+
+ int nG = rDst.GetGreen();
+ nS = rSrc.GetGreen();
+ nG = nS + (((nG - nS) * nAlphaVal) >> nAlphaShift);
+
+ int nB = rDst.GetBlue();
+ nS = rSrc.GetBlue();
+ nB = nS + (((nB - nS) * nAlphaVal) >> nAlphaShift);
+
+ rDst.SetColor( sal::static_int_cast<PIXBYTE>(nR),
+ sal::static_int_cast<PIXBYTE>(nG),
+ sal::static_int_cast<PIXBYTE>(nB) );
+ }
+}
+
+template <ScanlineFormat MASKFMT, ScanlineFormat SRCFMT, ScanlineFormat DSTFMT>
+static void ImplBlendLines( const TrueColorPixelPtr<DSTFMT>& rDst,
+ const TrueColorPixelPtr<SRCFMT>& rSrc, const TrueColorPixelPtr<MASKFMT>& rMsk,
+ int nPixelCount )
+{
+ TrueColorPixelPtr<MASKFMT> aMsk( rMsk );
+ TrueColorPixelPtr<DSTFMT> aDst( rDst );
+ TrueColorPixelPtr<SRCFMT> aSrc( rSrc );
+ while( --nPixelCount >= 0 )
+ {
+ // VCL masks store alpha as color, hence the GetRed() and not GetAlpha().
+ ImplBlendPixels(aDst, aSrc, aMsk.GetRed());
+ ++aDst;
+ ++aSrc;
+ ++aMsk;
+ }
+}
+
+static bool ImplCopyImage( BitmapBuffer& rDstBuffer, const BitmapBuffer& rSrcBuffer )
+{
+ const int nSrcLinestep = rSrcBuffer.mnScanlineSize;
+ int nDstLinestep = rDstBuffer.mnScanlineSize;
+
+ const PIXBYTE* pRawSrc = rSrcBuffer.mpBits;
+ PIXBYTE* pRawDst = rDstBuffer.mpBits;
+
+ // source and destination don't match upside down
+ if( ScanlineFormat::TopDown & (rSrcBuffer.mnFormat ^ rDstBuffer.mnFormat) )
+ {
+ pRawDst += (rSrcBuffer.mnHeight - 1) * nDstLinestep;
+ nDstLinestep = -rDstBuffer.mnScanlineSize;
+ }
+ else if( nSrcLinestep == nDstLinestep )
+ {
+ memcpy( pRawDst, pRawSrc, rSrcBuffer.mnHeight * nDstLinestep );
+ return true;
+ }
+
+ int nByteWidth = nSrcLinestep;
+ if( nByteWidth > rDstBuffer.mnScanlineSize )
+ nByteWidth = rDstBuffer.mnScanlineSize;
+
+ for( int y = rSrcBuffer.mnHeight; --y >= 0; )
+ {
+ memcpy( pRawDst, pRawSrc, nByteWidth );
+ pRawSrc += nSrcLinestep;
+ pRawDst += nDstLinestep;
+ }
+
+ return true;
+}
+
+template <ScanlineFormat DSTFMT,ScanlineFormat SRCFMT>
+static bool ImplConvertToBitmap( TrueColorPixelPtr<SRCFMT>& rSrcLine,
+ BitmapBuffer& rDstBuffer, const BitmapBuffer& rSrcBuffer )
+{
+ // help the compiler to avoid instantiations of unneeded conversions
+ SAL_WARN_IF( SRCFMT == DSTFMT, "vcl.gdi", "ImplConvertToBitmap into same format");
+ if( SRCFMT == DSTFMT )
+ return false;
+
+ const int nSrcLinestep = rSrcBuffer.mnScanlineSize;
+ int nDstLinestep = rDstBuffer.mnScanlineSize;
+
+ TrueColorPixelPtr<DSTFMT> aDstLine; aDstLine.SetRawPtr( rDstBuffer.mpBits );
+
+ // source and destination don't match upside down
+ if( ScanlineFormat::TopDown & (rSrcBuffer.mnFormat ^ rDstBuffer.mnFormat) )
+ {
+ aDstLine.AddByteOffset( (rSrcBuffer.mnHeight - 1) * nDstLinestep );
+ nDstLinestep = -nDstLinestep;
+ }
+
+ for( int y = rSrcBuffer.mnHeight; --y >= 0; )
+ {
+ ImplConvertLine( aDstLine, rSrcLine, rSrcBuffer.mnWidth );
+ rSrcLine.AddByteOffset( nSrcLinestep );
+ aDstLine.AddByteOffset( nDstLinestep );
+ }
+
+ return true;
+}
+
+template <ScanlineFormat SRCFMT>
+static bool ImplConvertFromBitmap( BitmapBuffer& rDst, const BitmapBuffer& rSrc )
+{
+ TrueColorPixelPtr<SRCFMT> aSrcType; aSrcType.SetRawPtr( rSrc.mpBits );
+
+ // select the matching instantiation for the destination's bitmap format
+ switch (RemoveScanline(rDst.mnFormat))
+ {
+ case ScanlineFormat::N1BitMsbPal:
+ case ScanlineFormat::N8BitPal:
+ break;
+
+ case ScanlineFormat::N32BitTcMask:
+// return ImplConvertToBitmap<ScanlineFormat::N32BitTcMask>( aSrcType, rDst, rSrc );
+ break;
+
+ case ScanlineFormat::N24BitTcBgr:
+ return ImplConvertToBitmap<ScanlineFormat::N24BitTcBgr>( aSrcType, rDst, rSrc );
+ case ScanlineFormat::N24BitTcRgb:
+ return ImplConvertToBitmap<ScanlineFormat::N24BitTcRgb>( aSrcType, rDst, rSrc );
+
+ case ScanlineFormat::N32BitTcAbgr:
+ return ImplConvertToBitmap<ScanlineFormat::N32BitTcAbgr>( aSrcType, rDst, rSrc );
+ case ScanlineFormat::N32BitTcArgb:
+ return ImplConvertToBitmap<ScanlineFormat::N32BitTcArgb>( aSrcType, rDst, rSrc );
+ case ScanlineFormat::N32BitTcBgra:
+ return ImplConvertToBitmap<ScanlineFormat::N32BitTcBgra>( aSrcType, rDst, rSrc );
+ case ScanlineFormat::N32BitTcRgba:
+ return ImplConvertToBitmap<ScanlineFormat::N32BitTcRgba>( aSrcType, rDst, rSrc );
+ default: break;
+ }
+
+ static int nNotAccelerated = 0;
+ SAL_WARN_IF( rSrc.mnWidth * rSrc.mnHeight >= 4000 && ++nNotAccelerated == 100,
+ "vcl.gdi",
+ "ImplConvertFromBitmap for not accelerated case (" << std::hex << static_cast<int>(rSrc.mnFormat) << "->" << static_cast<int>(rDst.mnFormat) << ")" );
+
+ return false;
+}
+
+// A universal stretching conversion is overkill in most common situations
+// => performance benefits for speeding up the non-stretching cases
+bool ImplFastBitmapConversion( BitmapBuffer& rDst, const BitmapBuffer& rSrc,
+ const SalTwoRect& rTR )
+{
+ // TODO:horizontal mirroring not implemented yet
+ if( rTR.mnDestWidth < 0 )
+ return false;
+ // vertical mirroring
+ if( rTR.mnDestHeight < 0 )
+ // TODO: rDst.mnFormat ^= ScanlineFormat::TopDown;
+ return false;
+
+ // offsetted conversion is not implemented yet
+ if( rTR.mnSrcX || rTR.mnSrcY )
+ return false;
+ if( rTR.mnDestX || rTR.mnDestY )
+ return false;
+
+ // stretched conversion is not implemented yet
+ if( rTR.mnDestWidth != rTR.mnSrcWidth )
+ return false;
+ if( rTR.mnDestHeight!= rTR.mnSrcHeight )
+ return false;
+
+ // check source image size
+ if( rSrc.mnWidth < rTR.mnSrcX + rTR.mnSrcWidth )
+ return false;
+ if( rSrc.mnHeight < rTR.mnSrcY + rTR.mnSrcHeight )
+ return false;
+
+ // check dest image size
+ if( rDst.mnWidth < rTR.mnDestX + rTR.mnDestWidth )
+ return false;
+ if( rDst.mnHeight < rTR.mnDestY + rTR.mnDestHeight )
+ return false;
+
+ const ScanlineFormat nSrcFormat = RemoveScanline(rSrc.mnFormat);
+ const ScanlineFormat nDstFormat = RemoveScanline(rDst.mnFormat);
+
+ // special handling of trivial cases
+ if( nSrcFormat == nDstFormat )
+ {
+ // accelerated palette conversions not yet implemented
+ if( rSrc.maPalette != rDst.maPalette )
+ return false;
+ return ImplCopyImage( rDst, rSrc );
+ }
+
+ // select the matching instantiation for the source's bitmap format
+ switch( nSrcFormat )
+ {
+ case ScanlineFormat::N1BitMsbPal:
+ break;
+
+ case ScanlineFormat::N32BitTcMask:
+// return ImplConvertFromBitmap<ScanlineFormat::N32BitTcMask>( rDst, rSrc );
+ break;
+
+ case ScanlineFormat::N8BitPal:
+ if(rSrc.maPalette.IsGreyPalette8Bit())
+ return ImplConvertFromBitmap<ScanlineFormat::N8BitPal>( rDst, rSrc );
+ break;
+
+ case ScanlineFormat::N24BitTcBgr:
+ return ImplConvertFromBitmap<ScanlineFormat::N24BitTcBgr>( rDst, rSrc );
+ case ScanlineFormat::N24BitTcRgb:
+ return ImplConvertFromBitmap<ScanlineFormat::N24BitTcRgb>( rDst, rSrc );
+
+ case ScanlineFormat::N32BitTcAbgr:
+ return ImplConvertFromBitmap<ScanlineFormat::N32BitTcAbgr>( rDst, rSrc );
+ case ScanlineFormat::N32BitTcArgb:
+ return ImplConvertFromBitmap<ScanlineFormat::N32BitTcArgb>( rDst, rSrc );
+ case ScanlineFormat::N32BitTcBgra:
+ return ImplConvertFromBitmap<ScanlineFormat::N32BitTcBgra>( rDst, rSrc );
+ case ScanlineFormat::N32BitTcRgba:
+ return ImplConvertFromBitmap<ScanlineFormat::N32BitTcRgba>( rDst, rSrc );
+ default: break;
+ }
+
+ static int nNotAccelerated = 0;
+ SAL_WARN_IF( rSrc.mnWidth * rSrc.mnHeight >= 4000 && ++nNotAccelerated == 100,
+ "vcl.gdi",
+ "ImplFastBitmapConversion for not accelerated case (" << std::hex << static_cast<int>(rSrc.mnFormat) << "->" << static_cast<int>(rDst.mnFormat) << ")" );
+
+ return false;
+}
+
+static inline ConstScanline ImplGetScanline( const BitmapBuffer& rBuf, tools::Long nY )
+{
+ if( rBuf.mnFormat & ScanlineFormat::TopDown )
+ return rBuf.mpBits + nY * rBuf.mnScanlineSize;
+ else
+ return rBuf.mpBits + (rBuf.mnHeight - 1 - nY) * rBuf.mnScanlineSize;
+}
+
+static inline Scanline ImplGetScanline( BitmapBuffer& rBuf, tools::Long nY )
+{
+ return const_cast<Scanline>(ImplGetScanline( const_cast<const BitmapBuffer&>(rBuf), nY ));
+}
+
+template <ScanlineFormat DSTFMT, ScanlineFormat SRCFMT>
+static bool ImplCopyToScanline( tools::Long nY, BitmapBuffer& rDst, TrueColorPixelPtr<SRCFMT>& rSrcLine, tools::Long nSrcWidth )
+{
+ TrueColorPixelPtr<DSTFMT> aDstType;
+ aDstType.SetRawPtr( ImplGetScanline( rDst, nY ));
+ ImplConvertLine( aDstType, rSrcLine, std::min( nSrcWidth, rDst.mnWidth ));
+ return true;
+}
+
+template <ScanlineFormat SRCFMT>
+static bool ImplCopyFromScanline( tools::Long nY, BitmapBuffer& rDst, ConstScanline aSrcScanline, tools::Long nSrcWidth )
+{
+ TrueColorPixelPtr<SRCFMT> aSrcType;
+ aSrcType.SetRawPtr( const_cast<Scanline>( aSrcScanline ));
+ // select the matching instantiation for the destination's bitmap format
+ switch( RemoveScanline( rDst.mnFormat ))
+ {
+ case ScanlineFormat::N24BitTcBgr:
+ return ImplCopyToScanline<ScanlineFormat::N24BitTcBgr>( nY, rDst, aSrcType, nSrcWidth );
+ case ScanlineFormat::N24BitTcRgb:
+ return ImplCopyToScanline<ScanlineFormat::N24BitTcRgb>( nY, rDst, aSrcType, nSrcWidth );
+
+ case ScanlineFormat::N32BitTcAbgr:
+ return ImplCopyToScanline<ScanlineFormat::N32BitTcAbgr>( nY, rDst, aSrcType, nSrcWidth );
+ case ScanlineFormat::N32BitTcArgb:
+ return ImplCopyToScanline<ScanlineFormat::N32BitTcArgb>( nY, rDst, aSrcType, nSrcWidth );
+ case ScanlineFormat::N32BitTcBgra:
+ return ImplCopyToScanline<ScanlineFormat::N32BitTcBgra>( nY, rDst, aSrcType, nSrcWidth );
+ case ScanlineFormat::N32BitTcRgba:
+ return ImplCopyToScanline<ScanlineFormat::N32BitTcRgba>( nY, rDst, aSrcType, nSrcWidth );
+ default:
+ break;
+ }
+ return false;
+
+}
+
+bool ImplFastCopyScanline( tools::Long nY, BitmapBuffer& rDst, ConstScanline aSrcScanline,
+ ScanlineFormat nSrcScanlineFormat, sal_uInt32 nSrcScanlineSize)
+{
+ if( rDst.mnHeight <= nY )
+ return false;
+
+ const ScanlineFormat nSrcFormat = RemoveScanline(nSrcScanlineFormat);
+ const ScanlineFormat nDstFormat = RemoveScanline(rDst.mnFormat);
+
+ // special handling of trivial cases
+ if( nSrcFormat == nDstFormat )
+ {
+ memcpy( ImplGetScanline( rDst, nY ), aSrcScanline, std::min<tools::Long>(nSrcScanlineSize, rDst.mnScanlineSize));
+ return true;
+ }
+
+ // select the matching instantiation for the source's bitmap format
+ switch( nSrcFormat )
+ {
+ case ScanlineFormat::N24BitTcBgr:
+ return ImplCopyFromScanline<ScanlineFormat::N24BitTcBgr>( nY, rDst, aSrcScanline, nSrcScanlineSize / 3 );
+ case ScanlineFormat::N24BitTcRgb:
+ return ImplCopyFromScanline<ScanlineFormat::N24BitTcRgb>( nY, rDst, aSrcScanline, nSrcScanlineSize / 3 );
+
+ case ScanlineFormat::N32BitTcAbgr:
+ return ImplCopyFromScanline<ScanlineFormat::N32BitTcAbgr>( nY, rDst, aSrcScanline, nSrcScanlineSize / 4 );
+ case ScanlineFormat::N32BitTcArgb:
+ return ImplCopyFromScanline<ScanlineFormat::N32BitTcArgb>( nY, rDst, aSrcScanline, nSrcScanlineSize / 4 );
+ case ScanlineFormat::N32BitTcBgra:
+ return ImplCopyFromScanline<ScanlineFormat::N32BitTcBgra>( nY, rDst, aSrcScanline, nSrcScanlineSize / 4 );
+ case ScanlineFormat::N32BitTcRgba:
+ return ImplCopyFromScanline<ScanlineFormat::N32BitTcRgba>( nY, rDst, aSrcScanline, nSrcScanlineSize / 4 );
+ default:
+ break;
+ }
+ return false;
+}
+
+bool ImplFastCopyScanline( tools::Long nY, BitmapBuffer& rDst, const BitmapBuffer& rSrc)
+{
+ if( nY >= rDst.mnHeight )
+ return false;
+ if( rSrc.maPalette != rDst.maPalette )
+ return false;
+ return ImplFastCopyScanline( nY, rDst, ImplGetScanline( rSrc, nY ), rSrc.mnFormat, rSrc.mnScanlineSize);
+}
+
+template <ScanlineFormat DSTFMT, ScanlineFormat SRCFMT> //,sal_uLong MSKFMT>
+static bool ImplBlendToBitmap( TrueColorPixelPtr<SRCFMT>& rSrcLine,
+ BitmapBuffer& rDstBuffer, const BitmapBuffer& rSrcBuffer,
+ const BitmapBuffer& rMskBuffer )
+{
+ SAL_WARN_IF(( rMskBuffer.mnFormat & ~ScanlineFormat::TopDown ) != ScanlineFormat::N8BitPal,
+ "vcl.gdi", "FastBmp BlendImage: unusual MSKFMT" );
+
+ const int nSrcLinestep = rSrcBuffer.mnScanlineSize;
+ int nMskLinestep = rMskBuffer.mnScanlineSize;
+ int nDstLinestep = rDstBuffer.mnScanlineSize;
+
+ TrueColorPixelPtr<ScanlineFormat::N8BitPal> aMskLine; aMskLine.SetRawPtr( rMskBuffer.mpBits );
+ TrueColorPixelPtr<DSTFMT> aDstLine; aDstLine.SetRawPtr( rDstBuffer.mpBits );
+
+ // special case for single line masks
+ if( rMskBuffer.mnHeight == 1 )
+ nMskLinestep = 0;
+
+ // source and mask don't match: upside down
+ if( (rSrcBuffer.mnFormat ^ rMskBuffer.mnFormat) & ScanlineFormat::TopDown )
+ {
+ aMskLine.AddByteOffset( (rSrcBuffer.mnHeight - 1) * nMskLinestep );
+ nMskLinestep = -nMskLinestep;
+ }
+
+ // source and destination don't match: upside down
+ if( (rSrcBuffer.mnFormat ^ rDstBuffer.mnFormat) & ScanlineFormat::TopDown )
+ {
+ aDstLine.AddByteOffset( (rDstBuffer.mnHeight - 1) * nDstLinestep );
+ nDstLinestep = -nDstLinestep;
+ }
+
+ assert(rDstBuffer.mnHeight <= rSrcBuffer.mnHeight && "not sure about that?");
+ for (int y = rDstBuffer.mnHeight; --y >= 0;)
+ {
+ ImplBlendLines(aDstLine, rSrcLine, aMskLine, rDstBuffer.mnWidth);
+ aDstLine.AddByteOffset( nDstLinestep );
+ rSrcLine.AddByteOffset( nSrcLinestep );
+ aMskLine.AddByteOffset( nMskLinestep );
+ }
+
+ return true;
+}
+
+// some specializations to reduce the code size
+template <>
+bool ImplBlendToBitmap<ScanlineFormat::N24BitTcBgr,ScanlineFormat::N24BitTcBgr>(
+ TrueColorPixelPtr<ScanlineFormat::N24BitTcBgr>&,
+ BitmapBuffer& rDstBuffer, const BitmapBuffer& rSrcBuffer,
+ const BitmapBuffer& rMskBuffer )
+ {
+ TrueColorPixelPtr<ScanlineFormat::N24BitTcRgb> aSrcType; aSrcType.SetRawPtr( rSrcBuffer.mpBits );
+ return ImplBlendToBitmap<ScanlineFormat::N24BitTcRgb>( aSrcType, rDstBuffer, rSrcBuffer, rMskBuffer );
+ }
+
+template <>
+bool ImplBlendToBitmap<ScanlineFormat::N32BitTcAbgr,ScanlineFormat::N32BitTcAbgr>(
+ TrueColorPixelPtr<ScanlineFormat::N32BitTcAbgr>&,
+ BitmapBuffer& rDstBuffer, const BitmapBuffer& rSrcBuffer,
+ const BitmapBuffer& rMskBuffer )
+ {
+ TrueColorPixelPtr<ScanlineFormat::N32BitTcArgb> aSrcType; aSrcType.SetRawPtr( rSrcBuffer.mpBits );
+ return ImplBlendToBitmap<ScanlineFormat::N32BitTcArgb>( aSrcType, rDstBuffer, rSrcBuffer, rMskBuffer );
+ }
+
+template <>
+bool ImplBlendToBitmap<ScanlineFormat::N32BitTcBgra,ScanlineFormat::N32BitTcBgra>(
+ TrueColorPixelPtr<ScanlineFormat::N32BitTcBgra>&,
+ BitmapBuffer& rDstBuffer, const BitmapBuffer& rSrcBuffer,
+ const BitmapBuffer& rMskBuffer )
+ {
+ TrueColorPixelPtr<ScanlineFormat::N32BitTcRgba> aSrcType; aSrcType.SetRawPtr( rSrcBuffer.mpBits );
+ return ImplBlendToBitmap<ScanlineFormat::N32BitTcRgba>( aSrcType, rDstBuffer, rSrcBuffer, rMskBuffer );
+ }
+
+template <ScanlineFormat SRCFMT>
+static bool ImplBlendFromBitmap( BitmapBuffer& rDst, const BitmapBuffer& rSrc, const BitmapBuffer& rMsk )
+{
+ TrueColorPixelPtr<SRCFMT> aSrcType; aSrcType.SetRawPtr( rSrc.mpBits );
+
+ // select the matching instantiation for the destination's bitmap format
+ switch (RemoveScanline(rDst.mnFormat))
+ {
+ case ScanlineFormat::N1BitMsbPal:
+ case ScanlineFormat::N8BitPal:
+ break;
+
+ case ScanlineFormat::N32BitTcMask:
+// return ImplBlendToBitmap<ScanlineFormat::N32BitTcMask>( aSrcType, rDst, rSrc, rMsk );
+ break;
+
+ case ScanlineFormat::N24BitTcBgr:
+ return ImplBlendToBitmap<ScanlineFormat::N24BitTcBgr>( aSrcType, rDst, rSrc, rMsk );
+ case ScanlineFormat::N24BitTcRgb:
+ return ImplBlendToBitmap<ScanlineFormat::N24BitTcRgb>( aSrcType, rDst, rSrc, rMsk );
+
+ case ScanlineFormat::N32BitTcAbgr:
+ return ImplBlendToBitmap<ScanlineFormat::N32BitTcAbgr>( aSrcType, rDst, rSrc, rMsk );
+ case ScanlineFormat::N32BitTcArgb:
+ return ImplBlendToBitmap<ScanlineFormat::N32BitTcArgb>( aSrcType, rDst, rSrc, rMsk );
+ case ScanlineFormat::N32BitTcBgra:
+ return ImplBlendToBitmap<ScanlineFormat::N32BitTcBgra>( aSrcType, rDst, rSrc, rMsk );
+ case ScanlineFormat::N32BitTcRgba:
+ return ImplBlendToBitmap<ScanlineFormat::N32BitTcRgba>( aSrcType, rDst, rSrc, rMsk );
+ default: break;
+ }
+
+ static int nNotAccelerated = 0;
+ SAL_WARN_IF( rSrc.mnWidth * rSrc.mnHeight >= 4000 && ++nNotAccelerated == 100,
+ "vcl.gdi",
+ "ImplBlendFromBitmap for not accelerated case (" << std::hex << static_cast<int>(rSrc.mnFormat) << "*" << static_cast<int>(rMsk.mnFormat) << "->" << static_cast<int>(rDst.mnFormat) );
+ return false;
+}
+
+bool ImplFastBitmapBlending( BitmapWriteAccess const & rDstWA,
+ const BitmapReadAccess& rSrcRA, const BitmapReadAccess& rMskRA,
+ const SalTwoRect& rTR )
+{
+ // accelerated blending of paletted bitmaps not implemented yet
+ if( rSrcRA.HasPalette() )
+ return false;
+ if( rDstWA.HasPalette() )
+ return false;
+ // TODO: either get rid of mask's use of 8BIT_PAL or check the palette
+
+ // horizontal mirroring not implemented yet
+ if( rTR.mnDestWidth < 0 )
+ return false;
+ // vertical mirroring
+ if( rTR.mnDestHeight < 0 )
+ // TODO: rDst.mnFormat ^= ScanlineFormat::TopDown;
+ return false;
+
+ // offsetted blending is not implemented yet
+ if( rTR.mnSrcX || rTR.mnSrcY )
+ return false;
+ if( rTR.mnDestX || rTR.mnDestY )
+ return false;
+
+ // stretched blending is not implemented yet
+ if( rTR.mnDestWidth != rTR.mnSrcWidth )
+ return false;
+ if( rTR.mnDestHeight!= rTR.mnSrcHeight )
+ return false;
+
+ // check source image size
+ if( rSrcRA.Width() < rTR.mnSrcX + rTR.mnSrcWidth )
+ return false;
+ if( rSrcRA.Height() < rTR.mnSrcY + rTR.mnSrcHeight )
+ return false;
+
+ // check mask image size
+ if( rMskRA.Width() < rTR.mnSrcX + rTR.mnSrcWidth )
+ return false;
+ if( rMskRA.Height() < rTR.mnSrcY + rTR.mnSrcHeight )
+ if( rMskRA.Height() != 1 )
+ return false;
+
+ // check dest image size
+ if( rDstWA.Width() < rTR.mnDestX + rTR.mnDestWidth )
+ return false;
+ if( rDstWA.Height() < rTR.mnDestY + rTR.mnDestHeight )
+ return false;
+
+ BitmapBuffer& rDst = *rDstWA.ImplGetBitmapBuffer();
+ const BitmapBuffer& rSrc = *rSrcRA.ImplGetBitmapBuffer();
+ const BitmapBuffer& rMsk = *rMskRA.ImplGetBitmapBuffer();
+
+ const ScanlineFormat nSrcFormat = RemoveScanline(rSrc.mnFormat);
+
+ // select the matching instantiation for the source's bitmap format
+ switch( nSrcFormat )
+ {
+ case ScanlineFormat::N1BitMsbPal:
+ break;
+
+ case ScanlineFormat::N32BitTcMask:
+// return ImplBlendFromBitmap<ScanlineFormat::N32BitTcMask>( rDst, rSrc );
+ break;
+
+ case ScanlineFormat::N8BitPal:
+ if(rSrc.maPalette.IsGreyPalette8Bit())
+ return ImplBlendFromBitmap<ScanlineFormat::N8BitPal>( rDst, rSrc, rMsk );
+ break;
+
+ case ScanlineFormat::N24BitTcBgr:
+ return ImplBlendFromBitmap<ScanlineFormat::N24BitTcBgr>( rDst, rSrc, rMsk );
+ case ScanlineFormat::N24BitTcRgb:
+ return ImplBlendFromBitmap<ScanlineFormat::N24BitTcRgb>( rDst, rSrc, rMsk );
+
+ case ScanlineFormat::N32BitTcAbgr:
+ return ImplBlendFromBitmap<ScanlineFormat::N32BitTcAbgr>( rDst, rSrc, rMsk );
+ case ScanlineFormat::N32BitTcArgb:
+ return ImplBlendFromBitmap<ScanlineFormat::N32BitTcArgb>( rDst, rSrc, rMsk );
+ case ScanlineFormat::N32BitTcBgra:
+ return ImplBlendFromBitmap<ScanlineFormat::N32BitTcBgra>( rDst, rSrc, rMsk );
+ case ScanlineFormat::N32BitTcRgba:
+ return ImplBlendFromBitmap<ScanlineFormat::N32BitTcRgba>( rDst, rSrc, rMsk );
+ default: break;
+ }
+
+ static int nNotAccelerated = 0;
+ SAL_WARN_IF( rSrc.mnWidth * rSrc.mnHeight >= 4000 && ++nNotAccelerated == 100,
+ "vcl.gdi",
+ "ImplFastBlend for not accelerated case (" << std::hex << static_cast<int>(rSrc.mnFormat) << "*" << static_cast<int>(rMsk.mnFormat) << "->" << static_cast<int>(rDst.mnFormat) << ")" );
+
+ return false;
+}
+
+bool ImplFastEraseBitmap( BitmapBuffer& rDst, const BitmapColor& rColor )
+{
+ const ScanlineFormat nDstFormat = RemoveScanline(rDst.mnFormat);
+
+ // erasing a bitmap is often just a byte-wise memory fill
+ bool bByteFill = true;
+ sal_uInt8 nFillByte;
+
+ switch( nDstFormat )
+ {
+ case ScanlineFormat::N1BitMsbPal:
+ nFillByte = rColor.GetIndex();
+ nFillByte = static_cast<sal_uInt8>( -(nFillByte & 1) ); // 0x00 or 0xFF
+ break;
+ case ScanlineFormat::N8BitPal:
+ nFillByte = rColor.GetIndex();
+ break;
+
+ case ScanlineFormat::N24BitTcBgr:
+ case ScanlineFormat::N24BitTcRgb:
+ nFillByte = rColor.GetRed();
+ if( (nFillByte != rColor.GetGreen())
+ || (nFillByte != rColor.GetBlue()) )
+ bByteFill = false;
+ break;
+
+ default:
+ bByteFill = false;
+ nFillByte = 0x00;
+ break;
+ }
+
+ if( bByteFill )
+ {
+ tools::Long nByteCount = rDst.mnHeight * rDst.mnScanlineSize;
+ memset( rDst.mpBits, nFillByte, nByteCount );
+ return true;
+ }
+
+ // TODO: handle other bitmap formats
+ switch( nDstFormat )
+ {
+ case ScanlineFormat::N32BitTcMask:
+
+ case ScanlineFormat::N24BitTcBgr:
+ case ScanlineFormat::N24BitTcRgb:
+
+ case ScanlineFormat::N32BitTcAbgr:
+ case ScanlineFormat::N32BitTcArgb:
+ case ScanlineFormat::N32BitTcBgra:
+ case ScanlineFormat::N32BitTcRgba:
+ break;
+
+ default:
+ break;
+ }
+
+ return false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/dibtools.cxx b/vcl/source/bitmap/dibtools.cxx
new file mode 100644
index 0000000000..4eb15ac284
--- /dev/null
+++ b/vcl/source/bitmap/dibtools.cxx
@@ -0,0 +1,1769 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+#include <sal/log.hxx>
+
+#include <cassert>
+
+#include <o3tl/safeint.hxx>
+#include <vcl/dibtools.hxx>
+#include <comphelper/fileformat.h>
+#include <tools/zcodec.hxx>
+#include <tools/stream.hxx>
+#include <tools/fract.hxx>
+#include <tools/helpers.hxx>
+#include <tools/GenericTypeSerializer.hxx>
+#include <unotools/configmgr.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/BitmapWriteAccess.hxx>
+#include <memory>
+
+#define DIBCOREHEADERSIZE ( 12UL )
+#define DIBINFOHEADERSIZE ( sizeof(DIBInfoHeader) )
+#define DIBV5HEADERSIZE ( sizeof(DIBV5Header) )
+
+// - DIBInfoHeader and DIBV5Header
+
+typedef sal_Int32 FXPT2DOT30;
+
+namespace
+{
+
+struct CIEXYZ
+{
+ FXPT2DOT30 aXyzX;
+ FXPT2DOT30 aXyzY;
+ FXPT2DOT30 aXyzZ;
+
+ CIEXYZ()
+ : aXyzX(0),
+ aXyzY(0),
+ aXyzZ(0)
+ {}
+};
+
+struct CIEXYZTriple
+{
+ CIEXYZ aXyzRed;
+ CIEXYZ aXyzGreen;
+ CIEXYZ aXyzBlue;
+
+ CIEXYZTriple()
+ {}
+};
+
+struct DIBInfoHeader
+{
+ sal_uInt32 nSize;
+ sal_Int32 nWidth;
+ sal_Int32 nHeight;
+ sal_uInt16 nPlanes;
+ sal_uInt16 nBitCount;
+ sal_uInt32 nCompression;
+ sal_uInt32 nSizeImage;
+ sal_Int32 nXPelsPerMeter;
+ sal_Int32 nYPelsPerMeter;
+ sal_uInt32 nColsUsed;
+ sal_uInt32 nColsImportant;
+
+ DIBInfoHeader()
+ : nSize(0),
+ nWidth(0),
+ nHeight(0),
+ nPlanes(0),
+ nBitCount(0),
+ nCompression(0),
+ nSizeImage(0),
+ nXPelsPerMeter(0),
+ nYPelsPerMeter(0),
+ nColsUsed(0),
+ nColsImportant(0)
+ {}
+};
+
+struct DIBV5Header : public DIBInfoHeader
+{
+ sal_uInt32 nV5RedMask;
+ sal_uInt32 nV5GreenMask;
+ sal_uInt32 nV5BlueMask;
+ sal_uInt32 nV5AlphaMask;
+ sal_uInt32 nV5CSType;
+ CIEXYZTriple aV5Endpoints;
+ sal_uInt32 nV5GammaRed;
+ sal_uInt32 nV5GammaGreen;
+ sal_uInt32 nV5GammaBlue;
+ sal_uInt32 nV5Intent;
+ sal_uInt32 nV5ProfileData;
+ sal_uInt32 nV5ProfileSize;
+ sal_uInt32 nV5Reserved;
+
+ DIBV5Header()
+ : nV5RedMask(0),
+ nV5GreenMask(0),
+ nV5BlueMask(0),
+ nV5AlphaMask(0),
+ nV5CSType(0),
+ nV5GammaRed(0),
+ nV5GammaGreen(0),
+ nV5GammaBlue(0),
+ nV5Intent(0),
+ nV5ProfileData(0),
+ nV5ProfileSize(0),
+ nV5Reserved(0)
+ {}
+};
+
+vcl::PixelFormat convertToBPP(sal_uInt16 nCount)
+{
+ return (nCount <= 8) ? vcl::PixelFormat::N8_BPP :
+ vcl::PixelFormat::N24_BPP;
+}
+
+bool isBitfieldCompression( ScanlineFormat nScanlineFormat )
+{
+ return ScanlineFormat::N32BitTcMask == nScanlineFormat;
+}
+
+bool ImplReadDIBInfoHeader(SvStream& rIStm, DIBV5Header& rHeader, bool& bTopDown, bool bMSOFormat)
+{
+ if (rIStm.remainingSize() <= 4)
+ return false;
+ // BITMAPINFOHEADER or BITMAPCOREHEADER or BITMAPV5HEADER
+ sal_uInt64 const aStartPos(rIStm.Tell());
+ rIStm.ReadUInt32( rHeader.nSize );
+
+ // BITMAPCOREHEADER
+ if ( rHeader.nSize == DIBCOREHEADERSIZE )
+ {
+ sal_Int16 nTmp16;
+
+ rIStm.ReadInt16( nTmp16 ); rHeader.nWidth = nTmp16;
+ rIStm.ReadInt16( nTmp16 ); rHeader.nHeight = nTmp16;
+ rIStm.ReadUInt16( rHeader.nPlanes );
+ rIStm.ReadUInt16( rHeader.nBitCount );
+ }
+ else if ( bMSOFormat && rHeader.nSize == DIBINFOHEADERSIZE )
+ {
+ sal_Int16 nTmp16(0);
+ rIStm.ReadInt16(nTmp16);
+ rHeader.nWidth = nTmp16;
+ rIStm.ReadInt16(nTmp16);
+ rHeader.nHeight = nTmp16;
+ sal_uInt8 nTmp8(0);
+ rIStm.ReadUChar(nTmp8);
+ rHeader.nPlanes = nTmp8;
+ rIStm.ReadUChar(nTmp8);
+ rHeader.nBitCount = nTmp8;
+ rIStm.ReadInt16(nTmp16);
+ rHeader.nSizeImage = nTmp16;
+ rIStm.ReadInt16(nTmp16);
+ rHeader.nCompression = nTmp16;
+ if ( !rHeader.nSizeImage ) // uncompressed?
+ rHeader.nSizeImage = ((rHeader.nWidth * rHeader.nBitCount + 31) & ~31) / 8 * rHeader.nHeight;
+ rIStm.ReadInt32( rHeader.nXPelsPerMeter );
+ rIStm.ReadInt32( rHeader.nYPelsPerMeter );
+ rIStm.ReadUInt32( rHeader.nColsUsed );
+ rIStm.ReadUInt32( rHeader.nColsImportant );
+ }
+ else
+ {
+ // BITMAPCOREHEADER, BITMAPV5HEADER or unknown. Read as far as possible
+ std::size_t nUsed(sizeof(rHeader.nSize));
+
+ auto readUInt16 = [&nUsed, &rHeader, &rIStm](sal_uInt16 & v) {
+ if (nUsed < rHeader.nSize) {
+ rIStm.ReadUInt16(v);
+ nUsed += sizeof(v);
+ }
+ };
+ auto readInt32 = [&nUsed, &rHeader, &rIStm](sal_Int32 & v) {
+ if (nUsed < rHeader.nSize) {
+ rIStm.ReadInt32(v);
+ nUsed += sizeof(v);
+ }
+ };
+ auto readUInt32 = [&nUsed, &rHeader, &rIStm](sal_uInt32 & v) {
+ if (nUsed < rHeader.nSize) {
+ rIStm.ReadUInt32(v);
+ nUsed += sizeof(v);
+ }
+ };
+
+ // read DIBInfoHeader entries
+ readInt32( rHeader.nWidth );
+ readInt32( rHeader.nHeight );
+ readUInt16( rHeader.nPlanes );
+ readUInt16( rHeader.nBitCount );
+ readUInt32( rHeader.nCompression );
+ readUInt32( rHeader.nSizeImage );
+ readInt32( rHeader.nXPelsPerMeter );
+ readInt32( rHeader.nYPelsPerMeter );
+ readUInt32( rHeader.nColsUsed );
+ readUInt32( rHeader.nColsImportant );
+
+ // read DIBV5HEADER members
+ readUInt32( rHeader.nV5RedMask );
+ readUInt32( rHeader.nV5GreenMask );
+ readUInt32( rHeader.nV5BlueMask );
+ readUInt32( rHeader.nV5AlphaMask );
+ readUInt32( rHeader.nV5CSType );
+
+ // read contained CIEXYZTriple's
+ readInt32( rHeader.aV5Endpoints.aXyzRed.aXyzX );
+ readInt32( rHeader.aV5Endpoints.aXyzRed.aXyzY );
+ readInt32( rHeader.aV5Endpoints.aXyzRed.aXyzZ );
+ readInt32( rHeader.aV5Endpoints.aXyzGreen.aXyzX );
+ readInt32( rHeader.aV5Endpoints.aXyzGreen.aXyzY );
+ readInt32( rHeader.aV5Endpoints.aXyzGreen.aXyzZ );
+ readInt32( rHeader.aV5Endpoints.aXyzBlue.aXyzX );
+ readInt32( rHeader.aV5Endpoints.aXyzBlue.aXyzY );
+ readInt32( rHeader.aV5Endpoints.aXyzBlue.aXyzZ );
+
+ readUInt32( rHeader.nV5GammaRed );
+ readUInt32( rHeader.nV5GammaGreen );
+ readUInt32( rHeader.nV5GammaBlue );
+ readUInt32( rHeader.nV5Intent );
+ readUInt32( rHeader.nV5ProfileData );
+ readUInt32( rHeader.nV5ProfileSize );
+ readUInt32( rHeader.nV5Reserved );
+
+ // Read color mask. An additional 12 bytes of color bitfields follow the info header (WinBMPv3-NT)
+ sal_uInt32 nColorMask = 0;
+ if (BITFIELDS == rHeader.nCompression && DIBINFOHEADERSIZE == rHeader.nSize)
+ {
+ rIStm.ReadUInt32( rHeader.nV5RedMask );
+ rIStm.ReadUInt32( rHeader.nV5GreenMask );
+ rIStm.ReadUInt32( rHeader.nV5BlueMask );
+ nColorMask = 12;
+ }
+
+ // seek to EndPos
+ if (!checkSeek(rIStm, aStartPos + rHeader.nSize + nColorMask))
+ return false;
+ }
+
+ if (!rIStm.good() || rHeader.nHeight == SAL_MIN_INT32)
+ return false;
+
+ if ( rHeader.nHeight < 0 )
+ {
+ bTopDown = true;
+ rHeader.nHeight *= -1;
+ }
+ else
+ {
+ bTopDown = false;
+ }
+
+ if ( rHeader.nWidth < 0 || rHeader.nXPelsPerMeter < 0 || rHeader.nYPelsPerMeter < 0 )
+ {
+ rIStm.SetError( SVSTREAM_FILEFORMAT_ERROR );
+ }
+
+ // #144105# protect a little against damaged files
+ assert(rHeader.nHeight >= 0);
+ if (rHeader.nHeight != 0 && rHeader.nWidth >= 0
+ && (rHeader.nSizeImage / 16 / static_cast<sal_uInt32>(rHeader.nHeight)
+ > o3tl::make_unsigned(rHeader.nWidth)))
+ {
+ rHeader.nSizeImage = 0;
+ }
+
+
+ if (rHeader.nPlanes != 1)
+ return false;
+
+ if (rHeader.nBitCount != 0 && rHeader.nBitCount != 1 &&
+ rHeader.nBitCount != 4 && rHeader.nBitCount != 8 &&
+ rHeader.nBitCount != 16 && rHeader.nBitCount != 24 &&
+ rHeader.nBitCount != 32)
+ {
+ return false;
+ }
+
+ return rIStm.good();
+}
+
+bool ImplReadDIBPalette(SvStream& rIStm, BitmapPalette& rPal, bool bQuad)
+{
+ const sal_uInt16 nColors = rPal.GetEntryCount();
+ const sal_uLong nPalSize = nColors * ( bQuad ? 4UL : 3UL );
+ BitmapColor aPalColor;
+
+ std::unique_ptr<sal_uInt8[]> pEntries(new sal_uInt8[ nPalSize ]);
+ if (rIStm.ReadBytes(pEntries.get(), nPalSize) != nPalSize)
+ {
+ return false;
+ }
+
+ sal_uInt8* pTmpEntry = pEntries.get();
+ for( sal_uInt16 i = 0; i < nColors; i++ )
+ {
+ aPalColor.SetBlue( *pTmpEntry++ );
+ aPalColor.SetGreen( *pTmpEntry++ );
+ aPalColor.SetRed( *pTmpEntry++ );
+
+ if( bQuad )
+ pTmpEntry++;
+
+ rPal[i] = aPalColor;
+ }
+
+ return rIStm.GetError() == ERRCODE_NONE;
+}
+
+BitmapColor SanitizePaletteIndex(sal_uInt8 nIndex, BitmapPalette& rPalette)
+{
+ const sal_uInt16 nPaletteEntryCount = rPalette.GetEntryCount();
+ if (nPaletteEntryCount && nIndex >= nPaletteEntryCount)
+ {
+ auto nSanitizedIndex = nIndex % nPaletteEntryCount;
+ SAL_WARN_IF(nIndex != nSanitizedIndex, "vcl", "invalid colormap index: "
+ << static_cast<unsigned int>(nIndex) << ", colormap len is: "
+ << nPaletteEntryCount);
+ nIndex = nSanitizedIndex;
+ }
+ return BitmapColor(nIndex);
+}
+
+bool ImplDecodeRLE(sal_uInt8* pBuffer, DIBV5Header const & rHeader, BitmapWriteAccess& rAcc, BitmapPalette& rPalette, bool bRLE4)
+{
+ Scanline pRLE = pBuffer;
+ Scanline pEndRLE = pBuffer + rHeader.nSizeImage;
+ tools::Long nY = rHeader.nHeight - 1;
+ const sal_uLong nWidth = rAcc.Width();
+ sal_uLong nCountByte;
+ sal_uLong nRunByte;
+ sal_uLong nX = 0;
+ sal_uInt8 cTmp;
+ bool bEndDecoding = false;
+
+ do
+ {
+ if (pRLE == pEndRLE)
+ return false;
+ if( ( nCountByte = *pRLE++ ) == 0 )
+ {
+ if (pRLE == pEndRLE)
+ return false;
+ nRunByte = *pRLE++;
+
+ if( nRunByte > 2 )
+ {
+ Scanline pScanline = rAcc.GetScanline(nY);
+ if( bRLE4 )
+ {
+ nCountByte = nRunByte >> 1;
+
+ for( sal_uLong i = 0; i < nCountByte; i++ )
+ {
+ if (pRLE == pEndRLE)
+ return false;
+
+ cTmp = *pRLE++;
+
+ if( nX < nWidth )
+ rAcc.SetPixelOnData(pScanline, nX++, SanitizePaletteIndex(cTmp >> 4, rPalette));
+
+ if( nX < nWidth )
+ rAcc.SetPixelOnData(pScanline, nX++, SanitizePaletteIndex(cTmp & 0x0f, rPalette));
+ }
+
+ if( nRunByte & 1 )
+ {
+ if (pRLE == pEndRLE)
+ return false;
+
+ if( nX < nWidth )
+ rAcc.SetPixelOnData(pScanline, nX++, SanitizePaletteIndex(*pRLE >> 4, rPalette));
+
+ pRLE++;
+ }
+
+ if( ( ( nRunByte + 1 ) >> 1 ) & 1 )
+ {
+ if (pRLE == pEndRLE)
+ return false;
+
+ pRLE++;
+ }
+ }
+ else
+ {
+ for( sal_uLong i = 0; i < nRunByte; i++ )
+ {
+ if (pRLE == pEndRLE)
+ return false;
+
+ if( nX < nWidth )
+ rAcc.SetPixelOnData(pScanline, nX++, SanitizePaletteIndex(*pRLE, rPalette));
+
+ pRLE++;
+ }
+
+ if( nRunByte & 1 )
+ {
+ if (pRLE == pEndRLE)
+ return false;
+
+ pRLE++;
+ }
+ }
+ }
+ else if( !nRunByte )
+ {
+ nY--;
+ nX = 0;
+ }
+ else if( nRunByte == 1 )
+ bEndDecoding = true;
+ else
+ {
+ if (pRLE == pEndRLE)
+ return false;
+
+ nX += *pRLE++;
+
+ if (pRLE == pEndRLE)
+ return false;
+
+ nY -= *pRLE++;
+ }
+ }
+ else
+ {
+ if (pRLE == pEndRLE)
+ return false;
+ cTmp = *pRLE++;
+
+ Scanline pScanline = rAcc.GetScanline(nY);
+ if( bRLE4 )
+ {
+ nRunByte = nCountByte >> 1;
+
+ for (sal_uLong i = 0; i < nRunByte && nX < nWidth; ++i)
+ {
+ rAcc.SetPixelOnData(pScanline, nX++, SanitizePaletteIndex(cTmp >> 4, rPalette));
+ if( nX < nWidth )
+ rAcc.SetPixelOnData(pScanline, nX++, SanitizePaletteIndex(cTmp & 0x0f, rPalette));
+ }
+
+ if( ( nCountByte & 1 ) && ( nX < nWidth ) )
+ rAcc.SetPixelOnData(pScanline, nX++, SanitizePaletteIndex(cTmp >> 4, rPalette));
+ }
+ else
+ {
+ for (sal_uLong i = 0; i < nCountByte && nX < nWidth; ++i)
+ rAcc.SetPixelOnData(pScanline, nX++, SanitizePaletteIndex(cTmp, rPalette));
+ }
+ }
+ }
+ while (!bEndDecoding && (nY >= 0));
+
+ return true;
+}
+
+bool ImplReadDIBBits(SvStream& rIStm, DIBV5Header& rHeader, BitmapWriteAccess& rAcc, BitmapPalette& rPalette, BitmapWriteAccess* pAccAlpha,
+ bool bTopDown, bool& rAlphaUsed, const sal_uInt64 nAlignedWidth)
+{
+ sal_uInt32 nRMask(( rHeader.nBitCount == 16 ) ? 0x00007c00UL : 0x00ff0000UL);
+ sal_uInt32 nGMask(( rHeader.nBitCount == 16 ) ? 0x000003e0UL : 0x0000ff00UL);
+ sal_uInt32 nBMask(( rHeader.nBitCount == 16 ) ? 0x0000001fUL : 0x000000ffUL);
+ bool bNative(false);
+ bool bTCMask(!pAccAlpha && ((16 == rHeader.nBitCount) || (32 == rHeader.nBitCount)));
+ bool bRLE((RLE_8 == rHeader.nCompression && 8 == rHeader.nBitCount) || (RLE_4 == rHeader.nCompression && 4 == rHeader.nBitCount));
+
+ // Is native format?
+ switch(rAcc.GetScanlineFormat())
+ {
+ case ScanlineFormat::N1BitMsbPal:
+ case ScanlineFormat::N24BitTcBgr:
+ {
+ // we can't trust arbitrary-sourced index based formats to have correct indexes, so we exclude the pal formats
+ // from raw read and force checking their colormap indexes
+ bNative = ( ( rAcc.IsBottomUp() != bTopDown ) && !bRLE && !bTCMask && ( rAcc.GetScanlineSize() == nAlignedWidth ) );
+ break;
+ }
+
+ default:
+ {
+ break;
+ }
+ }
+
+ // Read data
+ if (bNative)
+ {
+ if (nAlignedWidth
+ > std::numeric_limits<std::size_t>::max() / rHeader.nHeight)
+ {
+ return false;
+ }
+ std::size_t n = nAlignedWidth * rHeader.nHeight;
+ if (rIStm.ReadBytes(rAcc.GetBuffer(), n) != n)
+ {
+ return false;
+ }
+ }
+ else
+ {
+ if (rHeader.nV5RedMask > 0)
+ nRMask = rHeader.nV5RedMask;
+ if (rHeader.nV5GreenMask > 0)
+ nGMask = rHeader.nV5GreenMask;
+ if (rHeader.nV5BlueMask > 0)
+ nBMask = rHeader.nV5BlueMask;
+
+ const tools::Long nWidth(rHeader.nWidth);
+ const tools::Long nHeight(rHeader.nHeight);
+ tools::Long nResult = 0;
+ if (utl::ConfigManager::IsFuzzing() && (o3tl::checked_multiply(nWidth, nHeight, nResult) || nResult > 4000000))
+ return false;
+
+ if (bRLE)
+ {
+ if(!rHeader.nSizeImage)
+ {
+ rHeader.nSizeImage = rIStm.remainingSize();
+ }
+
+ if (rHeader.nSizeImage > rIStm.remainingSize())
+ return false;
+ std::vector<sal_uInt8> aBuffer(rHeader.nSizeImage);
+ if (rIStm.ReadBytes(aBuffer.data(), rHeader.nSizeImage) != rHeader.nSizeImage)
+ return false;
+ if (!ImplDecodeRLE(aBuffer.data(), rHeader, rAcc, rPalette, RLE_4 == rHeader.nCompression))
+ return false;
+ }
+ else
+ {
+ if (nAlignedWidth > rIStm.remainingSize())
+ {
+ // ofz#11188 avoid timeout
+ // all following paths will enter a case statement, and nCount
+ // is always at least 1, so we can check here before allocation
+ // if at least one row can be read
+ return false;
+ }
+ std::vector<sal_uInt8> aBuf(nAlignedWidth);
+
+ const tools::Long nI(bTopDown ? 1 : -1);
+ tools::Long nY(bTopDown ? 0 : nHeight - 1);
+ tools::Long nCount(nHeight);
+
+ switch(rHeader.nBitCount)
+ {
+ case 1:
+ {
+ for( ; nCount--; nY += nI )
+ {
+ sal_uInt8 * pTmp = aBuf.data();
+ if (rIStm.ReadBytes(pTmp, nAlignedWidth)
+ != nAlignedWidth)
+ {
+ return false;
+ }
+ sal_uInt8 cTmp = *pTmp++;
+ Scanline pScanline = rAcc.GetScanline(nY);
+ for( tools::Long nX = 0, nShift = 8; nX < nWidth; nX++ )
+ {
+ if( !nShift )
+ {
+ nShift = 8;
+ cTmp = *pTmp++;
+ }
+
+ auto nIndex = (cTmp >> --nShift) & 1;
+ rAcc.SetPixelOnData(pScanline, nX, SanitizePaletteIndex(nIndex, rPalette));
+ }
+ }
+ }
+ break;
+
+ case 4:
+ {
+ for( ; nCount--; nY += nI )
+ {
+ sal_uInt8 * pTmp = aBuf.data();
+ if (rIStm.ReadBytes(pTmp, nAlignedWidth)
+ != nAlignedWidth)
+ {
+ return false;
+ }
+ sal_uInt8 cTmp = *pTmp++;
+ Scanline pScanline = rAcc.GetScanline(nY);
+ for( tools::Long nX = 0, nShift = 2; nX < nWidth; nX++ )
+ {
+ if( !nShift )
+ {
+ nShift = 2;
+ cTmp = *pTmp++;
+ }
+
+ auto nIndex = (cTmp >> ( --nShift << 2 ) ) & 0x0f;
+ rAcc.SetPixelOnData(pScanline, nX, SanitizePaletteIndex(nIndex, rPalette));
+ }
+ }
+ }
+ break;
+
+ case 8:
+ {
+ for( ; nCount--; nY += nI )
+ {
+ sal_uInt8 * pTmp = aBuf.data();
+ if (rIStm.ReadBytes(pTmp, nAlignedWidth)
+ != nAlignedWidth)
+ {
+ return false;
+ }
+
+ Scanline pScanline = rAcc.GetScanline(nY);
+ for( tools::Long nX = 0; nX < nWidth; nX++ )
+ {
+ auto nIndex = *pTmp++;
+ rAcc.SetPixelOnData(pScanline, nX, SanitizePaletteIndex(nIndex, rPalette));
+ }
+ }
+ }
+ break;
+
+ case 16:
+ {
+ ColorMaskElement aRedMask(nRMask);
+ if (!aRedMask.CalcMaskShift())
+ return false;
+ ColorMaskElement aGreenMask(nGMask);
+ if (!aGreenMask.CalcMaskShift())
+ return false;
+ ColorMaskElement aBlueMask(nBMask);
+ if (!aBlueMask.CalcMaskShift())
+ return false;
+
+ ColorMask aMask(aRedMask, aGreenMask, aBlueMask);
+ BitmapColor aColor;
+
+ for( ; nCount--; nY += nI )
+ {
+ sal_uInt16 * pTmp16 = reinterpret_cast<sal_uInt16*>(aBuf.data());
+ if (rIStm.ReadBytes(pTmp16, nAlignedWidth)
+ != nAlignedWidth)
+ {
+ return false;
+ }
+
+ Scanline pScanline = rAcc.GetScanline(nY);
+ for( tools::Long nX = 0; nX < nWidth; nX++ )
+ {
+ aMask.GetColorFor16BitLSB( aColor, reinterpret_cast<sal_uInt8*>(pTmp16++) );
+ rAcc.SetPixelOnData(pScanline, nX, aColor);
+ }
+ }
+ }
+ break;
+
+ case 24:
+ {
+ BitmapColor aPixelColor;
+
+ for( ; nCount--; nY += nI )
+ {
+ sal_uInt8* pTmp = aBuf.data();
+ if (rIStm.ReadBytes(pTmp, nAlignedWidth)
+ != nAlignedWidth)
+ {
+ return false;
+ }
+
+ Scanline pScanline = rAcc.GetScanline(nY);
+ for( tools::Long nX = 0; nX < nWidth; nX++ )
+ {
+ aPixelColor.SetBlue( *pTmp++ );
+ aPixelColor.SetGreen( *pTmp++ );
+ aPixelColor.SetRed( *pTmp++ );
+ rAcc.SetPixelOnData(pScanline, nX, aPixelColor);
+ }
+ }
+ }
+ break;
+
+ case 32:
+ {
+ ColorMaskElement aRedMask(nRMask);
+ if (!aRedMask.CalcMaskShift())
+ return false;
+ ColorMaskElement aGreenMask(nGMask);
+ if (!aGreenMask.CalcMaskShift())
+ return false;
+ ColorMaskElement aBlueMask(nBMask);
+ if (!aBlueMask.CalcMaskShift())
+ return false;
+ ColorMask aMask(aRedMask, aGreenMask, aBlueMask);
+
+ BitmapColor aColor;
+ sal_uInt32* pTmp32;
+
+ if(pAccAlpha)
+ {
+ sal_uInt8 aAlpha;
+
+ for( ; nCount--; nY += nI )
+ {
+ pTmp32 = reinterpret_cast<sal_uInt32*>(aBuf.data());
+ if (rIStm.ReadBytes(pTmp32, nAlignedWidth)
+ != nAlignedWidth)
+ {
+ return false;
+ }
+
+ Scanline pScanline = rAcc.GetScanline(nY);
+ Scanline pAlphaScanline = pAccAlpha->GetScanline(nY);
+ for( tools::Long nX = 0; nX < nWidth; nX++ )
+ {
+ aMask.GetColorAndAlphaFor32Bit( aColor, aAlpha, reinterpret_cast<sal_uInt8*>(pTmp32++) );
+ rAcc.SetPixelOnData(pScanline, nX, aColor);
+ pAccAlpha->SetPixelOnData(pAlphaScanline, nX, BitmapColor(sal_uInt8(0xff) - aAlpha));
+ rAlphaUsed |= 0xff != aAlpha;
+ }
+ }
+ }
+ else
+ {
+ for( ; nCount--; nY += nI )
+ {
+ pTmp32 = reinterpret_cast<sal_uInt32*>(aBuf.data());
+ if (rIStm.ReadBytes(pTmp32, nAlignedWidth)
+ != nAlignedWidth)
+ {
+ return false;
+ }
+
+ Scanline pScanline = rAcc.GetScanline(nY);
+ for( tools::Long nX = 0; nX < nWidth; nX++ )
+ {
+ aMask.GetColorFor32Bit( aColor, reinterpret_cast<sal_uInt8*>(pTmp32++) );
+ rAcc.SetPixelOnData(pScanline, nX, aColor);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return rIStm.GetError() == ERRCODE_NONE;
+}
+
+bool ImplReadDIBBody(SvStream& rIStm, Bitmap& rBmp, AlphaMask* pBmpAlpha, sal_uInt64 nOffset, bool bMSOFormat)
+{
+ DIBV5Header aHeader;
+ const sal_uInt64 nStmPos = rIStm.Tell();
+ bool bTopDown(false);
+
+ if (!ImplReadDIBInfoHeader(rIStm, aHeader, bTopDown, bMSOFormat))
+ return false;
+
+ //BI_BITCOUNT_0 jpeg/png is unsupported
+ if (aHeader.nBitCount == 0)
+ return false;
+
+ if (aHeader.nWidth <= 0 || aHeader.nHeight <= 0)
+ return false;
+
+ // In case ImplReadDIB() didn't call ImplReadDIBFileHeader() before
+ // this method, nOffset is 0, that's OK.
+ if (nOffset && aHeader.nSize > nOffset)
+ {
+ // Header size claims to extend into the image data.
+ // Looks like an error.
+ return false;
+ }
+
+ sal_uInt16 nColors(0);
+ SvStream* pIStm;
+ std::unique_ptr<SvMemoryStream> pMemStm;
+ std::vector<sal_uInt8> aData;
+
+ if (aHeader.nBitCount <= 8)
+ {
+ if(aHeader.nColsUsed)
+ {
+ nColors = static_cast<sal_uInt16>(aHeader.nColsUsed);
+ }
+ else
+ {
+ nColors = ( 1 << aHeader.nBitCount );
+ }
+ }
+
+ if (ZCOMPRESS == aHeader.nCompression)
+ {
+ sal_uInt32 nCodedSize(0);
+ sal_uInt32 nUncodedSize(0);
+
+ // read coding information
+ rIStm.ReadUInt32( nCodedSize ).ReadUInt32( nUncodedSize ).ReadUInt32( aHeader.nCompression );
+ if (nCodedSize > rIStm.remainingSize())
+ nCodedSize = sal_uInt32(rIStm.remainingSize());
+
+ pMemStm.reset(new SvMemoryStream);
+ // There may be bytes left over or the codec might read more than
+ // necessary. So to preserve the correctness of the source stream copy
+ // the encoded block
+ pMemStm->WriteStream(rIStm, nCodedSize);
+ pMemStm->Seek(0);
+
+ size_t nSizeInc(4 * pMemStm->remainingSize());
+ if (nUncodedSize < nSizeInc)
+ nSizeInc = nUncodedSize;
+
+ if (nSizeInc > 0)
+ {
+ // decode buffer
+ ZCodec aCodec;
+ aCodec.BeginCompression();
+ aData.resize(nSizeInc);
+ size_t nDataPos(0);
+ while (nUncodedSize > nDataPos)
+ {
+ assert(aData.size() > nDataPos);
+ const size_t nToRead(std::min<size_t>(nUncodedSize - nDataPos, aData.size() - nDataPos));
+ assert(nToRead > 0);
+ assert(!aData.empty());
+ const tools::Long nRead = aCodec.Read(*pMemStm, aData.data() + nDataPos, sal_uInt32(nToRead));
+ if (nRead > 0)
+ {
+ nDataPos += static_cast<tools::ULong>(nRead);
+ // we haven't read everything yet: resize buffer and continue
+ if (nDataPos < nUncodedSize)
+ aData.resize(aData.size() + nSizeInc);
+ }
+ else
+ {
+ break;
+ }
+ }
+ // truncate the data buffer to actually read size
+ aData.resize(nDataPos);
+ // set the real uncoded size
+ nUncodedSize = sal_uInt32(aData.size());
+ aCodec.EndCompression();
+ }
+
+ if (aData.empty())
+ {
+ // add something so we can take address of the first element
+ aData.resize(1);
+ nUncodedSize = 0;
+ }
+
+ // set decoded bytes to memory stream,
+ // from which we will read the bitmap data
+ pMemStm.reset(new SvMemoryStream);
+ pIStm = pMemStm.get();
+ assert(!aData.empty());
+ pMemStm->SetBuffer(aData.data(), nUncodedSize, nUncodedSize);
+ nOffset = 0;
+ }
+ else
+ {
+ pIStm = &rIStm;
+ }
+
+ // read palette
+ BitmapPalette aPalette;
+ if (nColors)
+ {
+ aPalette.SetEntryCount(nColors);
+ ImplReadDIBPalette(*pIStm, aPalette, aHeader.nSize != DIBCOREHEADERSIZE);
+ }
+
+ if (pIStm->GetError())
+ return false;
+
+ if (nOffset)
+ {
+ // It is problematic to seek backwards. We are at the
+ // end of BITMAPINFOHEADER or 12 bytes further in case
+ // of WinBMPv3-NT format. It is possible to seek forward
+ // though because a gap may be there.
+ sal_Int64 nSeekRel = nOffset - (pIStm->Tell() - nStmPos);
+ if (nSeekRel > 0)
+ pIStm->SeekRel(nSeekRel);
+ }
+
+ const sal_Int64 nBitsPerLine (static_cast<sal_Int64>(aHeader.nWidth) * static_cast<sal_Int64>(aHeader.nBitCount));
+ if (nBitsPerLine > SAL_MAX_UINT32)
+ return false;
+ const sal_uInt64 nAlignedWidth(AlignedWidth4Bytes(static_cast<sal_uLong>(nBitsPerLine)));
+
+ switch (aHeader.nCompression)
+ {
+ case RLE_8:
+ {
+ if (aHeader.nBitCount != 8)
+ return false;
+ // (partially) check the image dimensions to avoid potential large bitmap allocation if the input is damaged
+ sal_uInt64 nMaxWidth = pIStm->remainingSize();
+ nMaxWidth *= 256; //assume generous compression ratio
+ nMaxWidth /= aHeader.nHeight;
+ if (nMaxWidth < o3tl::make_unsigned(aHeader.nWidth))
+ return false;
+ break;
+ }
+ case RLE_4:
+ {
+ if (aHeader.nBitCount != 4)
+ return false;
+ sal_uInt64 nMaxWidth = pIStm->remainingSize();
+ nMaxWidth *= 512; //assume generous compression ratio
+ nMaxWidth /= aHeader.nHeight;
+ if (nMaxWidth < o3tl::make_unsigned(aHeader.nWidth))
+ return false;
+ break;
+ }
+ default:
+ // tdf#122958 invalid compression value used
+ if (aHeader.nCompression & 0x000F)
+ {
+ // lets assume that there was an error in the generating application
+ // and allow through as COMPRESS_NONE if the bottom byte is 0
+ SAL_WARN( "vcl", "bad bmp compression scheme: " << aHeader.nCompression << ", rejecting bmp");
+ return false;
+ }
+ else
+ SAL_WARN( "vcl", "bad bmp compression scheme: " << aHeader.nCompression << ", assuming meant to be COMPRESS_NONE");
+ [[fallthrough]];
+ case BITFIELDS:
+ case ZCOMPRESS:
+ case COMPRESS_NONE:
+ {
+ // (partially) check the image dimensions to avoid potential large bitmap allocation if the input is damaged
+ sal_uInt64 nMaxWidth = pIStm->remainingSize();
+ nMaxWidth /= aHeader.nHeight;
+ if (nMaxWidth < nAlignedWidth)
+ return false;
+ break;
+ }
+ }
+
+ const Size aSizePixel(aHeader.nWidth, aHeader.nHeight);
+ AlphaMask aNewBmpAlpha;
+ BitmapScopedWriteAccess pAccAlpha;
+ bool bAlphaPossible(pBmpAlpha && aHeader.nBitCount == 32);
+
+ if (bAlphaPossible)
+ {
+ const bool bRedSet(0 != aHeader.nV5RedMask);
+ const bool bGreenSet(0 != aHeader.nV5GreenMask);
+ const bool bBlueSet(0 != aHeader.nV5BlueMask);
+
+ // some clipboard entries have alpha mask on zero to say that there is
+ // no alpha; do only use this when the other masks are set. The MS docu
+ // says that masks are only to be set when bV5Compression is set to
+ // BI_BITFIELDS, but there seem to exist a wild variety of usages...
+ if((bRedSet || bGreenSet || bBlueSet) && (0 == aHeader.nV5AlphaMask))
+ {
+ bAlphaPossible = false;
+ }
+ }
+
+ if (bAlphaPossible)
+ {
+ aNewBmpAlpha = AlphaMask(aSizePixel);
+ pAccAlpha = aNewBmpAlpha;
+ }
+
+ vcl::PixelFormat ePixelFormat(convertToBPP(aHeader.nBitCount));
+ const BitmapPalette* pPal = &aPalette;
+ //ofz#948 match the surrounding logic of case TransparentType::Bitmap of
+ //ReadDIBBitmapEx but do it while reading for performance
+
+ Bitmap aNewBmp(aSizePixel, ePixelFormat, pPal);
+ BitmapScopedWriteAccess pAcc(aNewBmp);
+ if (!pAcc)
+ return false;
+ if (pAcc->Width() != aHeader.nWidth || pAcc->Height() != aHeader.nHeight)
+ {
+ return false;
+ }
+
+ // read bits
+ bool bAlphaUsed(false);
+ bool bRet = ImplReadDIBBits(*pIStm, aHeader, *pAcc, aPalette, pAccAlpha.get(), bTopDown, bAlphaUsed, nAlignedWidth);
+
+ if (bRet && aHeader.nXPelsPerMeter && aHeader.nYPelsPerMeter)
+ {
+ MapMode aMapMode(
+ MapUnit::MapMM,
+ Point(),
+ Fraction(1000, aHeader.nXPelsPerMeter),
+ Fraction(1000, aHeader.nYPelsPerMeter));
+
+ aNewBmp.SetPrefMapMode(aMapMode);
+ aNewBmp.SetPrefSize(Size(aHeader.nWidth, aHeader.nHeight));
+ }
+
+ pAcc.reset();
+
+ if (bAlphaPossible)
+ {
+ pAccAlpha.reset();
+
+ if(!bAlphaUsed)
+ {
+ bAlphaPossible = false;
+ }
+ }
+
+ if (bRet)
+ {
+ rBmp = aNewBmp;
+
+ if(bAlphaPossible)
+ {
+ *pBmpAlpha = aNewBmpAlpha;
+ }
+ }
+
+ return bRet;
+}
+
+bool ImplReadDIBFileHeader( SvStream& rIStm, sal_uLong& rOffset )
+{
+ bool bRet = false;
+
+ const sal_uInt64 nStreamLength = rIStm.TellEnd();
+
+ sal_uInt16 nTmp16 = 0;
+ rIStm.ReadUInt16( nTmp16 );
+
+ if ( ( 0x4D42 == nTmp16 ) || ( 0x4142 == nTmp16 ) )
+ {
+ sal_uInt32 nTmp32(0);
+ if ( 0x4142 == nTmp16 )
+ {
+ rIStm.SeekRel( 12 );
+ rIStm.ReadUInt16( nTmp16 );
+ rIStm.SeekRel( 8 );
+ rIStm.ReadUInt32( nTmp32 );
+ rOffset = nTmp32 - 28;
+ bRet = ( 0x4D42 == nTmp16 );
+ }
+ else // 0x4D42 == nTmp16, 'MB' from BITMAPFILEHEADER
+ {
+ rIStm.SeekRel( 8 ); // we are on bfSize member of BITMAPFILEHEADER, forward to bfOffBits
+ rIStm.ReadUInt32( nTmp32 ); // read bfOffBits
+ rOffset = nTmp32 - 14; // adapt offset by sizeof(BITMAPFILEHEADER)
+ bRet = rIStm.GetError() == ERRCODE_NONE;
+ }
+
+ if ( rOffset >= nStreamLength )
+ {
+ // Offset claims that image starts past the end of the
+ // stream. Unlikely.
+ rIStm.SetError( SVSTREAM_FILEFORMAT_ERROR );
+ bRet = false;
+ }
+ }
+ else
+ rIStm.SetError( SVSTREAM_FILEFORMAT_ERROR );
+
+ return bRet;
+}
+
+bool ImplWriteDIBPalette( SvStream& rOStm, BitmapReadAccess const & rAcc )
+{
+ const sal_uInt16 nColors = rAcc.GetPaletteEntryCount();
+ const sal_uLong nPalSize = nColors * 4UL;
+ std::unique_ptr<sal_uInt8[]> pEntries(new sal_uInt8[ nPalSize ]);
+ sal_uInt8* pTmpEntry = pEntries.get();
+
+ for( sal_uInt16 i = 0; i < nColors; i++ )
+ {
+ const BitmapColor& rPalColor = rAcc.GetPaletteColor( i );
+
+ *pTmpEntry++ = rPalColor.GetBlue();
+ *pTmpEntry++ = rPalColor.GetGreen();
+ *pTmpEntry++ = rPalColor.GetRed();
+ *pTmpEntry++ = 0;
+ }
+
+ rOStm.WriteBytes( pEntries.get(), nPalSize );
+
+ return rOStm.GetError() == ERRCODE_NONE;
+}
+
+bool ImplWriteRLE( SvStream& rOStm, BitmapReadAccess const & rAcc, bool bRLE4 )
+{
+ const sal_uLong nWidth = rAcc.Width();
+ const sal_uLong nHeight = rAcc.Height();
+ sal_uLong nX;
+ sal_uLong nSaveIndex;
+ sal_uLong nCount;
+ sal_uLong nBufCount;
+ std::vector<sal_uInt8> aBuf(( nWidth << 1 ) + 2);
+ sal_uInt8 cPix;
+ sal_uInt8 cLast;
+ bool bFound;
+
+ for ( tools::Long nY = nHeight - 1; nY >= 0; nY-- )
+ {
+ sal_uInt8* pTmp = aBuf.data();
+ nX = nBufCount = 0;
+ Scanline pScanline = rAcc.GetScanline( nY );
+
+ while( nX < nWidth )
+ {
+ nCount = 1;
+ cPix = rAcc.GetIndexFromData( pScanline, nX++ );
+
+ while( ( nX < nWidth ) && ( nCount < 255 )
+ && ( cPix == rAcc.GetIndexFromData( pScanline, nX ) ) )
+ {
+ nX++;
+ nCount++;
+ }
+
+ if ( nCount > 1 )
+ {
+ *pTmp++ = static_cast<sal_uInt8>(nCount);
+ *pTmp++ = ( bRLE4 ? ( ( cPix << 4 ) | cPix ) : cPix );
+ nBufCount += 2;
+ }
+ else
+ {
+ cLast = cPix;
+ nSaveIndex = nX - 1;
+ bFound = false;
+
+ while( ( nX < nWidth ) && ( nCount < 256 ) )
+ {
+ cPix = rAcc.GetIndexFromData( pScanline, nX );
+ if (cPix == cLast)
+ break;
+ nX++; nCount++;
+ cLast = cPix;
+ bFound = true;
+ }
+
+ if ( bFound )
+ nX--;
+
+ if ( nCount > 3 )
+ {
+ *pTmp++ = 0;
+ *pTmp++ = static_cast<sal_uInt8>(--nCount);
+
+ if( bRLE4 )
+ {
+ for ( sal_uLong i = 0; i < nCount; i++, pTmp++ )
+ {
+ *pTmp = rAcc.GetIndexFromData( pScanline, nSaveIndex++ ) << 4;
+
+ if ( ++i < nCount )
+ *pTmp |= rAcc.GetIndexFromData( pScanline, nSaveIndex++ );
+ }
+
+ nCount = ( nCount + 1 ) >> 1;
+ }
+ else
+ {
+ for( sal_uLong i = 0; i < nCount; i++ )
+ *pTmp++ = rAcc.GetIndexFromData( pScanline, nSaveIndex++ );
+ }
+
+ if ( nCount & 1 )
+ {
+ *pTmp++ = 0;
+ nBufCount += ( nCount + 3 );
+ }
+ else
+ nBufCount += ( nCount + 2 );
+ }
+ else
+ {
+ *pTmp++ = 1;
+ *pTmp++ = rAcc.GetIndexFromData( pScanline, nSaveIndex ) << (bRLE4 ? 4 : 0);
+
+ if ( nCount == 3 )
+ {
+ *pTmp++ = 1;
+ *pTmp++ = rAcc.GetIndexFromData( pScanline, ++nSaveIndex ) << ( bRLE4 ? 4 : 0 );
+ nBufCount += 4;
+ }
+ else
+ nBufCount += 2;
+ }
+ }
+ }
+
+ aBuf[ nBufCount++ ] = 0;
+ aBuf[ nBufCount++ ] = 0;
+
+ rOStm.WriteBytes(aBuf.data(), nBufCount);
+ }
+
+ rOStm.WriteUChar( 0 );
+ rOStm.WriteUChar( 1 );
+
+ return rOStm.GetError() == ERRCODE_NONE;
+}
+
+bool ImplWriteDIBBits(SvStream& rOStm, BitmapReadAccess const & rAcc, sal_uLong nCompression, sal_uInt32& rImageSize)
+{
+ if(BITFIELDS == nCompression)
+ {
+ const ColorMask& rMask = rAcc.GetColorMask();
+ SVBT32 aVal32;
+
+ UInt32ToSVBT32( rMask.GetRedMask(), aVal32 );
+ rOStm.WriteBytes( aVal32, 4UL );
+
+ UInt32ToSVBT32( rMask.GetGreenMask(), aVal32 );
+ rOStm.WriteBytes( aVal32, 4UL );
+
+ UInt32ToSVBT32( rMask.GetBlueMask(), aVal32 );
+ rOStm.WriteBytes( aVal32, 4UL );
+
+ rImageSize = rOStm.Tell();
+
+ if( rAcc.IsBottomUp() )
+ rOStm.WriteBytes(rAcc.GetBuffer(), rAcc.Height() * rAcc.GetScanlineSize());
+ else
+ {
+ for( tools::Long nY = rAcc.Height() - 1, nScanlineSize = rAcc.GetScanlineSize(); nY >= 0; nY-- )
+ rOStm.WriteBytes( rAcc.GetScanline(nY), nScanlineSize );
+ }
+ }
+ else if((RLE_4 == nCompression) || (RLE_8 == nCompression))
+ {
+ rImageSize = rOStm.Tell();
+ ImplWriteRLE( rOStm, rAcc, RLE_4 == nCompression );
+ }
+ else if(!nCompression)
+ {
+ // #i5xxx# Limit bitcount to 24bit, the 32 bit cases are not
+ // handled properly below (would have to set color masks, and
+ // nCompression=BITFIELDS - but color mask is not set for
+ // formats != *_TC_*). Note that this very problem might cause
+ // trouble at other places - the introduction of 32 bit RGBA
+ // bitmaps is relatively recent.
+ // #i59239# discretize bitcount for aligned width to 1,8,24
+ // (other cases are not written below)
+ const auto ePixelFormat(convertToBPP(rAcc.GetBitCount()));
+ const sal_uLong nAlignedWidth(AlignedWidth4Bytes(rAcc.Width() * sal_Int32(ePixelFormat)));
+ bool bNative(false);
+
+ switch(rAcc.GetScanlineFormat())
+ {
+ case ScanlineFormat::N1BitMsbPal:
+ case ScanlineFormat::N8BitPal:
+ case ScanlineFormat::N24BitTcBgr:
+ {
+ if(rAcc.IsBottomUp() && (rAcc.GetScanlineSize() == nAlignedWidth))
+ {
+ bNative = true;
+ }
+
+ break;
+ }
+
+ default:
+ {
+ break;
+ }
+ }
+
+ rImageSize = rOStm.Tell();
+
+ if(bNative)
+ {
+ rOStm.WriteBytes(rAcc.GetBuffer(), nAlignedWidth * rAcc.Height());
+ }
+ else
+ {
+ const tools::Long nWidth(rAcc.Width());
+ const tools::Long nHeight(rAcc.Height());
+ std::vector<sal_uInt8> aBuf(nAlignedWidth);
+ switch(ePixelFormat)
+ {
+ case vcl::PixelFormat::N8_BPP:
+ {
+ for( tools::Long nY = nHeight - 1; nY >= 0; nY-- )
+ {
+ sal_uInt8* pTmp = aBuf.data();
+ Scanline pScanline = rAcc.GetScanline( nY );
+
+ for( tools::Long nX = 0; nX < nWidth; nX++ )
+ *pTmp++ = rAcc.GetIndexFromData( pScanline, nX );
+
+ rOStm.WriteBytes(aBuf.data(), nAlignedWidth);
+ }
+ }
+ break;
+
+ case vcl::PixelFormat::N24_BPP:
+ {
+ //valgrind, zero out the trailing unused alignment bytes
+ size_t nUnusedBytes = nAlignedWidth - nWidth * 3;
+ memset(aBuf.data() + nAlignedWidth - nUnusedBytes, 0, nUnusedBytes);
+ }
+ [[fallthrough]];
+ // #i59239# fallback to 24 bit format, if bitcount is non-default
+ default:
+ {
+ BitmapColor aPixelColor;
+
+ for( tools::Long nY = nHeight - 1; nY >= 0; nY-- )
+ {
+ sal_uInt8* pTmp = aBuf.data();
+
+ for( tools::Long nX = 0; nX < nWidth; nX++ )
+ {
+ // when alpha is used, this may be non-24bit main bitmap, so use GetColor
+ // instead of GetPixel to ensure RGB value
+ aPixelColor = rAcc.GetColor( nY, nX );
+
+ *pTmp++ = aPixelColor.GetBlue();
+ *pTmp++ = aPixelColor.GetGreen();
+ *pTmp++ = aPixelColor.GetRed();
+ }
+
+ rOStm.WriteBytes(aBuf.data(), nAlignedWidth);
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ rImageSize = rOStm.Tell() - rImageSize;
+
+ return (!rOStm.GetError());
+}
+
+bool ImplWriteDIBBody(const Bitmap& rBitmap, SvStream& rOStm, BitmapReadAccess const & rAcc, bool bCompressed)
+{
+ const MapMode aMapPixel(MapUnit::MapPixel);
+ DIBV5Header aHeader;
+ sal_uInt64 nImageSizePos(0);
+ sal_uInt64 nEndPos(0);
+ sal_uInt32 nCompression(COMPRESS_NONE);
+ bool bRet(false);
+
+ aHeader.nSize = DIBINFOHEADERSIZE; // size dependent on CF_DIB type to use
+ aHeader.nWidth = rAcc.Width();
+ aHeader.nHeight = rAcc.Height();
+ aHeader.nPlanes = 1;
+
+ if(isBitfieldCompression(rAcc.GetScanlineFormat()))
+ {
+ aHeader.nBitCount = 32;
+ aHeader.nSizeImage = rAcc.Height() * rAcc.GetScanlineSize();
+ nCompression = BITFIELDS;
+ }
+ else
+ {
+ // #i5xxx# Limit bitcount to 24bit, the 32 bit cases are
+ // not handled properly below (would have to set color
+ // masks, and nCompression=BITFIELDS - but color mask is
+ // not set for formats != *_TC_*). Note that this very
+ // problem might cause trouble at other places - the
+ // introduction of 32 bit RGBA bitmaps is relatively
+ // recent.
+ // #i59239# discretize bitcount to 1,8,24 (other cases
+ // are not written below)
+ const auto ePixelFormat(convertToBPP(rAcc.GetBitCount()));
+ aHeader.nBitCount = sal_uInt16(ePixelFormat);
+ aHeader.nSizeImage = rAcc.Height() * AlignedWidth4Bytes(rAcc.Width() * aHeader.nBitCount);
+
+ if (bCompressed)
+ {
+ if (ePixelFormat == vcl::PixelFormat::N8_BPP)
+ nCompression = RLE_8;
+ }
+ }
+
+ if((rOStm.GetCompressMode() & SvStreamCompressFlags::ZBITMAP) && (rOStm.GetVersion() >= SOFFICE_FILEFORMAT_40))
+ {
+ aHeader.nCompression = ZCOMPRESS;
+ }
+ else
+ {
+ aHeader.nCompression = nCompression;
+ }
+
+ if(rBitmap.GetPrefSize().Width() && rBitmap.GetPrefSize().Height() && (rBitmap.GetPrefMapMode() != aMapPixel))
+ {
+ // #i48108# Try to recover xpels/ypels as previously stored on
+ // disk. The problem with just converting maPrefSize to 100th
+ // mm and then relating that to the bitmap pixel size is that
+ // MapMode is integer-based, and suffers from roundoffs,
+ // especially if maPrefSize is small. Trying to circumvent
+ // that by performing part of the math in floating point.
+ const Size aScale100000(OutputDevice::LogicToLogic(Size(100000, 100000), MapMode(MapUnit::Map100thMM), rBitmap.GetPrefMapMode()));
+ const double fBmpWidthM(static_cast<double>(rBitmap.GetPrefSize().Width()) / aScale100000.Width());
+ const double fBmpHeightM(static_cast<double>(rBitmap.GetPrefSize().Height()) / aScale100000.Height());
+
+ if(!basegfx::fTools::equalZero(fBmpWidthM) && !basegfx::fTools::equalZero(fBmpHeightM))
+ {
+ aHeader.nXPelsPerMeter = basegfx::fround(rAcc.Width() / fabs(fBmpWidthM));
+ aHeader.nYPelsPerMeter = basegfx::fround(rAcc.Height() / fabs(fBmpHeightM));
+ }
+ }
+
+ aHeader.nColsUsed = ((aHeader.nBitCount <= 8) ? rAcc.GetPaletteEntryCount() : 0);
+ aHeader.nColsImportant = 0;
+
+ rOStm.WriteUInt32( aHeader.nSize );
+ rOStm.WriteInt32( aHeader.nWidth );
+ rOStm.WriteInt32( aHeader.nHeight );
+ rOStm.WriteUInt16( aHeader.nPlanes );
+ rOStm.WriteUInt16( aHeader.nBitCount );
+ rOStm.WriteUInt32( aHeader.nCompression );
+
+ nImageSizePos = rOStm.Tell();
+ rOStm.SeekRel( sizeof( aHeader.nSizeImage ) );
+
+ rOStm.WriteInt32( aHeader.nXPelsPerMeter );
+ rOStm.WriteInt32( aHeader.nYPelsPerMeter );
+ rOStm.WriteUInt32( aHeader.nColsUsed );
+ rOStm.WriteUInt32( aHeader.nColsImportant );
+
+ if(ZCOMPRESS == aHeader.nCompression)
+ {
+ ZCodec aCodec;
+ SvMemoryStream aMemStm(aHeader.nSizeImage + 4096, 65535);
+ sal_uInt64 nCodedPos(rOStm.Tell());
+ sal_uInt64 nLastPos(0);
+ sal_uInt32 nCodedSize(0);
+ sal_uInt32 nUncodedSize(0);
+
+ // write uncoded data palette
+ if(aHeader.nColsUsed)
+ {
+ ImplWriteDIBPalette(aMemStm, rAcc);
+ }
+
+ // write uncoded bits
+ bRet = ImplWriteDIBBits(aMemStm, rAcc, nCompression, aHeader.nSizeImage);
+
+ // get uncoded size
+ nUncodedSize = aMemStm.Tell();
+
+ // seek over compress info
+ rOStm.SeekRel(12);
+
+ // write compressed data
+ aCodec.BeginCompression(3);
+ aCodec.Write(rOStm, static_cast<sal_uInt8 const *>(aMemStm.GetData()), nUncodedSize);
+ aCodec.EndCompression();
+
+ // update compress info ( coded size, uncoded size, uncoded compression )
+ nLastPos = rOStm.Tell();
+ nCodedSize = nLastPos - nCodedPos - 12;
+ rOStm.Seek(nCodedPos);
+ rOStm.WriteUInt32( nCodedSize ).WriteUInt32( nUncodedSize ).WriteUInt32( nCompression );
+ rOStm.Seek(nLastPos);
+
+ if(bRet)
+ {
+ bRet = (ERRCODE_NONE == rOStm.GetError());
+ }
+ }
+ else
+ {
+ if(aHeader.nColsUsed)
+ {
+ ImplWriteDIBPalette(rOStm, rAcc);
+ }
+
+ bRet = ImplWriteDIBBits(rOStm, rAcc, aHeader.nCompression, aHeader.nSizeImage);
+ }
+
+ nEndPos = rOStm.Tell();
+ rOStm.Seek(nImageSizePos);
+ rOStm.WriteUInt32( aHeader.nSizeImage );
+ rOStm.Seek(nEndPos);
+
+ return bRet;
+}
+
+bool ImplWriteDIBFileHeader(SvStream& rOStm, BitmapReadAccess const & rAcc)
+{
+ const sal_uInt32 nPalCount((rAcc.HasPalette() ? rAcc.GetPaletteEntryCount() : isBitfieldCompression(rAcc.GetScanlineFormat()) ? 3UL : 0UL));
+ const sal_uInt32 nOffset(14 + DIBINFOHEADERSIZE + nPalCount * 4UL);
+
+ rOStm.WriteUInt16( 0x4D42 ); // 'MB' from BITMAPFILEHEADER
+ rOStm.WriteUInt32( nOffset + (rAcc.Height() * rAcc.GetScanlineSize()) );
+ rOStm.WriteUInt16( 0 );
+ rOStm.WriteUInt16( 0 );
+ rOStm.WriteUInt32( nOffset );
+
+ return rOStm.GetError() == ERRCODE_NONE;
+}
+
+bool ImplReadDIB(
+ Bitmap& rTarget,
+ AlphaMask* pTargetAlpha,
+ SvStream& rIStm,
+ bool bFileHeader,
+ bool bMSOFormat=false)
+{
+ const SvStreamEndian nOldFormat(rIStm.GetEndian());
+ const auto nOldPos(rIStm.Tell());
+ sal_uLong nOffset(0);
+ bool bRet(false);
+
+ rIStm.SetEndian(SvStreamEndian::LITTLE);
+
+ if(bFileHeader)
+ {
+ if(ImplReadDIBFileHeader(rIStm, nOffset))
+ {
+ bRet = ImplReadDIBBody(rIStm, rTarget, nOffset >= DIBV5HEADERSIZE ? pTargetAlpha : nullptr, nOffset, bMSOFormat);
+ }
+ }
+ else
+ {
+ bRet = ImplReadDIBBody(rIStm, rTarget, nullptr, nOffset, bMSOFormat);
+ }
+
+ if(!bRet)
+ {
+ if(!rIStm.GetError()) // Set error and stop processing whole stream due to security reason
+ {
+ rIStm.SetError(SVSTREAM_GENERALERROR);
+ }
+
+ rIStm.Seek(nOldPos);
+ }
+
+ rIStm.SetEndian(nOldFormat);
+
+ return bRet;
+}
+
+bool ImplWriteDIB(
+ const Bitmap& rSource,
+ SvStream& rOStm,
+ bool bCompressed,
+ bool bFileHeader)
+{
+ const Size aSizePix(rSource.GetSizePixel());
+ bool bRet(false);
+
+ if(!aSizePix.Width() || !aSizePix.Height())
+ return false;
+
+ BitmapScopedReadAccess pAcc(rSource);
+ const SvStreamEndian nOldFormat(rOStm.GetEndian());
+ const sal_uInt64 nOldPos(rOStm.Tell());
+
+ rOStm.SetEndian(SvStreamEndian::LITTLE);
+
+ if (pAcc)
+ {
+ if(bFileHeader)
+ {
+ if(ImplWriteDIBFileHeader(rOStm, *pAcc))
+ {
+ bRet = ImplWriteDIBBody(rSource, rOStm, *pAcc, bCompressed);
+ }
+ }
+ else
+ {
+ bRet = ImplWriteDIBBody(rSource, rOStm, *pAcc, bCompressed);
+ }
+
+ pAcc.reset();
+ }
+
+ if(!bRet)
+ {
+ rOStm.SetError(SVSTREAM_GENERALERROR);
+ rOStm.Seek(nOldPos);
+ }
+
+ rOStm.SetEndian(nOldFormat);
+
+ return bRet;
+}
+
+} // unnamed namespace
+
+bool ReadDIB(
+ Bitmap& rTarget,
+ SvStream& rIStm,
+ bool bFileHeader,
+ bool bMSOFormat)
+{
+ return ImplReadDIB(rTarget, nullptr, rIStm, bFileHeader, bMSOFormat);
+}
+
+bool ReadDIBBitmapEx(
+ BitmapEx& rTarget,
+ SvStream& rIStm,
+ bool bFileHeader,
+ bool bMSOFormat)
+{
+ Bitmap aBmp;
+ bool bRetval(ImplReadDIB(aBmp, nullptr, rIStm, bFileHeader, bMSOFormat) && !rIStm.GetError());
+
+ if(bRetval)
+ {
+ // base bitmap was read, set as return value and try to read alpha extra-data
+ const sal_uInt64 nStmPos(rIStm.Tell());
+ sal_uInt32 nMagic1(0);
+ sal_uInt32 nMagic2(0);
+
+ rTarget = BitmapEx(aBmp);
+ if (rIStm.remainingSize() >= 4)
+ rIStm.ReadUInt32( nMagic1 ).ReadUInt32( nMagic2 );
+ bRetval = (0x25091962 == nMagic1) && (0xACB20201 == nMagic2) && !rIStm.GetError();
+
+ if(bRetval)
+ {
+ sal_uInt8 tmp = 0;
+ rIStm.ReadUChar( tmp );
+ bRetval = !rIStm.GetError();
+
+ if(bRetval)
+ {
+ switch (tmp)
+ {
+ case 2: // TransparentType::Bitmap
+ {
+ Bitmap aMask;
+
+ bRetval = ImplReadDIB(aMask, nullptr, rIStm, true);
+
+ if(bRetval && !aMask.IsEmpty())
+ rTarget = BitmapEx(aBmp, aMask);
+
+ break;
+ }
+ case 1: // backwards compat for old option TransparentType::Color
+ {
+ Color aTransparentColor;
+
+ tools::GenericTypeSerializer aSerializer(rIStm);
+ aSerializer.readColor(aTransparentColor);
+
+ bRetval = rIStm.good();
+
+ if(bRetval)
+ {
+ rTarget = BitmapEx(aBmp, aTransparentColor);
+ }
+ break;
+ }
+ default: break;
+ }
+ }
+ }
+
+ if(!bRetval)
+ {
+ // alpha extra data could not be read; reset, but use base bitmap as result
+ rIStm.ResetError();
+ rIStm.Seek(nStmPos);
+ bRetval = true;
+ }
+ }
+
+ return bRetval;
+}
+
+bool ReadDIBV5(
+ Bitmap& rTarget,
+ AlphaMask& rTargetAlpha,
+ SvStream& rIStm)
+{
+ bool rv = ImplReadDIB(rTarget, &rTargetAlpha, rIStm, true);
+ // convert transparency->alpha
+ if (rv)
+ rTargetAlpha.Invert();
+ return rv;
+}
+
+bool ReadRawDIB(
+ BitmapEx& rTarget,
+ const unsigned char* pBuf,
+ const ScanlineFormat nFormat,
+ const int nHeight,
+ const int nStride)
+{
+ BitmapScopedWriteAccess pWriteAccess(rTarget.maBitmap);
+ for (int nRow = 0; nRow < nHeight; ++nRow)
+ {
+ pWriteAccess->CopyScanline(nRow, pBuf + (nStride * nRow), nFormat, nStride);
+ }
+
+ return true;
+}
+
+bool WriteDIB(
+ const Bitmap& rSource,
+ SvStream& rOStm,
+ bool bCompressed,
+ bool bFileHeader)
+{
+ return ImplWriteDIB(rSource, rOStm, bCompressed, bFileHeader);
+}
+
+bool WriteDIB(
+ const BitmapEx& rSource,
+ SvStream& rOStm,
+ bool bCompressed)
+{
+ return ImplWriteDIB(rSource.GetBitmap(), rOStm, bCompressed, /*bFileHeader*/true);
+}
+
+bool WriteDIBBitmapEx(
+ const BitmapEx& rSource,
+ SvStream& rOStm)
+{
+ if(ImplWriteDIB(rSource.GetBitmap(), rOStm, true, true))
+ {
+ rOStm.WriteUInt32( 0x25091962 );
+ rOStm.WriteUInt32( 0xACB20201 );
+ rOStm.WriteUChar( rSource.IsAlpha() ? 2 : 0 ); // Used to be TransparentType enum
+
+ if(rSource.IsAlpha())
+ {
+ // invert the alpha because the other routines actually want transparency
+ AlphaMask tmpAlpha = rSource.maAlphaMask;
+ tmpAlpha.Invert();
+ return ImplWriteDIB(tmpAlpha.GetBitmap(), rOStm, true, true);
+ }
+ }
+
+ return false;
+}
+
+sal_uInt32 getDIBV5HeaderSize()
+{
+ return DIBV5HEADERSIZE;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/floyd.hxx b/vcl/source/bitmap/floyd.hxx
new file mode 100644
index 0000000000..b5f26fb7a6
--- /dev/null
+++ b/vcl/source/bitmap/floyd.hxx
@@ -0,0 +1,177 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+const extern sal_uLong nVCLRLut[ 6 ] = { 16, 17, 18, 19, 20, 21 };
+const extern sal_uLong nVCLGLut[ 6 ] = { 0, 6, 12, 18, 24, 30 };
+const extern sal_uLong nVCLBLut[ 6 ] = { 0, 36, 72, 108, 144, 180 };
+
+const extern sal_uLong nVCLDitherLut[ 256 ] =
+{
+ 0, 49152, 12288, 61440, 3072, 52224, 15360, 64512, 768, 49920, 13056,
+ 62208, 3840, 52992, 16128, 65280, 32768, 16384, 45056, 28672, 35840, 19456,
+ 48128, 31744, 33536, 17152, 45824, 29440, 36608, 20224, 48896, 32512, 8192,
+ 57344, 4096, 53248, 11264, 60416, 7168, 56320, 8960, 58112, 4864, 54016,
+ 12032, 61184, 7936, 57088, 40960, 24576, 36864, 20480, 44032, 27648, 39936,
+ 23552, 41728, 25344, 37632, 21248, 44800, 28416, 40704, 24320, 2048, 51200,
+ 14336, 63488, 1024, 50176, 13312, 62464, 2816, 51968, 15104, 64256, 1792,
+ 50944, 14080, 63232, 34816, 18432, 47104, 30720, 33792, 17408, 46080, 29696,
+ 35584, 19200, 47872, 31488, 34560, 18176, 46848, 30464, 10240, 59392, 6144,
+ 55296, 9216, 58368, 5120, 54272, 11008, 60160, 6912, 56064, 9984, 59136,
+ 5888, 55040, 43008, 26624, 38912, 22528, 41984, 25600, 37888, 21504, 43776,
+ 27392, 39680, 23296, 42752, 26368, 38656, 22272, 512, 49664, 12800, 61952,
+ 3584, 52736, 15872, 65024, 256, 49408, 12544, 61696, 3328, 52480, 15616,
+ 64768, 33280, 16896, 45568, 29184, 36352, 19968, 48640, 32256, 33024, 16640,
+ 45312, 28928, 36096, 19712, 48384, 32000, 8704, 57856, 4608, 53760, 11776,
+ 60928, 7680, 56832, 8448, 57600, 4352, 53504, 11520, 60672, 7424, 56576,
+ 41472, 25088, 37376, 20992, 44544, 28160, 40448, 24064, 41216, 24832, 37120,
+ 20736, 44288, 27904, 40192, 23808, 2560, 51712, 14848, 64000, 1536, 50688,
+ 13824, 62976, 2304, 51456, 14592, 63744, 1280, 50432, 13568, 62720, 35328,
+ 18944, 47616, 31232, 34304, 17920, 46592, 30208, 35072, 18688, 47360, 30976,
+ 34048, 17664, 46336, 29952, 10752, 59904, 6656, 55808, 9728, 58880, 5632,
+ 54784, 10496, 59648, 6400, 55552, 9472, 58624, 5376, 54528, 43520, 27136,
+ 39424, 23040, 42496, 26112, 38400, 22016, 43264, 26880, 39168, 22784, 42240,
+ 25856, 38144, 21760
+};
+
+const extern sal_uLong nVCLLut[ 256 ] =
+{
+ 0, 1286, 2572, 3858, 5144, 6430, 7716, 9002,
+ 10288, 11574, 12860, 14146, 15432, 16718, 18004, 19290,
+ 20576, 21862, 23148, 24434, 25720, 27006, 28292, 29578,
+ 30864, 32150, 33436, 34722, 36008, 37294, 38580, 39866,
+ 41152, 42438, 43724, 45010, 46296, 47582, 48868, 50154,
+ 51440, 52726, 54012, 55298, 56584, 57870, 59156, 60442,
+ 61728, 63014, 64300, 65586, 66872, 68158, 69444, 70730,
+ 72016, 73302, 74588, 75874, 77160, 78446, 79732, 81018,
+ 82304, 83590, 84876, 86162, 87448, 88734, 90020, 91306,
+ 92592, 93878, 95164, 96450, 97736, 99022,100308,101594,
+ 102880,104166,105452,106738,108024,109310,110596,111882,
+ 113168,114454,115740,117026,118312,119598,120884,122170,
+ 123456,124742,126028,127314,128600,129886,131172,132458,
+ 133744,135030,136316,137602,138888,140174,141460,142746,
+ 144032,145318,146604,147890,149176,150462,151748,153034,
+ 154320,155606,156892,158178,159464,160750,162036,163322,
+ 164608,165894,167180,168466,169752,171038,172324,173610,
+ 174896,176182,177468,178754,180040,181326,182612,183898,
+ 185184,186470,187756,189042,190328,191614,192900,194186,
+ 195472,196758,198044,199330,200616,201902,203188,204474,
+ 205760,207046,208332,209618,210904,212190,213476,214762,
+ 216048,217334,218620,219906,221192,222478,223764,225050,
+ 226336,227622,228908,230194,231480,232766,234052,235338,
+ 236624,237910,239196,240482,241768,243054,244340,245626,
+ 246912,248198,249484,250770,252056,253342,254628,255914,
+ 257200,258486,259772,261058,262344,263630,264916,266202,
+ 267488,268774,270060,271346,272632,273918,275204,276490,
+ 277776,279062,280348,281634,282920,284206,285492,286778,
+ 288064,289350,290636,291922,293208,294494,295780,297066,
+ 298352,299638,300924,302210,303496,304782,306068,307354,
+ 308640,309926,311212,312498,313784,315070,316356,317642,
+ 318928,320214,321500,322786,324072,325358,326644,327930
+};
+
+const int FloydMap[256] =
+{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5
+};
+
+constexpr int FloydErrMap[256]
+ = { 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,
+ 52, 53, 54, 55, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
+ 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44,
+ 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+ 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37,
+ 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 5, 6, 7, 8,
+ 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
+ 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52,
+ 53, 54, 55, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
+ 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45,
+ 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
+ 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30 };
+
+constexpr int FloydError1[61] =
+{
+ -7680, -7424, -7168, -6912, -6656, -6400, -6144,
+ -5888, -5632, -5376, -5120, -4864, -4608, -4352,
+ -4096, -3840, -3584, -3328, -3072, -2816, -2560,
+ -2304, -2048, -1792, -1536, -1280, -1024, -768,
+ -512, -256, 0, 256, 512, 768, 1024, 1280, 1536,
+ 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584,
+ 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632,
+ 5888, 6144, 6400, 6656, 6912, 7168, 7424, 7680
+};
+
+constexpr int FloydError3[61] =
+{
+ -23040, -22272, -21504, -20736, -19968, -19200,
+ -18432, -17664, -16896, -16128, -15360, -14592,
+ -13824, -13056, -12288, -11520, -10752, -9984,
+ -9216, -8448, -7680, -6912, -6144, -5376, -4608,
+ -3840, -3072, -2304, -1536, -768, 0, 768, 1536,
+ 2304, 3072, 3840, 4608, 5376, 6144, 6912, 7680,
+ 8448, 9216, 9984, 10752, 11520, 12288, 13056,
+ 13824, 14592, 15360, 16128, 16896, 17664, 18432,
+ 19200, 19968, 20736, 21504, 22272, 23040
+};
+
+constexpr int FloydError5[61] =
+{
+ -38400, -37120, -35840, -34560, -33280, -32000,
+ -30720, -29440, -28160, -26880, -25600, -24320,
+ -23040, -21760, -20480, -19200, -17920, -16640,
+ -15360, -14080, -12800, -11520, -10240, -8960,
+ -7680, -6400, -5120, -3840, -2560, -1280, 0,
+ 1280, 2560, 3840, 5120, 6400, 7680, 8960, 10240,
+ 11520, 12800, 14080, 15360, 16640, 17920, 19200,
+ 20480, 21760, 23040, 24320, 25600, 26880, 28160,
+ 29440, 30720, 32000, 33280, 34560, 35840, 37120,
+ 38400
+};
+
+constexpr int FloydError7[61] =
+{
+ -53760, -51968, -50176, -48384, -46592, -44800,
+ -43008, -41216, -39424, -37632, -35840, -34048,
+ -32256, -30464, -28672, -26880, -25088, -23296,
+ -21504, -19712, -17920, -16128, -14336, -12544,
+ -10752, -8960, -7168, -5376, -3584, -1792, 0,
+ 1792, 3584, 5376, 7168, 8960, 10752, 12544, 14336,
+ 16128, 17920, 19712, 21504, 23296, 25088, 26880,
+ 28672, 30464, 32256, 34048, 35840, 37632, 39424,
+ 41216, 43008, 44800, 46592, 48384, 50176, 51968,
+ 53760
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/source/bitmap/impvect.cxx b/vcl/source/bitmap/impvect.cxx
new file mode 100644
index 0000000000..3f8f8471a6
--- /dev/null
+++ b/vcl/source/bitmap/impvect.cxx
@@ -0,0 +1,996 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/log.hxx>
+#include <vcl/BitmapReadAccess.hxx>
+#include <tools/link.hxx>
+#include <tools/poly.hxx>
+#include <tools/helpers.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/virdev.hxx>
+#include "impvect.hxx"
+#include <array>
+#include <memory>
+
+#define VECT_POLY_MAX 8192
+
+#define VECT_FREE_INDEX 0
+#define VECT_CONT_INDEX 1
+#define VECT_DONE_INDEX 2
+
+#define VECT_POLY_INLINE_INNER 1UL
+#define VECT_POLY_INLINE_OUTER 2UL
+#define VECT_POLY_OUTLINE_INNER 4UL
+#define VECT_POLY_OUTLINE_OUTER 8UL
+
+static void VECT_MAP( const std::unique_ptr<sal_Int32 []> & pMapIn, const std::unique_ptr<sal_Int32 []>& pMapOut, tools::Long nVal )
+{
+ pMapIn[nVal] = (nVal * 4) + 1;
+ pMapOut[nVal] = pMapIn[nVal] + 5;
+}
+static constexpr tools::Long BACK_MAP( tools::Long _def_nVal )
+{
+ return ((_def_nVal + 2) >> 2) - 1;
+}
+static void VECT_PROGRESS( const Link<tools::Long, void>* pProgress, tools::Long _def_nVal )
+{
+ if(pProgress)
+ pProgress->Call(_def_nVal);
+}
+
+namespace {
+
+class ImplVectMap;
+class ImplChain;
+
+}
+
+namespace ImplVectorizer
+{
+ static void ImplExpand( std::optional<ImplVectMap>& rMap, const BitmapReadAccess* pRAcc, const Color& rColor );
+ static void ImplCalculate( ImplVectMap& rMap, tools::PolyPolygon& rPolyPoly, sal_uInt8 cReduce );
+ static bool ImplGetChain( ImplVectMap& rMap, const Point& rStartPt, ImplChain& rChain );
+ static bool ImplIsUp( ImplVectMap const & rMap, tools::Long nY, tools::Long nX );
+ static void ImplLimitPolyPoly( tools::PolyPolygon& rPolyPoly );
+}
+
+namespace {
+
+struct ChainMove { tools::Long nDX; tools::Long nDY; };
+
+}
+
+const ChainMove aImplMove[ 8 ] = {
+ { 1, 0 },
+ { 0, -1 },
+ { -1, 0 },
+ { 0, 1 },
+ { 1, -1 },
+ { -1, -1 },
+ { -1, 1 },
+ { 1, 1 }
+ };
+
+const ChainMove aImplMoveInner[ 8 ] = {
+ { 0, 1 },
+ { 1, 0 },
+ { 0, -1 },
+ { -1, 0 },
+ { 0, 1 },
+ { 1, 0 },
+ { 0, -1 },
+ { -1, 0 }
+ };
+
+const ChainMove aImplMoveOuter[ 8 ] = {
+ { 0, -1 },
+ { -1, 0 },
+ { 0, 1 },
+ { 1, 0 },
+ { -1, 0 },
+ { 0, 1 },
+ { 1, 0 },
+ { 0, -1 }
+ };
+
+namespace {
+
+struct ImplColorSet
+{
+ BitmapColor maColor;
+ sal_uInt16 mnIndex = 0;
+ bool mbSet = false;
+};
+
+}
+
+static bool ImplColorSetCmpFnc( const ImplColorSet& lhs, const ImplColorSet& rhs)
+{
+ if( lhs.mbSet && rhs.mbSet )
+ {
+ const sal_uInt8 cLum1 = lhs.maColor.GetLuminance();
+ const sal_uInt8 cLum2 = rhs.maColor.GetLuminance();
+ return cLum1 < cLum2;
+ }
+ return lhs.mbSet > rhs.mbSet;
+}
+
+namespace {
+
+class ImplPointArray
+{
+ std::unique_ptr<Point[]> mpArray;
+ sal_uLong mnSize;
+ sal_uLong mnRealSize;
+
+public:
+
+ ImplPointArray();
+
+ void ImplSetSize( sal_uLong nSize );
+ sal_uLong ImplGetRealSize() const { return mnRealSize; }
+ void ImplSetRealSize( sal_uLong nRealSize ) { mnRealSize = nRealSize; }
+ void ImplCreatePoly( tools::Polygon& rPoly ) const;
+
+ inline Point& operator[]( sal_uLong nPos );
+ inline const Point& operator[]( sal_uLong nPos ) const;
+
+};
+
+}
+
+ImplPointArray::ImplPointArray() :
+ mnSize ( 0 ),
+ mnRealSize ( 0 )
+
+{
+}
+
+void ImplPointArray::ImplSetSize( sal_uLong nSize )
+{
+ const sal_uLong nTotal = nSize * sizeof( Point );
+
+ mnSize = nSize;
+ mnRealSize = 0;
+
+ mpArray = std::make_unique<Point[]>( nTotal );
+}
+
+inline Point& ImplPointArray::operator[]( sal_uLong nPos )
+{
+ SAL_WARN_IF( nPos >= mnSize, "vcl", "ImplPointArray::operator[]: nPos out of range!" );
+ return mpArray[ nPos ];
+}
+
+inline const Point& ImplPointArray::operator[]( sal_uLong nPos ) const
+{
+ SAL_WARN_IF( nPos >= mnSize, "vcl", "ImplPointArray::operator[]: nPos out of range!" );
+ return mpArray[ nPos ];
+}
+
+void ImplPointArray::ImplCreatePoly( tools::Polygon& rPoly ) const
+{
+ rPoly = tools::Polygon( sal::static_int_cast<sal_uInt16>(mnRealSize), mpArray.get() );
+}
+
+namespace {
+
+class ImplVectMap
+{
+private:
+
+ Scanline mpBuf;
+ Scanline* mpScan;
+ tools::Long mnWidth;
+ tools::Long mnHeight;
+
+public:
+
+ ImplVectMap( tools::Long nWidth, tools::Long nHeight );
+ ~ImplVectMap();
+
+ tools::Long Width() const { return mnWidth; }
+ tools::Long Height() const { return mnHeight; }
+
+ inline void Set( tools::Long nY, tools::Long nX, sal_uInt8 cVal );
+ inline sal_uInt8 Get( tools::Long nY, tools::Long nX ) const;
+
+ inline bool IsFree( tools::Long nY, tools::Long nX ) const;
+ inline bool IsCont( tools::Long nY, tools::Long nX ) const;
+ inline bool IsDone( tools::Long nY, tools::Long nX ) const;
+
+};
+
+}
+
+ImplVectMap::ImplVectMap( tools::Long nWidth, tools::Long nHeight ) :
+ mpBuf ( static_cast<Scanline>(rtl_allocateZeroMemory(nWidth * nHeight)) ),
+ mpScan ( static_cast<Scanline*>(std::malloc(nHeight * sizeof(Scanline))) ),
+ mnWidth ( nWidth ),
+ mnHeight( nHeight )
+{
+ const tools::Long nWidthAl = ( nWidth >> 2 ) + 1;
+ Scanline pTmp = mpBuf;
+
+ for( tools::Long nY = 0; nY < nHeight; pTmp += nWidthAl )
+ mpScan[ nY++ ] = pTmp;
+}
+
+ImplVectMap::~ImplVectMap()
+{
+ std::free( mpBuf );
+ std::free( mpScan );
+}
+
+inline void ImplVectMap::Set( tools::Long nY, tools::Long nX, sal_uInt8 cVal )
+{
+ const sal_uInt8 cShift = sal::static_int_cast<sal_uInt8>(6 - ( ( nX & 3 ) << 1 ));
+ auto & rPixel = mpScan[ nY ][ nX >> 2 ];
+ rPixel = (rPixel & ~( 3 << cShift ) ) | ( cVal << cShift );
+}
+
+inline sal_uInt8 ImplVectMap::Get( tools::Long nY, tools::Long nX ) const
+{
+ return sal::static_int_cast<sal_uInt8>( ( ( mpScan[ nY ][ nX >> 2 ] ) >> ( 6 - ( ( nX & 3 ) << 1 ) ) ) & 3 );
+}
+
+inline bool ImplVectMap::IsFree( tools::Long nY, tools::Long nX ) const
+{
+ return( VECT_FREE_INDEX == Get( nY, nX ) );
+}
+
+inline bool ImplVectMap::IsCont( tools::Long nY, tools::Long nX ) const
+{
+ return( VECT_CONT_INDEX == Get( nY, nX ) );
+}
+
+inline bool ImplVectMap::IsDone( tools::Long nY, tools::Long nX ) const
+{
+ return( VECT_DONE_INDEX == Get( nY, nX ) );
+}
+
+namespace {
+
+class ImplChain
+{
+private:
+
+ tools::Polygon maPoly;
+ Point maStartPt;
+ sal_uLong mnArraySize;
+ sal_uLong mnCount;
+ std::unique_ptr<sal_uInt8[]>
+ mpCodes;
+
+ void ImplGetSpace();
+
+ void ImplPostProcess( const ImplPointArray& rArr );
+
+ ImplChain(const ImplChain&) = delete;
+ ImplChain& operator=(const ImplChain&) = delete;
+
+public:
+
+ ImplChain();
+
+ void ImplBeginAdd( const Point& rStartPt );
+ inline void ImplAdd( sal_uInt8 nCode );
+ void ImplEndAdd( sal_uLong nTypeFlag );
+
+ const tools::Polygon& ImplGetPoly() const { return maPoly; }
+};
+
+}
+
+ImplChain::ImplChain() :
+ mnArraySize ( 1024 ),
+ mnCount ( 0 ),
+ mpCodes ( new sal_uInt8[mnArraySize] )
+{
+}
+
+void ImplChain::ImplGetSpace()
+{
+ const sal_uLong nOldArraySize = mnArraySize;
+ sal_uInt8* pNewCodes;
+
+ mnArraySize = mnArraySize << 1;
+ pNewCodes = new sal_uInt8[ mnArraySize ];
+ memcpy( pNewCodes, mpCodes.get(), nOldArraySize );
+ mpCodes.reset( pNewCodes );
+}
+
+void ImplChain::ImplBeginAdd( const Point& rStartPt )
+{
+ maPoly = tools::Polygon();
+ maStartPt = rStartPt;
+ mnCount = 0;
+}
+
+inline void ImplChain::ImplAdd( sal_uInt8 nCode )
+{
+ if( mnCount == mnArraySize )
+ ImplGetSpace();
+
+ mpCodes[ mnCount++ ] = nCode;
+}
+
+void ImplChain::ImplEndAdd( sal_uLong nFlag )
+{
+ if( mnCount )
+ {
+ ImplPointArray aArr;
+
+ if( nFlag & VECT_POLY_INLINE_INNER )
+ {
+ tools::Long nFirstX, nFirstY;
+ tools::Long nLastX, nLastY;
+
+ nFirstX = nLastX = maStartPt.X();
+ nFirstY = nLastY = maStartPt.Y();
+ aArr.ImplSetSize( mnCount << 1 );
+
+ sal_uInt16 nPolyPos;
+ sal_uLong i;
+ for( i = 0, nPolyPos = 0; i < ( mnCount - 1 ); i++ )
+ {
+ const sal_uInt8 cMove = mpCodes[ i ];
+ const sal_uInt8 cNextMove = mpCodes[ i + 1 ];
+ const ChainMove& rMove = aImplMove[ cMove ];
+ const ChainMove& rMoveInner = aImplMoveInner[ cMove ];
+// Point& rPt = aArr[ nPolyPos ];
+ bool bDone = true;
+
+ nLastX += rMove.nDX;
+ nLastY += rMove.nDY;
+
+ if( cMove < 4 )
+ {
+ if( ( cMove == 0 && cNextMove == 3 ) ||
+ ( cMove == 3 && cNextMove == 2 ) ||
+ ( cMove == 2 && cNextMove == 1 ) ||
+ ( cMove == 1 && cNextMove == 0 ) )
+ {
+ }
+ else if( cMove == 2 && cNextMove == 3 )
+ {
+ aArr[ nPolyPos ].setX( nLastX );
+ aArr[ nPolyPos++ ].setY( nLastY - 1 );
+
+ aArr[ nPolyPos ].setX( nLastX - 1 );
+ aArr[ nPolyPos++ ].setY( nLastY - 1 );
+
+ aArr[ nPolyPos ].setX( nLastX - 1 );
+ aArr[ nPolyPos++ ].setY( nLastY );
+ }
+ else if( cMove == 3 && cNextMove == 0 )
+ {
+ aArr[ nPolyPos ].setX( nLastX - 1 );
+ aArr[ nPolyPos++ ].setY( nLastY );
+
+ aArr[ nPolyPos ].setX( nLastX - 1 );
+ aArr[ nPolyPos++ ].setY( nLastY + 1 );
+
+ aArr[ nPolyPos ].setX( nLastX );
+ aArr[ nPolyPos++ ].setY( nLastY + 1 );
+ }
+ else if( cMove == 0 && cNextMove == 1 )
+ {
+ aArr[ nPolyPos ].setX( nLastX );
+ aArr[ nPolyPos++ ].setY( nLastY + 1 );
+
+ aArr[ nPolyPos ].setX( nLastX + 1 );
+ aArr[ nPolyPos++ ].setY( nLastY + 1 );
+
+ aArr[ nPolyPos ].setX( nLastX + 1 );
+ aArr[ nPolyPos++ ].setY( nLastY );
+ }
+ else if( cMove == 1 && cNextMove == 2 )
+ {
+ aArr[ nPolyPos ].setX( nLastX + 1 );
+ aArr[ nPolyPos++ ].setY( nLastY + 1 );
+
+ aArr[ nPolyPos ].setX( nLastX + 1 );
+ aArr[ nPolyPos++ ].setY( nLastY - 1 );
+
+ aArr[ nPolyPos ].setX( nLastX );
+ aArr[ nPolyPos++ ].setY( nLastY - 1 );
+ }
+ else
+ bDone = false;
+ }
+ else if( cMove == 7 && cNextMove == 0 )
+ {
+ aArr[ nPolyPos ].setX( nLastX - 1 );
+ aArr[ nPolyPos++ ].setY( nLastY );
+
+ aArr[ nPolyPos ].setX( nLastX );
+ aArr[ nPolyPos++ ].setY( nLastY + 1 );
+ }
+ else if( cMove == 4 && cNextMove == 1 )
+ {
+ aArr[ nPolyPos ].setX( nLastX );
+ aArr[ nPolyPos++ ].setY( nLastY + 1 );
+
+ aArr[ nPolyPos ].setX( nLastX + 1 );
+ aArr[ nPolyPos++ ].setY( nLastY );
+ }
+ else
+ bDone = false;
+
+ if( !bDone )
+ {
+ aArr[ nPolyPos ].setX( nLastX + rMoveInner.nDX );
+ aArr[ nPolyPos++ ].setY( nLastY + rMoveInner.nDY );
+ }
+ }
+
+ aArr[ nPolyPos ].setX( nFirstX + 1 );
+ aArr[ nPolyPos++ ].setY( nFirstY + 1 );
+ aArr.ImplSetRealSize( nPolyPos );
+ }
+ else if( nFlag & VECT_POLY_INLINE_OUTER )
+ {
+ tools::Long nFirstX, nFirstY;
+ tools::Long nLastX, nLastY;
+
+ nFirstX = nLastX = maStartPt.X();
+ nFirstY = nLastY = maStartPt.Y();
+ aArr.ImplSetSize( mnCount << 1 );
+
+ sal_uInt16 nPolyPos;
+ sal_uLong i;
+ for( i = 0, nPolyPos = 0; i < ( mnCount - 1 ); i++ )
+ {
+ const sal_uInt8 cMove = mpCodes[ i ];
+ const sal_uInt8 cNextMove = mpCodes[ i + 1 ];
+ const ChainMove& rMove = aImplMove[ cMove ];
+ const ChainMove& rMoveOuter = aImplMoveOuter[ cMove ];
+// Point& rPt = aArr[ nPolyPos ];
+ bool bDone = true;
+
+ nLastX += rMove.nDX;
+ nLastY += rMove.nDY;
+
+ if( cMove < 4 )
+ {
+ if( ( cMove == 0 && cNextMove == 1 ) ||
+ ( cMove == 1 && cNextMove == 2 ) ||
+ ( cMove == 2 && cNextMove == 3 ) ||
+ ( cMove == 3 && cNextMove == 0 ) )
+ {
+ }
+ else if( cMove == 0 && cNextMove == 3 )
+ {
+ aArr[ nPolyPos ].setX( nLastX );
+ aArr[ nPolyPos++ ].setY( nLastY - 1 );
+
+ aArr[ nPolyPos ].setX( nLastX + 1 );
+ aArr[ nPolyPos++ ].setY( nLastY - 1 );
+
+ aArr[ nPolyPos ].setX( nLastX + 1 );
+ aArr[ nPolyPos++ ].setY( nLastY );
+ }
+ else if( cMove == 3 && cNextMove == 2 )
+ {
+ aArr[ nPolyPos ].setX( nLastX + 1 );
+ aArr[ nPolyPos++ ].setY( nLastY );
+
+ aArr[ nPolyPos ].setX( nLastX + 1 );
+ aArr[ nPolyPos++ ].setY( nLastY + 1 );
+
+ aArr[ nPolyPos ].setX( nLastX );
+ aArr[ nPolyPos++ ].setY( nLastY + 1 );
+ }
+ else if( cMove == 2 && cNextMove == 1 )
+ {
+ aArr[ nPolyPos ].setX( nLastX );
+ aArr[ nPolyPos++ ].setY( nLastY + 1 );
+
+ aArr[ nPolyPos ].setX( nLastX - 1 );
+ aArr[ nPolyPos++ ].setY( nLastY + 1 );
+
+ aArr[ nPolyPos ].setX( nLastX - 1 );
+ aArr[ nPolyPos++ ].setY( nLastY );
+ }
+ else if( cMove == 1 && cNextMove == 0 )
+ {
+ aArr[ nPolyPos ].setX( nLastX - 1 );
+ aArr[ nPolyPos++ ].setY( nLastY );
+
+ aArr[ nPolyPos ].setX( nLastX - 1 );
+ aArr[ nPolyPos++ ].setY( nLastY - 1 );
+
+ aArr[ nPolyPos ].setX( nLastX );
+ aArr[ nPolyPos++ ].setY( nLastY - 1 );
+ }
+ else
+ bDone = false;
+ }
+ else if( cMove == 7 && cNextMove == 3 )
+ {
+ aArr[ nPolyPos ].setX( nLastX );
+ aArr[ nPolyPos++ ].setY( nLastY - 1 );
+
+ aArr[ nPolyPos ].setX( nLastX + 1 );
+ aArr[ nPolyPos++ ].setY( nLastY );
+ }
+ else if( cMove == 6 && cNextMove == 2 )
+ {
+ aArr[ nPolyPos ].setX( nLastX + 1 );
+ aArr[ nPolyPos++ ].setY( nLastY );
+
+ aArr[ nPolyPos ].setX( nLastX );
+ aArr[ nPolyPos++ ].setY( nLastY + 1 );
+ }
+ else
+ bDone = false;
+
+ if( !bDone )
+ {
+ aArr[ nPolyPos ].setX( nLastX + rMoveOuter.nDX );
+ aArr[ nPolyPos++ ].setY( nLastY + rMoveOuter.nDY );
+ }
+ }
+
+ aArr[ nPolyPos ].setX( nFirstX - 1 );
+ aArr[ nPolyPos++ ].setY( nFirstY - 1 );
+ aArr.ImplSetRealSize( nPolyPos );
+ }
+ else
+ {
+ tools::Long nLastX = maStartPt.X(), nLastY = maStartPt.Y();
+
+ aArr.ImplSetSize( mnCount + 1 );
+ aArr[ 0 ] = Point( nLastX, nLastY );
+
+ for( sal_uLong i = 0; i < mnCount; )
+ {
+ const ChainMove& rMove = aImplMove[ mpCodes[ i ] ];
+ nLastX += rMove.nDX;
+ nLastY += rMove.nDY;
+ aArr[ ++i ] = Point( nLastX, nLastY );
+ }
+
+ aArr.ImplSetRealSize( mnCount + 1 );
+ }
+
+ ImplPostProcess( aArr );
+ }
+ else
+ maPoly.SetSize( 0 );
+}
+
+void ImplChain::ImplPostProcess( const ImplPointArray& rArr )
+{
+ ImplPointArray aNewArr1;
+ ImplPointArray aNewArr2;
+ Point* pLast;
+ Point* pLeast;
+ sal_uLong nNewPos;
+ sal_uLong nCount = rArr.ImplGetRealSize();
+ sal_uLong n;
+
+ // pass 1
+ aNewArr1.ImplSetSize( nCount );
+ pLast = &( aNewArr1[ 0 ] );
+ pLast->setX( BACK_MAP( rArr[ 0 ].X() ) );
+ pLast->setY( BACK_MAP( rArr[ 0 ].Y() ) );
+
+ for( n = nNewPos = 1; n < nCount; )
+ {
+ const Point& rPt = rArr[ n++ ];
+ const tools::Long nX = BACK_MAP( rPt.X() );
+ const tools::Long nY = BACK_MAP( rPt.Y() );
+
+ if( nX != pLast->X() || nY != pLast->Y() )
+ {
+ pLast = pLeast = &( aNewArr1[ nNewPos++ ] );
+ pLeast->setX( nX );
+ pLeast->setY( nY );
+ }
+ }
+
+ nCount = nNewPos;
+ aNewArr1.ImplSetRealSize( nCount );
+
+ // pass 2
+ aNewArr2.ImplSetSize( nCount );
+ pLast = &( aNewArr2[ 0 ] );
+ *pLast = aNewArr1[ 0 ];
+
+ for( n = nNewPos = 1; n < nCount; )
+ {
+ pLeast = &( aNewArr1[ n++ ] );
+
+ if( pLeast->X() == pLast->X() )
+ {
+ while( n < nCount && aNewArr1[ n ].X() == pLast->X() )
+ pLeast = &( aNewArr1[ n++ ] );
+ }
+ else if( pLeast->Y() == pLast->Y() )
+ {
+ while( n < nCount && aNewArr1[ n ].Y() == pLast->Y() )
+ pLeast = &( aNewArr1[ n++ ] );
+ }
+
+ pLast = pLeast;
+ aNewArr2[ nNewPos++ ] = *pLast;
+ }
+
+ aNewArr2.ImplSetRealSize( nNewPos );
+ aNewArr2.ImplCreatePoly( maPoly );
+}
+
+namespace ImplVectorizer {
+
+bool ImplVectorize( const Bitmap& rColorBmp, GDIMetaFile& rMtf,
+ sal_uInt8 cReduce, const Link<tools::Long,void>* pProgress )
+{
+ bool bRet = false;
+
+ VECT_PROGRESS( pProgress, 0 );
+
+ std::optional<Bitmap> xBmp(std::in_place, rColorBmp );
+ BitmapScopedReadAccess pRAcc(*xBmp);
+
+ if( pRAcc )
+ {
+ double fPercent = 0.0;
+ double fPercentStep_2 = 0.0;
+ const tools::Long nWidth = pRAcc->Width();
+ const tools::Long nHeight = pRAcc->Height();
+ const sal_uInt16 nColorCount = pRAcc->GetPaletteEntryCount();
+ sal_uInt16 n;
+ std::array<ImplColorSet, 256> aColorSet;
+
+ rMtf.Clear();
+
+ // get used palette colors and sort them from light to dark colors
+ for( n = 0; n < nColorCount; n++ )
+ {
+ aColorSet[ n ].mnIndex = n;
+ aColorSet[ n ].maColor = pRAcc->GetPaletteColor( n );
+ }
+
+ for( tools::Long nY = 0; nY < nHeight; nY++ )
+ {
+ Scanline pScanlineRead = pRAcc->GetScanline( nY );
+ for( tools::Long nX = 0; nX < nWidth; nX++ )
+ aColorSet[ pRAcc->GetIndexFromData( pScanlineRead, nX ) ].mbSet = true;
+ }
+
+ std::sort( aColorSet.begin(), aColorSet.end(), ImplColorSetCmpFnc );
+
+ for( n = 0; n < 256; n++ )
+ if( !aColorSet[ n ].mbSet )
+ break;
+
+ if( n )
+ fPercentStep_2 = 45.0 / n;
+
+ fPercent += 10.0;
+ VECT_PROGRESS( pProgress, FRound( fPercent ) );
+
+ for( sal_uInt16 i = 0; i < n; i++ )
+ {
+ const BitmapColor aBmpCol( pRAcc->GetPaletteColor( aColorSet[ i ].mnIndex ) );
+ const Color aFindColor( aBmpCol.GetRed(), aBmpCol.GetGreen(), aBmpCol.GetBlue() );
+ std::optional<ImplVectMap> oMap;
+ ImplExpand( oMap, pRAcc.get(), aFindColor );
+
+ fPercent += fPercentStep_2;
+ VECT_PROGRESS( pProgress, FRound( fPercent ) );
+
+ if( oMap )
+ {
+ tools::PolyPolygon aPolyPoly;
+ ImplCalculate( *oMap, aPolyPoly, cReduce );
+ oMap.reset();
+
+ if( aPolyPoly.Count() )
+ {
+ ImplLimitPolyPoly( aPolyPoly );
+
+ aPolyPoly.Optimize( PolyOptimizeFlags::EDGES );
+
+ if( aPolyPoly.Count() )
+ {
+ rMtf.AddAction( new MetaLineColorAction( aFindColor, true ) );
+ rMtf.AddAction( new MetaFillColorAction( aFindColor, true ) );
+ rMtf.AddAction( new MetaPolyPolygonAction( std::move(aPolyPoly) ) );
+ }
+ }
+ }
+
+ fPercent += fPercentStep_2;
+ VECT_PROGRESS( pProgress, FRound( fPercent ) );
+ }
+
+ if( rMtf.GetActionSize() )
+ {
+ MapMode aMap( MapUnit::Map100thMM );
+ ScopedVclPtrInstance< VirtualDevice > aVDev;
+ const Size aLogSize1( aVDev->PixelToLogic( Size( 1, 1 ), aMap ) );
+
+ rMtf.SetPrefMapMode( aMap );
+ rMtf.SetPrefSize( Size( nWidth + 2, nHeight + 2 ) );
+ rMtf.Move( 1, 1 );
+ rMtf.Scale( aLogSize1.Width(), aLogSize1.Height() );
+ bRet = true;
+ }
+ }
+
+ pRAcc.reset();
+ xBmp.reset();
+ VECT_PROGRESS( pProgress, 100 );
+
+ return bRet;
+}
+
+void ImplLimitPolyPoly( tools::PolyPolygon& rPolyPoly )
+{
+ if( rPolyPoly.Count() <= VECT_POLY_MAX )
+ return;
+
+ tools::PolyPolygon aNewPolyPoly;
+ tools::Long nReduce = 0;
+ sal_uInt16 nNewCount;
+
+ do
+ {
+ aNewPolyPoly.Clear();
+ nReduce++;
+
+ for( sal_uInt16 i = 0, nCount = rPolyPoly.Count(); i < nCount; i++ )
+ {
+ const tools::Rectangle aBound( rPolyPoly[ i ].GetBoundRect() );
+
+ if( aBound.GetWidth() > nReduce && aBound.GetHeight() > nReduce )
+ {
+ if( rPolyPoly[ i ].GetSize() )
+ aNewPolyPoly.Insert( rPolyPoly[ i ] );
+ }
+ }
+
+ nNewCount = aNewPolyPoly.Count();
+ }
+ while( nNewCount > VECT_POLY_MAX );
+
+ rPolyPoly = aNewPolyPoly;
+}
+
+void ImplExpand( std::optional<ImplVectMap>& oMap, const BitmapReadAccess* pRAcc, const Color& rColor )
+{
+ if( !pRAcc || !pRAcc->Width() || !pRAcc->Height() )
+ return;
+
+ const tools::Long nOldWidth = pRAcc->Width();
+ const tools::Long nOldHeight = pRAcc->Height();
+ const tools::Long nNewWidth = ( nOldWidth << 2 ) + 4;
+ const tools::Long nNewHeight = ( nOldHeight << 2 ) + 4;
+ const BitmapColor aTest( pRAcc->GetBestMatchingColor( rColor ) );
+ std::unique_ptr<sal_Int32[]> pMapIn(new sal_Int32[ std::max( nOldWidth, nOldHeight ) ]);
+ std::unique_ptr<sal_Int32[]> pMapOut(new sal_Int32[ std::max( nOldWidth, nOldHeight ) ]);
+ tools::Long nX, nY, nTmpX, nTmpY;
+
+ oMap.emplace( nNewWidth, nNewHeight );
+
+ for( nX = 0; nX < nOldWidth; nX++ )
+ VECT_MAP( pMapIn, pMapOut, nX );
+
+ for( nY = 0, nTmpY = 5; nY < nOldHeight; nY++, nTmpY += 4 )
+ {
+ Scanline pScanlineRead = pRAcc->GetScanline( nY );
+ for( nX = 0; nX < nOldWidth; )
+ {
+ if( pRAcc->GetPixelFromData( pScanlineRead, nX ) == aTest )
+ {
+ nTmpX = pMapIn[ nX++ ];
+ nTmpY -= 3;
+
+ oMap->Set( nTmpY++, nTmpX, VECT_CONT_INDEX );
+ oMap->Set( nTmpY++, nTmpX, VECT_CONT_INDEX );
+ oMap->Set( nTmpY++, nTmpX, VECT_CONT_INDEX );
+ oMap->Set( nTmpY, nTmpX, VECT_CONT_INDEX );
+
+ while( nX < nOldWidth && pRAcc->GetPixelFromData( pScanlineRead, nX ) == aTest )
+ nX++;
+
+ nTmpX = pMapOut[ nX - 1 ];
+ nTmpY -= 3;
+
+ oMap->Set( nTmpY++, nTmpX, VECT_CONT_INDEX );
+ oMap->Set( nTmpY++, nTmpX, VECT_CONT_INDEX );
+ oMap->Set( nTmpY++, nTmpX, VECT_CONT_INDEX );
+ oMap->Set( nTmpY, nTmpX, VECT_CONT_INDEX );
+ }
+ else
+ nX++;
+ }
+ }
+
+ for( nY = 0; nY < nOldHeight; nY++ )
+ VECT_MAP( pMapIn, pMapOut, nY );
+
+ for( nX = 0, nTmpX = 5; nX < nOldWidth; nX++, nTmpX += 4 )
+ {
+ for( nY = 0; nY < nOldHeight; )
+ {
+ if( pRAcc->GetPixel( nY, nX ) == aTest )
+ {
+ nTmpX -= 3;
+ nTmpY = pMapIn[ nY++ ];
+
+ oMap->Set( nTmpY, nTmpX++, VECT_CONT_INDEX );
+ oMap->Set( nTmpY, nTmpX++, VECT_CONT_INDEX );
+ oMap->Set( nTmpY, nTmpX++, VECT_CONT_INDEX );
+ oMap->Set( nTmpY, nTmpX, VECT_CONT_INDEX );
+
+ while( nY < nOldHeight && pRAcc->GetPixel( nY, nX ) == aTest )
+ nY++;
+
+ nTmpX -= 3;
+ nTmpY = pMapOut[ nY - 1 ];
+
+ oMap->Set( nTmpY, nTmpX++, VECT_CONT_INDEX );
+ oMap->Set( nTmpY, nTmpX++, VECT_CONT_INDEX );
+ oMap->Set( nTmpY, nTmpX++, VECT_CONT_INDEX );
+ oMap->Set( nTmpY, nTmpX, VECT_CONT_INDEX );
+ }
+ else
+ nY++;
+ }
+ }
+}
+
+void ImplCalculate( ImplVectMap& rMap, tools::PolyPolygon& rPolyPoly, sal_uInt8 cReduce )
+{
+ const tools::Long nWidth = rMap.Width(), nHeight = rMap.Height();
+
+ for( tools::Long nY = 0; nY < nHeight; nY++ )
+ {
+ tools::Long nX = 0;
+ bool bInner = true;
+
+ while( nX < nWidth )
+ {
+ // skip free
+ while( ( nX < nWidth ) && rMap.IsFree( nY, nX ) )
+ nX++;
+
+ if( nX == nWidth )
+ break;
+
+ if( rMap.IsCont( nY, nX ) )
+ {
+ // new contour
+ ImplChain aChain;
+ const Point aStartPt( nX++, nY );
+
+ // get chain code
+ aChain.ImplBeginAdd( aStartPt );
+ ImplGetChain( rMap, aStartPt, aChain );
+
+ aChain.ImplEndAdd( bInner ? VECT_POLY_OUTLINE_INNER : VECT_POLY_OUTLINE_OUTER );
+
+ const tools::Polygon& rPoly = aChain.ImplGetPoly();
+
+ if( rPoly.GetSize() > 2 )
+ {
+ if( cReduce )
+ {
+ const tools::Rectangle aBound( rPoly.GetBoundRect() );
+
+ if( aBound.GetWidth() > cReduce && aBound.GetHeight() > cReduce )
+ rPolyPoly.Insert( rPoly );
+ }
+ else
+ rPolyPoly.Insert( rPoly );
+ }
+
+ // skip rest of detected contour
+ while( rMap.IsCont( nY, nX ) )
+ nX++;
+ }
+ else
+ {
+ // process done segment
+ const tools::Long nStartSegX = nX++;
+
+ while( rMap.IsDone( nY, nX ) )
+ nX++;
+
+ if( ( ( nX - nStartSegX ) == 1 ) || ( ImplIsUp( rMap, nY, nStartSegX ) != ImplIsUp( rMap, nY, nX - 1 ) ) )
+ bInner = !bInner;
+ }
+ }
+ }
+}
+
+bool ImplGetChain( ImplVectMap& rMap, const Point& rStartPt, ImplChain& rChain )
+{
+ tools::Long nActX = rStartPt.X();
+ tools::Long nActY = rStartPt.Y();
+ sal_uLong nFound;
+ sal_uLong nLastDir = 0;
+ sal_uLong nDir;
+
+ do
+ {
+ nFound = 0;
+
+ // first try last direction
+ tools::Long nTryX = nActX + aImplMove[ nLastDir ].nDX;
+ tools::Long nTryY = nActY + aImplMove[ nLastDir ].nDY;
+
+ if( rMap.IsCont( nTryY, nTryX ) )
+ {
+ rChain.ImplAdd( static_cast<sal_uInt8>(nLastDir) );
+ nActY = nTryY;
+ nActX = nTryX;
+ rMap.Set( nActY, nActX, VECT_DONE_INDEX );
+ nFound = 1;
+ }
+ else
+ {
+ // try other directions
+ for( nDir = 0; nDir < 8; nDir++ )
+ {
+ // we already tried nLastDir
+ if( nDir != nLastDir )
+ {
+ nTryX = nActX + aImplMove[ nDir ].nDX;
+ nTryY = nActY + aImplMove[ nDir ].nDY;
+
+ if( rMap.IsCont( nTryY, nTryX ) )
+ {
+ rChain.ImplAdd( static_cast<sal_uInt8>(nDir) );
+ nActY = nTryY;
+ nActX = nTryX;
+ rMap.Set( nActY, nActX, VECT_DONE_INDEX );
+ nFound = 1;
+ nLastDir = nDir;
+ break;
+ }
+ }
+ }
+ }
+ }
+ while( nFound );
+
+ return true;
+}
+
+bool ImplIsUp( ImplVectMap const & rMap, tools::Long nY, tools::Long nX )
+{
+ if( rMap.IsDone( nY - 1, nX ) )
+ return true;
+ else if( rMap.IsDone( nY + 1, nX ) )
+ return false;
+ else if( rMap.IsDone( nY - 1, nX - 1 ) || rMap.IsDone( nY - 1, nX + 1 ) )
+ return true;
+ else
+ return false;
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/impvect.hxx b/vcl/source/bitmap/impvect.hxx
new file mode 100644
index 0000000000..257d1b5e5a
--- /dev/null
+++ b/vcl/source/bitmap/impvect.hxx
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <vcl/gdimtf.hxx>
+
+namespace tools { class PolyPolygon; }
+
+namespace ImplVectorizer
+{
+ bool ImplVectorize( const Bitmap& rColorBmp, GDIMetaFile& rMtf,
+ sal_uInt8 cReduce, const Link<tools::Long,void>* pProgress );
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/salbmp.cxx b/vcl/source/bitmap/salbmp.cxx
new file mode 100644
index 0000000000..67912262e6
--- /dev/null
+++ b/vcl/source/bitmap/salbmp.cxx
@@ -0,0 +1,335 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <salbmp.hxx>
+#include <o3tl/enumarray.hxx>
+#include <rtl/crc.h>
+
+static BitmapChecksum scanlineChecksum(BitmapChecksum nCrc, const sal_uInt8* bits, int lineBitsCount, sal_uInt8 extraBitsMask)
+{
+ if( lineBitsCount / 8 > 0 )
+ nCrc = rtl_crc32( nCrc, bits, lineBitsCount / 8 );
+ if( extraBitsMask != 0 )
+ {
+ sal_uInt8 extraByte = bits[ lineBitsCount / 8 ] & extraBitsMask;
+ nCrc = rtl_crc32( nCrc, &extraByte, 1 );
+ }
+ return nCrc;
+}
+
+void SalBitmap::updateChecksum() const
+{
+ if (mbChecksumValid)
+ return;
+
+ BitmapChecksum nCrc = 0;
+ SalBitmap* pThis = const_cast<SalBitmap*>(this);
+ BitmapBuffer* pBuf = pThis->AcquireBuffer(BitmapAccessMode::Read);
+ if (pBuf)
+ {
+ nCrc = pBuf->maPalette.GetChecksum();
+ const int lineBitsCount = pBuf->mnWidth * pBuf->mnBitCount;
+ // With 1bpp/4bpp format we need to check only used bits in the last byte.
+ sal_uInt8 extraBitsMask = 0;
+ if( lineBitsCount % 8 != 0 )
+ {
+ const int extraBitsCount = lineBitsCount % 8;
+ switch( RemoveScanline( pBuf->mnFormat ))
+ {
+ case ScanlineFormat::N1BitMsbPal:
+ {
+ static const sal_uInt8 mask1Bit[] = { 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff };
+ extraBitsMask = mask1Bit[ extraBitsCount ];
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ if( pBuf->mnFormat & ScanlineFormat::TopDown )
+ {
+ if( pBuf->mnScanlineSize == lineBitsCount / 8 )
+ nCrc = rtl_crc32(nCrc, pBuf->mpBits, pBuf->mnScanlineSize * pBuf->mnHeight);
+ else // Do not include padding with undefined content in the checksum.
+ for( tools::Long y = 0; y < pBuf->mnHeight; ++y )
+ nCrc = scanlineChecksum(nCrc, pBuf->mpBits + y * pBuf->mnScanlineSize, lineBitsCount, extraBitsMask);
+ }
+ else // Compute checksum in the order of scanlines, to make it consistent between different bitmap implementations.
+ {
+ for( tools::Long y = pBuf->mnHeight - 1; y >= 0; --y )
+ nCrc = scanlineChecksum(nCrc, pBuf->mpBits + y * pBuf->mnScanlineSize, lineBitsCount, extraBitsMask);
+ }
+ pThis->ReleaseBuffer(pBuf, BitmapAccessMode::Read);
+ pThis->mnChecksum = nCrc;
+ pThis->mbChecksumValid = true;
+ }
+ else
+ {
+ pThis->mbChecksumValid = false;
+ }
+}
+
+namespace
+{
+
+class ImplPixelFormat
+{
+protected:
+ const sal_uInt8* mpData;
+public:
+ static ImplPixelFormat* GetFormat( sal_uInt16 nBits, const BitmapPalette& rPalette );
+
+ virtual void StartLine( const sal_uInt8* pLine ) { mpData = pLine; }
+ virtual const BitmapColor& ReadPixel() = 0;
+ virtual ~ImplPixelFormat() { }
+};
+
+class ImplPixelFormat8 : public ImplPixelFormat
+{
+private:
+ const BitmapPalette& mrPalette;
+
+public:
+ explicit ImplPixelFormat8( const BitmapPalette& rPalette )
+ : mrPalette( rPalette )
+ {
+ }
+ virtual const BitmapColor& ReadPixel() override
+ {
+ assert( mrPalette.GetEntryCount() > *mpData );
+ return mrPalette[ *mpData++ ];
+ }
+};
+
+class ImplPixelFormat4 : public ImplPixelFormat
+{
+private:
+ const BitmapPalette& mrPalette;
+ sal_uInt32 mnX;
+ sal_uInt32 mnShift;
+
+public:
+ explicit ImplPixelFormat4( const BitmapPalette& rPalette )
+ : mrPalette( rPalette )
+ , mnX(0)
+ , mnShift(4)
+ {
+ }
+ virtual void StartLine( const sal_uInt8* pLine ) override
+ {
+ mpData = pLine;
+ mnX = 0;
+ mnShift = 4;
+ }
+ virtual const BitmapColor& ReadPixel() override
+ {
+ sal_uInt32 nIdx = ( mpData[mnX >> 1] >> mnShift) & 0x0f;
+ assert( mrPalette.GetEntryCount() > nIdx );
+ const BitmapColor& rColor = mrPalette[nIdx];
+ mnX++;
+ mnShift ^= 4;
+ return rColor;
+ }
+};
+
+class ImplPixelFormat1 : public ImplPixelFormat
+{
+private:
+ const BitmapPalette& mrPalette;
+ sal_uInt32 mnX;
+
+public:
+ explicit ImplPixelFormat1( const BitmapPalette& rPalette )
+ : mrPalette(rPalette)
+ , mnX(0)
+ {
+ }
+ virtual void StartLine( const sal_uInt8* pLine ) override
+ {
+ mpData = pLine;
+ mnX = 0;
+ }
+ virtual const BitmapColor& ReadPixel() override
+ {
+ const BitmapColor& rColor = mrPalette[ (mpData[mnX >> 3 ] >> ( 7 - ( mnX & 7 ) )) & 1];
+ mnX++;
+ return rColor;
+ }
+};
+
+ImplPixelFormat* ImplPixelFormat::GetFormat( sal_uInt16 nBits, const BitmapPalette& rPalette )
+{
+ switch( nBits )
+ {
+ case 1: return new ImplPixelFormat1( rPalette );
+ case 4: return new ImplPixelFormat4( rPalette );
+ case 8: return new ImplPixelFormat8( rPalette );
+ }
+
+ return nullptr;
+}
+
+// Optimized conversion from 1bpp. Currently LO uses 1bpp bitmaps for masks, which is nowadays
+// a lousy obsolete format, as the memory saved is just not worth the cost of fiddling with the bits.
+// Ideally LO should move to RGBA bitmaps. Until then, try to be faster with 1bpp bitmaps.
+typedef void(*WriteColorFunction)( sal_uInt8 color8Bit, sal_uInt8*& dst );
+void writeColorA8(sal_uInt8 color8Bit, sal_uInt8*& dst ) { *dst++ = color8Bit; };
+void writeColorRGBA(sal_uInt8 color8Bit, sal_uInt8*& dst ) { *dst++ = color8Bit; *dst++ = color8Bit; *dst++ = color8Bit; *dst++ = 0xff; };
+typedef void(*WriteBlackWhiteFunction)( sal_uInt8*& dst, int count );
+void writeBlackA8(sal_uInt8*& dst, int count ) { memset( dst, 0, count ); dst += count; };
+void writeWhiteA8(sal_uInt8*& dst, int count ) { memset( dst, 0xff, count ); dst += count; };
+void writeWhiteRGBA(sal_uInt8*& dst, int count ) { memset( dst, 0xff, count * 4 ); dst += count * 4; };
+void writeBlackRGBA(sal_uInt8*& dst, int count )
+{
+ for( int i = 0; i < count; ++i )
+ {
+ dst[0] = 0x00;
+ dst[1] = 0x00;
+ dst[2] = 0x00;
+ dst[3] = 0xff;
+ dst += 4;
+ }
+};
+
+template< WriteColorFunction func, WriteBlackWhiteFunction funcBlack, WriteBlackWhiteFunction funcWhite >
+void writeBlackWhiteData( const sal_uInt8* src, sal_uInt8* dst, int width, int height, int bytesPerRow )
+{
+ for( int y = 0; y < height; ++y )
+ {
+ const sal_uInt8* srcLine = src;
+ int xsize = width;
+ while( xsize >= 64 )
+ {
+ // TODO alignment?
+ const sal_uInt64* src64 = reinterpret_cast< const sal_uInt64* >( src );
+ if( *src64 == 0x00 )
+ funcBlack( dst, 64 );
+ else if( *src64 == static_cast< sal_uInt64 >( -1 ))
+ funcWhite( dst, 64 );
+ else
+ break;
+ src += sizeof( sal_uInt64 );
+ xsize -= 64;
+ }
+ while( xsize >= 8 )
+ {
+ if( *src == 0x00 ) // => eight black pixels
+ funcBlack( dst, 8 );
+ else if( *src == 0xff ) // => eight white pixels
+ funcWhite( dst, 8 );
+ else
+ for( int bit = 7; bit >= 0; --bit )
+ func(( *src >> bit ) & 1 ? 0xff : 0, dst );
+ ++src;
+ xsize -= 8;
+ }
+ for( int bit = 7; bit > 7 - xsize; --bit )
+ func(( *src >> bit ) & 1 ? 0xff : 0, dst );
+ ++src;
+ src = srcLine + bytesPerRow;
+ }
+}
+
+} // namespace
+
+std::unique_ptr< sal_uInt8[] > SalBitmap::convertDataBitCount( const sal_uInt8* src,
+ int width, int height, int bitCount, int bytesPerRow, const BitmapPalette& palette, BitConvert type )
+{
+ assert( bitCount == 1 || bitCount == 4 || bitCount == 8 );
+ static const o3tl::enumarray<BitConvert, int> bpp = { 1, 4, 4 };
+ std::unique_ptr< sal_uInt8[] > data( new sal_uInt8[width * height * bpp[ type ]] );
+
+ if(type == BitConvert::A8 && bitCount == 8 && palette.IsGreyPalette8Bit())
+ { // no actual data conversion
+ for( int y = 0; y < height; ++y )
+ memcpy( data.get() + y * width, src + y * bytesPerRow, width );
+ return data;
+ }
+
+ if(bitCount == 1 && palette.GetEntryCount() == 2 && palette[ 0 ] == COL_BLACK && palette[ 1 ] == COL_WHITE)
+ {
+ switch( type )
+ {
+ case BitConvert::A8 :
+ writeBlackWhiteData< writeColorA8, writeBlackA8, writeWhiteA8 >
+ ( src, data.get(), width, height, bytesPerRow );
+ return data;
+ case BitConvert::BGRA :
+ case BitConvert::RGBA :
+ // BGRA/RGBA is the same, all 3 values get the same value
+ writeBlackWhiteData< writeColorRGBA, writeBlackRGBA, writeWhiteRGBA >
+ ( src, data.get(), width, height, bytesPerRow );
+ return data;
+ }
+ }
+
+ std::unique_ptr<ImplPixelFormat> pSrcFormat(ImplPixelFormat::GetFormat(bitCount, palette));
+
+ const sal_uInt8* pSrcData = src;
+ sal_uInt8* pDstData = data.get();
+
+ sal_uInt32 nY = height;
+ while( nY-- )
+ {
+ pSrcFormat->StartLine( pSrcData );
+
+ sal_uInt32 nX = width;
+ switch( type )
+ {
+ case BitConvert::A8 :
+ while( nX-- )
+ {
+ const BitmapColor& c = pSrcFormat->ReadPixel();
+ *pDstData++ = c.GetBlue();
+ }
+ break;
+ case BitConvert::BGRA :
+ while( nX-- )
+ {
+ const BitmapColor& c = pSrcFormat->ReadPixel();
+ *pDstData++ = c.GetBlue();
+ *pDstData++ = c.GetGreen();
+ *pDstData++ = c.GetRed();
+ *pDstData++ = 0xff;
+ }
+ break;
+ case BitConvert::RGBA :
+ while( nX-- )
+ {
+ const BitmapColor& c = pSrcFormat->ReadPixel();
+ *pDstData++ = c.GetRed();
+ *pDstData++ = c.GetGreen();
+ *pDstData++ = c.GetBlue();
+ *pDstData++ = 0xff;
+ }
+ break;
+ }
+
+ pSrcData += bytesPerRow;
+ }
+ return data;
+}
+
+const basegfx::SystemDependentDataHolder* SalBitmap::accessSystemDependentDataHolder() const
+{
+ // default has no support, returns nullptr
+ return nullptr;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */