/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include <sal/config.h>

#include <cstdlib>
#include <memory>
#include <numeric>

#include <svsys.h>

#include "gdiimpl.hxx"

#include <string.h>
#include <rtl/strbuf.hxx>
#include <sal/log.hxx>
#include <tools/poly.hxx>
#include <basegfx/polygon/b2dpolygon.hxx>
#include <basegfx/polygon/b2dpolygontools.hxx>
#include <basegfx/polygon/b2dpolypolygontools.hxx>
#include <win/wincomp.hxx>
#include <win/saldata.hxx>
#include <win/salgdi.h>
#include <win/salbmp.h>
#include <win/scoped_gdi.hxx>
#include <vcl/BitmapAccessMode.hxx>
#include <vcl/BitmapBuffer.hxx>
#include <vcl/BitmapPalette.hxx>
#include <win/salframe.h>
#include <basegfx/matrix/b2dhommatrixtools.hxx>
#include <basegfx/utils/systemdependentdata.hxx>

#include <outdata.hxx>
#include <win/salids.hrc>
#include <ControlCacheKey.hxx>

#if defined _MSC_VER
#ifndef min
#define min(a,b)    (((a) < (b)) ? (a) : (b))
#endif
#ifndef max
#define max(a,b)    (((a) > (b)) ? (a) : (b))
#endif
#endif

#include <prewin.h>

#include <gdiplus.h>
#include <gdiplusenums.h>
#include <gdipluscolor.h>

#include <postwin.h>

#define SAL_POLYPOLYCOUNT_STACKBUF          8
#define SAL_POLYPOLYPOINTS_STACKBUF         64

#define SAL_POLY_STACKBUF       32

namespace {

// #100127# Fill point and flag memory from array of points which
// might also contain bezier control points for the PolyDraw() GDI method
// Make sure pWinPointAry and pWinFlagAry are big enough
void ImplPreparePolyDraw( bool                      bCloseFigures,
                          sal_uLong                 nPoly,
                          const sal_uInt32*         pPoints,
                          const Point* const*    pPtAry,
                          const PolyFlags* const*   pFlgAry,
                          POINT*                    pWinPointAry,
                          BYTE*                     pWinFlagAry     )
{
    sal_uLong nCurrPoly;
    for( nCurrPoly=0; nCurrPoly<nPoly; ++nCurrPoly )
    {
        const Point* pCurrPoint = *pPtAry++;
        const PolyFlags* pCurrFlag = *pFlgAry++;
        const sal_uInt32 nCurrPoints = *pPoints++;
        const bool bHaveFlagArray( pCurrFlag );
        sal_uLong nCurrPoint;

        if( nCurrPoints )
        {
            // start figure
            *pWinPointAry++ = POINT { static_cast<LONG>(pCurrPoint->getX()), static_cast<LONG>(pCurrPoint->getY()) };
            pCurrPoint++;
            *pWinFlagAry++  = PT_MOVETO;
            ++pCurrFlag;

            for( nCurrPoint=1; nCurrPoint<nCurrPoints; )
            {
                // #102067# Check existence of flag array
                if( bHaveFlagArray &&
                    ( nCurrPoint + 2 ) < nCurrPoints )
                {
                    PolyFlags P4( pCurrFlag[ 2 ] );

                    if( ( PolyFlags::Control == pCurrFlag[ 0 ] ) &&
                        ( PolyFlags::Control == pCurrFlag[ 1 ] ) &&
                        ( PolyFlags::Normal == P4 || PolyFlags::Smooth == P4 || PolyFlags::Symmetric == P4 ) )
                    {
                        // control point one
                        *pWinPointAry++ = POINT { static_cast<LONG>(pCurrPoint->getX()), static_cast<LONG>(pCurrPoint->getY()) };
                        pCurrPoint++;
                        *pWinFlagAry++  = PT_BEZIERTO;

                        // control point two
                        *pWinPointAry++ = POINT { static_cast<LONG>(pCurrPoint->getX()), static_cast<LONG>(pCurrPoint->getY()) };
                        pCurrPoint++;
                        *pWinFlagAry++  = PT_BEZIERTO;

                        // end point
                        *pWinPointAry++ = POINT { static_cast<LONG>(pCurrPoint->getX()), static_cast<LONG>(pCurrPoint->getY()) };
                        pCurrPoint++;
                        *pWinFlagAry++  = PT_BEZIERTO;

                        nCurrPoint += 3;
                        pCurrFlag += 3;
                        continue;
                    }
                }

                // regular line point
                *pWinPointAry++ = POINT { static_cast<LONG>(pCurrPoint->getX()), static_cast<LONG>(pCurrPoint->getY()) };
                pCurrPoint++;
                *pWinFlagAry++  = PT_LINETO;
                ++pCurrFlag;
                ++nCurrPoint;
            }

            // end figure?
            if( bCloseFigures )
                pWinFlagAry[-1] |= PT_CLOSEFIGURE;
        }
    }
}

Color ImplGetROPColor( SalROPColor nROPColor )
{
    Color nColor;
    if ( nROPColor == SalROPColor::N0 )
        nColor = Color( 0, 0, 0 );
    else
        nColor = Color( 255, 255, 255 );
    return nColor;
}

bool IsDitherColor(BYTE nRed, BYTE nGreen, BYTE nBlue)
{
    constexpr sal_uInt8 DITHER_PAL_DELTA = 51;

    return !(nRed % DITHER_PAL_DELTA) &&
           !(nGreen % DITHER_PAL_DELTA) &&
           !(nBlue % DITHER_PAL_DELTA);
}

bool IsPaletteColor(BYTE nRed, BYTE nGreen, BYTE nBlue)
{
    static const PALETTEENTRY aImplSalSysPalEntryAry[] =
    {
    {    0,    0,    0, 0 },
    {    0,    0, 0x80, 0 },
    {    0, 0x80,    0, 0 },
    {    0, 0x80, 0x80, 0 },
    { 0x80,    0,    0, 0 },
    { 0x80,    0, 0x80, 0 },
    { 0x80, 0x80,    0, 0 },
    { 0x80, 0x80, 0x80, 0 },
    { 0xC0, 0xC0, 0xC0, 0 },
    {    0,    0, 0xFF, 0 },
    {    0, 0xFF,    0, 0 },
    {    0, 0xFF, 0xFF, 0 },
    { 0xFF,    0,    0, 0 },
    { 0xFF,    0, 0xFF, 0 },
    { 0xFF, 0xFF,    0, 0 },
    { 0xFF, 0xFF, 0xFF, 0 }
    };

    for (const auto& rPalEntry : aImplSalSysPalEntryAry)
    {
        if(rPalEntry.peRed == nRed &&
           rPalEntry.peGreen == nGreen &&
           rPalEntry.peBlue == nBlue)
        {
            return true;
        }
    }

    return false;
}

bool IsExtraColor(BYTE nRed, BYTE nGreen, BYTE nBlue)
{
    return (nRed == 0) && (nGreen == 184) && (nBlue == 255);
}

bool ImplIsPaletteEntry(BYTE nRed, BYTE nGreen, BYTE nBlue)
{
    return IsDitherColor(nRed, nGreen, nBlue) ||
           IsPaletteColor(nRed, nGreen, nBlue) ||
           IsExtraColor(nRed, nGreen, nBlue);
}

} // namespace

WinSalGraphicsImpl::WinSalGraphicsImpl(WinSalGraphics& rParent):
    mrParent(rParent),
    mbXORMode(false),
    mbPen(false),
    mhPen(nullptr),
    mbStockPen(false),
    mbBrush(false),
    mbStockBrush(false),
    mhBrush(nullptr)
{
}

WinSalGraphicsImpl::~WinSalGraphicsImpl()
{
    if ( mhPen )
    {
        if ( !mbStockPen )
            DeletePen( mhPen );
    }

    if ( mhBrush )
    {
        if ( !mbStockBrush )
            DeleteBrush( mhBrush );
    }
}

void WinSalGraphicsImpl::Init()
{
}

void WinSalGraphicsImpl::freeResources()
{
}

bool WinSalGraphicsImpl::drawEPS(tools::Long, tools::Long, tools::Long, tools::Long, void*, sal_uInt32)
{
    return false;
}

void WinSalGraphicsImpl::copyBits( const SalTwoRect& rPosAry, SalGraphics* pSrcGraphics )
{
    HDC     hSrcDC;
    DWORD   nRop;

    if ( pSrcGraphics )
        hSrcDC = static_cast<WinSalGraphics*>(pSrcGraphics)->getHDC();
    else
        hSrcDC = mrParent.getHDC();

    if ( mbXORMode )
        nRop = SRCINVERT;
    else
        nRop = SRCCOPY;

    if ( (rPosAry.mnSrcWidth  == rPosAry.mnDestWidth) &&
         (rPosAry.mnSrcHeight == rPosAry.mnDestHeight) )
    {
        BitBlt( mrParent.getHDC(),
                static_cast<int>(rPosAry.mnDestX), static_cast<int>(rPosAry.mnDestY),
                static_cast<int>(rPosAry.mnDestWidth), static_cast<int>(rPosAry.mnDestHeight),
                hSrcDC,
                static_cast<int>(rPosAry.mnSrcX), static_cast<int>(rPosAry.mnSrcY),
                nRop );
    }
    else
    {
        int nOldStretchMode = SetStretchBltMode( mrParent.getHDC(), STRETCH_DELETESCANS );
        StretchBlt( mrParent.getHDC(),
                    static_cast<int>(rPosAry.mnDestX), static_cast<int>(rPosAry.mnDestY),
                    static_cast<int>(rPosAry.mnDestWidth), static_cast<int>(rPosAry.mnDestHeight),
                    hSrcDC,
                    static_cast<int>(rPosAry.mnSrcX), static_cast<int>(rPosAry.mnSrcY),
                    static_cast<int>(rPosAry.mnSrcWidth), static_cast<int>(rPosAry.mnSrcHeight),
                    nRop );
        SetStretchBltMode( mrParent.getHDC(), nOldStretchMode );
    }
}

namespace
{

void MakeInvisibleArea(const RECT& rSrcRect,
                       int nLeft, int nTop, int nRight, int nBottom,
                       HRGN& rhInvalidateRgn)
{
    if (!rhInvalidateRgn)
    {
        rhInvalidateRgn = CreateRectRgnIndirect(&rSrcRect);
    }

    ScopedHRGN hTempRgn(CreateRectRgn(nLeft, nTop, nRight, nBottom));
    CombineRgn(rhInvalidateRgn, rhInvalidateRgn, hTempRgn.get(), RGN_DIFF);
}

void ImplCalcOutSideRgn( const RECT& rSrcRect,
                         int nLeft, int nTop, int nRight, int nBottom,
                         HRGN& rhInvalidateRgn )
{
    // calculate area outside the visible region
    if (rSrcRect.left < nLeft)
    {
        MakeInvisibleArea(rSrcRect, -31999, 0, nLeft, 31999, rhInvalidateRgn);
    }
    if (rSrcRect.top < nTop)
    {
        MakeInvisibleArea(rSrcRect, 0, -31999, 31999, nTop, rhInvalidateRgn);
    }
    if (rSrcRect.right > nRight)
    {
        MakeInvisibleArea(rSrcRect, nRight, 0, 31999, 31999, rhInvalidateRgn);
    }
    if (rSrcRect.bottom > nBottom)
    {
        MakeInvisibleArea(rSrcRect, 0, nBottom, 31999, 31999, rhInvalidateRgn);
    }
}

} // namespace

