Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quellverzeichnis  gdiimpl.cxx

  Interaktion und
PortierbarkeitC
 

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


#include <sal/config.h>

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

#include <svsys.h>

#include "gdiimpl.hxx"

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

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

#include <prewin.h>

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

#include <postwin.h>

#define SAL_POLYPOLYCOUNT_STACKBUF          8
#define SAL_POLYPOLYPOINTS_STACKBUF         64

#define SAL_POLY_STACKBUF       32

namespace {

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

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

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

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

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

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

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

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

            // end figure
            pWinFlagAry[-1] |= PT_CLOSEFIGURE;
        }
    }
}

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

// namespace

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

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

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

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

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

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

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

namespace
{

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

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

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

// namespace

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        DeleteRegion( hInvalidateRgn );
    }

}

namespace {

void ImplDrawBitmap( HDC hDC, const SalTwoRect& rPosAry, const WinSalBitmap& rSalBitmap,
                     bool bPrinter, int nDrawMode )
{
    if( !hDC )
        return;

    HGLOBAL     hDrawDIB;
    HBITMAP     hDrawDDB = rSalBitmap.ImplGethDDB();
    std::optional<WinSalBitmap> xTmpSalBmp;
    bool        bPrintDDB = ( bPrinter && hDrawDDB );

    if( bPrintDDB )
    {
        xTmpSalBmp.emplace();
        xTmpSalBmp->Create(rSalBitmap, vcl::bitDepthToPixelFormat(rSalBitmap.GetBitCount()));
        hDrawDIB = xTmpSalBmp->ImplGethDIB();
    }
    else
        hDrawDIB = rSalBitmap.ImplGethDIB();

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

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

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

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

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

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

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

            SetStretchBltMode( hDC, nOldStretchMode );
        }

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

// namespace

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

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

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

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

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

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

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

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

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

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

    ScopedHBITMAP hMemBitmap;
    ScopedHBITMAP hMaskBitmap;

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

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

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

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

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

    // now MemDC contains background, MaskDC the transparency mask

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

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

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

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

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

    return bRet;
}

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

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

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

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

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

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

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

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

    std::shared_ptr<WinSalBitmap> pSalBitmap;

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

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

    {
        ScopedCachedHDC<CACHED_HDC_1> hBmpDC(hBmpBitmap);

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

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

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

    return pSalBitmap;
}

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

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

namespace
{

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

    return pSalData->mh50Brush;
}

// namespace

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

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

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

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

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

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

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

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

    if ( nSalFlags & SalInvert::TrackFrame )
        Polyline( mrParent.getHDC(), pWinPtAry.get(), static_cast<int>(nPoints) );
    else
        Polygon( mrParent.getHDC(), pWinPtAry.get(), static_cast<int>(nPoints) );

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

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

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

tools::Long WinSalGraphicsImpl::GetGraphicsWidth() const
{
    if( !mrParent.gethWnd() || !IsWindow( mrParent.gethWnd() ) )
        return 0;

    WinSalFrame* pFrame = GetWindowPtr( mrParent.gethWnd() );
    if( !pFrame )
        return 0;

    if (pFrame->GetWidth())
        return pFrame->GetWidth();

    // TODO: perhaps not needed, width should always be up-to-date
    RECT aRect;
    GetClientRect( mrParent.gethWnd(), &aRect );
    return aRect.right;
}

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&&nbsp;rCandidate)
{
    if(rCandidate.areControlPointsUsed())
        return false;

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

    return true;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        // debug code if you want to check range of the newly applied ClipRegion
        //RECT aBound;
        //const int aRegionType = GetRgnBox(mrParent.mhRegion, &aBound);
    }
    else
    {
        // #i123585# See above, this is a valid case, execute it
        SelectClipRgn( mrParent.getHDC(), nullptr );
    }
}

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

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

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

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

    ResetPen(hNewPen);

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

HPEN WinSalGraphicsImpl::SearchStockPen(COLORREF nPenColor)
{
    // Only screen, because printer has problems, when we use stock objects.
    if (mrParent.isPrinter())
        return nullptr;

    const SalData* pSalData = GetSalData();

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

    return nullptr;
}

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

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

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

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

    mhPen = hNewPen;
}

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

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

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

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

    ResetBrush(hNewBrush);

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

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

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

    return nullptr;
}

