/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */
 
#include <config_features.h>
 
#include <rtl/math.hxx>
#include <sal/log.hxx>
#include <osl/diagnose.h>
#include <tools/helpers.hxx>
 
#include <utility>
#include <vcl/alpha.hxx>
#include <vcl/bitmap.hxx>
#include <vcl/outdev.hxx>
 
#include <svdata.hxx>
#include <salinst.hxx>
#include <salbmp.hxx>
#if HAVE_FEATURE_SKIA
#include <vcl/skia/SkiaHelper.hxx>
#endif
#include <vcl/bitmap/BitmapMonochromeFilter.hxx>
#include <vcl/ImageTree.hxx>
#include <vcl/filter/PngImageWriter.hxx>
 
#include <basegfx/matrix/b2dhommatrixtools.hxx>
#include <basegfx/color/bcolormodifier.hxx>
#include <bitmap/BitmapScaleSuperFilter.hxx>
#include <bitmap/BitmapScaleConvolutionFilter.hxx>
#include <bitmap/BitmapFastScaleFilter.hxx>
#include <bitmap/BitmapInterpolateScaleFilter.hxx>
#include <vcl/BitmapWriteAccess.hxx>
#include <bitmap/impoctree.hxx>
#include <bitmap/Octree.hxx>
#include <bitmap/BlendFrameCache.hxx>
#include <com/sun/star/beans/XFastPropertySet.hpp>
#include <o3tl/any.hxx>
#include <o3tl/environment.hxx>
 
#include "floyd.hxx"
 
#include <math.h>
#include <algorithm>
#include <memory>
 
#ifdef DBG_UTIL
#include <cstdlib>
#include <tools/stream.hxx>
#include <vcl/graphicfilter.hxx>
#endif
 
#if USE_HEADLESS_CODE
#include <headless/svpbmp.hxx>
#include <headless/CairoCommon.hxx>
#endif
 
Bitmap::Bitmap()
{
}
 
Bitmap::Bitmap(const Bitmap& rBitmap)
    : mxSalBmp(rBitmap.mxSalBmp)
    , maPrefMapMode(rBitmap.maPrefMapMode)
    , maPrefSize(rBitmap.maPrefSize)
{
}
 
Bitmap::Bitmap(std::shared_ptr<SalBitmap> pSalBitmap)
    : mxSalBmp(std::move(pSalBitmap))
    , maPrefMapMode(MapMode(MapUnit::MapPixel))
    , maPrefSize(mxSalBmp->GetSize())
{
}
 
Bitmap::Bitmap( const Size& rSizePixel, vcl::PixelFormat ePixelFormat, const BitmapPalette* pPal )
{
    if (!(rSizePixel.Width() && rSizePixel.Height()))
        return;
 
    switch (ePixelFormat)
    {
        case vcl::PixelFormat::N8_BPP:
        {
            static const BitmapPalette aPalN8_BPP = [] {
                BitmapPalette aPal(1 << sal_uInt16(vcl::PixelFormat::N8_BPP));
                aPal[ 0 ] = COL_BLACK;
                aPal[ 1 ] = COL_BLUE;
                aPal[ 2 ] = COL_GREEN;
                aPal[ 3 ] = COL_CYAN;
                aPal[ 4 ] = COL_RED;
                aPal[ 5 ] = COL_MAGENTA;
                aPal[ 6 ] = COL_BROWN;
                aPal[ 7 ] = COL_GRAY;
                aPal[ 8 ] = COL_LIGHTGRAY;
                aPal[ 9 ] = COL_LIGHTBLUE;
                aPal[ 10 ] = COL_LIGHTGREEN;
                aPal[ 11 ] = COL_LIGHTCYAN;
                aPal[ 12 ] = COL_LIGHTRED;
                aPal[ 13 ] = COL_LIGHTMAGENTA;
                aPal[ 14 ] = COL_YELLOW;
                aPal[ 15 ] = COL_WHITE;
 
                // Create dither palette
                sal_uInt16 nActCol = 16;
 
                for( sal_uInt16 nB = 0; nB < 256; nB += 51 )
                    for( sal_uInt16 nG = 0; nG < 256; nG += 51 )
                        for( sal_uInt16 nR = 0; nR < 256; nR += 51 )
                            aPal[ nActCol++ ] = BitmapColor( static_cast<sal_uInt8>(nR), static_cast<sal_uInt8>(nG), static_cast<sal_uInt8>(nB) );
 
                // Set standard Office colors
                aPal[ nActCol++ ] = BitmapColor( 0, 184, 255 );
                return aPal;
            }();
            if (!pPal)
                pPal = &aPalN8_BPP;
            break;
        }
        default:
        {
            static const BitmapPalette aPalEmpty;
            if (!pPal || !vcl::isPalettePixelFormat(ePixelFormat))
                pPal = &aPalEmpty;
            break;
        }
    }
 
    mxSalBmp = ImplGetSVData()->mpDefInst->CreateSalBitmap();
    mxSalBmp->Create(rSizePixel, ePixelFormat, *pPal);
}
 
static Bitmap createBitmapFromColorAndAlpha(const Bitmap& rColorBitmap, const Bitmap& rAlphaBitmap)
{
    if (rAlphaBitmap.IsEmpty())
        return rColorBitmap;
    else
    {
        Size aSize = rColorBitmap.GetSizePixel();
        static const BitmapPalette aPalEmpty;
        std::shared_ptr<SalBitmap> xSalBmp = ImplGetSVData()->mpDefInst->CreateSalBitmap();
        const bool bSuccess = xSalBmp->Create(aSize, vcl::PixelFormat::N32_BPP, aPalEmpty);
        if (!bSuccess)
        {
            SAL_WARN("vcl", "Bitmap::Bitmap(): could not create image");
            return Bitmap(std::move(xSalBmp));
        }
        Bitmap aRetBmp(std::move(xSalBmp));
        BitmapScopedReadAccess pReadColorAcc(rColorBitmap);
        BitmapScopedReadAccess pReadAlphaAcc(rAlphaBitmap);
        BitmapScopedWriteAccess pWriteAcc(aRetBmp);
        auto nHeight = pReadColorAcc->Height();
        auto nWidth = pReadColorAcc->Width();
        bool bPalette = pReadColorAcc->HasPalette();
 
        for ( tools::Long nY = 0; nY < nHeight; nY++ )
        {
            Scanline pScanlineColor = pReadColorAcc->GetScanline( nY );
            Scanline pScanlineAlpha = pReadAlphaAcc->GetScanline( nY );
            Scanline pScanlineWrite = pWriteAcc->GetScanline( nY );
            for (tools::Long nX = 0; nX < nWidth; ++nX)
            {
                BitmapColor aCol;
                if (bPalette)
                    aCol = pReadColorAcc->GetPaletteColor(pReadColorAcc->GetIndexFromData(pScanlineColor, nX));
                else
                    aCol = pReadColorAcc->GetPixelFromData(pScanlineColor, nX);
                auto nAlpha = pReadAlphaAcc->GetPixelFromData(pScanlineAlpha, nX).GetIndex();
                aCol.SetAlpha(nAlpha);
                pWriteAcc->SetPixelOnData(pScanlineWrite, nX, aCol);
            }
        }
        return aRetBmp;
 
// So.... in theory the following code should work, and be much more efficient. In practice, the gen/cairo
// code is doing something weird involving masks that results in alpha not doing the same thing as on the other
// backends.
//        ScopedVclPtrInstance<VirtualDevice> xDev(DeviceFormat::WITH_ALPHA);
//        Size aPixelSize = rBitmapEx.GetSizePixel();
//        xDev->SetOutputSizePixel(aPixelSize, /*bErase*/true, /*bAlphaMaskTransparent*/true);
//        xDev->DrawBitmap(Point(0, 0), aPixelSize, rBitmapEx);
//        mxSalBmp = xDev->GetBitmap(Point(0,0), aPixelSize).mxSalBmp;
 
    }
}
 
Bitmap::Bitmap( const Bitmap& rBitmap, Point aSrc, Size aSize )
{
    if( rBitmap.IsEmpty() || aSize.IsEmpty() )
        return;
 
    *this = Bitmap(aSize, rBitmap.getPixelFormat());
 
    tools::Rectangle aDestRect( Point( 0, 0 ), aSize );
    tools::Rectangle aSrcRect( aSrc, aSize );
    CopyPixel( aDestRect, aSrcRect, rBitmap );
}
 
Bitmap::Bitmap( const OUString& rIconName )
{
    loadFromIconTheme( rIconName );
}
 
void Bitmap::loadFromIconTheme( const OUString& rIconName )
{
    bool bSuccess;
    OUString aIconTheme;
 
    try
    {
        aIconTheme = Application::GetSettings().GetStyleSettings().DetermineIconTheme();
        bSuccess = ImageTree::get().loadImage(rIconName, aIconTheme, *this, true);
    }
    catch (...)
    {
        bSuccess = false;
    }
 
    SAL_WARN_IF( !bSuccess, "vcl", "Bitmap::Bitmap(): could not load image " << rIconName << " via icon theme " << aIconTheme);
}
 
Bitmap::Bitmap( const Bitmap& rBmp, const Bitmap& rMask )
{
    if (rMask.IsEmpty())
    {
        *this = rBmp;
        return;
    }
 
    assert(typeid(rMask) != typeid(AlphaMask)
        && "If this mask is actually an AlphaMask, then it will be inverted unnecessarily "
           "and the alpha channel will be wrong");
 
    AlphaMask aAlphaMask;
    if( rMask.getPixelFormat() == vcl::PixelFormat::N8_BPP && rMask.HasGreyPalette8Bit() )
    {
        aAlphaMask = rMask;
        aAlphaMask.Invert();
    }
    else if( rMask.getPixelFormat() == vcl::PixelFormat::N8_BPP )
    {
        Bitmap aMask(rMask);
        BitmapFilter::Filter(aMask, BitmapMonochromeFilter(255));
        aMask.Invert();
        aAlphaMask = aMask;
    }
    else
    {
        // convert to alpha bitmap
        SAL_WARN("vcl", "Bitmap: forced mask to monochrome");
        Bitmap aMask(rMask);
        BitmapFilter::Filter(aMask, BitmapMonochromeFilter(255));
        aMask.Invert();
        aAlphaMask = aMask;
    }
 
    if (!rBmp.IsEmpty() && rBmp.GetSizePixel() != aAlphaMask.GetSizePixel())
    {
        SAL_WARN("vcl", "Mask size differs from Bitmap size, corrected Mask (!)");
        aAlphaMask.Scale(rBmp.GetSizePixel(), BmpScaleFlag::Fast);
    }
    *this = createBitmapFromColorAndAlpha(rBmp, aAlphaMask.GetBitmap());
}
 
Bitmap::Bitmap( const Bitmap& rBmp, const AlphaMask& rAlphaMask )
{
    if (!rBmp.IsEmpty() && !rAlphaMask.IsEmpty() && rBmp.GetSizePixel() != rAlphaMask.GetSizePixel())
    {
        SAL_WARN("vcl", "Alpha size differs from Bitmap size, corrected Mask (!)");
        AlphaMask aNewMask = rAlphaMask;
        aNewMask.Scale(rBmp.GetSizePixel(), BmpScaleFlag::Fast);
        *this = createBitmapFromColorAndAlpha(rBmp, aNewMask.GetBitmap());
    }
    else
        *this = createBitmapFromColorAndAlpha(rBmp, rAlphaMask.GetBitmap());
}
 
 
Bitmap::Bitmap( const Bitmap& rBmp, const Color& rTransparentColor )
{
    AlphaMask aAlphaMask = rBmp.CreateAlphaMask( rTransparentColor );
 
    SAL_WARN_IF(rBmp.GetSizePixel() != aAlphaMask.GetSizePixel(), "vcl",
                "Bitmap::Bitmap(): size mismatch for bitmap and alpha mask.");
    *this = createBitmapFromColorAndAlpha(rBmp, aAlphaMask.GetBitmap());
}
 
 
#ifdef DBG_UTIL
 
namespace
{
void savePNG(const OUString& sWhere, const Bitmap& rBmp)
{
    SvFileStream aStream(sWhere, StreamMode::WRITE | StreamMode::TRUNC);
    GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
    rFilter.compressAsPNG(rBmp, aStream);
}
}
 
#endif
 
Bitmap::~Bitmap()
{
#ifdef DBG_UTIL
    // VCL_DUMP_BMP_PATH should be like C:/path/ or ~/path/
    static const OUString sDumpPath(o3tl::getEnvironment(u"VCL_DUMP_BMP_PATH"_ustr));
    // Stepping into the dtor of a bitmap you need, and setting the volatile variable to true in
    // debugger, would dump the bitmap in question
    static volatile bool save(false);
    if (!sDumpPath.isEmpty() && save)
    {
        save = false;
        savePNG(sDumpPath + "BitmapDump.png", *this);
    }
#endif
}
 
namespace
{
template <size_t N>
constexpr std::enable_if_t<255 % (N - 1) == 0, std::array<BitmapColor, N>> getGreyscalePalette()
{
    const int step = 255 / (N - 1);
    std::array<BitmapColor, N> a;
    for (size_t i = 0; i < N; ++i)
        a[i] = BitmapColor(i * step, i * step, i * step);
    return a;
}
}
 
const BitmapPalette& Bitmap::GetGreyPalette( int nEntries )
{
    // Create greyscale palette with 2, 4, 16 or 256 entries
    switch (nEntries)
    {
        case 2:
        {
            static const BitmapPalette aGreyPalette2 = getGreyscalePalette<2>();
            return aGreyPalette2;
        }
        case 4:
        {
            static const BitmapPalette aGreyPalette4 = getGreyscalePalette<4>();
            return aGreyPalette4;
        }
        case 16:
        {
            static const BitmapPalette aGreyPalette16 = getGreyscalePalette<16>();
            return aGreyPalette16;
        }
        case 256:
        {
            static const BitmapPalette aGreyPalette256 = getGreyscalePalette<256>();
            return aGreyPalette256;
        }
    }
    OSL_FAIL("Bitmap::GetGreyPalette: invalid entry count (2/4/16/256 allowed)");
    return GetGreyPalette(2);
}
 
Bitmap& Bitmap::operator=( const Bitmap& rBitmap )
{
    if (this == &rBitmap)
        return *this;
 
    maPrefSize = rBitmap.maPrefSize;
    maPrefMapMode = rBitmap.maPrefMapMode;
    mxSalBmp = rBitmap.mxSalBmp;
 
    return *this;
}
 
#if USE_HEADLESS_CODE
void* Bitmap::tryToGetCairoSurface() const
{
    SvpSalBitmap* pSvpSalBitmap(dynamic_cast<SvpSalBitmap*>(ImplGetSalBitmap().get()));
    if (nullptr == pSvpSalBitmap)
        return nullptr;
 
    const BitmapBuffer* pBitmapBuffer(pSvpSalBitmap->GetBuffer());
    if (nullptr == pBitmapBuffer)
        return nullptr;
 
    return CairoCommon::createCairoSurface(pBitmapBuffer);
}
#endif
 