void WinSalGraphicsImpl::copyArea( tools::Long nDestX, tools::Long nDestY,
                            tools::Long nSrcX, tools::Long nSrcY,
                            tools::Long nSrcWidth, tools::Long nSrcHeight,
                            bool bWindowInvalidate )
{
    bool    bRestoreClipRgn = false;
    HRGN    hOldClipRgn = nullptr;
    int     nOldClipRgnType = ERROR;
    HRGN    hInvalidateRgn = nullptr;

    // do we have to invalidate also the overlapping regions?
    if ( bWindowInvalidate && mrParent.isWindow() )
    {
        // compute and invalidate those parts that were either off-screen or covered by other windows
        //  while performing the above BitBlt
        // those regions then have to be invalidated as they contain useless/wrong data
        RECT    aSrcRect;
        RECT    aClipRect;
        RECT    aTempRect;
        RECT    aTempRect2;
        HRGN    hTempRgn;
        HWND    hWnd;

        // restrict srcRect to this window (calc intersection)
        aSrcRect.left   = static_cast<int>(nSrcX);
        aSrcRect.top    = static_cast<int>(nSrcY);
        aSrcRect.right  = aSrcRect.left+static_cast<int>(nSrcWidth);
        aSrcRect.bottom = aSrcRect.top+static_cast<int>(nSrcHeight);
        GetClientRect( mrParent.gethWnd(), &aClipRect );
        if ( IntersectRect( &aSrcRect, &aSrcRect, &aClipRect ) )
        {
            // transform srcRect to screen coordinates
            POINT aPt;
            aPt.x = 0;
            aPt.y = 0;
            ClientToScreen( mrParent.gethWnd(), &aPt );
            aSrcRect.left   += aPt.x;
            aSrcRect.top    += aPt.y;
            aSrcRect.right  += aPt.x;
            aSrcRect.bottom += aPt.y;
            hInvalidateRgn = nullptr;

            // compute the parts that are off screen (ie invisible)
            RECT theScreen;
            ImplSalGetWorkArea( nullptr, &theScreen, nullptr );  // find the screen area taking multiple monitors into account
            ImplCalcOutSideRgn( aSrcRect, theScreen.left, theScreen.top, theScreen.right, theScreen.bottom, hInvalidateRgn );

            // calculate regions that are covered by other windows
            HRGN hTempRgn2 = nullptr;
            HWND hWndTopWindow = mrParent.gethWnd();
            // Find the TopLevel Window, because only Windows which are in
            // in the foreground of our TopLevel window must be considered
            if ( GetWindowStyle( hWndTopWindow ) & WS_CHILD )
            {
                RECT aTempRect3 = aSrcRect;
                do
                {
                    hWndTopWindow = ::GetParent( hWndTopWindow );

                    // Test if the Parent clips our window
                    GetClientRect( hWndTopWindow, &aTempRect );
                    POINT aPt2;
                    aPt2.x = 0;
                    aPt2.y = 0;
                    ClientToScreen( hWndTopWindow, &aPt2 );
                    aTempRect.left   += aPt2.x;
                    aTempRect.top    += aPt2.y;
                    aTempRect.right  += aPt2.x;
                    aTempRect.bottom += aPt2.y;
                    IntersectRect( &aTempRect3, &aTempRect3, &aTempRect );
                }
                while ( GetWindowStyle( hWndTopWindow ) & WS_CHILD );

                // If one or more Parents clip our window, then we must
                // calculate the outside area
                if ( !EqualRect( &aSrcRect, &aTempRect3 ) )
                {
                    ImplCalcOutSideRgn( aSrcRect,
                                        aTempRect3.left, aTempRect3.top,
                                        aTempRect3.right, aTempRect3.bottom,
                                        hInvalidateRgn );
                }
            }
            // retrieve the top-most (z-order) child window
            hWnd = GetWindow( GetDesktopWindow(), GW_CHILD );
            while ( hWnd )
            {
                if ( hWnd == hWndTopWindow )
                    break;
                if ( IsWindowVisible( hWnd ) && !IsIconic( hWnd ) )
                {
                    GetWindowRect( hWnd, &aTempRect );
                    if ( IntersectRect( &aTempRect2, &aSrcRect, &aTempRect ) )
                    {
                        // hWnd covers part or all of aSrcRect
                        if ( !hInvalidateRgn )
                            hInvalidateRgn = CreateRectRgnIndirect( &aSrcRect );

                        // get full bounding box of hWnd
                        hTempRgn = CreateRectRgnIndirect( &aTempRect );

                        // get region of hWnd (the window may be shaped)
                        if ( !hTempRgn2 )
                            hTempRgn2 = CreateRectRgn( 0, 0, 0, 0 );
                        int nRgnType = GetWindowRgn( hWnd, hTempRgn2 );
                        if ( (nRgnType != ERROR) && (nRgnType != NULLREGION) )
                        {
                            // convert window region to screen coordinates
                            OffsetRgn( hTempRgn2, aTempRect.left, aTempRect.top );
                            // and intersect with the window's bounding box
                            CombineRgn( hTempRgn, hTempRgn, hTempRgn2, RGN_AND );
                        }
                        // finally compute that part of aSrcRect which is not covered by any parts of hWnd
                        CombineRgn( hInvalidateRgn, hInvalidateRgn, hTempRgn, RGN_DIFF );
                        DeleteRegion( hTempRgn );
                    }
                }
                // retrieve the next window in the z-order, i.e. the window below hwnd
                hWnd = GetWindow( hWnd, GW_HWNDNEXT );
            }
            if ( hTempRgn2 )
                DeleteRegion( hTempRgn2 );
            if ( hInvalidateRgn )
            {
                // hInvalidateRgn contains the fully visible parts of the original srcRect
                hTempRgn = CreateRectRgnIndirect( &aSrcRect );
                // subtract it from the original rect to get the occluded parts
                int nRgnType = CombineRgn( hInvalidateRgn, hTempRgn, hInvalidateRgn, RGN_DIFF );
                DeleteRegion( hTempRgn );

                if ( (nRgnType != ERROR) && (nRgnType != NULLREGION) )
                {
                    // move the occluded parts to the destination pos
                    int nOffX = static_cast<int>(nDestX-nSrcX);
                    int nOffY = static_cast<int>(nDestY-nSrcY);
                    OffsetRgn( hInvalidateRgn, nOffX-aPt.x, nOffY-aPt.y );

                    // by excluding hInvalidateRgn from the system's clip region
                    // we will prevent bitblt from copying useless data
                    // especially now shadows from overlapping windows will appear (#i36344)
                    hOldClipRgn = CreateRectRgn( 0, 0, 0, 0 );
                    nOldClipRgnType = GetClipRgn( mrParent.getHDC(), hOldClipRgn );

                    bRestoreClipRgn = true; // indicate changed clipregion and force invalidate
                    ExtSelectClipRgn( mrParent.getHDC(), hInvalidateRgn, RGN_DIFF );
                }
            }
        }
    }

    BitBlt( mrParent.getHDC(),
            static_cast<int>(nDestX), static_cast<int>(nDestY),
            static_cast<int>(nSrcWidth), static_cast<int>(nSrcHeight),
            mrParent.getHDC(),
            static_cast<int>(nSrcX), static_cast<int>(nSrcY),
            SRCCOPY );

    if( bRestoreClipRgn )
    {
        // restore old clip region
        if( nOldClipRgnType != ERROR )
            SelectClipRgn( mrParent.getHDC(), hOldClipRgn);
        DeleteRegion( hOldClipRgn );

        // invalidate regions that were not copied
        bool    bInvalidate = true;

        // Combine Invalidate vcl::Region with existing ClipRegion
        HRGN    hTempRgn = CreateRectRgn( 0, 0, 0, 0 );
        if ( GetClipRgn( mrParent.getHDC(), hTempRgn ) == 1 )
        {
            int nRgnType = CombineRgn( hInvalidateRgn, hTempRgn, hInvalidateRgn, RGN_AND );
            if ( (nRgnType == ERROR) || (nRgnType == NULLREGION) )
                bInvalidate = false;
        }
        DeleteRegion( hTempRgn );

        if ( bInvalidate )
        {
            InvalidateRgn( mrParent.gethWnd(), hInvalidateRgn, TRUE );
            // here we only initiate an update if this is the MainThread,
            // so that there is no deadlock when handling the Paint event,
            // as the SolarMutex is already held by this Thread
            SalData*    pSalData = GetSalData();
            DWORD       nCurThreadId = GetCurrentThreadId();
            if ( pSalData->mnAppThreadId == nCurThreadId )
                UpdateWindow( mrParent.gethWnd() );
        }

        DeleteRegion( hInvalidateRgn );
    }

}

namespace {

void ImplDrawBitmap( HDC hDC, const SalTwoRect& rPosAry, const WinSalBitmap& rSalBitmap,
                     bool bPrinter, int nDrawMode )
{
    if( hDC )
    {
        HGLOBAL     hDrawDIB;
        HBITMAP     hDrawDDB = rSalBitmap.ImplGethDDB();
        std::unique_ptr<WinSalBitmap> xTmpSalBmp;
        bool        bPrintDDB = ( bPrinter && hDrawDDB );

        if( bPrintDDB )
        {
            xTmpSalBmp.reset(new WinSalBitmap);
            xTmpSalBmp->Create( rSalBitmap, rSalBitmap.GetBitCount() );
            hDrawDIB = xTmpSalBmp->ImplGethDIB();
        }
        else
            hDrawDIB = rSalBitmap.ImplGethDIB();

        if( hDrawDIB )
        {
            PBITMAPINFO         pBI = static_cast<PBITMAPINFO>(GlobalLock( hDrawDIB ));
            PBYTE               pBits = reinterpret_cast<PBYTE>(pBI) + pBI->bmiHeader.biSize +
                                        WinSalBitmap::ImplGetDIBColorCount( hDrawDIB ) * sizeof( RGBQUAD );
            const int           nOldStretchMode = SetStretchBltMode( hDC, STRETCH_DELETESCANS );

            StretchDIBits( hDC,
                           static_cast<int>(rPosAry.mnDestX), static_cast<int>(rPosAry.mnDestY),
                           static_cast<int>(rPosAry.mnDestWidth), static_cast<int>(rPosAry.mnDestHeight),
                           static_cast<int>(rPosAry.mnSrcX), static_cast<int>(pBI->bmiHeader.biHeight - rPosAry.mnSrcHeight - rPosAry.mnSrcY),
                           static_cast<int>(rPosAry.mnSrcWidth), static_cast<int>(rPosAry.mnSrcHeight),
                           pBits, pBI, DIB_RGB_COLORS, nDrawMode );

            GlobalUnlock( hDrawDIB );
            SetStretchBltMode( hDC, nOldStretchMode );
        }
        else if( hDrawDDB && !bPrintDDB )
        {
            ScopedCachedHDC<CACHED_HDC_DRAW> hBmpDC(hDrawDDB);

            COLORREF    nOldBkColor = RGB(0xFF,0xFF,0xFF);
            COLORREF    nOldTextColor = RGB(0,0,0);
            bool        bMono = ( rSalBitmap.GetBitCount() == 1 );

            if( bMono )
            {
                COLORREF nBkColor = RGB( 0xFF, 0xFF, 0xFF );
                COLORREF nTextColor = RGB( 0x00, 0x00, 0x00 );
                //fdo#33455 handle 1 bit depth pngs with palette entries
                //to set fore/back colors
                if (BitmapBuffer* pBitmapBuffer = const_cast<WinSalBitmap&>(rSalBitmap).AcquireBuffer(BitmapAccessMode::Info))
                {
                    const BitmapPalette& rPalette = pBitmapBuffer->maPalette;
                    if (rPalette.GetEntryCount() == 2)
                    {
                        Color nCol = rPalette[0];
                        nTextColor = RGB( nCol.GetRed(), nCol.GetGreen(), nCol.GetBlue() );
                        nCol = rPalette[1];
                        nBkColor = RGB( nCol.GetRed(), nCol.GetGreen(), nCol.GetBlue() );
                    }
                    const_cast<WinSalBitmap&>(rSalBitmap).ReleaseBuffer(pBitmapBuffer, BitmapAccessMode::Info);
                }
                nOldBkColor = SetBkColor( hDC, nBkColor );
                nOldTextColor = ::SetTextColor( hDC, nTextColor );
            }

            if ( (rPosAry.mnSrcWidth  == rPosAry.mnDestWidth) &&
                 (rPosAry.mnSrcHeight == rPosAry.mnDestHeight) )
            {
                BitBlt( hDC,
                        static_cast<int>(rPosAry.mnDestX), static_cast<int>(rPosAry.mnDestY),
                        static_cast<int>(rPosAry.mnDestWidth), static_cast<int>(rPosAry.mnDestHeight),
                        hBmpDC.get(),
                        static_cast<int>(rPosAry.mnSrcX), static_cast<int>(rPosAry.mnSrcY),
                        nDrawMode );
            }
            else
            {
                const int nOldStretchMode = SetStretchBltMode( hDC, STRETCH_DELETESCANS );

                StretchBlt( hDC,
                            static_cast<int>(rPosAry.mnDestX), static_cast<int>(rPosAry.mnDestY),
                            static_cast<int>(rPosAry.mnDestWidth), static_cast<int>(rPosAry.mnDestHeight),
                            hBmpDC.get(),
                            static_cast<int>(rPosAry.mnSrcX), static_cast<int>(rPosAry.mnSrcY),
                            static_cast<int>(rPosAry.mnSrcWidth), static_cast<int>(rPosAry.mnSrcHeight),
                            nDrawMode );

                SetStretchBltMode( hDC, nOldStretchMode );
            }

            if( bMono )
            {
                SetBkColor( hDC, nOldBkColor );
                ::SetTextColor( hDC, nOldTextColor );
            }
        }
    }
}

} // namespace

void WinSalGraphicsImpl::drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap)
{
    bool bTryDirectPaint(!mrParent.isPrinter() && !mbXORMode);

    if(bTryDirectPaint)
    {
        // only paint direct when no scaling and no MapMode, else the
        // more expensive conversions may be done for short-time Bitmap/BitmapEx
        // used for buffering only
        if(rPosAry.mnSrcWidth == rPosAry.mnDestWidth && rPosAry.mnSrcHeight == rPosAry.mnDestHeight)
        {
            bTryDirectPaint = false;
        }
    }

    // try to draw using GdiPlus directly
    if(bTryDirectPaint && TryDrawBitmapGDIPlus(rPosAry, rSalBitmap))
    {
        return;
    }

    // fall back old stuff
    assert(dynamic_cast<const WinSalBitmap*>(&rSalBitmap));

    ImplDrawBitmap(mrParent.getHDC(), rPosAry, static_cast<const WinSalBitmap&>(rSalBitmap),
        mrParent.isPrinter(),
        mbXORMode ? SRCINVERT : SRCCOPY );
}

