/* -*- 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 <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++)
                {
                    pAcc->SetPixelOnData(pScanline, nX,
                                         pAcc->GetPixelFromData(pScanline, nX).Invert());
                }
            }
        }

        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);
                pNewAcc->SetPixelOnData(
                    pScanline, nX,
                    aCol.Merge(rMergeColor, 255 - pAlphaAcc->GetIndexFromData(pScanlineAlpha, nX)));
            }
        }

        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);
        BitmapColor aPixel;
        BitmapColor aMaskPixel;
        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)
            {
                pAcc->SetPixelOnData(
                    pScanline, nX,
                    pAcc->GetPixelFromData(pScanline, nX)
                        .Merge(rBackgroundColor,
                               255 - pAlphaAcc->GetIndexFromData(pScanlineAlpha, nX)));
            }
        }

        bRet = true;
    }

    return bRet;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