Bitmap& Bitmap::operator=( Bitmap&& rBitmap ) noexcept
{
    maPrefSize = std::move(rBitmap.maPrefSize);
    maPrefMapMode = std::move(rBitmap.maPrefMapMode);
    mxSalBmp = std::move(rBitmap.mxSalBmp);
 
    return *this;
}
 
bool Bitmap::operator==( const Bitmap& rBmp ) const
{
    if (rBmp.mxSalBmp == mxSalBmp) // Includes both are nullptr
        return true;
    if (!rBmp.mxSalBmp || !mxSalBmp)
        return false;
    if (rBmp.mxSalBmp->GetSize() != mxSalBmp->GetSize() ||
        rBmp.mxSalBmp->GetBitCount() != mxSalBmp->GetBitCount())
        return false;
    BitmapChecksum aChecksum1 = rBmp.mxSalBmp->GetChecksum();
    BitmapChecksum aChecksum2 = mxSalBmp->GetChecksum();
    // If the bitmaps can't calculate a checksum, best to regard them as different.
    if (aChecksum1 == 0 || aChecksum2 == 0)
        return false;
    return aChecksum1 == aChecksum2;
}
 
void Bitmap::SetEmpty()
{
    maPrefMapMode = MapMode();
    maPrefSize = Size();
    mxSalBmp.reset();
}
 
Size Bitmap::GetSizePixel() const
{
    return( mxSalBmp ? mxSalBmp->GetSize() : Size() );
}
 
vcl::PixelFormat Bitmap::getPixelFormat() const
{
    if (!mxSalBmp)
        return vcl::PixelFormat::INVALID;
 
    sal_uInt16 nBitCount = mxSalBmp->GetBitCount();
    if (nBitCount <= 8)
        return vcl::PixelFormat::N8_BPP;
    if (nBitCount <= 24)
        return vcl::PixelFormat::N24_BPP;
    if (nBitCount <= 32)
        return vcl::PixelFormat::N32_BPP;
 
    return vcl::PixelFormat::INVALID;
}
 
bool Bitmap::HasAlpha() const
{
    if (!mxSalBmp)
        return false;
    switch(mxSalBmp->GetScanlineFormat())
    {
        case ScanlineFormat::N32BitTcAbgr:
        case ScanlineFormat::N32BitTcArgb:
        case ScanlineFormat::N32BitTcBgra:
        case ScanlineFormat::N32BitTcRgba:
            return true;
        default:
            return false;
    }
}
 
bool Bitmap::HasGreyPaletteAny() const
{
    bool bRet = false;
 
    BitmapScopedInfoAccess pIAcc(*this);
 
    if( pIAcc )
    {
        bRet = pIAcc->HasPalette() && pIAcc->GetPalette().IsGreyPaletteAny();
    }
 
    return bRet;
}
 
bool Bitmap::HasGreyPalette8Bit() const
{
    bool            bRet = false;
    BitmapScopedInfoAccess pIAcc(*this);
 
    if( pIAcc )
    {
        bRet = pIAcc->HasPalette() && pIAcc->GetPalette().IsGreyPalette8Bit();
    }
 
    return bRet;
}
 
BitmapChecksum Bitmap::GetChecksum() const
{
    if( !mxSalBmp )
        return 0;
 
    BitmapChecksum nRet = mxSalBmp->GetChecksum();
    if (!nRet)
    {
        // nRet == 0 => probably, we were not able to acquire
        // the buffer in SalBitmap::updateChecksum;
        // so, we need to update the imp bitmap for this bitmap instance
        // as we do in BitmapInfoAccess::ImplCreate
        std::shared_ptr<SalBitmap> xNewImpBmp(ImplGetSVData()->mpDefInst->CreateSalBitmap());
        if (xNewImpBmp->Create(*mxSalBmp))
        {
            Bitmap* pThis = const_cast<Bitmap*>(this);
            pThis->mxSalBmp = std::move(xNewImpBmp);
            nRet = mxSalBmp->GetChecksum();
        }
    }
 
    return nRet;
}
 
void Bitmap::ImplMakeUnique()
{
    if (mxSalBmp && mxSalBmp.use_count() > 1)
    {
        std::shared_ptr<SalBitmap> xOldImpBmp = mxSalBmp;
        mxSalBmp = ImplGetSVData()->mpDefInst->CreateSalBitmap();
        (void)mxSalBmp->Create(*xOldImpBmp);
    }
}
 
void Bitmap::ReassignWithSize(const Bitmap& rBitmap)
{
    const Size aOldSizePix(GetSizePixel());
    const Size aNewSizePix(rBitmap.GetSizePixel());
    const MapMode aOldMapMode(maPrefMapMode);
    Size aNewPrefSize;
 
    if ((aOldSizePix != aNewSizePix) && aOldSizePix.Width() && aOldSizePix.Height())
    {
        aNewPrefSize.setWidth(maPrefSize.Width() * aNewSizePix.Width() / aOldSizePix.Width());
        aNewPrefSize.setHeight(maPrefSize.Height() * aNewSizePix.Height() / aOldSizePix.Height());
    }
    else
    {
        aNewPrefSize = maPrefSize;
    }
 
    *this = rBitmap;
 
    maPrefSize = aNewPrefSize;
    maPrefMapMode = aOldMapMode;
}
 
void Bitmap::ImplSetSalBitmap(const std::shared_ptr<SalBitmap>& xImpBmp)
{
    mxSalBmp = xImpBmp;
}
 
bool Bitmap::Crop( const tools::Rectangle& rRectPixel )
{
    const Size          aSizePix( GetSizePixel() );
    tools::Rectangle           aRect( rRectPixel );
 
    aRect.Intersection( tools::Rectangle( Point(), aSizePix ) );
 
    if( aRect.IsEmpty() || aSizePix == aRect.GetSize())
        return false;
 
    BitmapScopedReadAccess pReadAcc(*this);
    if( !pReadAcc )
        return false;
 
    const tools::Rectangle     aNewRect( Point(), aRect.GetSize() );
    Bitmap aNewBmp(aNewRect.GetSize(), getPixelFormat(), &pReadAcc->GetPalette());
    BitmapScopedWriteAccess pWriteAcc(aNewBmp);
    if( !pWriteAcc )
        return false;
 
    const tools::Long nOldX = aRect.Left();
    const tools::Long nOldY = aRect.Top();
    const tools::Long nNewWidth = aNewRect.GetWidth();
    const tools::Long nNewHeight = aNewRect.GetHeight();
 
    for( tools::Long nY = 0, nY2 = nOldY; nY < nNewHeight; nY++, nY2++ )
    {
        Scanline pScanline = pWriteAcc->GetScanline(nY);
        Scanline pScanlineRead = pReadAcc->GetScanline(nY2);
        for( tools::Long nX = 0, nX2 = nOldX; nX < nNewWidth; nX++, nX2++ )
            pWriteAcc->SetPixelOnData( pScanline, nX, pReadAcc->GetPixelFromData( pScanlineRead, nX2 ) );
    }
 
    pWriteAcc.reset();
    pReadAcc.reset();
 
    ReassignWithSize( aNewBmp );
 
    return true;
};
 
bool Bitmap::CopyPixel( const tools::Rectangle& rRectDst,
                        const tools::Rectangle& rRectSrc )
{
    const Size  aSizePix( GetSizePixel() );
    tools::Rectangle   aRectDst( rRectDst );
 
    aRectDst.Intersection( tools::Rectangle( Point(), aSizePix ) );
 
    if( aRectDst.IsEmpty() )
        return false;
 
    tools::Rectangle aRectSrc( rRectSrc );
 
    aRectSrc.Intersection( tools::Rectangle( Point(), aSizePix ) );
 
    if( aRectSrc.IsEmpty() || ( aRectSrc == aRectDst ) )
        return false;
 
    BitmapScopedWriteAccess   pWriteAcc(*this);
    if( !pWriteAcc )
        return false;
 
    const tools::Long  nWidth = std::min( aRectSrc.GetWidth(), aRectDst.GetWidth() );
    const tools::Long  nHeight = std::min( aRectSrc.GetHeight(), aRectDst.GetHeight() );
    const tools::Long  nSrcX = aRectSrc.Left();
    const tools::Long  nSrcY = aRectSrc.Top();
    const tools::Long  nSrcEndX1 = nSrcX + nWidth - 1;
    const tools::Long  nSrcEndY1 = nSrcY + nHeight - 1;
    const tools::Long  nDstX = aRectDst.Left();
    const tools::Long  nDstY = aRectDst.Top();
    const tools::Long  nDstEndX1 = nDstX + nWidth - 1;
    const tools::Long  nDstEndY1 = nDstY + nHeight - 1;
 
    if( ( nDstX <= nSrcX ) && ( nDstY <= nSrcY ) )
    {
        for( tools::Long nY = nSrcY, nYN = nDstY; nY <= nSrcEndY1; nY++, nYN++ )
        {
            Scanline pScanline = pWriteAcc->GetScanline(nYN);
            Scanline pScanlineSrc = pWriteAcc->GetScanline(nY);
            for( tools::Long nX = nSrcX, nXN = nDstX; nX <= nSrcEndX1; nX++, nXN++ )
                pWriteAcc->SetPixelOnData( pScanline, nXN, pWriteAcc->GetPixelFromData( pScanlineSrc, nX ) );
        }
    }
    else if( ( nDstX <= nSrcX ) && ( nDstY >= nSrcY ) )
    {
        for( tools::Long nY = nSrcEndY1, nYN = nDstEndY1; nY >= nSrcY; nY--, nYN-- )
        {
            Scanline pScanline = pWriteAcc->GetScanline(nYN);
            Scanline pScanlineSrc = pWriteAcc->GetScanline(nY);
            for( tools::Long nX = nSrcX, nXN = nDstX; nX <= nSrcEndX1; nX++, nXN++ )
                pWriteAcc->SetPixelOnData( pScanline, nXN, pWriteAcc->GetPixelFromData( pScanlineSrc, nX ) );
        }
    }
    else if( ( nDstX >= nSrcX ) && ( nDstY <= nSrcY ) )
    {
        for( tools::Long nY = nSrcY, nYN = nDstY; nY <= nSrcEndY1; nY++, nYN++ )
        {
            Scanline pScanline = pWriteAcc->GetScanline(nYN);
            Scanline pScanlineSrc = pWriteAcc->GetScanline(nY);
            for( tools::Long nX = nSrcEndX1, nXN = nDstEndX1; nX >= nSrcX; nX--, nXN-- )
                pWriteAcc->SetPixelOnData( pScanline, nXN, pWriteAcc->GetPixelFromData( pScanlineSrc, nX ) );
        }
    }
    else
    {
        for( tools::Long nY = nSrcEndY1, nYN = nDstEndY1; nY >= nSrcY; nY--, nYN-- )
        {
            Scanline pScanline = pWriteAcc->GetScanline(nYN);
            Scanline pScanlineSrc = pWriteAcc->GetScanline(nY);
            for( tools::Long nX = nSrcEndX1, nXN = nDstEndX1; nX >= nSrcX; nX--, nXN-- )
                pWriteAcc->SetPixelOnData( pScanline, nXN, pWriteAcc->GetPixelFromData( pScanlineSrc, nX ) );
        }
    }
 
    return true;
}
 
bool Bitmap::CopyPixel( const tools::Rectangle& rRectDst,
                        const tools::Rectangle& rRectSrc, const Bitmap& rBmpSrc )
{
    const Size  aSizePix( GetSizePixel() );
    tools::Rectangle   aRectDst( rRectDst );
 
    aRectDst.Intersection( tools::Rectangle( Point(), aSizePix ) );
 
    if( aRectDst.IsEmpty() )
        return false;
 
    if( rBmpSrc.mxSalBmp == mxSalBmp ) // if self-copy
        return CopyPixel(rRectDst, rRectSrc);
 
    Bitmap*         pSrc = &const_cast<Bitmap&>(rBmpSrc);
    const Size      aCopySizePix( pSrc->GetSizePixel() );
    tools::Rectangle       aRectSrc( rRectSrc );
    const sal_uInt16 nSrcBitCount = vcl::pixelFormatBitCount(rBmpSrc.getPixelFormat());
    const sal_uInt16 nDstBitCount = vcl::pixelFormatBitCount(getPixelFormat());
 
    if( nSrcBitCount > nDstBitCount )
    {
        int nNextIndex = 0;
 
        if (nSrcBitCount == 24)
            Convert( BmpConversion::N24Bit );
        else if (nSrcBitCount == 8)
        {
            Convert( BmpConversion::N8BitColors );
            nNextIndex = 16;
        }
        else if (nSrcBitCount == 4)
        {
            assert(false);
        }
 
        if( nNextIndex )
        {
            BitmapScopedReadAccess  pSrcAcc(*pSrc);
            BitmapScopedWriteAccess pDstAcc(*this);
 
            if( pSrcAcc && pDstAcc )
            {
                const int nSrcCount = pSrcAcc->GetPaletteEntryCount();
                const int nDstCount = 1 << nDstBitCount;
 
                for (int i = 0; ( i < nSrcCount ) && ( nNextIndex < nDstCount ); ++i)
                {
                    const BitmapColor& rSrcCol = pSrcAcc->GetPaletteColor( static_cast<sal_uInt16>(i) );
 
                    bool bFound = false;
 
                    for (int j = 0; j < nDstCount; ++j)
                    {
                        if( rSrcCol == pDstAcc->GetPaletteColor( static_cast<sal_uInt16>(j) ) )
                        {
                            bFound = true;
                            break;
                        }
                    }
 
                    if( !bFound )
                        pDstAcc->SetPaletteColor( static_cast<sal_uInt16>(nNextIndex++), rSrcCol );
                }
            }
        }
    }
 
    aRectSrc.Intersection( tools::Rectangle( Point(), aCopySizePix ) );
 
    if( aRectSrc.IsEmpty() )
        return false;
 
    BitmapScopedReadAccess pReadAcc(*pSrc);
    if( !pReadAcc )
        return false;
 
    BitmapScopedWriteAccess pWriteAcc(*this);
    if( !pWriteAcc )
        return false;
 
    const tools::Long  nWidth = std::min( aRectSrc.GetWidth(), aRectDst.GetWidth() );
    const tools::Long  nHeight = std::min( aRectSrc.GetHeight(), aRectDst.GetHeight() );
    const tools::Long  nSrcEndX = aRectSrc.Left() + nWidth;
    const tools::Long  nSrcEndY = aRectSrc.Top() + nHeight;
    tools::Long        nDstY = aRectDst.Top();
 
    if( pReadAcc->HasPalette() && pWriteAcc->HasPalette() )
    {
        const sal_uInt16    nCount = pReadAcc->GetPaletteEntryCount();
        std::unique_ptr<sal_uInt8[]> pMap(new sal_uInt8[ nCount ]);
 
        // Create index map for the color table, as the bitmap should be copied
        // retaining it's color information relatively well
        for( sal_uInt16 i = 0; i < nCount; i++ )
            pMap[ i ] = static_cast<sal_uInt8>(pWriteAcc->GetBestPaletteIndex( pReadAcc->GetPaletteColor( i ) ));
 
        for( tools::Long nSrcY = aRectSrc.Top(); nSrcY < nSrcEndY; nSrcY++, nDstY++ )
        {
            Scanline pScanline = pWriteAcc->GetScanline(nDstY);
            Scanline pScanlineRead = pReadAcc->GetScanline(nSrcY);
            for( tools::Long nSrcX = aRectSrc.Left(), nDstX = aRectDst.Left(); nSrcX < nSrcEndX; nSrcX++, nDstX++ )
                pWriteAcc->SetPixelOnData( pScanline, nDstX, BitmapColor( pMap[ pReadAcc->GetIndexFromData( pScanlineRead, nSrcX ) ] ));
        }
    }
    else if( pReadAcc->HasPalette() )
    {
        for( tools::Long nSrcY = aRectSrc.Top(); nSrcY < nSrcEndY; nSrcY++, nDstY++ )
        {
            Scanline pScanline = pWriteAcc->GetScanline(nDstY);
            Scanline pScanlineRead = pReadAcc->GetScanline(nSrcY);
            for( tools::Long nSrcX = aRectSrc.Left(), nDstX = aRectDst.Left(); nSrcX < nSrcEndX; nSrcX++, nDstX++ )
                pWriteAcc->SetPixelOnData( pScanline, nDstX, pReadAcc->GetPaletteColor( pReadAcc->GetIndexFromData( pScanlineRead, nSrcX ) ) );
        }
    }
    else
        for( tools::Long nSrcY = aRectSrc.Top(); nSrcY < nSrcEndY; nSrcY++, nDstY++ )
        {
            Scanline pScanline = pWriteAcc->GetScanline(nDstY);
            Scanline pScanlineRead = pReadAcc->GetScanline(nSrcY);
            for( tools::Long nSrcX = aRectSrc.Left(), nDstX = aRectDst.Left(); nSrcX < nSrcEndX; nSrcX++, nDstX++ )
                pWriteAcc->SetPixelOnData( pScanline, nDstX, pReadAcc->GetPixelFromData( pScanlineRead, nSrcX ) );
        }
 
    bool bRet = ( nWidth > 0 ) && ( nHeight > 0 );
 
    return bRet;
}
 
bool Bitmap::Expand( sal_Int32 nDX, sal_Int32 nDY, const Color* pInitColor )
{
    if( !nDX && !nDY )
        return false;
 
    const Size          aSizePixel( GetSizePixel() );
    const tools::Long          nWidth = aSizePixel.Width();
    const tools::Long          nHeight = aSizePixel.Height();
    const Size          aNewSize( nWidth + nDX, nHeight + nDY );
    BitmapScopedReadAccess pReadAcc(*this);
    if( !pReadAcc )
        return false;
 
    BitmapPalette       aBmpPal( pReadAcc->GetPalette() );
    Bitmap aNewBmp(aNewSize, getPixelFormat(), &aBmpPal);
    BitmapScopedWriteAccess pWriteAcc(aNewBmp);
    if( !pWriteAcc )
        return false;
 
    BitmapColor aColor;
    const tools::Long  nNewX = nWidth;
    const tools::Long  nNewY = nHeight;
    const tools::Long  nNewWidth = pWriteAcc->Width();
    const tools::Long  nNewHeight = pWriteAcc->Height();
    tools::Long        nX;
    tools::Long        nY;
 
    if( pInitColor )
        aColor = pWriteAcc->GetBestMatchingColor( *pInitColor );
 
    for( nY = 0; nY < nHeight; nY++ )
    {
        pWriteAcc->CopyScanline( nY, *pReadAcc );
 
        if( pInitColor && nDX )
        {
            Scanline pScanline = pWriteAcc->GetScanline(nY);
            for( nX = nNewX; nX < nNewWidth; nX++ )
                pWriteAcc->SetPixelOnData( pScanline, nX, aColor );
        }
    }
 
    if( pInitColor && nDY )
        for( nY = nNewY; nY < nNewHeight; nY++ )
        {
            Scanline pScanline = pWriteAcc->GetScanline(nY);
            for( nX = 0; nX < nNewWidth; nX++ )
                pWriteAcc->SetPixelOnData( pScanline, nX, aColor );
        }
 
    pWriteAcc.reset();
    pReadAcc.reset();
 
    ReassignWithSize(aNewBmp);
 
    return true;
}
 
Bitmap Bitmap::CreateDisplayBitmap( OutputDevice* pDisplay ) const
{
    Bitmap aDispBmp( *this );
 
    SalGraphics* pDispGraphics = pDisplay->GetGraphics();
 
    if( mxSalBmp && pDispGraphics )
    {
        std::shared_ptr<SalBitmap> xImpDispBmp(ImplGetSVData()->mpDefInst->CreateSalBitmap());
        if (xImpDispBmp->Create(*mxSalBmp, *pDispGraphics))
            aDispBmp.ImplSetSalBitmap(xImpDispBmp);
    }
 
    return aDispBmp;
}
 
bool Bitmap::GetSystemData( BitmapSystemData& rData ) const
{
    return mxSalBmp && mxSalBmp->GetSystemData(rData);
}
 
 
bool Bitmap::Convert( BmpConversion eConversion )
{
    // try to convert in backend
    if (mxSalBmp)
    {
        // avoid large chunk of obsolete and hopefully rarely used conversions.
        if (eConversion == BmpConversion::N8BitNoConversion)
        {
            if (mxSalBmp->GetBitCount() == 8 && HasGreyPalette8Bit())
                return true;
            std::shared_ptr<SalBitmap> xImpBmp(ImplGetSVData()->mpDefInst->CreateSalBitmap());
            // frequently used conversion for creating alpha masks
            if (xImpBmp->Create(*mxSalBmp) && xImpBmp->InterpretAs8Bit())
            {
                ImplSetSalBitmap(xImpBmp);
                SAL_INFO( "vcl.opengl", "Ref count: " << mxSalBmp.use_count() );
                return true;
            }
        }
        if (eConversion == BmpConversion::N8BitGreys)
        {
            std::shared_ptr<SalBitmap> xImpBmp(ImplGetSVData()->mpDefInst->CreateSalBitmap());
            if (xImpBmp->Create(*mxSalBmp) && xImpBmp->ConvertToGreyscale())
            {
                ImplSetSalBitmap(xImpBmp);
                SAL_INFO( "vcl.opengl", "Ref count: " << mxSalBmp.use_count() );
                return true;
            }
        }
    }
 
    const sal_uInt16 nBitCount = vcl::pixelFormatBitCount(getPixelFormat());
    bool bRet = false;
 
    switch( eConversion )
    {
        case BmpConversion::N1BitThreshold:
            bRet = BitmapFilter::Filter(*this, BitmapMonochromeFilter(128));
        break;
 
        case BmpConversion::N8BitGreys:
            bRet = ImplMakeGreyscales();
        break;
 
        case BmpConversion::N8BitNoConversion:
            bRet = ImplMake8BitNoConversion();
        break;
 
        case BmpConversion::N8BitColors:
        {
            if( nBitCount < 8 )
                bRet = ImplConvertUp(vcl::PixelFormat::N8_BPP);
            else if( nBitCount > 8 )
                bRet = ImplConvertDown8BPP();
            else
                bRet = true;
        }
        break;
 
        case BmpConversion::N8BitTrans:
        {
            Color aTrans( BMP_COL_TRANS );
 
            if( nBitCount < 8 )
                bRet = ImplConvertUp(vcl::PixelFormat::N8_BPP, &aTrans );
            else
                bRet = ImplConvertDown8BPP(&aTrans );
        }
        break;
 
        case BmpConversion::N24Bit:
        {
            if( nBitCount < 24 )
                bRet = ImplConvertUp(vcl::PixelFormat::N24_BPP);
            else
                bRet = true;
        }
        break;
 
        case BmpConversion::N32Bit:
        {
            // check for alpha, because even if we are a 32-bit format, we might not be a 32-bit format with alpha
            if( !HasAlpha() )
                bRet = ImplConvertUp(vcl::PixelFormat::N32_BPP);
            else
                bRet = true;
        }
        break;
 
        default:
            OSL_FAIL( "Bitmap::Convert(): Unsupported conversion" );
        break;
    }
 
    return bRet;
}
 
bool Bitmap::ImplMakeGreyscales()
{
    BitmapScopedReadAccess pReadAcc(*this);
    if( !pReadAcc )
        return false;
 
    const BitmapPalette& rPal = GetGreyPalette(256);
    sal_uLong nShift = 0;
    bool bPalDiffers = !pReadAcc->HasPalette() || ( rPal.GetEntryCount() != pReadAcc->GetPaletteEntryCount() );
 
    if( !bPalDiffers )
        bPalDiffers = ( rPal != pReadAcc->GetPalette() );
    if( !bPalDiffers )
        return true;
 
    const auto ePixelFormat = vcl::PixelFormat::N8_BPP;
    Bitmap aNewBmp(GetSizePixel(), ePixelFormat, &rPal );
    BitmapScopedWriteAccess pWriteAcc(aNewBmp);
    if( !pWriteAcc )
        return false;
 
    const tools::Long nWidth = pWriteAcc->Width();
    const tools::Long nHeight = pWriteAcc->Height();
 
    if( pReadAcc->HasPalette() )
    {
        for( tools::Long nY = 0; nY < nHeight; nY++ )
        {
            Scanline pScanline = pWriteAcc->GetScanline(nY);
            Scanline pScanlineRead = pReadAcc->GetScanline(nY);
            for( tools::Long nX = 0; nX < nWidth; nX++ )
            {
                const sal_uInt8 cIndex = pReadAcc->GetIndexFromData( pScanlineRead, nX );
                pWriteAcc->SetPixelOnData( pScanline, nX,
                    BitmapColor(pReadAcc->GetPaletteColor( cIndex ).GetLuminance() >> nShift) );
            }
        }
    }
    else if( pReadAcc->GetScanlineFormat() == ScanlineFormat::N24BitTcBgr &&
             pWriteAcc->GetScanlineFormat() == ScanlineFormat::N8BitPal )
    {
        nShift += 8;
 
        for( tools::Long nY = 0; nY < nHeight; nY++ )
        {
            Scanline pReadScan = pReadAcc->GetScanline( nY );
            Scanline pWriteScan = pWriteAcc->GetScanline( nY );
 
            for( tools::Long nX = 0; nX < nWidth; nX++ )
            {
                const sal_uLong nB = *pReadScan++;
                const sal_uLong nG = *pReadScan++;
                const sal_uLong nR = *pReadScan++;
 
                *pWriteScan++ = static_cast<sal_uInt8>( ( nB * 28UL + nG * 151UL + nR * 77UL ) >> nShift );
            }
        }
    }
    else if( pReadAcc->GetScanlineFormat() == ScanlineFormat::N24BitTcRgb &&
             pWriteAcc->GetScanlineFormat() == ScanlineFormat::N8BitPal )
    {
        nShift += 8;
 
        for( tools::Long nY = 0; nY < nHeight; nY++ )
        {
            Scanline pReadScan = pReadAcc->GetScanline( nY );
            Scanline pWriteScan = pWriteAcc->GetScanline( nY );
 
            for( tools::Long nX = 0; nX < nWidth; nX++ )
            {
                const sal_uLong nR = *pReadScan++;
                const sal_uLong nG = *pReadScan++;
                const sal_uLong nB = *pReadScan++;
 
                *pWriteScan++ = static_cast<sal_uInt8>( ( nB * 28UL + nG * 151UL + nR * 77UL ) >> nShift );
            }
        }
    }
    else
    {
        for( tools::Long nY = 0; nY < nHeight; nY++ )
        {
            Scanline pScanline = pWriteAcc->GetScanline(nY);
            Scanline pScanlineRead = pReadAcc->GetScanline(nY);
            for( tools::Long nX = 0; nX < nWidth; nX++ )
                pWriteAcc->SetPixelOnData( pScanline, nX, BitmapColor(pReadAcc->GetPixelFromData( pScanlineRead, nX ).GetLuminance() >> nShift) );
        }
    }
 
    pWriteAcc.reset();
    pReadAcc.reset();
 
    const MapMode aMap( maPrefMapMode );
    const Size aSize( maPrefSize );
 
    *this = std::move(aNewBmp);
 
    maPrefMapMode = aMap;
    maPrefSize = aSize;
 
    return true;
}
 
// Used for the bitmap->alpha layer conversion, just takes the red channel
bool Bitmap::ImplMake8BitNoConversion()
{
    BitmapScopedReadAccess pReadAcc(*this);
    if (!pReadAcc)
        return false;
 
    const BitmapPalette& rPal = GetGreyPalette(256);
    bool bPalDiffers
        = !pReadAcc->HasPalette() || (rPal.GetEntryCount() != pReadAcc->GetPaletteEntryCount());
 
    if (!bPalDiffers)
        bPalDiffers = (rPal != pReadAcc->GetPalette());
    if (!bPalDiffers)
        return true;
 
    const auto ePixelFormat = vcl::PixelFormat::N8_BPP;
    Bitmap aNewBmp(GetSizePixel(), ePixelFormat, &rPal);
    BitmapScopedWriteAccess pWriteAcc(aNewBmp);
    if (!pWriteAcc)
        return false;
 
    const tools::Long nWidth = pWriteAcc->Width();
    const tools::Long nHeight = pWriteAcc->Height();
 
    if (pReadAcc->HasPalette())
    {
        for (tools::Long nY = 0; nY < nHeight; nY++)
        {
            Scanline pScanline = pWriteAcc->GetScanline(nY);
            Scanline pScanlineRead = pReadAcc->GetScanline(nY);
            for (tools::Long nX = 0; nX < nWidth; nX++)
            {
                const sal_uInt8 cIndex = pReadAcc->GetIndexFromData(pScanlineRead, nX);
                pWriteAcc->SetPixelOnData(
                    pScanline, nX,
                    BitmapColor(pReadAcc->GetPaletteColor(cIndex).GetRed()));
            }
        }
    }
    else if (pReadAcc->GetScanlineFormat() == ScanlineFormat::N24BitTcBgr
             && pWriteAcc->GetScanlineFormat() == ScanlineFormat::N8BitPal)
    {
        for (tools::Long nY = 0; nY < nHeight; nY++)
        {
            Scanline pReadScan = pReadAcc->GetScanline(nY);
            Scanline pWriteScan = pWriteAcc->GetScanline(nY);
 
            for (tools::Long nX = 0; nX < nWidth; nX++)
            {
                pReadScan++;
                pReadScan++;
                const sal_uLong nR = *pReadScan++;
 
                *pWriteScan++ = static_cast<sal_uInt8>(nR);
            }
        }
    }
    else if (pReadAcc->GetScanlineFormat() == ScanlineFormat::N24BitTcRgb
             && pWriteAcc->GetScanlineFormat() == ScanlineFormat::N8BitPal)
    {
        for (tools::Long nY = 0; nY < nHeight; nY++)
        {
            Scanline pReadScan = pReadAcc->GetScanline(nY);
            Scanline pWriteScan = pWriteAcc->GetScanline(nY);
 
            for (tools::Long nX = 0; nX < nWidth; nX++)
            {
                const sal_uLong nR = *pReadScan++;
                pReadScan++;
                pReadScan++;
 
                *pWriteScan++ = static_cast<sal_uInt8>(nR);
            }
        }
    }
    else
    {
        for (tools::Long nY = 0; nY < nHeight; nY++)
        {
            Scanline pScanline = pWriteAcc->GetScanline(nY);
            Scanline pScanlineRead = pReadAcc->GetScanline(nY);
            for (tools::Long nX = 0; nX < nWidth; nX++)
                pWriteAcc->SetPixelOnData(
                    pScanline, nX,
                    BitmapColor(pReadAcc->GetPixelFromData(pScanlineRead, nX).GetRed()));
        }
    }
 
    pWriteAcc.reset();
    pReadAcc.reset();
 
    const MapMode aMap(maPrefMapMode);
    const Size aSize(maPrefSize);
 
    *this = std::move(aNewBmp);
 
    maPrefMapMode = aMap;
    maPrefSize = aSize;
 
    return true;
}
 