void WinSalGraphicsImpl::drawBitmap( const SalTwoRect& rPosAry,
                              const SalBitmap& rSSalBitmap,
                              const SalBitmap& rSTransparentBitmap )
{
    SAL_WARN_IF( mrParent.isPrinter(), "vcl", "No transparency print possible!" );
    bool bTryDirectPaint(!mrParent.isPrinter() && !mbXORMode);

    // try to draw using GdiPlus directly
    if(bTryDirectPaint && drawAlphaBitmap(rPosAry, rSSalBitmap, rSTransparentBitmap))
    {
        return;
    }

    assert(dynamic_cast<const WinSalBitmap*>(&rSSalBitmap));
    assert(dynamic_cast<const WinSalBitmap*>(&rSTransparentBitmap));

    const WinSalBitmap& rSalBitmap = static_cast<const WinSalBitmap&>(rSSalBitmap);
    const WinSalBitmap& rTransparentBitmap = static_cast<const WinSalBitmap&>(rSTransparentBitmap);

    SalTwoRect  aPosAry = rPosAry;
    int         nDstX = static_cast<int>(aPosAry.mnDestX);
    int         nDstY = static_cast<int>(aPosAry.mnDestY);
    int         nDstWidth = static_cast<int>(aPosAry.mnDestWidth);
    int         nDstHeight = static_cast<int>(aPosAry.mnDestHeight);
    HDC         hDC = mrParent.getHDC();

    ScopedHBITMAP hMemBitmap;
    ScopedHBITMAP hMaskBitmap;

    if( ( nDstWidth > CACHED_HDC_DEFEXT ) || ( nDstHeight > CACHED_HDC_DEFEXT ) )
    {
        hMemBitmap.reset(CreateCompatibleBitmap(hDC, nDstWidth, nDstHeight));
        hMaskBitmap.reset(CreateCompatibleBitmap(hDC, nDstWidth, nDstHeight));
    }

    ScopedCachedHDC<CACHED_HDC_1> hMemDC(hMemBitmap.get());
    ScopedCachedHDC<CACHED_HDC_2> hMaskDC(hMaskBitmap.get());

    aPosAry.mnDestX = aPosAry.mnDestY = 0;
    BitBlt( hMemDC.get(), 0, 0, nDstWidth, nDstHeight, hDC, nDstX, nDstY, SRCCOPY );

    // WIN/WNT seems to have a minor problem mapping the correct color of the
    // mask to the palette if we draw the DIB directly ==> draw DDB
    if( ( GetBitCount() <= 8 ) && rTransparentBitmap.ImplGethDIB() && rTransparentBitmap.GetBitCount() == 1 )
    {
        WinSalBitmap aTmp;

        if( aTmp.Create( rTransparentBitmap, &mrParent ) )
            ImplDrawBitmap( hMaskDC.get(), aPosAry, aTmp, false, SRCCOPY );
    }
    else
        ImplDrawBitmap( hMaskDC.get(), aPosAry, rTransparentBitmap, false, SRCCOPY );

    // now MemDC contains background, MaskDC the transparency mask

    // #105055# Respect XOR mode
    if( mbXORMode )
    {
        ImplDrawBitmap( hMaskDC.get(), aPosAry, rSalBitmap, false, SRCERASE );
        // now MaskDC contains the bitmap area with black background
        BitBlt( hMemDC.get(), 0, 0, nDstWidth, nDstHeight, hMaskDC.get(), 0, 0, SRCINVERT );
        // now MemDC contains background XORed bitmap area ontop
    }
    else
    {
        BitBlt( hMemDC.get(), 0, 0, nDstWidth, nDstHeight, hMaskDC.get(), 0, 0, SRCAND );
        // now MemDC contains background with masked-out bitmap area
        ImplDrawBitmap( hMaskDC.get(), aPosAry, rSalBitmap, false, SRCERASE );
        // now MaskDC contains the bitmap area with black background
        BitBlt( hMemDC.get(), 0, 0, nDstWidth, nDstHeight, hMaskDC.get(), 0, 0, SRCPAINT );
        // now MemDC contains background and bitmap merged together
    }
    // copy to output DC
    BitBlt( hDC, nDstX, nDstY, nDstWidth, nDstHeight, hMemDC.get(), 0, 0, SRCCOPY );
}

bool WinSalGraphicsImpl::drawAlphaRect( tools::Long nX, tools::Long nY, tools::Long nWidth,
                                    tools::Long nHeight, sal_uInt8 nTransparency )
{
    if( mbPen || !mbBrush || mbXORMode )
        return false; // can only perform solid fills without XOR.

    ScopedCachedHDC<CACHED_HDC_1> hMemDC(nullptr);
    SetPixel( hMemDC.get(), int(0), int(0), mnBrushColor );

    BLENDFUNCTION aFunc = {
        AC_SRC_OVER,
        0,
        sal::static_int_cast<sal_uInt8>(255 - 255L*nTransparency/100),
        0
    };

    // hMemDC contains a 1x1 bitmap of the right color - stretch-blit
    // that to dest hdc
    bool bRet = GdiAlphaBlend(mrParent.getHDC(), nX, nY, nWidth, nHeight,
                              hMemDC.get(), 0,0,1,1,
                              aFunc ) == TRUE;

    return bRet;
}

void WinSalGraphicsImpl::drawMask(const SalTwoRect& rPosAry,
                                  const SalBitmap& rSSalBitmap,
                                  Color nMaskColor)
{
    SAL_WARN_IF( mrParent.isPrinter(), "vcl", "No transparency print possible!" );

    assert(dynamic_cast<const WinSalBitmap*>(&rSSalBitmap));

    const WinSalBitmap& rSalBitmap = static_cast<const WinSalBitmap&>(rSSalBitmap);

    SalTwoRect  aPosAry = rPosAry;
    const HDC hDC = mrParent.getHDC();

    ScopedSelectedHBRUSH hBrush(hDC, CreateSolidBrush(RGB(nMaskColor.GetRed(),
                                                          nMaskColor.GetGreen(),
                                                          nMaskColor.GetBlue())));

    // WIN/WNT seems to have a minor problem mapping the correct color of the
    // mask to the palette if we draw the DIB directly ==> draw DDB
    if( ( GetBitCount() <= 8 ) && rSalBitmap.ImplGethDIB() && rSalBitmap.GetBitCount() == 1 )
    {
        WinSalBitmap aTmp;

        if( aTmp.Create( rSalBitmap, &mrParent ) )
            ImplDrawBitmap( hDC, aPosAry, aTmp, false, 0x00B8074AUL );
    }
    else
        ImplDrawBitmap( hDC, aPosAry, rSalBitmap, false, 0x00B8074AUL );
}

std::shared_ptr<SalBitmap> WinSalGraphicsImpl::getBitmap( tools::Long nX, tools::Long nY, tools::Long nDX, tools::Long nDY )
{
    SAL_WARN_IF( mrParent.isPrinter(), "vcl", "No ::GetBitmap() from printer possible!" );

    std::shared_ptr<WinSalBitmap> pSalBitmap;

    nDX = std::abs( nDX );
    nDY = std::abs( nDY );

    HDC     hDC = mrParent.getHDC();
    HBITMAP hBmpBitmap = CreateCompatibleBitmap( hDC, nDX, nDY );
    bool    bRet;

    {
        ScopedCachedHDC<CACHED_HDC_1> hBmpDC(hBmpBitmap);

        bRet = BitBlt(hBmpDC.get(), 0, 0,
                      static_cast<int>(nDX), static_cast<int>(nDY), hDC,
                      static_cast<int>(nX), static_cast<int>(nY), SRCCOPY) ? TRUE : FALSE;
    }

    if( bRet )
    {
        pSalBitmap = std::make_shared<WinSalBitmap>();

        if( !pSalBitmap->Create( hBmpBitmap, false, false ) )
        {
            pSalBitmap.reset();
        }
    }
    else
    {
        // #124826# avoid resource leak! Happens when running without desktop access (remote desktop, service, may be screensavers)
        DeleteBitmap( hBmpBitmap );
    }

    return pSalBitmap;
}

Color WinSalGraphicsImpl::getPixel( tools::Long nX, tools::Long nY )
{
    COLORREF aWinCol = ::GetPixel( mrParent.getHDC(), static_cast<int>(nX), static_cast<int>(nY) );

    if ( CLR_INVALID == aWinCol )
        return Color( 0, 0, 0 );
    else
        return Color( GetRValue( aWinCol ),
                              GetGValue( aWinCol ),
                              GetBValue( aWinCol ) );
}

namespace
{

HBRUSH Get50PercentBrush()
{
    SalData* pSalData = GetSalData();
    if ( !pSalData->mh50Brush )
    {
        if ( !pSalData->mh50Bmp )
            pSalData->mh50Bmp = ImplLoadSalBitmap( SAL_RESID_BITMAP_50 );
        pSalData->mh50Brush = CreatePatternBrush( pSalData->mh50Bmp );
    }

    return pSalData->mh50Brush;
}

} // namespace

void WinSalGraphicsImpl::invert( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, SalInvert nFlags )
{
    if ( nFlags & SalInvert::TrackFrame )
    {
        HPEN    hDotPen = CreatePen( PS_DOT, 0, 0 );
        HPEN    hOldPen = SelectPen( mrParent.getHDC(), hDotPen );
        HBRUSH  hOldBrush = SelectBrush( mrParent.getHDC(), GetStockBrush( NULL_BRUSH ) );
        int     nOldROP = SetROP2( mrParent.getHDC(), R2_NOT );

        Rectangle( mrParent.getHDC(), static_cast<int>(nX), static_cast<int>(nY), static_cast<int>(nX+nWidth), static_cast<int>(nY+nHeight) );

        SetROP2( mrParent.getHDC(), nOldROP );
        SelectPen( mrParent.getHDC(), hOldPen );
        SelectBrush( mrParent.getHDC(), hOldBrush );
        DeletePen( hDotPen );
    }
    else if ( nFlags & SalInvert::N50 )
    {
        COLORREF nOldTextColor = ::SetTextColor( mrParent.getHDC(), 0 );
        HBRUSH hOldBrush = SelectBrush( mrParent.getHDC(), Get50PercentBrush() );
        PatBlt( mrParent.getHDC(), nX, nY, nWidth, nHeight, PATINVERT );
        ::SetTextColor( mrParent.getHDC(), nOldTextColor );
        SelectBrush( mrParent.getHDC(), hOldBrush );
    }
    else
    {
         RECT aRect;
         aRect.left      = static_cast<int>(nX);
         aRect.top       = static_cast<int>(nY);
         aRect.right     = static_cast<int>(nX)+nWidth;
         aRect.bottom    = static_cast<int>(nY)+nHeight;
         ::InvertRect( mrParent.getHDC(), &aRect );
    }
}

void WinSalGraphicsImpl::invert( sal_uInt32 nPoints, const Point* pPtAry, SalInvert nSalFlags )
{
    HPEN        hPen;
    HPEN        hOldPen;
    HBRUSH      hBrush;
    HBRUSH      hOldBrush = nullptr;
    COLORREF    nOldTextColor RGB(0,0,0);
    int         nOldROP = SetROP2( mrParent.getHDC(), R2_NOT );

    if ( nSalFlags & SalInvert::TrackFrame )
        hPen = CreatePen( PS_DOT, 0, 0 );
    else
    {

        if ( nSalFlags & SalInvert::N50 )
            hBrush = Get50PercentBrush();
        else
            hBrush = GetStockBrush( BLACK_BRUSH );

        hPen = GetStockPen( NULL_PEN );
        nOldTextColor = ::SetTextColor( mrParent.getHDC(), 0 );
        hOldBrush = SelectBrush( mrParent.getHDC(), hBrush );
    }
    hOldPen = SelectPen( mrParent.getHDC(), hPen );

    std::unique_ptr<POINT[]> pWinPtAry(new POINT[nPoints]);
    for (sal_uInt32 i=0; i<nPoints; ++i)
        pWinPtAry[i] = POINT { static_cast<LONG>(pPtAry[i].getX()), static_cast<LONG>(pPtAry[i].getY()) };

    // for Windows 95 and its maximum number of points
    if ( nSalFlags & SalInvert::TrackFrame )
    {
        if ( !Polyline( mrParent.getHDC(), pWinPtAry.get(), static_cast<int>(nPoints) ) && (nPoints > MAX_64KSALPOINTS) )
            Polyline( mrParent.getHDC(), pWinPtAry.get(), MAX_64KSALPOINTS );
    }
    else
    {
        if ( !Polygon( mrParent.getHDC(), pWinPtAry.get(), static_cast<int>(nPoints) ) && (nPoints > MAX_64KSALPOINTS) )
            Polygon( mrParent.getHDC(), pWinPtAry.get(), MAX_64KSALPOINTS );
    }

    SetROP2( mrParent.getHDC(), nOldROP );
    SelectPen( mrParent.getHDC(), hOldPen );

    if ( nSalFlags & SalInvert::TrackFrame )
        DeletePen( hPen );
    else
    {
        ::SetTextColor( mrParent.getHDC(), nOldTextColor );
        SelectBrush( mrParent.getHDC(), hOldBrush );
    }
}

sal_uInt16 WinSalGraphicsImpl::GetBitCount() const
{
    return static_cast<sal_uInt16>(GetDeviceCaps( mrParent.getHDC(), BITSPIXEL ));
}

tools::Long WinSalGraphicsImpl::GetGraphicsWidth() const
{
    if( mrParent.gethWnd() && IsWindow( mrParent.gethWnd() ) )
    {
        WinSalFrame* pFrame = GetWindowPtr( mrParent.gethWnd() );
        if( pFrame )
        {
            if( pFrame->maGeometry.nWidth )
                return pFrame->maGeometry.nWidth;
            else
            {
                // TODO: perhaps not needed, maGeometry should always be up-to-date
                RECT aRect;
                GetClientRect( mrParent.gethWnd(), &aRect );
                return aRect.right;
            }
        }
    }

    return 0;
}

