    /* -*- 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 <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 <vcl/salbtype.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 DITHER_PAL_DELTA                51
    #define DITHER_MAX_SYSCOLOR             16
    #define DMAP( _def_nVal, _def_nThres )  ((pDitherDiff[_def_nVal]>(_def_nThres))?pDitherHigh[_def_nVal]:pDitherLow[_def_nVal])

    #define SAL_POLY_STACKBUF       32

    namespace {

    // #100127# draw an array of points which might also contain bezier control points
    void ImplRenderPath( HDC hdc, sal_uLong nPoints, const SalPoint* pPtAry, const PolyFlags* pFlgAry )
    {
        if( nPoints )
        {
            // 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, pPtAry->mnX, pPtAry->mnY, nullptr );
            ++pPtAry; ++pFlgAry;

            for( sal_uLong i=1; i<nPoints; ++i, ++pPtAry, ++pFlgAry )
            {
                if( *pFlgAry != PolyFlags::Control )
                {
                    LineTo( hdc, pPtAry->mnX, pPtAry->mnY );
                }
                else if( nPoints - i > 2 )
                {
                    PolyBezierTo( hdc, reinterpret_cast<const POINT*>(pPtAry), 3 );
                    i += 2; pPtAry += 2; pFlgAry += 2;
                }
            }
        }
    }

    // #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 SalPoint* const*    pPtAry,
                              const PolyFlags* const*   pFlgAry,
                              POINT*                    pWinPointAry,
                              BYTE*                     pWinFlagAry     )
    {
        sal_uLong nCurrPoly;
        for( nCurrPoly=0; nCurrPoly<nPoly; ++nCurrPoly )
        {
            const POINT* pCurrPoint = reinterpret_cast<const POINT*>( *pPtAry++ );
            const PolyFlags* pCurrFlag = *pFlgAry++;
            const sal_uInt32 nCurrPoints = *pPoints++;
            const bool bHaveFlagArray( pCurrFlag );
            sal_uLong nCurrPoint;

            if( nCurrPoints )
            {
                // start figure
                *pWinPointAry++ = *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++ = *pCurrPoint++;
                            *pWinFlagAry++  = PT_BEZIERTO;

                            // control point two
                            *pWinPointAry++ = *pCurrPoint++;
                            *pWinFlagAry++  = PT_BEZIERTO;

                            // end point
                            *pWinPointAry++ = *pCurrPoint++;
                            *pWinFlagAry++  = PT_BEZIERTO;

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

                    // regular line point
                    *pWinPointAry++ = *pCurrPoint++;
                    *pWinFlagAry++  = PT_LINETO;
                    ++pCurrFlag;
                    ++nCurrPoint;
                }

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

    static PALETTEENTRY aImplSalSysPalEntryAry[ DITHER_MAX_SYSCOLOR ] =
    {
    {    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 }
    };

    static PALETTEENTRY aImplExtraColor1 =
    {
        0, 184, 255, 0
    };

    static 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 }
    };

    static 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 }
    };

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

    int ImplIsPaletteEntry( BYTE nRed, BYTE nGreen, BYTE nBlue )
    {
        // dither color?
        if ( !(nRed % DITHER_PAL_DELTA) && !(nGreen % DITHER_PAL_DELTA) && !(nBlue % DITHER_PAL_DELTA) )
            return TRUE;

        PALETTEENTRY* pPalEntry = aImplSalSysPalEntryAry;

        // standard palette color?
        for ( sal_uInt16 i = 0; i < DITHER_MAX_SYSCOLOR; i++, pPalEntry++ )
        {
            if( pPalEntry->peRed == nRed && pPalEntry->peGreen == nGreen && pPalEntry->peBlue == nBlue )
                return TRUE;
        }

        // extra color?
        if ( aImplExtraColor1.peRed == nRed &&
             aImplExtraColor1.peGreen == nGreen &&
             aImplExtraColor1.peBlue == nBlue )
        {
            return TRUE;
        }

        return FALSE;
    }

    }

    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(long, long, long, long, void*, sal_uLong)
    {
        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 );
        }
    }

    void ImplCalcOutSideRgn( const RECT& rSrcRect,
                             int nLeft, int nTop, int nRight, int nBottom,
                             HRGN& rhInvalidateRgn )
    {
        HRGN hTempRgn;

        // calculate area outside the visible region
        if ( rSrcRect.left < nLeft )
        {
            if ( !rhInvalidateRgn )
                rhInvalidateRgn = CreateRectRgnIndirect( &rSrcRect );
            hTempRgn = CreateRectRgn( -31999, 0, nLeft, 31999 );
            CombineRgn( rhInvalidateRgn, rhInvalidateRgn, hTempRgn, RGN_DIFF );
            DeleteRegion( hTempRgn );
        }
        if ( rSrcRect.top < nTop )
        {
            if ( !rhInvalidateRgn )
                rhInvalidateRgn = CreateRectRgnIndirect( &rSrcRect );
            hTempRgn = CreateRectRgn( 0, -31999, 31999, nTop );
            CombineRgn( rhInvalidateRgn, rhInvalidateRgn, hTempRgn, RGN_DIFF );
            DeleteRegion( hTempRgn );
        }
        if ( rSrcRect.right > nRight )
        {
            if ( !rhInvalidateRgn )
                rhInvalidateRgn = CreateRectRgnIndirect( &rSrcRect );
            hTempRgn = CreateRectRgn( nRight, 0, 31999, 31999 );
            CombineRgn( rhInvalidateRgn, rhInvalidateRgn, hTempRgn, RGN_DIFF );
            DeleteRegion( hTempRgn );
        }
        if ( rSrcRect.bottom > nBottom )
        {
            if ( !rhInvalidateRgn )
                rhInvalidateRgn = CreateRectRgnIndirect( &rSrcRect );
            hTempRgn = CreateRectRgn( 0, nBottom, 31999, 31999 );
            CombineRgn( rhInvalidateRgn, rhInvalidateRgn, hTempRgn, RGN_DIFF );
            DeleteRegion( hTempRgn );
        }
    }

    void WinSalGraphicsImpl::copyArea( long nDestX, long nDestY,
                                long nSrcX, long nSrcY,
                                long nSrcWidth, 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, than 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();
            WinSalBitmap*   pTmpSalBmp = nullptr;
            bool        bPrintDDB = ( bPrinter && hDrawDDB );

            if( bPrintDDB )
            {
                pTmpSalBmp = new WinSalBitmap;
                pTmpSalBmp->Create( rSalBitmap, rSalBitmap.GetBitCount() );
                hDrawDIB = pTmpSalBmp->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 )
            {
                HDC         hBmpDC = ImplGetCachedDC( CACHED_HDC_DRAW, 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].GetColor();
                            nTextColor = RGB( nCol.GetRed(), nCol.GetGreen(), nCol.GetBlue() );
                            nCol = rPalette[1].GetColor();
                            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,
                            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,
                                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 );
                }

                ImplReleaseCachedDC( CACHED_HDC_DRAW );
            }

            if( bPrintDDB )
                delete pTmpSalBmp;
        }
    }

    }

    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();
        HBITMAP     hMemBitmap = nullptr;
        HBITMAP     hMaskBitmap = nullptr;

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

        HDC hMemDC = ImplGetCachedDC( CACHED_HDC_1, hMemBitmap );
        HDC hMaskDC = ImplGetCachedDC( CACHED_HDC_2, hMaskBitmap );

        aPosAry.mnDestX = aPosAry.mnDestY = 0;
        BitBlt( hMemDC, 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, aPosAry, aTmp, false, SRCCOPY );
        }
        else
            ImplDrawBitmap( hMaskDC, aPosAry, rTransparentBitmap, false, SRCCOPY );

        // now MemDC contains background, MaskDC the transparency mask

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

        ImplReleaseCachedDC( CACHED_HDC_1 );
        ImplReleaseCachedDC( CACHED_HDC_2 );

        // hMemBitmap != 0 ==> hMaskBitmap != 0
        if( hMemBitmap )
        {
            DeleteObject( hMemBitmap );
            DeleteObject( hMaskBitmap );
        }
    }

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

        HDC hMemDC = ImplGetCachedDC( CACHED_HDC_1 );
        SetPixel( hMemDC, 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, 0,0,1,1,
                                  aFunc ) == TRUE;

        ImplReleaseCachedDC( CACHED_HDC_1 );

        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 BYTE  cRed = nMaskColor.GetRed();
        const BYTE  cGreen = nMaskColor.GetGreen();
        const BYTE  cBlue = nMaskColor.GetBlue();
        HDC         hDC = mrParent.getHDC();
        HBRUSH      hMaskBrush = CreateSolidBrush( RGB( cRed, cGreen, cBlue ) );
        HBRUSH      hOldBrush = SelectBrush( hDC, hMaskBrush );

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

        SelectBrush( hDC, hOldBrush );
        DeleteBrush( hMaskBrush );
    }

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

        std::shared_ptr<WinSalBitmap> pSalBitmap;

        nDX = labs( nDX );
        nDY = labs( nDY );

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

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

        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( long nX, 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 ) );
    }

    void WinSalGraphicsImpl::invert( long nX, long nY, long nWidth, 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 )
        {
            SalData* pSalData = GetSalData();
            if ( !pSalData->mh50Brush )
            {
                if ( !pSalData->mh50Bmp )
                    pSalData->mh50Bmp = ImplLoadSalBitmap( SAL_RESID_BITMAP_50 );
                pSalData->mh50Brush = CreatePatternBrush( pSalData->mh50Bmp );
            }

            COLORREF nOldTextColor = ::SetTextColor( mrParent.getHDC(), 0 );
            HBRUSH hOldBrush = SelectBrush( mrParent.getHDC(), pSalData->mh50Brush );
            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 SalPoint* 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 )
            {
                SalData* pSalData = GetSalData();
                if ( !pSalData->mh50Brush )
                {
                    if ( !pSalData->mh50Bmp )
                        pSalData->mh50Bmp = ImplLoadSalBitmap( SAL_RESID_BITMAP_50 );
                    pSalData->mh50Brush = CreatePatternBrush( pSalData->mh50Bmp );
                }

                hBrush = pSalData->mh50Brush;
            }
            else
                hBrush = GetStockBrush( BLACK_BRUSH );

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

        POINT const * pWinPtAry;
        // for NT, we can handover the array directly
        static_assert( sizeof( POINT ) == sizeof( SalPoint ), "must be the same size" );

        pWinPtAry = reinterpret_cast<POINT const *>(pPtAry);
        // for Windows 95 and its maximum number of points
        if ( nSalFlags & SalInvert::TrackFrame )
        {
            if ( !Polyline( mrParent.getHDC(), pWinPtAry, static_cast<int>(nPoints) ) && (nPoints > MAX_64KSALPOINTS) )
                Polyline( mrParent.getHDC(), pWinPtAry, MAX_64KSALPOINTS );
        }
        else
        {
            if ( !Polygon( mrParent.getHDC(), pWinPtAry, static_cast<int>(nPoints) ) && (nPoints > MAX_64KSALPOINTS) )
                Polygon( mrParent.getHDC(), pWinPtAry, 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 ));
    }

    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(sal_uInt32 a(0); a < rCandidate.count(); a++)
        {
            if(!containsOnlyHorizontalAndVerticalEdges(rCandidate.getB2DPolygon(a)))
            {
                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::B2DHomMatrix(basegfx::utils::createSourceRangeTargetRangeTransform(aRangeS, aRangeT));
                }

                for(sal_uInt32 a(0); a < nCount; a++)
                {
                    const basegfx::B2DPolygon aPoly(
                        basegfx::utils::adaptiveSubdivideByDistance(
                            aPolyPolygon.getB2DPolygon(a),
                            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[0], &aPolyCounts[0], 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 long nW(rectangle.GetWidth());
                const long nH(rectangle.GetHeight());

                if(nW && nH)
                {
                    const long nRight(rectangle.Left() + nW);
                    const 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++ )
                        {
                            HRGN hRgn = CreateRectRgn( pRect->left, pRect->top, pRect->right, pRect->bottom );
                            CombineRgn( mrParent.mhRegion, mrParent.mhRegion, hRgn, RGN_OR );
                            DeleteRegion( hRgn );
                        }
                    }
                }

                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()
    {
        // create and select new pen
        HPEN hNewPen = GetStockPen( NULL_PEN );
        HPEN hOldPen = SelectPen( mrParent.getHDC(), hNewPen );

        // destroy or save old pen
        if ( mhPen )
        {
            if ( !mbStockPen )
                DeletePen( mhPen );
        }
        else
            mrParent.mhDefPen = hOldPen;

        // set new data
        mhPen       = hNewPen;
        mbPen       = FALSE;
        mbStockPen  = TRUE;
    }

    void WinSalGraphicsImpl::SetLineColor( Color nColor )
    {
        maLineColor = nColor;
        COLORREF    nPenColor = PALETTERGB( nColor.GetRed(),
                                            nColor.GetGreen(),
                                            nColor.GetBlue() );
        HPEN        hNewPen = nullptr;
        bool        bStockPen = FALSE;

        // search for stock pen (only screen, because printer have problems,
        // when we use stock objects)
        if ( !mrParent.isPrinter() )
        {
            SalData* pSalData = GetSalData();
            for ( sal_uInt16 i = 0; i < pSalData->mnStockPenCount; i++ )
            {
                if ( nPenColor == pSalData->maStockPenColorAry[i] )
                {
                    hNewPen = pSalData->mhStockPenAry[i];
                    bStockPen = TRUE;
                    break;
                }
            }
        }

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

            hNewPen = CreatePen( PS_SOLID, mrParent.mnPenWidth, nPenColor );
            bStockPen = FALSE;
        }

        // select new pen
        HPEN hOldPen = SelectPen( mrParent.getHDC(), hNewPen );

        // destroy or save old pen
        if ( mhPen )
        {
            if ( !mbStockPen )
                DeletePen( mhPen );
        }
        else
            mrParent.mhDefPen = hOldPen;

        // set new data
        mnPenColor  = nPenColor;
        mhPen       = hNewPen;
        mbPen       = TRUE;
        mbStockPen  = bStockPen;
    }

    void WinSalGraphicsImpl::SetFillColor()
    {
        // create and select new brush
        HBRUSH hNewBrush = GetStockBrush( NULL_BRUSH );
        HBRUSH hOldBrush = SelectBrush( mrParent.getHDC(), hNewBrush );

        // destroy or save old brush
        if ( mhBrush )
        {
            if ( !mbStockBrush )
                DeleteBrush( mhBrush );
        }
        else
            mrParent.mhDefBrush = hOldBrush;

        // set new data
        mhBrush     = hNewBrush;
        mbBrush     = FALSE;
        mbStockBrush = TRUE;
    }

    void WinSalGraphicsImpl::SetFillColor( Color nColor )
    {
        maFillColor = nColor;
        SalData*    pSalData    = GetSalData();
        BYTE        nRed        = nColor.GetRed();
        BYTE        nGreen      = nColor.GetGreen();
        BYTE        nBlue       = nColor.GetBlue();
        COLORREF    nBrushColor = PALETTERGB( nRed, nGreen, nBlue );
        HBRUSH      hNewBrush   = nullptr;
        bool        bStockBrush = FALSE;

        // search for stock brush (only screen, because printer have problems,
        // when we use stock objects)
        if ( !mrParent.isPrinter() )
        {
            for ( sal_uInt16 i = 0; i < pSalData->mnStockBrushCount; i++ )
            {
                if ( nBrushColor == pSalData->maStockBrushColorAry[ i ] )
                {
                    hNewBrush = pSalData->mhStockBrushAry[i];
                    bStockBrush = TRUE;
                    break;
                }
            }
        }

        // create new brush
        if ( !hNewBrush )
        {
            if ( mrParent.isPrinter() || !pSalData->mhDitherDIB )
                hNewBrush = CreateSolidBrush( nBrushColor );
            else
            {
                if ( 24 == reinterpret_cast<BITMAPINFOHEADER*>(pSalData->mpDitherDIB)->biBitCount )
                {
                    BYTE* pTmp = pSalData->mpDitherDIBData;
                    long* pDitherDiff = pSalData->mpDitherDiff;
                    BYTE* pDitherLow = pSalData->mpDitherLow;
                    BYTE* pDitherHigh = pSalData->mpDitherHigh;

                    for( long nY = 0L; nY < 8L; nY++ )
                    {
                        for( long nX = 0L; nX < 8L; nX++ )
                        {
                            const long nThres = aOrdDither16Bit[ nY ][ nX ];
                            *pTmp++ = DMAP( nBlue, nThres );
                            *pTmp++ = DMAP( nGreen, nThres );
                            *pTmp++ = DMAP( nRed, nThres );
                        }
                    }

                    hNewBrush = CreateDIBPatternBrush( pSalData->mhDitherDIB, DIB_RGB_COLORS );
                }
                else if ( ImplIsSysColorEntry( nColor ) )
                {
                    nBrushColor = PALRGB_TO_RGB( nBrushColor );
                    hNewBrush = CreateSolidBrush( nBrushColor );
                }
                else if ( ImplIsPaletteEntry( nRed, nGreen, nBlue ) )
                    hNewBrush = CreateSolidBrush( nBrushColor );
                else
                {
                    BYTE* pTmp = pSalData->mpDitherDIBData;
                    long* pDitherDiff = pSalData->mpDitherDiff;
                    BYTE* pDitherLow = pSalData->mpDitherLow;
                    BYTE* pDitherHigh = pSalData->mpDitherHigh;

                    for ( long nY = 0L; nY < 8L; nY++ )
                    {
                        for ( long nX = 0L; nX < 8L; nX++ )
                        {
                            const long nThres = aOrdDither8Bit[ nY ][ nX ];
                            *pTmp = DMAP( nRed, nThres ) + DMAP( nGreen, nThres ) * 6 + DMAP( nBlue, nThres ) * 36;
                            pTmp++;
                        }
                    }

                    hNewBrush = CreateDIBPatternBrush( pSalData->mhDitherDIB, DIB_PAL_COLORS );
                }
            }

            bStockBrush = FALSE;
        }

        // select new brush
        HBRUSH hOldBrush = SelectBrush( mrParent.getHDC(), hNewBrush );

        // destroy or save old brush
        if ( mhBrush )
        {
            if ( !mbStockBrush )
                DeleteBrush( mhBrush );
        }
        else
            mrParent.mhDefBrush = hOldBrush;

        // set new data
        mnBrushColor = nBrushColor;
        mhBrush     = hNewBrush;
        mbBrush     = TRUE;
        mbStockBrush = bStockBrush;
    }

    void WinSalGraphicsImpl::SetXORMode( bool bSet)
    {
        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( long nX, long nY, COLORREF crColor )
    {
        if ( mbXORMode )
        {
            HBRUSH hBrush = CreateSolidBrush( crColor );
            HBRUSH hOldBrush = SelectBrush( mrParent.getHDC(), hBrush );
            PatBlt( mrParent.getHDC(), static_cast<int>(nX), static_cast<int>(nY), int(1), int(1), PATINVERT );
            SelectBrush( mrParent.getHDC(), hOldBrush );
            DeleteBrush( hBrush );
        }
        else
            SetPixel( mrParent.getHDC(), static_cast<int>(nX), static_cast<int>(nY), crColor );
    }

    void WinSalGraphicsImpl::drawPixel( long nX, long nY )
    {
        drawPixelImpl( nX, nY, mnPenColor );
    }

    void WinSalGraphicsImpl::drawPixel( long nX, 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( long nX1, long nY1, long nX2, 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( long nX, long nY, long nWidth, 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 SalPoint* pPtAry )
    {
        // for NT, we can handover the array directly
        static_assert( sizeof( POINT ) == sizeof( SalPoint ), "must be the same size" );

        POINT const * pWinPtAry = reinterpret_cast<POINT const *>(pPtAry);

        // for Windows 95 and its maximum number of points
        if ( !Polyline( mrParent.getHDC(), pWinPtAry, static_cast<int>(nPoints) ) && (nPoints > MAX_64KSALPOINTS) )
            Polyline( mrParent.getHDC(), pWinPtAry, 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 SalPoint* pPtAry )
    {
        // for NT, we can handover the array directly
        static_assert( sizeof( POINT ) == sizeof( SalPoint ), "must be the same size" );

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

    void WinSalGraphicsImpl::drawPolyPolygon( sal_uInt32 nPoly, const sal_uInt32* pPoints,
                                       PCONSTSALPOINT* 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];
        // for NT, we can handover the array directly
        static_assert( sizeof( POINT ) == sizeof( SalPoint ), "must be the same size" );
        UINT            n = 0;
        for ( i = 0; i < static_cast<UINT>(nPoly); i++ )
        {
            nPoints = pWinPointAry[i];
            const SalPoint* pPolyAry = pPtAry[i];
            memcpy( pWinPointAryAry+n, pPolyAry, (nPoints-1)*sizeof(POINT) );
            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 SalPoint* pPtAry, const PolyFlags* pFlgAry )
    {
        static_assert( sizeof( POINT ) == sizeof( SalPoint ), "must be the same size" );

        ImplRenderPath( mrParent.getHDC(), nPoints, pPtAry, pFlgAry );

        return true;
    }

    bool WinSalGraphicsImpl::drawPolygonBezier( sal_uInt32 nPoints, const SalPoint* pPtAry, const PolyFlags* pFlgAry )
    {
        static_assert( sizeof( POINT ) == sizeof( SalPoint ), "must be the same size" );

        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 SalPoint* const* pPtAry, const PolyFlags* const* pFlgAry )
    {
        static_assert( sizeof( POINT ) == sizeof( SalPoint ), "must be the same size" );

        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;
    }

    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);
    }

    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 mathematicaly 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();
                        }
                    }
                }
            }
        }
    }

    class SystemDependentData_GraphicsPath : public basegfx::SystemDependentData
    {
    private:
        Gdiplus::GraphicsPath           maGraphicsPath;
        bool                            mbPixelSnapHairline;

    public:
        SystemDependentData_GraphicsPath(
            basegfx::SystemDependentDataManager& rSystemDependentDataManager);

        Gdiplus::GraphicsPath& getGraphicsPath() { return maGraphicsPath; }

        bool getPixelSnapHairline() const { return mbPixelSnapHairline; }
        void setPixelSnapHairline(bool bNew) { mbPixelSnapHairline = bNew; }
    };

    SystemDependentData_GraphicsPath::SystemDependentData_GraphicsPath(
        basegfx::SystemDependentDataManager& rSystemDependentDataManager)
    :   basegfx::SystemDependentData(rSystemDependentDataManager),
        maGraphicsPath(),
        mbPixelSnapHairline(false)
    {
    }

    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);
        }

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

        if(!pSystemDependentData_GraphicsPath)
        {
            // add to buffering mechanism
            pSystemDependentData_GraphicsPath = rPolyPolygon.addOrReplaceSystemDependentData<SystemDependentData_GraphicsPath>(
                ImplGetSystemDependentDataManager());

            // 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-cloded 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
            // && rLineWidths > 0.0) creates polygon fill infos that are not reusable
            // for the fill case (see ::drawPolyLine bnelow) - 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
            for(sal_uInt32 a(0); a < nCount; a++)
            {
                if(0 != a)
                {
                    // #i101491# not needed for first run
                    pSystemDependentData_GraphicsPath->getGraphicsPath().StartFigure();
                }

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

                pSystemDependentData_GraphicsPath->getGraphicsPath().CloseFigure();
            }
        }

        if(mrParent.getAntiAliasB2DDraw())
        {
            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,
            &pSystemDependentData_GraphicsPath->getGraphicsPath());

        return true;
    }

    bool WinSalGraphicsImpl::drawPolyLine(
        const basegfx::B2DHomMatrix& rObjectToDevice,
        const basegfx::B2DPolygon& rPolygon,
        double fTransparency,
        const basegfx::B2DVector& rLineWidths,
        basegfx::B2DLineJoin eLineJoin,
        css::drawing::LineCap eLineCap,
        double fMiterMinimumAngle,
        bool bPixelSnapHairline)
    {
        if(!mbPen || 0 == rPolygon.count())
        {
            return true;
        }

        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(rLineWidths.getX()));
        bool bNoLineJoin(false);
        Gdiplus::Matrix aMatrix;

        // 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);
        }

        switch(eLineJoin)
        {
            case basegfx::B2DLineJoin::NONE :
            {
                if(basegfx::fTools::more(rLineWidths.getX(), 0.0))
                {
                    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;
            }
        }

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

        if(pSystemDependentData_GraphicsPath)
        {
            // check data validity
            if(pSystemDependentData_GraphicsPath->getPixelSnapHairline() != bPixelSnapHairline)
            {
                // data invalid, forget
                pSystemDependentData_GraphicsPath.reset();
            }
        }

        if(!pSystemDependentData_GraphicsPath)
        {
            // add to buffering mechanism
            pSystemDependentData_GraphicsPath = rPolygon.addOrReplaceSystemDependentData<SystemDependentData_GraphicsPath>(
                ImplGetSystemDependentDataManager());

            // fill data of buffered data
            pSystemDependentData_GraphicsPath->setPixelSnapHairline(bPixelSnapHairline);

            impAddB2DPolygonToGDIPlusGraphicsPathReal(
                pSystemDependentData_GraphicsPath->getGraphicsPath(),
                rPolygon,
                rObjectToDevice,
                bNoLineJoin,
                bPixelSnapHairline);

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

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

        aGraphics.DrawPath(
            &aPen,
            &pSystemDependentData_GraphicsPath->getGraphicsPath());

        return true;
    }

    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 paralellogram
        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);
    }

    void setInterpolationMode(
        Gdiplus::Graphics& rGraphics,
        long rSrcWidth,
        long rDestWidth,
        long rSrcHeight,
        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.get())
            {
                Gdiplus::Graphics aGraphics(mrParent.getHDC());

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

                paintToGdiPlus(
                    aGraphics,
                    rTR,
                    *aARGB.get());

                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.get())
            {
                Gdiplus::Graphics aGraphics(mrParent.getHDC());

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

                paintToGdiPlus(
                    aGraphics,
                    rTR,
                    *aARGB.get());

                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.get())
        {
            const long nSrcWidth(aARGB->GetWidth());
            const long nSrcHeight(aARGB->GetHeight());

            if(nSrcWidth && nSrcHeight)
            {
                const long nDestWidth(basegfx::fround(basegfx::B2DVector(rX - rNull).getLength()));
                const 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 paralellogram
                    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::TryRenderCachedNativeControl(ControlCacheKey& /*rControlCacheKey*/, int /*nX*/, int /*nY*/)
    {
        return false;
    }

    bool WinSalGraphicsImpl::RenderAndCacheNativeControl(OpenGLCompatibleDC& /*rWhite*/, OpenGLCompatibleDC& /*rBlack*/,
            int /*nX*/, int /*nY*/ , ControlCacheKey& /*aControlCacheKey*/)
    {
        return false;
    }

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

↑ V773 Visibility scope of the 'pTmpSalBmp' pointer was exited without releasing the memory. A memory leak is possible.

↑ V730 Not all members of a class are initialized inside the constructor. Consider inspecting: mnPenColor, mnBrushColor.