bool Bitmap::ImplConvertUp(vcl::PixelFormat ePixelFormat, Color const* pExtColor)
{
    SAL_WARN_IF(ePixelFormat <= getPixelFormat(), "vcl", "New pixel format must be greater!" );
 
    BitmapScopedReadAccess pReadAcc(*this);
    if (!pReadAcc)
        return false;
 
    BitmapPalette aPalette;
    Bitmap aNewBmp(GetSizePixel(), ePixelFormat, pReadAcc->HasPalette() ? &pReadAcc->GetPalette() : &aPalette);
    BitmapScopedWriteAccess pWriteAcc(aNewBmp);
    if (!pWriteAcc)
        return false;
 
    const tools::Long nWidth = pWriteAcc->Width();
    const tools::Long nHeight = pWriteAcc->Height();
 
    if (pWriteAcc->HasPalette())
    {
        const BitmapPalette& rOldPalette = pReadAcc->GetPalette();
        const sal_uInt16 nOldCount = rOldPalette.GetEntryCount();
        assert(nOldCount <= (1 << vcl::pixelFormatBitCount(getPixelFormat())));
 
        aPalette.SetEntryCount(1 << vcl::pixelFormatBitCount(ePixelFormat));
 
        for (sal_uInt16 i = 0; i < nOldCount; i++)
            aPalette[i] = rOldPalette[i];
 
        if (pExtColor)
            aPalette[aPalette.GetEntryCount() - 1] = *pExtColor;
 
        pWriteAcc->SetPalette(aPalette);
 
        for (tools::Long nY = 0; nY < nHeight; nY++)
        {
            Scanline pScanline = pWriteAcc->GetScanline(nY);
            Scanline pScanlineRead = pReadAcc->GetScanline(nY);
            for (tools::Long nX = 0; nX < nWidth; nX++)
            {
                pWriteAcc->SetPixelOnData(pScanline, nX, pReadAcc->GetPixelFromData(pScanlineRead, nX));
            }
        }
    }
    else
    {
        if (pReadAcc->HasPalette())
        {
            for (tools::Long nY = 0; nY < nHeight; nY++)
            {
                Scanline pScanline = pWriteAcc->GetScanline(nY);
                Scanline pScanlineRead = pReadAcc->GetScanline(nY);
                for (tools::Long nX = 0; nX < nWidth; nX++)
                {
                    pWriteAcc->SetPixelOnData(pScanline, nX, pReadAcc->GetPaletteColor(pReadAcc->GetIndexFromData(pScanlineRead, nX)));
                }
            }
        }
        else
        {
            for (tools::Long nY = 0; nY < nHeight; nY++)
            {
                Scanline pScanline = pWriteAcc->GetScanline(nY);
                Scanline pScanlineRead = pReadAcc->GetScanline(nY);
                for (tools::Long nX = 0; nX < nWidth; nX++)
                {
                    pWriteAcc->SetPixelOnData(pScanline, nX, pReadAcc->GetPixelFromData(pScanlineRead, nX));
                }
            }
        }
    }
 
    const MapMode aMap(maPrefMapMode);
    const Size aSize(maPrefSize);
 
    *this = std::move(aNewBmp);
 
    maPrefMapMode = aMap;
    maPrefSize = aSize;
 
    return true;
}
 
bool Bitmap::ImplConvertDown8BPP(Color const * pExtColor)
{
    SAL_WARN_IF(vcl::PixelFormat::N8_BPP > getPixelFormat(), "vcl", "New pixelformat must be lower ( or equal when pExtColor is set )!");
 
    BitmapScopedReadAccess pReadAcc(*this);
    if (!pReadAcc)
        return false;
 
    BitmapPalette aPalette;
    Bitmap aNewBmp(GetSizePixel(), vcl::PixelFormat::N8_BPP, &aPalette);
    BitmapScopedWriteAccess pWriteAcc(aNewBmp);
    if (!pWriteAcc)
        return false;
 
    sal_Int16 nNewBitCount = sal_Int16(vcl::PixelFormat::N8_BPP);
    const sal_uInt16 nCount = 1 << nNewBitCount;
    const tools::Long nWidth = pWriteAcc->Width();
    const tools::Long nWidth1 = nWidth - 1;
    const tools::Long nHeight = pWriteAcc->Height();
    Octree aOctree(*pReadAcc, pExtColor ? (nCount - 1) : nCount);
    aPalette = aOctree.GetPalette();
    InverseColorMap aColorMap(aPalette);
    BitmapColor aColor;
    ImpErrorQuad aErrQuad;
    std::vector<ImpErrorQuad> aErrQuad1(nWidth);
    std::vector<ImpErrorQuad> aErrQuad2(nWidth);
    ImpErrorQuad* pQLine1 = aErrQuad1.data();
    ImpErrorQuad* pQLine2 = nullptr;
    tools::Long nYTmp = 0;
    sal_uInt8 cIndex;
    bool bQ1 = true;
 
    if (pExtColor)
    {
        aPalette.SetEntryCount(aPalette.GetEntryCount() + 1);
        aPalette[aPalette.GetEntryCount() - 1] = *pExtColor;
    }
 
    // set Black/White always, if we have enough space
    if (aPalette.GetEntryCount() < (nCount - 1))
    {
        aPalette.SetEntryCount(aPalette.GetEntryCount() + 2);
        aPalette[aPalette.GetEntryCount() - 2] = COL_BLACK;
        aPalette[aPalette.GetEntryCount() - 1] = COL_WHITE;
    }
 
    pWriteAcc->SetPalette(aPalette);
 
    for (tools::Long nY = 0; nY < std::min(nHeight, tools::Long(2)); nY++, nYTmp++)
    {
        pQLine2 = !nY ? aErrQuad1.data() : aErrQuad2.data();
        Scanline pScanlineRead = pReadAcc->GetScanline(nYTmp);
        for (tools::Long nX = 0; nX < nWidth; nX++)
        {
            if (pReadAcc->HasPalette())
                pQLine2[nX] = pReadAcc->GetPaletteColor(pReadAcc->GetIndexFromData(pScanlineRead, nX));
            else
                pQLine2[nX] = pReadAcc->GetPixelFromData(pScanlineRead, nX);
        }
    }
 
    assert(pQLine2 || nHeight == 0);
 
    for (tools::Long nY = 0; nY < nHeight; nY++, nYTmp++)
    {
        // first pixel in the line
        cIndex = aColorMap.GetBestPaletteIndex(pQLine1[0].ImplGetColor());
        Scanline pScanline = pWriteAcc->GetScanline(nY);
        pWriteAcc->SetPixelOnData(pScanline, 0, BitmapColor(cIndex));
 
        tools::Long nX;
        for (nX = 1; nX < nWidth1; nX++)
        {
            aColor = pQLine1[nX].ImplGetColor();
            cIndex = aColorMap.GetBestPaletteIndex(aColor);
            aErrQuad = (ImpErrorQuad(aColor) -= pWriteAcc->GetPaletteColor(cIndex));
            pQLine1[++nX].ImplAddColorError7(aErrQuad);
            pQLine2[nX--].ImplAddColorError1(aErrQuad);
            pQLine2[nX--].ImplAddColorError5(aErrQuad);
            pQLine2[nX++].ImplAddColorError3(aErrQuad);
            pWriteAcc->SetPixelOnData(pScanline, nX, BitmapColor(cIndex));
        }
 
        // Last RowPixel
        if (nX < nWidth)
        {
            cIndex = aColorMap.GetBestPaletteIndex(pQLine1[nWidth1].ImplGetColor());
            pWriteAcc->SetPixelOnData(pScanline, nX, BitmapColor(cIndex));
        }
 
        // Refill/copy row buffer
        pQLine1 = pQLine2;
        bQ1 = !bQ1;
        pQLine2 = bQ1 ? aErrQuad2.data() : aErrQuad1.data();
 
        if (nYTmp < nHeight)
        {
            Scanline pScanlineRead = pReadAcc->GetScanline(nYTmp);
            for (nX = 0; nX < nWidth; nX++)
            {
                if (pReadAcc->HasPalette())
                    pQLine2[nX] = pReadAcc->GetPaletteColor(pReadAcc->GetIndexFromData(pScanlineRead, nX));
                else
                    pQLine2[nX] = pReadAcc->GetPixelFromData(pScanlineRead, nX);
            }
        }
    }
 
    pWriteAcc.reset();
 
    const MapMode aMap(maPrefMapMode);
    const Size aSize(maPrefSize);
 
    *this = std::move(aNewBmp);
 
    maPrefMapMode = aMap;
    maPrefSize = aSize;
 
    return true;
}
 
bool Bitmap::Scale( const double& rScaleX, const double& rScaleY, BmpScaleFlag nScaleFlag )
{
    if(basegfx::fTools::equalZero(rScaleX) || basegfx::fTools::equalZero(rScaleY))
    {
        // no scale
        return true;
    }
 
    if(basegfx::fTools::equal(rScaleX, 1.0) && basegfx::fTools::equal(rScaleY, 1.0))
    {
        // no scale
        return true;
    }
 
    const auto eStartPixelFormat = getPixelFormat();
 
    if (mxSalBmp && mxSalBmp->ScalingSupported())
    {
        // implementation specific scaling
        std::shared_ptr<SalBitmap> xImpBmp(ImplGetSVData()->mpDefInst->CreateSalBitmap());
        if (xImpBmp->Create(*mxSalBmp) && xImpBmp->Scale(rScaleX, rScaleY, nScaleFlag))
        {
            ImplSetSalBitmap(xImpBmp);
            SAL_INFO( "vcl.opengl", "Ref count: " << mxSalBmp.use_count() );
            maPrefMapMode = MapMode( MapUnit::MapPixel );
            maPrefSize = xImpBmp->GetSize();
            return true;
        }
    }
 
    bool bRetval(false);
 
    switch(nScaleFlag)
    {
        case BmpScaleFlag::Default:
            if (GetSizePixel().Width() < 2 || GetSizePixel().Height() < 2)
                bRetval = BitmapFilter::Filter(*this, BitmapFastScaleFilter(rScaleX, rScaleY));
            else
                bRetval = BitmapFilter::Filter(*this, BitmapScaleSuperFilter(rScaleX, rScaleY));
            break;
 
        case BmpScaleFlag::Fast:
        case BmpScaleFlag::NearestNeighbor:
            bRetval = BitmapFilter::Filter(*this, BitmapFastScaleFilter(rScaleX, rScaleY));
            break;
 
        case BmpScaleFlag::Interpolate:
            bRetval = BitmapFilter::Filter(*this, BitmapInterpolateScaleFilter(rScaleX, rScaleY));
            break;
 
        case BmpScaleFlag::BestQuality:
        case BmpScaleFlag::Lanczos:
            bRetval = BitmapFilter::Filter(*this, vcl::BitmapScaleLanczos3Filter(rScaleX, rScaleY));
            break;
 
        case BmpScaleFlag::BiCubic:
            bRetval = BitmapFilter::Filter(*this, vcl::BitmapScaleBicubicFilter(rScaleX, rScaleY));
            break;
 
        case BmpScaleFlag::BiLinear:
            bRetval = BitmapFilter::Filter(*this, vcl::BitmapScaleBilinearFilter(rScaleX, rScaleY));
            break;
    }
 
    OSL_ENSURE(!bRetval || eStartPixelFormat == getPixelFormat(), "Bitmap::Scale has changed the ColorDepth, this should *not* happen (!)");
    return bRetval;
}
 
bool Bitmap::Scale( const Size& rNewSize, BmpScaleFlag nScaleFlag )
{
    const Size aSize( GetSizePixel() );
    bool bRet;
 
    if( aSize.Width() && aSize.Height() )
    {
        bRet = Scale( static_cast<double>(rNewSize.Width()) / aSize.Width(),
                      static_cast<double>(rNewSize.Height()) / aSize.Height(),
                      nScaleFlag );
    }
    else
        bRet = true;
 
    return bRet;
}
 
bool Bitmap::HasFastScale()
{
#if HAVE_FEATURE_SKIA
    if( SkiaHelper::isVCLSkiaEnabled() && SkiaHelper::renderMethodToUse() != SkiaHelper::RenderRaster)
        return true;
#endif
    return false;
}
 
void Bitmap::AdaptBitCount(Bitmap& rNew) const
{
    // aNew is the result of some operation; adapt it's BitCount to the original (this)
    // Also check HasAlpha() in case we are dealing with one of the 32-bit formats
    // without an alpha channel.
    if (getPixelFormat() == rNew.getPixelFormat() && HasAlpha() == rNew.HasAlpha())
        return;
 
    switch (getPixelFormat())
    {
        case vcl::PixelFormat::N8_BPP:
        {
            if(HasGreyPaletteAny())
            {
                rNew.Convert(BmpConversion::N8BitGreys);
            }
            else
            {
                rNew.Convert(BmpConversion::N8BitColors);
            }
            break;
        }
        case vcl::PixelFormat::N24_BPP:
        {
            rNew.Convert(BmpConversion::N24Bit);
            break;
        }
        case vcl::PixelFormat::N32_BPP:
        {
            rNew.Convert(BmpConversion::N32Bit);
            break;
        }
        case vcl::PixelFormat::INVALID:
        {
            SAL_WARN("vcl", "Can't adapt the pixelformat as it is invalid.");
            break;
        }
    }
}
 
static void shiftColors(sal_Int32* pColorArray, const BitmapScopedReadAccess& pReadAcc)
{
    Scanline pScanlineRead = pReadAcc->GetScanline(0); // Why always 0?
    for (tools::Long n = 0, nWidth = pReadAcc->Width(); n < nWidth; ++n)
    {
        const BitmapColor aColor = pReadAcc->GetColorFromData(pScanlineRead, n);
        *pColorArray++ = static_cast<sal_Int32>(aColor.GetBlue()) << 12;
        *pColorArray++ = static_cast<sal_Int32>(aColor.GetGreen()) << 12;
        *pColorArray++ = static_cast<sal_Int32>(aColor.GetRed()) << 12;
    }
}
 
bool Bitmap::Dither()
{
    const Size aSize( GetSizePixel() );
    if( aSize.Width() == 1 || aSize.Height() == 1 )
        return true;
    if( ( aSize.Width() <= 3 ) || ( aSize.Height() <= 2 ) )
        return false;
 
    BitmapScopedReadAccess pReadAcc(*this);
    Bitmap aNewBmp(GetSizePixel(), vcl::PixelFormat::N8_BPP);
    BitmapScopedWriteAccess pWriteAcc(aNewBmp);
    if( !pReadAcc || !pWriteAcc )
        return false;
 
    tools::Long nWidth = pReadAcc->Width();
    tools::Long nWidth1 = nWidth - 1;
    tools::Long nHeight = pReadAcc->Height();
    tools::Long nW = nWidth * 3;
    tools::Long nW2 = nW - 3;
    std::unique_ptr<sal_Int32[]> p1(new sal_Int32[ nW ]);
    std::unique_ptr<sal_Int32[]> p2(new sal_Int32[ nW ]);
    sal_Int32* p1T = p1.get();
    sal_Int32* p2T = p2.get();
    shiftColors(p2T, pReadAcc);
    for( tools::Long nYAcc = 0; nYAcc < nHeight; nYAcc++ )
    {
        std::swap(p1T, p2T);
        if (nYAcc < nHeight - 1)
            shiftColors(p2T, pReadAcc);
 
        auto CalcError = [](tools::Long n)
        {
            n = std::clamp<tools::Long>(n >> 12, 0, 255);
            return std::pair(FloydErrMap[n], FloydMap[n]);
        };
 
        auto CalcErrors = [&](tools::Long n)
        { return std::tuple_cat(CalcError(p1T[n]), CalcError(p1T[n + 1]), CalcError(p1T[n + 2])); };
 
        auto CalcT = [](sal_Int32* dst, const int* src, int b, int g, int r)
        {
            dst[0] += src[b];
            dst[1] += src[g];
            dst[2] += src[r];
        };
 
        auto Calc1 = [&](int x, int b, int g, int r) { CalcT(p2T + x + 3, FloydError1, b, g, r); };
        auto Calc3 = [&](int x, int b, int g, int r) { CalcT(p2T + x - 3, FloydError3, b, g, r); };
        auto Calc5 = [&](int x, int b, int g, int r) { CalcT(p2T + x, FloydError5, b, g, r); };
        auto Calc7 = [&](int x, int b, int g, int r) { CalcT(p1T + x + 3, FloydError7, b, g, r); };
 
        Scanline pScanline = pWriteAcc->GetScanline(nYAcc);
        // Examine first Pixel separately
        {
            auto [nBErr, nBC, nGErr, nGC, nRErr, nRC] = CalcErrors(0);
            Calc1(0, nBErr, nGErr, nRErr);
            Calc5(0, nBErr, nGErr, nRErr);
            Calc7(0, nBErr, nGErr, nRErr);
            pWriteAcc->SetPixelOnData( pScanline, 0, BitmapColor(static_cast<sal_uInt8>(nVCLBLut[ nBC ] + nVCLGLut[nGC ] + nVCLRLut[nRC ])) );
        }
        // Get middle Pixels using a loop
        for ( tools::Long nX = 3, nXAcc = 1; nX < nW2; nX += 3, nXAcc++ )
        {
            auto [nBErr, nBC, nGErr, nGC, nRErr, nRC] = CalcErrors(nX);
            Calc1(nX, nBErr, nGErr, nRErr);
            Calc3(nX, nBErr, nGErr, nRErr);
            Calc5(nX, nBErr, nGErr, nRErr);
            Calc7(nX, nBErr, nGErr, nRErr);
            pWriteAcc->SetPixelOnData( pScanline, nXAcc, BitmapColor(static_cast<sal_uInt8>(nVCLBLut[ nBC ] + nVCLGLut[nGC ] + nVCLRLut[nRC ])) );
        }
        // Treat last Pixel separately
        {
            auto [nBErr, nBC, nGErr, nGC, nRErr, nRC] = CalcErrors(nW2);
            Calc3(nW2, nBErr, nGErr, nRErr);
            Calc5(nW2, nBErr, nGErr, nRErr);
            pWriteAcc->SetPixelOnData( pScanline, nWidth1, BitmapColor(static_cast<sal_uInt8>(nVCLBLut[ nBC ] + nVCLGLut[nGC ] + nVCLRLut[nRC ])) );
        }
    }
    pReadAcc.reset();
    pWriteAcc.reset();
    const MapMode aMap( maPrefMapMode );
    const Size aPrefSize( maPrefSize );
    *this = std::move(aNewBmp);
    maPrefMapMode = aMap;
    maPrefSize = aPrefSize;
    return true;
}
 
bool Bitmap::Adjust( short nLuminancePercent, short nContrastPercent,
                     short nChannelRPercent, short nChannelGPercent, short nChannelBPercent,
                     double fGamma, bool bInvert, bool msoBrightness )
{
    // nothing to do => return quickly
    if( !nLuminancePercent && !nContrastPercent &&
        !nChannelRPercent && !nChannelGPercent && !nChannelBPercent &&
        ( fGamma == 1.0 ) && !bInvert )
    {
        return true;
    }
 
    BitmapScopedWriteAccess pAcc(*this);
    if( !pAcc )
        return false;
 
    BitmapColor aCol;
    const tools::Long nW = pAcc->Width();
    const tools::Long nH = pAcc->Height();
    std::unique_ptr<sal_uInt8[]> cMapR(new sal_uInt8[ 256 ]);
    std::unique_ptr<sal_uInt8[]> cMapG(new sal_uInt8[ 256 ]);
    std::unique_ptr<sal_uInt8[]> cMapB(new sal_uInt8[ 256 ]);
    double fM, fROff, fGOff, fBOff, fOff;
 
    // calculate slope
    if( nContrastPercent >= 0 )
        fM = 128.0 / ( 128.0 - 1.27 * std::clamp( nContrastPercent, short(0), short(100) ) );
    else
        fM = ( 128.0 + 1.27 * std::clamp( nContrastPercent, short(-100), short(0) ) ) / 128.0;
 
    if(!msoBrightness)
        // total offset = luminance offset + contrast offset
        fOff = std::clamp( nLuminancePercent, short(-100), short(100) ) * 2.55 + 128.0 - fM * 128.0;
    else
        fOff = std::clamp( nLuminancePercent, short(-100), short(100) ) * 2.55;
 
    // channel offset = channel offset + total offset
    fROff = nChannelRPercent * 2.55 + fOff;
    fGOff = nChannelGPercent * 2.55 + fOff;
    fBOff = nChannelBPercent * 2.55 + fOff;
 
    // calculate gamma value
    fGamma = ( fGamma <= 0.0 || fGamma > 10.0 ) ? 1.0 : ( 1.0 / fGamma );
    const bool bGamma = ( fGamma != 1.0 );
 
    // create mapping table
    for( tools::Long nX = 0; nX < 256; nX++ )
    {
        if(!msoBrightness)
        {
            cMapR[nX] = basegfx::fround<sal_uInt8>(nX * fM + fROff);
            cMapG[nX] = basegfx::fround<sal_uInt8>(nX * fM + fGOff);
            cMapB[nX] = basegfx::fround<sal_uInt8>(nX * fM + fBOff);
        }
        else
        {
            // LO simply uses (in a somewhat optimized form) "newcolor = (oldcolor-128)*contrast+brightness+128"
            // as the formula, i.e. contrast first, brightness afterwards. MSOffice, for whatever weird reason,
            // use neither first, but apparently it applies half of brightness before contrast and half afterwards.
            cMapR[nX] = basegfx::fround<sal_uInt8>((nX + fROff / 2 - 128) * fM + 128 + fROff / 2);
            cMapG[nX] = basegfx::fround<sal_uInt8>((nX + fGOff / 2 - 128) * fM + 128 + fGOff / 2);
            cMapB[nX] = basegfx::fround<sal_uInt8>((nX + fBOff / 2 - 128) * fM + 128 + fBOff / 2);
        }
        if( bGamma )
        {
            cMapR[ nX ] = GAMMA( cMapR[ nX ], fGamma );
            cMapG[ nX ] = GAMMA( cMapG[ nX ], fGamma );
            cMapB[ nX ] = GAMMA( cMapB[ nX ], fGamma );
        }
 
        if( bInvert )
        {
            cMapR[ nX ] = ~cMapR[ nX ];
            cMapG[ nX ] = ~cMapG[ nX ];
            cMapB[ nX ] = ~cMapB[ nX ];
        }
    }
 
    // do modifying
    if( pAcc->HasPalette() )
    {
        BitmapColor aNewCol;
 
        for( sal_uInt16 i = 0, nCount = pAcc->GetPaletteEntryCount(); i < nCount; i++ )
        {
            const BitmapColor& rCol = pAcc->GetPaletteColor( i );
            aNewCol.SetRed( cMapR[ rCol.GetRed() ] );
            aNewCol.SetGreen( cMapG[ rCol.GetGreen() ] );
            aNewCol.SetBlue( cMapB[ rCol.GetBlue() ] );
            pAcc->SetPaletteColor( i, aNewCol );
        }
    }
    else if( pAcc->GetScanlineFormat() == ScanlineFormat::N24BitTcBgr )
    {
        for( tools::Long nY = 0; nY < nH; nY++ )
        {
            Scanline pScan = pAcc->GetScanline( nY );
 
            for( tools::Long nX = 0; nX < nW; nX++ )
            {
                *pScan = cMapB[ *pScan ]; pScan++;
                *pScan = cMapG[ *pScan ]; pScan++;
                *pScan = cMapR[ *pScan ]; pScan++;
            }
        }
    }
    else if( pAcc->GetScanlineFormat() == ScanlineFormat::N24BitTcRgb )
    {
        for( tools::Long nY = 0; nY < nH; nY++ )
        {
            Scanline pScan = pAcc->GetScanline( nY );
 
            for( tools::Long nX = 0; nX < nW; nX++ )
            {
                *pScan = cMapR[ *pScan ]; pScan++;
                *pScan = cMapG[ *pScan ]; pScan++;
                *pScan = cMapB[ *pScan ]; pScan++;
            }
        }
    }
    else
    {
        for( tools::Long nY = 0; nY < nH; nY++ )
        {
            Scanline pScanline = pAcc->GetScanline(nY);
            for( tools::Long nX = 0; nX < nW; nX++ )
            {
                aCol = pAcc->GetPixelFromData( pScanline, nX );
                aCol.SetRed( cMapR[ aCol.GetRed() ] );
                aCol.SetGreen( cMapG[ aCol.GetGreen() ] );
                aCol.SetBlue( cMapB[ aCol.GetBlue() ] );
                pAcc->SetPixelOnData( pScanline, nX, aCol );
            }
        }
    }
 
    pAcc.reset();
 
    return true;
}
 
namespace
{
inline sal_uInt8 backBlendAlpha(sal_uInt16 alpha, sal_uInt16 srcCol, sal_uInt16 startCol)
{
    const sal_uInt16 nAlpha((alpha * startCol) / 255);
    if(srcCol > nAlpha)
    {
        return static_cast<sal_uInt8>(((srcCol - nAlpha) * 255) / (255 - nAlpha));
    }
 
    return 0;
}
}
 
void Bitmap::RemoveBlendedStartColor(
    const Color& rStartColor,
    const AlphaMask& rAlphaMask)
{
    // no content, done
    if(IsEmpty())
        return;
 
    BitmapScopedWriteAccess pAcc(*this);
    const tools::Long nHeight(pAcc->Height());
    const tools::Long nWidth(pAcc->Width());
 
    // no content, done
    if(0 == nHeight || 0 == nWidth)
        return;
 
    BitmapScopedReadAccess pAlphaAcc(rAlphaMask);
 
    // inequal sizes of content and alpha, avoid change (maybe assert?)
    if(pAlphaAcc->Height() != nHeight || pAlphaAcc->Width() != nWidth)
        return;
 
    // prepare local values as sal_uInt16 to avoid multiple conversions
    const sal_uInt16 nStartColRed(rStartColor.GetRed());
    const sal_uInt16 nStartColGreen(rStartColor.GetGreen());
    const sal_uInt16 nStartColBlue(rStartColor.GetBlue());
 
    for (tools::Long y = 0; y < nHeight; ++y)
    {
        for (tools::Long x = 0; x < nWidth; ++x)
        {
            // get alpha value
            const sal_uInt8 nAlpha8(pAlphaAcc->GetColor(y, x).GetRed());
 
            // not or completely transparent, no adaptation needed
            if(0 == nAlpha8 || 255 == nAlpha8)
                continue;
 
            // prepare local value as sal_uInt16 to avoid multiple conversions
            const sal_uInt16 nAlpha16(static_cast<sal_uInt16>(nAlpha8));
 
            // get source color
            BitmapColor aColor(pAcc->GetColor(y, x));
 
            // modify/blend back source color
            aColor.SetRed(backBlendAlpha(nAlpha16, static_cast<sal_uInt16>(aColor.GetRed()), nStartColRed));
            aColor.SetGreen(backBlendAlpha(nAlpha16, static_cast<sal_uInt16>(aColor.GetGreen()), nStartColGreen));
            aColor.SetBlue(backBlendAlpha(nAlpha16, static_cast<sal_uInt16>(aColor.GetBlue()), nStartColBlue));
 
            // write result back
            pAcc->SetPixel(y, x, aColor);
        }
    }
}
 
const basegfx::SystemDependentDataHolder* Bitmap::accessSystemDependentDataHolder() const
{
    return mxSalBmp.get();
}
 