void WinSalGraphicsImpl::ResetClipRegion()
{
    if ( mrParent.mhRegion )
    {
        DeleteRegion( mrParent.mhRegion );
        mrParent.mhRegion = nullptr;
    }

    SelectClipRgn( mrParent.getHDC(), nullptr );
}

static bool containsOnlyHorizontalAndVerticalEdges(const basegfx::B2DPolygon& rCandidate)
{
    if(rCandidate.areControlPointsUsed())
    {
        return false;
    }

    const sal_uInt32 nPointCount(rCandidate.count());

    if(nPointCount < 2)
    {
        return true;
    }

    const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount + 1 : nPointCount);
    basegfx::B2DPoint aLast(rCandidate.getB2DPoint(0));

    for(sal_uInt32 a(1); a < nEdgeCount; a++)
    {
        const sal_uInt32 nNextIndex(a % nPointCount);
        const basegfx::B2DPoint aCurrent(rCandidate.getB2DPoint(nNextIndex));

        if(!basegfx::fTools::equal(aLast.getX(), aCurrent.getX()) && !basegfx::fTools::equal(aLast.getY(), aCurrent.getY()))
        {
            return false;
        }

        aLast = aCurrent;
    }

    return true;
}

static bool containsOnlyHorizontalAndVerticalEdges(const basegfx::B2DPolyPolygon& rCandidate)
{
    if(rCandidate.areControlPointsUsed())
    {
        return false;
    }

    for(auto const& rPolygon : rCandidate)
    {
        if(!containsOnlyHorizontalAndVerticalEdges(rPolygon))
        {
            return false;
        }
    }

    return true;
}

bool WinSalGraphicsImpl::setClipRegion( const vcl::Region& i_rClip )
{
    if ( mrParent.mhRegion )
    {
        DeleteRegion( mrParent.mhRegion );
        mrParent.mhRegion = nullptr;
    }

    bool bUsePolygon(i_rClip.HasPolyPolygonOrB2DPolyPolygon());
    static bool bTryToAvoidPolygon(true);

    // #i122149# try to avoid usage of tools::PolyPolygon ClipRegions when tools::PolyPolygon is no curve
    // and only contains horizontal/vertical edges. In that case, use the fallback
    // in GetRegionRectangles which will use vcl::Region::GetAsRegionBand() which will do
    // the correct polygon-to-RegionBand transformation.
    // Background is that when using the same Rectangle as rectangle or as Polygon
    // clip region will lead to different results; the polygon-based one will be
    // one pixel less to the right and down (see GDI docu for CreatePolygonRgn). This
    // again is because of the polygon-nature and it's classic handling when filling.
    // This also means that all cases which use a 'true' polygon-based incarnation of
    // a vcl::Region should know what they do - it may lead to repaint errors.
    if(bUsePolygon && bTryToAvoidPolygon)
    {
        const basegfx::B2DPolyPolygon aPolyPolygon( i_rClip.GetAsB2DPolyPolygon() );

        if(!aPolyPolygon.areControlPointsUsed())
        {
            if(containsOnlyHorizontalAndVerticalEdges(aPolyPolygon))
            {
                bUsePolygon = false;
            }
        }
    }

    if(bUsePolygon)
    {
        // #i122149# check the comment above to know that this may lead to potential repaint
        // problems. It may be solved (if needed) by scaling the polygon by one in X
        // and Y. Currently the workaround to only use it if really unavoidable will
        // solve most cases. When someone is really using polygon-based Regions he
        // should know what he is doing.
        // Added code to do that scaling to check if it works, testing it.
        const basegfx::B2DPolyPolygon aPolyPolygon( i_rClip.GetAsB2DPolyPolygon() );
        const sal_uInt32 nCount(aPolyPolygon.count());

        if( nCount )
        {
            std::vector< POINT > aPolyPoints;
            aPolyPoints.reserve( 1024 );
            std::vector< INT > aPolyCounts( nCount, 0 );
            basegfx::B2DHomMatrix aExpand;
            sal_uInt32 nTargetCount(0);
            static bool bExpandByOneInXandY(true);

            if(bExpandByOneInXandY)
            {
                const basegfx::B2DRange aRangeS(aPolyPolygon.getB2DRange());
                const basegfx::B2DRange aRangeT(aRangeS.getMinimum(), aRangeS.getMaximum() + basegfx::B2DTuple(1.0, 1.0));
                aExpand = basegfx::utils::createSourceRangeTargetRangeTransform(aRangeS, aRangeT);
            }

            for(auto const& rPolygon : aPolyPolygon)
            {
                const basegfx::B2DPolygon aPoly(
                    basegfx::utils::adaptiveSubdivideByDistance(
                        rPolygon,
                        1));
                const sal_uInt32 nPoints(aPoly.count());

                // tdf#40863 For CustomShapes there is a hack (see
                // f64ef72743e55389e446e0d4bc6febd475011023) that adds polygons
                // with a single point in top-left and bottom-right corner
                // of the BoundRect to be able to determine the correct BoundRect
                // in the slideshow. Unfortunately, CreatePolyPolygonRgn below
                // fails with polygons containing a single pixel, so clipping is
                // lost. For now, use only polygons with more than two points - the
                // ones that may have an area.
                // Note: polygons with one point which are curves may have an area,
                // but the polygon is already subdivided here, so no need to test
                // this.
                if(nPoints > 2)
                {
                    aPolyCounts[nTargetCount] = nPoints;
                    nTargetCount++;

                    for( sal_uInt32 b = 0; b < nPoints; b++ )
                    {
                        basegfx::B2DPoint aPt(aPoly.getB2DPoint(b));

                        if(bExpandByOneInXandY)
                        {
                            aPt = aExpand * aPt;
                        }

                        POINT aPOINT;
                        // #i122149# do correct rounding
                        aPOINT.x = basegfx::fround(aPt.getX());
                        aPOINT.y = basegfx::fround(aPt.getY());
                        aPolyPoints.push_back( aPOINT );
                    }
                }
            }

            if(nTargetCount)
            {
                mrParent.mhRegion = CreatePolyPolygonRgn( aPolyPoints.data(), aPolyCounts.data(), nTargetCount, ALTERNATE );
            }
        }
    }
    else
    {
        RectangleVector aRectangles;
        i_rClip.GetRegionRectangles(aRectangles);

        sal_uLong nRectBufSize = sizeof(RECT)*aRectangles.size();
        if ( aRectangles.size() < SAL_CLIPRECT_COUNT )
        {
            if ( !mrParent.mpStdClipRgnData )
                mrParent.mpStdClipRgnData = reinterpret_cast<RGNDATA*>(new BYTE[sizeof(RGNDATA)-1+(SAL_CLIPRECT_COUNT*sizeof(RECT))]);
            mrParent.mpClipRgnData = mrParent.mpStdClipRgnData;
        }
        else
            mrParent.mpClipRgnData = reinterpret_cast<RGNDATA*>(new BYTE[sizeof(RGNDATA)-1+nRectBufSize]);
        mrParent.mpClipRgnData->rdh.dwSize   = sizeof( RGNDATAHEADER );
        mrParent.mpClipRgnData->rdh.iType    = RDH_RECTANGLES;
        mrParent.mpClipRgnData->rdh.nCount   = aRectangles.size();
        mrParent.mpClipRgnData->rdh.nRgnSize = nRectBufSize;
        RECT*       pBoundRect = &(mrParent.mpClipRgnData->rdh.rcBound);
        SetRectEmpty( pBoundRect );
        RECT* pNextClipRect         = reinterpret_cast<RECT*>(&(mrParent.mpClipRgnData->Buffer));
        bool bFirstClipRect         = true;

        for (auto const& rectangle : aRectangles)
        {
            const tools::Long nW(rectangle.GetWidth());
            const tools::Long nH(rectangle.GetHeight());

            if(nW && nH)
            {
                const tools::Long nRight(rectangle.Left() + nW);
                const tools::Long nBottom(rectangle.Top() + nH);

                if(bFirstClipRect)
                {
                    pBoundRect->left = rectangle.Left();
                    pBoundRect->top = rectangle.Top();
                    pBoundRect->right = nRight;
                    pBoundRect->bottom = nBottom;
                    bFirstClipRect = false;
                }
                else
                {
                    if(rectangle.Left() < pBoundRect->left)
                    {
                        pBoundRect->left = static_cast<int>(rectangle.Left());
                    }

                    if(rectangle.Top() < pBoundRect->top)
                    {
                        pBoundRect->top = static_cast<int>(rectangle.Top());
                    }

                    if(nRight > pBoundRect->right)
                    {
                        pBoundRect->right = static_cast<int>(nRight);
                    }

                    if(nBottom > pBoundRect->bottom)
                    {
                        pBoundRect->bottom = static_cast<int>(nBottom);
                    }
                }

                pNextClipRect->left = static_cast<int>(rectangle.Left());
                pNextClipRect->top = static_cast<int>(rectangle.Top());
                pNextClipRect->right = static_cast<int>(nRight);
                pNextClipRect->bottom = static_cast<int>(nBottom);
                pNextClipRect++;
            }
            else
            {
                mrParent.mpClipRgnData->rdh.nCount--;
                mrParent.mpClipRgnData->rdh.nRgnSize -= sizeof( RECT );
            }
        }

        // create clip region from ClipRgnData
        if(0 == mrParent.mpClipRgnData->rdh.nCount)
        {
            // #i123585# region is empty; this may happen when e.g. a tools::PolyPolygon is given
            // that contains no polygons or only empty ones (no width/height). This is
            // perfectly fine and we are done, except setting it (see end of method)
        }
        else if(1 == mrParent.mpClipRgnData->rdh.nCount)
        {
            RECT* pRect = &(mrParent.mpClipRgnData->rdh.rcBound);
            mrParent.mhRegion = CreateRectRgn( pRect->left, pRect->top,
                                                     pRect->right, pRect->bottom );
        }
        else if(mrParent.mpClipRgnData->rdh.nCount > 1)
        {
            sal_uLong nSize = mrParent.mpClipRgnData->rdh.nRgnSize+sizeof(RGNDATAHEADER);
            mrParent.mhRegion = ExtCreateRegion( nullptr, nSize, mrParent.mpClipRgnData );

            // if ExtCreateRegion(...) is not supported
            if( !mrParent.mhRegion )
            {
                RGNDATAHEADER const & pHeader = mrParent.mpClipRgnData->rdh;

                if( pHeader.nCount )
                {
                    RECT* pRect = reinterpret_cast<RECT*>(mrParent.mpClipRgnData->Buffer);
                    mrParent.mhRegion = CreateRectRgn( pRect->left, pRect->top, pRect->right, pRect->bottom );
                    pRect++;

                    for( sal_uLong n = 1; n < pHeader.nCount; n++, pRect++ )
                    {
                        ScopedHRGN hRgn(CreateRectRgn(pRect->left, pRect->top, pRect->right, pRect->bottom));
                        CombineRgn( mrParent.mhRegion, mrParent.mhRegion, hRgn.get(), RGN_OR );
                    }
                }
            }

            if ( mrParent.mpClipRgnData != mrParent.mpStdClipRgnData )
                delete [] reinterpret_cast<BYTE*>(mrParent.mpClipRgnData);
        }
    }

    if( mrParent.mhRegion )
    {
        SelectClipRgn( mrParent.getHDC(), mrParent.mhRegion );

        // debug code if you want to check range of the newly applied ClipRegion
        //RECT aBound;
        //const int aRegionType = GetRgnBox(mrParent.mhRegion, &aBound);

        //bool bBla = true;
    }
    else
    {
        // #i123585# See above, this is a valid case, execute it
        SelectClipRgn( mrParent.getHDC(), nullptr );
    }

    // #i123585# retval no longer dependent of mrParent.mhRegion, see TaskId comments above
    return true;
}

void WinSalGraphicsImpl::SetLineColor()
{
    ResetPen(GetStockPen(NULL_PEN));

    // set new data
    mbPen       = false;
    mbStockPen  = true;
}

void WinSalGraphicsImpl::SetLineColor(Color nColor)
{
    COLORREF nPenColor = PALETTERGB(nColor.GetRed(),
                                    nColor.GetGreen(),
                                    nColor.GetBlue());
    bool bStockPen = false;

    HPEN hNewPen = SearchStockPen(nPenColor);
    if (hNewPen)
        bStockPen = true;
    else
        hNewPen = MakePen(nColor);

    ResetPen(hNewPen);

    // set new data
    mnPenColor  = nPenColor;
    maLineColor = nColor;
    mbPen       = true;
    mbStockPen  = bStockPen;
}

HPEN WinSalGraphicsImpl::SearchStockPen(COLORREF nPenColor)
{
    // Only screen, because printer has problems, when we use stock objects.
    if (!mrParent.isPrinter())
    {
        const SalData* pSalData = GetSalData();

        for (sal_uInt16 i = 0; i < pSalData->mnStockPenCount; i++)
        {
            if (nPenColor == pSalData->maStockPenColorAry[i])
                return pSalData->mhStockPenAry[i];
        }
    }

    return nullptr;
}

