/* -*- 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();

        if (pWriteAcc->GetPalette().IsGreyPalette8Bit())
        {
            // For alpha masks, we need to actually invert the underlying data
            // or the optimisations elsewhere do not always work right. If this is a bottleneck,
            // probably better to try improving it inside the mxSalBmp->Invert() call above.
            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 (sal_uInt16 i = 0; i < nActColors; ++i)
            {
                BitmapColor aBmpColor = pWriteAcc->GetPaletteColor(i);
                aBmpColor.Invert();
                pWriteAcc->SetPaletteColor(i, 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.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 = std::move(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 = std::move(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.

    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 (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.

    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 = std::move(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 = std::move(aNewBmp);

    maPrefMapMode = aMap;
    maPrefSize = aSize;

    return true;
}

bool Bitmap::Replace(const Color& rSearchColor, const Color& rReplaceColor, sal_uInt8 nTol)
{
    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())
                {
                    BitmapColor aNewCol = aReplace;
                    aNewCol.SetAlpha(aCol.GetAlpha());
                    pAcc->SetPixelOnData(pScanline, nX, aNewCol);
                }
            }
        }
    }

    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())
                    {
                        BitmapColor aNewCol = aReplaces[i];
                        aNewCol.SetAlpha(aCol.GetAlpha());
                        pAcc->SetPixelOnData(pScanline, nX, aNewCol);
                        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: */