std::pair<Bitmap, AlphaMask> Bitmap::SplitIntoColorAndAlpha() const
{
    assert(HasAlpha() && "only valid to call this when this is a 32-bit combined color+alpha bitmap");
    Bitmap aColorBmp(GetSizePixel(), vcl::PixelFormat::N24_BPP);
    aColorBmp.SetPrefSize(GetPrefSize());
    aColorBmp.SetPrefMapMode(GetPrefMapMode());
    AlphaMask aAlphaBmp(GetSizePixel());
    aAlphaBmp.SetPrefMapMode(GetPrefMapMode());
 
    // We will probably need to make this more efficient by pushing it down to the *SalBitmap implementations,
    // but for now, do the simple and safe thing.
    {
        BitmapScopedReadAccess pThisAcc(*this);
        BitmapScopedWriteAccess pColorAcc(aColorBmp);
        BitmapScopedWriteAccess pAlphaAcc(aAlphaBmp);
 
        const tools::Long nHeight(pThisAcc->Height());
        const tools::Long nWidth(pThisAcc->Width());
 
        for (tools::Long y = 0; y < nHeight; ++y)
        {
            Scanline pScanlineRead = pThisAcc->GetScanline( y );
            Scanline pScanlineColor = pColorAcc->GetScanline( y );
            Scanline pScanlineAlpha = pAlphaAcc->GetScanline( y );
            for (tools::Long x = 0; x < nWidth; ++x)
            {
                BitmapColor aColor = pThisAcc->GetPixelFromData(pScanlineRead, x);
 
                // write result back
                pColorAcc->SetPixelOnData(pScanlineColor, x, aColor);
                pAlphaAcc->SetPixelOnData(pScanlineAlpha, x, BitmapColor(aColor.GetAlpha()));
            }
        }
    }
 
    return { std::move(aColorBmp), std::move(aAlphaBmp) };
}
 
Color Bitmap::GetPixelColor(sal_Int32 nX, sal_Int32 nY) const
{
    BitmapScopedReadAccess pReadAccess( *this );
    assert(pReadAccess);
 
    BitmapColor aColor = pReadAccess->GetColor(nY, nX);
    return aColor;
}
 
void Bitmap::Expand(sal_Int32 nDX, sal_Int32 nDY, bool bExpandTransparent)
{
    if (IsEmpty())
        return;
    if( !nDX && !nDY )
        return;
 
    const Size          aSizePixel( GetSizePixel() );
    const tools::Long   nWidth = aSizePixel.Width();
    const tools::Long   nHeight = aSizePixel.Height();
    const Size          aNewSize( nWidth + nDX, nHeight + nDY );
    BitmapScopedReadAccess pReadAcc(*this);
    assert(pReadAcc);
    if( !pReadAcc )
        return;
 
    BitmapPalette aBmpPal( pReadAcc->GetPalette() );
    Bitmap aNewBmp(aNewSize, getPixelFormat(), &aBmpPal);
    BitmapScopedWriteAccess pWriteAcc(aNewBmp);
    assert(pWriteAcc);
    if( !pWriteAcc )
        return;
 
    const tools::Long  nNewX = nWidth;
    const tools::Long  nNewY = nHeight;
    const tools::Long  nNewWidth = pWriteAcc->Width();
    const tools::Long  nNewHeight = pWriteAcc->Height();
 
    Color aInitColor( bExpandTransparent ? COL_ALPHA_TRANSPARENT : COL_ALPHA_OPAQUE );
    BitmapColor aColor = pWriteAcc->GetBestMatchingColor( aInitColor );
 
    for( tools::Long nY = 0; nY < nHeight; nY++ )
    {
        pWriteAcc->CopyScanline( nY, *pReadAcc );
 
        if( nDX )
        {
            Scanline pScanline = pWriteAcc->GetScanline(nY);
            for( tools::Long nX = nNewX; nX < nNewWidth; nX++ )
                pWriteAcc->SetPixelOnData( pScanline, nX, aColor );
        }
    }
 
    if( nDY )
        for( tools::Long nY = nNewY; nY < nNewHeight; nY++ )
        {
            Scanline pScanline = pWriteAcc->GetScanline(nY);
            for( tools::Long nX = 0; nX < nNewWidth; nX++ )
                pWriteAcc->SetPixelOnData( pScanline, nX, aColor );
        }
 
    pWriteAcc.reset();
    pReadAcc.reset();
 
    ReassignWithSize(aNewBmp);
}
 
namespace
{
class BufferedData_ModifiedBitmap : public basegfx::SystemDependentData
{
    Bitmap maChangedBitmap;
    basegfx::BColorModifierStack maBColorModifierStack;
 
public:
    BufferedData_ModifiedBitmap(
        const Bitmap& rChangedBitmap,
        const basegfx::BColorModifierStack& rBColorModifierStack)
    : basegfx::SystemDependentData(
        Application::GetSystemDependentDataManager(),
        basegfx::SDD_Type::SDDType_ModifiedBitmap)
    , maChangedBitmap(rChangedBitmap)
    , maBColorModifierStack(rBColorModifierStack)
    {
    }
 
    const Bitmap& getChangedBitmap() const { return maChangedBitmap; }
    const basegfx::BColorModifierStack& getBColorModifierStack() const { return maBColorModifierStack; }
 
    virtual sal_Int64 estimateUsageInBytes() const override;
};
 
sal_Int64 BufferedData_ModifiedBitmap::estimateUsageInBytes() const
{
    return maChangedBitmap.GetSizeBytes();
}
}
 
Bitmap Bitmap::Modify(const basegfx::BColorModifierStack& rBColorModifierStack) const
{
    if (0 == rBColorModifierStack.count())
    {
        // no modifiers, done
        return *this;
    }
 
    // check for BColorModifier_replace at the top of the stack
    const basegfx::BColorModifierSharedPtr& rLastModifier(rBColorModifierStack.getBColorModifier(rBColorModifierStack.count() - 1));
    const basegfx::BColorModifier_replace* pLastModifierReplace(dynamic_cast<const basegfx::BColorModifier_replace*>(rLastModifier.get()));
 
    if (nullptr != pLastModifierReplace && !HasAlpha())
    {
        // at the top of the stack we have a BColorModifier_replace -> no Bitmap needed,
        // representation can be replaced by filled colored polygon. signal the caller
        // about that by returning empty Bitmap
        return Bitmap();
    }
 
    const basegfx::SystemDependentDataHolder* pHolder(accessSystemDependentDataHolder());
    std::shared_ptr<BufferedData_ModifiedBitmap> pBufferedData_ModifiedBitmap;
 
    if (nullptr != pHolder)
    {
        // try to access SystemDependentDataHolder and buffered data
        pBufferedData_ModifiedBitmap = std::static_pointer_cast<BufferedData_ModifiedBitmap>(
            pHolder->getSystemDependentData(basegfx::SDD_Type::SDDType_ModifiedBitmap));
 
        if (nullptr != pBufferedData_ModifiedBitmap
            && !(pBufferedData_ModifiedBitmap->getBColorModifierStack() == rBColorModifierStack))
        {
            // BColorModifierStack is different -> data invalid
            pBufferedData_ModifiedBitmap = nullptr;
        }
 
        if (nullptr != pBufferedData_ModifiedBitmap)
        {
            // found existing instance of modified Bitmap, return reused/buffered result
            return pBufferedData_ModifiedBitmap->getChangedBitmap();
        }
    }
 
    // have to create modified Bitmap, but we want to preserve the alpha information
    Bitmap aChangedBitmap;
 
    if (nullptr != pLastModifierReplace)
    {
        // special case -> we have BColorModifier_replace but Alpha channel
        if (vcl::isPalettePixelFormat(getPixelFormat()))
        {
            assert(!HasAlpha());
            aChangedBitmap = *this;
            // For e.g. 8bit Bitmaps, the nearest color to the given erase color is
            // determined and used -> this may be different from what is wanted here.
            // Better create a new bitmap with the needed color explicitly.
            BitmapScopedReadAccess xReadAccess(aChangedBitmap);
            assert(xReadAccess);
            if(xReadAccess)
            {
                BitmapPalette aNewPalette(xReadAccess->GetPalette());
                aNewPalette[0] = BitmapColor(Color(pLastModifierReplace->getBColor()));
                aChangedBitmap = Bitmap(
                    aChangedBitmap.GetSizePixel(),
                    aChangedBitmap.getPixelFormat(),
                    &aNewPalette);
            }
        }
        else if (HasAlpha())
        {
            // clear bitmap with dest color
            AlphaMask aAlphaMask(CreateAlphaMask());
            Bitmap aTmpBitmap(CreateColorBitmap());
            aTmpBitmap.Erase(Color(pLastModifierReplace->getBColor()));
            aChangedBitmap = createBitmapFromColorAndAlpha(aTmpBitmap, aAlphaMask.GetBitmap());
        }
        else
        {
            // clear bitmap with dest color
            aChangedBitmap = *this;
            aChangedBitmap.Erase(Color(pLastModifierReplace->getBColor()));
        }
    }
    else
    {
        aChangedBitmap = *this;
        BitmapScopedWriteAccess xContent(aChangedBitmap);
        assert(xContent);
        if(xContent)
        {
            const double fConvertColor(1.0 / 255.0);
 
            if(xContent->HasPalette())
            {
                const sal_uInt16 nCount(xContent->GetPaletteEntryCount());
 
                for(sal_uInt16 b(0); b < nCount; b++)
                {
                    const BitmapColor& rCol = xContent->GetPaletteColor(b);
                    const basegfx::BColor aBSource(
                        rCol.GetRed() * fConvertColor,
                        rCol.GetGreen() * fConvertColor,
                        rCol.GetBlue() * fConvertColor);
                    const basegfx::BColor aBDest(rBColorModifierStack.getModifiedColor(aBSource));
                    xContent->SetPaletteColor(b, BitmapColor(Color(aBDest)));
                }
            }
            else if(ScanlineFormat::N24BitTcBgr == xContent->GetScanlineFormat())
            {
                for(tools::Long y(0), nHeight(xContent->Height()); y < nHeight; y++)
                {
                    Scanline pScan = xContent->GetScanline(y);
 
                    for(tools::Long x(0), nWidth(xContent->Width()); x < nWidth; x++)
                    {
                        const basegfx::BColor aBSource(
                            *(pScan + 2)* fConvertColor,
                            *(pScan + 1) * fConvertColor,
                            *pScan * fConvertColor);
                        const basegfx::BColor aBDest(rBColorModifierStack.getModifiedColor(aBSource));
                        *pScan++ = static_cast< sal_uInt8 >(aBDest.getBlue() * 255.0);
                        *pScan++ = static_cast< sal_uInt8 >(aBDest.getGreen() * 255.0);
                        *pScan++ = static_cast< sal_uInt8 >(aBDest.getRed() * 255.0);
                    }
                }
            }
            else if(ScanlineFormat::N24BitTcRgb == xContent->GetScanlineFormat())
            {
                for(tools::Long y(0), nHeight(xContent->Height()); y < nHeight; y++)
                {
                    Scanline pScan = xContent->GetScanline(y);
 
                    for(tools::Long x(0), nWidth(xContent->Width()); x < nWidth; x++)
                    {
                        const basegfx::BColor aBSource(
                            *pScan * fConvertColor,
                            *(pScan + 1) * fConvertColor,
                            *(pScan + 2) * fConvertColor);
                        const basegfx::BColor aBDest(rBColorModifierStack.getModifiedColor(aBSource));
                        *pScan++ = static_cast< sal_uInt8 >(aBDest.getRed() * 255.0);
                        *pScan++ = static_cast< sal_uInt8 >(aBDest.getGreen() * 255.0);
                        *pScan++ = static_cast< sal_uInt8 >(aBDest.getBlue() * 255.0);
                    }
                }
            }
            else
            {
                for(tools::Long y(0), nHeight(xContent->Height()); y < nHeight; y++)
                {
                    Scanline pScanline = xContent->GetScanline( y );
                    for(tools::Long x(0), nWidth(xContent->Width()); x < nWidth; x++)
                    {
                        const BitmapColor aBMCol(xContent->GetColor(y, x));
                        const basegfx::BColor aBSource(
                            static_cast<double>(aBMCol.GetRed()) * fConvertColor,
                            static_cast<double>(aBMCol.GetGreen()) * fConvertColor,
                            static_cast<double>(aBMCol.GetBlue()) * fConvertColor);
                        const basegfx::BColor aBDest(rBColorModifierStack.getModifiedColor(aBSource));
                        // preserve alpha
                        BitmapColor aDestCol((Color(aBDest)));
                        aDestCol.SetAlpha(aBMCol.GetAlpha());
                        xContent->SetPixelOnData(pScanline, x, aDestCol);
                    }
                }
            }
        }
    }
 
    if (nullptr != pHolder)
    {
        // create new BufferedData_ModifiedBitmap (should be nullptr here)
        if (nullptr == pBufferedData_ModifiedBitmap)
        {
            pBufferedData_ModifiedBitmap = std::make_shared<BufferedData_ModifiedBitmap>(aChangedBitmap, rBColorModifierStack);
        }
 
        // register it, evtl. it's a new one
        basegfx::SystemDependentData_SharedPtr r2(pBufferedData_ModifiedBitmap);
        const_cast<basegfx::SystemDependentDataHolder*>(pHolder)->addOrReplaceSystemDependentData(r2);
    }
 
    // return result
    return aChangedBitmap;
}
 
/** Get the average color of the entire image.
    i.e. like scaling it down to a 1x1 pixel image.
*/
Color Bitmap::GetAverageColor() const
{
    BitmapScopedReadAccess xContent(*this);
    assert(xContent);
    if(!xContent)
        return Color();
 
    double fRed = 0;
    double fGreen = 0;
    double fBlue = 0;
    double fAlpha = 0;
    int nCnt = 0;
    for(tools::Long y(0), nHeight(xContent->Height()); y < nHeight; y++)
    {
        Scanline pScanline = xContent->GetScanline( y );
        for(tools::Long x(0), nWidth(xContent->Width()); x < nWidth; x++)
        {
            const BitmapColor aCol(xContent->GetPixelFromData(pScanline, x));
            fRed += aCol.GetRed();
            fGreen += aCol.GetGreen();
            fBlue += aCol.GetBlue();
            fAlpha += aCol.GetAlpha();
            nCnt++;
        }
    }
 
    if(!nCnt)
        return Color();
 
    return Color(ColorAlpha, static_cast<sal_uInt8>(fAlpha / nCnt),
                   static_cast<sal_uInt8>(fRed / nCnt),
                   static_cast<sal_uInt8>(fGreen / nCnt),
                   static_cast<sal_uInt8>(fBlue / nCnt));
}
 
void Bitmap::Draw( OutputDevice* pOutDev, const Point& rDestPt ) const
{
    pOutDev->DrawBitmap( rDestPt, *this );
}
 
void Bitmap::Draw( OutputDevice* pOutDev,
                     const Point& rDestPt, const Size& rDestSize ) const
{
    pOutDev->DrawBitmap( rDestPt, rDestSize, *this );
}
 
AlphaMask Bitmap::CreateAlphaMask() const
{
    assert(HasAlpha());
    if (!HasAlpha())
        return AlphaMask();
    std::pair<Bitmap, AlphaMask> aPair = SplitIntoColorAndAlpha();
    return aPair.second;
}
 
Bitmap Bitmap::CreateColorBitmap() const
{
    if (!HasAlpha())
        return *this;
    std::pair<Bitmap, AlphaMask> aPair = SplitIntoColorAndAlpha();
    return aPair.first;
}
 
void Bitmap::ChangeColorAlpha( sal_uInt8 cIndexFrom, sal_Int8 nAlphaTo )
{
    assert(HasAlpha());
    if (!HasAlpha())
        return;
 
    BitmapScopedWriteAccess pAccess(*this);
    assert( pAccess );
    if ( !pAccess )
        return;
 
    for ( tools::Long nY = 0, nHeight = pAccess->Height(); nY < nHeight; nY++ )
    {
        Scanline pScanline = pAccess->GetScanline( nY );
        for ( tools::Long nX = 0, nWidth = pAccess->Width(); nX < nWidth; nX++ )
        {
            BitmapColor aCol = pAccess->GetPixelFromData( pScanline, nX );
            const sal_uInt8 cIndex = aCol.GetAlpha();
            if ( cIndex == cIndexFrom )
            {
                aCol.SetAlpha(nAlphaTo);
                pAccess->SetPixelOnData( pScanline, nX, aCol );
            }
        }
    }
}
 
void Bitmap::AdjustTransparency(sal_uInt8 cTrans)
{
    if (!HasAlpha())
    {
        AlphaMask aAlpha(GetSizePixel(), &cTrans);
        *this = createBitmapFromColorAndAlpha(*this, aAlpha.GetBitmap());
    }
    else
    {
        BitmapScopedWriteAccess pA(*this);
        assert(pA);
        if( !pA )
            return;
 
        sal_uLong nTrans = cTrans;
        const tools::Long  nWidth = pA->Width(), nHeight = pA->Height();
 
        BitmapColor aAlphaValue( 0 );
 
        for( tools::Long nY = 0; nY < nHeight; nY++ )
        {
            Scanline pScanline = pA->GetScanline( nY );
            for( tools::Long nX = 0; nX < nWidth; nX++ )
            {
                BitmapColor aCol = pA->GetPixelFromData( pScanline, nX );
                sal_uLong nNewTrans = nTrans + (255 - aCol.GetAlpha());
                // clamp to 255
                nNewTrans = ( nNewTrans & 0xffffff00 ) ? 255 : nNewTrans;
                // convert back to alpha
                aCol.SetAlpha( static_cast<sal_uInt8>(255 - nNewTrans) );
                pA->SetPixelOnData( pScanline, nX, aCol );
            }
        }
    }
}
 
// Shift alpha transparent pixels between cppcanvas/ implementations
// and vcl in a generally grotesque and under-performing fashion
bool Bitmap::Create( const css::uno::Reference< css::rendering::XBitmapCanvas > &xBitmapCanvas,
                       const Size &rSize )
{
    css::uno::Reference< css::beans::XFastPropertySet > xFastPropertySet( xBitmapCanvas, css::uno::UNO_QUERY );
    if( xFastPropertySet )
    {
        // 0 means get Bitmap
        css::uno::Any aAny = xFastPropertySet->getFastPropertyValue( 0 );
        std::unique_ptr<Bitmap> xBitmap(reinterpret_cast<Bitmap*>(*o3tl::doAccess<sal_Int64>(aAny)));
        if( xBitmap )
        {
            *this = *xBitmap;
            return true;
        }
    }
 
    std::shared_ptr<SalBitmap> pSalBmp = ImplGetSVData()->mpDefInst->CreateSalBitmap();
    Size aLocalSize(rSize);
    if( pSalBmp->Create( xBitmapCanvas, aLocalSize ) )
    {
        *this = Bitmap(std::move(pSalBmp));
        return true;
    }
 
    return false;
}
 
void Bitmap::BlendAlpha(sal_uInt8 nAlpha)
{
    if (!HasAlpha())
    {
        sal_uInt8 cTrans = 255 - nAlpha;
        *this = createBitmapFromColorAndAlpha( *this, AlphaMask(GetSizePixel(), &cTrans).GetBitmap() );
    }
    else
    {
        BitmapScopedWriteAccess pAccess(*this);
        assert(pAccess);
        if( !pAccess )
            return;
 
        const tools::Long  nWidth = pAccess->Width(), nHeight = pAccess->Height();
 
        BitmapColor aAlphaValue( 0 );
 
        for( tools::Long nY = 0; nY < nHeight; nY++ )
        {
            Scanline pScanline = pAccess->GetScanline( nY );
            for( tools::Long nX = 0; nX < nWidth; nX++ )
            {
                BitmapColor aCol = pAccess->GetPixelFromData( pScanline, nX );
                sal_uInt8 nNewAlpha = static_cast<sal_Int32>(nAlpha) * aCol.GetAlpha() / 255;
                aCol.SetAlpha( nNewAlpha );
                pAccess->SetPixelOnData( pScanline, nX, aCol );
            }
        }
    }
}
 
void Bitmap::ReplaceTransparency(const Color& rReplaceColor)
{
    if( !HasAlpha() )
        return;
 
    Bitmap aNewBmp(GetSizePixel(), vcl::PixelFormat::N24_BPP);
    aNewBmp.SetPrefSize(GetPrefSize());
    aNewBmp.SetPrefMapMode(GetPrefMapMode());
 
    {
        BitmapScopedReadAccess pReadAccess(*this);
        assert(pReadAccess);
        if( !pReadAccess )
            return;
 
        BitmapScopedWriteAccess pWriteAccess(aNewBmp);
        assert(pWriteAccess);
        if( !pWriteAccess )
            return;
 
        const tools::Long  nWidth = pReadAccess->Width(), nHeight = pReadAccess->Height();
        for( tools::Long nY = 0; nY < nHeight; nY++ )
        {
            Scanline pReadScanline = pReadAccess->GetScanline( nY );
            Scanline pWriteScanline = pWriteAccess->GetScanline( nY );
            for( tools::Long nX = 0; nX < nWidth; nX++ )
            {
                BitmapColor aCol = pReadAccess->GetPixelFromData( pReadScanline, nX );
                aCol.Merge(rReplaceColor, aCol.GetAlpha());
                aCol.SetAlpha(255);
                pWriteAccess->SetPixelOnData( pWriteScanline, nX, aCol );
            }
        }
    }
 
    *this = std::move(aNewBmp);
}
 
Bitmap Bitmap::AutoScaleBitmap(Bitmap const & aBitmap, const tools::Long aStandardSize)
{
    Point aEmptyPoint(0,0);
    double imgposX = 0;
    double imgposY = 0;
    Bitmap aRet = aBitmap;
    double imgOldWidth = aRet.GetSizePixel().Width();
    double imgOldHeight = aRet.GetSizePixel().Height();
 
    if (imgOldWidth >= aStandardSize || imgOldHeight >= aStandardSize)
    {
        sal_Int32 imgNewWidth = 0;
        sal_Int32 imgNewHeight = 0;
        if (imgOldWidth >= imgOldHeight)
        {
            imgNewWidth = aStandardSize;
            imgNewHeight = sal_Int32(imgOldHeight / (imgOldWidth / aStandardSize) + 0.5);
            imgposX = 0;
            imgposY = (aStandardSize - (imgOldHeight / (imgOldWidth / aStandardSize) + 0.5)) / 2 + 0.5;
        }
        else
        {
            imgNewHeight = aStandardSize;
            imgNewWidth = sal_Int32(imgOldWidth / (imgOldHeight / aStandardSize) + 0.5);
            imgposY = 0;
            imgposX = (aStandardSize - (imgOldWidth / (imgOldHeight / aStandardSize) + 0.5)) / 2 + 0.5;
        }
 
        Size aScaledSize( imgNewWidth, imgNewHeight );
        aRet.Scale( aScaledSize, BmpScaleFlag::BestQuality );
    }
    else
    {
        imgposX = (aStandardSize - imgOldWidth) / 2 + 0.5;
        imgposY = (aStandardSize - imgOldHeight) / 2 + 0.5;
    }
 
    Size aStdSize( aStandardSize, aStandardSize );
    tools::Rectangle aRect(aEmptyPoint, aStdSize );
 
    ScopedVclPtrInstance< VirtualDevice > aVirDevice(*Application::GetDefaultDevice());
    aVirDevice->SetOutputSizePixel( aStdSize );
    aVirDevice->SetFillColor( COL_TRANSPARENT );
    aVirDevice->SetLineColor( COL_TRANSPARENT );
 
    // Draw a rect into virDevice
    aVirDevice->DrawRect( aRect );
    Point aPointPixel( static_cast<tools::Long>(imgposX), static_cast<tools::Long>(imgposY) );
    aVirDevice->DrawBitmap( aPointPixel, aRet );
    aRet = aVirDevice->GetBitmap( aEmptyPoint, aStdSize );
 
    return aRet;
}
 
void Bitmap::CombineMaskOr(Color rTransColor, sal_uInt8 nTol)
{
    Bitmap aColBmp = CreateColorBitmap();
    AlphaMask aNewMask = aColBmp.CreateAlphaMask( rTransColor, nTol );
 
    if ( HasAlpha() )
        aNewMask.AlphaCombineOr( CreateAlphaMask() );
 
    const MapMode aMap( maPrefMapMode );
    const Size aSize( maPrefSize );
 
    *this = createBitmapFromColorAndAlpha(aColBmp, aNewMask.GetBitmap());
 
    maPrefMapMode = aMap;
    maPrefSize = aSize;
}
 
/**
 * Retrieves the color model data we need for the XImageConsumer stuff.
 */
void  Bitmap::GetColorModel(css::uno::Sequence< sal_Int32 >& rRGBPalette,
        sal_uInt32& rnRedMask, sal_uInt32& rnGreenMask, sal_uInt32& rnBlueMask, sal_uInt32& rnAlphaMask, sal_uInt32& rnTransparencyIndex,
        sal_uInt32& rnWidth, sal_uInt32& rnHeight, sal_uInt8& rnBitCount)
{
    BitmapScopedReadAccess pReadAccess( *this );
    assert( pReadAccess );
 
    if( pReadAccess->HasPalette() )
    {
        sal_uInt16 nPalCount = pReadAccess->GetPaletteEntryCount();
 
        if( nPalCount )
        {
            rRGBPalette = css::uno::Sequence< sal_Int32 >( nPalCount + 1 );
 
            sal_Int32* pTmp = rRGBPalette.getArray();
 
            for( sal_uInt32 i = 0; i < nPalCount; i++, pTmp++ )
            {
                const BitmapColor& rCol = pReadAccess->GetPaletteColor( static_cast<sal_uInt16>(i) );
 
                *pTmp = static_cast<sal_Int32>(rCol.GetRed()) << sal_Int32(24);
                *pTmp |= static_cast<sal_Int32>(rCol.GetGreen()) << sal_Int32(16);
                *pTmp |= static_cast<sal_Int32>(rCol.GetBlue()) << sal_Int32(8);
                *pTmp |= sal_Int32(0x000000ffL);
            }
 
            rnTransparencyIndex = 0;
        }
    }
    else
    {
        rnRedMask = 0xff000000UL;
        rnGreenMask = 0x00ff0000UL;
        rnBlueMask = 0x0000ff00UL;
        rnAlphaMask = 0x000000ffUL;
        rnTransparencyIndex = 0;
    }
 
    rnWidth = pReadAccess->Width();
    rnHeight = pReadAccess->Height();
    rnBitCount = pReadAccess->GetBitCount();
}
 
Bitmap createAlphaBlendFrame(
    const Size& rSize,
    sal_uInt8 nAlpha,
    const Color& rColorTopLeft,
    const Color& rColorBottomRight)
{
    const sal_uInt32 nW(rSize.Width());
    const sal_uInt32 nH(rSize.Height());
 
    if(nW || nH)
    {
        Color aColTopRight(rColorTopLeft);
        Color aColBottomLeft(rColorTopLeft);
        const sal_uInt32 nDE(nW + nH);
 
        aColTopRight.Merge(rColorBottomRight, 255 - sal_uInt8((nW * 255) / nDE));
        aColBottomLeft.Merge(rColorBottomRight, 255 - sal_uInt8((nH * 255) / nDE));
 
        return createAlphaBlendFrame(rSize, nAlpha, rColorTopLeft, aColTopRight, rColorBottomRight, aColBottomLeft);
    }
 
    return Bitmap();
}
 
Bitmap createAlphaBlendFrame(
    const Size& rSize,
    sal_uInt8 nAlpha,
    const Color& rColorTopLeft,
    const Color& rColorTopRight,
    const Color& rColorBottomRight,
    const Color& rColorBottomLeft)
{
    ImplSVData* pSVData = ImplGetSVData();
    if (!pSVData->mpBlendFrameCache)
    {
        pSVData->mpBlendFrameCache.reset(new BlendFrameCache(rSize, nAlpha, rColorTopLeft, rColorTopRight, rColorBottomRight, rColorBottomLeft));
        return pSVData->mpBlendFrameCache->m_aLastResult;
    }
 
    BlendFrameCache* pBlendFrameCache = pSVData->mpBlendFrameCache.get();
 
    if(pBlendFrameCache->m_aLastSize == rSize
        && pBlendFrameCache->m_nLastAlpha == nAlpha
        && pBlendFrameCache->m_aLastColorTopLeft == rColorTopLeft
        && pBlendFrameCache->m_aLastColorTopRight == rColorTopRight
        && pBlendFrameCache->m_aLastColorBottomRight == rColorBottomRight
        && pBlendFrameCache->m_aLastColorBottomLeft == rColorBottomLeft)
    {
        return pBlendFrameCache->m_aLastResult;
    }
 
    pSVData->mpBlendFrameCache.reset(new BlendFrameCache(rSize, nAlpha, rColorTopLeft, rColorTopRight, rColorBottomRight, rColorBottomLeft));
    return pSVData->mpBlendFrameCache->m_aLastResult;
}
 
static Bitmap DetectEdges( const Bitmap& rBmp )
{
    constexpr sal_uInt8 cEdgeDetectThreshold = 128;
    const Size  aSize( rBmp.GetSizePixel() );
 
    if( ( aSize.Width() <= 2 ) || ( aSize.Height() <= 2 ) )
        return rBmp;
 
    Bitmap aWorkBmp( rBmp );
 
    if( !aWorkBmp.Convert( BmpConversion::N8BitGreys ) )
        return rBmp;
 
    ScopedVclPtr<VirtualDevice> pVirDev(VclPtr<VirtualDevice>::Create());
    pVirDev->SetOutputSizePixel(aSize);
    BitmapScopedReadAccess pReadAcc(aWorkBmp);
    if( !pReadAcc )
        return rBmp;
 
    const tools::Long          nWidth = aSize.Width();
    const tools::Long          nWidth2 = nWidth - 2;
    const tools::Long          nHeight = aSize.Height();
    const tools::Long          nHeight2 = nHeight - 2;
    const tools::Long          lThres2 = static_cast<tools::Long>(cEdgeDetectThreshold) * cEdgeDetectThreshold;
    tools::Long                nSum1;
    tools::Long                nSum2;
    tools::Long                lGray;
 
    // initialize border with white pixels
    pVirDev->SetLineColor( COL_WHITE );
    pVirDev->DrawLine( Point(), Point( nWidth - 1, 0L ) );
    pVirDev->DrawLine( Point( nWidth - 1, 0L ), Point( nWidth - 1, nHeight - 1 ) );
    pVirDev->DrawLine( Point( nWidth - 1, nHeight - 1 ), Point( 0L, nHeight - 1 ) );
    pVirDev->DrawLine( Point( 0, nHeight - 1 ), Point() );
 
    for( tools::Long nY = 0, nY1 = 1, nY2 = 2; nY < nHeight2; nY++, nY1++, nY2++ )
    {
        Scanline pScanlineRead = pReadAcc->GetScanline( nY );
        Scanline pScanlineRead1 = pReadAcc->GetScanline( nY1 );
        Scanline pScanlineRead2 = pReadAcc->GetScanline( nY2 );
        for( tools::Long nX = 0, nXDst = 1, nXTmp; nX < nWidth2; nX++, nXDst++ )
        {
            nXTmp = nX;
 
            nSum2 = pReadAcc->GetIndexFromData( pScanlineRead, nXTmp++ );
            nSum1 = -nSum2;
            nSum2 += static_cast<tools::Long>(pReadAcc->GetIndexFromData( pScanlineRead, nXTmp++ )) << 1;
            lGray = pReadAcc->GetIndexFromData( pScanlineRead, nXTmp );
            nSum1 += lGray;
            nSum2 += lGray;
 
            nSum1 += static_cast<tools::Long>(pReadAcc->GetIndexFromData( pScanlineRead1, nXTmp )) << 1;
            nXTmp -= 2;
            nSum1 -= static_cast<tools::Long>(pReadAcc->GetIndexFromData( pScanlineRead1, nXTmp )) << 1;
 
            lGray = -static_cast<tools::Long>(pReadAcc->GetIndexFromData( pScanlineRead2, nXTmp++ ));
            nSum1 += lGray;
            nSum2 += lGray;
            nSum2 -= static_cast<tools::Long>(pReadAcc->GetIndexFromData( pScanlineRead2, nXTmp++ )) << 1;
            lGray = static_cast<tools::Long>(pReadAcc->GetIndexFromData( pScanlineRead2, nXTmp ));
            nSum1 += lGray;
            nSum2 -= lGray;
 
            if( ( nSum1 * nSum1 + nSum2 * nSum2 ) < lThres2 )
                pVirDev->DrawPixel( Point(nXDst, nY), COL_WHITE );
            else
                pVirDev->DrawPixel( Point(nXDst, nY), COL_BLACK );
        }
    }
 
    pReadAcc.reset();
 
    Bitmap aRetBmp = pVirDev->GetBitmap(Point(0,0), aSize);
 
    if( aRetBmp.IsEmpty() )
        aRetBmp = rBmp;
    else
    {
        aRetBmp.SetPrefMapMode( rBmp.GetPrefMapMode() );
        aRetBmp.SetPrefSize( rBmp.GetPrefSize() );
    }
 
    return aRetBmp;
}
 
/** Get contours in image */
tools::Polygon  Bitmap::GetContour( bool bContourEdgeDetect,
                                    const tools::Rectangle* pWorkRectPixel )
{
    Bitmap aWorkBmp;
    tools::Rectangle   aWorkRect( Point(), GetSizePixel() );
 
    if( pWorkRectPixel )
        aWorkRect.Intersection( *pWorkRectPixel );
 
    aWorkRect.Normalize();
 
    if ((aWorkRect.GetWidth() <= 4) || (aWorkRect.GetHeight() <= 4))
        return tools::Polygon();
 
    // if the flag is set, we need to detect edges
    if( bContourEdgeDetect )
        aWorkBmp = DetectEdges( CreateColorBitmap() );
    else
        aWorkBmp = CreateColorBitmap();
 
    BitmapScopedReadAccess pAcc(aWorkBmp);
 
    const tools::Long nWidth = pAcc ? pAcc->Width() : 0;
    const tools::Long nHeight = pAcc ? pAcc->Height() : 0;
 
    if (!pAcc || !nWidth || !nHeight)
        return tools::Polygon();
 
    // tdf#161833 treat semi-transparent pixels as opaque
    // Limiting the contour wrapping polygon to only opaque pixels
    // causes clipping of any shadows or other semi-transparent
    // areas in the image. So, instead of testing for fully opaque
    // pixels, treat pixels that are not fully transparent as opaque.
    // tdf#162062 only apply fix for tdf#161833 if there is a palette
    const BitmapColor   aTransparent = pAcc->GetBestMatchingColor( pAcc->HasPalette() ? COL_ALPHA_TRANSPARENT : COL_ALPHA_OPAQUE );
 
    std::unique_ptr<Point[]> pPoints1;
    std::unique_ptr<Point[]> pPoints2;
 
    pPoints1.reset(new Point[ nHeight ]);
    pPoints2.reset(new Point[ nHeight ]);
 
    const tools::Long nStartX1 = aWorkRect.Left() + 1;
    const tools::Long nEndX1 = aWorkRect.Right();
    const tools::Long nStartX2 = nEndX1 - 1;
    const tools::Long nStartY1 = aWorkRect.Top() + 1;
    const tools::Long nEndY1 = aWorkRect.Bottom();
 
    sal_uInt16 nPolyPos = 0;
 
    for (tools::Long nY = nStartY1; nY < nEndY1; nY++)
    {
        tools::Long nX = nStartX1;
        Scanline pScanline = pAcc->GetScanline( nY );
 
        // scan row from left to right
        while( nX < nEndX1 )
        {
            if( aTransparent != pAcc->GetPixelFromData( pScanline, nX ) )
            {
                pPoints1[ nPolyPos ] = Point( nX, nY );
                nX = nStartX2;
 
                // this loop always breaks eventually as there is at least one pixel
                while( true )
                {
                    if( aTransparent != pAcc->GetPixelFromData( pScanline, nX ) )
                    {
                        pPoints2[ nPolyPos ] = Point( nX, nY );
                        break;
                    }
 
                    nX--;
                }
 
                nPolyPos++;
                break;
            }
 
            nX++;
        }
    }
 
    const sal_uInt16 nNewSize1 = nPolyPos << 1;
 
    tools::Polygon aRetPoly(nPolyPos, pPoints1.get());
    aRetPoly.SetSize( nNewSize1 + 1 );
    aRetPoly[ nNewSize1 ] = aRetPoly[ 0 ];
 
    for( sal_uInt16 j = nPolyPos; nPolyPos < nNewSize1; )
    {
        aRetPoly[ nPolyPos++ ] = pPoints2[ --j ];
    }
 
    Size const& rPrefSize = aWorkBmp.GetPrefSize();
    const double fFactorX = static_cast<double>(rPrefSize.Width()) / nWidth;
    const double fFactorY = static_cast<double>(rPrefSize.Height()) / nHeight;
 
    if( ( fFactorX != 0. ) && ( fFactorY != 0. ) )
        aRetPoly.Scale( fFactorX, fFactorY );
 
    return aRetPoly;
}
 
static Bitmap impTransformBitmap(
    const Bitmap& rSource,
    const Size& rDestinationSize,
    const basegfx::B2DHomMatrix& rTransform,
    bool bSmooth)
{
    Bitmap aDestination(rDestinationSize, rSource.getPixelFormat());
    BitmapScopedWriteAccess xWrite(aDestination);
 
    if(xWrite)
    {
        BitmapScopedReadAccess xRead(rSource);
 
        if (xRead)
        {
            const Size aDestinationSizePixel(aDestination.GetSizePixel());
 
            // tdf#157795 set color to black outside of bitmap bounds
            // Due to commit 81994cb2b8b32453a92bcb011830fcb884f22ff3,
            // transparent areas are now black instead of white.
            // tdf#160831 only set outside color to black for alpha masks
            // The outside color still needs to be white for the content
            // so only apply the fix for tdf#157795 to the alpha mask.
            const BitmapColor aOutside(ColorAlpha, 0xff, 0xff, 0xff, 0x00);
 
            for(tools::Long y(0); y < aDestinationSizePixel.getHeight(); y++)
            {
                Scanline pScanline = xWrite->GetScanline( y );
                for(tools::Long x(0); x < aDestinationSizePixel.getWidth(); x++)
                {
                    const basegfx::B2DPoint aSourceCoor(rTransform * basegfx::B2DPoint(x, y));
 
                    if(bSmooth)
                    {
                        xWrite->SetPixelOnData(
                            pScanline,
                            x,
                            xRead->GetInterpolatedColorWithFallback(
                                aSourceCoor.getY(),
                                aSourceCoor.getX(),
                                aOutside));
                    }
                    else
                    {
                        // this version does the correct <= 0.0 checks, so no need
                        // to do the static_cast< sal_Int32 > self and make an error
                        xWrite->SetPixelOnData(
                            pScanline,
                            x,
                            xRead->GetColorWithFallback(
                                aSourceCoor.getY(),
                                aSourceCoor.getX(),
                                aOutside));
                    }
                }
            }
        }
    }
    xWrite.reset();
 
    rSource.AdaptBitCount(aDestination);
 
    return aDestination;
}
 
/// Decides if rTransformation needs smoothing or not (e.g. 180 deg rotation doesn't need it).
static bool implTransformNeedsSmooth(const basegfx::B2DHomMatrix& rTransformation)
{
    basegfx::B2DVector aScale, aTranslate;
    double fRotate, fShearX;
    rTransformation.decompose(aScale, aTranslate, fRotate, fShearX);
    if (aScale != basegfx::B2DVector(1, 1))
    {
        return true;
    }
 
    fRotate = fmod( fRotate, 2 * M_PI );
    if (fRotate < 0)
    {
        fRotate += 2 * M_PI;
    }
    if (!rtl::math::approxEqual(fRotate, 0)
        && !rtl::math::approxEqual(fRotate, M_PI_2)
        && !rtl::math::approxEqual(fRotate, M_PI)
        && !rtl::math::approxEqual(fRotate, 3 * M_PI_2))
    {
        return true;
    }
 
    if (!rtl::math::approxEqual(fShearX, 0))
    {
        return true;
    }
 
    return false;
}
 
Bitmap Bitmap::TransformBitmap(
    double fWidth,
    double fHeight,
    const basegfx::B2DHomMatrix& rTransformation) const
{
    if(fWidth <= 1 || fHeight <= 1)
        return Bitmap();
 
    // force destination to 24 bit, we want to smooth output
    const Size aDestinationSize(basegfx::fround<tools::Long>(fWidth), basegfx::fround<tools::Long>(fHeight));
    bool bSmooth = implTransformNeedsSmooth(rTransformation);
    const Bitmap aDestination(impTransformBitmap(*this, aDestinationSize, rTransformation, bSmooth));
 
    return aDestination;
}
 
Bitmap Bitmap::getTransformed(
    const basegfx::B2DHomMatrix& rTransformation,
    const basegfx::B2DRange& rVisibleRange,
    double fMaximumArea) const
{
    if (IsEmpty())
        return Bitmap();
 
    const sal_uInt32 nSourceWidth(GetSizePixel().Width());
    const sal_uInt32 nSourceHeight(GetSizePixel().Height());
 
    if (!nSourceWidth || !nSourceHeight)
        return Bitmap();
 
    // Get aOutlineRange
    basegfx::B2DRange aOutlineRange(0.0, 0.0, 1.0, 1.0);
 
    aOutlineRange.transform(rTransformation);
 
    // create visible range from it by moving from relative to absolute
    basegfx::B2DRange aVisibleRange(rVisibleRange);
 
    aVisibleRange.transform(
        basegfx::utils::createScaleTranslateB2DHomMatrix(
            aOutlineRange.getRange(),
            aOutlineRange.getMinimum()));
 
    // get target size (which is visible range's size)
    double fWidth(aVisibleRange.getWidth());
    double fHeight(aVisibleRange.getHeight());
 
    if (fWidth < 1.0 || fHeight < 1.0)
        return Bitmap();
 
    // test if discrete size (pixel) maybe too big and limit it
    const double fArea(fWidth * fHeight);
    const bool bNeedToReduce(basegfx::fTools::more(fArea, fMaximumArea));
    double fReduceFactor(1.0);
 
    if(bNeedToReduce)
    {
        fReduceFactor = sqrt(fMaximumArea / fArea);
        fWidth *= fReduceFactor;
        fHeight *= fReduceFactor;
    }
 
    // Build complete transform from source pixels to target pixels.
    // Start by scaling from source pixel size to unit coordinates
    basegfx::B2DHomMatrix aTransform(
        basegfx::utils::createScaleB2DHomMatrix(
            1.0 / nSourceWidth,
            1.0 / nSourceHeight));
 
    // multiply with given transform which leads from unit coordinates inside
    // aOutlineRange
    aTransform = rTransformation * aTransform;
 
    // subtract top-left of absolute VisibleRange
    aTransform.translate(
        -aVisibleRange.getMinX(),
        -aVisibleRange.getMinY());
 
    // scale to target pixels (if needed)
    if(bNeedToReduce)
    {
        aTransform.scale(fReduceFactor, fReduceFactor);
    }
 
    // invert to get transformation from target pixel coordinates to source pixels
    aTransform.invert();
 
    // create bitmap using source, destination and linear back-transformation
    return TransformBitmap(fWidth, fHeight, aTransform);
}
 
void Bitmap::DumpAsPng(const char* pFileName) const
{
    OUString sPath(u"file:///tmp/bitmap.png"_ustr);
    if (pFileName)
    {
        sPath = OUString::fromUtf8(pFileName);
    }
    else if (OUString env = o3tl::getEnvironment(u"VCL_DUMP_BMP_PATH"_ustr); !env.isEmpty())
    {
        sPath = env;
    }
    SvFileStream aStream(sPath, StreamMode::STD_READWRITE | StreamMode::TRUNC);
    assert(aStream.good());
    vcl::PngImageWriter aWriter(aStream);
    aWriter.write(*this);
}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V530 The return value of function 'Intersection' is required to be utilized.

V530 The return value of function 'Intersection' is required to be utilized.

V530 The return value of function 'Intersection' is required to be utilized.

V530 The return value of function 'Intersection' is required to be utilized.

V530 The return value of function 'Intersection' is required to be utilized.

V530 The return value of function 'Intersection' is required to be utilized.