HPEN WinSalGraphicsImpl::MakePen(Color nColor)
{
    COLORREF nPenColor = PALETTERGB(nColor.GetRed(),
                                    nColor.GetGreen(),
                                    nColor.GetBlue());

    if (!mrParent.isPrinter())
    {
        if (GetSalData()->mhDitherPal && ImplIsSysColorEntry(nColor))
        {
            nPenColor = PALRGB_TO_RGB(nPenColor);
        }
    }

    return CreatePen(PS_SOLID, mrParent.mnPenWidth, nPenColor);
}

void WinSalGraphicsImpl::ResetPen(HPEN hNewPen)
{
    HPEN hOldPen = SelectPen(mrParent.getHDC(), hNewPen);

    if (mhPen)
    {
        if (!mbStockPen)
        {
            DeletePen(mhPen);
        }
    }
    else
    {
        mrParent.mhDefPen = hOldPen;
    }

    mhPen = hNewPen;
}

void WinSalGraphicsImpl::SetFillColor()
{
    ResetBrush(GetStockBrush(NULL_BRUSH));

    // set new data
    mbBrush     = false;
    mbStockBrush = true;
}

void WinSalGraphicsImpl::SetFillColor(Color nColor)
{
    COLORREF nBrushColor = PALETTERGB(nColor.GetRed(),
                                      nColor.GetGreen(),
                                      nColor.GetBlue());
    bool bStockBrush = false;

    HBRUSH hNewBrush = SearchStockBrush(nBrushColor);
    if (hNewBrush)
        bStockBrush = true;
    else
        hNewBrush = MakeBrush(nColor);

    ResetBrush(hNewBrush);

    // set new data
    mnBrushColor = nBrushColor;
    maFillColor = nColor;
    mbBrush     = true;
    mbStockBrush = bStockBrush;
}

HBRUSH WinSalGraphicsImpl::SearchStockBrush(COLORREF nBrushColor)
{
    // Only screen, because printer has problems, when we use stock objects.
    if (!mrParent.isPrinter())
    {
        const SalData* pSalData = GetSalData();

        for (sal_uInt16 i = 0; i < pSalData->mnStockBrushCount; i++)
        {
            if (nBrushColor == pSalData->maStockBrushColorAry[i])
                return pSalData->mhStockBrushAry[i];
        }
    }

    return nullptr;
}

namespace
{

BYTE GetDitherMappingValue(BYTE nVal, BYTE nThres, const SalData* pSalData)
{
    return (pSalData->mpDitherDiff[nVal] > nThres) ?
        pSalData->mpDitherHigh[nVal] : pSalData->mpDitherLow[nVal];
}

HBRUSH Make16BitDIBPatternBrush(Color nColor)
{
    const SalData* pSalData = GetSalData();

    const BYTE nRed   = nColor.GetRed();
    const BYTE nGreen = nColor.GetGreen();
    const BYTE nBlue  = nColor.GetBlue();

    static const BYTE aOrdDither16Bit[8][8] =
    {
       { 0, 6, 1, 7, 0, 6, 1, 7 },
       { 4, 2, 5, 3, 4, 2, 5, 3 },
       { 1, 7, 0, 6, 1, 7, 0, 6 },
       { 5, 3, 4, 2, 5, 3, 4, 2 },
       { 0, 6, 1, 7, 0, 6, 1, 7 },
       { 4, 2, 5, 3, 4, 2, 5, 3 },
       { 1, 7, 0, 6, 1, 7, 0, 6 },
       { 5, 3, 4, 2, 5, 3, 4, 2 }
    };

    BYTE* pTmp = pSalData->mpDitherDIBData;

    for(int nY = 0; nY < 8; ++nY)
    {
        for(int nX = 0; nX < 8; ++nX)
        {
            const BYTE nThres = aOrdDither16Bit[nY][nX];
            *pTmp++ = GetDitherMappingValue(nBlue, nThres, pSalData);
            *pTmp++ = GetDitherMappingValue(nGreen, nThres, pSalData);
            *pTmp++ = GetDitherMappingValue(nRed, nThres, pSalData);
        }
    }

    return CreateDIBPatternBrush(pSalData->mhDitherDIB, DIB_RGB_COLORS);
}

HBRUSH Make8BitDIBPatternBrush(Color nColor)
{
    const SalData* pSalData = GetSalData();

    const BYTE nRed   = nColor.GetRed();
    const BYTE nGreen = nColor.GetGreen();
    const BYTE nBlue  = nColor.GetBlue();

    static const BYTE aOrdDither8Bit[8][8] =
    {
       {  0, 38,  9, 48,  2, 40, 12, 50 },
       { 25, 12, 35, 22, 28, 15, 37, 24 },
       {  6, 44,  3, 41,  8, 47,  5, 44 },
       { 32, 19, 28, 16, 34, 21, 31, 18 },
       {  1, 40, 11, 49,  0, 39, 10, 48 },
       { 27, 14, 36, 24, 26, 13, 36, 23 },
       {  8, 46,  4, 43,  7, 45,  4, 42 },
       { 33, 20, 30, 17, 32, 20, 29, 16 }
    };

    BYTE* pTmp = pSalData->mpDitherDIBData;

    for (int nY = 0; nY < 8; ++nY)
    {
        for (int nX = 0; nX < 8; ++nX)
        {
            const BYTE nThres = aOrdDither8Bit[nY][nX];
            *pTmp = GetDitherMappingValue(nRed, nThres, pSalData) +
                    GetDitherMappingValue(nGreen, nThres, pSalData) * 6 +
                    GetDitherMappingValue(nBlue, nThres, pSalData) * 36;
            pTmp++;
        }
    }

    return CreateDIBPatternBrush(pSalData->mhDitherDIB, DIB_PAL_COLORS);
}

} // namespace

HBRUSH WinSalGraphicsImpl::MakeBrush(Color nColor)
{
    const SalData* pSalData = GetSalData();

    const BYTE        nRed        = nColor.GetRed();
    const BYTE        nGreen      = nColor.GetGreen();
    const BYTE        nBlue       = nColor.GetBlue();
    const COLORREF    nBrushColor = PALETTERGB(nRed, nGreen, nBlue);

    if (mrParent.isPrinter() || !pSalData->mhDitherDIB)
        return CreateSolidBrush(nBrushColor);

    if (24 == reinterpret_cast<BITMAPINFOHEADER*>(pSalData->mpDitherDIB)->biBitCount)
        return Make16BitDIBPatternBrush(nColor);

    if (ImplIsSysColorEntry(nColor))
        return CreateSolidBrush(PALRGB_TO_RGB(nBrushColor));

    if (ImplIsPaletteEntry(nRed, nGreen, nBlue))
        return CreateSolidBrush(nBrushColor);

    return Make8BitDIBPatternBrush(nColor);
}

void WinSalGraphicsImpl::ResetBrush(HBRUSH hNewBrush)
{
    HBRUSH hOldBrush = SelectBrush(mrParent.getHDC(), hNewBrush);

    if (mhBrush)
    {
        if (!mbStockBrush)
        {
            DeleteBrush(mhBrush);
        }
    }
    else
    {
        mrParent.mhDefBrush = hOldBrush;
    }

    mhBrush = hNewBrush;
}

void WinSalGraphicsImpl::SetXORMode( bool bSet, bool )
{
    mbXORMode = bSet;
    ::SetROP2( mrParent.getHDC(), bSet ? R2_XORPEN : R2_COPYPEN );
}

void WinSalGraphicsImpl::SetROPLineColor( SalROPColor nROPColor )
{
    SetLineColor( ImplGetROPColor( nROPColor ) );
}

void WinSalGraphicsImpl::SetROPFillColor( SalROPColor nROPColor )
{
    SetFillColor( ImplGetROPColor( nROPColor ) );
}

void WinSalGraphicsImpl::DrawPixelImpl( tools::Long nX, tools::Long nY, COLORREF crColor )
{
    const HDC hDC = mrParent.getHDC();

    if (!mbXORMode)
    {
        SetPixel(hDC, static_cast<int>(nX), static_cast<int>(nY), crColor);
        return;
    }

    ScopedSelectedHBRUSH hBrush(hDC, CreateSolidBrush(crColor));
    PatBlt(hDC, static_cast<int>(nX), static_cast<int>(nY), int(1), int(1), PATINVERT);
}

void WinSalGraphicsImpl::drawPixel( tools::Long nX, tools::Long nY )
{
    DrawPixelImpl( nX, nY, mnPenColor );
}

void WinSalGraphicsImpl::drawPixel( tools::Long nX, tools::Long nY, Color nColor )
{
    COLORREF nCol = PALETTERGB( nColor.GetRed(),
                                nColor.GetGreen(),
                                nColor.GetBlue() );

    if ( !mrParent.isPrinter() &&
         GetSalData()->mhDitherPal &&
         ImplIsSysColorEntry( nColor ) )
        nCol = PALRGB_TO_RGB( nCol );

    DrawPixelImpl( nX, nY, nCol );
}

void WinSalGraphicsImpl::drawLine( tools::Long nX1, tools::Long nY1, tools::Long nX2, tools::Long nY2 )
{
    MoveToEx( mrParent.getHDC(), static_cast<int>(nX1), static_cast<int>(nY1), nullptr );

    LineTo( mrParent.getHDC(), static_cast<int>(nX2), static_cast<int>(nY2) );

    // LineTo doesn't draw the last pixel
    if ( !mrParent.isPrinter() )
        DrawPixelImpl( nX2, nY2, mnPenColor );
}

void WinSalGraphicsImpl::drawRect( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight )
{
    if ( !mbPen )
    {
        if ( !mrParent.isPrinter() )
        {
            PatBlt( mrParent.getHDC(), static_cast<int>(nX), static_cast<int>(nY), static_cast<int>(nWidth), static_cast<int>(nHeight),
                    mbXORMode ? PATINVERT : PATCOPY );
        }
        else
        {
            RECT aWinRect;
            aWinRect.left   = nX;
            aWinRect.top    = nY;
            aWinRect.right  = nX+nWidth;
            aWinRect.bottom = nY+nHeight;
            ::FillRect( mrParent.getHDC(), &aWinRect, mhBrush );
        }
    }
    else
        Rectangle( mrParent.getHDC(), static_cast<int>(nX), static_cast<int>(nY), static_cast<int>(nX+nWidth), static_cast<int>(nY+nHeight) );
}

void WinSalGraphicsImpl::drawPolyLine( sal_uInt32 nPoints, const Point* pPtAry )
{
    std::unique_ptr<POINT[]> pWinPtAry(new POINT[nPoints]);
    for (sal_uInt32 i=0; i<nPoints; ++i)
        pWinPtAry[i] = POINT { static_cast<LONG>(pPtAry[i].getX()), static_cast<LONG>(pPtAry[i].getY()) };

    // for Windows 95 and its maximum number of points
    if ( !Polyline( mrParent.getHDC(), pWinPtAry.get(), static_cast<int>(nPoints) ) && (nPoints > MAX_64KSALPOINTS) )
        Polyline( mrParent.getHDC(), pWinPtAry.get(), MAX_64KSALPOINTS );

    // Polyline seems to uses LineTo, which doesn't paint the last pixel (see 87eb8f8ee)
    if ( !mrParent.isPrinter() )
        DrawPixelImpl( pWinPtAry[nPoints-1].x, pWinPtAry[nPoints-1].y, mnPenColor );
}

void WinSalGraphicsImpl::drawPolygon( sal_uInt32 nPoints, const Point* pPtAry )
{
    std::unique_ptr<POINT[]> pWinPtAry(new POINT[nPoints]);
    for (sal_uInt32 i=0; i<nPoints; ++i)
        pWinPtAry[i] = POINT { static_cast<LONG>(pPtAry[i].getX()), static_cast<LONG>(pPtAry[i].getY()) };

    // for Windows 95 and its maximum number of points
    if ( !Polygon( mrParent.getHDC(), pWinPtAry.get(), static_cast<int>(nPoints) ) && (nPoints > MAX_64KSALPOINTS) )
        Polygon( mrParent.getHDC(), pWinPtAry.get(), MAX_64KSALPOINTS );
}