HBRUSH WinSalGraphicsImpl::MakeBrush(Color nColor)
{
    const BYTE        nRed        = nColor.GetRed();
    const BYTE        nGreen      = nColor.GetGreen();
    const BYTE        nBlue       = nColor.GetBlue();
    const COLORREF    nBrushColor = RGB(nRed, nGreen, nBlue);

    return CreateSolidBrush(nBrushColor);
}

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

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

    mhBrush = hNewBrush;
}

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

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

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

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

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

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

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

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

    DrawPixelImpl( nX, nY, nCol );
}

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

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

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

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

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

    Polyline( mrParent.getHDC(), pWinPtAry.get(), static_cast<int>(nPoints) );

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

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

    Polygon( mrParent.getHDC(), pWinPtAry.get(), static_cast<int>(nPoints) );
}

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

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

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

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

    PolyPolygon( mrParent.getHDC(), pWinPointAryAry, reinterpret_cast<int*>(pWinPointAry)static_cast<UINT>(nPoly) );

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

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

    const HDC hdc = mrParent.getHDC();

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

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

        ++pPtAry;
        ++pFlgAry;
    }

    return true;
}

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

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

    bool bRet( false );

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

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

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

    return bRet;
}

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

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

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

    bool bRet( false );

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

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

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

    return bRet;
}

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

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

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

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

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

        aSnappedPoint *= rObjectToDeviceInv;

        return aSnappedPoint;
    }

    return rPolygon.getB2DPoint(nIndex);
}

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

    const sal_uInt32 nEdgeCount(rPolygon.isClosed() ? nCount : nCount - 1);
    if(nEdgeCount == 0)
        return;

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

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

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

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

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

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

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

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

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

namespace {

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

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

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

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

    virtual sal_Int64 estimateUsageInBytes() const override;
};

}

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

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

    if(!mpGraphicsPath)
        return 0;

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

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

    return nRetval;
}

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

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

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

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

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

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

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

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

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

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

            pGraphicsPath->CloseFigure();
        }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    return true;
}

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

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

    aAttributes.SetWrapMode(Gdiplus::WrapModeTileFlipXY);

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

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

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

bool WinSalGraphicsImpl::TryDrawBitmapGDIPlus(const SalTwoRect& rTR, const SalBitmap& rSrcBitmap)
{
    if(!rTR.mnSrcWidth || !rTR.mnSrcHeight || !rTR.mnDestWidth || !rTR.mnDestHeight)
        return false;

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

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

    if(!aARGB)
        return false;

    Gdiplus::Graphics aGraphics(mrParent.getHDC());

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

    paintToGdiPlus(
        aGraphics,
        rTR,
        *aARGB);

    return true;
}

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)
        return false;

    assert(dynamic_cast<const WinSalBitmap*>(&rSrcBitmap));
    assert(dynamic_cast<const WinSalBitmap*>(&rAlphaBmp));

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

    if(!aARGB)
        return false;

    Gdiplus::Graphics aGraphics(mrParent.getHDC());

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

    paintToGdiPlus(
        aGraphics,
        rTR,
        *aARGB);

    return true;
}

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

    if( fAlpha != 1.0 )
        return false;

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

    if(!aARGB)
        return false;

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

    if(!nSrcWidth || !nSrcHeight)
        return true;

    const tools::Long nDestWidth(basegfx::fround<tools::Long>(basegfx::B2DVector(rX - rNull).getLength()));
    const tools::Long nDestHeight(basegfx::fround<tools::Long>(basegfx::B2DVector(rY - rNull).getLength()));

    if(!nDestWidth || !nDestHeight)
        return true;

    Gdiplus::Graphics aGraphics(mrParent.getHDC());
    Gdiplus::PointF aDestPoints[3];
    Gdiplus::ImageAttributes aAttributes;

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

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

    aAttributes.SetWrapMode(Gdiplus::WrapModeTileFlipXY);

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

    return true;
}

bool WinSalGraphicsImpl::hasFastDrawTransformedBitmap() const
{
    return false;
}

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

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

bool WinSalGraphicsImpl::supportsOperation(OutDevSupportType /*eType*/) const
{
    return false;
}

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

Messung V0.5 in Prozent
C=93 H=98 G=95

¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen0.70Angebot  (Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können 2026-05-08) ¤

*Eine klare Vorstellung vom Zielzustand






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge