diff options
Diffstat (limited to 'vcl/source/bitmap/bitmappaint.cxx')
-rw-r--r-- | vcl/source/bitmap/bitmappaint.cxx | 1226 |
1 files changed, 1226 insertions, 0 deletions
diff --git a/vcl/source/bitmap/bitmappaint.cxx b/vcl/source/bitmap/bitmappaint.cxx new file mode 100644 index 0000000000..cc3674ad31 --- /dev/null +++ b/vcl/source/bitmap/bitmappaint.cxx @@ -0,0 +1,1226 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <tools/poly.hxx> +#include <tools/helpers.hxx> + +#include <vcl/bitmap.hxx> +#include <vcl/alpha.hxx> + +#include <vcl/BitmapWriteAccess.hxx> +#include <salbmp.hxx> +#include <svdata.hxx> +#include <salinst.hxx> + +#include <algorithm> +#include <memory> + +static BitmapColor UpdatePaletteForNewColor(BitmapScopedWriteAccess& pAcc, + const sal_uInt16 nActColors, + const sal_uInt16 nMaxColors, const tools::Long nHeight, + const tools::Long nWidth, + const BitmapColor& rWantedColor); + +bool Bitmap::Erase(const Color& rFillColor) +{ + if (IsEmpty()) + return true; + + // implementation specific replace + std::shared_ptr<SalBitmap> xImpBmp(ImplGetSVData()->mpDefInst->CreateSalBitmap()); + if (xImpBmp->Create(*mxSalBmp) && xImpBmp->Erase(rFillColor)) + { + ImplSetSalBitmap(xImpBmp); + maPrefMapMode = MapMode(MapUnit::MapPixel); + maPrefSize = xImpBmp->GetSize(); + return true; + } + + BitmapScopedWriteAccess pWriteAcc(*this); + bool bRet = false; + + if (pWriteAcc) + { + pWriteAcc->Erase(rFillColor); + bRet = true; + } + + return bRet; +} + +bool Bitmap::Invert() +{ + if (!mxSalBmp) + return false; + + // try optimised call, much faster on Skia + if (mxSalBmp->Invert()) + { + mxSalBmp->InvalidateChecksum(); + return true; + } + + BitmapScopedWriteAccess pWriteAcc(*this); + const tools::Long nWidth = pWriteAcc->Width(); + const tools::Long nHeight = pWriteAcc->Height(); + + if (pWriteAcc->HasPalette()) + { + const sal_uInt16 nActColors = pWriteAcc->GetPaletteEntryCount(); + const sal_uInt16 nMaxColors = 1 << pWriteAcc->GetBitCount(); + + if (pWriteAcc->GetPalette().IsGreyPalette8Bit()) + { + for (tools::Long nY = 0; nY < nHeight; nY++) + { + Scanline pScanline = pWriteAcc->GetScanline(nY); + for (tools::Long nX = 0; nX < nWidth; nX++) + { + BitmapColor aBmpColor = pWriteAcc->GetPixelFromData(pScanline, nX); + aBmpColor.SetIndex(0xff - aBmpColor.GetIndex()); + pWriteAcc->SetPixelOnData(pScanline, nX, aBmpColor); + } + } + } + else + { + for (tools::Long nY = 0; nY < nHeight; nY++) + { + Scanline pScanline = pWriteAcc->GetScanline(nY); + for (tools::Long nX = 0; nX < nWidth; nX++) + { + BitmapColor aBmpColor = pWriteAcc->GetPixelFromData(pScanline, nX); + aBmpColor = pWriteAcc->GetPaletteColor(aBmpColor.GetIndex()); + aBmpColor.Invert(); + BitmapColor aReplace = UpdatePaletteForNewColor( + pWriteAcc, nActColors, nMaxColors, nHeight, nWidth, aBmpColor); + pWriteAcc->SetPixelOnData(pScanline, nX, aReplace); + } + } + } + } + else + { + for (tools::Long nY = 0; nY < nHeight; nY++) + { + Scanline pScanline = pWriteAcc->GetScanline(nY); + for (tools::Long nX = 0; nX < nWidth; nX++) + { + BitmapColor aBmpColor = pWriteAcc->GetPixelFromData(pScanline, nX); + aBmpColor.Invert(); + pWriteAcc->SetPixelOnData(pScanline, nX, aBmpColor); + } + } + } + mxSalBmp->InvalidateChecksum(); + + return true; +} + +namespace +{ +// Put each scanline's content horizontally mirrored into the other one. +// (optimized version accessing pixel values directly). +template <int bitCount> +void mirrorScanlines(Scanline scanline1, Scanline scanline2, tools::Long nWidth) +{ + constexpr int byteCount = bitCount / 8; + Scanline pos1 = scanline1; + Scanline pos2 = scanline2 + (nWidth - 1) * byteCount; // last in second scanline + sal_uInt8 tmp[byteCount]; + for (tools::Long i = 0; i < nWidth; ++i) + { + memcpy(tmp, pos1, byteCount); + memcpy(pos1, pos2, byteCount); + memcpy(pos2, tmp, byteCount); + pos1 += byteCount; + pos2 -= byteCount; + } +} +} + +bool Bitmap::Mirror(BmpMirrorFlags nMirrorFlags) +{ + bool bHorz(nMirrorFlags & BmpMirrorFlags::Horizontal); + bool bVert(nMirrorFlags & BmpMirrorFlags::Vertical); + bool bRet = false; + + if (bHorz && !bVert) + { + BitmapScopedWriteAccess pAcc(*this); + + if (pAcc) + { + const tools::Long nWidth = pAcc->Width(); + const tools::Long nHeight = pAcc->Height(); + const tools::Long nWidth1 = nWidth - 1; + const tools::Long nWidth_2 = nWidth / 2; + const tools::Long nSecondHalf = nWidth - nWidth_2; + + switch (pAcc->GetBitCount()) + { + // Special-case these, swap the halves of scanlines while mirroring them. + case 32: + for (tools::Long nY = 0; nY < nHeight; nY++) + mirrorScanlines<32>(pAcc->GetScanline(nY), + pAcc->GetScanline(nY) + 4 * nSecondHalf, nWidth_2); + break; + case 24: + for (tools::Long nY = 0; nY < nHeight; nY++) + mirrorScanlines<24>(pAcc->GetScanline(nY), + pAcc->GetScanline(nY) + 3 * nSecondHalf, nWidth_2); + break; + case 8: + for (tools::Long nY = 0; nY < nHeight; nY++) + mirrorScanlines<8>(pAcc->GetScanline(nY), + pAcc->GetScanline(nY) + nSecondHalf, nWidth_2); + break; + default: + for (tools::Long nY = 0; nY < nHeight; nY++) + { + Scanline pScanline = pAcc->GetScanline(nY); + for (tools::Long nX = 0, nOther = nWidth1; nX < nWidth_2; nX++, nOther--) + { + const BitmapColor aTemp(pAcc->GetPixelFromData(pScanline, nX)); + + pAcc->SetPixelOnData(pScanline, nX, + pAcc->GetPixelFromData(pScanline, nOther)); + pAcc->SetPixelOnData(pScanline, nOther, aTemp); + } + } + } + + pAcc.reset(); + bRet = true; + } + } + else if (bVert && !bHorz) + { + BitmapScopedWriteAccess pAcc(*this); + + if (pAcc) + { + const tools::Long nScanSize = pAcc->GetScanlineSize(); + std::unique_ptr<sal_uInt8[]> pBuffer(new sal_uInt8[nScanSize]); + const tools::Long nHeight = pAcc->Height(); + const tools::Long nHeight1 = nHeight - 1; + const tools::Long nHeight_2 = nHeight >> 1; + + for (tools::Long nY = 0, nOther = nHeight1; nY < nHeight_2; nY++, nOther--) + { + memcpy(pBuffer.get(), pAcc->GetScanline(nY), nScanSize); + memcpy(pAcc->GetScanline(nY), pAcc->GetScanline(nOther), nScanSize); + memcpy(pAcc->GetScanline(nOther), pBuffer.get(), nScanSize); + } + + pAcc.reset(); + bRet = true; + } + } + else if (bHorz && bVert) + { + BitmapScopedWriteAccess pAcc(*this); + + if (pAcc) + { + const tools::Long nWidth = pAcc->Width(); + const tools::Long nWidth1 = nWidth - 1; + const tools::Long nHeight = pAcc->Height(); + tools::Long nHeight_2 = nHeight / 2; + const tools::Long nWidth_2 = nWidth / 2; + const tools::Long nSecondHalf = nWidth - nWidth_2; + + switch (pAcc->GetBitCount()) + { + case 32: + for (tools::Long nY = 0, nOtherY = nHeight - 1; nY < nHeight_2; nY++, nOtherY--) + mirrorScanlines<32>(pAcc->GetScanline(nY), pAcc->GetScanline(nOtherY), + nWidth); + if (nHeight & 1) + mirrorScanlines<32>(pAcc->GetScanline(nHeight_2), + pAcc->GetScanline(nHeight_2) + 4 * nSecondHalf, + nWidth_2); + break; + case 24: + for (tools::Long nY = 0, nOtherY = nHeight - 1; nY < nHeight_2; nY++, nOtherY--) + mirrorScanlines<24>(pAcc->GetScanline(nY), pAcc->GetScanline(nOtherY), + nWidth); + if (nHeight & 1) + mirrorScanlines<24>(pAcc->GetScanline(nHeight_2), + pAcc->GetScanline(nHeight_2) + 3 * nSecondHalf, + nWidth_2); + break; + case 8: + for (tools::Long nY = 0, nOtherY = nHeight - 1; nY < nHeight_2; nY++, nOtherY--) + mirrorScanlines<8>(pAcc->GetScanline(nY), pAcc->GetScanline(nOtherY), + nWidth); + if (nHeight & 1) + mirrorScanlines<8>(pAcc->GetScanline(nHeight_2), + pAcc->GetScanline(nHeight_2) + nSecondHalf, nWidth_2); + break; + default: + for (tools::Long nY = 0, nOtherY = nHeight - 1; nY < nHeight_2; nY++, nOtherY--) + { + Scanline pScanline = pAcc->GetScanline(nY); + Scanline pScanlineOther = pAcc->GetScanline(nOtherY); + for (tools::Long nX = 0, nOtherX = nWidth1; nX < nWidth; nX++, nOtherX--) + { + const BitmapColor aTemp(pAcc->GetPixelFromData(pScanline, nX)); + + pAcc->SetPixelOnData(pScanline, nX, + pAcc->GetPixelFromData(pScanlineOther, nOtherX)); + pAcc->SetPixelOnData(pScanlineOther, nOtherX, aTemp); + } + } + + // if necessary, also mirror the middle line horizontally + if (nHeight & 1) + { + Scanline pScanline = pAcc->GetScanline(nHeight_2); + for (tools::Long nX = 0, nOtherX = nWidth1; nX < nWidth_2; nX++, nOtherX--) + { + const BitmapColor aTemp(pAcc->GetPixelFromData(pScanline, nX)); + pAcc->SetPixelOnData(pScanline, nX, + pAcc->GetPixelFromData(pScanline, nOtherX)); + pAcc->SetPixelOnData(pScanline, nOtherX, aTemp); + } + } + } + + pAcc.reset(); + bRet = true; + } + } + else + bRet = true; + + return bRet; +} + +bool Bitmap::Rotate(Degree10 nAngle10, const Color& rFillColor) +{ + nAngle10 %= 3600_deg10; + nAngle10 = (nAngle10 < 0_deg10) ? (Degree10(3599) + nAngle10) : nAngle10; + + if (!nAngle10) + return true; + if (nAngle10 == 1800_deg10) + return Mirror(BmpMirrorFlags::Horizontal | BmpMirrorFlags::Vertical); + + BitmapScopedReadAccess pReadAcc(*this); + if (!pReadAcc) + return false; + + Bitmap aRotatedBmp; + bool bRet = false; + const Size aSizePix(GetSizePixel()); + + if (nAngle10 == 900_deg10 || nAngle10 == 2700_deg10) + { + const Size aNewSizePix(aSizePix.Height(), aSizePix.Width()); + Bitmap aNewBmp(aNewSizePix, getPixelFormat(), &pReadAcc->GetPalette()); + BitmapScopedWriteAccess pWriteAcc(aNewBmp); + + if (pWriteAcc) + { + const tools::Long nWidth = aSizePix.Width(); + const tools::Long nWidth1 = nWidth - 1; + const tools::Long nHeight = aSizePix.Height(); + const tools::Long nHeight1 = nHeight - 1; + const tools::Long nNewWidth = aNewSizePix.Width(); + const tools::Long nNewHeight = aNewSizePix.Height(); + + if (nAngle10 == 900_deg10) + { + for (tools::Long nY = 0, nOtherX = nWidth1; nY < nNewHeight; nY++, nOtherX--) + { + Scanline pScanline = pWriteAcc->GetScanline(nY); + for (tools::Long nX = 0, nOtherY = 0; nX < nNewWidth; nX++) + { + pWriteAcc->SetPixelOnData(pScanline, nX, + pReadAcc->GetPixel(nOtherY++, nOtherX)); + } + } + } + else if (nAngle10 == 2700_deg10) + { + for (tools::Long nY = 0, nOtherX = 0; nY < nNewHeight; nY++, nOtherX++) + { + Scanline pScanline = pWriteAcc->GetScanline(nY); + for (tools::Long nX = 0, nOtherY = nHeight1; nX < nNewWidth; nX++) + { + pWriteAcc->SetPixelOnData(pScanline, nX, + pReadAcc->GetPixel(nOtherY--, nOtherX)); + } + } + } + + pWriteAcc.reset(); + } + + aRotatedBmp = aNewBmp; + } + else + { + Point aTmpPoint; + tools::Rectangle aTmpRectangle(aTmpPoint, aSizePix); + tools::Polygon aPoly(aTmpRectangle); + aPoly.Rotate(aTmpPoint, nAngle10); + + tools::Rectangle aNewBound(aPoly.GetBoundRect()); + const Size aNewSizePix(aNewBound.GetSize()); + Bitmap aNewBmp(aNewSizePix, getPixelFormat(), &pReadAcc->GetPalette()); + BitmapScopedWriteAccess pWriteAcc(aNewBmp); + + if (pWriteAcc) + { + const BitmapColor aFillColor(pWriteAcc->GetBestMatchingColor(rFillColor)); + const double fCosAngle = cos(toRadians(nAngle10)); + const double fSinAngle = sin(toRadians(nAngle10)); + const double fXMin = aNewBound.Left(); + const double fYMin = aNewBound.Top(); + const sal_Int32 nWidth = aSizePix.Width(); + const sal_Int32 nHeight = aSizePix.Height(); + const sal_Int32 nNewWidth = aNewSizePix.Width(); + const sal_Int32 nNewHeight = aNewSizePix.Height(); + // we store alternating values of cos/sin. We do this instead of + // separate arrays to improve cache hit. + std::unique_ptr<sal_Int32[]> pCosSinX(new sal_Int32[nNewWidth * 2]); + std::unique_ptr<sal_Int32[]> pCosSinY(new sal_Int32[nNewHeight * 2]); + + for (sal_Int32 nIdx = 0, nX = 0; nX < nNewWidth; nX++) + { + const double fTmp = (fXMin + nX) * 64; + + pCosSinX[nIdx++] = std::round(fCosAngle * fTmp); + pCosSinX[nIdx++] = std::round(fSinAngle * fTmp); + } + + for (sal_Int32 nIdx = 0, nY = 0; nY < nNewHeight; nY++) + { + const double fTmp = (fYMin + nY) * 64; + + pCosSinY[nIdx++] = std::round(fCosAngle * fTmp); + pCosSinY[nIdx++] = std::round(fSinAngle * fTmp); + } + + for (sal_Int32 nCosSinYIdx = 0, nY = 0; nY < nNewHeight; nY++) + { + sal_Int32 nCosY = pCosSinY[nCosSinYIdx++]; + sal_Int32 nSinY = pCosSinY[nCosSinYIdx++]; + Scanline pScanline = pWriteAcc->GetScanline(nY); + + for (sal_Int32 nCosSinXIdx = 0, nX = 0; nX < nNewWidth; nX++) + { + sal_Int32 nRotX = (pCosSinX[nCosSinXIdx++] - nSinY) >> 6; + sal_Int32 nRotY = (pCosSinX[nCosSinXIdx++] + nCosY) >> 6; + + if ((nRotX > -1) && (nRotX < nWidth) && (nRotY > -1) && (nRotY < nHeight)) + { + pWriteAcc->SetPixelOnData(pScanline, nX, pReadAcc->GetPixel(nRotY, nRotX)); + } + else + { + pWriteAcc->SetPixelOnData(pScanline, nX, aFillColor); + } + } + } + + pWriteAcc.reset(); + } + + aRotatedBmp = aNewBmp; + } + + pReadAcc.reset(); + + bRet = !aRotatedBmp.IsEmpty(); + if (bRet) + ReassignWithSize(aRotatedBmp); + + return bRet; +}; + +Bitmap Bitmap::CreateMask(const Color& rTransColor) const +{ + BitmapScopedReadAccess pReadAcc(*this); + if (!pReadAcc) + return Bitmap(); + + // Historically LO used 1bpp masks, but 8bpp masks are much faster, + // better supported by hardware, and the memory savings are not worth + // it anymore. + // TODO: Possibly remove the 1bpp code later. + + if ((pReadAcc->GetScanlineFormat() == ScanlineFormat::N1BitMsbPal) + && pReadAcc->GetBestMatchingColor(COL_WHITE) == pReadAcc->GetBestMatchingColor(rTransColor)) + { + // if we're a 1 bit pixel already, and the transcolor matches the color that would replace it + // already, then just return a copy + return *this; + } + + auto ePixelFormat = vcl::PixelFormat::N8_BPP; + Bitmap aNewBmp(GetSizePixel(), ePixelFormat, &Bitmap::GetGreyPalette(256)); + BitmapScopedWriteAccess pWriteAcc(aNewBmp); + if (!pWriteAcc) + return Bitmap(); + + const tools::Long nWidth = pReadAcc->Width(); + const tools::Long nHeight = pReadAcc->Height(); + const BitmapColor aBlack(pWriteAcc->GetBestMatchingColor(COL_BLACK)); + const BitmapColor aWhite(pWriteAcc->GetBestMatchingColor(COL_WHITE)); + + const BitmapColor aTest(pReadAcc->GetBestMatchingColor(rTransColor)); + + if (pWriteAcc->GetScanlineFormat() == pReadAcc->GetScanlineFormat() && aWhite.GetIndex() == 1 + && (pReadAcc->GetScanlineFormat() == ScanlineFormat::N1BitMsbPal)) + { + for (tools::Long nY = 0; nY < nHeight; ++nY) + { + Scanline pSrc = pReadAcc->GetScanline(nY); + Scanline pDst = pWriteAcc->GetScanline(nY); + assert(pWriteAcc->GetScanlineSize() == pReadAcc->GetScanlineSize()); + const tools::Long nScanlineSize = pWriteAcc->GetScanlineSize(); + for (tools::Long nX = 0; nX < nScanlineSize; ++nX) + pDst[nX] = ~pSrc[nX]; + } + } + else if (pReadAcc->GetScanlineFormat() == ScanlineFormat::N8BitPal) + { + // optimized for 8Bit source palette + const sal_uInt8 cTest = aTest.GetIndex(); + + for (tools::Long nY = 0; nY < nHeight; ++nY) + { + Scanline pSrc = pReadAcc->GetScanline(nY); + Scanline pDst = pWriteAcc->GetScanline(nY); + for (tools::Long nX = 0; nX < nWidth; ++nX) + { + if (cTest == pSrc[nX]) + pDst[nX] = aWhite.GetIndex(); + else + pDst[nX] = aBlack.GetIndex(); + } + } + } + else + { + // not optimized + for (tools::Long nY = 0; nY < nHeight; ++nY) + { + Scanline pScanline = pWriteAcc->GetScanline(nY); + Scanline pScanlineRead = pReadAcc->GetScanline(nY); + for (tools::Long nX = 0; nX < nWidth; ++nX) + { + if (aTest == pReadAcc->GetPixelFromData(pScanlineRead, nX)) + pWriteAcc->SetPixelOnData(pScanline, nX, aWhite); + else + pWriteAcc->SetPixelOnData(pScanline, nX, aBlack); + } + } + } + + pWriteAcc.reset(); + pReadAcc.reset(); + + aNewBmp.maPrefSize = maPrefSize; + aNewBmp.maPrefMapMode = maPrefMapMode; + + return aNewBmp; +} + +Bitmap Bitmap::CreateMask(const Color& rTransColor, sal_uInt8 nTol) const +{ + if (nTol == 0) + return CreateMask(rTransColor); + + BitmapScopedReadAccess pReadAcc(*this); + if (!pReadAcc) + return Bitmap(); + + // Historically LO used 1bpp masks, but 8bpp masks are much faster, + // better supported by hardware, and the memory savings are not worth + // it anymore. + // TODO: Possibly remove the 1bpp code later. + + auto ePixelFormat = vcl::PixelFormat::N8_BPP; + Bitmap aNewBmp(GetSizePixel(), ePixelFormat, &Bitmap::GetGreyPalette(256)); + BitmapScopedWriteAccess pWriteAcc(aNewBmp); + if (!pWriteAcc) + return Bitmap(); + + const tools::Long nWidth = pReadAcc->Width(); + const tools::Long nHeight = pReadAcc->Height(); + const BitmapColor aBlack(pWriteAcc->GetBestMatchingColor(COL_BLACK)); + const BitmapColor aWhite(pWriteAcc->GetBestMatchingColor(COL_WHITE)); + + BitmapColor aCol; + tools::Long nR, nG, nB; + const tools::Long nMinR = std::clamp<tools::Long>(rTransColor.GetRed() - nTol, 0, 255); + const tools::Long nMaxR = std::clamp<tools::Long>(rTransColor.GetRed() + nTol, 0, 255); + const tools::Long nMinG = std::clamp<tools::Long>(rTransColor.GetGreen() - nTol, 0, 255); + const tools::Long nMaxG = std::clamp<tools::Long>(rTransColor.GetGreen() + nTol, 0, 255); + const tools::Long nMinB = std::clamp<tools::Long>(rTransColor.GetBlue() - nTol, 0, 255); + const tools::Long nMaxB = std::clamp<tools::Long>(rTransColor.GetBlue() + nTol, 0, 255); + + if (pReadAcc->HasPalette()) + { + for (tools::Long nY = 0; nY < nHeight; nY++) + { + Scanline pScanline = pWriteAcc->GetScanline(nY); + Scanline pScanlineRead = pReadAcc->GetScanline(nY); + for (tools::Long nX = 0; nX < nWidth; nX++) + { + aCol = pReadAcc->GetPaletteColor(pReadAcc->GetIndexFromData(pScanlineRead, nX)); + nR = aCol.GetRed(); + nG = aCol.GetGreen(); + nB = aCol.GetBlue(); + + if (nMinR <= nR && nMaxR >= nR && nMinG <= nG && nMaxG >= nG && nMinB <= nB + && nMaxB >= nB) + { + pWriteAcc->SetPixelOnData(pScanline, nX, aWhite); + } + else + { + pWriteAcc->SetPixelOnData(pScanline, nX, aBlack); + } + } + } + } + else + { + for (tools::Long nY = 0; nY < nHeight; nY++) + { + Scanline pScanline = pWriteAcc->GetScanline(nY); + Scanline pScanlineRead = pReadAcc->GetScanline(nY); + for (tools::Long nX = 0; nX < nWidth; nX++) + { + aCol = pReadAcc->GetPixelFromData(pScanlineRead, nX); + nR = aCol.GetRed(); + nG = aCol.GetGreen(); + nB = aCol.GetBlue(); + + if (nMinR <= nR && nMaxR >= nR && nMinG <= nG && nMaxG >= nG && nMinB <= nB + && nMaxB >= nB) + { + pWriteAcc->SetPixelOnData(pScanline, nX, aWhite); + } + else + { + pWriteAcc->SetPixelOnData(pScanline, nX, aBlack); + } + } + } + } + + pWriteAcc.reset(); + pReadAcc.reset(); + + aNewBmp.maPrefSize = maPrefSize; + aNewBmp.maPrefMapMode = maPrefMapMode; + + return aNewBmp; +} + +AlphaMask Bitmap::CreateAlphaMask(const Color& rTransColor) const +{ + BitmapScopedReadAccess pReadAcc(*this); + if (!pReadAcc) + return AlphaMask(); + + // Historically LO used 1bpp masks, but 8bpp masks are much faster, + // better supported by hardware, and the memory savings are not worth + // it anymore. + + if ((pReadAcc->GetScanlineFormat() == ScanlineFormat::N1BitMsbPal) + && pReadAcc->GetBestMatchingColor(COL_ALPHA_TRANSPARENT) + == pReadAcc->GetBestMatchingColor(rTransColor)) + { + // if we're a 1 bit pixel already, and the transcolor matches the color that would replace it + // already, then just return a copy + return AlphaMask(*this); + } + + AlphaMask aNewBmp(GetSizePixel()); + BitmapScopedWriteAccess pWriteAcc(aNewBmp); + if (!pWriteAcc) + return AlphaMask(); + + const tools::Long nWidth = pReadAcc->Width(); + const tools::Long nHeight = pReadAcc->Height(); + const BitmapColor aOpaqueColor(pWriteAcc->GetBestMatchingColor(COL_ALPHA_OPAQUE)); + const BitmapColor aTransparentColor(pWriteAcc->GetBestMatchingColor(COL_ALPHA_TRANSPARENT)); + + const BitmapColor aTest(pReadAcc->GetBestMatchingColor(rTransColor)); + + if (pReadAcc->GetScanlineFormat() == ScanlineFormat::N8BitPal) + { + // optimized for 8Bit source palette + const sal_uInt8 cTest = aTest.GetIndex(); + + for (tools::Long nY = 0; nY < nHeight; ++nY) + { + Scanline pSrc = pReadAcc->GetScanline(nY); + Scanline pDst = pWriteAcc->GetScanline(nY); + for (tools::Long nX = 0; nX < nWidth; ++nX) + { + if (cTest == pSrc[nX]) + pDst[nX] = aTransparentColor.GetIndex(); + else + pDst[nX] = aOpaqueColor.GetIndex(); + } + } + } + else + { + // not optimized + for (tools::Long nY = 0; nY < nHeight; ++nY) + { + Scanline pScanline = pWriteAcc->GetScanline(nY); + Scanline pScanlineRead = pReadAcc->GetScanline(nY); + for (tools::Long nX = 0; nX < nWidth; ++nX) + { + if (aTest == pReadAcc->GetPixelFromData(pScanlineRead, nX)) + pWriteAcc->SetPixelOnData(pScanline, nX, aTransparentColor); + else + pWriteAcc->SetPixelOnData(pScanline, nX, aOpaqueColor); + } + } + } + + pWriteAcc.reset(); + pReadAcc.reset(); + + aNewBmp.SetPrefSize(maPrefSize); + aNewBmp.SetPrefMapMode(maPrefMapMode); + + return aNewBmp; +} + +AlphaMask Bitmap::CreateAlphaMask(const Color& rTransColor, sal_uInt8 nTol) const +{ + if (nTol == 0) + return CreateAlphaMask(rTransColor); + + BitmapScopedReadAccess pReadAcc(*this); + if (!pReadAcc) + return AlphaMask(); + + // Historically LO used 1bpp masks, but 8bpp masks are much faster, + // better supported by hardware, and the memory savings are not worth + // it anymore. + // TODO: Possibly remove the 1bpp code later. + + AlphaMask aNewBmp(GetSizePixel()); + BitmapScopedWriteAccess pWriteAcc(aNewBmp); + if (!pWriteAcc) + return AlphaMask(); + + const tools::Long nWidth = pReadAcc->Width(); + const tools::Long nHeight = pReadAcc->Height(); + const BitmapColor aOpaqueColor(pWriteAcc->GetBestMatchingColor(COL_ALPHA_OPAQUE)); + const BitmapColor aTransparentColor(pWriteAcc->GetBestMatchingColor(COL_ALPHA_TRANSPARENT)); + + BitmapColor aCol; + tools::Long nR, nG, nB; + const tools::Long nMinR = std::clamp<tools::Long>(rTransColor.GetRed() - nTol, 0, 255); + const tools::Long nMaxR = std::clamp<tools::Long>(rTransColor.GetRed() + nTol, 0, 255); + const tools::Long nMinG = std::clamp<tools::Long>(rTransColor.GetGreen() - nTol, 0, 255); + const tools::Long nMaxG = std::clamp<tools::Long>(rTransColor.GetGreen() + nTol, 0, 255); + const tools::Long nMinB = std::clamp<tools::Long>(rTransColor.GetBlue() - nTol, 0, 255); + const tools::Long nMaxB = std::clamp<tools::Long>(rTransColor.GetBlue() + nTol, 0, 255); + + if (pReadAcc->HasPalette()) + { + for (tools::Long nY = 0; nY < nHeight; nY++) + { + Scanline pScanline = pWriteAcc->GetScanline(nY); + Scanline pScanlineRead = pReadAcc->GetScanline(nY); + for (tools::Long nX = 0; nX < nWidth; nX++) + { + aCol = pReadAcc->GetPaletteColor(pReadAcc->GetIndexFromData(pScanlineRead, nX)); + nR = aCol.GetRed(); + nG = aCol.GetGreen(); + nB = aCol.GetBlue(); + + if (nMinR <= nR && nMaxR >= nR && nMinG <= nG && nMaxG >= nG && nMinB <= nB + && nMaxB >= nB) + { + pWriteAcc->SetPixelOnData(pScanline, nX, aTransparentColor); + } + else + { + pWriteAcc->SetPixelOnData(pScanline, nX, aOpaqueColor); + } + } + } + } + else + { + for (tools::Long nY = 0; nY < nHeight; nY++) + { + Scanline pScanline = pWriteAcc->GetScanline(nY); + Scanline pScanlineRead = pReadAcc->GetScanline(nY); + for (tools::Long nX = 0; nX < nWidth; nX++) + { + aCol = pReadAcc->GetPixelFromData(pScanlineRead, nX); + nR = aCol.GetRed(); + nG = aCol.GetGreen(); + nB = aCol.GetBlue(); + + if (nMinR <= nR && nMaxR >= nR && nMinG <= nG && nMaxG >= nG && nMinB <= nB + && nMaxB >= nB) + { + pWriteAcc->SetPixelOnData(pScanline, nX, aTransparentColor); + } + else + { + pWriteAcc->SetPixelOnData(pScanline, nX, aOpaqueColor); + } + } + } + } + + pWriteAcc.reset(); + pReadAcc.reset(); + + aNewBmp.SetPrefSize(maPrefSize); + aNewBmp.SetPrefMapMode(maPrefMapMode); + + return aNewBmp; +} + +vcl::Region Bitmap::CreateRegion(const Color& rColor, const tools::Rectangle& rRect) const +{ + tools::Rectangle aRect(rRect); + BitmapScopedReadAccess pReadAcc(*this); + + aRect.Intersection(tools::Rectangle(Point(), GetSizePixel())); + aRect.Normalize(); + + if (!pReadAcc) + return vcl::Region(aRect); + + vcl::Region aRegion; + const tools::Long nLeft = aRect.Left(); + const tools::Long nTop = aRect.Top(); + const tools::Long nRight = aRect.Right(); + const tools::Long nBottom = aRect.Bottom(); + const BitmapColor aMatch(pReadAcc->GetBestMatchingColor(rColor)); + + std::vector<tools::Long> aLine; + tools::Long nYStart(nTop); + tools::Long nY(nTop); + + for (; nY <= nBottom; nY++) + { + std::vector<tools::Long> aNewLine; + tools::Long nX(nLeft); + Scanline pScanlineRead = pReadAcc->GetScanline(nY); + + for (; nX <= nRight;) + { + while ((nX <= nRight) && (aMatch != pReadAcc->GetPixelFromData(pScanlineRead, nX))) + nX++; + + if (nX <= nRight) + { + aNewLine.push_back(nX); + + while ((nX <= nRight) && (aMatch == pReadAcc->GetPixelFromData(pScanlineRead, nX))) + { + nX++; + } + + aNewLine.push_back(nX - 1); + } + } + + if (aNewLine != aLine) + { + // need to write aLine, it's different from the next line + if (!aLine.empty()) + { + tools::Rectangle aSubRect; + + // enter y values and proceed ystart + aSubRect.SetTop(nYStart); + aSubRect.SetBottom(nY ? nY - 1 : 0); + + for (size_t a(0); a < aLine.size();) + { + aSubRect.SetLeft(aLine[a++]); + aSubRect.SetRight(aLine[a++]); + aRegion.Union(aSubRect); + } + } + + // copy line as new line + aLine = aNewLine; + nYStart = nY; + } + } + + // write last line if used + if (!aLine.empty()) + { + tools::Rectangle aSubRect; + + // enter y values + aSubRect.SetTop(nYStart); + aSubRect.SetBottom(nY ? nY - 1 : 0); + + for (size_t a(0); a < aLine.size();) + { + aSubRect.SetLeft(aLine[a++]); + aSubRect.SetRight(aLine[a++]); + aRegion.Union(aSubRect); + } + } + + pReadAcc.reset(); + + return aRegion; +} + +bool Bitmap::ReplaceMask(const AlphaMask& rMask, const Color& rReplaceColor) +{ + BitmapScopedReadAccess pMaskAcc(rMask); + BitmapScopedWriteAccess pAcc(*this); + + if (!pMaskAcc || !pAcc) + return false; + + const tools::Long nWidth = std::min(pMaskAcc->Width(), pAcc->Width()); + const tools::Long nHeight = std::min(pMaskAcc->Height(), pAcc->Height()); + const BitmapColor aMaskWhite(pMaskAcc->GetBestMatchingColor(COL_WHITE)); + BitmapColor aReplace; + + if (pAcc->HasPalette()) + { + const sal_uInt16 nActColors = pAcc->GetPaletteEntryCount(); + const sal_uInt16 nMaxColors = 1 << pAcc->GetBitCount(); + + aReplace = UpdatePaletteForNewColor(pAcc, nActColors, nMaxColors, nHeight, nWidth, + BitmapColor(rReplaceColor)); + } + else + aReplace = rReplaceColor; + + for (tools::Long nY = 0; nY < nHeight; nY++) + { + Scanline pScanline = pAcc->GetScanline(nY); + Scanline pScanlineMask = pMaskAcc->GetScanline(nY); + for (tools::Long nX = 0; nX < nWidth; nX++) + { + if (pMaskAcc->GetPixelFromData(pScanlineMask, nX) == aMaskWhite) + pAcc->SetPixelOnData(pScanline, nX, aReplace); + } + } + + return true; +} + +bool Bitmap::Replace(const AlphaMask& rAlpha, const Color& rMergeColor) +{ + Bitmap aNewBmp(GetSizePixel(), vcl::PixelFormat::N24_BPP); + BitmapScopedReadAccess pAcc(*this); + BitmapScopedReadAccess pAlphaAcc(rAlpha); + BitmapScopedWriteAccess pNewAcc(aNewBmp); + + if (!pAcc || !pAlphaAcc || !pNewAcc) + return false; + + BitmapColor aCol; + const tools::Long nWidth = std::min(pAlphaAcc->Width(), pAcc->Width()); + const tools::Long nHeight = std::min(pAlphaAcc->Height(), pAcc->Height()); + + for (tools::Long nY = 0; nY < nHeight; nY++) + { + Scanline pScanline = pNewAcc->GetScanline(nY); + Scanline pScanlineAlpha = pAlphaAcc->GetScanline(nY); + for (tools::Long nX = 0; nX < nWidth; nX++) + { + aCol = pAcc->GetColor(nY, nX); + aCol.Merge(rMergeColor, pAlphaAcc->GetIndexFromData(pScanlineAlpha, nX)); + pNewAcc->SetPixelOnData(pScanline, nX, aCol); + } + } + + pAcc.reset(); + pAlphaAcc.reset(); + pNewAcc.reset(); + + const MapMode aMap(maPrefMapMode); + const Size aSize(maPrefSize); + + *this = aNewBmp; + + maPrefMapMode = aMap; + maPrefSize = aSize; + + return true; +} + +bool Bitmap::Replace(const Color& rSearchColor, const Color& rReplaceColor, sal_uInt8 nTol) +{ + if (mxSalBmp) + { + // implementation specific replace + std::shared_ptr<SalBitmap> xImpBmp(ImplGetSVData()->mpDefInst->CreateSalBitmap()); + if (xImpBmp->Create(*mxSalBmp) && xImpBmp->Replace(rSearchColor, rReplaceColor, nTol)) + { + ImplSetSalBitmap(xImpBmp); + maPrefMapMode = MapMode(MapUnit::MapPixel); + maPrefSize = xImpBmp->GetSize(); + return true; + } + } + + BitmapScopedWriteAccess pAcc(*this); + if (!pAcc) + return false; + + const tools::Long nMinR = std::clamp<tools::Long>(rSearchColor.GetRed() - nTol, 0, 255); + const tools::Long nMaxR = std::clamp<tools::Long>(rSearchColor.GetRed() + nTol, 0, 255); + const tools::Long nMinG = std::clamp<tools::Long>(rSearchColor.GetGreen() - nTol, 0, 255); + const tools::Long nMaxG = std::clamp<tools::Long>(rSearchColor.GetGreen() + nTol, 0, 255); + const tools::Long nMinB = std::clamp<tools::Long>(rSearchColor.GetBlue() - nTol, 0, 255); + const tools::Long nMaxB = std::clamp<tools::Long>(rSearchColor.GetBlue() + nTol, 0, 255); + + if (pAcc->HasPalette()) + { + for (sal_uInt16 i = 0, nPalCount = pAcc->GetPaletteEntryCount(); i < nPalCount; i++) + { + const BitmapColor& rCol = pAcc->GetPaletteColor(i); + + if (nMinR <= rCol.GetRed() && nMaxR >= rCol.GetRed() && nMinG <= rCol.GetGreen() + && nMaxG >= rCol.GetGreen() && nMinB <= rCol.GetBlue() && nMaxB >= rCol.GetBlue()) + { + pAcc->SetPaletteColor(i, rReplaceColor); + } + } + } + else + { + BitmapColor aCol; + const BitmapColor aReplace(pAcc->GetBestMatchingColor(rReplaceColor)); + + for (tools::Long nY = 0, nHeight = pAcc->Height(); nY < nHeight; nY++) + { + Scanline pScanline = pAcc->GetScanline(nY); + for (tools::Long nX = 0, nWidth = pAcc->Width(); nX < nWidth; nX++) + { + aCol = pAcc->GetPixelFromData(pScanline, nX); + + if (nMinR <= aCol.GetRed() && nMaxR >= aCol.GetRed() && nMinG <= aCol.GetGreen() + && nMaxG >= aCol.GetGreen() && nMinB <= aCol.GetBlue() + && nMaxB >= aCol.GetBlue()) + { + pAcc->SetPixelOnData(pScanline, nX, aReplace); + } + } + } + } + + pAcc.reset(); + + return true; +} + +bool Bitmap::Replace(const Color* pSearchColors, const Color* pReplaceColors, size_t nColorCount, + sal_uInt8 const* pTols) +{ + BitmapScopedWriteAccess pAcc(*this); + if (!pAcc) + return false; + + std::vector<sal_uInt8> aMinR(nColorCount); + std::vector<sal_uInt8> aMaxR(nColorCount); + std::vector<sal_uInt8> aMinG(nColorCount); + std::vector<sal_uInt8> aMaxG(nColorCount); + std::vector<sal_uInt8> aMinB(nColorCount); + std::vector<sal_uInt8> aMaxB(nColorCount); + + if (pTols) + { + for (size_t i = 0; i < nColorCount; ++i) + { + const Color& rCol = pSearchColors[i]; + const sal_uInt8 nTol = pTols[i]; + + aMinR[i] = std::clamp(rCol.GetRed() - nTol, 0, 255); + aMaxR[i] = std::clamp(rCol.GetRed() + nTol, 0, 255); + aMinG[i] = std::clamp(rCol.GetGreen() - nTol, 0, 255); + aMaxG[i] = std::clamp(rCol.GetGreen() + nTol, 0, 255); + aMinB[i] = std::clamp(rCol.GetBlue() - nTol, 0, 255); + aMaxB[i] = std::clamp(rCol.GetBlue() + nTol, 0, 255); + } + } + else + { + for (size_t i = 0; i < nColorCount; ++i) + { + const Color& rCol = pSearchColors[i]; + + aMinR[i] = rCol.GetRed(); + aMaxR[i] = rCol.GetRed(); + aMinG[i] = rCol.GetGreen(); + aMaxG[i] = rCol.GetGreen(); + aMinB[i] = rCol.GetBlue(); + aMaxB[i] = rCol.GetBlue(); + } + } + + if (pAcc->HasPalette()) + { + for (sal_uInt16 nEntry = 0, nPalCount = pAcc->GetPaletteEntryCount(); nEntry < nPalCount; + nEntry++) + { + const BitmapColor& rCol = pAcc->GetPaletteColor(nEntry); + + for (size_t i = 0; i < nColorCount; ++i) + { + if (aMinR[i] <= rCol.GetRed() && aMaxR[i] >= rCol.GetRed() + && aMinG[i] <= rCol.GetGreen() && aMaxG[i] >= rCol.GetGreen() + && aMinB[i] <= rCol.GetBlue() && aMaxB[i] >= rCol.GetBlue()) + { + pAcc->SetPaletteColor(nEntry, pReplaceColors[i]); + break; + } + } + } + } + else + { + std::vector<BitmapColor> aReplaces(nColorCount); + + for (size_t i = 0; i < nColorCount; ++i) + aReplaces[i] = pAcc->GetBestMatchingColor(pReplaceColors[i]); + + for (tools::Long nY = 0, nHeight = pAcc->Height(); nY < nHeight; nY++) + { + Scanline pScanline = pAcc->GetScanline(nY); + for (tools::Long nX = 0, nWidth = pAcc->Width(); nX < nWidth; nX++) + { + BitmapColor aCol = pAcc->GetPixelFromData(pScanline, nX); + + for (size_t i = 0; i < nColorCount; ++i) + { + if (aMinR[i] <= aCol.GetRed() && aMaxR[i] >= aCol.GetRed() + && aMinG[i] <= aCol.GetGreen() && aMaxG[i] >= aCol.GetGreen() + && aMinB[i] <= aCol.GetBlue() && aMaxB[i] >= aCol.GetBlue()) + { + pAcc->SetPixelOnData(pScanline, nX, aReplaces[i]); + break; + } + } + } + } + } + + pAcc.reset(); + + return true; +} + +// TODO: Have a look at OutputDevice::ImplDrawAlpha() for some +// optimizations. Might even consolidate the code here and there. +bool Bitmap::Blend(const AlphaMask& rAlpha, const Color& rBackgroundColor) +{ + // Convert to a truecolor bitmap, if we're a paletted one. There's room for tradeoff decision here, + // maybe later for an overload (or a flag) + if (vcl::isPalettePixelFormat(getPixelFormat())) + Convert(BmpConversion::N24Bit); + + BitmapScopedReadAccess pAlphaAcc(rAlpha); + + BitmapScopedWriteAccess pAcc(*this); + + if (!pAlphaAcc || !pAcc) + return false; + + const tools::Long nWidth = std::min(pAlphaAcc->Width(), pAcc->Width()); + const tools::Long nHeight = std::min(pAlphaAcc->Height(), pAcc->Height()); + + for (tools::Long nY = 0; nY < nHeight; ++nY) + { + Scanline pScanline = pAcc->GetScanline(nY); + Scanline pScanlineAlpha = pAlphaAcc->GetScanline(nY); + for (tools::Long nX = 0; nX < nWidth; ++nX) + { + BitmapColor aBmpColor = pAcc->GetPixelFromData(pScanline, nX); + aBmpColor.Merge(rBackgroundColor, pAlphaAcc->GetIndexFromData(pScanlineAlpha, nX)); + pAcc->SetPixelOnData(pScanline, nX, aBmpColor); + } + } + + return true; +} + +static BitmapColor UpdatePaletteForNewColor(BitmapScopedWriteAccess& pAcc, + const sal_uInt16 nActColors, + const sal_uInt16 nMaxColors, const tools::Long nHeight, + const tools::Long nWidth, + const BitmapColor& rWantedColor) +{ + // default to the nearest color + sal_uInt16 aReplacePalIndex = pAcc->GetMatchingPaletteIndex(rWantedColor); + if (aReplacePalIndex != SAL_MAX_UINT16) + return BitmapColor(static_cast<sal_uInt8>(aReplacePalIndex)); + + // for paletted images without a matching palette entry + + // if the palette has empty entries use the last one + if (nActColors < nMaxColors) + { + pAcc->SetPaletteEntryCount(nActColors + 1); + pAcc->SetPaletteColor(nActColors, rWantedColor); + return BitmapColor(static_cast<sal_uInt8>(nActColors)); + } + + // look for an unused palette entry (NOTE: expensive!) + std::unique_ptr<bool[]> pFlags(new bool[nMaxColors]); + + // Set all entries to false + std::fill(pFlags.get(), pFlags.get() + nMaxColors, false); + + for (tools::Long nY = 0; nY < nHeight; nY++) + { + Scanline pScanline = pAcc->GetScanline(nY); + for (tools::Long nX = 0; nX < nWidth; nX++) + pFlags[pAcc->GetIndexFromData(pScanline, nX)] = true; + } + + for (sal_uInt16 i = 0; i < nMaxColors; i++) + { + // Hurray, we do have an unused entry + if (!pFlags[i]) + { + pAcc->SetPaletteColor(i, rWantedColor); + return BitmapColor(static_cast<sal_uInt8>(i)); + } + } + assert(false && "found nothing"); + return BitmapColor(0); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |