diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
commit | 267c6f2ac71f92999e969232431ba04678e7437e (patch) | |
tree | 358c9467650e1d0a1d7227a21dac2e3d08b622b2 /vcl/source/bitmap | |
parent | Initial commit. (diff) | |
download | libreoffice-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')
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: */ |