void WinSalGraphicsImpl::drawPolyPolygon( sal_uInt32 nPoly, const sal_uInt32* pPoints,
                                   const Point** pPtAry )
{
    UINT    aWinPointAry[SAL_POLYPOLYCOUNT_STACKBUF];
    UINT*   pWinPointAry;
    UINT    nPolyPolyPoints = 0;
    UINT    nPoints;
    UINT    i;

    if ( nPoly <= SAL_POLYPOLYCOUNT_STACKBUF )
        pWinPointAry = aWinPointAry;
    else
        pWinPointAry = new UINT[nPoly];

    for ( i = 0; i < static_cast<UINT>(nPoly); i++ )
    {
        nPoints = static_cast<UINT>(pPoints[i])+1;
        pWinPointAry[i] = nPoints;
        nPolyPolyPoints += nPoints;
    }

    POINT  aWinPointAryAry[SAL_POLYPOLYPOINTS_STACKBUF];
    POINT* pWinPointAryAry;
    if ( nPolyPolyPoints <= SAL_POLYPOLYPOINTS_STACKBUF )
        pWinPointAryAry = aWinPointAryAry;
    else
        pWinPointAryAry = new POINT[nPolyPolyPoints];
    UINT            n = 0;
    for ( i = 0; i < static_cast<UINT>(nPoly); i++ )
    {
        nPoints = pWinPointAry[i];
        const Point* pPolyAry = pPtAry[i];
        for (sal_uInt32 j=0; j<nPoints-1; ++j)
            pWinPointAryAry[n+j] = POINT { static_cast<LONG>(pPolyAry[j].getX()), static_cast<LONG>(pPolyAry[j].getY()) };
        pWinPointAryAry[n+nPoints-1] = pWinPointAryAry[n];
        n += nPoints;
    }

    if ( !PolyPolygon( mrParent.getHDC(), pWinPointAryAry, reinterpret_cast<int*>(pWinPointAry), static_cast<UINT>(nPoly) ) &&
         (nPolyPolyPoints > MAX_64KSALPOINTS) )
    {
        nPolyPolyPoints  = 0;
        nPoly = 0;
        do
        {
            nPolyPolyPoints += pWinPointAry[static_cast<UINT>(nPoly)];
            nPoly++;
        }
        while ( nPolyPolyPoints < MAX_64KSALPOINTS );
        nPoly--;
        if ( pWinPointAry[static_cast<UINT>(nPoly)] > MAX_64KSALPOINTS )
            pWinPointAry[static_cast<UINT>(nPoly)] = MAX_64KSALPOINTS;
        if ( nPoly == 1 )
            Polygon( mrParent.getHDC(), pWinPointAryAry, *pWinPointAry );
        else
            PolyPolygon( mrParent.getHDC(), pWinPointAryAry, reinterpret_cast<int*>(pWinPointAry), nPoly );
    }

    if ( pWinPointAry != aWinPointAry )
        delete [] pWinPointAry;
    if ( pWinPointAryAry != aWinPointAryAry )
        delete [] pWinPointAryAry;
}

bool WinSalGraphicsImpl::drawPolyLineBezier( sal_uInt32 nPoints, const Point* pPtAry, const PolyFlags* pFlgAry )
{
    // #100127# draw an array of points which might also contain bezier control points
    if (!nPoints)
        return true;

    const HDC hdc = mrParent.getHDC();

    // TODO: profile whether the following options are faster:
    // a) look ahead and draw consecutive bezier or line segments by PolyBezierTo/PolyLineTo resp.
    // b) convert our flag array to window's and use PolyDraw
    MoveToEx(hdc, static_cast<LONG>(pPtAry->getX()), static_cast<LONG>(pPtAry->getY()), nullptr);
    ++pPtAry;
    ++pFlgAry;

    for(sal_uInt32 i = 1; i < nPoints; ++i)
    {
        if(*pFlgAry != PolyFlags::Control)
        {
            LineTo(hdc, pPtAry->getX(), pPtAry->getY());
        }
        else if(nPoints - i > 2)
        {
            POINT bezierPoints[] = {
                POINT { static_cast<LONG>(pPtAry[0].getX()), static_cast<LONG>(pPtAry[0].getY()) },
                POINT { static_cast<LONG>(pPtAry[1].getX()), static_cast<LONG>(pPtAry[1].getY()) },
                POINT { static_cast<LONG>(pPtAry[2].getX()), static_cast<LONG>(pPtAry[2].getY()) },
            };
            PolyBezierTo(hdc, bezierPoints, 3);
            i += 2;
            pPtAry += 2;
            pFlgAry += 2;
        }

        ++pPtAry;
        ++pFlgAry;
    }

    return true;
}

bool WinSalGraphicsImpl::drawPolygonBezier( sal_uInt32 nPoints, const Point* pPtAry, const PolyFlags* pFlgAry )
{
    POINT   aStackAry1[SAL_POLY_STACKBUF];
    BYTE    aStackAry2[SAL_POLY_STACKBUF];
    POINT*  pWinPointAry;
    BYTE*   pWinFlagAry;
    if( nPoints > SAL_POLY_STACKBUF )
    {
        pWinPointAry = new POINT[ nPoints ];
        pWinFlagAry = new BYTE[ nPoints ];
    }
    else
    {
        pWinPointAry = aStackAry1;
        pWinFlagAry = aStackAry2;
    }

    sal_uInt32 nPoints_i32(nPoints);
    ImplPreparePolyDraw(true, 1, &nPoints_i32, &pPtAry, &pFlgAry, pWinPointAry, pWinFlagAry);

    bool bRet( false );

    if( BeginPath( mrParent.getHDC() ) )
    {
        PolyDraw(mrParent.getHDC(), pWinPointAry, pWinFlagAry, nPoints);

        if( EndPath( mrParent.getHDC() ) )
        {
            if( StrokeAndFillPath( mrParent.getHDC() ) )
                bRet = true;
        }
    }

    if( pWinPointAry != aStackAry1 )
    {
        delete [] pWinPointAry;
        delete [] pWinFlagAry;
    }

    return bRet;
}

bool WinSalGraphicsImpl::drawPolyPolygonBezier( sal_uInt32 nPoly, const sal_uInt32* pPoints,
                                             const Point* const* pPtAry, const PolyFlags* const* pFlgAry )
{
    sal_uLong nCurrPoly, nTotalPoints;
    const sal_uInt32* pCurrPoints = pPoints;
    for( nCurrPoly=0, nTotalPoints=0; nCurrPoly<nPoly; ++nCurrPoly )
        nTotalPoints += *pCurrPoints++;

    POINT      aStackAry1[SAL_POLY_STACKBUF];
    BYTE       aStackAry2[SAL_POLY_STACKBUF];
    POINT*     pWinPointAry;
    BYTE*      pWinFlagAry;
    if( nTotalPoints > SAL_POLY_STACKBUF )
    {
        pWinPointAry = new POINT[ nTotalPoints ];
        pWinFlagAry = new BYTE[ nTotalPoints ];
    }
    else
    {
        pWinPointAry = aStackAry1;
        pWinFlagAry = aStackAry2;
    }

    ImplPreparePolyDraw(true, nPoly, pPoints, pPtAry, pFlgAry, pWinPointAry, pWinFlagAry);

    bool bRet( false );

    if( BeginPath( mrParent.getHDC() ) )
    {
        PolyDraw(mrParent.getHDC(), pWinPointAry, pWinFlagAry, nTotalPoints);

        if( EndPath( mrParent.getHDC() ) )
        {
            if( StrokeAndFillPath( mrParent.getHDC() ) )
                bRet = true;
        }
    }

    if( pWinPointAry != aStackAry1 )
    {
        delete [] pWinPointAry;
        delete [] pWinFlagAry;
    }

    return bRet;
}

static basegfx::B2DPoint impPixelSnap(
    const basegfx::B2DPolygon& rPolygon,
    const basegfx::B2DHomMatrix& rObjectToDevice,
    basegfx::B2DHomMatrix& rObjectToDeviceInv,
    sal_uInt32 nIndex)
{
    const sal_uInt32 nCount(rPolygon.count());

    // get the data
    const basegfx::B2ITuple aPrevTuple(basegfx::fround(rObjectToDevice * rPolygon.getB2DPoint((nIndex + nCount - 1) % nCount)));
    const basegfx::B2DPoint aCurrPoint(rObjectToDevice * rPolygon.getB2DPoint(nIndex));
    const basegfx::B2ITuple aCurrTuple(basegfx::fround(aCurrPoint));
    const basegfx::B2ITuple aNextTuple(basegfx::fround(rObjectToDevice * rPolygon.getB2DPoint((nIndex + 1) % nCount)));

    // get the states
    const bool bPrevVertical(aPrevTuple.getX() == aCurrTuple.getX());
    const bool bNextVertical(aNextTuple.getX() == aCurrTuple.getX());
    const bool bPrevHorizontal(aPrevTuple.getY() == aCurrTuple.getY());
    const bool bNextHorizontal(aNextTuple.getY() == aCurrTuple.getY());
    const bool bSnapX(bPrevVertical || bNextVertical);
    const bool bSnapY(bPrevHorizontal || bNextHorizontal);

    if(bSnapX || bSnapY)
    {
        basegfx::B2DPoint aSnappedPoint(
            bSnapX ? aCurrTuple.getX() : aCurrPoint.getX(),
            bSnapY ? aCurrTuple.getY() : aCurrPoint.getY());

        if(rObjectToDeviceInv.isIdentity())
        {
            rObjectToDeviceInv = rObjectToDevice;
            rObjectToDeviceInv.invert();
        }

        aSnappedPoint *= rObjectToDeviceInv;

        return aSnappedPoint;
    }

    return rPolygon.getB2DPoint(nIndex);
}

static void impAddB2DPolygonToGDIPlusGraphicsPathReal(
    Gdiplus::GraphicsPath& rGraphicsPath,
    const basegfx::B2DPolygon& rPolygon,
    const basegfx::B2DHomMatrix& rObjectToDevice,
    bool bNoLineJoin,
    bool bPixelSnapHairline)
{
    sal_uInt32 nCount(rPolygon.count());

    if(nCount)
    {
        const sal_uInt32 nEdgeCount(rPolygon.isClosed() ? nCount : nCount - 1);

        if(nEdgeCount)
        {
            const bool bControls(rPolygon.areControlPointsUsed());
            basegfx::B2DPoint aCurr(rPolygon.getB2DPoint(0));
            basegfx::B2DHomMatrix aObjectToDeviceInv;

            if(bPixelSnapHairline)
            {
                aCurr = impPixelSnap(rPolygon, rObjectToDevice, aObjectToDeviceInv, 0);
            }

            for(sal_uInt32 a(0); a < nEdgeCount; a++)
            {
                const sal_uInt32 nNextIndex((a + 1) % nCount);
                basegfx::B2DPoint aNext(rPolygon.getB2DPoint(nNextIndex));
                const bool b1stControlPointUsed(bControls && rPolygon.isNextControlPointUsed(a));
                const bool b2ndControlPointUsed(bControls && rPolygon.isPrevControlPointUsed(nNextIndex));

                if(bPixelSnapHairline)
                {
                    aNext = impPixelSnap(rPolygon, rObjectToDevice, aObjectToDeviceInv, nNextIndex);
                }

                if(b1stControlPointUsed || b2ndControlPointUsed)
                {
                    basegfx::B2DPoint aCa(rPolygon.getNextControlPoint(a));
                    basegfx::B2DPoint aCb(rPolygon.getPrevControlPoint(nNextIndex));

                    // tdf#99165 MS Gdiplus cannot handle creating correct extra geometry for fat lines
                    // with LineCap or LineJoin when a bezier segment starts or ends trivial, e.g. has
                    // no 1st or 2nd control point, despite that these are mathematically correct definitions
                    // (basegfx can handle that).
                    // Caution: This error (and it's correction) might be necessary for other graphical
                    // sub-systems in a similar way.
                    // tdf#101026 The 1st attempt to create a mathematically correct replacement control
                    // vector was wrong. Best alternative is one as close as possible which means short.
                    if(!b1stControlPointUsed)
                    {
                        aCa = aCurr + ((aCb - aCurr) * 0.0005);
                    }
                    else if(!b2ndControlPointUsed)
                    {
                        aCb = aNext + ((aCa - aNext) * 0.0005);
                    }

                    rGraphicsPath.AddBezier(
                        static_cast< Gdiplus::REAL >(aCurr.getX()), static_cast< Gdiplus::REAL >(aCurr.getY()),
                        static_cast< Gdiplus::REAL >(aCa.getX()), static_cast< Gdiplus::REAL >(aCa.getY()),
                        static_cast< Gdiplus::REAL >(aCb.getX()), static_cast< Gdiplus::REAL >(aCb.getY()),
                        static_cast< Gdiplus::REAL >(aNext.getX()), static_cast< Gdiplus::REAL >(aNext.getY()));
                }
                else
                {
                    rGraphicsPath.AddLine(
                        static_cast< Gdiplus::REAL >(aCurr.getX()), static_cast< Gdiplus::REAL >(aCurr.getY()),
                        static_cast< Gdiplus::REAL >(aNext.getX()), static_cast< Gdiplus::REAL >(aNext.getY()));
                }

                if(a + 1 < nEdgeCount)
                {
                    aCurr = aNext;

                    if(bNoLineJoin)
                    {
                        rGraphicsPath.StartFigure();
                    }
                }
            }
        }
    }
}

namespace {

class SystemDependentData_GraphicsPath : public basegfx::SystemDependentData
{
private:
    // the path data itself
    std::shared_ptr<Gdiplus::GraphicsPath>  mpGraphicsPath;

    // all other values the triangulation is based on and
    // need to be compared with to check for data validity
    bool                                    mbNoLineJoin;
    std::vector< double >                       maStroke;

public:
    SystemDependentData_GraphicsPath(
        basegfx::SystemDependentDataManager& rSystemDependentDataManager,
        std::shared_ptr<Gdiplus::GraphicsPath>& rpGraphicsPath,
        bool bNoLineJoin,
        const std::vector< double >* pStroke); // MM01

    // read access
    std::shared_ptr<Gdiplus::GraphicsPath>& getGraphicsPath() { return mpGraphicsPath; }
    bool getNoLineJoin() const { return mbNoLineJoin; }
    const std::vector< double >& getStroke() const { return maStroke; }

    virtual sal_Int64 estimateUsageInBytes() const override;
};

}

SystemDependentData_GraphicsPath::SystemDependentData_GraphicsPath(
    basegfx::SystemDependentDataManager& rSystemDependentDataManager,
    std::shared_ptr<Gdiplus::GraphicsPath>& rpGraphicsPath,
    bool bNoLineJoin,
    const std::vector< double >* pStroke)
:   basegfx::SystemDependentData(rSystemDependentDataManager),
    mpGraphicsPath(rpGraphicsPath),
    mbNoLineJoin(bNoLineJoin),
    maStroke()
{
    if(nullptr != pStroke)
    {
        maStroke = *pStroke;
    }
}

sal_Int64 SystemDependentData_GraphicsPath::estimateUsageInBytes() const
{
    sal_Int64 nRetval(0);

    if(mpGraphicsPath)
    {
        const INT nPointCount(mpGraphicsPath->GetPointCount());

        if(0 != nPointCount)
        {
            // Each point has
            // - 2 x sizeof(Gdiplus::REAL)
            // - 1 byte (see GetPathTypes in docu)
            nRetval = nPointCount * ((2 * sizeof(Gdiplus::REAL)) + 1);
        }
    }

    return nRetval;
}

bool WinSalGraphicsImpl::drawPolyPolygon(
    const basegfx::B2DHomMatrix& rObjectToDevice,
    const basegfx::B2DPolyPolygon& rPolyPolygon,
    double fTransparency)
{
    const sal_uInt32 nCount(rPolyPolygon.count());

    if(!mbBrush || 0 == nCount || fTransparency < 0.0 || fTransparency > 1.0)
    {
        return true;
    }

    Gdiplus::Graphics aGraphics(mrParent.getHDC());
    const sal_uInt8 aTrans(sal_uInt8(255) - static_cast<sal_uInt8>(basegfx::fround(fTransparency * 255.0)));
    const Gdiplus::Color aTestColor(aTrans, maFillColor.GetRed(), maFillColor.GetGreen(), maFillColor.GetBlue());
    const Gdiplus::SolidBrush aSolidBrush(aTestColor.GetValue());

    // Set full (Object-to-Device) transformation - if used
    if(rObjectToDevice.isIdentity())
    {
        aGraphics.ResetTransform();
    }
    else
    {
        Gdiplus::Matrix aMatrix;

        aMatrix.SetElements(
            rObjectToDevice.get(0, 0),
            rObjectToDevice.get(1, 0),
            rObjectToDevice.get(0, 1),
            rObjectToDevice.get(1, 1),
            rObjectToDevice.get(0, 2),
            rObjectToDevice.get(1, 2));
        aGraphics.SetTransform(&aMatrix);
    }

    // prepare local instance of Gdiplus::GraphicsPath
    std::shared_ptr<Gdiplus::GraphicsPath> pGraphicsPath;

    // try to access buffered data
    std::shared_ptr<SystemDependentData_GraphicsPath> pSystemDependentData_GraphicsPath(
        rPolyPolygon.getSystemDependentData<SystemDependentData_GraphicsPath>());

    if(pSystemDependentData_GraphicsPath)
    {
        // copy buffered data
        pGraphicsPath = pSystemDependentData_GraphicsPath->getGraphicsPath();
    }
    else
    {
        // Note: In principle we could use the same buffered geometry at line
        // and fill polygons. Checked that in a first try, used
        // GraphicsPath::AddPath from Gdiplus combined with below used
        // StartFigure/CloseFigure, worked well (thus the line-draw version
        // may create non-closed partial Polygon data).
        //
        // But in current reality it gets not used due to e.g.
        // SdrPathPrimitive2D::create2DDecomposition creating transformed
        // line and fill polygon-primitives (what could be changed).
        //
        // There will probably be more hindrances here in other rendering paths
        // which could all be found - intention to do this would be: Use more
        // transformations, less modifications of B2DPolygons/B2DPolyPolygons.
        //
        // A fix for SdrPathPrimitive2D would be to create the sub-geometry
        // and embed into a TransformPrimitive2D containing the transformation.
        //
        // A 2nd problem is that the NoLineJoin mode (basegfx::B2DLineJoin::NONE
        // && !bIsHairline) creates polygon fill infos that are not reusable
        // for the fill case (see ::drawPolyLine below) - thus we would need a
        // bool and/or two system-dependent paths buffered - doable, but complicated.
        //
        // All in all: Make B2DPolyPolygon a SystemDependentDataProvider and buffer
        // the whole to-be-filled PolyPolygon independent from evtl. line-polygon
        // (at least for now...)

        // create data
        pGraphicsPath = std::make_shared<Gdiplus::GraphicsPath>();

        for(sal_uInt32 a(0); a < nCount; a++)
        {
            if(0 != a)
            {
                // #i101491# not needed for first run
                pGraphicsPath->StartFigure();
            }

            impAddB2DPolygonToGDIPlusGraphicsPathReal(
                *pGraphicsPath,
                rPolyPolygon.getB2DPolygon(a),
                rObjectToDevice, // not used due to the two 'false' values below, but to not forget later
                false,
                false);

            pGraphicsPath->CloseFigure();
        }

        // add to buffering mechanism
        rPolyPolygon.addOrReplaceSystemDependentData<SystemDependentData_GraphicsPath>(
            ImplGetSystemDependentDataManager(),
            pGraphicsPath,
            false,
            nullptr);
    }

    if(mrParent.getAntiAlias())
    {
        aGraphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
    }
    else
    {
        aGraphics.SetSmoothingMode(Gdiplus::SmoothingModeNone);
    }

    if(mrParent.isPrinter())
    {
        // #i121591#
        // Normally GdiPlus should not be used for printing at all since printers cannot
        // print transparent filled polygon geometry and normally this does not happen
        // since OutputDevice::RemoveTransparenciesFromMetaFile is used as preparation
        // and no transparent parts should remain for printing. But this can be overridden
        // by the user and thus happens. This call can only come (currently) from
        // OutputDevice::DrawTransparent, see comments there with the same TaskID.
        // If it is used, the mapping for the printer is wrong and needs to be corrected. I
        // checked that there is *no* transformation set and estimated that a stable factor
        // dependent of the printer's DPI is used. Create and set a transformation here to
        // correct this.
        const Gdiplus::REAL aDpiX(aGraphics.GetDpiX());
        const Gdiplus::REAL aDpiY(aGraphics.GetDpiY());

        // Now the transformation maybe/is already used (see above), so do
        // modify it without resetting to not destroy it.
        // I double-checked with MS docu that Gdiplus::MatrixOrderAppend does what
        // we need - in our notation, would be a multiply from left to execute
        // current transform first and this scale last.
        // I tried to trigger this code using Print from the menu and various
        // targets, but got no hit, thus maybe obsolete anyways. If someone knows
        // more, feel free to remove it.
        // One more hint: This *may* also be needed now in ::drawPolyLine below
        // since it also uses transformations now.
        //
        // aGraphics.ResetTransform();

        aGraphics.ScaleTransform(
            Gdiplus::REAL(100.0) / aDpiX,
            Gdiplus::REAL(100.0) / aDpiY,
            Gdiplus::MatrixOrderAppend);
    }

    // use created or buffered data
    aGraphics.FillPath(
        &aSolidBrush,
        &(*pGraphicsPath));

    return true;
}

bool WinSalGraphicsImpl::drawPolyLine(
    const basegfx::B2DHomMatrix& rObjectToDevice,
    const basegfx::B2DPolygon& rPolygon,
    double fTransparency,
    double fLineWidth,
    const std::vector< double >* pStroke, // MM01
    basegfx::B2DLineJoin eLineJoin,
    css::drawing::LineCap eLineCap,
    double fMiterMinimumAngle,
    bool bPixelSnapHairline)
{
    // MM01 check done for simple reasons
    if(!mbPen || !rPolygon.count() || fTransparency < 0.0 || fTransparency > 1.0)
    {
        return true;
    }

    // need to check/handle LineWidth when ObjectToDevice transformation is used
    const bool bObjectToDeviceIsIdentity(rObjectToDevice.isIdentity());
    const bool bIsHairline(fLineWidth == 0);

    // tdf#124848 calculate-back logical LineWidth for a hairline
    // since this implementation hands over the transformation to
    // the graphic sub-system
    if(bIsHairline)
    {
        fLineWidth = 1.0;

        if(!bObjectToDeviceIsIdentity)
        {
            basegfx::B2DHomMatrix aObjectToDeviceInv(rObjectToDevice);
            aObjectToDeviceInv.invert();
            fLineWidth = (aObjectToDeviceInv * basegfx::B2DVector(fLineWidth, 0)).getLength();
        }
    }

    Gdiplus::Graphics aGraphics(mrParent.getHDC());
    const sal_uInt8 aTrans = static_cast<sal_uInt8>(basegfx::fround( 255 * (1.0 - fTransparency) ));
    const Gdiplus::Color aTestColor(aTrans, maLineColor.GetRed(), maLineColor.GetGreen(), maLineColor.GetBlue());
    Gdiplus::Pen aPen(aTestColor.GetValue(), Gdiplus::REAL(fLineWidth));
    bool bNoLineJoin(false);

    // Set full (Object-to-Device) transformation - if used
    if(bObjectToDeviceIsIdentity)
    {
        aGraphics.ResetTransform();
    }
    else
    {
        Gdiplus::Matrix aMatrix;

        aMatrix.SetElements(
            rObjectToDevice.get(0, 0),
            rObjectToDevice.get(1, 0),
            rObjectToDevice.get(0, 1),
            rObjectToDevice.get(1, 1),
            rObjectToDevice.get(0, 2),
            rObjectToDevice.get(1, 2));
        aGraphics.SetTransform(&aMatrix);
    }

    switch(eLineJoin)
    {
        case basegfx::B2DLineJoin::NONE :
        {
            if(!bIsHairline)
            {
                bNoLineJoin = true;
            }
            break;
        }
        case basegfx::B2DLineJoin::Bevel :
        {
            aPen.SetLineJoin(Gdiplus::LineJoinBevel);
            break;
        }
        case basegfx::B2DLineJoin::Miter :
        {
            const Gdiplus::REAL aMiterLimit(1.0/sin(fMiterMinimumAngle/2.0));

            aPen.SetMiterLimit(aMiterLimit);
            // tdf#99165 MS's LineJoinMiter creates non standard conform miter additional
            // graphics, somewhere clipped in some distance from the edge point, dependent
            // of MiterLimit. The more default-like option is LineJoinMiterClipped, so use
            // that instead
            aPen.SetLineJoin(Gdiplus::LineJoinMiterClipped);
            break;
        }
        case basegfx::B2DLineJoin::Round :
        {
            aPen.SetLineJoin(Gdiplus::LineJoinRound);
            break;
        }
    }

    switch(eLineCap)
    {
        default: /*css::drawing::LineCap_BUTT*/
        {
            // nothing to do
            break;
        }
        case css::drawing::LineCap_ROUND:
        {
            aPen.SetStartCap(Gdiplus::LineCapRound);
            aPen.SetEndCap(Gdiplus::LineCapRound);
            break;
        }
        case css::drawing::LineCap_SQUARE:
        {
            aPen.SetStartCap(Gdiplus::LineCapSquare);
            aPen.SetEndCap(Gdiplus::LineCapSquare);
            break;
        }
    }

    // prepare local instance of Gdiplus::GraphicsPath
    std::shared_ptr<Gdiplus::GraphicsPath> pGraphicsPath;

    // try to access buffered data
    std::shared_ptr<SystemDependentData_GraphicsPath> pSystemDependentData_GraphicsPath(
        rPolygon.getSystemDependentData<SystemDependentData_GraphicsPath>());

    // MM01 need to do line dashing as fallback stuff here now
    const double fDotDashLength(nullptr != pStroke ? std::accumulate(pStroke->begin(), pStroke->end(), 0.0) : 0.0);
    const bool bStrokeUsed(0.0 != fDotDashLength);
    assert(!bStrokeUsed || (bStrokeUsed && pStroke));

    // MM01 decide if to stroke directly
    static bool bDoDirectGDIPlusStroke(true);

    // activate to stroke directly
    if(bDoDirectGDIPlusStroke && bStrokeUsed)
    {
        // tdf#124848 the fix of tdf#130478 that was needed here before
        // gets much easier when already handling the hairline case above,
        // the back-calculated logical linewidth is already here, just use it.
        // Still be careful - a zero LineWidth *should* not happen, but...
        std::vector<Gdiplus::REAL> aDashArray(pStroke->size());
        const double fFactor(fLineWidth == 0 ? 1.0 : 1.0 / fLineWidth);

        // tdf#134128. ODF adds caps to the dashes and dots, but GDI makes caps from the
        // dash or dot themselve. We tweak aDashArray to look the same in GDI (e.g. Impress edit mode)
        // and other renderes (e.g. Impress slide show), while keeping the total length of the
        // pattern.
        // Patterns are always a sequence dash space dash space ...
        if (eLineCap != css::drawing::LineCap_BUTT)
        {
            size_t nSize = pStroke->size();
            // We want to treat dash and space in pairs. There should be no odd size. If so, we ignore
            // last item.
            nSize /= 2;
            for(size_t a(0); a < nSize; a++)
            {
                double fDashLengthRel = (*pStroke)[2 * a] * fFactor;
                double fSpaceLengthRel = (*pStroke)[2 * a + 1] * fFactor;
                // GDI allows only positive lengths for space, Skia negative lengths too. Thus the
                // appearance is different, in case space is too small.
                double fCorrect = fSpaceLengthRel - 1.0 <= 0 ? fSpaceLengthRel - 0.01 : 1.0;
                aDashArray[2 * a] = Gdiplus::REAL(fDashLengthRel + fCorrect);
                aDashArray[2 * a + 1] = Gdiplus::REAL(fSpaceLengthRel - fCorrect);
            }
        }
        else
        {
            for(size_t a(0); a < pStroke->size(); a++)
            {
                aDashArray[a] = Gdiplus::REAL((*pStroke)[a] * fFactor);
            }
        }
        if (eLineCap == css::drawing::LineCap_ROUND)
            aPen.SetDashCap(Gdiplus::DashCapRound);
        else
            aPen.SetDashCap(Gdiplus::DashCapFlat); // "square" doesn't exist in Gdiplus
        aPen.SetDashOffset(Gdiplus::REAL(0.0));
        aPen.SetDashPattern(aDashArray.data(), aDashArray.size());
    }

    if(!bDoDirectGDIPlusStroke && pSystemDependentData_GraphicsPath)
    {
        // MM01 - check on stroke change. Used against not used, or if oth used,
        // equal or different? Triangulation geometry creation depends heavily
        // on stroke, independent of being transformation independent
        const bool bStrokeWasUsed(!pSystemDependentData_GraphicsPath->getStroke().empty());

        if(bStrokeWasUsed != bStrokeUsed
        || (bStrokeUsed && *pStroke != pSystemDependentData_GraphicsPath->getStroke()))
        {
            // data invalid, forget
            pSystemDependentData_GraphicsPath.reset();
        }
    }

    if(pSystemDependentData_GraphicsPath)
    {
        // check data validity
        if (pSystemDependentData_GraphicsPath->getNoLineJoin() != bNoLineJoin
            || bPixelSnapHairline /*tdf#124700*/)
        {
            // data invalid, forget
            pSystemDependentData_GraphicsPath.reset();
        }
    }

    if(pSystemDependentData_GraphicsPath)
    {
        // copy buffered data
        pGraphicsPath = pSystemDependentData_GraphicsPath->getGraphicsPath();
    }
    else
    {
        // fill data of buffered data
        pGraphicsPath = std::make_shared<Gdiplus::GraphicsPath>();

        if(!bDoDirectGDIPlusStroke && bStrokeUsed)
        {
            // MM01 need to do line dashing as fallback stuff here now
            basegfx::B2DPolyPolygon aPolyPolygonLine;

            // apply LineStyle
            basegfx::utils::applyLineDashing(
                rPolygon, // source
                *pStroke, // pattern
                &aPolyPolygonLine, // target for lines
                nullptr, // target for gaps
                fDotDashLength); // full length if available

            // MM01 checked/verified, ok
            for(sal_uInt32 a(0); a < aPolyPolygonLine.count(); a++)
            {
                const basegfx::B2DPolygon aPolyLine(aPolyPolygonLine.getB2DPolygon(a));
                pGraphicsPath->StartFigure();
                impAddB2DPolygonToGDIPlusGraphicsPathReal(
                    *pGraphicsPath,
                    aPolyLine,
                    rObjectToDevice,
                    bNoLineJoin,
                    bPixelSnapHairline);
            }
        }
        else
        {
            // no line dashing or direct stroke, just copy
            impAddB2DPolygonToGDIPlusGraphicsPathReal(
                *pGraphicsPath,
                rPolygon,
                rObjectToDevice,
                bNoLineJoin,
                bPixelSnapHairline);

            if(rPolygon.isClosed() && !bNoLineJoin)
            {
                // #i101491# needed to create the correct line joins
                pGraphicsPath->CloseFigure();
            }
        }

        // add to buffering mechanism
        if (!bPixelSnapHairline /*tdf#124700*/)
        {
            rPolygon.addOrReplaceSystemDependentData<SystemDependentData_GraphicsPath>(
                ImplGetSystemDependentDataManager(),
                pGraphicsPath,
                bNoLineJoin,
                pStroke);
        }
    }

    if(mrParent.getAntiAlias())
    {
        aGraphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
    }
    else
    {
        aGraphics.SetSmoothingMode(Gdiplus::SmoothingModeNone);
    }

    if(mrParent.isPrinter())
    {
        // tdf#122384 As mentioned above in WinSalGraphicsImpl::drawPolyPolygon
        // (look for 'One more hint: This *may* also be needed now in'...).
        // See comments in same spot above *urgently* before doing changes here,
        // these comments are *still fully valid* at this place (!)
        const Gdiplus::REAL aDpiX(aGraphics.GetDpiX());
        const Gdiplus::REAL aDpiY(aGraphics.GetDpiY());

        aGraphics.ScaleTransform(
            Gdiplus::REAL(100.0) / aDpiX,
            Gdiplus::REAL(100.0) / aDpiY,
            Gdiplus::MatrixOrderAppend);
    }

    aGraphics.DrawPath(
        &aPen,
        &(*pGraphicsPath));

    return true;
}

static void paintToGdiPlus(
    Gdiplus::Graphics& rGraphics,
    const SalTwoRect& rTR,
    Gdiplus::Bitmap& rBitmap)
{
    // only parts of source are used
    Gdiplus::PointF aDestPoints[3];
    Gdiplus::ImageAttributes aAttributes;

    // define target region as parallelogram
    aDestPoints[0].X = Gdiplus::REAL(rTR.mnDestX);
    aDestPoints[0].Y = Gdiplus::REAL(rTR.mnDestY);
    aDestPoints[1].X = Gdiplus::REAL(rTR.mnDestX + rTR.mnDestWidth);
    aDestPoints[1].Y = Gdiplus::REAL(rTR.mnDestY);
    aDestPoints[2].X = Gdiplus::REAL(rTR.mnDestX);
    aDestPoints[2].Y = Gdiplus::REAL(rTR.mnDestY + rTR.mnDestHeight);

    aAttributes.SetWrapMode(Gdiplus::WrapModeTileFlipXY);

    rGraphics.DrawImage(
        &rBitmap,
        aDestPoints,
        3,
        Gdiplus::REAL(rTR.mnSrcX),
        Gdiplus::REAL(rTR.mnSrcY),
        Gdiplus::REAL(rTR.mnSrcWidth),
        Gdiplus::REAL(rTR.mnSrcHeight),
        Gdiplus::UnitPixel,
        &aAttributes);
}

static void setInterpolationMode(
    Gdiplus::Graphics& rGraphics,
    tools::Long rSrcWidth,
    tools::Long rDestWidth,
    tools::Long rSrcHeight,
    tools::Long rDestHeight)
{
    const bool bSameWidth(rSrcWidth == rDestWidth);
    const bool bSameHeight(rSrcHeight == rDestHeight);

    if(bSameWidth && bSameHeight)
    {
        rGraphics.SetInterpolationMode(Gdiplus::InterpolationModeInvalid);
    }
    else if(rDestWidth > rSrcWidth && rDestHeight > rSrcHeight)
    {
        rGraphics.SetInterpolationMode(Gdiplus::InterpolationModeDefault);
    }
    else if(rDestWidth < rSrcWidth && rDestHeight < rSrcHeight)
    {
        rGraphics.SetInterpolationMode(Gdiplus::InterpolationModeBicubic);
    }
    else
    {
        rGraphics.SetInterpolationMode(Gdiplus::InterpolationModeDefault);
    }
}

bool WinSalGraphicsImpl::TryDrawBitmapGDIPlus(const SalTwoRect& rTR, const SalBitmap& rSrcBitmap)
{
    if(rTR.mnSrcWidth && rTR.mnSrcHeight && rTR.mnDestWidth && rTR.mnDestHeight)
    {
        assert(dynamic_cast<const WinSalBitmap*>(&rSrcBitmap));

        const WinSalBitmap& rSalBitmap = static_cast< const WinSalBitmap& >(rSrcBitmap);
        std::shared_ptr< Gdiplus::Bitmap > aARGB(rSalBitmap.ImplGetGdiPlusBitmap());

        if(aARGB)
        {
            Gdiplus::Graphics aGraphics(mrParent.getHDC());

            setInterpolationMode(
                aGraphics,
                rTR.mnSrcWidth,
                rTR.mnDestWidth,
                rTR.mnSrcHeight,
                rTR.mnDestHeight);

            paintToGdiPlus(
                aGraphics,
                rTR,
                *aARGB);

            return true;
        }
    }

    return false;
}

bool WinSalGraphicsImpl::blendBitmap(
    const SalTwoRect&,
    const SalBitmap&)
{
    return false;
}

bool WinSalGraphicsImpl::blendAlphaBitmap(
    const SalTwoRect&,
    const SalBitmap&,
    const SalBitmap&,
    const SalBitmap&)
{
    return false;
}

bool WinSalGraphicsImpl::drawAlphaBitmap(
    const SalTwoRect& rTR,
    const SalBitmap& rSrcBitmap,
    const SalBitmap& rAlphaBmp)
{
    if(rTR.mnSrcWidth && rTR.mnSrcHeight && rTR.mnDestWidth && rTR.mnDestHeight)
    {
        assert(dynamic_cast<const WinSalBitmap*>(&rSrcBitmap));
        assert(dynamic_cast<const WinSalBitmap*>(&rAlphaBmp));

        const WinSalBitmap& rSalBitmap = static_cast< const WinSalBitmap& >(rSrcBitmap);
        const WinSalBitmap& rSalAlpha = static_cast< const WinSalBitmap& >(rAlphaBmp);
        std::shared_ptr< Gdiplus::Bitmap > aARGB(rSalBitmap.ImplGetGdiPlusBitmap(&rSalAlpha));

        if(aARGB)
        {
            Gdiplus::Graphics aGraphics(mrParent.getHDC());

            setInterpolationMode(
                aGraphics,
                rTR.mnSrcWidth,
                rTR.mnDestWidth,
                rTR.mnSrcHeight,
                rTR.mnDestHeight);

            paintToGdiPlus(
                aGraphics,
                rTR,
                *aARGB);

            return true;
        }
    }

    return false;
}

bool WinSalGraphicsImpl::drawTransformedBitmap(
    const basegfx::B2DPoint& rNull,
    const basegfx::B2DPoint& rX,
    const basegfx::B2DPoint& rY,
    const SalBitmap& rSourceBitmap,
    const SalBitmap* pAlphaBitmap)
{
    assert(dynamic_cast<const WinSalBitmap*>(&rSourceBitmap));
    assert(!pAlphaBitmap || dynamic_cast<const WinSalBitmap*>(pAlphaBitmap));

    const WinSalBitmap& rSalBitmap = static_cast< const WinSalBitmap& >(rSourceBitmap);
    const WinSalBitmap* pSalAlpha = static_cast< const WinSalBitmap* >(pAlphaBitmap);
    std::shared_ptr< Gdiplus::Bitmap > aARGB(rSalBitmap.ImplGetGdiPlusBitmap(pSalAlpha));

    if(aARGB)
    {
        const tools::Long nSrcWidth(aARGB->GetWidth());
        const tools::Long nSrcHeight(aARGB->GetHeight());

        if(nSrcWidth && nSrcHeight)
        {
            const tools::Long nDestWidth(basegfx::fround(basegfx::B2DVector(rX - rNull).getLength()));
            const tools::Long nDestHeight(basegfx::fround(basegfx::B2DVector(rY - rNull).getLength()));

            if(nDestWidth && nDestHeight)
            {
                Gdiplus::Graphics aGraphics(mrParent.getHDC());
                Gdiplus::PointF aDestPoints[3];
                Gdiplus::ImageAttributes aAttributes;

                setInterpolationMode(
                    aGraphics,
                    nSrcWidth,
                    nDestWidth,
                    nSrcHeight,
                    nDestHeight);

                // this mode is only capable of drawing the whole bitmap to a parallelogram
                aDestPoints[0].X = Gdiplus::REAL(rNull.getX());
                aDestPoints[0].Y = Gdiplus::REAL(rNull.getY());
                aDestPoints[1].X = Gdiplus::REAL(rX.getX());
                aDestPoints[1].Y = Gdiplus::REAL(rX.getY());
                aDestPoints[2].X = Gdiplus::REAL(rY.getX());
                aDestPoints[2].Y = Gdiplus::REAL(rY.getY());

                aAttributes.SetWrapMode(Gdiplus::WrapModeTileFlipXY);

                aGraphics.DrawImage(
                    aARGB.get(),
                    aDestPoints,
                    3,
                    Gdiplus::REAL(0.0),
                    Gdiplus::REAL(0.0),
                    Gdiplus::REAL(nSrcWidth),
                    Gdiplus::REAL(nSrcHeight),
                    Gdiplus::UnitPixel,
                    &aAttributes);
            }
        }

        return true;
    }

    return false;
}

bool WinSalGraphicsImpl::drawGradient(const tools::PolyPolygon& /*rPolygon*/,
        const Gradient& /*rGradient*/)
{
    return false;
}

bool WinSalGraphicsImpl::implDrawGradient(basegfx::B2DPolyPolygon const & /*rPolyPolygon*/,
    SalGradient const & /*rGradient*/)
{
    return false;
}

bool WinSalGraphicsImpl::supportsOperation(OutDevSupportType eType) const
{
    static bool bAllowForTest(true);
    bool bRet = false;

    switch (eType)
    {
        case OutDevSupportType::TransparentRect:
            bRet = mrParent.mbVirDev || mrParent.mbWindow;
            break;
        case OutDevSupportType::B2DDraw:
            bRet = bAllowForTest;
            break;
        default:
            break;
    }
    return bRet;
}

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