diff options
Diffstat (limited to 'vcl/source/bitmap')
33 files changed, 9246 insertions, 0 deletions
diff --git a/vcl/source/bitmap/BitmapAlphaClampFilter.cxx b/vcl/source/bitmap/BitmapAlphaClampFilter.cxx new file mode 100644 index 000000000..3db6b43e2 --- /dev/null +++ b/vcl/source/bitmap/BitmapAlphaClampFilter.cxx @@ -0,0 +1,45 @@ +/* -*- 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 <bitmapwriteaccess.hxx> + +BitmapEx BitmapAlphaClampFilter::execute(BitmapEx const& rBitmapEx) const +{ + if (!rBitmapEx.IsTransparent()) + return rBitmapEx; + + AlphaMask aBitmapAlpha(rBitmapEx.GetAlpha()); + { + AlphaScopedWriteAccess pWriteAlpha(aBitmapAlpha); + const Size aSize(rBitmapEx.GetSizePixel()); + + for (long nY = 0; nY < aSize.Height(); ++nY) + { + Scanline pScanAlpha = pWriteAlpha->GetScanline(nY); + + for (long nX = 0; nX < aSize.Width(); ++nX) + { + BitmapColor aBitmapAlphaValue(pWriteAlpha->GetPixelFromData(pScanAlpha, nX)); + if (aBitmapAlphaValue.GetIndex() > mcThreshold) + { + aBitmapAlphaValue.SetIndex(255); + 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 000000000..fe4f63f90 --- /dev/null +++ b/vcl/source/bitmap/BitmapBasicMorphologyFilter.cxx @@ -0,0 +1,360 @@ +/* -*- 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/bitmapaccess.hxx> +#include <vcl/BitmapBasicMorphologyFilter.hxx> + +#include <bitmapwriteaccess.hxx> + +#include <algorithm> + +namespace +{ +struct FilterSharedData +{ + BitmapReadAccess* mpReadAccess; + BitmapWriteAccess* mpWriteAccess; + long mnRadius; + sal_uInt8 mnOutsideVal; + Color maOutsideColor; + + FilterSharedData(Bitmap::ScopedReadAccess& rReadAccess, BitmapScopedWriteAccess& rWriteAccess, + long nRadius, sal_uInt8 nOutsideVal) + : mpReadAccess(rReadAccess.get()) + , mpWriteAccess(rWriteAccess.get()) + , mnRadius(nRadius) + , mnOutsideVal(nOutsideVal) + , maOutsideColor(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(BitmapReadAccess* pReadAccess, long x, long 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(BitmapWriteAccess* pWriteAccess, long x, long 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{ 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(BitmapReadAccess* pReadAccess, long x, long y, sal_uInt8* /*pHint*/ = nullptr) + { + const auto& rSource = pReadAccess->GetColor(y, x); + aResult = Color(MorphologyOp::apply(rSource.GetTransparency(), aResult.GetTransparency()), + MorphologyOp::apply(rSource.GetRed(), aResult.GetRed()), + MorphologyOp::apply(rSource.GetGreen(), aResult.GetGreen()), + MorphologyOp::apply(rSource.GetBlue(), aResult.GetBlue())); + } + + void copy(BitmapWriteAccess* pWriteAccess, long x, long y, sal_uInt8* /*pHint*/ = nullptr) + { + pWriteAccess->SetPixel(y, x, aResult); + } +}; + +bool GetMinMax(long nCenter, long nRadius, long nMaxLimit, long& nMin, long& 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 long nStart, const long nEnd) + { + BitmapReadAccess* pReadAccess = rShared.mpReadAccess; + BitmapWriteAccess* pWriteAccess = rShared.mpWriteAccess; + + const long nLastIndex = pReadAccess->Width() - 1; + + for (long y = nStart; y <= nEnd; y++) + { + // Optimization + sal_uInt8* const pSourceHint = pReadAccess->GetScanline(y); + sal_uInt8* const pDestHint = pWriteAccess->GetScanline(y); + for (long 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 + long iMin, iMax; + const bool bLookOutside = GetMinMax(x, rShared.mnRadius, nLastIndex, iMin, iMax); + Value<MorphologyOp, nComponentWidth> aResult(rShared, bLookOutside); + for (long i = iMin; i <= iMax; ++i) + aResult.apply(pReadAccess, i, y, pSourceHint); + + aResult.copy(pWriteAccess, x, y, pDestHint); + } + } + } + + static void Vertical(FilterSharedData const& rShared, const long nStart, const long nEnd) + { + BitmapReadAccess* pReadAccess = rShared.mpReadAccess; + BitmapWriteAccess* pWriteAccess = rShared.mpWriteAccess; + + const long nLastIndex = pReadAccess->Height() - 1; + + for (long x = nStart; x <= nEnd; x++) + { + for (long 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 + long iMin, iMax; + const bool bLookOutside = GetMinMax(y, rShared.mnRadius, nLastIndex, iMin, iMax); + Value<MorphologyOp, nComponentWidth> aResult(rShared, bLookOutside); + for (long i = iMin; i <= iMax; ++i) + aResult.apply(pReadAccess, x, i); + + aResult.copy(pWriteAccess, x, y); + } + } + } +}; + +typedef void (*passFn)(FilterSharedData const& rShared, long nStart, long nEnd); + +class FilterTask : public comphelper::ThreadTask +{ + passFn mpFunction; + FilterSharedData& mrShared; + long mnStart; + long mnEnd; + +public: + explicit FilterTask(const std::shared_ptr<comphelper::ThreadTaskTag>& pTag, passFn pFunction, + FilterSharedData& rShared, long nStart, long nEnd) + : comphelper::ThreadTask(pTag) + , mpFunction(pFunction) + , mrShared(rShared) + , mnStart(nStart) + , mnEnd(nEnd) + { + } + + virtual void doWork() override { mpFunction(mrShared, mnStart, mnEnd); } +}; + +constexpr long nThreadStrip = 16; + +template <typename MorphologyOp, int nComponentWidth> +void runFilter(Bitmap& rBitmap, const long 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(); + + { + Bitmap::ScopedReadAccess pReadAccess(rBitmap); + BitmapScopedWriteAccess pWriteAccess(rBitmap); + FilterSharedData aSharedData(pReadAccess, pWriteAccess, nRadius, nOutsideVal); + + const long nLastIndex = pReadAccess->Height() - 1; + long nStripStart = 0; + for (; nStripStart < nLastIndex - nThreadStrip; nStripStart += nThreadStrip) + { + long 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); + } + { + Bitmap::ScopedReadAccess pReadAccess(rBitmap); + BitmapScopedWriteAccess pWriteAccess(rBitmap); + FilterSharedData aSharedData(pReadAccess, pWriteAccess, nRadius, nOutsideVal); + + const long nLastIndex = pReadAccess->Width() - 1; + long nStripStart = 0; + for (; nStripStart < nLastIndex - nThreadStrip; nStripStart += nThreadStrip) + { + long 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 + { + { + Bitmap::ScopedReadAccess pReadAccess(rBitmap); + BitmapScopedWriteAccess pWriteAccess(rBitmap); + FilterSharedData aSharedData(pReadAccess, pWriteAccess, nRadius, nOutsideVal); + long nFirstIndex = 0; + long nLastIndex = pReadAccess->Height() - 1; + myPass::Horizontal(aSharedData, nFirstIndex, nLastIndex); + } + { + Bitmap::ScopedReadAccess pReadAccess(rBitmap); + BitmapScopedWriteAccess pWriteAccess(rBitmap); + FilterSharedData aSharedData(pReadAccess, pWriteAccess, nRadius, nOutsideVal); + long nFirstIndex = 0; + long 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.GetMask()); +} + +Bitmap BitmapBasicMorphologyFilter::filter(Bitmap const& rBitmap) const +{ + Bitmap bitmapCopy(rBitmap); + ScanlineFormat nScanlineFormat; + { + Bitmap::ScopedReadAccess pReadAccess(bitmapCopy); + nScanlineFormat = pReadAccess->GetScanlineFormat(); + } + + 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 000000000..390319d8a --- /dev/null +++ b/vcl/source/bitmap/BitmapColorQuantizationFilter.cxx @@ -0,0 +1,232 @@ +/* -*- 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 <algorithm> + +#include <vcl/bitmap.hxx> +#include <vcl/bitmapex.hxx> +#include <vcl/BitmapColorQuantizationFilter.hxx> +#include <vcl/bitmapaccess.hxx> + +#include <bitmapwriteaccess.hxx> + +#include <cstdlib> + +BitmapEx BitmapColorQuantizationFilter::execute(BitmapEx const& aBitmapEx) const +{ + Bitmap aBitmap = aBitmapEx.GetBitmap(); + + bool bRet = false; + + if (aBitmap.GetColorCount() <= sal_Int64(mnNewColorCount)) + { + bRet = true; + } + else + { + Bitmap::ScopedReadAccess pRAcc(aBitmap); + sal_uInt16 nBitCount; + + auto const cappedNewColorCount = std::min(mnNewColorCount, sal_uInt16(256)); + + if (cappedNewColorCount < 17) + nBitCount = 4; + else + nBitCount = 8; + + if (pRAcc) + { + 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 long nWidth = pRAcc->Width(); + const long nHeight = pRAcc->Height(); + std::unique_ptr<PopularColorCount[]> pCountTable(new PopularColorCount[nTotalColors]); + + memset(pCountTable.get(), 0, nTotalColors * sizeof(PopularColorCount)); + + for (long nR = 0, nIndex = 0; nR < 256; nR += nColorOffset) + { + for (long nG = 0; nG < 256; nG += nColorOffset) + { + for (long nB = 0; nB < 256; nB += nColorOffset) + { + pCountTable[nIndex].mnIndex = nIndex; + nIndex++; + } + } + } + + if (pRAcc->HasPalette()) + { + for (long nY = 0; nY < nHeight; nY++) + { + Scanline pScanlineRead = pRAcc->GetScanline(nY); + for (long 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 (long nY = 0; nY < nHeight; nY++) + { + Scanline pScanlineRead = pRAcc->GetScanline(nY); + for (long 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(), nBitCount, &aNewPal); + BitmapScopedWriteAccess pWAcc(aNewBmp); + + if (pWAcc) + { + BitmapColor aDstCol(sal_uInt8(0)); + std::unique_ptr<sal_uInt8[]> pIndexMap(new sal_uInt8[nTotalColors]); + + for (long nR = 0, nIndex = 0; nR < 256; nR += nColorOffset) + { + for (long nG = 0; nG < 256; nG += nColorOffset) + { + for (long 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 (long nY = 0; nY < nHeight; nY++) + { + Scanline pScanline = pWAcc->GetScanline(nY); + Scanline pScanlineRead = pRAcc->GetScanline(nY); + for (long 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 (long nY = 0; nY < nHeight; nY++) + { + Scanline pScanline = pWAcc->GetScanline(nY); + Scanline pScanlineRead = pRAcc->GetScanline(nY); + + for (long 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(); + bRet = true; + } + + pCountTable.reset(); + pRAcc.reset(); + + if (bRet) + { + const MapMode aMap(aBitmap.GetPrefMapMode()); + const Size aSize(aBitmap.GetPrefSize()); + + aBitmap = aNewBmp; + + aBitmap.SetPrefMapMode(aMap); + aBitmap.SetPrefSize(aSize); + } + } + } + + if (bRet) + return BitmapEx(aBitmap); + + return BitmapEx(); +} + +/* 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 000000000..343642b77 --- /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 <bitmapwriteaccess.hxx> +#include <BitmapColorizeFilter.hxx> + +BitmapEx BitmapColorizeFilter::execute(BitmapEx const& rBitmapEx) const +{ + Bitmap aBitmap = rBitmapEx.GetBitmap(); + BitmapScopedWriteAccess pWriteAccess(aBitmap); + + if (!pWriteAccess) + return rBitmapEx; + + BitmapColor aBitmapColor; + const long nW = pWriteAccess->Width(); + const long nH = pWriteAccess->Height(); + std::vector<sal_uInt8> aMapR(256); + std::vector<sal_uInt8> aMapG(256); + std::vector<sal_uInt8> aMapB(256); + long nX; + long 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] = MinMax((nX + cR) / 2, 0, 255); + aMapG[nX] = MinMax((nX + cG) / 2, 0, 255); + aMapB[nX] = MinMax((nX + cB) / 2, 0, 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 000000000..94e0c6e02 --- /dev/null +++ b/vcl/source/bitmap/BitmapConvolutionMatrixFilter.cxx @@ -0,0 +1,208 @@ +/* -*- 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/bitmapaccess.hxx> +#include <vcl/BitmapConvolutionMatrixFilter.hxx> +#include <vcl/BitmapSharpenFilter.hxx> + +#include <bitmapwriteaccess.hxx> +#include <array> + +BitmapEx BitmapConvolutionMatrixFilter::execute(BitmapEx const& rBitmapEx) const +{ + Bitmap aBitmap(rBitmapEx.GetBitmap()); + + const long nDivisor = 8; + Bitmap::ScopedReadAccess pReadAcc(aBitmap); + bool bRet = false; + + if (pReadAcc) + { + Bitmap aNewBmp(aBitmap.GetSizePixel(), 24); + BitmapScopedWriteAccess pWriteAcc(aNewBmp); + + if (pWriteAcc) + { + const long nWidth = pWriteAcc->Width(), nWidth2 = nWidth + 2; + const long nHeight = pWriteAcc->Height(), nHeight2 = nHeight + 2; + std::unique_ptr<long[]> pColm(new long[nWidth2]); + std::unique_ptr<long[]> pRows(new long[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; + long nY, nX, i, nSumR, nSumG, nSumB, nMatrixVal, nTmp; + std::array<std::array<long, 256>, 9> aKoeff; + long* 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>(MinMax(nSumR / nDivisor, 0, 255)), + static_cast<sal_uInt8>(MinMax(nSumG / nDivisor, 0, 255)), + static_cast<sal_uInt8>(MinMax(nSumB / nDivisor, 0, 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(); + + bRet = true; + } + + pReadAcc.reset(); + + if (bRet) + { + const MapMode aMap(aBitmap.GetPrefMapMode()); + const Size aPrefSize(aBitmap.GetPrefSize()); + + aBitmap = aNewBmp; + + aBitmap.SetPrefMapMode(aMap); + aBitmap.SetPrefSize(aPrefSize); + } + } + + if (bRet) + return BitmapEx(aBitmap); + + return BitmapEx(); +} + +static const long 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 000000000..5ade2451a --- /dev/null +++ b/vcl/source/bitmap/BitmapDisabledImageFilter.cxx @@ -0,0 +1,61 @@ +/* -*- 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/bitmapaccess.hxx> + +#include <bitmapwriteaccess.hxx> +#include <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 + sal_uInt16 nBitCount = rBitmapEx.GetBitCount(); + if (nBitCount < 8) + nBitCount = 8; + + const BitmapPalette* pPal = nBitCount == 8 ? &Bitmap::GetGreyPalette(256) : nullptr; + Bitmap aGrey(aSize, nBitCount, pPal); + BitmapScopedWriteAccess pGrey(aGrey); + + BitmapEx aReturnBitmap; + Bitmap aReadBitmap(rBitmapEx.GetBitmap()); + Bitmap::ScopedReadAccess pRead(aReadBitmap); + if (pRead && pGrey) + { + for (long nY = 0; nY < aSize.Height(); ++nY) + { + Scanline pGreyScan = pGrey->GetScanline(nY); + Scanline pReadScan = pRead->GetScanline(nY); + + for (long nX = 0; nX < 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(nLum, nLum, nLum, aColor.GetAlpha()); + pGrey->SetPixelOnData(pGreyScan, nX, aGreyValue); + } + } + } + + if (rBitmapEx.IsTransparent()) + { + aReturnBitmap = BitmapEx(aGrey, rBitmapEx.GetAlpha()); + } + 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 000000000..65d4b3f41 --- /dev/null +++ b/vcl/source/bitmap/BitmapDuoToneFilter.cxx @@ -0,0 +1,65 @@ +/* -*- 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/bitmapaccess.hxx> + +#include <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 long nWidth = aBitmap.GetSizePixel().Width(); + const long nHeight = aBitmap.GetSizePixel().Height(); + + Bitmap aResultBitmap(aBitmap.GetSizePixel(), 24); + Bitmap::ScopedReadAccess pReadAcc(aBitmap); + BitmapScopedWriteAccess pWriteAcc(aResultBitmap); + const BitmapColor aColorOne(static_cast<sal_uInt8>(mnColorOne >> 16), + static_cast<sal_uInt8>(mnColorOne >> 8), + static_cast<sal_uInt8>(mnColorOne)); + const BitmapColor aColorTwo(static_cast<sal_uInt8>(mnColorTwo >> 16), + static_cast<sal_uInt8>(mnColorTwo >> 8), + static_cast<sal_uInt8>(mnColorTwo)); + + for (long x = 0; x < nWidth; x++) + { + for (long 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 000000000..4ea239b1a --- /dev/null +++ b/vcl/source/bitmap/BitmapEmbossGreyFilter.cxx @@ -0,0 +1,158 @@ +/* -*- 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 <algorithm> + +#include <tools/helpers.hxx> +#include <vcl/bitmap.hxx> +#include <vcl/bitmapex.hxx> +#include <vcl/bitmapaccess.hxx> +#include <vcl/BitmapEmbossGreyFilter.hxx> + +#include <bitmapwriteaccess.hxx> + +BitmapEx BitmapEmbossGreyFilter::execute(BitmapEx const& rBitmapEx) const +{ + Bitmap aBitmap(rBitmapEx.GetBitmap()); + + bool bRet = aBitmap.ImplMakeGreyscales(256); + + if (bRet) + { + bRet = false; + + Bitmap::ScopedReadAccess pReadAcc(aBitmap); + + if (pReadAcc) + { + Bitmap aNewBmp(aBitmap.GetSizePixel(), 8, &pReadAcc->GetPalette()); + BitmapScopedWriteAccess pWriteAcc(aNewBmp); + + if (pWriteAcc) + { + BitmapColor aGrey(sal_uInt8(0)); + const long nWidth = pWriteAcc->Width(); + const long nHeight = pWriteAcc->Height(); + long nGrey11, nGrey12, nGrey13; + long nGrey21, nGrey22, nGrey23; + long nGrey31, nGrey32, nGrey33; + double fAzim = basegfx::deg2rad(mnAzimuthAngle100 * 0.01); + double fElev = basegfx::deg2rad(mnElevationAngle100 * 0.01); + std::unique_ptr<long[]> pHMap(new long[nWidth + 2]); + std::unique_ptr<long[]> pVMap(new long[nHeight + 2]); + long nX, nY, nNx, nNy, nDotL; + const long nLx = FRound(cos(fAzim) * cos(fElev) * 255.0); + const long nLy = FRound(sin(fAzim) * cos(fElev) * 255.0); + const long nLz = FRound(sin(fElev) * 255.0); + const auto nZ2 = ((6 * 255) / 4) * ((6 * 255) / 4); + const long nNzLz = ((6 * 255) / 4) * nLz; + const sal_uInt8 cLz = static_cast<sal_uInt8>(std::clamp(nLz, 0L, 255L)); + + // 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++) + { + nNx = nGrey11 + nGrey21 + nGrey31 - nGrey13 - nGrey23 - nGrey33; + nNy = nGrey31 + nGrey32 + nGrey33 - nGrey11 - nGrey12 - nGrey13; + + if (!nNx && !nNy) + { + aGrey.SetIndex(cLz); + } + else if ((nDotL = nNx * nLx + nNy * nLy + nNzLz) < 0) + { + aGrey.SetIndex(0); + } + else + { + const double fGrey + = nDotL / sqrt(static_cast<double>(nNx * nNx + nNy * nNy + nZ2)); + aGrey.SetIndex(static_cast<sal_uInt8>(std::clamp(fGrey, 0.0, 255.0))); + } + + pWriteAcc->SetPixelOnData(pScanline, nX, aGrey); + + if (nX < (nWidth - 1)) + { + const long 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(); + bRet = true; + } + + pReadAcc.reset(); + + if (bRet) + { + const MapMode aMap(aBitmap.GetPrefMapMode()); + const Size aPrefSize(aBitmap.GetPrefSize()); + + aBitmap = aNewBmp; + + 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/BitmapFastScaleFilter.cxx b/vcl/source/bitmap/BitmapFastScaleFilter.cxx new file mode 100644 index 000000000..cff8f3148 --- /dev/null +++ b/vcl/source/bitmap/BitmapFastScaleFilter.cxx @@ -0,0 +1,131 @@ +/* -*- 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 <vcl/bitmapex.hxx> +#include <vcl/bitmapaccess.hxx> + +#include <bitmapwriteaccess.hxx> +#include <BitmapFastScaleFilter.hxx> +#include <sal/log.hxx> + +BitmapEx BitmapFastScaleFilter::execute(BitmapEx const& rBitmapEx) const +{ + SAL_INFO("vcl.gdi", "BitmapFastScaleFilter::execute()"); + + Bitmap aBitmap(rBitmapEx.GetBitmap()); + + const Size aSizePix(aBitmap.GetSizePixel()); + const long nNewWidth = FRound(aSizePix.Width() * mfScaleX); + const long nNewHeight = FRound(aSizePix.Height() * mfScaleY); + bool bRet = false; + + SAL_INFO("vcl.gdi", "New width: " << nNewWidth << "\nNew height: " << nNewHeight); + + if (nNewWidth && nNewHeight) + { + Bitmap::ScopedReadAccess pReadAcc(aBitmap); + + if (pReadAcc) + { + Bitmap aNewBmp(Size(nNewWidth, nNewHeight), aBitmap.GetBitCount(), + &pReadAcc->GetPalette()); + BitmapScopedWriteAccess pWriteAcc(aNewBmp); + + if (pWriteAcc) + { + const long nScanlineSize = pWriteAcc->GetScanlineSize(); + const long nNewWidth1 = nNewWidth - 1; + const long nNewHeight1 = nNewHeight - 1; + + if (nNewWidth1 && nNewHeight1) + { + const double nWidth = pReadAcc->Width(); + const double nHeight = pReadAcc->Height(); + std::unique_ptr<long[]> pLutX(new long[nNewWidth]); + std::unique_ptr<long[]> pLutY(new long[nNewHeight]); + + for (long nX = 0; nX < nNewWidth; nX++) + { + pLutX[nX] = long(nX * nWidth / nNewWidth); + } + + for (long nY = 0; nY < nNewHeight; nY++) + { + pLutY[nY] = long(nY * nHeight / nNewHeight); + } + + long nActY = 0; + while (nActY < nNewHeight) + { + long nMapY = pLutY[nActY]; + Scanline pScanline = pWriteAcc->GetScanline(nActY); + Scanline pScanlineRead = pReadAcc->GetScanline(nMapY); + + for (long 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"); + } + } + } + + Bitmap aMask(rBitmapEx.GetMask()); + + if (bRet && (rBitmapEx.GetTransparentType() == TransparentType::Bitmap) && !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 000000000..da51daedf --- /dev/null +++ b/vcl/source/bitmap/BitmapFilterStackBlur.cxx @@ -0,0 +1,629 @@ +/* -*- 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/bitmapaccess.hxx> +#include <bitmapwriteaccess.hxx> +#include <sal/log.hxx> + +#include <comphelper/threadpool.hxx> + +namespace +{ +static 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 }; + +static 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; + long mnRadius; + long mnComponentWidth; + long mnDiv; + long mnColorChannels; + + BlurSharedData(BitmapReadAccess* pReadAccess, BitmapWriteAccess* pWriteAccess, long aRadius, + long nComponentWidth, long 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<long> maPositionTable; + std::vector<long> maWeightTable; + + std::vector<long> mnSumVector; + std::vector<long> mnInSumVector; + std::vector<long> 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(long nLastIndex) + { + for (long i = 0; i < maShared.mnDiv; i++) + { + maPositionTable[i] = std::clamp(i - maShared.mnRadius, 0L, nLastIndex); + maWeightTable[i] = maShared.mnRadius + 1 - std::abs(i - maShared.mnRadius); + } + } + + long getMultiplyValue() { return static_cast<long>(constMultiplyTable[maShared.mnRadius]); } + + long getShiftValue() { return static_cast<long>(constShiftTable[maShared.mnRadius]); } +}; + +typedef void (*BlurRangeFn)(BlurSharedData const& rShared, long nStartY, long nEndY); + +class BlurTask : public comphelper::ThreadTask +{ + BlurRangeFn mpBlurFunction; + BlurSharedData& mrShared; + long mnStartY; + long mnEndY; + +public: + explicit BlurTask(const std::shared_ptr<comphelper::ThreadTaskTag>& pTag, + BlurRangeFn pBlurFunction, BlurSharedData& rShared, long nStartY, long 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(long*& pValue1, long nConstant) + { + pValue1[0] += nConstant; + pValue1[1] += nConstant; + pValue1[2] += nConstant; + } + + static inline void set(long*& pValue1, long nConstant) + { + pValue1[0] = nConstant; + pValue1[1] = nConstant; + pValue1[2] = nConstant; + } + + static inline void add(long*& pValue1, sal_uInt8*& pValue2) + { + pValue1[0] += pValue2[0]; + pValue1[1] += pValue2[1]; + pValue1[2] += pValue2[2]; + } + + static inline void add(long*& pValue1, long*& pValue2) + { + pValue1[0] += pValue2[0]; + pValue1[1] += pValue2[1]; + pValue1[2] += pValue2[2]; + } + + static inline void sub(long*& pValue1, sal_uInt8*& pValue2) + { + pValue1[0] -= pValue2[0]; + pValue1[1] -= pValue2[1]; + pValue1[2] -= pValue2[2]; + } + + static inline void sub(long*& pValue1, long*& pValue2) + { + pValue1[0] -= pValue2[0]; + pValue1[1] -= pValue2[1]; + pValue1[2] -= pValue2[2]; + } + + static inline void assignPtr(sal_uInt8*& pValue1, sal_uInt8*& pValue2) + { + pValue1[0] = pValue2[0]; + pValue1[1] = pValue2[1]; + pValue1[2] = pValue2[2]; + } + + static inline void assignMulAndShr(sal_uInt8*& result, long*& sum, long multiply, long 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(long*& pValue1, long nConstant) { pValue1[0] += nConstant; } + + static inline void set(long*& pValue1, long nConstant) { pValue1[0] = nConstant; } + + static inline void add(long*& pValue1, sal_uInt8*& pValue2) { pValue1[0] += pValue2[0]; } + + static inline void add(long*& pValue1, long*& pValue2) { pValue1[0] += pValue2[0]; } + + static inline void sub(long*& pValue1, sal_uInt8*& pValue2) { pValue1[0] -= pValue2[0]; } + + static inline void sub(long*& pValue1, long*& pValue2) { pValue1[0] -= pValue2[0]; } + + static inline void assignPtr(sal_uInt8*& pValue1, sal_uInt8*& pValue2) + { + pValue1[0] = pValue2[0]; + } + + static inline void assignMulAndShr(sal_uInt8*& result, long*& sum, long multiply, long shift) + { + result[0] = (multiply * sum[0]) >> shift; + } +}; + +template <typename SumFunction> +void stackBlurHorizontal(BlurSharedData const& rShared, long nStart, long nEnd) +{ + BitmapReadAccess* pReadAccess = rShared.mpReadAccess; + BitmapWriteAccess* pWriteAccess = rShared.mpWriteAccess; + + BlurArrays aArrays(rShared); + + sal_uInt8* pStack = aArrays.maStackBuffer.data(); + sal_uInt8* pStackPtr; + + long nWidth = pReadAccess->Width(); + long nLastIndexX = nWidth - 1; + + long nMultiplyValue = aArrays.getMultiplyValue(); + long nShiftValue = aArrays.getShiftValue(); + + long nRadius = rShared.mnRadius; + long nComponentWidth = rShared.mnComponentWidth; + long nDiv = rShared.mnDiv; + + Scanline pSourcePointer; + Scanline pDestinationPointer; + + long nXPosition; + long nStackIndex; + long nStackIndexStart; + long nWeight; + + aArrays.initializeWeightAndPositions(nLastIndexX); + + long* nSum = aArrays.mnSumVector.data(); + long* nInSum = aArrays.mnInSumVector.data(); + long* nOutSum = aArrays.mnOutSumVector.data(); + + long* pPositionPointer = aArrays.maPositionTable.data(); + long* pWeightPointer = aArrays.maWeightTable.data(); + + for (long y = nStart; y <= nEnd; y++) + { + SumFunction::set(nSum, 0L); + SumFunction::set(nInSum, 0L); + SumFunction::set(nOutSum, 0L); + + // 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 (long 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 (long 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, long nStart, long nEnd) +{ + BitmapReadAccess* pReadAccess = rShared.mpReadAccess; + BitmapWriteAccess* pWriteAccess = rShared.mpWriteAccess; + + BlurArrays aArrays(rShared); + + sal_uInt8* pStack = aArrays.maStackBuffer.data(); + sal_uInt8* pStackPtr; + + long nHeight = pReadAccess->Height(); + long nLastIndexY = nHeight - 1; + + long nMultiplyValue = aArrays.getMultiplyValue(); + long nShiftValue = aArrays.getShiftValue(); + + long nRadius = rShared.mnRadius; + long nComponentWidth = rShared.mnComponentWidth; + long nDiv = rShared.mnDiv; + + Scanline pSourcePointer; + Scanline pDestinationPointer; + + long nYPosition; + long nStackIndex; + long nStackIndexStart; + long nWeight; + + aArrays.initializeWeightAndPositions(nLastIndexY); + + long* nSum = aArrays.mnSumVector.data(); + long* nInSum = aArrays.mnInSumVector.data(); + long* nOutSum = aArrays.mnOutSumVector.data(); + long* pPositionPointer = aArrays.maPositionTable.data(); + long* pWeightPointer = aArrays.maWeightTable.data(); + + for (long x = nStart; x <= nEnd; x++) + { + SumFunction::set(nSum, 0L); + SumFunction::set(nInSum, 0L); + SumFunction::set(nOutSum, 0L); + + // 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 (long 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 (long 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 long nThreadStrip = 16; + +void runStackBlur(Bitmap& rBitmap, const long nRadius, const long nComponentWidth, + const long nColorChannels, BlurRangeFn pBlurHorizontalFn, + BlurRangeFn pBlurVerticalFn, const bool bParallel) +{ + if (bParallel) + { + try + { + comphelper::ThreadPool& rShared = comphelper::ThreadPool::getSharedOptimalPool(); + auto pTag = comphelper::ThreadPool::createThreadTaskTag(); + + { + Bitmap::ScopedReadAccess pReadAccess(rBitmap); + BitmapScopedWriteAccess pWriteAccess(rBitmap); + BlurSharedData aSharedData(pReadAccess.get(), pWriteAccess.get(), nRadius, + nComponentWidth, nColorChannels); + + const long nLastIndex = pReadAccess->Height() - 1; + long nStripStart = 0; + for (; nStripStart < nLastIndex - nThreadStrip; nStripStart += nThreadStrip) + { + long nStripEnd = nStripStart + nThreadStrip - 1; + auto pTask(std::make_unique<BlurTask>(pTag, pBlurHorizontalFn, aSharedData, + nStripStart, nStripEnd)); + rShared.pushTask(std::move(pTask)); + } + // Do the last (or the only) strip in main thread without threading overhead + pBlurHorizontalFn(aSharedData, nStripStart, nLastIndex); + rShared.waitUntilDone(pTag); + } + { + Bitmap::ScopedReadAccess pReadAccess(rBitmap); + BitmapScopedWriteAccess pWriteAccess(rBitmap); + BlurSharedData aSharedData(pReadAccess.get(), pWriteAccess.get(), nRadius, + nComponentWidth, nColorChannels); + + const long nLastIndex = pReadAccess->Width() - 1; + long nStripStart = 0; + for (; nStripStart < nLastIndex - nThreadStrip; nStripStart += nThreadStrip) + { + long nStripEnd = nStripStart + nThreadStrip - 1; + auto pTask(std::make_unique<BlurTask>(pTag, pBlurVerticalFn, aSharedData, + nStripStart, nStripEnd)); + rShared.pushTask(std::move(pTask)); + } + // Do the last (or the only) strip in main thread without threading overhead + pBlurVerticalFn(aSharedData, nStripStart, nLastIndex); + rShared.waitUntilDone(pTag); + } + } + catch (...) + { + SAL_WARN("vcl.gdi", "threaded bitmap blurring failed"); + } + } + else + { + { + Bitmap::ScopedReadAccess pReadAccess(rBitmap); + BitmapScopedWriteAccess pWriteAccess(rBitmap); + BlurSharedData aSharedData(pReadAccess.get(), pWriteAccess.get(), nRadius, + nComponentWidth, nColorChannels); + long nFirstIndex = 0; + long nLastIndex = pReadAccess->Height() - 1; + pBlurHorizontalFn(aSharedData, nFirstIndex, nLastIndex); + } + { + Bitmap::ScopedReadAccess pReadAccess(rBitmap); + BitmapScopedWriteAccess pWriteAccess(rBitmap); + BlurSharedData aSharedData(pReadAccess.get(), pWriteAccess.get(), nRadius, + nComponentWidth, nColorChannels); + long nFirstIndex = 0; + long 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 long 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 long 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.GetMask()); +} + +Bitmap BitmapFilterStackBlur::filter(Bitmap const& rBitmap) const +{ + Bitmap bitmapCopy(rBitmap); + ScanlineFormat nScanlineFormat; + { + Bitmap::ScopedReadAccess pReadAccess(bitmapCopy); + nScanlineFormat = pReadAccess->GetScanlineFormat(); + } + + 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 000000000..c264dcd13 --- /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/bitmapaccess.hxx> +#include <vcl/BitmapGaussianSeparableBlurFilter.hxx> + +#include <bitmapwriteaccess.hxx> + +BitmapEx BitmapGaussianSeparableBlurFilter::execute(BitmapEx const& rBitmapEx) const +{ + Bitmap aBitmap(rBitmapEx.GetBitmap()); + + const long nWidth = aBitmap.GetSizePixel().Width(); + const long 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); + + Bitmap::ScopedReadAccess pReadAcc(aBitmap); + + // switch coordinates as convolution pass transposes result + Bitmap aNewBitmap(Size(nHeight, nWidth), 24); + + 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 = Bitmap::ScopedReadAccess(aBitmap); + aNewBitmap = Bitmap(Size(nWidth, nHeight), 24); + 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>(MinMax(aValueRed / aSum, 0, 255)), + static_cast<sal_uInt8>(MinMax(aValueGreen / aSum, 0, 255)), + static_cast<sal_uInt8>(MinMax(aValueBlue / aSum, 0, 255))); + + 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/BitmapInterpolateScaleFilter.cxx b/vcl/source/bitmap/BitmapInterpolateScaleFilter.cxx new file mode 100644 index 000000000..73f521480 --- /dev/null +++ b/vcl/source/bitmap/BitmapInterpolateScaleFilter.cxx @@ -0,0 +1,237 @@ +/* -*- 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/bitmapaccess.hxx> + +#include <bitmapwriteaccess.hxx> +#include <BitmapFastScaleFilter.hxx> +#include <BitmapInterpolateScaleFilter.hxx> + +BitmapEx BitmapInterpolateScaleFilter::execute(BitmapEx const& rBitmapEx) const +{ + Bitmap aBitmap(rBitmapEx.GetBitmap()); + + const Size aSizePix(aBitmap.GetSizePixel()); + const long nNewWidth = FRound(aSizePix.Width() * mfScaleX); + const long nNewHeight = FRound(aSizePix.Height() * mfScaleY); + bool bRet = false; + + if ((nNewWidth > 1) && (nNewHeight > 1)) + { + Bitmap::ScopedReadAccess pReadAcc(aBitmap); + if (pReadAcc) + { + long nWidth = pReadAcc->Width(); + long nHeight = pReadAcc->Height(); + Bitmap aNewBmp(Size(nNewWidth, nHeight), 24); + BitmapScopedWriteAccess pWriteAcc(aNewBmp); + + if (pWriteAcc) + { + const long nNewWidth1 = nNewWidth - 1; + const long nWidth1 = pReadAcc->Width() - 1; + const double fRevScaleX = static_cast<double>(nWidth1) / nNewWidth1; + + std::unique_ptr<long[]> pLutInt(new long[nNewWidth]); + std::unique_ptr<long[]> pLutFrac(new long[nNewWidth]); + + for (long nX = 0, nTemp = nWidth - 2; nX < nNewWidth; nX++) + { + double fTemp = nX * fRevScaleX; + pLutInt[nX] = MinMax(static_cast<long>(fTemp), 0, nTemp); + fTemp -= pLutInt[nX]; + pLutFrac[nX] = static_cast<long>(fTemp * 1024.); + } + + for (long nY = 0; nY < nHeight; nY++) + { + Scanline pScanlineRead = pReadAcc->GetScanline(nY); + if (1 == nWidth) + { + BitmapColor aCol0; + if (pReadAcc->HasPalette()) + { + aCol0 = pReadAcc->GetPaletteColor( + pReadAcc->GetIndexFromData(pScanlineRead, 0)); + } + else + { + aCol0 = pReadAcc->GetPixelFromData(pScanlineRead, 0); + } + + Scanline pScanline = pWriteAcc->GetScanline(nY); + for (long nX = 0; nX < nNewWidth; nX++) + { + pWriteAcc->SetPixelOnData(pScanline, nX, aCol0); + } + } + else + { + Scanline pScanline = pWriteAcc->GetScanline(nY); + for (long nX = 0; nX < nNewWidth; nX++) + { + long 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]; + + long lXR0 = aCol0.GetRed(); + long lXG0 = aCol0.GetGreen(); + long lXB0 = aCol0.GetBlue(); + long lXR1 = aCol1.GetRed() - lXR0; + long lXG1 = aCol1.GetGreen() - lXG0; + long 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), 24); + pReadAcc = Bitmap::ScopedReadAccess(aBitmap); + pWriteAcc = BitmapScopedWriteAccess(aNewBmp); + + if (pReadAcc && pWriteAcc) + { + const long nNewHeight1 = nNewHeight - 1; + const long nHeight1 = pReadAcc->Height() - 1; + const double fRevScaleY = static_cast<double>(nHeight1) / nNewHeight1; + + std::unique_ptr<long[]> pLutInt(new long[nNewHeight]); + std::unique_ptr<long[]> pLutFrac(new long[nNewHeight]); + + for (long nY = 0, nTemp = nHeight - 2; nY < nNewHeight; nY++) + { + double fTemp = nY * fRevScaleY; + pLutInt[nY] = MinMax(static_cast<long>(fTemp), 0, nTemp); + fTemp -= pLutInt[nY]; + pLutFrac[nY] = static_cast<long>(fTemp * 1024.); + } + + // after 1st step, bitmap *is* 24bit format (see above) + OSL_ENSURE(!pReadAcc->HasPalette(), "OOps, somehow ImplScaleInterpolate " + "in-between format has palette, should not " + "happen (!)"); + + for (long nX = 0; nX < nNewWidth; nX++) + { + if (1 == nHeight) + { + BitmapColor aCol0 = pReadAcc->GetPixel(0, nX); + + for (long nY = 0; nY < nNewHeight; nY++) + { + pWriteAcc->SetPixel(nY, nX, aCol0); + } + } + else + { + for (long nY = 0; nY < nNewHeight; nY++) + { + long nTemp = pLutInt[nY]; + + BitmapColor aCol0 = pReadAcc->GetPixel(nTemp++, nX); + BitmapColor aCol1 = pReadAcc->GetPixel(nTemp, nX); + + nTemp = pLutFrac[nY]; + + long lXR0 = aCol0.GetRed(); + long lXG0 = aCol0.GetGreen(); + long lXB0 = aCol0.GetBlue(); + long lXR1 = aCol1.GetRed() - lXR0; + long lXG1 = aCol1.GetGreen() - lXG0; + long 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 000000000..45013b143 --- /dev/null +++ b/vcl/source/bitmap/BitmapLightenFilter.cxx @@ -0,0 +1,69 @@ +/* -*- 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/bitmapaccess.hxx> + +#include <bitmapwriteaccess.hxx> +#include <BitmapLightenFilter.hxx> + +BitmapEx BitmapLightenFilter::execute(BitmapEx const& rBitmapEx) const +{ + const Size aSize(rBitmapEx.GetSizePixel()); + + Bitmap aBitmap(rBitmapEx.GetBitmap()); + Bitmap aDarkBitmap(aSize, 24); + + Bitmap::ScopedReadAccess pRead(aBitmap); + BitmapScopedWriteAccess pWrite(aDarkBitmap); + + if (pRead && pWrite) + { + for (long nY = 0; nY < aSize.Height(); ++nY) + { + Scanline pScanline = pWrite->GetScanline(nY); + Scanline pScanlineRead = pRead->GetScanline(nY); + for (long nX = 0; nX < 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.GetAlpha()); +} + +/* 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 000000000..60edc557a --- /dev/null +++ b/vcl/source/bitmap/BitmapMedianFilter.cxx @@ -0,0 +1,218 @@ +/* -*- 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/bitmapaccess.hxx> +#include <vcl/BitmapMedianFilter.hxx> + +#include <bitmapwriteaccess.hxx> + +#define S2(a, b) \ + { \ + long 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()); + + Bitmap::ScopedReadAccess pReadAcc(aBitmap); + bool bRet = false; + + if (pReadAcc) + { + Bitmap aNewBmp(aBitmap.GetSizePixel(), 24); + BitmapScopedWriteAccess pWriteAcc(aNewBmp); + + if (pWriteAcc) + { + const long nWidth = pWriteAcc->Width(), nWidth2 = nWidth + 2; + const long nHeight = pWriteAcc->Height(), nHeight2 = nHeight + 2; + std::unique_ptr<long[]> pColm(new long[nWidth2]); + std::unique_ptr<long[]> pRows(new long[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; + long nY, nX, i; + long nR1, nR2, nR3, nR4, nR5, nR6, nR7, nR8, nR9; + long nG1, nG2, nG3, nG4, nG5, nG6, nG7, nG8, nG9; + long 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(); + + bRet = true; + } + + pReadAcc.reset(); + + if (bRet) + { + const MapMode aMap(aBitmap.GetPrefMapMode()); + const Size aPrefSize(aBitmap.GetPrefSize()); + + aBitmap = aNewBmp; + + 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/BitmapMonochromeFilter.cxx b/vcl/source/bitmap/BitmapMonochromeFilter.cxx new file mode 100644 index 000000000..72bacf849 --- /dev/null +++ b/vcl/source/bitmap/BitmapMonochromeFilter.cxx @@ -0,0 +1,101 @@ +/* -*- 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/bitmapaccess.hxx> + +#include <bitmapwriteaccess.hxx> + +BitmapEx BitmapMonochromeFilter::execute(BitmapEx const& aBitmapEx) const +{ + Bitmap aBitmap = aBitmapEx.GetBitmap(); + Bitmap::ScopedReadAccess pReadAcc(aBitmap); + bool bRet = false; + + if (pReadAcc) + { + Bitmap aNewBmp(aBitmap.GetSizePixel(), 1); + BitmapScopedWriteAccess pWriteAcc(aNewBmp); + + if (pWriteAcc) + { + const BitmapColor aBlack(pWriteAcc->GetBestMatchingColor(COL_BLACK)); + const BitmapColor aWhite(pWriteAcc->GetBestMatchingColor(COL_WHITE)); + const long nWidth = pWriteAcc->Width(); + const long nHeight = pWriteAcc->Height(); + + if (pReadAcc->HasPalette()) + { + for (long nY = 0; nY < nHeight; nY++) + { + Scanline pScanline = pWriteAcc->GetScanline(nY); + Scanline pScanlineRead = pReadAcc->GetScanline(nY); + for (long 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 (long nY = 0; nY < nHeight; nY++) + { + Scanline pScanline = pWriteAcc->GetScanline(nY); + Scanline pScanlineRead = pReadAcc->GetScanline(nY); + for (long 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(); + bRet = true; + } + + pReadAcc.reset(); + + if (bRet) + { + const MapMode aMap(aBitmap.GetPrefMapMode()); + const Size aSize(aBitmap.GetPrefSize()); + + aBitmap = aNewBmp; + + aBitmap.SetPrefMapMode(aMap); + aBitmap.SetPrefSize(aSize); + } + } + + if (bRet) + return BitmapEx(aBitmap); + + return BitmapEx(); +} + +/* 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 000000000..ee0d10275 --- /dev/null +++ b/vcl/source/bitmap/BitmapMosaicFilter.cxx @@ -0,0 +1,185 @@ +/* -*- 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/bitmapaccess.hxx> +#include <vcl/BitmapMosaicFilter.hxx> + +#include <bitmapwriteaccess.hxx> + +BitmapEx BitmapMosaicFilter::execute(BitmapEx const& rBitmapEx) const +{ + Bitmap aBitmap(rBitmapEx.GetBitmap()); + + bool bRet = false; + + if (mnTileWidth > 1 || mnTileHeight > 1) + { + std::unique_ptr<Bitmap> pNewBmp; + BitmapReadAccess* pReadAcc; + BitmapWriteAccess* pWriteAcc; + + if (aBitmap.GetBitCount() > 8) + { + pReadAcc = pWriteAcc = aBitmap.AcquireWriteAccess(); + } + else + { + pNewBmp.reset(new Bitmap(aBitmap.GetSizePixel(), 24)); + pReadAcc = aBitmap.AcquireReadAccess(); + pWriteAcc = pNewBmp->AcquireWriteAccess(); + } + + bool bConditionsMet = false; + long nWidth(0); + long nHeight(0); + if (pReadAcc && pWriteAcc) + { + nWidth = pReadAcc->Width(); + nHeight = pReadAcc->Height(); + bConditionsMet = (nWidth > 0 && nHeight > 0); + } + + if (bConditionsMet) + { + BitmapColor aCol; + long 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; + } + + Bitmap::ReleaseAccess(pReadAcc); + + if (pNewBmp) + { + Bitmap::ReleaseAccess(pWriteAcc); + + 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 000000000..a335f04fb --- /dev/null +++ b/vcl/source/bitmap/BitmapPopArtFilter.cxx @@ -0,0 +1,97 @@ +/* -*- 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/BitmapPopArtFilter.hxx> + +#include <bitmapwriteaccess.hxx> + +BitmapEx BitmapPopArtFilter::execute(BitmapEx const& rBitmapEx) const +{ + Bitmap aBitmap(rBitmapEx.GetBitmap()); + + bool bRet = (aBitmap.GetBitCount() <= 8) || aBitmap.Convert(BmpConversion::N8BitColors); + + if (bRet) + { + bRet = false; + + BitmapScopedWriteAccess pWriteAcc(aBitmap); + + if (pWriteAcc) + { + const long nWidth = pWriteAcc->Width(); + const long nHeight = pWriteAcc->Height(); + const int nEntryCount = 1 << pWriteAcc->GetBitCount(); + int n = 0; + std::vector<PopArtEntry> aPopArtTable(nEntryCount); + + for (n = 0; n < nEntryCount; n++) + { + PopArtEntry& rEntry = aPopArtTable[n]; + rEntry.mnIndex = static_cast<sal_uInt16>(n); + rEntry.mnCount = 0; + } + + // get pixel count for each palette entry + for (long nY = 0; nY < nHeight; nY++) + { + Scanline pScanline = pWriteAcc->GetScanline(nY); + for (long nX = 0; nX < nWidth; nX++) + { + aPopArtTable[pWriteAcc->GetIndexFromData(pScanline, nX)].mnCount++; + } + } + + // sort table + std::sort(aPopArtTable.begin(), aPopArtTable.end(), + [](const PopArtEntry& lhs, const PopArtEntry& rhs) { + return lhs.mnCount < rhs.mnCount; + }); + + // get last used entry + sal_uLong nFirstEntry; + sal_uLong nLastEntry = 0; + + for (n = 0; n < nEntryCount; n++) + { + if (aPopArtTable[n].mnCount) + nLastEntry = n; + } + + // rotate palette (one entry) + const BitmapColor aFirstCol(pWriteAcc->GetPaletteColor( + sal::static_int_cast<sal_uInt16>(aPopArtTable[0].mnIndex))); + + for (nFirstEntry = 0; nFirstEntry < nLastEntry; nFirstEntry++) + { + pWriteAcc->SetPaletteColor( + sal::static_int_cast<sal_uInt16>(aPopArtTable[nFirstEntry].mnIndex), + pWriteAcc->GetPaletteColor( + sal::static_int_cast<sal_uInt16>(aPopArtTable[nFirstEntry + 1].mnIndex))); + } + + pWriteAcc->SetPaletteColor( + sal::static_int_cast<sal_uInt16>(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/BitmapScaleConvolutionFilter.cxx b/vcl/source/bitmap/BitmapScaleConvolutionFilter.cxx new file mode 100644 index 000000000..b679e172d --- /dev/null +++ b/vcl/source/bitmap/BitmapScaleConvolutionFilter.cxx @@ -0,0 +1,391 @@ +/* -*- 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/bitmapaccess.hxx> + +#include <bitmapwriteaccess.hxx> +#include <BitmapScaleConvolutionFilter.hxx> + +#include <algorithm> +#include <memory> + +namespace vcl +{ + +namespace +{ + +void ImplCalculateContributions( + const long aSourceSize, + const long aDestinationSize, + long& 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 = (long(fabs(ceil(fScaledRadius))) * 2) + 1; + const long nAllocSize(aDestinationSize * aNumberOfContributions); + rWeights.resize(nAllocSize); + rPixels.resize(nAllocSize); + rCounts.resize(aDestinationSize); + + for(long i(0); i < aDestinationSize; i++) + { + const long 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))); + long 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 long aPixelIndex(MinMax(j, 0, aSourceSize - 1)); + const long 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 long nWidth(rSource.GetSizePixel().Width()); + const long nNewWidth(FRound(nWidth * rScaleX)); + + if(nWidth == nNewWidth) + { + return true; + } + + Bitmap::ScopedReadAccess pReadAcc(rSource); + + if(pReadAcc) + { + std::vector<sal_Int16> aWeights; + std::vector<sal_Int32> aPixels; + std::vector<sal_Int32> aCounts; + long aNumberOfContributions(0); + + const long nHeight(rSource.GetSizePixel().Height()); + ImplCalculateContributions(nWidth, nNewWidth, aNumberOfContributions, aWeights, aPixels, aCounts, aKernel); + rTarget = Bitmap(Size(nNewWidth, nHeight), 24); + BitmapScopedWriteAccess pWriteAcc(rTarget); + bool bResult(pWriteAcc); + + if(bResult) + { + for(long y(0); y < nHeight; y++) + { + Scanline pScanline = pWriteAcc->GetScanline( y ); + Scanline pScanlineRead = pReadAcc->GetScanline( y ); + for(long x(0); x < nNewWidth; x++) + { + const long aBaseIndex(x * aNumberOfContributions); + sal_Int32 aSum(0); + sal_Int32 aValueRed(0); + sal_Int32 aValueGreen(0); + sal_Int32 aValueBlue(0); + + for(long j(0); j < aCounts[x]; j++) + { + const long 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 >(MinMax(static_cast< sal_Int32 >(aValueRed / aSum), 0, 255)), + static_cast< sal_uInt8 >(MinMax(static_cast< sal_Int32 >(aValueGreen / aSum), 0, 255)), + static_cast< sal_uInt8 >(MinMax(static_cast< 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 long nHeight(rSource.GetSizePixel().Height()); + const long nNewHeight(FRound(nHeight * rScaleY)); + + if(nHeight == nNewHeight) + { + return true; + } + + Bitmap::ScopedReadAccess pReadAcc(rSource); + + if(pReadAcc) + { + std::vector<sal_Int16> aWeights; + std::vector<sal_Int32> aPixels; + std::vector<sal_Int32> aCounts; + long aNumberOfContributions(0); + + const long nWidth(rSource.GetSizePixel().Width()); + ImplCalculateContributions(nHeight, nNewHeight, aNumberOfContributions, aWeights, aPixels, aCounts, aKernel); + rTarget = Bitmap(Size(nWidth, nNewHeight), 24); + BitmapScopedWriteAccess pWriteAcc(rTarget); + bool bResult(pWriteAcc); + + if(pWriteAcc) + { + std::vector<BitmapColor> aScanline(nHeight); + for(long x(0); x < nWidth; x++) + { + for(long y(0); y < nHeight; y++) + if(pReadAcc->HasPalette()) + aScanline[y] = pReadAcc->GetPaletteColor(pReadAcc->GetPixelIndex(y, x)); + else + aScanline[y] = pReadAcc->GetPixel(y, x); + for(long y(0); y < nNewHeight; y++) + { + const long aBaseIndex(y * aNumberOfContributions); + sal_Int32 aSum(0); + sal_Int32 aValueRed(0); + sal_Int32 aValueGreen(0); + sal_Int32 aValueBlue(0); + + for(long j(0); j < aCounts[y]; j++) + { + const long 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 >(MinMax(static_cast< sal_Int32 >(aValueRed / aSum), 0, 255)), + static_cast< sal_uInt8 >(MinMax(static_cast< sal_Int32 >(aValueGreen / aSum), 0, 255)), + static_cast< sal_uInt8 >(MinMax(static_cast< 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(); + + if(bResult) + { + return true; + } + } + + return false; +} + +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 long nWidth(rBitmap.GetSizePixel().Width()); + const long nHeight(rBitmap.GetSizePixel().Height()); + const long nNewWidth(FRound(nWidth * fScaleX)); + const long 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 long nStartSize(nWidth * nHeight); + const long nEndSize(nNewWidth * nNewHeight); + + bMirrorAfter = nStartSize > nEndSize; + + if(!bMirrorAfter) + { + bResult = rBitmap.Mirror(nMirrorFlags); + } + } + + Bitmap aResult; + + if (bResult) + { + const long nInBetweenSizeHorFirst(nHeight * nNewWidth); + const long 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 000000000..ff808ddcd --- /dev/null +++ b/vcl/source/bitmap/BitmapScaleSuperFilter.cxx @@ -0,0 +1,1202 @@ +/* -*- 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/threadpool.hxx> + +#include <tools/helpers.hxx> +#include <vcl/bitmapaccess.hxx> + +#include <bitmapwriteaccess.hxx> +#include <BitmapScaleSuperFilter.hxx> + +#include <algorithm> +#include <memory> +#include <svdata.hxx> +#include <sal/log.hxx> + +namespace { + +#define 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; + long mnDestW; + bool mbHMirr; + bool mbVMirr; + std::vector<long> maMapIX; + std::vector<long> maMapIY; + std::vector<BilinearWeightType> maMapFX; + std::vector<BilinearWeightType> maMapFY; + + ScaleContext( BitmapReadAccess *pSrc, + BitmapWriteAccess *pDest, + long nSrcW, long nDestW, + long nSrcH, long 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(long nSourceLength, long nDestinationLength, bool bMirrored, + std::vector<long> & rMapIX, std::vector<BilinearWeightType> & rMapFX) + { + const double fRevScale = (nDestinationLength > 1) ? double(nSourceLength - 1) / (nDestinationLength - 1) : 0.0; + + long nTemp = nSourceLength - 2; + long nTempX = nSourceLength - 1; + + for (long i = 0; i < nDestinationLength; i++) + { + double fTemp = i * fRevScale; + if (bMirrored) + fTemp = nTempX - fTemp; + rMapIX[i] = MinMax(long(fTemp), 0, nTemp); + rMapFX[i] = BilinearWeightType((fTemp - rMapIX[i]) * (BilinearWeightType(1) << MAP_PRECISION)); + } + } +}; + +constexpr long constScaleThreadStrip = 32; + +typedef void (*ScaleRangeFn)(ScaleContext &rContext, long nStartY, long nEndY); + +class ScaleTask : public comphelper::ThreadTask +{ + ScaleRangeFn mpScaleRangeFunction; + ScaleContext& mrContext; + long mnStartY; + long mnEndY; + +public: + explicit ScaleTask(const std::shared_ptr<comphelper::ThreadTaskTag>& pTag, + ScaleRangeFn pScaleRangeFunction, + ScaleContext& rContext, + long nStartY, long nEndY) + : comphelper::ThreadTask(pTag) + , mpScaleRangeFunction(pScaleRangeFunction) + , mrContext(rContext) + , mnStartY(nStartY) + , mnEndY(nEndY) + {} + + virtual void doWork() override + { + mpScaleRangeFunction(mrContext, mnStartY, mnEndY); + } +}; + +void scaleUp32bit(ScaleContext &rCtx, long nStartY, long nEndY) +{ + const int nColorComponents = 4; + + const long nStartX = 0; + const long nEndX = rCtx.mnDestW - 1; + + for (long nY = nStartY; nY <= nEndY; nY++) + { + long 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); + + sal_uInt8 nComponent1[nColorComponents]; + sal_uInt8 nComponent2[nColorComponents]; + + Scanline pColorPtr0; + Scanline pColorPtr1; + + for (long nX = nStartX; nX <= nEndX; nX++) + { + long nTempX = rCtx.maMapIX[nX]; + BilinearWeightType nTempFX = rCtx.maMapFX[nX]; + + pColorPtr0 = pLine0 + nTempX * nColorComponents; + pColorPtr1 = pColorPtr0 + nColorComponents; + + nComponent1[0] = MAP(*pColorPtr0, *pColorPtr1, nTempFX); + pColorPtr0++; pColorPtr1++; + nComponent1[1] = MAP(*pColorPtr0, *pColorPtr1, nTempFX); + pColorPtr0++; pColorPtr1++; + nComponent1[2] = MAP(*pColorPtr0, *pColorPtr1, nTempFX); + pColorPtr0++; pColorPtr1++; + nComponent1[3] = MAP(*pColorPtr0, *pColorPtr1, nTempFX); + + pColorPtr0 = pLine1 + nTempX * nColorComponents; + pColorPtr1 = pColorPtr0 + nColorComponents; + + nComponent2[0] = MAP(*pColorPtr0, *pColorPtr1, nTempFX); + pColorPtr0++; pColorPtr1++; + nComponent2[1] = MAP(*pColorPtr0, *pColorPtr1, nTempFX); + pColorPtr0++; pColorPtr1++; + nComponent2[2] = MAP(*pColorPtr0, *pColorPtr1, nTempFX); + pColorPtr0++; pColorPtr1++; + nComponent2[3] = MAP(*pColorPtr0, *pColorPtr1, nTempFX); + + *pScanDest = MAP(nComponent1[0], nComponent2[0], nTempFY); + pScanDest++; + *pScanDest = MAP(nComponent1[1], nComponent2[1], nTempFY); + pScanDest++; + *pScanDest = MAP(nComponent1[2], nComponent2[2], nTempFY); + pScanDest++; + *pScanDest = MAP(nComponent1[3], nComponent2[3], nTempFY); + pScanDest++; + } + } +} + +void scaleUpPalette8bit(ScaleContext &rCtx, long nStartY, long nEndY) +{ + const long nStartX = 0, nEndX = rCtx.mnDestW - 1; + + for( long nY = nStartY; nY <= nEndY; nY++ ) + { + long 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(long nX = nStartX, nXDst = 0; nX <= nEndX; nX++ ) + { + long 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(ScaleContext &rCtx, long nStartY, long nEndY) +{ + const long nStartX = 0, nEndX = rCtx.mnDestW - 1; + + for( long nY = nStartY; nY <= nEndY; nY++ ) + { + long nTempY = rCtx.maMapIY[ nY ]; + BilinearWeightType nTempFY = rCtx.maMapFY[ nY ]; + Scanline pScanline = rCtx.mpDest->GetScanline( nY ); + + for( long nX = nStartX, nXDst = 0; nX <= nEndX; nX++ ) + { + long 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 scaleUp24bit(ScaleContext &rCtx, long nStartY, long nEndY) +{ + const int nColorComponents = 3; + + const long nStartX = 0; + const long nEndX = rCtx.mnDestW - 1; + + for (long nY = nStartY; nY <= nEndY; nY++) + { + long 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); + + sal_uInt8 nComponent1[nColorComponents]; + sal_uInt8 nComponent2[nColorComponents]; + + Scanline pColorPtr0; + Scanline pColorPtr1; + + for (long nX = nStartX; nX <= nEndX; nX++) + { + long nTempX = rCtx.maMapIX[nX]; + BilinearWeightType nTempFX = rCtx.maMapFX[nX]; + + pColorPtr0 = pLine0 + nTempX * nColorComponents; + pColorPtr1 = pColorPtr0 + nColorComponents; + + nComponent1[0] = MAP(*pColorPtr0, *pColorPtr1, nTempFX); + pColorPtr0++; pColorPtr1++; + nComponent1[1] = MAP(*pColorPtr0, *pColorPtr1, nTempFX); + pColorPtr0++; pColorPtr1++; + nComponent1[2] = MAP(*pColorPtr0, *pColorPtr1, nTempFX); + + pColorPtr0 = pLine1 + nTempX * nColorComponents; + pColorPtr1 = pColorPtr0 + nColorComponents; + + nComponent2[0] = MAP(*pColorPtr0, *pColorPtr1, nTempFX); + pColorPtr0++; pColorPtr1++; + nComponent2[1] = MAP(*pColorPtr0, *pColorPtr1, nTempFX); + pColorPtr0++; pColorPtr1++; + nComponent2[2] = MAP(*pColorPtr0, *pColorPtr1, nTempFX); + + *pScanDest = MAP(nComponent1[0], nComponent2[0], nTempFY); + pScanDest++; + *pScanDest = MAP(nComponent1[1], nComponent2[1], nTempFY); + pScanDest++; + *pScanDest = MAP(nComponent1[2], nComponent2[2], nTempFY); + pScanDest++; + } + } +} + +void scaleUpNonPaletteGeneral(ScaleContext &rCtx, long nStartY, long nEndY) +{ + const long nStartX = 0, nEndX = rCtx.mnDestW - 1; + + for( long nY = nStartY; nY <= nEndY; nY++ ) + { + long nTempY = rCtx.maMapIY[ nY ]; + BilinearWeightType nTempFY = rCtx.maMapFY[ nY ]; + Scanline pScanDest = rCtx.mpDest->GetScanline( nY ); + + for( long nX = nStartX, nXDst = 0; nX <= nEndX; nX++ ) + { + long 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 scaleDown32bit(ScaleContext &rCtx, long nStartY, long nEndY) +{ + const int constColorComponents = 4; + + const long nStartX = 0; + const long nEndX = rCtx.mnDestW - 1; + + for (long nY = nStartY; nY <= nEndY; nY++) + { + long nTop = rCtx.mbVMirr ? (nY + 1) : nY; + long nBottom = rCtx.mbVMirr ? nY : (nY + 1); + + long nLineStart; + long 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 (long nX = nStartX; nX <= nEndX; nX++) + { + long nLeft = rCtx.mbHMirr ? (nX + 1) : nX; + long nRight = rCtx.mbHMirr ? nX : (nX + 1); + + long nRowStart; + long 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 nSum1 = 0; + int nSum2 = 0; + int nSum3 = 0; + int nSum4 = 0; + BilinearWeightType nTotalWeightY = 0; + + for (long i = 0; i<= nLineRange; i++) + { + Scanline pTmpY = rCtx.mpSrc->GetScanline(nLineStart + i); + Scanline pTmpX = pTmpY + constColorComponents * nRowStart; + + int nSumRow1 = 0; + int nSumRow2 = 0; + int nSumRow3 = 0; + int nSumRow4 = 0; + BilinearWeightType nTotalWeightX = 0; + + for (long j = 0; j <= nRowRange; j++) + { + if (nX == nEndX) + { + nSumRow1 += (*pTmpX) << MAP_PRECISION; pTmpX++; + nSumRow2 += (*pTmpX) << MAP_PRECISION; pTmpX++; + nSumRow3 += (*pTmpX) << MAP_PRECISION; pTmpX++; + nSumRow4 += (*pTmpX) << MAP_PRECISION; pTmpX++; + nTotalWeightX += lclMaxWeight(); + } + else if(j == 0) + { + BilinearWeightType nWeightX = lclMaxWeight() - rCtx.maMapFX[nLeft]; + nSumRow1 += (nWeightX * (*pTmpX)); pTmpX++; + nSumRow2 += (nWeightX * (*pTmpX)); pTmpX++; + nSumRow3 += (nWeightX * (*pTmpX)); pTmpX++; + nSumRow4 += (nWeightX * (*pTmpX)); pTmpX++; + nTotalWeightX += nWeightX; + } + else if ( nRowRange == j ) + { + BilinearWeightType nWeightX = rCtx.maMapFX[ nRight ] ; + nSumRow1 += (nWeightX * (*pTmpX)); pTmpX++; + nSumRow2 += (nWeightX * (*pTmpX)); pTmpX++; + nSumRow3 += (nWeightX * (*pTmpX)); pTmpX++; + nSumRow4 += (nWeightX * (*pTmpX)); pTmpX++; + nTotalWeightX += nWeightX; + } + else + { + nSumRow1 += (*pTmpX) << MAP_PRECISION; pTmpX++; + nSumRow2 += (*pTmpX) << MAP_PRECISION; pTmpX++; + nSumRow3 += (*pTmpX) << MAP_PRECISION; pTmpX++; + nSumRow4 += (*pTmpX) << MAP_PRECISION; pTmpX++; + 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) + { + nSumRow1 /= nTotalWeightX; + nSumRow2 /= nTotalWeightX; + nSumRow3 /= nTotalWeightX; + nSumRow4 /= nTotalWeightX; + } + nSum1 += nWeightY * nSumRow1; + nSum2 += nWeightY * nSumRow2; + nSum3 += nWeightY * nSumRow3; + nSum4 += nWeightY * nSumRow4; + nTotalWeightY += nWeightY; + } + + if (nTotalWeightY) + { + nSum1 /= nTotalWeightY; + nSum2 /= nTotalWeightY; + nSum3 /= nTotalWeightY; + nSum4 /= nTotalWeightY; + } + + // Write the calculated color components to the destination + *pScanDest = nSum1; pScanDest++; + *pScanDest = nSum2; pScanDest++; + *pScanDest = nSum3; pScanDest++; + *pScanDest = nSum4; pScanDest++; + } + } +} + +void scaleDownPalette8bit(ScaleContext &rCtx, long nStartY, long nEndY) +{ + const long nStartX = 0, nEndX = rCtx.mnDestW - 1; + + for( long nY = nStartY; nY <= nEndY; nY++ ) + { + long nTop = rCtx.mbVMirr ? ( nY + 1 ) : nY; + long nBottom = rCtx.mbVMirr ? nY : ( nY + 1 ) ; + + long 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( long nX = nStartX , nXDst = 0; nX <= nEndX; nX++ ) + { + long nLeft = rCtx.mbHMirr ? ( nX + 1 ) : nX; + long nRight = rCtx.mbHMirr ? nX : ( nX + 1 ) ; + + long nRowStart; + long 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(long 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(long 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(ScaleContext &rCtx, long nStartY, long nEndY) +{ + const long nStartX = 0, nEndX = rCtx.mnDestW - 1; + + for( long nY = nStartY; nY <= nEndY; nY++ ) + { + long nTop = rCtx.mbVMirr ? ( nY + 1 ) : nY; + long nBottom = rCtx.mbVMirr ? nY : ( nY + 1 ) ; + + long 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( long nX = nStartX , nXDst = 0; nX <= nEndX; nX++ ) + { + long nLeft = rCtx.mbHMirr ? ( nX + 1 ) : nX; + long nRight = rCtx.mbHMirr ? nX : ( nX + 1 ) ; + + long 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(long 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(long 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(); + } + } + + long 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 scaleDown24bit(ScaleContext &rCtx, long nStartY, long nEndY) +{ + const int constColorComponents = 3; + + const long nStartX = 0; + const long nEndX = rCtx.mnDestW - 1; + + for (long nY = nStartY; nY <= nEndY; nY++) + { + long nTop = rCtx.mbVMirr ? (nY + 1) : nY; + long nBottom = rCtx.mbVMirr ? nY : (nY + 1); + + long nLineStart; + long 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 (long nX = nStartX; nX <= nEndX; nX++) + { + long nLeft = rCtx.mbHMirr ? (nX + 1) : nX; + long nRight = rCtx.mbHMirr ? nX : (nX + 1); + + long nRowStart; + long 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 nSum1 = 0; + int nSum2 = 0; + int nSum3 = 0; + BilinearWeightType nTotalWeightY = 0; + + for (long i = 0; i<= nLineRange; i++) + { + Scanline pTmpY = rCtx.mpSrc->GetScanline(nLineStart + i); + Scanline pTmpX = pTmpY + constColorComponents * nRowStart; + + int nSumRow1 = 0; + int nSumRow2 = 0; + int nSumRow3 = 0; + BilinearWeightType nTotalWeightX = 0; + + for (long j = 0; j <= nRowRange; j++) + { + if (nX == nEndX) + { + nSumRow1 += (*pTmpX) << MAP_PRECISION; pTmpX++; + nSumRow2 += (*pTmpX) << MAP_PRECISION; pTmpX++; + nSumRow3 += (*pTmpX) << MAP_PRECISION; pTmpX++; + nTotalWeightX += lclMaxWeight(); + } + else if(j == 0) + { + BilinearWeightType nWeightX = lclMaxWeight() - rCtx.maMapFX[nLeft]; + nSumRow1 += (nWeightX * (*pTmpX)); pTmpX++; + nSumRow2 += (nWeightX * (*pTmpX)); pTmpX++; + nSumRow3 += (nWeightX * (*pTmpX)); pTmpX++; + nTotalWeightX += nWeightX; + } + else if ( nRowRange == j ) + { + BilinearWeightType nWeightX = rCtx.maMapFX[ nRight ] ; + nSumRow1 += (nWeightX * (*pTmpX)); pTmpX++; + nSumRow2 += (nWeightX * (*pTmpX)); pTmpX++; + nSumRow3 += (nWeightX * (*pTmpX)); pTmpX++; + nTotalWeightX += nWeightX; + } + else + { + nSumRow1 += (*pTmpX) << MAP_PRECISION; pTmpX++; + nSumRow2 += (*pTmpX) << MAP_PRECISION; pTmpX++; + nSumRow3 += (*pTmpX) << MAP_PRECISION; pTmpX++; + 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) + { + nSumRow1 /= nTotalWeightX; + nSumRow2 /= nTotalWeightX; + nSumRow3 /= nTotalWeightX; + } + nSum1 += nWeightY * nSumRow1; + nSum2 += nWeightY * nSumRow2; + nSum3 += nWeightY * nSumRow3; + nTotalWeightY += nWeightY; + } + + if (nTotalWeightY) + { + nSum1 /= nTotalWeightY; + nSum2 /= nTotalWeightY; + nSum3 /= nTotalWeightY; + } + + // Write the calculated color components to the destination + *pScanDest = nSum1; pScanDest++; + *pScanDest = nSum2; pScanDest++; + *pScanDest = nSum3; pScanDest++; + } + } +} + +void scaleDownNonPaletteGeneral(ScaleContext &rCtx, long nStartY, long nEndY) +{ + const long nStartX = 0, nEndX = rCtx.mnDestW - 1; + + for( long nY = nStartY; nY <= nEndY; nY++ ) + { + long nTop = rCtx.mbVMirr ? ( nY + 1 ) : nY; + long nBottom = rCtx.mbVMirr ? nY : ( nY + 1 ) ; + + long 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( long nX = nStartX , nXDst = 0; nX <= nEndX; nX++ ) + { + long nLeft = rCtx.mbHMirr ? ( nX + 1 ) : nX; + long nRight = rCtx.mbHMirr ? nX : ( nX + 1 ) ; + + long 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(long 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(long 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 long nDstW = FRound(aSizePix.Width() * fScaleX); + const long nDstH = FRound(aSizePix.Height() * fScaleY); + + const 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"); + } + + { + Bitmap::ScopedReadAccess pReadAccess(aBitmap); + + sal_uInt16 nSourceBitcount = aBitmap.GetBitCount(); + + Bitmap aOutBmp(Size(nDstW, nDstH), std::max(nSourceBitcount, sal_uInt16(24))); + Size aOutSize = aOutBmp.GetSizePixel(); + sal_uInt16 nTargetBitcount = aOutBmp.GetBitCount(); + + if (!aOutSize.Width() || !aOutSize.Height()) + { + SAL_WARN("vcl.gdi", "bmp creation failed"); + return BitmapEx(); + } + + BitmapScopedWriteAccess pWriteAccess(aOutBmp); + + const long nStartY = 0; + const long nEndY = nDstH - 1; + + if (pReadAccess && pWriteAccess) + { + ScaleRangeFn pScaleRangeFn; + 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 (nSourceBitcount != nTargetBitcount) + { + 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 ? scaleUp24bit : scaleDown24bit; + break; + case ScanlineFormat::N32BitTcRgba: + case ScanlineFormat::N32BitTcBgra: + case ScanlineFormat::N32BitTcArgb: + case ScanlineFormat::N32BitTcAbgr: + pScaleRangeFn = bScaleUp ? scaleUp32bit : scaleDown32bit; + 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; + + 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(); + + long nStripYStart = nStartY; + long nStripYEnd = nStripYStart + constScaleThreadStrip - 1; + + while (nStripYEnd < nEndY) + { + std::unique_ptr<ScaleTask> pTask(new ScaleTask(pTag, pScaleRangeFn, aContext, nStripYStart, nStripYEnd)); + rShared.pushTask(std::move(pTask)); + nStripYStart += constScaleThreadStrip; + nStripYEnd += constScaleThreadStrip; + } + if (nStripYStart <= nEndY) + { + std::unique_ptr<ScaleTask> pTask(new ScaleTask(pTag, pScaleRangeFn, aContext, nStripYStart, nEndY)); + rShared.pushTask(std::move(pTask)); + } + 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 000000000..58d5e3810 --- /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/bitmapaccess.hxx> +#include <vcl/BitmapGaussianSeparableBlurFilter.hxx> +#include <vcl/BitmapSeparableUnsharpenFilter.hxx> + +#include <bitmapwriteaccess.hxx> + +BitmapEx BitmapSeparableUnsharpenFilter::execute(BitmapEx const& rBitmapEx) const +{ + Bitmap aBitmap(rBitmapEx.GetBitmap()); + + const long nWidth = aBitmap.GetSizePixel().Width(); + const long 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), 24); + + Bitmap::ScopedReadAccess pReadAccBlur(aBlur); + Bitmap::ScopedReadAccess pReadAcc(aBitmap); + BitmapScopedWriteAccess pWriteAcc(aResultBitmap); + + BitmapColor aColor, aColorBlur; + + // For all pixels in original image subtract pixels values from blurred image + for (long y = 0; y < nHeight; y++) + { + Scanline pScanline = pWriteAcc->GetScanline(y); + for (long x = 0; x < nWidth; x++) + { + aColorBlur = pReadAccBlur->GetColor(y, x); + aColor = pReadAcc->GetColor(y, x); + + BitmapColor aResultColor( + static_cast<sal_uInt8>(MinMax( + aColor.GetRed() + (aColor.GetRed() - aColorBlur.GetRed()) * aAmount, 0, 255)), + static_cast<sal_uInt8>(MinMax( + aColor.GetGreen() + (aColor.GetGreen() - aColorBlur.GetGreen()) * aAmount, 0, + 255)), + static_cast<sal_uInt8>( + MinMax(aColor.GetBlue() + (aColor.GetBlue() - aColorBlur.GetBlue()) * aAmount, + 0, 255))); + + 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 000000000..5123bf130 --- /dev/null +++ b/vcl/source/bitmap/BitmapSepiaFilter.cxx @@ -0,0 +1,111 @@ +/* -*- 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 <algorithm> + +#include <vcl/bitmap.hxx> +#include <vcl/bitmapex.hxx> +#include <vcl/bitmapaccess.hxx> +#include <vcl/BitmapSepiaFilter.hxx> + +#include <bitmapwriteaccess.hxx> + +BitmapEx BitmapSepiaFilter::execute(BitmapEx const& rBitmapEx) const +{ + Bitmap aBitmap(rBitmapEx.GetBitmap()); + Bitmap::ScopedReadAccess pReadAcc(aBitmap); + bool bRet = false; + + if (pReadAcc) + { + const long 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(), 8, &aSepiaPal); + BitmapScopedWriteAccess pWriteAcc(aNewBmp); + + if (pWriteAcc) + { + BitmapColor aCol(sal_uInt8(0)); + const long nWidth = pWriteAcc->Width(); + const long 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 (long nY = 0; nY < nHeight; nY++) + { + Scanline pScanline = pWriteAcc->GetScanline(nY); + Scanline pScanlineRead = pReadAcc->GetScanline(nY); + for (long nX = 0; nX < nWidth; nX++) + { + aCol.SetIndex(pIndexMap[pReadAcc->GetIndexFromData(pScanlineRead, nX)]); + pWriteAcc->SetPixelOnData(pScanline, nX, aCol); + } + } + } + else + { + for (long nY = 0; nY < nHeight; nY++) + { + Scanline pScanline = pWriteAcc->GetScanline(nY); + Scanline pScanlineRead = pReadAcc->GetScanline(nY); + for (long nX = 0; nX < nWidth; nX++) + { + aCol.SetIndex(pReadAcc->GetPixelFromData(pScanlineRead, nX).GetLuminance()); + pWriteAcc->SetPixelOnData(pScanline, nX, aCol); + } + } + } + + pWriteAcc.reset(); + bRet = true; + } + + pReadAcc.reset(); + + if (bRet) + { + const MapMode aMap(aBitmap.GetPrefMapMode()); + const Size aPrefSize(aBitmap.GetPrefSize()); + + aBitmap = aNewBmp; + + aBitmap.SetPrefMapMode(aMap); + aBitmap.SetPrefSize(aPrefSize); + } + } + + if (bRet) + return rBitmapEx; + + return BitmapEx(); +} + +/* 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 000000000..1d21ed9e1 --- /dev/null +++ b/vcl/source/bitmap/BitmapSimpleColorQuantizationFilter.cxx @@ -0,0 +1,113 @@ +/* -*- 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/bitmapaccess.hxx> +#include <vcl/BitmapSimpleColorQuantizationFilter.hxx> + +#include <bitmapwriteaccess.hxx> +#include <bitmap/Octree.hxx> + +BitmapEx BitmapSimpleColorQuantizationFilter::execute(BitmapEx const& aBitmapEx) const +{ + Bitmap aBitmap = aBitmapEx.GetBitmap(); + + bool bRet = false; + + if (aBitmap.GetColorCount() <= sal_Int64(mnNewColorCount)) + { + bRet = true; + } + else + { + Bitmap aNewBmp; + Bitmap::ScopedReadAccess pRAcc(aBitmap); + const sal_uInt16 nColorCount = std::min(mnNewColorCount, sal_uInt16(256)); + sal_uInt16 nBitCount = 0; + + if (nColorCount <= 2) + nBitCount = 1; + else if (nColorCount <= 16) + nBitCount = 4; + else + nBitCount = 8; + + if (pRAcc) + { + Octree aOct(*pRAcc, nColorCount); + const BitmapPalette& rPal = aOct.GetPalette(); + + aNewBmp = Bitmap(aBitmap.GetSizePixel(), nBitCount, &rPal); + BitmapScopedWriteAccess pWAcc(aNewBmp); + + if (pWAcc) + { + const long nWidth = pRAcc->Width(); + const long nHeight = pRAcc->Height(); + + if (pRAcc->HasPalette()) + { + for (long nY = 0; nY < nHeight; nY++) + { + Scanline pScanline = pWAcc->GetScanline(nY); + Scanline pScanlineRead = pRAcc->GetScanline(nY); + for (long 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 (long nY = 0; nY < nHeight; nY++) + { + Scanline pScanline = pWAcc->GetScanline(nY); + Scanline pScanlineRead = pRAcc->GetScanline(nY); + for (long 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(); + bRet = true; + } + + pRAcc.reset(); + } + + if (bRet) + { + const MapMode aMap(aBitmap.GetPrefMapMode()); + const Size aSize(aBitmap.GetPrefSize()); + + aBitmap = aNewBmp; + + aBitmap.SetPrefMapMode(aMap); + aBitmap.SetPrefSize(aSize); + } + } + + if (bRet) + return BitmapEx(aBitmap); + + return BitmapEx(); +} + +/* 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 000000000..67cea0cb3 --- /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 rBitmapEx; + + 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 000000000..5ce4987a2 --- /dev/null +++ b/vcl/source/bitmap/BitmapSobelGreyFilter.cxx @@ -0,0 +1,170 @@ +/* -*- 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 <algorithm> + +#include <vcl/bitmap.hxx> +#include <vcl/bitmapex.hxx> +#include <vcl/bitmapaccess.hxx> +#include <vcl/BitmapSobelGreyFilter.hxx> + +#include <bitmapwriteaccess.hxx> + +BitmapEx BitmapSobelGreyFilter::execute(BitmapEx const& rBitmapEx) const +{ + Bitmap aBitmap(rBitmapEx.GetBitmap()); + + bool bRet = aBitmap.ImplMakeGreyscales(256); + + if (bRet) + { + bRet = false; + + Bitmap::ScopedReadAccess pReadAcc(aBitmap); + + if (pReadAcc) + { + Bitmap aNewBmp(aBitmap.GetSizePixel(), 8, &pReadAcc->GetPalette()); + BitmapScopedWriteAccess pWriteAcc(aNewBmp); + + if (pWriteAcc) + { + BitmapColor aGrey(sal_uInt8(0)); + const long nWidth = pWriteAcc->Width(); + const long nHeight = pWriteAcc->Height(); + const long nMask111 = -1, nMask121 = 0, nMask131 = 1; + const long nMask211 = -2, nMask221 = 0, nMask231 = 2; + const long nMask311 = -1, nMask321 = 0, nMask331 = 1; + const long nMask112 = 1, nMask122 = 2, nMask132 = 1; + const long nMask212 = 0, nMask222 = 0, nMask232 = 0; + const long nMask312 = -1, nMask322 = -2, nMask332 = -1; + long nGrey11, nGrey12, nGrey13; + long nGrey21, nGrey22, nGrey23; + long nGrey31, nGrey32, nGrey33; + std::unique_ptr<long[]> pHMap(new long[nWidth + 2]); + std::unique_ptr<long[]> pVMap(new long[nHeight + 2]); + long 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<long>( + sqrt(static_cast<double>(nSum1 * nSum1 + nSum2 * nSum2))); + + aGrey.SetIndex(~static_cast<sal_uInt8>(std::clamp(nSum1, 0L, 255L))); + pWriteAcc->SetPixelOnData(pScanline, nX, aGrey); + + if (nX < (nWidth - 1)) + { + const long 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(); + bRet = true; + } + + pReadAcc.reset(); + + if (bRet) + { + const MapMode aMap(aBitmap.GetPrefMapMode()); + const Size aPrefSize(aBitmap.GetPrefSize()); + + aBitmap = aNewBmp; + + 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/BitmapSolarizeFilter.cxx b/vcl/source/bitmap/BitmapSolarizeFilter.cxx new file mode 100644 index 000000000..bd7518b4c --- /dev/null +++ b/vcl/source/bitmap/BitmapSolarizeFilter.cxx @@ -0,0 +1,71 @@ +/* -*- 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 <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 long nWidth = pWriteAcc->Width(); + const long nHeight = pWriteAcc->Height(); + + for (long nY = 0; nY < nHeight; nY++) + { + Scanline pScanline = pWriteAcc->GetScanline(nY); + for (long 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 rBitmapEx; + + 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 000000000..9abb48086 --- /dev/null +++ b/vcl/source/bitmap/BitmapSymmetryCheck.cxx @@ -0,0 +1,84 @@ +/* -*- 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 <BitmapSymmetryCheck.hxx> + +BitmapSymmetryCheck::BitmapSymmetryCheck() +{} + +BitmapSymmetryCheck::~BitmapSymmetryCheck() +{} + +bool BitmapSymmetryCheck::check(Bitmap& rBitmap) +{ + Bitmap::ScopedReadAccess aReadAccess(rBitmap); + return checkImpl(aReadAccess.get()); +} + +bool BitmapSymmetryCheck::checkImpl(BitmapReadAccess const * pReadAccess) +{ + long nHeight = pReadAccess->Height(); + long nWidth = pReadAccess->Width(); + + long nHeightHalf = nHeight / 2; + long nWidthHalf = nWidth / 2; + + bool bHeightEven = (nHeight % 2) == 0; + bool bWidthEven = (nWidth % 2) == 0; + + for (long y = 0; y < nHeightHalf; ++y) + { + Scanline pScanlineRead = pReadAccess->GetScanline( y ); + Scanline pScanlineRead2 = pReadAccess->GetScanline( nHeight - y - 1 ); + for (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 (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 (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 000000000..3f0226047 --- /dev/null +++ b/vcl/source/bitmap/BitmapTools.cxx @@ -0,0 +1,1131 @@ +/* -*- 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/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/bitmapaccess.hxx> +#include <vcl/virdev.hxx> +#if ENABLE_CAIRO_CANVAS +#include <cairo.h> +#endif +#include <tools/diagnose_ex.h> +#include <tools/fract.hxx> +#include <tools/stream.hxx> +#include <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 (Primitive2DReference 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) +*/ +BitmapEx CreateFromData( sal_uInt8 const *pData, sal_Int32 nWidth, sal_Int32 nHeight, sal_Int32 nStride, sal_uInt16 nBitCount ) +{ + assert(nStride >= (nWidth * nBitCount / 8)); + assert( nBitCount == 1 || nBitCount == 24 || nBitCount == 32); + Bitmap aBmp( Size( nWidth, nHeight ), nBitCount ); + + BitmapScopedWriteAccess pWrite(aBmp); + assert(pWrite.get()); + if( !pWrite ) + return BitmapEx(); + std::unique_ptr<AlphaMask> pAlphaMask; + AlphaScopedWriteAccess xMaskAcc; + if (nBitCount == 32) + { + pAlphaMask.reset( new AlphaMask( Size(nWidth, nHeight) ) ); + xMaskAcc = AlphaScopedWriteAccess(*pAlphaMask); + } + if (nBitCount == 1) + { + for( long y = 0; y < nHeight; ++y ) + { + Scanline pScanline = pWrite->GetScanline(y); + for (long x = 0; x < nWidth; ++x) + { + sal_uInt8 const *p = pData + y * nStride / 8; + int bitIndex = (y * nStride) % 8; + pWrite->SetPixelOnData(pScanline, x, BitmapColor((*p >> bitIndex) & 1)); + } + } + } + else + { + for( long y = 0; y < nHeight; ++y ) + { + sal_uInt8 const *p = pData + (y * nStride); + Scanline pScanline = pWrite->GetScanline(y); + for (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 = pData + (y * nStride) + 3; + Scanline pMaskScanLine = xMaskAcc->GetScanline(y); + for (long x = 0; x < nWidth; ++x) + { + xMaskAcc->SetPixelOnData(pMaskScanLine, x, BitmapColor(*p)); + p += 4; + } + } + } + } + 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); + Bitmap aBmp( rawBitmap.maSize, nBitCount ); + + BitmapScopedWriteAccess pWrite(aBmp); + assert(pWrite.get()); + if( !pWrite ) + return BitmapEx(); + std::unique_ptr<AlphaMask> pAlphaMask; + AlphaScopedWriteAccess xMaskAcc; + if (nBitCount == 32) + { + pAlphaMask.reset( new AlphaMask( rawBitmap.maSize ) ); + xMaskAcc = AlphaScopedWriteAccess(*pAlphaMask); + } + + auto nHeight = rawBitmap.maSize.getHeight(); + auto nWidth = rawBitmap.maSize.getWidth(); + auto nStride = nWidth * nBitCount / 8; + for( long y = 0; y < nHeight; ++y ) + { + sal_uInt8 const *p = rawBitmap.mpData.get() + (y * nStride); + Scanline pScanline = pWrite->GetScanline(y); + for (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 (long x = 0; x < nWidth; ++x) + { + xMaskAcc->SetPixelOnData(pMaskScanLine, x, BitmapColor(*p)); + p += 4; + } + } + } + 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. + +#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 12, 0) + cairo_surface_t *pPixels = cairo_surface_create_similar_image(pSurface, +#else + cairo_surface_t *pPixels = cairo_image_surface_create( +#endif + 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, 24 ); + ::AlphaMask aMask( aSize ); + + BitmapScopedWriteAccess pRGBWrite(aRGB); + assert(pRGBWrite); + if (!pRGBWrite) + return nullptr; + + AlphaScopedWriteAccess 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 ); + vcl::bitmap::lookup_table unpremultiply_table = vcl::bitmap::get_unpremultiply_table(); + for( long y = 0; y < aSize.Height(); y++ ) + { + sal_uInt32 *pPix = reinterpret_cast<sal_uInt32 *>(pSrc + nStride * y); + for( 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 + nR = unpremultiply_table[nAlpha][nR]; + nG = unpremultiply_table[nAlpha][nG]; + nB = unpremultiply_table[nAlpha][nB]; + } + pRGBWrite->SetPixel( y, x, BitmapColor( nR, nG, nB ) ); + pMaskWrite->SetPixelIndex( y, x, 255 - 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.IsTransparent() ) + { + if( rBitmap.IsAlpha() ) + aSrcAlpha = rBitmap.GetAlpha().GetBitmap(); + else + aSrcAlpha = rBitmap.GetMask(); + } + + Bitmap::ScopedReadAccess pReadAccess( aSrcBitmap ); + Bitmap::ScopedReadAccess pAlphaReadAccess( rBitmap.IsTransparent() ? + aSrcAlpha.AcquireReadAccess() : + nullptr, + aSrcAlpha ); + + if( pReadAccess.get() == nullptr || + (pAlphaReadAccess.get() == nullptr && rBitmap.IsTransparent()) ) + { + // 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.IsTransparent() ) + { + 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 + { + // mask transparency - determine used palette colors + const BitmapColor& rCol0( pAlphaReadAccess->GetPaletteColor( 0 ) ); + const BitmapColor& rCol1( pAlphaReadAccess->GetPaletteColor( 1 ) ); + + // shortcut for true luminance calculation + // (assumes that palette is grey-level) + aAlphaMap[0] = rCol0.GetRed(); + aAlphaMap[1] = rCol1.GetRed(); + } + } + // 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.GetBitCount(), &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( long y=0; y<aDestBmpSize.Height(); ++y ) + { + // differentiate mask and alpha channel (on-off + // vs. multi-level transparency) + if( rBitmap.IsTransparent() ) + { + Scanline pScan = pWriteAccess->GetScanline( y ); + Scanline pScanAlpha = pAlphaWriteAccess->GetScanline( y ); + // Handling alpha and mask just the same... + for( 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(255) ); + } + 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( 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(255) ); + } + else + { + pAlphaWriteAccess->SetPixelOnData( pScanAlpha, x, BitmapColor(0) ); + 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.GetAlpha(); + } + else if(TransparentType::Bitmap == rBitmapEx.GetTransparentType()) + { + aOldMask = rBitmapEx.GetMask(); + } + else if(TransparentType::Color == rBitmapEx.GetTransparentType()) + { + aOldMask = rBitmapEx.GetBitmap().CreateMask(rBitmapEx.GetTransparentColor()); + } + + { + AlphaScopedWriteAccess 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(long y(0); y < pOld->Height(); y++) + { + Scanline pScanline = pOld->GetScanline( y ); + for(long x(0); x < pOld->Width(); x++) + { + const double fOpOld(1.0 - (pOld->GetIndexFromData(pScanline, x) * fFactor)); + const sal_uInt8 aCol(basegfx::fround((1.0 - (fOpOld * fOpNew)) * 255.0)); + + pOld->SetPixelOnData(pScanline, x, BitmapColor(aCol)); + } + } + } + else + { + AlphaMask::ScopedReadAccess 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(long y(0); y < pOld->Height(); y++) + { + Scanline pScanline = pOld->GetScanline( y ); + for(long x(0); x < pOld->Width(); x++) + { + const double fOpOld(1.0 - (pOld->GetIndexFromData(pScanline, x) * fFactor)); + const double fOpNew(1.0 - (pNew->GetIndexFromData(pScanline, x) * fFactor)); + const sal_uInt8 aCol(basegfx::fround((1.0 - (fOpOld * fOpNew)) * 255.0)); + + 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.IsTransparent()) + { + // bitmap already uses a Mask or Alpha, we need to blend that with + // the new masking in pVDev + if(aBmpEx.IsAlpha()) + { + // need to blend in AlphaMask quality (8Bit) + AlphaMask fromVDev(aVDevMask); + AlphaMask fromBmpEx(aBmpEx.GetAlpha()); + AlphaMask::ScopedReadAccess pR(fromVDev); + AlphaScopedWriteAccess pW(fromBmpEx); + + if(pR && pW) + { + const long nWidth(std::min(pR->Width(), pW->Width())); + const long nHeight(std::min(pR->Height(), pW->Height())); + + for(long nY(0); nY < nHeight; nY++) + { + Scanline pScanlineR = pR->GetScanline( nY ); + Scanline pScanlineW = pW->GetScanline( nY ); + for(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 transparency (0 == no, 255 == fully transparent), + // so to blend these we have to multiply the inverse (opacity) + // and re-invert the result to transparence + const sal_uInt8 nCombined(0x00ff - (((0x00ff - nIndR) * (0x00ff - nIndW)) >> 8)); + + pW->SetPixelOnData(pScanlineW, nX, BitmapColor(nCombined)); + } + } + } + + pR.reset(); + pW.reset(); + aBmpEx = BitmapEx(aBmpEx.GetBitmap(), fromBmpEx); + } + else + { + // need to blend in Mask quality (1Bit) + Bitmap aMask(aVDevMask.CreateMask(COL_WHITE)); + + if ( rBitmap.GetTransparentColor() == COL_WHITE ) + { + aMask.CombineSimple( rBitmap.GetMask(), BmpCombine::Or ); + } + else + { + aMask.CombineSimple( rBitmap.GetMask(), BmpCombine::And ); + } + + aBmpEx = BitmapEx( rBitmap.GetBitmap(), aMask ); + } + } + 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.GetAlpha().GetBitmap(), aMem, false, true); + return css::uno::Sequence< sal_Int8 >( static_cast<sal_Int8 const *>(aMem.GetData()), aMem.Tell() ); + } + else if ( aBmpEx.IsTransparent() ) + { + SvMemoryStream aMem; + WriteDIB(aBmpEx.GetMask(), 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, long nY, const long nWidth, unsigned char* data, long nOff ) +{ + bool bIsAlpha = false; + long nX; + int nAlpha; + Scanline pReadScan; + + nOff += 3; + + switch( pAlphaReadAcc->GetScanlineFormat() ) + { + case ScanlineFormat::N8BitTcMask: + pReadScan = pAlphaReadAcc->GetScanline( nY ); + for( nX = 0; nX < nWidth; nX++ ) + { + nAlpha = data[ nOff ] = 255 - ( *pReadScan++ ); + if( nAlpha != 255 ) + bIsAlpha = true; + nOff += 4; + } + break; + case ScanlineFormat::N8BitPal: + pReadScan = pAlphaReadAcc->GetScanline( nY ); + for( nX = 0; nX < nWidth; nX++ ) + { + BitmapColor const& rColor( + pAlphaReadAcc->GetPaletteColor(*pReadScan)); + pReadScan++; + nAlpha = data[ nOff ] = 255 - 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 ] = 255 - 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, long& rnWidth, long& rnHeight ) +{ + AlphaMask aAlpha = aBmpEx.GetAlpha(); + + ::BitmapReadAccess* pBitmapReadAcc = aBitmap.AcquireReadAccess(); + ::BitmapReadAccess* pAlphaReadAcc = nullptr; + const long nWidth = rnWidth = pBitmapReadAcc->Width(); + const long nHeight = rnHeight = pBitmapReadAcc->Height(); + long nX, nY; + bool bIsAlpha = false; + + if( aBmpEx.IsTransparent() || aBmpEx.IsAlpha() ) + pAlphaReadAcc = aAlpha.AcquireReadAccess(); + + data = static_cast<unsigned char*>(malloc( nWidth*nHeight*4 )); + + long nOff = 0; + ::Color aColor; + unsigned int nAlpha = 255; + + vcl::bitmap::lookup_table premultiply_table = vcl::bitmap::get_premultiply_table(); + for( nY = 0; nY < nHeight; nY++ ) + { + ::Scanline pReadScan; + + switch( pBitmapReadAcc->GetScanlineFormat() ) + { + case ScanlineFormat::N8BitPal: + pReadScan = pBitmapReadAcc->GetScanline( nY ); + if( pAlphaReadAcc ) + if( readAlpha( pAlphaReadAcc, 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 + data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetRed()]; + data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetGreen()]; + data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetBlue()]; +#else + data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetBlue()]; + data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetGreen()]; + data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetRed()]; + nOff++; +#endif + } + break; + case ScanlineFormat::N24BitTcBgr: + pReadScan = pBitmapReadAcc->GetScanline( nY ); + if( pAlphaReadAcc ) + if( readAlpha( pAlphaReadAcc, 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; + data[ nOff + 3 ] = premultiply_table[nAlpha][*pReadScan++]; + data[ nOff + 2 ] = premultiply_table[nAlpha][*pReadScan++]; + data[ nOff + 1 ] = premultiply_table[nAlpha][*pReadScan++]; + nOff += 4; +#else + if( pAlphaReadAcc ) + nAlpha = data[ nOff + 3 ]; + else + nAlpha = data[ nOff + 3 ] = 255; + data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++]; + data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++]; + data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++]; + nOff++; +#endif + } + break; + case ScanlineFormat::N24BitTcRgb: + pReadScan = pBitmapReadAcc->GetScanline( nY ); + if( pAlphaReadAcc ) + if( readAlpha( pAlphaReadAcc, 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; + data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++]; + data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++]; + data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++]; +#else + if( pAlphaReadAcc ) + nAlpha = data[ nOff + 3 ]; + else + nAlpha = data[ nOff + 3 ] = 255; + data[ nOff++ ] = premultiply_table[nAlpha][pReadScan[ 2 ]]; + data[ nOff++ ] = premultiply_table[nAlpha][pReadScan[ 1 ]]; + data[ nOff++ ] = premultiply_table[nAlpha][pReadScan[ 0 ]]; + pReadScan += 3; + nOff++; +#endif + } + break; + case ScanlineFormat::N32BitTcBgra: + pReadScan = pBitmapReadAcc->GetScanline( nY ); + if( pAlphaReadAcc ) + if( readAlpha( pAlphaReadAcc, 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; + data[ nOff++ ] = premultiply_table[nAlpha][pReadScan[ 2 ]]; + data[ nOff++ ] = premultiply_table[nAlpha][pReadScan[ 1 ]]; + data[ nOff++ ] = premultiply_table[nAlpha][pReadScan[ 0 ]]; + pReadScan += 4; +#else + if( pAlphaReadAcc ) + nAlpha = data[ nOff + 3 ]; + else + nAlpha = data[ nOff + 3 ] = 255; + data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++]; + data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++]; + data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++]; + pReadScan++; + nOff++; +#endif + } + break; + case ScanlineFormat::N32BitTcRgba: + pReadScan = pBitmapReadAcc->GetScanline( nY ); + if( pAlphaReadAcc ) + if( readAlpha( pAlphaReadAcc, 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; + data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++]; + data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++]; + data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++]; + pReadScan++; +#else + if( pAlphaReadAcc ) + nAlpha = data[ nOff + 3 ]; + else + nAlpha = data[ nOff + 3 ] = 255; + data[ nOff++ ] = premultiply_table[nAlpha][pReadScan[ 2 ]]; + data[ nOff++ ] = premultiply_table[nAlpha][pReadScan[ 1 ]]; + data[ nOff++ ] = premultiply_table[nAlpha][pReadScan[ 0 ]]; + 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, 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; + data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetRed()]; + data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetGreen()]; + data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetBlue()]; +#else + if( pAlphaReadAcc ) + nAlpha = data[ nOff + 3 ]; + else + nAlpha = data[ nOff + 3 ] = 255; + data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetBlue()]; + data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetGreen()]; + data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetRed()]; + nOff ++; +#endif + } + } + } + + ::Bitmap::ReleaseAccess( pBitmapReadAcc ); + if( pAlphaReadAcc ) + aAlpha.ReleaseAccess( pAlphaReadAcc ); + + bHasAlpha = bIsAlpha; + +} + + uno::Sequence< sal_Int8 > CanvasExtractBitmapData(BitmapEx const & rBitmapEx, const geometry::IntegerRectangle2D& rect) + { + Bitmap aBitmap( rBitmapEx.GetBitmap() ); + Bitmap aAlpha( rBitmapEx.GetAlpha().GetBitmap() ); + + Bitmap::ScopedReadAccess pReadAccess( aBitmap ); + Bitmap::ScopedReadAccess pAlphaReadAccess( aAlpha.IsEmpty() ? + nullptr : aAlpha.AcquireReadAccess(), + 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( long y=rect.Y1; + y<aBmpSize.Height() && y<rect.Y2; + ++y ) + { + if( pAlphaReadAccess.get() != nullptr ) + { + Scanline pScanlineReadAlpha = pAlphaReadAccess->GetScanline( y ); + for( 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++ ] = pAlphaReadAccess->GetIndexFromData( pScanlineReadAlpha, x ); + } + } + else + { + for( 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), 1, &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.IsTransparent()) + { + Bitmap aBitmap(rBitmapEx.GetBitmap()); + + if(8 == aBitmap.GetSizePixel().Width() && 8 == aBitmap.GetSizePixel().Height()) + { + if(2 == aBitmap.GetColorCount()) + { + BitmapReadAccess* pRead = aBitmap.AcquireReadAccess(); + + if(pRead) + { + if(pRead->HasPalette() && 2 == pRead->GetPaletteEntryCount()) + { + const BitmapPalette& rPalette = pRead->GetPalette(); + + // #i123564# background and foreground were exchanged; of course + // rPalette[0] is the background color + o_rFront = rPalette[1]; + o_rBack = rPalette[0]; + + bRet = true; + } + + Bitmap::ReleaseAccess(pRead); + } + } + } + } + + return bRet; + } + + 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; + } + + lookup_table get_unpremultiply_table() + { + static bool inited; + static sal_uInt8 unpremultiply_table[256][256]; + + if (!inited) + { + for (int a = 0; a < 256; ++a) + for (int c = 0; c < 256; ++c) + unpremultiply_table[a][c] = unpremultiply(c, a); + inited = true; + } + + return unpremultiply_table; + } + + lookup_table get_premultiply_table() + { + static bool inited; + static sal_uInt8 premultiply_table[256][256]; + + if (!inited) + { + for (int a = 0; a < 256; ++a) + for (int c = 0; c < 256; ++c) + premultiply_table[a][c] = premultiply(c, a); + inited = true; + } + + return premultiply_table; + } + +bool convertBitmap32To24Plus8(BitmapEx const & rInput, BitmapEx & rResult) +{ + Bitmap aBitmap(rInput.GetBitmap()); + if (aBitmap.GetBitCount() != 32) + return false; + + Size aSize = aBitmap.GetSizePixel(); + Bitmap aResultBitmap(aSize, 24); + AlphaMask aResultAlpha(aSize); + { + BitmapScopedWriteAccess pResultBitmapAccess(aResultBitmap); + AlphaScopedWriteAccess pResultAlphaAccess(aResultAlpha); + + Bitmap::ScopedReadAccess pReadAccess(aBitmap); + + for (long nY = 0; nY < aSize.Height(); ++nY) + { + Scanline aResultScan = pResultBitmapAccess->GetScanline(nY); + Scanline aResultScanAlpha = pResultAlphaAccess->GetScanline(nY); + + Scanline aReadScan = pReadAccess->GetScanline(nY); + + for (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.IsTransparent()) + rResult = BitmapEx(aResultBitmap, rInput.GetAlpha()); + else + rResult = BitmapEx(aResultBitmap, aResultAlpha); + return true; +} + +} // end vcl::bitmap + +/* 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 000000000..44f911ef0 --- /dev/null +++ b/vcl/source/bitmap/Octree.cxx @@ -0,0 +1,288 @@ +/* -*- 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/bitmapaccess.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) + , mpColor(nullptr) + , mnPalIndex(0) +{ + const BitmapReadAccess* pAccess = &rReadAcc; + sal_uLong nMax(nColors); + + if (!!*pAccess) + { + const long nWidth = pAccess->Width(); + const long nHeight = pAccess->Height(); + + if (pAccess->HasPalette()) + { + for (long nY = 0; nY < nHeight; nY++) + { + Scanline pScanline = pAccess->GetScanline(nY); + for (long nX = 0; nX < nWidth; nX++) + { + mpColor = &pAccess->GetPaletteColor(pAccess->GetIndexFromData(pScanline, nX)); + mnLevel = 0; + add(pTree); + + while (mnLeafCount > nMax) + reduce(); + } + } + } + else + { + BitmapColor aColor; + + mpColor = &aColor; + + for (long nY = 0; nY < nHeight; nY++) + { + Scanline pScanline = pAccess->GetScanline(nY); + for (long nX = 0; nX < nWidth; nX++) + { + aColor = pAccess->GetPixelFromData(pScanline, nX); + mnLevel = 0; + add(pTree); + + while (mnLeafCount > nMax) + reduce(); + } + } + } + } +} + +Octree::~Octree() {} + +void Octree::add(std::unique_ptr<OctreeNode>& rpNode) +{ + // 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 += mpColor->GetRed(); + rpNode->nGreen += mpColor->GetGreen(); + rpNode->nBlue += mpColor->GetBlue(); + } + else + { + const sal_uLong nShift = 7 - mnLevel; + const sal_uInt8 cMask = 0x80 >> mnLevel; + const sal_uLong nIndex = (((mpColor->GetRed() & cMask) >> nShift) << 2) + | (((mpColor->GetGreen() & cMask) >> nShift) << 1) + | ((mpColor->GetBlue() & cMask) >> nShift); + + mnLevel++; + add(rpNode->pChild[nIndex]); + } +} + +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) +{ + if (pNode->bLeaf) + mnPalIndex = pNode->nPalIndex; + else + { + const sal_uLong nShift = 7 - mnLevel; + const sal_uInt8 cMask = 0x80 >> mnLevel; + mnLevel++; + const sal_uLong nIndex = (((mpColor->GetRed() & cMask) >> nShift) << 2) + | (((mpColor->GetGreen() & cMask) >> nShift) << 1) + | ((mpColor->GetBlue() & cMask) >> nShift); + + GetPalIndex(pNode->pChild[nIndex].get()); + } +} + +const BitmapPalette& Octree::GetPalette() +{ + maPalette.SetEntryCount(sal_uInt16(mnLeafCount)); + mnPalIndex = 0; + CreatePalette(pTree.get()); + return maPalette; +} + +sal_uInt16 Octree::GetBestPaletteIndex(const BitmapColor& rColor) +{ + mpColor = &rColor; + mnPalIndex = 65535; + mnLevel = 0; + GetPalIndex(pTree.get()); + 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 long x = 1 << gnBits; + const long x2 = x >> 1; + sal_uLong r, g, b; + long rxx, gxx, bxx; + + ImplCreateBuffers(); + + for (int nIndex = 0; nIndex < nColors; nIndex++) + { + const BitmapColor& rColor = rPal[static_cast<sal_uInt16>(nIndex)]; + const long cRed = rColor.GetRed(); + const long cGreen = rColor.GetGreen(); + const long cBlue = rColor.GetBlue(); + + long rdist = cRed - x2; + long gdist = cGreen - x2; + long bdist = cBlue - x2; + rdist = rdist * rdist + gdist * gdist + bdist * bdist; + + const long crinc = (xsqr - (cRed << gnBits)) << 1; + const long cginc = (xsqr - (cGreen << gnBits)) << 1; + const 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<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/bitmap.cxx b/vcl/source/bitmap/bitmap.cxx new file mode 100644 index 000000000..af5a6f440 --- /dev/null +++ b/vcl/source/bitmap/bitmap.cxx @@ -0,0 +1,892 @@ +/* -*- 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/bitmap.hxx> +#include <vcl/bitmapaccess.hxx> +#include <vcl/outdev.hxx> + +#include <svdata.hxx> +#include <salinst.hxx> +#include <salbmp.hxx> +#include <bitmapwriteaccess.hxx> + +#include <algorithm> +#include <memory> + +Bitmap::Bitmap() +{ +} + +Bitmap::Bitmap(const Bitmap& rBitmap) + : mxSalBmp(rBitmap.mxSalBmp) + , maPrefMapMode(rBitmap.maPrefMapMode) + , maPrefSize(rBitmap.maPrefSize) +{ +} + +Bitmap::Bitmap(std::shared_ptr<SalBitmap> const & pSalBitmap) + : mxSalBmp(pSalBitmap) + , maPrefMapMode(MapMode(MapUnit::MapPixel)) + , maPrefSize(mxSalBmp->GetSize()) +{ +} + +Bitmap::Bitmap( const Size& rSizePixel, sal_uInt16 nBitCount, const BitmapPalette* pPal ) +{ + if (rSizePixel.Width() && rSizePixel.Height()) + { + BitmapPalette aPal; + BitmapPalette* pRealPal = nullptr; + + if( nBitCount <= 8 ) + { + if( !pPal ) + { + if( 1 == nBitCount ) + { + aPal.SetEntryCount( 2 ); + aPal[ 0 ] = COL_BLACK; + aPal[ 1 ] = COL_WHITE; + } + else if( ( 4 == nBitCount ) || ( 8 == nBitCount ) ) + { + aPal.SetEntryCount( 1 << nBitCount ); + 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 + if( 8 == nBitCount ) + { + 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 ); + } + } + } + else + pRealPal = const_cast<BitmapPalette*>(pPal); + } + + mxSalBmp = ImplGetSVData()->mpDefInst->CreateSalBitmap(); + mxSalBmp->Create( rSizePixel, nBitCount, pRealPal ? *pRealPal : aPal ); + } +} + +Bitmap::~Bitmap() +{ +} + +const BitmapPalette& Bitmap::GetGreyPalette( int nEntries ) +{ + // Create greyscale palette with 2, 4, 16 or 256 entries + switch (nEntries) + { + case 2: + { + static const BitmapPalette aGreyPalette2 = [] { + BitmapPalette aPalette(2); + aPalette[0] = BitmapColor(0, 0, 0); + aPalette[1] = BitmapColor(255, 255, 255); + return aPalette; + }(); + + return aGreyPalette2; + } + case 4: + { + static const BitmapPalette aGreyPalette4 = [] { + BitmapPalette aPalette(4); + aPalette[0] = BitmapColor(0, 0, 0); + aPalette[1] = BitmapColor(85, 85, 85); + aPalette[2] = BitmapColor(170, 170, 170); + aPalette[3] = BitmapColor(255, 255, 255); + return aPalette; + }(); + + return aGreyPalette4; + } + case 16: + { + static const BitmapPalette aGreyPalette16 = [] { + sal_uInt8 cGrey = 0; + sal_uInt8 const cGreyInc = 17; + + BitmapPalette aPalette(16); + + for (sal_uInt16 i = 0; i < 16; ++i, cGrey += cGreyInc) + aPalette[i] = BitmapColor(cGrey, cGrey, cGrey); + + return aPalette; + }(); + + return aGreyPalette16; + } + case 256: + { + static const BitmapPalette aGreyPalette256 = [] { + BitmapPalette aPalette(256); + + for (sal_uInt16 i = 0; i < 256; ++i) + aPalette[i] = BitmapColor(static_cast<sal_uInt8>(i), static_cast<sal_uInt8>(i), + static_cast<sal_uInt8>(i)); + + return aPalette; + }(); + + return aGreyPalette256; + } + } + OSL_FAIL("Bitmap::GetGreyPalette: invalid entry count (2/4/16/256 allowed)"); + return GetGreyPalette(2); +} + +bool BitmapPalette::IsGreyPaletteAny() const +{ + 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(maBitmapColor[0]); + const BitmapColor& rCol1(maBitmapColor[1]); + bRet = rCol0.GetRed() == rCol0.GetGreen() && rCol0.GetRed() == rCol0.GetBlue() && + rCol1.GetRed() == rCol1.GetGreen() && rCol1.GetRed() == rCol1.GetBlue(); + } + return bRet; +} + +bool BitmapPalette::IsGreyPalette8Bit() const +{ + 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( maBitmapColor[i] != BitmapColor(i, i, i)) + return false; + } + return true; +} + +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, aChecksum2; + rBmp.mxSalBmp->GetChecksum(aChecksum1); + mxSalBmp->GetChecksum(aChecksum2); + // 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() ); +} + +sal_uInt16 Bitmap::GetBitCount() const +{ + if (!mxSalBmp) + return 0; + + sal_uInt16 nBitCount = mxSalBmp->GetBitCount(); + if (nBitCount <= 1) + return 1; + if (nBitCount <= 4) + return 4; + if (nBitCount <= 8) + return 8; + if (nBitCount <= 24) + return 24; + if (nBitCount <= 32) + return 32; + return 0; +} + +bool Bitmap::HasGreyPaletteAny() const +{ + const sal_uInt16 nBitCount = GetBitCount(); + bool bRet = nBitCount == 1; + + ScopedInfoAccess pIAcc(const_cast<Bitmap&>(*this)); + + if( pIAcc ) + { + bRet = pIAcc->HasPalette() && pIAcc->GetPalette().IsGreyPaletteAny(); + } + + return bRet; +} + +bool Bitmap::HasGreyPalette8Bit() const +{ + bool bRet = false; + ScopedInfoAccess pIAcc(const_cast<Bitmap&>(*this)); + + if( pIAcc ) + { + bRet = pIAcc->HasPalette() && pIAcc->GetPalette().IsGreyPalette8Bit(); + } + + return bRet; +} + +BitmapChecksum Bitmap::GetChecksum() const +{ + BitmapChecksum nRet = 0; + + if( mxSalBmp ) + { + mxSalBmp->GetChecksum(nRet); + + 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, GetBitCount())) + { + Bitmap* pThis = const_cast<Bitmap*>(this); + pThis->mxSalBmp = xNewImpBmp; + mxSalBmp->GetChecksum(nRet); + } + } + } + + 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; +} + +BitmapInfoAccess* Bitmap::AcquireInfoAccess() +{ + std::unique_ptr<BitmapInfoAccess> pInfoAccess(new BitmapInfoAccess( *this )); + + if( !*pInfoAccess ) + { + return nullptr; + } + + return pInfoAccess.release(); +} + +BitmapReadAccess* Bitmap::AcquireReadAccess() +{ + std::unique_ptr<BitmapReadAccess> pReadAccess(new BitmapReadAccess( *this )); + + if( !*pReadAccess ) + { + return nullptr; + } + + return pReadAccess.release(); +} + +BitmapWriteAccess* Bitmap::AcquireWriteAccess() +{ + std::unique_ptr<BitmapWriteAccess> pWriteAccess(new BitmapWriteAccess( *this )); + + if( !*pWriteAccess ) + { + return nullptr; + } + + return pWriteAccess.release(); +} + +void Bitmap::ReleaseAccess( BitmapInfoAccess* pBitmapAccess ) +{ + delete pBitmapAccess; +} + +bool Bitmap::Crop( const tools::Rectangle& rRectPixel ) +{ + const Size aSizePix( GetSizePixel() ); + tools::Rectangle aRect( rRectPixel ); + bool bRet = false; + + aRect.Intersection( tools::Rectangle( Point(), aSizePix ) ); + + if( !aRect.IsEmpty() && aSizePix != aRect.GetSize()) + { + ScopedReadAccess pReadAcc(*this); + + if( pReadAcc ) + { + const tools::Rectangle aNewRect( Point(), aRect.GetSize() ); + Bitmap aNewBmp( aNewRect.GetSize(), GetBitCount(), &pReadAcc->GetPalette() ); + BitmapScopedWriteAccess pWriteAcc(aNewBmp); + + if( pWriteAcc ) + { + const long nOldX = aRect.Left(); + const long nOldY = aRect.Top(); + const long nNewWidth = aNewRect.GetWidth(); + const long nNewHeight = aNewRect.GetHeight(); + + for( long nY = 0, nY2 = nOldY; nY < nNewHeight; nY++, nY2++ ) + { + Scanline pScanline = pWriteAcc->GetScanline(nY); + Scanline pScanlineRead = pReadAcc->GetScanline(nY2); + for( long nX = 0, nX2 = nOldX; nX < nNewWidth; nX++, nX2++ ) + pWriteAcc->SetPixelOnData( pScanline, nX, pReadAcc->GetPixelFromData( pScanlineRead, nX2 ) ); + } + + pWriteAcc.reset(); + bRet = true; + } + + pReadAcc.reset(); + + if( bRet ) + ReassignWithSize( aNewBmp ); + } + } + + return bRet; +}; + +bool Bitmap::CopyPixel( const tools::Rectangle& rRectDst, + const tools::Rectangle& rRectSrc, const Bitmap* pBmpSrc ) +{ + const Size aSizePix( GetSizePixel() ); + tools::Rectangle aRectDst( rRectDst ); + bool bRet = false; + + aRectDst.Intersection( tools::Rectangle( Point(), aSizePix ) ); + + if( !aRectDst.IsEmpty() ) + { + if( pBmpSrc && ( pBmpSrc->mxSalBmp != mxSalBmp ) ) + { + Bitmap* pSrc = const_cast<Bitmap*>(pBmpSrc); + const Size aCopySizePix( pSrc->GetSizePixel() ); + tools::Rectangle aRectSrc( rRectSrc ); + const sal_uInt16 nSrcBitCount = pBmpSrc->GetBitCount(); + const sal_uInt16 nDstBitCount = GetBitCount(); + + 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) + { + Convert( BmpConversion::N4BitColors ); + nNextIndex = 2; + } + + if( nNextIndex ) + { + ScopedReadAccess pSrcAcc(*pSrc); + BitmapScopedWriteAccess pDstAcc(*this); + + if( pSrcAcc && pDstAcc ) + { + const int nSrcCount = pDstAcc->GetPaletteEntryCount(); + const int nDstCount = 1 << nDstBitCount; + + for (int i = 0; ( i < nSrcCount ) && ( nNextIndex < nSrcCount ); ++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() ) + { + ScopedReadAccess pReadAcc(*pSrc); + + if( pReadAcc ) + { + BitmapScopedWriteAccess pWriteAcc(*this); + + if( pWriteAcc ) + { + const long nWidth = std::min( aRectSrc.GetWidth(), aRectDst.GetWidth() ); + const long nHeight = std::min( aRectSrc.GetHeight(), aRectDst.GetHeight() ); + const long nSrcEndX = aRectSrc.Left() + nWidth; + const long nSrcEndY = aRectSrc.Top() + nHeight; + 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( long nSrcY = aRectSrc.Top(); nSrcY < nSrcEndY; nSrcY++, nDstY++ ) + { + Scanline pScanline = pWriteAcc->GetScanline(nDstY); + Scanline pScanlineRead = pReadAcc->GetScanline(nSrcY); + for( 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( long nSrcY = aRectSrc.Top(); nSrcY < nSrcEndY; nSrcY++, nDstY++ ) + { + Scanline pScanline = pWriteAcc->GetScanline(nDstY); + Scanline pScanlineRead = pReadAcc->GetScanline(nSrcY); + for( long nSrcX = aRectSrc.Left(), nDstX = aRectDst.Left(); nSrcX < nSrcEndX; nSrcX++, nDstX++ ) + pWriteAcc->SetPixelOnData( pScanline, nDstX, pReadAcc->GetPaletteColor( pReadAcc->GetIndexFromData( pScanlineRead, nSrcX ) ) ); + } + } + else + for( long nSrcY = aRectSrc.Top(); nSrcY < nSrcEndY; nSrcY++, nDstY++ ) + { + Scanline pScanline = pWriteAcc->GetScanline(nDstY); + Scanline pScanlineRead = pReadAcc->GetScanline(nSrcY); + for( long nSrcX = aRectSrc.Left(), nDstX = aRectDst.Left(); nSrcX < nSrcEndX; nSrcX++, nDstX++ ) + pWriteAcc->SetPixelOnData( pScanline, nDstX, pReadAcc->GetPixelFromData( pScanlineRead, nSrcX ) ); + } + + pWriteAcc.reset(); + bRet = ( nWidth > 0 ) && ( nHeight > 0 ); + } + + pReadAcc.reset(); + } + } + } + else + { + tools::Rectangle aRectSrc( rRectSrc ); + + aRectSrc.Intersection( tools::Rectangle( Point(), aSizePix ) ); + + if( !aRectSrc.IsEmpty() && ( aRectSrc != aRectDst ) ) + { + BitmapScopedWriteAccess pWriteAcc(*this); + + if( pWriteAcc ) + { + const long nWidth = std::min( aRectSrc.GetWidth(), aRectDst.GetWidth() ); + const long nHeight = std::min( aRectSrc.GetHeight(), aRectDst.GetHeight() ); + const long nSrcX = aRectSrc.Left(); + const long nSrcY = aRectSrc.Top(); + const long nSrcEndX1 = nSrcX + nWidth - 1; + const long nSrcEndY1 = nSrcY + nHeight - 1; + const long nDstX = aRectDst.Left(); + const long nDstY = aRectDst.Top(); + const long nDstEndX1 = nDstX + nWidth - 1; + const long nDstEndY1 = nDstY + nHeight - 1; + + if( ( nDstX <= nSrcX ) && ( nDstY <= nSrcY ) ) + { + for( long nY = nSrcY, nYN = nDstY; nY <= nSrcEndY1; nY++, nYN++ ) + { + Scanline pScanline = pWriteAcc->GetScanline(nYN); + Scanline pScanlineSrc = pWriteAcc->GetScanline(nY); + for( long nX = nSrcX, nXN = nDstX; nX <= nSrcEndX1; nX++, nXN++ ) + pWriteAcc->SetPixelOnData( pScanline, nXN, pWriteAcc->GetPixelFromData( pScanlineSrc, nX ) ); + } + } + else if( ( nDstX <= nSrcX ) && ( nDstY >= nSrcY ) ) + { + for( long nY = nSrcEndY1, nYN = nDstEndY1; nY >= nSrcY; nY--, nYN-- ) + { + Scanline pScanline = pWriteAcc->GetScanline(nYN); + Scanline pScanlineSrc = pWriteAcc->GetScanline(nY); + for( long nX = nSrcX, nXN = nDstX; nX <= nSrcEndX1; nX++, nXN++ ) + pWriteAcc->SetPixelOnData( pScanline, nXN, pWriteAcc->GetPixelFromData( pScanlineSrc, nX ) ); + } + } + else if( ( nDstX >= nSrcX ) && ( nDstY <= nSrcY ) ) + { + for( long nY = nSrcY, nYN = nDstY; nY <= nSrcEndY1; nY++, nYN++ ) + { + Scanline pScanline = pWriteAcc->GetScanline(nYN); + Scanline pScanlineSrc = pWriteAcc->GetScanline(nY); + for( long nX = nSrcEndX1, nXN = nDstEndX1; nX >= nSrcX; nX--, nXN-- ) + pWriteAcc->SetPixelOnData( pScanline, nXN, pWriteAcc->GetPixelFromData( pScanlineSrc, nX ) ); + } + } + else + { + for( long nY = nSrcEndY1, nYN = nDstEndY1; nY >= nSrcY; nY--, nYN-- ) + { + Scanline pScanline = pWriteAcc->GetScanline(nYN); + Scanline pScanlineSrc = pWriteAcc->GetScanline(nY); + for( long nX = nSrcEndX1, nXN = nDstEndX1; nX >= nSrcX; nX--, nXN-- ) + pWriteAcc->SetPixelOnData( pScanline, nXN, pWriteAcc->GetPixelFromData( pScanlineSrc, nX ) ); + } + } + + pWriteAcc.reset(); + bRet = true; + } + } + } + } + + return bRet; +} + +bool Bitmap::CopyPixel_AlphaOptimized( const tools::Rectangle& rRectDst, const tools::Rectangle& rRectSrc, + const Bitmap* pBmpSrc ) +{ + // 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 ); + bool bRet = false; + + aRectDst.Intersection( tools::Rectangle( Point(), aSizePix ) ); + + if( !aRectDst.IsEmpty() ) + { + if( pBmpSrc && ( pBmpSrc->mxSalBmp != mxSalBmp ) ) + { + Bitmap* pSrc = const_cast<Bitmap*>(pBmpSrc); + const Size aCopySizePix( pSrc->GetSizePixel() ); + tools::Rectangle aRectSrc( rRectSrc ); + + aRectSrc.Intersection( tools::Rectangle( Point(), aCopySizePix ) ); + + if( !aRectSrc.IsEmpty() ) + { + ScopedReadAccess pReadAcc(*pSrc); + + if( pReadAcc ) + { + BitmapScopedWriteAccess pWriteAcc(*this); + + if( pWriteAcc ) + { + const long nWidth = std::min( aRectSrc.GetWidth(), aRectDst.GetWidth() ); + const long nHeight = std::min( aRectSrc.GetHeight(), aRectDst.GetHeight() ); + const long nSrcEndX = aRectSrc.Left() + nWidth; + const long nSrcEndY = aRectSrc.Top() + nHeight; + long nDstY = aRectDst.Top(); + + for( long nSrcY = aRectSrc.Top(); nSrcY < nSrcEndY; nSrcY++, nDstY++) + { + Scanline pScanline = pWriteAcc->GetScanline(nDstY); + Scanline pScanlineRead = pReadAcc->GetScanline(nSrcY); + for( long nSrcX = aRectSrc.Left(), nDstX = aRectDst.Left(); nSrcX < nSrcEndX; nSrcX++, nDstX++ ) + pWriteAcc->SetPixelOnData( pScanline, nDstX, pReadAcc->GetPixelFromData( pScanlineRead, nSrcX ) ); + } + + pWriteAcc.reset(); + bRet = ( nWidth > 0 ) && ( nHeight > 0 ); + } + + pReadAcc.reset(); + } + } + } + else + { + tools::Rectangle aRectSrc( rRectSrc ); + + aRectSrc.Intersection( tools::Rectangle( Point(), aSizePix ) ); + + if( !aRectSrc.IsEmpty() && ( aRectSrc != aRectDst ) ) + { + BitmapScopedWriteAccess pWriteAcc(*this); + + if( pWriteAcc ) + { + const long nWidth = std::min( aRectSrc.GetWidth(), aRectDst.GetWidth() ); + const long nHeight = std::min( aRectSrc.GetHeight(), aRectDst.GetHeight() ); + const long nSrcX = aRectSrc.Left(); + const long nSrcY = aRectSrc.Top(); + const long nSrcEndX1 = nSrcX + nWidth - 1; + const long nSrcEndY1 = nSrcY + nHeight - 1; + const long nDstX = aRectDst.Left(); + const long nDstY = aRectDst.Top(); + const long nDstEndX1 = nDstX + nWidth - 1; + const long nDstEndY1 = nDstY + nHeight - 1; + + if( ( nDstX <= nSrcX ) && ( nDstY <= nSrcY ) ) + { + for( long nY = nSrcY, nYN = nDstY; nY <= nSrcEndY1; nY++, nYN++ ) + { + Scanline pScanline = pWriteAcc->GetScanline(nYN); + Scanline pScanlineSrc = pWriteAcc->GetScanline(nY); + for( long nX = nSrcX, nXN = nDstX; nX <= nSrcEndX1; nX++, nXN++ ) + pWriteAcc->SetPixelOnData( pScanline, nXN, pWriteAcc->GetPixelFromData( pScanlineSrc, nX ) ); + } + } + else if( ( nDstX <= nSrcX ) && ( nDstY >= nSrcY ) ) + { + for( long nY = nSrcEndY1, nYN = nDstEndY1; nY >= nSrcY; nY--, nYN-- ) + { + Scanline pScanline = pWriteAcc->GetScanline(nYN); + Scanline pScanlineSrc = pWriteAcc->GetScanline(nY); + for( long nX = nSrcX, nXN = nDstX; nX <= nSrcEndX1; nX++, nXN++ ) + pWriteAcc->SetPixelOnData( pScanline, nXN, pWriteAcc->GetPixelFromData( pScanlineSrc, nX ) ); + } + } + else if( ( nDstX >= nSrcX ) && ( nDstY <= nSrcY ) ) + { + for( long nY = nSrcY, nYN = nDstY; nY <= nSrcEndY1; nY++, nYN++ ) + { + Scanline pScanline = pWriteAcc->GetScanline(nYN); + Scanline pScanlineSrc = pWriteAcc->GetScanline(nY); + for( long nX = nSrcEndX1, nXN = nDstEndX1; nX >= nSrcX; nX--, nXN-- ) + pWriteAcc->SetPixelOnData( pScanline, nXN, pWriteAcc->GetPixelFromData( pScanlineSrc, nX ) ); + } + } + else + { + for( long nY = nSrcEndY1, nYN = nDstEndY1; nY >= nSrcY; nY--, nYN-- ) + { + Scanline pScanline = pWriteAcc->GetScanline(nYN); + Scanline pScanlineSrc = pWriteAcc->GetScanline(nY); + for( long nX = nSrcEndX1, nXN = nDstEndX1; nX >= nSrcX; nX--, nXN-- ) + pWriteAcc->SetPixelOnData( pScanline, nXN, pWriteAcc->GetPixelFromData( pScanlineSrc, nX ) ); + } + } + + pWriteAcc.reset(); + bRet = true; + } + } + } + } + + return bRet; + +} + +bool Bitmap::Expand( sal_uLong nDX, sal_uLong nDY, const Color* pInitColor ) +{ + bool bRet = false; + + if( nDX || nDY ) + { + const Size aSizePixel( GetSizePixel() ); + const long nWidth = aSizePixel.Width(); + const long nHeight = aSizePixel.Height(); + const Size aNewSize( nWidth + nDX, nHeight + nDY ); + ScopedReadAccess pReadAcc(*this); + + if( pReadAcc ) + { + BitmapPalette aBmpPal( pReadAcc->GetPalette() ); + Bitmap aNewBmp( aNewSize, GetBitCount(), &aBmpPal ); + BitmapScopedWriteAccess pWriteAcc(aNewBmp); + + if( pWriteAcc ) + { + BitmapColor aColor; + const long nNewX = nWidth; + const long nNewY = nHeight; + const long nNewWidth = pWriteAcc->Width(); + const long nNewHeight = pWriteAcc->Height(); + long nX; + 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(); + bRet = true; + } + + pReadAcc.reset(); + + if (bRet) + ReassignWithSize(aNewBmp); + } + } + + return bRet; +} + +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); +} + +/* 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 000000000..58a01f1fb --- /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"); + 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<AnimationBitmap>>& 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 000000000..5c14e1476 --- /dev/null +++ b/vcl/source/bitmap/bitmappaint.cxx @@ -0,0 +1,1146 @@ +/* -*- 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/bitmapaccess.hxx> +#include <vcl/alpha.hxx> + +#include <bitmapwriteaccess.hxx> +#include <salbmp.hxx> +#include <svdata.hxx> +#include <salinst.hxx> + +#include <algorithm> +#include <memory> + +bool Bitmap::Erase(const Color& rFillColor) +{ + if (IsEmpty()) + return true; + + BitmapScopedWriteAccess pWriteAcc(*this); + bool bRet = false; + + if (pWriteAcc) + { + const ScanlineFormat nFormat = pWriteAcc->GetScanlineFormat(); + sal_uInt8 cIndex = 0; + bool bFast = false; + + switch (nFormat) + { + case ScanlineFormat::N1BitMsbPal: + case ScanlineFormat::N1BitLsbPal: + { + cIndex = static_cast<sal_uInt8>(pWriteAcc->GetBestPaletteIndex(rFillColor)); + cIndex = (cIndex ? 255 : 0); + bFast = true; + } + break; + + case ScanlineFormat::N4BitMsnPal: + case ScanlineFormat::N4BitLsnPal: + { + cIndex = static_cast<sal_uInt8>(pWriteAcc->GetBestPaletteIndex(rFillColor)); + cIndex = cIndex | (cIndex << 4); + bFast = true; + } + break; + + case ScanlineFormat::N8BitPal: + { + cIndex = static_cast<sal_uInt8>(pWriteAcc->GetBestPaletteIndex(rFillColor)); + bFast = true; + } + break; + + case ScanlineFormat::N24BitTcBgr: + case ScanlineFormat::N24BitTcRgb: + { + if (rFillColor.GetRed() == rFillColor.GetGreen() + && rFillColor.GetRed() == rFillColor.GetBlue()) + { + cIndex = rFillColor.GetRed(); + bFast = true; + } + else + { + bFast = false; + } + } + break; + + default: + bFast = false; + break; + } + + if (bFast) + { + const sal_uLong nBufSize = pWriteAcc->GetScanlineSize() * pWriteAcc->Height(); + memset(pWriteAcc->GetBuffer(), cIndex, nBufSize); + } + else + { + const tools::Rectangle aRect(Point(), Size(pWriteAcc->Width(), pWriteAcc->Height())); + pWriteAcc->SetFillColor(rFillColor); + pWriteAcc->FillRect(aRect); + } + + bRet = true; + } + + return bRet; +} + +bool Bitmap::Invert() +{ + BitmapScopedWriteAccess pAcc(*this); + bool bRet = false; + + if (pAcc) + { + if (pAcc->HasPalette()) + { + BitmapPalette aBmpPal(pAcc->GetPalette()); + const sal_uInt16 nCount = aBmpPal.GetEntryCount(); + + for (sal_uInt16 i = 0; i < nCount; i++) + { + aBmpPal[i].Invert(); + } + + pAcc->SetPalette(aBmpPal); + } + else + { + const long nWidth = pAcc->Width(); + const long nHeight = pAcc->Height(); + + for (long nY = 0; nY < nHeight; nY++) + { + Scanline pScanline = pAcc->GetScanline(nY); + for (long nX = 0; nX < nWidth; nX++) + { + BitmapColor aBmpColor = pAcc->GetPixelFromData(pScanline, nX); + aBmpColor.Invert(); + pAcc->SetPixelOnData(pScanline, nX, aBmpColor); + } + } + } + + mxSalBmp->InvalidateChecksum(); + pAcc.reset(); + bRet = true; + } + + return bRet; +} + +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 long nWidth = pAcc->Width(); + const long nHeight = pAcc->Height(); + const long nWidth1 = nWidth - 1; + const long nWidth_2 = nWidth >> 1; + + for (long nY = 0; nY < nHeight; nY++) + { + Scanline pScanline = pAcc->GetScanline(nY); + for (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 long nScanSize = pAcc->GetScanlineSize(); + std::unique_ptr<sal_uInt8[]> pBuffer(new sal_uInt8[nScanSize]); + const long nHeight = pAcc->Height(); + const long nHeight1 = nHeight - 1; + const long nHeight_2 = nHeight >> 1; + + for (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 long nWidth = pAcc->Width(); + const long nWidth1 = nWidth - 1; + const long nHeight = pAcc->Height(); + long nHeight_2 = nHeight >> 1; + + for (long nY = 0, nOtherY = nHeight - 1; nY < nHeight_2; nY++, nOtherY--) + { + Scanline pScanline = pAcc->GetScanline(nY); + Scanline pScanlineOther = pAcc->GetScanline(nOtherY); + for (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 (long nX = 0, nOtherX = nWidth1, nWidth_2 = nWidth >> 1; 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(long nAngle10, const Color& rFillColor) +{ + bool bRet = false; + + nAngle10 %= 3600; + nAngle10 = (nAngle10 < 0) ? (3599L + nAngle10) : nAngle10; + + if (!nAngle10) + bRet = true; + else if (nAngle10 == 1800) + bRet = Mirror(BmpMirrorFlags::Horizontal | BmpMirrorFlags::Vertical); + else + { + ScopedReadAccess pReadAcc(*this); + Bitmap aRotatedBmp; + + if (pReadAcc) + { + const Size aSizePix(GetSizePixel()); + + if (nAngle10 == 900 || nAngle10 == 2700) + { + const Size aNewSizePix(aSizePix.Height(), aSizePix.Width()); + Bitmap aNewBmp(aNewSizePix, GetBitCount(), &pReadAcc->GetPalette()); + BitmapScopedWriteAccess pWriteAcc(aNewBmp); + + if (pWriteAcc) + { + const long nWidth = aSizePix.Width(); + const long nWidth1 = nWidth - 1; + const long nHeight = aSizePix.Height(); + const long nHeight1 = nHeight - 1; + const long nNewWidth = aNewSizePix.Width(); + const long nNewHeight = aNewSizePix.Height(); + + if (nAngle10 == 900) + { + for (long nY = 0, nOtherX = nWidth1; nY < nNewHeight; nY++, nOtherX--) + { + Scanline pScanline = pWriteAcc->GetScanline(nY); + for (long nX = 0, nOtherY = 0; nX < nNewWidth; nX++) + { + pWriteAcc->SetPixelOnData(pScanline, nX, + pReadAcc->GetPixel(nOtherY++, nOtherX)); + } + } + } + else if (nAngle10 == 2700) + { + for (long nY = 0, nOtherX = 0; nY < nNewHeight; nY++, nOtherX++) + { + Scanline pScanline = pWriteAcc->GetScanline(nY); + for (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, static_cast<sal_uInt16>(nAngle10)); + + tools::Rectangle aNewBound(aPoly.GetBoundRect()); + const Size aNewSizePix(aNewBound.GetSize()); + Bitmap aNewBmp(aNewSizePix, GetBitCount(), &pReadAcc->GetPalette()); + BitmapScopedWriteAccess pWriteAcc(aNewBmp); + + if (pWriteAcc) + { + const BitmapColor aFillColor(pWriteAcc->GetBestMatchingColor(rFillColor)); + const double fCosAngle = cos(nAngle10 * F_PI1800); + const double fSinAngle = sin(nAngle10 * F_PI1800); + const double fXMin = aNewBound.Left(); + const double fYMin = aNewBound.Top(); + const long nWidth = aSizePix.Width(); + const long nHeight = aSizePix.Height(); + const long nNewWidth = aNewSizePix.Width(); + const long nNewHeight = aNewSizePix.Height(); + long nX; + long nY; + long nRotX; + long nRotY; + std::unique_ptr<long[]> pCosX(new long[nNewWidth]); + std::unique_ptr<long[]> pSinX(new long[nNewWidth]); + std::unique_ptr<long[]> pCosY(new long[nNewHeight]); + std::unique_ptr<long[]> pSinY(new long[nNewHeight]); + + for (nX = 0; nX < nNewWidth; nX++) + { + const double fTmp = (fXMin + nX) * 64.; + + pCosX[nX] = FRound(fCosAngle * fTmp); + pSinX[nX] = FRound(fSinAngle * fTmp); + } + + for (nY = 0; nY < nNewHeight; nY++) + { + const double fTmp = (fYMin + nY) * 64.; + + pCosY[nY] = FRound(fCosAngle * fTmp); + pSinY[nY] = FRound(fSinAngle * fTmp); + } + + for (nY = 0; nY < nNewHeight; nY++) + { + long nSinY = pSinY[nY]; + long nCosY = pCosY[nY]; + Scanline pScanline = pWriteAcc->GetScanline(nY); + + for (nX = 0; nX < nNewWidth; nX++) + { + nRotX = (pCosX[nX] - nSinY) >> 6; + nRotY = (pSinX[nX] + 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; + if (bRet) + ReassignWithSize(aRotatedBmp); + } + + return bRet; +}; + +Bitmap Bitmap::CreateMask(const Color& rTransColor, sal_uInt8 nTol) const +{ + ScopedReadAccess pReadAcc(const_cast<Bitmap&>(*this)); + + if (!nTol && pReadAcc + && (pReadAcc->GetScanlineFormat() == ScanlineFormat::N1BitLsbPal + || 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; + } + + Bitmap aNewBmp(GetSizePixel(), 1); + BitmapScopedWriteAccess pWriteAcc(aNewBmp); + bool bRet = false; + + if (pWriteAcc && pReadAcc) + { + const long nWidth = pReadAcc->Width(); + const long nHeight = pReadAcc->Height(); + const BitmapColor aBlack(pWriteAcc->GetBestMatchingColor(COL_BLACK)); + const BitmapColor aWhite(pWriteAcc->GetBestMatchingColor(COL_WHITE)); + + if (!nTol) + { + const BitmapColor aTest(pReadAcc->GetBestMatchingColor(rTransColor)); + + if (pReadAcc->GetScanlineFormat() == ScanlineFormat::N4BitMsnPal + || pReadAcc->GetScanlineFormat() == ScanlineFormat::N4BitLsnPal) + { + // optimized for 4Bit-MSN/LSN source palette + const sal_uInt8 cTest = aTest.GetIndex(); + const long nShiftInit + = ((pReadAcc->GetScanlineFormat() == ScanlineFormat::N4BitMsnPal) ? 4 : 0); + + if (pWriteAcc->GetScanlineFormat() == ScanlineFormat::N1BitMsbPal + && aWhite.GetIndex() == 1) + { + // optimized for 1Bit-MSB destination palette + for (long nY = 0; nY < nHeight; ++nY) + { + Scanline pSrc = pReadAcc->GetScanline(nY); + Scanline pDst = pWriteAcc->GetScanline(nY); + for (long nX = 0, nShift = nShiftInit; nX < nWidth; nX++, nShift ^= 4) + { + if (cTest == ((pSrc[nX >> 1] >> nShift) & 0x0f)) + pDst[nX >> 3] |= 1 << (7 - (nX & 7)); + else + pDst[nX >> 3] &= ~(1 << (7 - (nX & 7))); + } + } + } + else + { + for (long nY = 0; nY < nHeight; ++nY) + { + Scanline pSrc = pReadAcc->GetScanline(nY); + Scanline pDst = pWriteAcc->GetScanline(nY); + for (long nX = 0, nShift = nShiftInit; nX < nWidth; nX++, nShift ^= 4) + { + if (cTest == ((pSrc[nX >> 1] >> nShift) & 0x0f)) + pWriteAcc->SetPixelOnData(pDst, nX, aWhite); + else + pWriteAcc->SetPixelOnData(pDst, nX, aBlack); + } + } + } + } + else if (pReadAcc->GetScanlineFormat() == ScanlineFormat::N8BitPal) + { + // optimized for 8Bit source palette + const sal_uInt8 cTest = aTest.GetIndex(); + + if (pWriteAcc->GetScanlineFormat() == ScanlineFormat::N1BitMsbPal + && aWhite.GetIndex() == 1) + { + // optimized for 1Bit-MSB destination palette + for (long nY = 0; nY < nHeight; ++nY) + { + Scanline pSrc = pReadAcc->GetScanline(nY); + Scanline pDst = pWriteAcc->GetScanline(nY); + for (long nX = 0; nX < nWidth; ++nX) + { + if (cTest == pSrc[nX]) + pDst[nX >> 3] |= 1 << (7 - (nX & 7)); + else + pDst[nX >> 3] &= ~(1 << (7 - (nX & 7))); + } + } + } + else + { + for (long nY = 0; nY < nHeight; ++nY) + { + Scanline pSrc = pReadAcc->GetScanline(nY); + Scanline pDst = pWriteAcc->GetScanline(nY); + for (long nX = 0; nX < nWidth; ++nX) + { + if (cTest == pSrc[nX]) + pWriteAcc->SetPixelOnData(pDst, nX, aWhite); + else + pWriteAcc->SetPixelOnData(pDst, nX, aBlack); + } + } + } + } + else if (pWriteAcc->GetScanlineFormat() == pReadAcc->GetScanlineFormat() + && aWhite.GetIndex() == 1 + && (pReadAcc->GetScanlineFormat() == ScanlineFormat::N1BitLsbPal + || pReadAcc->GetScanlineFormat() == ScanlineFormat::N1BitMsbPal)) + { + for (long nY = 0; nY < nHeight; ++nY) + { + Scanline pSrc = pReadAcc->GetScanline(nY); + Scanline pDst = pWriteAcc->GetScanline(nY); + assert(pWriteAcc->GetScanlineSize() == pReadAcc->GetScanlineSize()); + const long nScanlineSize = pWriteAcc->GetScanlineSize(); + for (long nX = 0; nX < nScanlineSize; ++nX) + pDst[nX] = ~pSrc[nX]; + } + } + else + { + // not optimized + for (long nY = 0; nY < nHeight; ++nY) + { + Scanline pScanline = pWriteAcc->GetScanline(nY); + Scanline pScanlineRead = pReadAcc->GetScanline(nY); + for (long nX = 0; nX < nWidth; ++nX) + { + if (aTest == pReadAcc->GetPixelFromData(pScanlineRead, nX)) + pWriteAcc->SetPixelOnData(pScanline, nX, aWhite); + else + pWriteAcc->SetPixelOnData(pScanline, nX, aBlack); + } + } + } + } + else + { + BitmapColor aCol; + long nR, nG, nB; + const long nMinR = MinMax<long>(rTransColor.GetRed() - nTol, 0, 255); + const long nMaxR = MinMax<long>(rTransColor.GetRed() + nTol, 0, 255); + const long nMinG = MinMax<long>(rTransColor.GetGreen() - nTol, 0, 255); + const long nMaxG = MinMax<long>(rTransColor.GetGreen() + nTol, 0, 255); + const long nMinB = MinMax<long>(rTransColor.GetBlue() - nTol, 0, 255); + const long nMaxB = MinMax<long>(rTransColor.GetBlue() + nTol, 0, 255); + + if (pReadAcc->HasPalette()) + { + for (long nY = 0; nY < nHeight; nY++) + { + Scanline pScanline = pWriteAcc->GetScanline(nY); + Scanline pScanlineRead = pReadAcc->GetScanline(nY); + for (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 (long nY = 0; nY < nHeight; nY++) + { + Scanline pScanline = pWriteAcc->GetScanline(nY); + Scanline pScanlineRead = pReadAcc->GetScanline(nY); + for (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); + } + } + } + } + } + + bRet = true; + } + + pWriteAcc.reset(); + pReadAcc.reset(); + + if (bRet) + { + aNewBmp.maPrefSize = maPrefSize; + aNewBmp.maPrefMapMode = maPrefMapMode; + } + else + aNewBmp = Bitmap(); + + return aNewBmp; +} + +vcl::Region Bitmap::CreateRegion(const Color& rColor, const tools::Rectangle& rRect) const +{ + vcl::Region aRegion; + tools::Rectangle aRect(rRect); + ScopedReadAccess pReadAcc(const_cast<Bitmap&>(*this)); + + aRect.Intersection(tools::Rectangle(Point(), GetSizePixel())); + aRect.Justify(); + + if (pReadAcc) + { + const long nLeft = aRect.Left(); + const long nTop = aRect.Top(); + const long nRight = aRect.Right(); + const long nBottom = aRect.Bottom(); + const BitmapColor aMatch(pReadAcc->GetBestMatchingColor(rColor)); + + std::vector<long> aLine; + long nYStart(nTop); + long nY(nTop); + + for (; nY <= nBottom; nY++) + { + std::vector<long> aNewLine; + 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(); + } + else + { + aRegion = aRect; + } + + return aRegion; +} + +bool Bitmap::Replace(const Bitmap& rMask, const Color& rReplaceColor) +{ + ScopedReadAccess pMaskAcc(const_cast<Bitmap&>(rMask)); + BitmapScopedWriteAccess pAcc(*this); + bool bRet = false; + + if (pMaskAcc && pAcc) + { + const long nWidth = std::min(pMaskAcc->Width(), pAcc->Width()); + const 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(); + + // default to the nearest color + aReplace = pAcc->GetBestMatchingColor(rReplaceColor); + + // for paletted images without a matching palette entry + // look for an unused palette entry (NOTE: expensive!) + if (pAcc->GetPaletteColor(aReplace.GetIndex()) != BitmapColor(rReplaceColor)) + { + // if the palette has empty entries use the last one + if (nActColors < nMaxColors) + { + pAcc->SetPaletteEntryCount(nActColors + 1); + pAcc->SetPaletteColor(nActColors, rReplaceColor); + aReplace = BitmapColor(static_cast<sal_uInt8>(nActColors)); + } + else + { + std::unique_ptr<bool[]> pFlags(new bool[nMaxColors]); + + // Set all entries to false + std::fill(pFlags.get(), pFlags.get() + nMaxColors, false); + + for (long nY = 0; nY < nHeight; nY++) + { + Scanline pScanline = pAcc->GetScanline(nY); + for (long nX = 0; nX < nWidth; nX++) + pFlags[pAcc->GetIndexFromData(pScanline, nX)] = true; + } + + for (sal_uInt16 i = 0; i < nMaxColors; i++) + { + // Hurray, we do have an unused entry + if (!pFlags[i]) + { + pAcc->SetPaletteColor(i, rReplaceColor); + aReplace = BitmapColor(static_cast<sal_uInt8>(i)); + } + } + } + } + } + else + aReplace = rReplaceColor; + + for (long nY = 0; nY < nHeight; nY++) + { + Scanline pScanline = pAcc->GetScanline(nY); + Scanline pScanlineMask = pMaskAcc->GetScanline(nY); + for (long nX = 0; nX < nWidth; nX++) + { + if (pMaskAcc->GetPixelFromData(pScanlineMask, nX) == aMaskWhite) + pAcc->SetPixelOnData(pScanline, nX, aReplace); + } + } + + bRet = true; + } + + return bRet; +} + +bool Bitmap::Replace(const AlphaMask& rAlpha, const Color& rMergeColor) +{ + Bitmap aNewBmp(GetSizePixel(), 24); + ScopedReadAccess pAcc(*this); + AlphaMask::ScopedReadAccess pAlphaAcc(const_cast<AlphaMask&>(rAlpha)); + BitmapScopedWriteAccess pNewAcc(aNewBmp); + bool bRet = false; + + if (pAcc && pAlphaAcc && pNewAcc) + { + BitmapColor aCol; + const long nWidth = std::min(pAlphaAcc->Width(), pAcc->Width()); + const long nHeight = std::min(pAlphaAcc->Height(), pAcc->Height()); + + for (long nY = 0; nY < nHeight; nY++) + { + Scanline pScanline = pNewAcc->GetScanline(nY); + Scanline pScanlineAlpha = pAlphaAcc->GetScanline(nY); + for (long nX = 0; nX < nWidth; nX++) + { + aCol = pAcc->GetColor(nY, nX); + aCol.Merge(rMergeColor, 255 - pAlphaAcc->GetIndexFromData(pScanlineAlpha, nX)); + pNewAcc->SetPixelOnData(pScanline, nX, aCol); + } + } + + bRet = true; + } + + pAcc.reset(); + pAlphaAcc.reset(); + pNewAcc.reset(); + + if (bRet) + { + const MapMode aMap(maPrefMapMode); + const Size aSize(maPrefSize); + + *this = aNewBmp; + + maPrefMapMode = aMap; + maPrefSize = aSize; + } + + return bRet; +} + +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; + } + } + + // Bitmaps with 1 bit color depth can cause problems if they have other entries than black/white + // in their palette + if (GetBitCount() == 1) + Convert(BmpConversion::N4BitColors); + + BitmapScopedWriteAccess pAcc(*this); + bool bRet = false; + + if (pAcc) + { + const long nMinR = MinMax<long>(rSearchColor.GetRed() - nTol, 0, 255); + const long nMaxR = MinMax<long>(rSearchColor.GetRed() + nTol, 0, 255); + const long nMinG = MinMax<long>(rSearchColor.GetGreen() - nTol, 0, 255); + const long nMaxG = MinMax<long>(rSearchColor.GetGreen() + nTol, 0, 255); + const long nMinB = MinMax<long>(rSearchColor.GetBlue() - nTol, 0, 255); + const long nMaxB = MinMax<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 (long nY = 0, nHeight = pAcc->Height(); nY < nHeight; nY++) + { + Scanline pScanline = pAcc->GetScanline(nY); + for (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(); + bRet = true; + } + + return bRet; +} + +bool Bitmap::Replace(const Color* pSearchColors, const Color* pReplaceColors, sal_uLong nColorCount, + sal_uInt8 const* pTols) +{ + // Bitmaps with 1 bit color depth can cause problems if they have other entries than black/white + // in their palette + if (GetBitCount() == 1) + Convert(BmpConversion::N4BitColors); + + BitmapScopedWriteAccess pAcc(*this); + bool bRet = false; + + if (pAcc) + { + std::unique_ptr<long[]> pMinR(new long[nColorCount]); + std::unique_ptr<long[]> pMaxR(new long[nColorCount]); + std::unique_ptr<long[]> pMinG(new long[nColorCount]); + std::unique_ptr<long[]> pMaxG(new long[nColorCount]); + std::unique_ptr<long[]> pMinB(new long[nColorCount]); + std::unique_ptr<long[]> pMaxB(new long[nColorCount]); + + if (pTols) + { + for (sal_uLong i = 0; i < nColorCount; i++) + { + const Color& rCol = pSearchColors[i]; + const sal_uInt8 nTol = pTols[i]; + + pMinR[i] = MinMax<long>(rCol.GetRed() - nTol, 0, 255); + pMaxR[i] = MinMax<long>(rCol.GetRed() + nTol, 0, 255); + pMinG[i] = MinMax<long>(rCol.GetGreen() - nTol, 0, 255); + pMaxG[i] = MinMax<long>(rCol.GetGreen() + nTol, 0, 255); + pMinB[i] = MinMax<long>(rCol.GetBlue() - nTol, 0, 255); + pMaxB[i] = MinMax<long>(rCol.GetBlue() + nTol, 0, 255); + } + } + else + { + for (sal_uLong i = 0; i < nColorCount; i++) + { + const Color& rCol = pSearchColors[i]; + + pMinR[i] = rCol.GetRed(); + pMaxR[i] = rCol.GetRed(); + pMinG[i] = rCol.GetGreen(); + pMaxG[i] = rCol.GetGreen(); + pMinB[i] = rCol.GetBlue(); + pMaxB[i] = rCol.GetBlue(); + } + } + + if (pAcc->HasPalette()) + { + for (sal_uInt16 nEntry = 0, nPalCount = pAcc->GetPaletteEntryCount(); + nEntry < nPalCount; nEntry++) + { + const BitmapColor& rCol = pAcc->GetPaletteColor(nEntry); + + for (sal_uLong i = 0; i < nColorCount; i++) + { + if (pMinR[i] <= rCol.GetRed() && pMaxR[i] >= rCol.GetRed() + && pMinG[i] <= rCol.GetGreen() && pMaxG[i] >= rCol.GetGreen() + && pMinB[i] <= rCol.GetBlue() && pMaxB[i] >= rCol.GetBlue()) + { + pAcc->SetPaletteColor(nEntry, pReplaceColors[i]); + break; + } + } + } + } + else + { + BitmapColor aCol; + std::unique_ptr<BitmapColor[]> pReplaces(new BitmapColor[nColorCount]); + + for (sal_uLong i = 0; i < nColorCount; i++) + pReplaces[i] = pAcc->GetBestMatchingColor(pReplaceColors[i]); + + for (long nY = 0, nHeight = pAcc->Height(); nY < nHeight; nY++) + { + Scanline pScanline = pAcc->GetScanline(nY); + for (long nX = 0, nWidth = pAcc->Width(); nX < nWidth; nX++) + { + aCol = pAcc->GetPixelFromData(pScanline, nX); + + for (sal_uLong i = 0; i < nColorCount; i++) + { + if (pMinR[i] <= aCol.GetRed() && pMaxR[i] >= aCol.GetRed() + && pMinG[i] <= aCol.GetGreen() && pMaxG[i] >= aCol.GetGreen() + && pMinB[i] <= aCol.GetBlue() && pMaxB[i] >= aCol.GetBlue()) + { + pAcc->SetPixelOnData(pScanline, nX, pReplaces[i]); + break; + } + } + } + } + } + + pAcc.reset(); + bRet = true; + } + + return bRet; +} + +bool Bitmap::CombineSimple(const Bitmap& rMask, BmpCombine eCombine) +{ + ScopedReadAccess pMaskAcc(const_cast<Bitmap&>(rMask)); + BitmapScopedWriteAccess pAcc(*this); + bool bRet = false; + + if (pMaskAcc && pAcc) + { + const long nWidth = std::min(pMaskAcc->Width(), pAcc->Width()); + const long nHeight = std::min(pMaskAcc->Height(), pAcc->Height()); + const Color aColBlack(COL_BLACK); + const BitmapColor aWhite(pAcc->GetBestMatchingColor(COL_WHITE)); + const BitmapColor aBlack(pAcc->GetBestMatchingColor(aColBlack)); + const BitmapColor aMaskBlack(pMaskAcc->GetBestMatchingColor(aColBlack)); + + switch (eCombine) + { + case BmpCombine::And: + { + for (long nY = 0; nY < nHeight; nY++) + { + Scanline pScanline = pAcc->GetScanline(nY); + Scanline pScanlineMask = pMaskAcc->GetScanline(nY); + for (long nX = 0; nX < nWidth; nX++) + { + if (pMaskAcc->GetPixelFromData(pScanlineMask, nX) != aMaskBlack + && pAcc->GetPixelFromData(pScanline, nX) != aBlack) + { + pAcc->SetPixelOnData(pScanline, nX, aWhite); + } + else + { + pAcc->SetPixelOnData(pScanline, nX, aBlack); + } + } + } + } + break; + + case BmpCombine::Or: + { + for (long nY = 0; nY < nHeight; nY++) + { + Scanline pScanline = pAcc->GetScanline(nY); + Scanline pScanlineMask = pMaskAcc->GetScanline(nY); + for (long nX = 0; nX < nWidth; nX++) + { + if (pMaskAcc->GetPixelFromData(pScanlineMask, nX) != aMaskBlack + || pAcc->GetPixelFromData(pScanline, nX) != aBlack) + { + pAcc->SetPixelOnData(pScanline, nX, aWhite); + } + else + { + pAcc->SetPixelOnData(pScanline, nX, aBlack); + } + } + } + } + break; + } + + bRet = true; + } + + return bRet; +} + +// 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 (GetBitCount() <= 8) + Convert(BmpConversion::N24Bit); + + AlphaMask::ScopedReadAccess pAlphaAcc(const_cast<AlphaMask&>(rAlpha)); + + BitmapScopedWriteAccess pAcc(*this); + bool bRet = false; + + if (pAlphaAcc && pAcc) + { + const long nWidth = std::min(pAlphaAcc->Width(), pAcc->Width()); + const long nHeight = std::min(pAlphaAcc->Height(), pAcc->Height()); + + for (long nY = 0; nY < nHeight; ++nY) + { + Scanline pScanline = pAcc->GetScanline(nY); + Scanline pScanlineAlpha = pAlphaAcc->GetScanline(nY); + for (long nX = 0; nX < nWidth; ++nX) + { + BitmapColor aBmpColor = pAcc->GetPixelFromData(pScanline, nX); + aBmpColor.Merge(rBackgroundColor, + 255 - pAlphaAcc->GetIndexFromData(pScanlineAlpha, nX)); + pAcc->SetPixelOnData(pScanline, nX, aBmpColor); + } + } + + bRet = true; + } + + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/bitmap/checksum.cxx b/vcl/source/bitmap/checksum.cxx new file mode 100644 index 000000000..d33d88403 --- /dev/null +++ b/vcl/source/bitmap/checksum.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/. + * + * 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/types.h> +#include <vcl/checksum.hxx> + + +/*======================================================================== + * + * vcl_crc64Table (CRC polynomial 0x95AC9329AC4BC9B5ULL). + * + *======================================================================*/ +static const sal_uInt64 vcl_crc64Table[256] = { + 0x0000000000000000ULL, 0x7ad870c830358979ULL, 0xf5b0e190606b12f2ULL, + 0x8f689158505e9b8bULL, 0xc038e5739841b68fULL, 0xbae095bba8743ff6ULL, + 0x358804e3f82aa47dULL, 0x4f50742bc81f2d04ULL, 0xab28ecb46814fe75ULL, + 0xd1f09c7c5821770cULL, 0x5e980d24087fec87ULL, 0x24407dec384a65feULL, + 0x6b1009c7f05548faULL, 0x11c8790fc060c183ULL, 0x9ea0e857903e5a08ULL, + 0xe478989fa00bd371ULL, 0x7d08ff3b88be6f81ULL, 0x07d08ff3b88be6f8ULL, + 0x88b81eabe8d57d73ULL, 0xf2606e63d8e0f40aULL, 0xbd301a4810ffd90eULL, + 0xc7e86a8020ca5077ULL, 0x4880fbd87094cbfcULL, 0x32588b1040a14285ULL, + 0xd620138fe0aa91f4ULL, 0xacf86347d09f188dULL, 0x2390f21f80c18306ULL, + 0x594882d7b0f40a7fULL, 0x1618f6fc78eb277bULL, 0x6cc0863448deae02ULL, + 0xe3a8176c18803589ULL, 0x997067a428b5bcf0ULL, 0xfa11fe77117cdf02ULL, + 0x80c98ebf2149567bULL, 0x0fa11fe77117cdf0ULL, 0x75796f2f41224489ULL, + 0x3a291b04893d698dULL, 0x40f16bccb908e0f4ULL, 0xcf99fa94e9567b7fULL, + 0xb5418a5cd963f206ULL, 0x513912c379682177ULL, 0x2be1620b495da80eULL, + 0xa489f35319033385ULL, 0xde51839b2936bafcULL, 0x9101f7b0e12997f8ULL, + 0xebd98778d11c1e81ULL, 0x64b116208142850aULL, 0x1e6966e8b1770c73ULL, + 0x8719014c99c2b083ULL, 0xfdc17184a9f739faULL, 0x72a9e0dcf9a9a271ULL, + 0x08719014c99c2b08ULL, 0x4721e43f0183060cULL, 0x3df994f731b68f75ULL, + 0xb29105af61e814feULL, 0xc849756751dd9d87ULL, 0x2c31edf8f1d64ef6ULL, + 0x56e99d30c1e3c78fULL, 0xd9810c6891bd5c04ULL, 0xa3597ca0a188d57dULL, + 0xec09088b6997f879ULL, 0x96d1784359a27100ULL, 0x19b9e91b09fcea8bULL, + 0x636199d339c963f2ULL, 0xdf7adabd7a6e2d6fULL, 0xa5a2aa754a5ba416ULL, + 0x2aca3b2d1a053f9dULL, 0x50124be52a30b6e4ULL, 0x1f423fcee22f9be0ULL, + 0x659a4f06d21a1299ULL, 0xeaf2de5e82448912ULL, 0x902aae96b271006bULL, + 0x74523609127ad31aULL, 0x0e8a46c1224f5a63ULL, 0x81e2d7997211c1e8ULL, + 0xfb3aa75142244891ULL, 0xb46ad37a8a3b6595ULL, 0xceb2a3b2ba0eececULL, + 0x41da32eaea507767ULL, 0x3b024222da65fe1eULL, 0xa2722586f2d042eeULL, + 0xd8aa554ec2e5cb97ULL, 0x57c2c41692bb501cULL, 0x2d1ab4dea28ed965ULL, + 0x624ac0f56a91f461ULL, 0x1892b03d5aa47d18ULL, 0x97fa21650afae693ULL, + 0xed2251ad3acf6feaULL, 0x095ac9329ac4bc9bULL, 0x7382b9faaaf135e2ULL, + 0xfcea28a2faafae69ULL, 0x8632586aca9a2710ULL, 0xc9622c4102850a14ULL, + 0xb3ba5c8932b0836dULL, 0x3cd2cdd162ee18e6ULL, 0x460abd1952db919fULL, + 0x256b24ca6b12f26dULL, 0x5fb354025b277b14ULL, 0xd0dbc55a0b79e09fULL, + 0xaa03b5923b4c69e6ULL, 0xe553c1b9f35344e2ULL, 0x9f8bb171c366cd9bULL, + 0x10e3202993385610ULL, 0x6a3b50e1a30ddf69ULL, 0x8e43c87e03060c18ULL, + 0xf49bb8b633338561ULL, 0x7bf329ee636d1eeaULL, 0x012b592653589793ULL, + 0x4e7b2d0d9b47ba97ULL, 0x34a35dc5ab7233eeULL, 0xbbcbcc9dfb2ca865ULL, + 0xc113bc55cb19211cULL, 0x5863dbf1e3ac9decULL, 0x22bbab39d3991495ULL, + 0xadd33a6183c78f1eULL, 0xd70b4aa9b3f20667ULL, 0x985b3e827bed2b63ULL, + 0xe2834e4a4bd8a21aULL, 0x6debdf121b863991ULL, 0x1733afda2bb3b0e8ULL, + 0xf34b37458bb86399ULL, 0x8993478dbb8deae0ULL, 0x06fbd6d5ebd3716bULL, + 0x7c23a61ddbe6f812ULL, 0x3373d23613f9d516ULL, 0x49aba2fe23cc5c6fULL, + 0xc6c333a67392c7e4ULL, 0xbc1b436e43a74e9dULL, 0x95ac9329ac4bc9b5ULL, + 0xef74e3e19c7e40ccULL, 0x601c72b9cc20db47ULL, 0x1ac40271fc15523eULL, + 0x5594765a340a7f3aULL, 0x2f4c0692043ff643ULL, 0xa02497ca54616dc8ULL, + 0xdafce7026454e4b1ULL, 0x3e847f9dc45f37c0ULL, 0x445c0f55f46abeb9ULL, + 0xcb349e0da4342532ULL, 0xb1eceec59401ac4bULL, 0xfebc9aee5c1e814fULL, + 0x8464ea266c2b0836ULL, 0x0b0c7b7e3c7593bdULL, 0x71d40bb60c401ac4ULL, + 0xe8a46c1224f5a634ULL, 0x927c1cda14c02f4dULL, 0x1d148d82449eb4c6ULL, + 0x67ccfd4a74ab3dbfULL, 0x289c8961bcb410bbULL, 0x5244f9a98c8199c2ULL, + 0xdd2c68f1dcdf0249ULL, 0xa7f41839ecea8b30ULL, 0x438c80a64ce15841ULL, + 0x3954f06e7cd4d138ULL, 0xb63c61362c8a4ab3ULL, 0xcce411fe1cbfc3caULL, + 0x83b465d5d4a0eeceULL, 0xf96c151de49567b7ULL, 0x76048445b4cbfc3cULL, + 0x0cdcf48d84fe7545ULL, 0x6fbd6d5ebd3716b7ULL, 0x15651d968d029fceULL, + 0x9a0d8ccedd5c0445ULL, 0xe0d5fc06ed698d3cULL, 0xaf85882d2576a038ULL, + 0xd55df8e515432941ULL, 0x5a3569bd451db2caULL, 0x20ed197575283bb3ULL, + 0xc49581ead523e8c2ULL, 0xbe4df122e51661bbULL, 0x3125607ab548fa30ULL, + 0x4bfd10b2857d7349ULL, 0x04ad64994d625e4dULL, 0x7e7514517d57d734ULL, + 0xf11d85092d094cbfULL, 0x8bc5f5c11d3cc5c6ULL, 0x12b5926535897936ULL, + 0x686de2ad05bcf04fULL, 0xe70573f555e26bc4ULL, 0x9ddd033d65d7e2bdULL, + 0xd28d7716adc8cfb9ULL, 0xa85507de9dfd46c0ULL, 0x273d9686cda3dd4bULL, + 0x5de5e64efd965432ULL, 0xb99d7ed15d9d8743ULL, 0xc3450e196da80e3aULL, + 0x4c2d9f413df695b1ULL, 0x36f5ef890dc31cc8ULL, 0x79a59ba2c5dc31ccULL, + 0x037deb6af5e9b8b5ULL, 0x8c157a32a5b7233eULL, 0xf6cd0afa9582aa47ULL, + 0x4ad64994d625e4daULL, 0x300e395ce6106da3ULL, 0xbf66a804b64ef628ULL, + 0xc5bed8cc867b7f51ULL, 0x8aeeace74e645255ULL, 0xf036dc2f7e51db2cULL, + 0x7f5e4d772e0f40a7ULL, 0x05863dbf1e3ac9deULL, 0xe1fea520be311aafULL, + 0x9b26d5e88e0493d6ULL, 0x144e44b0de5a085dULL, 0x6e963478ee6f8124ULL, + 0x21c640532670ac20ULL, 0x5b1e309b16452559ULL, 0xd476a1c3461bbed2ULL, + 0xaeaed10b762e37abULL, 0x37deb6af5e9b8b5bULL, 0x4d06c6676eae0222ULL, + 0xc26e573f3ef099a9ULL, 0xb8b627f70ec510d0ULL, 0xf7e653dcc6da3dd4ULL, + 0x8d3e2314f6efb4adULL, 0x0256b24ca6b12f26ULL, 0x788ec2849684a65fULL, + 0x9cf65a1b368f752eULL, 0xe62e2ad306bafc57ULL, 0x6946bb8b56e467dcULL, + 0x139ecb4366d1eea5ULL, 0x5ccebf68aecec3a1ULL, 0x2616cfa09efb4ad8ULL, + 0xa97e5ef8cea5d153ULL, 0xd3a62e30fe90582aULL, 0xb0c7b7e3c7593bd8ULL, + 0xca1fc72bf76cb2a1ULL, 0x45775673a732292aULL, 0x3faf26bb9707a053ULL, + 0x70ff52905f188d57ULL, 0x0a2722586f2d042eULL, 0x854fb3003f739fa5ULL, + 0xff97c3c80f4616dcULL, 0x1bef5b57af4dc5adULL, 0x61372b9f9f784cd4ULL, + 0xee5fbac7cf26d75fULL, 0x9487ca0fff135e26ULL, 0xdbd7be24370c7322ULL, + 0xa10fceec0739fa5bULL, 0x2e675fb4576761d0ULL, 0x54bf2f7c6752e8a9ULL, + 0xcdcf48d84fe75459ULL, 0xb71738107fd2dd20ULL, 0x387fa9482f8c46abULL, + 0x42a7d9801fb9cfd2ULL, 0x0df7adabd7a6e2d6ULL, 0x772fdd63e7936bafULL, + 0xf8474c3bb7cdf024ULL, 0x829f3cf387f8795dULL, 0x66e7a46c27f3aa2cULL, + 0x1c3fd4a417c62355ULL, 0x935745fc4798b8deULL, 0xe98f353477ad31a7ULL, + 0xa6df411fbfb21ca3ULL, 0xdc0731d78f8795daULL, 0x536fa08fdfd90e51ULL, + 0x29b7d047efec8728ULL +}; + +/*======================================================================== + * + * vcl_crc64 implementation. + * + *======================================================================*/ +#define UPDCRC64(crc, octet) \ + (vcl_crc64Table[((crc) ^ (octet)) & 0xff] ^ ((crc) >> 8)) + +/* + * vcl_crc64. + */ +sal_uInt64 vcl_crc64 ( + sal_uInt64 Crc, + const void *Data, sal_uInt32 DatLen) SAL_THROW_EXTERN_C() +{ + if (Data) + { + const sal_uInt8 *p = static_cast<const sal_uInt8 *>(Data); + const sal_uInt8 *q = p + DatLen; + + Crc = ~Crc; + while (p < q) + Crc = UPDCRC64(Crc, *(p++)); + Crc = ~Crc; + } + return Crc; +} + +/* + * vcl_get_crc64_table. + */ +const sal_uInt64* vcl_get_crc64_table() +{ + return vcl_crc64Table; +} + +/* 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 000000000..f731a5690 --- /dev/null +++ b/vcl/source/bitmap/salbmp.cxx @@ -0,0 +1,225 @@ +/* -*- 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> + +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(); + nCrc = vcl_get_checksum(nCrc, pBuf->mpBits, pBuf->mnScanlineSize * pBuf->mnHeight); + 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; +} + +} // 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 int bpp[] = { 1, 3, 3, 4, 4 }; + std::unique_ptr< sal_uInt8[] > data( new sal_uInt8[width * height * bpp[ static_cast<int>(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; + } + + 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::BGR : + while( nX-- ) + { + const BitmapColor& c = pSrcFormat->ReadPixel(); + *pDstData++ = c.GetBlue(); + *pDstData++ = c.GetGreen(); + *pDstData++ = c.GetRed(); + } + break; + case BitConvert::RGB : + while( nX-- ) + { + const BitmapColor& c = pSrcFormat->ReadPixel(); + *pDstData++ = c.GetRed(); + *pDstData++ = c.GetGreen(); + *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; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |