diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
commit | ed5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch) | |
tree | 7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /vcl/win/gdi/gdiimpl.cxx | |
parent | Initial commit. (diff) | |
download | libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.tar.xz libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.zip |
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | vcl/win/gdi/gdiimpl.cxx | 2753 |
1 files changed, 2753 insertions, 0 deletions
diff --git a/vcl/win/gdi/gdiimpl.cxx b/vcl/win/gdi/gdiimpl.cxx new file mode 100644 index 000000000..2c4187325 --- /dev/null +++ b/vcl/win/gdi/gdiimpl.cxx @@ -0,0 +1,2753 @@ +/* -*- 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> + +#if defined _MSC_VER +#ifndef min +#define min(a,b) (((a) < (b)) ? (a) : (b)) +#endif +#ifndef max +#define max(a,b) (((a) > (b)) ? (a) : (b)) +#endif +#endif + +#include <prewin.h> + +#include <gdiplus.h> +#include <gdiplusenums.h> +#include <gdipluscolor.h> + +#include <postwin.h> + +#define SAL_POLYPOLYCOUNT_STACKBUF 8 +#define SAL_POLYPOLYPOINTS_STACKBUF 64 + +#define SAL_POLY_STACKBUF 32 + +namespace { + +// #100127# Fill point and flag memory from array of points which +// might also contain bezier control points for the PolyDraw() GDI method +// Make sure pWinPointAry and pWinFlagAry are big enough +void ImplPreparePolyDraw( bool bCloseFigures, + sal_uLong nPoly, + const sal_uInt32* pPoints, + const Point* const* pPtAry, + const PolyFlags* const* pFlgAry, + POINT* pWinPointAry, + BYTE* pWinFlagAry ) +{ + sal_uLong nCurrPoly; + for( nCurrPoly=0; nCurrPoly<nPoly; ++nCurrPoly ) + { + const Point* pCurrPoint = *pPtAry++; + const PolyFlags* pCurrFlag = *pFlgAry++; + const sal_uInt32 nCurrPoints = *pPoints++; + const bool bHaveFlagArray( pCurrFlag ); + sal_uLong nCurrPoint; + + if( nCurrPoints ) + { + // start figure + *pWinPointAry++ = POINT { static_cast<LONG>(pCurrPoint->getX()), static_cast<LONG>(pCurrPoint->getY()) }; + pCurrPoint++; + *pWinFlagAry++ = PT_MOVETO; + ++pCurrFlag; + + for( nCurrPoint=1; nCurrPoint<nCurrPoints; ) + { + // #102067# Check existence of flag array + if( bHaveFlagArray && + ( nCurrPoint + 2 ) < nCurrPoints ) + { + PolyFlags P4( pCurrFlag[ 2 ] ); + + if( ( PolyFlags::Control == pCurrFlag[ 0 ] ) && + ( PolyFlags::Control == pCurrFlag[ 1 ] ) && + ( PolyFlags::Normal == P4 || PolyFlags::Smooth == P4 || PolyFlags::Symmetric == P4 ) ) + { + // control point one + *pWinPointAry++ = POINT { static_cast<LONG>(pCurrPoint->getX()), static_cast<LONG>(pCurrPoint->getY()) }; + pCurrPoint++; + *pWinFlagAry++ = PT_BEZIERTO; + + // control point two + *pWinPointAry++ = POINT { static_cast<LONG>(pCurrPoint->getX()), static_cast<LONG>(pCurrPoint->getY()) }; + pCurrPoint++; + *pWinFlagAry++ = PT_BEZIERTO; + + // end point + *pWinPointAry++ = POINT { static_cast<LONG>(pCurrPoint->getX()), static_cast<LONG>(pCurrPoint->getY()) }; + pCurrPoint++; + *pWinFlagAry++ = PT_BEZIERTO; + + nCurrPoint += 3; + pCurrFlag += 3; + continue; + } + } + + // regular line point + *pWinPointAry++ = POINT { static_cast<LONG>(pCurrPoint->getX()), static_cast<LONG>(pCurrPoint->getY()) }; + pCurrPoint++; + *pWinFlagAry++ = PT_LINETO; + ++pCurrFlag; + ++nCurrPoint; + } + + // end figure? + if( bCloseFigures ) + pWinFlagAry[-1] |= PT_CLOSEFIGURE; + } + } +} + +Color ImplGetROPColor( SalROPColor nROPColor ) +{ + Color nColor; + if ( nROPColor == SalROPColor::N0 ) + nColor = Color( 0, 0, 0 ); + else + nColor = Color( 255, 255, 255 ); + return nColor; +} + +bool IsDitherColor(BYTE nRed, BYTE nGreen, BYTE nBlue) +{ + constexpr sal_uInt8 DITHER_PAL_DELTA = 51; + + return !(nRed % DITHER_PAL_DELTA) && + !(nGreen % DITHER_PAL_DELTA) && + !(nBlue % DITHER_PAL_DELTA); +} + +bool IsPaletteColor(BYTE nRed, BYTE nGreen, BYTE nBlue) +{ + static const PALETTEENTRY aImplSalSysPalEntryAry[] = + { + { 0, 0, 0, 0 }, + { 0, 0, 0x80, 0 }, + { 0, 0x80, 0, 0 }, + { 0, 0x80, 0x80, 0 }, + { 0x80, 0, 0, 0 }, + { 0x80, 0, 0x80, 0 }, + { 0x80, 0x80, 0, 0 }, + { 0x80, 0x80, 0x80, 0 }, + { 0xC0, 0xC0, 0xC0, 0 }, + { 0, 0, 0xFF, 0 }, + { 0, 0xFF, 0, 0 }, + { 0, 0xFF, 0xFF, 0 }, + { 0xFF, 0, 0, 0 }, + { 0xFF, 0, 0xFF, 0 }, + { 0xFF, 0xFF, 0, 0 }, + { 0xFF, 0xFF, 0xFF, 0 } + }; + + for (const auto& rPalEntry : aImplSalSysPalEntryAry) + { + if(rPalEntry.peRed == nRed && + rPalEntry.peGreen == nGreen && + rPalEntry.peBlue == nBlue) + { + return true; + } + } + + return false; +} + +bool IsExtraColor(BYTE nRed, BYTE nGreen, BYTE nBlue) +{ + return (nRed == 0) && (nGreen == 184) && (nBlue == 255); +} + +bool ImplIsPaletteEntry(BYTE nRed, BYTE nGreen, BYTE nBlue) +{ + return IsDitherColor(nRed, nGreen, nBlue) || + IsPaletteColor(nRed, nGreen, nBlue) || + IsExtraColor(nRed, nGreen, nBlue); +} + +} // namespace + +WinSalGraphicsImpl::WinSalGraphicsImpl(WinSalGraphics& rParent): + mrParent(rParent), + mbXORMode(false), + mbPen(false), + mhPen(nullptr), + mbStockPen(false), + mbBrush(false), + mbStockBrush(false), + mhBrush(nullptr) +{ +} + +WinSalGraphicsImpl::~WinSalGraphicsImpl() +{ + if ( mhPen ) + { + if ( !mbStockPen ) + DeletePen( mhPen ); + } + + if ( mhBrush ) + { + if ( !mbStockBrush ) + DeleteBrush( mhBrush ); + } +} + +void WinSalGraphicsImpl::Init() +{ +} + +void WinSalGraphicsImpl::freeResources() +{ +} + +bool WinSalGraphicsImpl::drawEPS(tools::Long, tools::Long, tools::Long, tools::Long, void*, sal_uInt32) +{ + return false; +} + +void WinSalGraphicsImpl::copyBits( const SalTwoRect& rPosAry, SalGraphics* pSrcGraphics ) +{ + HDC hSrcDC; + DWORD nRop; + + if ( pSrcGraphics ) + hSrcDC = static_cast<WinSalGraphics*>(pSrcGraphics)->getHDC(); + else + hSrcDC = mrParent.getHDC(); + + if ( mbXORMode ) + nRop = SRCINVERT; + else + nRop = SRCCOPY; + + if ( (rPosAry.mnSrcWidth == rPosAry.mnDestWidth) && + (rPosAry.mnSrcHeight == rPosAry.mnDestHeight) ) + { + BitBlt( mrParent.getHDC(), + static_cast<int>(rPosAry.mnDestX), static_cast<int>(rPosAry.mnDestY), + static_cast<int>(rPosAry.mnDestWidth), static_cast<int>(rPosAry.mnDestHeight), + hSrcDC, + static_cast<int>(rPosAry.mnSrcX), static_cast<int>(rPosAry.mnSrcY), + nRop ); + } + else + { + int nOldStretchMode = SetStretchBltMode( mrParent.getHDC(), STRETCH_DELETESCANS ); + StretchBlt( mrParent.getHDC(), + static_cast<int>(rPosAry.mnDestX), static_cast<int>(rPosAry.mnDestY), + static_cast<int>(rPosAry.mnDestWidth), static_cast<int>(rPosAry.mnDestHeight), + hSrcDC, + static_cast<int>(rPosAry.mnSrcX), static_cast<int>(rPosAry.mnSrcY), + static_cast<int>(rPosAry.mnSrcWidth), static_cast<int>(rPosAry.mnSrcHeight), + nRop ); + SetStretchBltMode( mrParent.getHDC(), nOldStretchMode ); + } +} + +namespace +{ + +void MakeInvisibleArea(const RECT& rSrcRect, + int nLeft, int nTop, int nRight, int nBottom, + HRGN& rhInvalidateRgn) +{ + if (!rhInvalidateRgn) + { + rhInvalidateRgn = CreateRectRgnIndirect(&rSrcRect); + } + + ScopedHRGN hTempRgn(CreateRectRgn(nLeft, nTop, nRight, nBottom)); + CombineRgn(rhInvalidateRgn, rhInvalidateRgn, hTempRgn.get(), RGN_DIFF); +} + +void ImplCalcOutSideRgn( const RECT& rSrcRect, + int nLeft, int nTop, int nRight, int nBottom, + HRGN& rhInvalidateRgn ) +{ + // calculate area outside the visible region + if (rSrcRect.left < nLeft) + { + MakeInvisibleArea(rSrcRect, -31999, 0, nLeft, 31999, rhInvalidateRgn); + } + if (rSrcRect.top < nTop) + { + MakeInvisibleArea(rSrcRect, 0, -31999, 31999, nTop, rhInvalidateRgn); + } + if (rSrcRect.right > nRight) + { + MakeInvisibleArea(rSrcRect, nRight, 0, 31999, 31999, rhInvalidateRgn); + } + if (rSrcRect.bottom > nBottom) + { + MakeInvisibleArea(rSrcRect, 0, nBottom, 31999, 31999, rhInvalidateRgn); + } +} + +} // namespace + +void WinSalGraphicsImpl::copyArea( tools::Long nDestX, tools::Long nDestY, + tools::Long nSrcX, tools::Long nSrcY, + tools::Long nSrcWidth, tools::Long nSrcHeight, + bool bWindowInvalidate ) +{ + bool bRestoreClipRgn = false; + HRGN hOldClipRgn = nullptr; + int nOldClipRgnType = ERROR; + HRGN hInvalidateRgn = nullptr; + + // do we have to invalidate also the overlapping regions? + if ( bWindowInvalidate && mrParent.isWindow() ) + { + // compute and invalidate those parts that were either off-screen or covered by other windows + // while performing the above BitBlt + // those regions then have to be invalidated as they contain useless/wrong data + RECT aSrcRect; + RECT aClipRect; + RECT aTempRect; + RECT aTempRect2; + HRGN hTempRgn; + HWND hWnd; + + // restrict srcRect to this window (calc intersection) + aSrcRect.left = static_cast<int>(nSrcX); + aSrcRect.top = static_cast<int>(nSrcY); + aSrcRect.right = aSrcRect.left+static_cast<int>(nSrcWidth); + aSrcRect.bottom = aSrcRect.top+static_cast<int>(nSrcHeight); + GetClientRect( mrParent.gethWnd(), &aClipRect ); + if ( IntersectRect( &aSrcRect, &aSrcRect, &aClipRect ) ) + { + // transform srcRect to screen coordinates + POINT aPt; + aPt.x = 0; + aPt.y = 0; + ClientToScreen( mrParent.gethWnd(), &aPt ); + aSrcRect.left += aPt.x; + aSrcRect.top += aPt.y; + aSrcRect.right += aPt.x; + aSrcRect.bottom += aPt.y; + hInvalidateRgn = nullptr; + + // compute the parts that are off screen (ie invisible) + RECT theScreen; + ImplSalGetWorkArea( nullptr, &theScreen, nullptr ); // find the screen area taking multiple monitors into account + ImplCalcOutSideRgn( aSrcRect, theScreen.left, theScreen.top, theScreen.right, theScreen.bottom, hInvalidateRgn ); + + // calculate regions that are covered by other windows + HRGN hTempRgn2 = nullptr; + HWND hWndTopWindow = mrParent.gethWnd(); + // Find the TopLevel Window, because only Windows which are in + // in the foreground of our TopLevel window must be considered + if ( GetWindowStyle( hWndTopWindow ) & WS_CHILD ) + { + RECT aTempRect3 = aSrcRect; + do + { + hWndTopWindow = ::GetParent( hWndTopWindow ); + + // Test if the Parent clips our window + GetClientRect( hWndTopWindow, &aTempRect ); + POINT aPt2; + aPt2.x = 0; + aPt2.y = 0; + ClientToScreen( hWndTopWindow, &aPt2 ); + aTempRect.left += aPt2.x; + aTempRect.top += aPt2.y; + aTempRect.right += aPt2.x; + aTempRect.bottom += aPt2.y; + IntersectRect( &aTempRect3, &aTempRect3, &aTempRect ); + } + while ( GetWindowStyle( hWndTopWindow ) & WS_CHILD ); + + // If one or more Parents clip our window, then we must + // calculate the outside area + if ( !EqualRect( &aSrcRect, &aTempRect3 ) ) + { + ImplCalcOutSideRgn( aSrcRect, + aTempRect3.left, aTempRect3.top, + aTempRect3.right, aTempRect3.bottom, + hInvalidateRgn ); + } + } + // retrieve the top-most (z-order) child window + hWnd = GetWindow( GetDesktopWindow(), GW_CHILD ); + while ( hWnd ) + { + if ( hWnd == hWndTopWindow ) + break; + if ( IsWindowVisible( hWnd ) && !IsIconic( hWnd ) ) + { + GetWindowRect( hWnd, &aTempRect ); + if ( IntersectRect( &aTempRect2, &aSrcRect, &aTempRect ) ) + { + // hWnd covers part or all of aSrcRect + if ( !hInvalidateRgn ) + hInvalidateRgn = CreateRectRgnIndirect( &aSrcRect ); + + // get full bounding box of hWnd + hTempRgn = CreateRectRgnIndirect( &aTempRect ); + + // get region of hWnd (the window may be shaped) + if ( !hTempRgn2 ) + hTempRgn2 = CreateRectRgn( 0, 0, 0, 0 ); + int nRgnType = GetWindowRgn( hWnd, hTempRgn2 ); + if ( (nRgnType != ERROR) && (nRgnType != NULLREGION) ) + { + // convert window region to screen coordinates + OffsetRgn( hTempRgn2, aTempRect.left, aTempRect.top ); + // and intersect with the window's bounding box + CombineRgn( hTempRgn, hTempRgn, hTempRgn2, RGN_AND ); + } + // finally compute that part of aSrcRect which is not covered by any parts of hWnd + CombineRgn( hInvalidateRgn, hInvalidateRgn, hTempRgn, RGN_DIFF ); + DeleteRegion( hTempRgn ); + } + } + // retrieve the next window in the z-order, i.e. the window below hwnd + hWnd = GetWindow( hWnd, GW_HWNDNEXT ); + } + if ( hTempRgn2 ) + DeleteRegion( hTempRgn2 ); + if ( hInvalidateRgn ) + { + // hInvalidateRgn contains the fully visible parts of the original srcRect + hTempRgn = CreateRectRgnIndirect( &aSrcRect ); + // subtract it from the original rect to get the occluded parts + int nRgnType = CombineRgn( hInvalidateRgn, hTempRgn, hInvalidateRgn, RGN_DIFF ); + DeleteRegion( hTempRgn ); + + if ( (nRgnType != ERROR) && (nRgnType != NULLREGION) ) + { + // move the occluded parts to the destination pos + int nOffX = static_cast<int>(nDestX-nSrcX); + int nOffY = static_cast<int>(nDestY-nSrcY); + OffsetRgn( hInvalidateRgn, nOffX-aPt.x, nOffY-aPt.y ); + + // by excluding hInvalidateRgn from the system's clip region + // we will prevent bitblt from copying useless data + // especially now shadows from overlapping windows will appear (#i36344) + hOldClipRgn = CreateRectRgn( 0, 0, 0, 0 ); + nOldClipRgnType = GetClipRgn( mrParent.getHDC(), hOldClipRgn ); + + bRestoreClipRgn = true; // indicate changed clipregion and force invalidate + ExtSelectClipRgn( mrParent.getHDC(), hInvalidateRgn, RGN_DIFF ); + } + } + } + } + + BitBlt( mrParent.getHDC(), + static_cast<int>(nDestX), static_cast<int>(nDestY), + static_cast<int>(nSrcWidth), static_cast<int>(nSrcHeight), + mrParent.getHDC(), + static_cast<int>(nSrcX), static_cast<int>(nSrcY), + SRCCOPY ); + + if( bRestoreClipRgn ) + { + // restore old clip region + if( nOldClipRgnType != ERROR ) + SelectClipRgn( mrParent.getHDC(), hOldClipRgn); + DeleteRegion( hOldClipRgn ); + + // invalidate regions that were not copied + bool bInvalidate = true; + + // Combine Invalidate vcl::Region with existing ClipRegion + HRGN hTempRgn = CreateRectRgn( 0, 0, 0, 0 ); + if ( GetClipRgn( mrParent.getHDC(), hTempRgn ) == 1 ) + { + int nRgnType = CombineRgn( hInvalidateRgn, hTempRgn, hInvalidateRgn, RGN_AND ); + if ( (nRgnType == ERROR) || (nRgnType == NULLREGION) ) + bInvalidate = false; + } + DeleteRegion( hTempRgn ); + + if ( bInvalidate ) + { + InvalidateRgn( mrParent.gethWnd(), hInvalidateRgn, TRUE ); + // here we only initiate an update if this is the MainThread, + // so that there is no deadlock when handling the Paint event, + // as the SolarMutex is already held by this Thread + SalData* pSalData = GetSalData(); + DWORD nCurThreadId = GetCurrentThreadId(); + if ( pSalData->mnAppThreadId == nCurThreadId ) + UpdateWindow( mrParent.gethWnd() ); + } + + DeleteRegion( hInvalidateRgn ); + } + +} + +namespace { + +void ImplDrawBitmap( HDC hDC, const SalTwoRect& rPosAry, const WinSalBitmap& rSalBitmap, + bool bPrinter, int nDrawMode ) +{ + if( hDC ) + { + HGLOBAL hDrawDIB; + HBITMAP hDrawDDB = rSalBitmap.ImplGethDDB(); + std::unique_ptr<WinSalBitmap> xTmpSalBmp; + bool bPrintDDB = ( bPrinter && hDrawDDB ); + + if( bPrintDDB ) + { + xTmpSalBmp.reset(new WinSalBitmap); + xTmpSalBmp->Create(rSalBitmap, vcl::bitDepthToPixelFormat(rSalBitmap.GetBitCount())); + hDrawDIB = xTmpSalBmp->ImplGethDIB(); + } + else + hDrawDIB = rSalBitmap.ImplGethDIB(); + + if( hDrawDIB ) + { + PBITMAPINFO pBI = static_cast<PBITMAPINFO>(GlobalLock( hDrawDIB )); + PBYTE pBits = reinterpret_cast<PBYTE>(pBI) + pBI->bmiHeader.biSize + + WinSalBitmap::ImplGetDIBColorCount( hDrawDIB ) * sizeof( RGBQUAD ); + const int nOldStretchMode = SetStretchBltMode( hDC, STRETCH_DELETESCANS ); + + StretchDIBits( hDC, + static_cast<int>(rPosAry.mnDestX), static_cast<int>(rPosAry.mnDestY), + static_cast<int>(rPosAry.mnDestWidth), static_cast<int>(rPosAry.mnDestHeight), + static_cast<int>(rPosAry.mnSrcX), static_cast<int>(pBI->bmiHeader.biHeight - rPosAry.mnSrcHeight - rPosAry.mnSrcY), + static_cast<int>(rPosAry.mnSrcWidth), static_cast<int>(rPosAry.mnSrcHeight), + pBits, pBI, DIB_RGB_COLORS, nDrawMode ); + + GlobalUnlock( hDrawDIB ); + SetStretchBltMode( hDC, nOldStretchMode ); + } + else if( hDrawDDB && !bPrintDDB ) + { + ScopedCachedHDC<CACHED_HDC_DRAW> hBmpDC(hDrawDDB); + + COLORREF nOldBkColor = RGB(0xFF,0xFF,0xFF); + COLORREF nOldTextColor = RGB(0,0,0); + bool bMono = ( rSalBitmap.GetBitCount() == 1 ); + + if( bMono ) + { + COLORREF nBkColor = RGB( 0xFF, 0xFF, 0xFF ); + COLORREF nTextColor = RGB( 0x00, 0x00, 0x00 ); + //fdo#33455 handle 1 bit depth pngs with palette entries + //to set fore/back colors + if (BitmapBuffer* pBitmapBuffer = const_cast<WinSalBitmap&>(rSalBitmap).AcquireBuffer(BitmapAccessMode::Info)) + { + const BitmapPalette& rPalette = pBitmapBuffer->maPalette; + if (rPalette.GetEntryCount() == 2) + { + Color nCol = rPalette[0]; + nTextColor = RGB( nCol.GetRed(), nCol.GetGreen(), nCol.GetBlue() ); + nCol = rPalette[1]; + nBkColor = RGB( nCol.GetRed(), nCol.GetGreen(), nCol.GetBlue() ); + } + const_cast<WinSalBitmap&>(rSalBitmap).ReleaseBuffer(pBitmapBuffer, BitmapAccessMode::Info); + } + nOldBkColor = SetBkColor( hDC, nBkColor ); + nOldTextColor = ::SetTextColor( hDC, nTextColor ); + } + + if ( (rPosAry.mnSrcWidth == rPosAry.mnDestWidth) && + (rPosAry.mnSrcHeight == rPosAry.mnDestHeight) ) + { + BitBlt( hDC, + static_cast<int>(rPosAry.mnDestX), static_cast<int>(rPosAry.mnDestY), + static_cast<int>(rPosAry.mnDestWidth), static_cast<int>(rPosAry.mnDestHeight), + hBmpDC.get(), + static_cast<int>(rPosAry.mnSrcX), static_cast<int>(rPosAry.mnSrcY), + nDrawMode ); + } + else + { + const int nOldStretchMode = SetStretchBltMode( hDC, STRETCH_DELETESCANS ); + + StretchBlt( hDC, + static_cast<int>(rPosAry.mnDestX), static_cast<int>(rPosAry.mnDestY), + static_cast<int>(rPosAry.mnDestWidth), static_cast<int>(rPosAry.mnDestHeight), + hBmpDC.get(), + static_cast<int>(rPosAry.mnSrcX), static_cast<int>(rPosAry.mnSrcY), + static_cast<int>(rPosAry.mnSrcWidth), static_cast<int>(rPosAry.mnSrcHeight), + nDrawMode ); + + SetStretchBltMode( hDC, nOldStretchMode ); + } + + if( bMono ) + { + SetBkColor( hDC, nOldBkColor ); + ::SetTextColor( hDC, nOldTextColor ); + } + } + } +} + +} // namespace + +void WinSalGraphicsImpl::drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap) +{ + bool bTryDirectPaint(!mrParent.isPrinter() && !mbXORMode); + + if(bTryDirectPaint) + { + // only paint direct when no scaling and no MapMode, else the + // more expensive conversions may be done for short-time Bitmap/BitmapEx + // used for buffering only + if(rPosAry.mnSrcWidth == rPosAry.mnDestWidth && rPosAry.mnSrcHeight == rPosAry.mnDestHeight) + { + bTryDirectPaint = false; + } + } + + // try to draw using GdiPlus directly + if(bTryDirectPaint && TryDrawBitmapGDIPlus(rPosAry, rSalBitmap)) + { + return; + } + + // fall back old stuff + assert(dynamic_cast<const WinSalBitmap*>(&rSalBitmap)); + + ImplDrawBitmap(mrParent.getHDC(), rPosAry, static_cast<const WinSalBitmap&>(rSalBitmap), + mrParent.isPrinter(), + mbXORMode ? SRCINVERT : SRCCOPY ); +} + +void WinSalGraphicsImpl::drawBitmap( const SalTwoRect& rPosAry, + const SalBitmap& rSSalBitmap, + const SalBitmap& rSTransparentBitmap ) +{ + SAL_WARN_IF( mrParent.isPrinter(), "vcl", "No transparency print possible!" ); + bool bTryDirectPaint(!mrParent.isPrinter() && !mbXORMode); + + // try to draw using GdiPlus directly + if(bTryDirectPaint && drawAlphaBitmap(rPosAry, rSSalBitmap, rSTransparentBitmap)) + { + return; + } + + assert(dynamic_cast<const WinSalBitmap*>(&rSSalBitmap)); + assert(dynamic_cast<const WinSalBitmap*>(&rSTransparentBitmap)); + + const WinSalBitmap& rSalBitmap = static_cast<const WinSalBitmap&>(rSSalBitmap); + const WinSalBitmap& rTransparentBitmap = static_cast<const WinSalBitmap&>(rSTransparentBitmap); + + SalTwoRect aPosAry = rPosAry; + int nDstX = static_cast<int>(aPosAry.mnDestX); + int nDstY = static_cast<int>(aPosAry.mnDestY); + int nDstWidth = static_cast<int>(aPosAry.mnDestWidth); + int nDstHeight = static_cast<int>(aPosAry.mnDestHeight); + HDC hDC = mrParent.getHDC(); + + ScopedHBITMAP hMemBitmap; + ScopedHBITMAP hMaskBitmap; + + if( ( nDstWidth > CACHED_HDC_DEFEXT ) || ( nDstHeight > CACHED_HDC_DEFEXT ) ) + { + hMemBitmap.reset(CreateCompatibleBitmap(hDC, nDstWidth, nDstHeight)); + hMaskBitmap.reset(CreateCompatibleBitmap(hDC, nDstWidth, nDstHeight)); + } + + ScopedCachedHDC<CACHED_HDC_1> hMemDC(hMemBitmap.get()); + ScopedCachedHDC<CACHED_HDC_2> hMaskDC(hMaskBitmap.get()); + + aPosAry.mnDestX = aPosAry.mnDestY = 0; + BitBlt( hMemDC.get(), 0, 0, nDstWidth, nDstHeight, hDC, nDstX, nDstY, SRCCOPY ); + + // WIN/WNT seems to have a minor problem mapping the correct color of the + // mask to the palette if we draw the DIB directly ==> draw DDB + if( ( GetBitCount() <= 8 ) && rTransparentBitmap.ImplGethDIB() && rTransparentBitmap.GetBitCount() == 1 ) + { + WinSalBitmap aTmp; + + if( aTmp.Create( rTransparentBitmap, &mrParent ) ) + ImplDrawBitmap( hMaskDC.get(), aPosAry, aTmp, false, SRCCOPY ); + } + else + ImplDrawBitmap( hMaskDC.get(), aPosAry, rTransparentBitmap, false, SRCCOPY ); + + // now MemDC contains background, MaskDC the transparency mask + + // #105055# Respect XOR mode + if( mbXORMode ) + { + ImplDrawBitmap( hMaskDC.get(), aPosAry, rSalBitmap, false, SRCERASE ); + // now MaskDC contains the bitmap area with black background + BitBlt( hMemDC.get(), 0, 0, nDstWidth, nDstHeight, hMaskDC.get(), 0, 0, SRCINVERT ); + // now MemDC contains background XORed bitmap area 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 ); + } + else + ImplDrawBitmap( hDC, aPosAry, rSalBitmap, false, 0x00B8074AUL ); +} + +std::shared_ptr<SalBitmap> WinSalGraphicsImpl::getBitmap( tools::Long nX, tools::Long nY, tools::Long nDX, tools::Long nDY ) +{ + SAL_WARN_IF( mrParent.isPrinter(), "vcl", "No ::GetBitmap() from printer possible!" ); + + std::shared_ptr<WinSalBitmap> pSalBitmap; + + nDX = std::abs( nDX ); + nDY = std::abs( nDY ); + + HDC hDC = mrParent.getHDC(); + HBITMAP hBmpBitmap = CreateCompatibleBitmap( hDC, nDX, nDY ); + bool bRet; + + { + ScopedCachedHDC<CACHED_HDC_1> hBmpDC(hBmpBitmap); + + bRet = BitBlt(hBmpDC.get(), 0, 0, + static_cast<int>(nDX), static_cast<int>(nDY), hDC, + static_cast<int>(nX), static_cast<int>(nY), SRCCOPY) ? TRUE : FALSE; + } + + if( bRet ) + { + pSalBitmap = std::make_shared<WinSalBitmap>(); + + if( !pSalBitmap->Create( hBmpBitmap ) ) + { + pSalBitmap.reset(); + } + } + else + { + // #124826# avoid resource leak! Happens when running without desktop access (remote desktop, service, may be screensavers) + DeleteBitmap( hBmpBitmap ); + } + + return pSalBitmap; +} + +Color WinSalGraphicsImpl::getPixel( tools::Long nX, tools::Long nY ) +{ + COLORREF aWinCol = ::GetPixel( mrParent.getHDC(), static_cast<int>(nX), static_cast<int>(nY) ); + + if ( CLR_INVALID == aWinCol ) + return Color( 0, 0, 0 ); + else + return Color( GetRValue( aWinCol ), + GetGValue( aWinCol ), + GetBValue( aWinCol ) ); +} + +namespace +{ + +HBRUSH Get50PercentBrush() +{ + SalData* pSalData = GetSalData(); + if ( !pSalData->mh50Brush ) + { + if ( !pSalData->mh50Bmp ) + pSalData->mh50Bmp = ImplLoadSalBitmap( SAL_RESID_BITMAP_50 ); + pSalData->mh50Brush = CreatePatternBrush( pSalData->mh50Bmp ); + } + + return pSalData->mh50Brush; +} + +} // namespace + +void WinSalGraphicsImpl::invert( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, SalInvert nFlags ) +{ + if ( nFlags & SalInvert::TrackFrame ) + { + HPEN hDotPen = CreatePen( PS_DOT, 0, 0 ); + HPEN hOldPen = SelectPen( mrParent.getHDC(), hDotPen ); + HBRUSH hOldBrush = SelectBrush( mrParent.getHDC(), GetStockBrush( NULL_BRUSH ) ); + int nOldROP = SetROP2( mrParent.getHDC(), R2_NOT ); + + Rectangle( mrParent.getHDC(), static_cast<int>(nX), static_cast<int>(nY), static_cast<int>(nX+nWidth), static_cast<int>(nY+nHeight) ); + + SetROP2( mrParent.getHDC(), nOldROP ); + SelectPen( mrParent.getHDC(), hOldPen ); + SelectBrush( mrParent.getHDC(), hOldBrush ); + DeletePen( hDotPen ); + } + else if ( nFlags & SalInvert::N50 ) + { + COLORREF nOldTextColor = ::SetTextColor( mrParent.getHDC(), 0 ); + HBRUSH hOldBrush = SelectBrush( mrParent.getHDC(), Get50PercentBrush() ); + PatBlt( mrParent.getHDC(), nX, nY, nWidth, nHeight, PATINVERT ); + ::SetTextColor( mrParent.getHDC(), nOldTextColor ); + SelectBrush( mrParent.getHDC(), hOldBrush ); + } + else + { + RECT aRect; + aRect.left = static_cast<int>(nX); + aRect.top = static_cast<int>(nY); + aRect.right = static_cast<int>(nX)+nWidth; + aRect.bottom = static_cast<int>(nY)+nHeight; + ::InvertRect( mrParent.getHDC(), &aRect ); + } +} + +void WinSalGraphicsImpl::invert( sal_uInt32 nPoints, const Point* pPtAry, SalInvert nSalFlags ) +{ + HPEN hPen; + HPEN hOldPen; + HBRUSH hBrush; + HBRUSH hOldBrush = nullptr; + COLORREF nOldTextColor RGB(0,0,0); + int nOldROP = SetROP2( mrParent.getHDC(), R2_NOT ); + + if ( nSalFlags & SalInvert::TrackFrame ) + hPen = CreatePen( PS_DOT, 0, 0 ); + else + { + + if ( nSalFlags & SalInvert::N50 ) + hBrush = Get50PercentBrush(); + else + hBrush = GetStockBrush( BLACK_BRUSH ); + + hPen = GetStockPen( NULL_PEN ); + nOldTextColor = ::SetTextColor( mrParent.getHDC(), 0 ); + hOldBrush = SelectBrush( mrParent.getHDC(), hBrush ); + } + hOldPen = SelectPen( mrParent.getHDC(), hPen ); + + std::unique_ptr<POINT[]> pWinPtAry(new POINT[nPoints]); + for (sal_uInt32 i=0; i<nPoints; ++i) + pWinPtAry[i] = POINT { static_cast<LONG>(pPtAry[i].getX()), static_cast<LONG>(pPtAry[i].getY()) }; + + // for Windows 95 and its maximum number of points + if ( nSalFlags & SalInvert::TrackFrame ) + { + if ( !Polyline( mrParent.getHDC(), pWinPtAry.get(), static_cast<int>(nPoints) ) && (nPoints > MAX_64KSALPOINTS) ) + Polyline( mrParent.getHDC(), pWinPtAry.get(), MAX_64KSALPOINTS ); + } + else + { + if ( !Polygon( mrParent.getHDC(), pWinPtAry.get(), static_cast<int>(nPoints) ) && (nPoints > MAX_64KSALPOINTS) ) + Polygon( mrParent.getHDC(), pWinPtAry.get(), MAX_64KSALPOINTS ); + } + + SetROP2( mrParent.getHDC(), nOldROP ); + SelectPen( mrParent.getHDC(), hOldPen ); + + if ( nSalFlags & SalInvert::TrackFrame ) + DeletePen( hPen ); + else + { + ::SetTextColor( mrParent.getHDC(), nOldTextColor ); + SelectBrush( mrParent.getHDC(), hOldBrush ); + } +} + +sal_uInt16 WinSalGraphicsImpl::GetBitCount() const +{ + return static_cast<sal_uInt16>(GetDeviceCaps( mrParent.getHDC(), BITSPIXEL )); +} + +tools::Long WinSalGraphicsImpl::GetGraphicsWidth() const +{ + if( mrParent.gethWnd() && IsWindow( mrParent.gethWnd() ) ) + { + WinSalFrame* pFrame = GetWindowPtr( mrParent.gethWnd() ); + if( pFrame ) + { + if( pFrame->maGeometry.nWidth ) + return pFrame->maGeometry.nWidth; + else + { + // TODO: perhaps not needed, maGeometry should always be up-to-date + RECT aRect; + GetClientRect( mrParent.gethWnd(), &aRect ); + return aRect.right; + } + } + } + + return 0; +} + +void WinSalGraphicsImpl::ResetClipRegion() +{ + if ( mrParent.mhRegion ) + { + DeleteRegion( mrParent.mhRegion ); + mrParent.mhRegion = nullptr; + } + + SelectClipRgn( mrParent.getHDC(), nullptr ); +} + +static bool containsOnlyHorizontalAndVerticalEdges(const basegfx::B2DPolygon& rCandidate) +{ + if(rCandidate.areControlPointsUsed()) + { + return false; + } + + const sal_uInt32 nPointCount(rCandidate.count()); + + if(nPointCount < 2) + { + return true; + } + + const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount + 1 : nPointCount); + basegfx::B2DPoint aLast(rCandidate.getB2DPoint(0)); + + for(sal_uInt32 a(1); a < nEdgeCount; a++) + { + const sal_uInt32 nNextIndex(a % nPointCount); + const basegfx::B2DPoint aCurrent(rCandidate.getB2DPoint(nNextIndex)); + + if(!basegfx::fTools::equal(aLast.getX(), aCurrent.getX()) && !basegfx::fTools::equal(aLast.getY(), aCurrent.getY())) + { + return false; + } + + aLast = aCurrent; + } + + return true; +} + +static bool containsOnlyHorizontalAndVerticalEdges(const basegfx::B2DPolyPolygon& rCandidate) +{ + if(rCandidate.areControlPointsUsed()) + { + return false; + } + + for(auto const& rPolygon : rCandidate) + { + if(!containsOnlyHorizontalAndVerticalEdges(rPolygon)) + { + return false; + } + } + + return true; +} + +bool WinSalGraphicsImpl::setClipRegion( const vcl::Region& i_rClip ) +{ + if ( mrParent.mhRegion ) + { + DeleteRegion( mrParent.mhRegion ); + mrParent.mhRegion = nullptr; + } + + bool bUsePolygon(i_rClip.HasPolyPolygonOrB2DPolyPolygon()); + static bool bTryToAvoidPolygon(true); + + // #i122149# try to avoid usage of tools::PolyPolygon ClipRegions when tools::PolyPolygon is no curve + // and only contains horizontal/vertical edges. In that case, use the fallback + // in GetRegionRectangles which will use vcl::Region::GetAsRegionBand() which will do + // the correct polygon-to-RegionBand transformation. + // Background is that when using the same Rectangle as rectangle or as Polygon + // clip region will lead to different results; the polygon-based one will be + // one pixel less to the right and down (see GDI docu for CreatePolygonRgn). This + // again is because of the polygon-nature and it's classic handling when filling. + // This also means that all cases which use a 'true' polygon-based incarnation of + // a vcl::Region should know what they do - it may lead to repaint errors. + if(bUsePolygon && bTryToAvoidPolygon) + { + const basegfx::B2DPolyPolygon aPolyPolygon( i_rClip.GetAsB2DPolyPolygon() ); + + if(!aPolyPolygon.areControlPointsUsed()) + { + if(containsOnlyHorizontalAndVerticalEdges(aPolyPolygon)) + { + bUsePolygon = false; + } + } + } + + if(bUsePolygon) + { + // #i122149# check the comment above to know that this may lead to potential repaint + // problems. It may be solved (if needed) by scaling the polygon by one in X + // and Y. Currently the workaround to only use it if really unavoidable will + // solve most cases. When someone is really using polygon-based Regions he + // should know what he is doing. + // Added code to do that scaling to check if it works, testing it. + const basegfx::B2DPolyPolygon aPolyPolygon( i_rClip.GetAsB2DPolyPolygon() ); + const sal_uInt32 nCount(aPolyPolygon.count()); + + if( nCount ) + { + std::vector< POINT > aPolyPoints; + aPolyPoints.reserve( 1024 ); + std::vector< INT > aPolyCounts( nCount, 0 ); + basegfx::B2DHomMatrix aExpand; + sal_uInt32 nTargetCount(0); + static bool bExpandByOneInXandY(true); + + if(bExpandByOneInXandY) + { + const basegfx::B2DRange aRangeS(aPolyPolygon.getB2DRange()); + const basegfx::B2DRange aRangeT(aRangeS.getMinimum(), aRangeS.getMaximum() + basegfx::B2DTuple(1.0, 1.0)); + aExpand = basegfx::utils::createSourceRangeTargetRangeTransform(aRangeS, aRangeT); + } + + for(auto const& rPolygon : aPolyPolygon) + { + const basegfx::B2DPolygon aPoly( + basegfx::utils::adaptiveSubdivideByDistance( + rPolygon, + 1)); + const sal_uInt32 nPoints(aPoly.count()); + + // tdf#40863 For CustomShapes there is a hack (see + // f64ef72743e55389e446e0d4bc6febd475011023) that adds polygons + // with a single point in top-left and bottom-right corner + // of the BoundRect to be able to determine the correct BoundRect + // in the slideshow. Unfortunately, CreatePolyPolygonRgn below + // fails with polygons containing a single pixel, so clipping is + // lost. For now, use only polygons with more than two points - the + // ones that may have an area. + // Note: polygons with one point which are curves may have an area, + // but the polygon is already subdivided here, so no need to test + // this. + if(nPoints > 2) + { + aPolyCounts[nTargetCount] = nPoints; + nTargetCount++; + + for( sal_uInt32 b = 0; b < nPoints; b++ ) + { + basegfx::B2DPoint aPt(aPoly.getB2DPoint(b)); + + if(bExpandByOneInXandY) + { + aPt = aExpand * aPt; + } + + POINT aPOINT; + // #i122149# do correct rounding + aPOINT.x = basegfx::fround(aPt.getX()); + aPOINT.y = basegfx::fround(aPt.getY()); + aPolyPoints.push_back( aPOINT ); + } + } + } + + if(nTargetCount) + { + mrParent.mhRegion = CreatePolyPolygonRgn( aPolyPoints.data(), aPolyCounts.data(), nTargetCount, ALTERNATE ); + } + } + } + else + { + RectangleVector aRectangles; + i_rClip.GetRegionRectangles(aRectangles); + + sal_uLong nRectBufSize = sizeof(RECT)*aRectangles.size(); + if ( aRectangles.size() < SAL_CLIPRECT_COUNT ) + { + if ( !mrParent.mpStdClipRgnData ) + mrParent.mpStdClipRgnData = reinterpret_cast<RGNDATA*>(new BYTE[sizeof(RGNDATA)-1+(SAL_CLIPRECT_COUNT*sizeof(RECT))]); + mrParent.mpClipRgnData = mrParent.mpStdClipRgnData; + } + else + mrParent.mpClipRgnData = reinterpret_cast<RGNDATA*>(new BYTE[sizeof(RGNDATA)-1+nRectBufSize]); + mrParent.mpClipRgnData->rdh.dwSize = sizeof( RGNDATAHEADER ); + mrParent.mpClipRgnData->rdh.iType = RDH_RECTANGLES; + mrParent.mpClipRgnData->rdh.nCount = aRectangles.size(); + mrParent.mpClipRgnData->rdh.nRgnSize = nRectBufSize; + RECT* pBoundRect = &(mrParent.mpClipRgnData->rdh.rcBound); + SetRectEmpty( pBoundRect ); + RECT* pNextClipRect = reinterpret_cast<RECT*>(&(mrParent.mpClipRgnData->Buffer)); + bool bFirstClipRect = true; + + for (auto const& rectangle : aRectangles) + { + const tools::Long nW(rectangle.GetWidth()); + const tools::Long nH(rectangle.GetHeight()); + + if(nW && nH) + { + const tools::Long nRight(rectangle.Left() + nW); + const tools::Long nBottom(rectangle.Top() + nH); + + if(bFirstClipRect) + { + pBoundRect->left = rectangle.Left(); + pBoundRect->top = rectangle.Top(); + pBoundRect->right = nRight; + pBoundRect->bottom = nBottom; + bFirstClipRect = false; + } + else + { + if(rectangle.Left() < pBoundRect->left) + { + pBoundRect->left = static_cast<int>(rectangle.Left()); + } + + if(rectangle.Top() < pBoundRect->top) + { + pBoundRect->top = static_cast<int>(rectangle.Top()); + } + + if(nRight > pBoundRect->right) + { + pBoundRect->right = static_cast<int>(nRight); + } + + if(nBottom > pBoundRect->bottom) + { + pBoundRect->bottom = static_cast<int>(nBottom); + } + } + + pNextClipRect->left = static_cast<int>(rectangle.Left()); + pNextClipRect->top = static_cast<int>(rectangle.Top()); + pNextClipRect->right = static_cast<int>(nRight); + pNextClipRect->bottom = static_cast<int>(nBottom); + pNextClipRect++; + } + else + { + mrParent.mpClipRgnData->rdh.nCount--; + mrParent.mpClipRgnData->rdh.nRgnSize -= sizeof( RECT ); + } + } + + // create clip region from ClipRgnData + if(0 == mrParent.mpClipRgnData->rdh.nCount) + { + // #i123585# region is empty; this may happen when e.g. a tools::PolyPolygon is given + // that contains no polygons or only empty ones (no width/height). This is + // perfectly fine and we are done, except setting it (see end of method) + } + else if(1 == mrParent.mpClipRgnData->rdh.nCount) + { + RECT* pRect = &(mrParent.mpClipRgnData->rdh.rcBound); + mrParent.mhRegion = CreateRectRgn( pRect->left, pRect->top, + pRect->right, pRect->bottom ); + } + else if(mrParent.mpClipRgnData->rdh.nCount > 1) + { + sal_uLong nSize = mrParent.mpClipRgnData->rdh.nRgnSize+sizeof(RGNDATAHEADER); + mrParent.mhRegion = ExtCreateRegion( nullptr, nSize, mrParent.mpClipRgnData ); + + // if ExtCreateRegion(...) is not supported + if( !mrParent.mhRegion ) + { + RGNDATAHEADER const & pHeader = mrParent.mpClipRgnData->rdh; + + if( pHeader.nCount ) + { + RECT* pRect = reinterpret_cast<RECT*>(mrParent.mpClipRgnData->Buffer); + mrParent.mhRegion = CreateRectRgn( pRect->left, pRect->top, pRect->right, pRect->bottom ); + pRect++; + + for( sal_uLong n = 1; n < pHeader.nCount; n++, pRect++ ) + { + ScopedHRGN hRgn(CreateRectRgn(pRect->left, pRect->top, pRect->right, pRect->bottom)); + CombineRgn( mrParent.mhRegion, mrParent.mhRegion, hRgn.get(), RGN_OR ); + } + } + } + + if ( mrParent.mpClipRgnData != mrParent.mpStdClipRgnData ) + delete [] reinterpret_cast<BYTE*>(mrParent.mpClipRgnData); + } + } + + if( mrParent.mhRegion ) + { + SelectClipRgn( mrParent.getHDC(), mrParent.mhRegion ); + + // debug code if you want to check range of the newly applied ClipRegion + //RECT aBound; + //const int aRegionType = GetRgnBox(mrParent.mhRegion, &aBound); + + //bool bBla = true; + } + else + { + // #i123585# See above, this is a valid case, execute it + SelectClipRgn( mrParent.getHDC(), nullptr ); + } + + // #i123585# retval no longer dependent of mrParent.mhRegion, see TaskId comments above + return true; +} + +void WinSalGraphicsImpl::SetLineColor() +{ + ResetPen(GetStockPen(NULL_PEN)); + + // set new data + mbPen = false; + mbStockPen = true; +} + +void WinSalGraphicsImpl::SetLineColor(Color nColor) +{ + COLORREF nPenColor = PALETTERGB(nColor.GetRed(), + nColor.GetGreen(), + nColor.GetBlue()); + bool bStockPen = false; + + HPEN hNewPen = SearchStockPen(nPenColor); + if (hNewPen) + bStockPen = true; + else + hNewPen = MakePen(nColor); + + ResetPen(hNewPen); + + // set new data + mnPenColor = nPenColor; + maLineColor = nColor; + mbPen = true; + mbStockPen = bStockPen; +} + +HPEN WinSalGraphicsImpl::SearchStockPen(COLORREF nPenColor) +{ + // Only screen, because printer has problems, when we use stock objects. + if (!mrParent.isPrinter()) + { + const SalData* pSalData = GetSalData(); + + for (sal_uInt16 i = 0; i < pSalData->mnStockPenCount; i++) + { + if (nPenColor == pSalData->maStockPenColorAry[i]) + return pSalData->mhStockPenAry[i]; + } + } + + return nullptr; +} + +HPEN WinSalGraphicsImpl::MakePen(Color nColor) +{ + COLORREF nPenColor = PALETTERGB(nColor.GetRed(), + nColor.GetGreen(), + nColor.GetBlue()); + + if (!mrParent.isPrinter()) + { + if (GetSalData()->mhDitherPal && ImplIsSysColorEntry(nColor)) + { + nPenColor = PALRGB_TO_RGB(nPenColor); + } + } + + return CreatePen(PS_SOLID, mrParent.mnPenWidth, nPenColor); +} + +void WinSalGraphicsImpl::ResetPen(HPEN hNewPen) +{ + HPEN hOldPen = SelectPen(mrParent.getHDC(), hNewPen); + + if (mhPen) + { + if (!mbStockPen) + { + DeletePen(mhPen); + } + } + else + { + mrParent.mhDefPen = hOldPen; + } + + mhPen = hNewPen; +} + +void WinSalGraphicsImpl::SetFillColor() +{ + ResetBrush(GetStockBrush(NULL_BRUSH)); + + // set new data + mbBrush = false; + mbStockBrush = true; +} + +void WinSalGraphicsImpl::SetFillColor(Color nColor) +{ + COLORREF nBrushColor = PALETTERGB(nColor.GetRed(), + nColor.GetGreen(), + nColor.GetBlue()); + bool bStockBrush = false; + + HBRUSH hNewBrush = SearchStockBrush(nBrushColor); + if (hNewBrush) + bStockBrush = true; + else + hNewBrush = MakeBrush(nColor); + + ResetBrush(hNewBrush); + + // set new data + mnBrushColor = nBrushColor; + maFillColor = nColor; + mbBrush = true; + mbStockBrush = bStockBrush; +} + +HBRUSH WinSalGraphicsImpl::SearchStockBrush(COLORREF nBrushColor) +{ + // Only screen, because printer has problems, when we use stock objects. + if (!mrParent.isPrinter()) + { + const SalData* pSalData = GetSalData(); + + for (sal_uInt16 i = 0; i < pSalData->mnStockBrushCount; i++) + { + if (nBrushColor == pSalData->maStockBrushColorAry[i]) + return pSalData->mhStockBrushAry[i]; + } + } + + return nullptr; +} + +namespace +{ + +BYTE GetDitherMappingValue(BYTE nVal, BYTE nThres, const SalData* pSalData) +{ + return (pSalData->mpDitherDiff[nVal] > nThres) ? + pSalData->mpDitherHigh[nVal] : pSalData->mpDitherLow[nVal]; +} + +HBRUSH Make16BitDIBPatternBrush(Color nColor) +{ + const SalData* pSalData = GetSalData(); + + const BYTE nRed = nColor.GetRed(); + const BYTE nGreen = nColor.GetGreen(); + const BYTE nBlue = nColor.GetBlue(); + + static const BYTE aOrdDither16Bit[8][8] = + { + { 0, 6, 1, 7, 0, 6, 1, 7 }, + { 4, 2, 5, 3, 4, 2, 5, 3 }, + { 1, 7, 0, 6, 1, 7, 0, 6 }, + { 5, 3, 4, 2, 5, 3, 4, 2 }, + { 0, 6, 1, 7, 0, 6, 1, 7 }, + { 4, 2, 5, 3, 4, 2, 5, 3 }, + { 1, 7, 0, 6, 1, 7, 0, 6 }, + { 5, 3, 4, 2, 5, 3, 4, 2 } + }; + + BYTE* pTmp = pSalData->mpDitherDIBData; + + for(int nY = 0; nY < 8; ++nY) + { + for(int nX = 0; nX < 8; ++nX) + { + const BYTE nThres = aOrdDither16Bit[nY][nX]; + *pTmp++ = GetDitherMappingValue(nBlue, nThres, pSalData); + *pTmp++ = GetDitherMappingValue(nGreen, nThres, pSalData); + *pTmp++ = GetDitherMappingValue(nRed, nThres, pSalData); + } + } + + return CreateDIBPatternBrush(pSalData->mhDitherDIB, DIB_RGB_COLORS); +} + +HBRUSH Make8BitDIBPatternBrush(Color nColor) +{ + const SalData* pSalData = GetSalData(); + + const BYTE nRed = nColor.GetRed(); + const BYTE nGreen = nColor.GetGreen(); + const BYTE nBlue = nColor.GetBlue(); + + static const BYTE aOrdDither8Bit[8][8] = + { + { 0, 38, 9, 48, 2, 40, 12, 50 }, + { 25, 12, 35, 22, 28, 15, 37, 24 }, + { 6, 44, 3, 41, 8, 47, 5, 44 }, + { 32, 19, 28, 16, 34, 21, 31, 18 }, + { 1, 40, 11, 49, 0, 39, 10, 48 }, + { 27, 14, 36, 24, 26, 13, 36, 23 }, + { 8, 46, 4, 43, 7, 45, 4, 42 }, + { 33, 20, 30, 17, 32, 20, 29, 16 } + }; + + BYTE* pTmp = pSalData->mpDitherDIBData; + + for (int nY = 0; nY < 8; ++nY) + { + for (int nX = 0; nX < 8; ++nX) + { + const BYTE nThres = aOrdDither8Bit[nY][nX]; + *pTmp = GetDitherMappingValue(nRed, nThres, pSalData) + + GetDitherMappingValue(nGreen, nThres, pSalData) * 6 + + GetDitherMappingValue(nBlue, nThres, pSalData) * 36; + pTmp++; + } + } + + return CreateDIBPatternBrush(pSalData->mhDitherDIB, DIB_PAL_COLORS); +} + +} // namespace + +HBRUSH WinSalGraphicsImpl::MakeBrush(Color nColor) +{ + const SalData* pSalData = GetSalData(); + + const BYTE nRed = nColor.GetRed(); + const BYTE nGreen = nColor.GetGreen(); + const BYTE nBlue = nColor.GetBlue(); + const COLORREF nBrushColor = PALETTERGB(nRed, nGreen, nBlue); + + if (mrParent.isPrinter() || !pSalData->mhDitherDIB) + return CreateSolidBrush(nBrushColor); + + if (24 == reinterpret_cast<BITMAPINFOHEADER*>(pSalData->mpDitherDIB)->biBitCount) + return Make16BitDIBPatternBrush(nColor); + + if (ImplIsSysColorEntry(nColor)) + return CreateSolidBrush(PALRGB_TO_RGB(nBrushColor)); + + if (ImplIsPaletteEntry(nRed, nGreen, nBlue)) + return CreateSolidBrush(nBrushColor); + + return Make8BitDIBPatternBrush(nColor); +} + +void WinSalGraphicsImpl::ResetBrush(HBRUSH hNewBrush) +{ + HBRUSH hOldBrush = SelectBrush(mrParent.getHDC(), hNewBrush); + + if (mhBrush) + { + if (!mbStockBrush) + { + DeleteBrush(mhBrush); + } + } + else + { + mrParent.mhDefBrush = hOldBrush; + } + + mhBrush = hNewBrush; +} + +void WinSalGraphicsImpl::SetXORMode( bool bSet, bool ) +{ + mbXORMode = bSet; + ::SetROP2( mrParent.getHDC(), bSet ? R2_XORPEN : R2_COPYPEN ); +} + +void WinSalGraphicsImpl::SetROPLineColor( SalROPColor nROPColor ) +{ + SetLineColor( ImplGetROPColor( nROPColor ) ); +} + +void WinSalGraphicsImpl::SetROPFillColor( SalROPColor nROPColor ) +{ + SetFillColor( ImplGetROPColor( nROPColor ) ); +} + +void WinSalGraphicsImpl::DrawPixelImpl( tools::Long nX, tools::Long nY, COLORREF crColor ) +{ + const HDC hDC = mrParent.getHDC(); + + if (!mbXORMode) + { + SetPixel(hDC, static_cast<int>(nX), static_cast<int>(nY), crColor); + return; + } + + ScopedSelectedHBRUSH hBrush(hDC, CreateSolidBrush(crColor)); + PatBlt(hDC, static_cast<int>(nX), static_cast<int>(nY), int(1), int(1), PATINVERT); +} + +void WinSalGraphicsImpl::drawPixel( tools::Long nX, tools::Long nY ) +{ + DrawPixelImpl( nX, nY, mnPenColor ); +} + +void WinSalGraphicsImpl::drawPixel( tools::Long nX, tools::Long nY, Color nColor ) +{ + COLORREF nCol = PALETTERGB( nColor.GetRed(), + nColor.GetGreen(), + nColor.GetBlue() ); + + if ( !mrParent.isPrinter() && + GetSalData()->mhDitherPal && + ImplIsSysColorEntry( nColor ) ) + nCol = PALRGB_TO_RGB( nCol ); + + DrawPixelImpl( nX, nY, nCol ); +} + +void WinSalGraphicsImpl::drawLine( tools::Long nX1, tools::Long nY1, tools::Long nX2, tools::Long nY2 ) +{ + MoveToEx( mrParent.getHDC(), static_cast<int>(nX1), static_cast<int>(nY1), nullptr ); + + LineTo( mrParent.getHDC(), static_cast<int>(nX2), static_cast<int>(nY2) ); + + // LineTo doesn't draw the last pixel + if ( !mrParent.isPrinter() ) + DrawPixelImpl( nX2, nY2, mnPenColor ); +} + +void WinSalGraphicsImpl::drawRect( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight ) +{ + if ( !mbPen ) + { + if ( !mrParent.isPrinter() ) + { + PatBlt( mrParent.getHDC(), static_cast<int>(nX), static_cast<int>(nY), static_cast<int>(nWidth), static_cast<int>(nHeight), + mbXORMode ? PATINVERT : PATCOPY ); + } + else + { + RECT aWinRect; + aWinRect.left = nX; + aWinRect.top = nY; + aWinRect.right = nX+nWidth; + aWinRect.bottom = nY+nHeight; + ::FillRect( mrParent.getHDC(), &aWinRect, mhBrush ); + } + } + else + Rectangle( mrParent.getHDC(), static_cast<int>(nX), static_cast<int>(nY), static_cast<int>(nX+nWidth), static_cast<int>(nY+nHeight) ); +} + +void WinSalGraphicsImpl::drawPolyLine( sal_uInt32 nPoints, const Point* pPtAry ) +{ + std::unique_ptr<POINT[]> pWinPtAry(new POINT[nPoints]); + for (sal_uInt32 i=0; i<nPoints; ++i) + pWinPtAry[i] = POINT { static_cast<LONG>(pPtAry[i].getX()), static_cast<LONG>(pPtAry[i].getY()) }; + + // for Windows 95 and its maximum number of points + if ( !Polyline( mrParent.getHDC(), pWinPtAry.get(), static_cast<int>(nPoints) ) && (nPoints > MAX_64KSALPOINTS) ) + Polyline( mrParent.getHDC(), pWinPtAry.get(), MAX_64KSALPOINTS ); + + // Polyline seems to uses LineTo, which doesn't paint the last pixel (see 87eb8f8ee) + if ( !mrParent.isPrinter() ) + DrawPixelImpl( pWinPtAry[nPoints-1].x, pWinPtAry[nPoints-1].y, mnPenColor ); +} + +void WinSalGraphicsImpl::drawPolygon( sal_uInt32 nPoints, const Point* pPtAry ) +{ + std::unique_ptr<POINT[]> pWinPtAry(new POINT[nPoints]); + for (sal_uInt32 i=0; i<nPoints; ++i) + pWinPtAry[i] = POINT { static_cast<LONG>(pPtAry[i].getX()), static_cast<LONG>(pPtAry[i].getY()) }; + + // for Windows 95 and its maximum number of points + if ( !Polygon( mrParent.getHDC(), pWinPtAry.get(), static_cast<int>(nPoints) ) && (nPoints > MAX_64KSALPOINTS) ) + Polygon( mrParent.getHDC(), pWinPtAry.get(), MAX_64KSALPOINTS ); +} + +void WinSalGraphicsImpl::drawPolyPolygon( sal_uInt32 nPoly, const sal_uInt32* pPoints, + const Point** pPtAry ) +{ + UINT aWinPointAry[SAL_POLYPOLYCOUNT_STACKBUF]; + UINT* pWinPointAry; + UINT nPolyPolyPoints = 0; + UINT nPoints; + UINT i; + + if ( nPoly <= SAL_POLYPOLYCOUNT_STACKBUF ) + pWinPointAry = aWinPointAry; + else + pWinPointAry = new UINT[nPoly]; + + for ( i = 0; i < static_cast<UINT>(nPoly); i++ ) + { + nPoints = static_cast<UINT>(pPoints[i])+1; + pWinPointAry[i] = nPoints; + nPolyPolyPoints += nPoints; + } + + POINT aWinPointAryAry[SAL_POLYPOLYPOINTS_STACKBUF]; + POINT* pWinPointAryAry; + if ( nPolyPolyPoints <= SAL_POLYPOLYPOINTS_STACKBUF ) + pWinPointAryAry = aWinPointAryAry; + else + pWinPointAryAry = new POINT[nPolyPolyPoints]; + UINT n = 0; + for ( i = 0; i < static_cast<UINT>(nPoly); i++ ) + { + nPoints = pWinPointAry[i]; + const Point* pPolyAry = pPtAry[i]; + for (sal_uInt32 j=0; j<nPoints-1; ++j) + pWinPointAryAry[n+j] = POINT { static_cast<LONG>(pPolyAry[j].getX()), static_cast<LONG>(pPolyAry[j].getY()) }; + pWinPointAryAry[n+nPoints-1] = pWinPointAryAry[n]; + n += nPoints; + } + + if ( !PolyPolygon( mrParent.getHDC(), pWinPointAryAry, reinterpret_cast<int*>(pWinPointAry), static_cast<UINT>(nPoly) ) && + (nPolyPolyPoints > MAX_64KSALPOINTS) ) + { + nPolyPolyPoints = 0; + nPoly = 0; + do + { + nPolyPolyPoints += pWinPointAry[static_cast<UINT>(nPoly)]; + nPoly++; + } + while ( nPolyPolyPoints < MAX_64KSALPOINTS ); + nPoly--; + if ( pWinPointAry[static_cast<UINT>(nPoly)] > MAX_64KSALPOINTS ) + pWinPointAry[static_cast<UINT>(nPoly)] = MAX_64KSALPOINTS; + if ( nPoly == 1 ) + Polygon( mrParent.getHDC(), pWinPointAryAry, *pWinPointAry ); + else + PolyPolygon( mrParent.getHDC(), pWinPointAryAry, reinterpret_cast<int*>(pWinPointAry), nPoly ); + } + + if ( pWinPointAry != aWinPointAry ) + delete [] pWinPointAry; + if ( pWinPointAryAry != aWinPointAryAry ) + delete [] pWinPointAryAry; +} + +bool WinSalGraphicsImpl::drawPolyLineBezier( sal_uInt32 nPoints, const Point* pPtAry, const PolyFlags* pFlgAry ) +{ + // #100127# draw an array of points which might also contain bezier control points + if (!nPoints) + return true; + + const HDC hdc = mrParent.getHDC(); + + // TODO: profile whether the following options are faster: + // a) look ahead and draw consecutive bezier or line segments by PolyBezierTo/PolyLineTo resp. + // b) convert our flag array to window's and use PolyDraw + MoveToEx(hdc, static_cast<LONG>(pPtAry->getX()), static_cast<LONG>(pPtAry->getY()), nullptr); + ++pPtAry; + ++pFlgAry; + + for(sal_uInt32 i = 1; i < nPoints; ++i) + { + if(*pFlgAry != PolyFlags::Control) + { + LineTo(hdc, pPtAry->getX(), pPtAry->getY()); + } + else if(nPoints - i > 2) + { + POINT bezierPoints[] = { + POINT { static_cast<LONG>(pPtAry[0].getX()), static_cast<LONG>(pPtAry[0].getY()) }, + POINT { static_cast<LONG>(pPtAry[1].getX()), static_cast<LONG>(pPtAry[1].getY()) }, + POINT { static_cast<LONG>(pPtAry[2].getX()), static_cast<LONG>(pPtAry[2].getY()) }, + }; + PolyBezierTo(hdc, bezierPoints, 3); + i += 2; + pPtAry += 2; + pFlgAry += 2; + } + + ++pPtAry; + ++pFlgAry; + } + + return true; +} + +bool WinSalGraphicsImpl::drawPolygonBezier( sal_uInt32 nPoints, const Point* pPtAry, const PolyFlags* pFlgAry ) +{ + POINT aStackAry1[SAL_POLY_STACKBUF]; + BYTE aStackAry2[SAL_POLY_STACKBUF]; + POINT* pWinPointAry; + BYTE* pWinFlagAry; + if( nPoints > SAL_POLY_STACKBUF ) + { + pWinPointAry = new POINT[ nPoints ]; + pWinFlagAry = new BYTE[ nPoints ]; + } + else + { + pWinPointAry = aStackAry1; + pWinFlagAry = aStackAry2; + } + + sal_uInt32 nPoints_i32(nPoints); + ImplPreparePolyDraw(true, 1, &nPoints_i32, &pPtAry, &pFlgAry, pWinPointAry, pWinFlagAry); + + bool bRet( false ); + + if( BeginPath( mrParent.getHDC() ) ) + { + PolyDraw(mrParent.getHDC(), pWinPointAry, pWinFlagAry, nPoints); + + if( EndPath( mrParent.getHDC() ) ) + { + if( StrokeAndFillPath( mrParent.getHDC() ) ) + bRet = true; + } + } + + if( pWinPointAry != aStackAry1 ) + { + delete [] pWinPointAry; + delete [] pWinFlagAry; + } + + return bRet; +} + +bool WinSalGraphicsImpl::drawPolyPolygonBezier( sal_uInt32 nPoly, const sal_uInt32* pPoints, + const Point* const* pPtAry, const PolyFlags* const* pFlgAry ) +{ + sal_uLong nCurrPoly, nTotalPoints; + const sal_uInt32* pCurrPoints = pPoints; + for( nCurrPoly=0, nTotalPoints=0; nCurrPoly<nPoly; ++nCurrPoly ) + nTotalPoints += *pCurrPoints++; + + POINT aStackAry1[SAL_POLY_STACKBUF]; + BYTE aStackAry2[SAL_POLY_STACKBUF]; + POINT* pWinPointAry; + BYTE* pWinFlagAry; + if( nTotalPoints > SAL_POLY_STACKBUF ) + { + pWinPointAry = new POINT[ nTotalPoints ]; + pWinFlagAry = new BYTE[ nTotalPoints ]; + } + else + { + pWinPointAry = aStackAry1; + pWinFlagAry = aStackAry2; + } + + ImplPreparePolyDraw(true, nPoly, pPoints, pPtAry, pFlgAry, pWinPointAry, pWinFlagAry); + + bool bRet( false ); + + if( BeginPath( mrParent.getHDC() ) ) + { + PolyDraw(mrParent.getHDC(), pWinPointAry, pWinFlagAry, nTotalPoints); + + if( EndPath( mrParent.getHDC() ) ) + { + if( StrokeAndFillPath( mrParent.getHDC() ) ) + bRet = true; + } + } + + if( pWinPointAry != aStackAry1 ) + { + delete [] pWinPointAry; + delete [] pWinFlagAry; + } + + return bRet; +} + +static basegfx::B2DPoint impPixelSnap( + const basegfx::B2DPolygon& rPolygon, + const basegfx::B2DHomMatrix& rObjectToDevice, + basegfx::B2DHomMatrix& rObjectToDeviceInv, + sal_uInt32 nIndex) +{ + const sal_uInt32 nCount(rPolygon.count()); + + // get the data + const basegfx::B2ITuple aPrevTuple(basegfx::fround(rObjectToDevice * rPolygon.getB2DPoint((nIndex + nCount - 1) % nCount))); + const basegfx::B2DPoint aCurrPoint(rObjectToDevice * rPolygon.getB2DPoint(nIndex)); + const basegfx::B2ITuple aCurrTuple(basegfx::fround(aCurrPoint)); + const basegfx::B2ITuple aNextTuple(basegfx::fround(rObjectToDevice * rPolygon.getB2DPoint((nIndex + 1) % nCount))); + + // get the states + const bool bPrevVertical(aPrevTuple.getX() == aCurrTuple.getX()); + const bool bNextVertical(aNextTuple.getX() == aCurrTuple.getX()); + const bool bPrevHorizontal(aPrevTuple.getY() == aCurrTuple.getY()); + const bool bNextHorizontal(aNextTuple.getY() == aCurrTuple.getY()); + const bool bSnapX(bPrevVertical || bNextVertical); + const bool bSnapY(bPrevHorizontal || bNextHorizontal); + + if(bSnapX || bSnapY) + { + basegfx::B2DPoint aSnappedPoint( + bSnapX ? aCurrTuple.getX() : aCurrPoint.getX(), + bSnapY ? aCurrTuple.getY() : aCurrPoint.getY()); + + if(rObjectToDeviceInv.isIdentity()) + { + rObjectToDeviceInv = rObjectToDevice; + rObjectToDeviceInv.invert(); + } + + aSnappedPoint *= rObjectToDeviceInv; + + return aSnappedPoint; + } + + return rPolygon.getB2DPoint(nIndex); +} + +static void impAddB2DPolygonToGDIPlusGraphicsPathReal( + Gdiplus::GraphicsPath& rGraphicsPath, + const basegfx::B2DPolygon& rPolygon, + const basegfx::B2DHomMatrix& rObjectToDevice, + bool bNoLineJoin, + bool bPixelSnapHairline) +{ + sal_uInt32 nCount(rPolygon.count()); + + if(nCount) + { + const sal_uInt32 nEdgeCount(rPolygon.isClosed() ? nCount : nCount - 1); + + if(nEdgeCount) + { + const bool bControls(rPolygon.areControlPointsUsed()); + basegfx::B2DPoint aCurr(rPolygon.getB2DPoint(0)); + basegfx::B2DHomMatrix aObjectToDeviceInv; + + if(bPixelSnapHairline) + { + aCurr = impPixelSnap(rPolygon, rObjectToDevice, aObjectToDeviceInv, 0); + } + + for(sal_uInt32 a(0); a < nEdgeCount; a++) + { + const sal_uInt32 nNextIndex((a + 1) % nCount); + basegfx::B2DPoint aNext(rPolygon.getB2DPoint(nNextIndex)); + const bool b1stControlPointUsed(bControls && rPolygon.isNextControlPointUsed(a)); + const bool b2ndControlPointUsed(bControls && rPolygon.isPrevControlPointUsed(nNextIndex)); + + if(bPixelSnapHairline) + { + aNext = impPixelSnap(rPolygon, rObjectToDevice, aObjectToDeviceInv, nNextIndex); + } + + if(b1stControlPointUsed || b2ndControlPointUsed) + { + basegfx::B2DPoint aCa(rPolygon.getNextControlPoint(a)); + basegfx::B2DPoint aCb(rPolygon.getPrevControlPoint(nNextIndex)); + + // tdf#99165 MS Gdiplus cannot handle creating correct extra geometry for fat lines + // with LineCap or LineJoin when a bezier segment starts or ends trivial, e.g. has + // no 1st or 2nd control point, despite that these are mathematically correct definitions + // (basegfx can handle that). + // Caution: This error (and it's correction) might be necessary for other graphical + // sub-systems in a similar way. + // tdf#101026 The 1st attempt to create a mathematically correct replacement control + // vector was wrong. Best alternative is one as close as possible which means short. + if(!b1stControlPointUsed) + { + aCa = aCurr + ((aCb - aCurr) * 0.0005); + } + else if(!b2ndControlPointUsed) + { + aCb = aNext + ((aCa - aNext) * 0.0005); + } + + rGraphicsPath.AddBezier( + static_cast< Gdiplus::REAL >(aCurr.getX()), static_cast< Gdiplus::REAL >(aCurr.getY()), + static_cast< Gdiplus::REAL >(aCa.getX()), static_cast< Gdiplus::REAL >(aCa.getY()), + static_cast< Gdiplus::REAL >(aCb.getX()), static_cast< Gdiplus::REAL >(aCb.getY()), + static_cast< Gdiplus::REAL >(aNext.getX()), static_cast< Gdiplus::REAL >(aNext.getY())); + } + else + { + rGraphicsPath.AddLine( + static_cast< Gdiplus::REAL >(aCurr.getX()), static_cast< Gdiplus::REAL >(aCurr.getY()), + static_cast< Gdiplus::REAL >(aNext.getX()), static_cast< Gdiplus::REAL >(aNext.getY())); + } + + if(a + 1 < nEdgeCount) + { + aCurr = aNext; + + if(bNoLineJoin) + { + rGraphicsPath.StartFigure(); + } + } + } + } + } +} + +namespace { + +class SystemDependentData_GraphicsPath : public basegfx::SystemDependentData +{ +private: + // the path data itself + std::shared_ptr<Gdiplus::GraphicsPath> mpGraphicsPath; + + // all other values the triangulation is based on and + // need to be compared with to check for data validity + bool mbNoLineJoin; + std::vector< double > maStroke; + +public: + SystemDependentData_GraphicsPath( + basegfx::SystemDependentDataManager& rSystemDependentDataManager, + std::shared_ptr<Gdiplus::GraphicsPath>& rpGraphicsPath, + bool bNoLineJoin, + const std::vector< double >* pStroke); // MM01 + + // read access + std::shared_ptr<Gdiplus::GraphicsPath>& getGraphicsPath() { return mpGraphicsPath; } + bool getNoLineJoin() const { return mbNoLineJoin; } + const std::vector< double >& getStroke() const { return maStroke; } + + virtual sal_Int64 estimateUsageInBytes() const override; +}; + +} + +SystemDependentData_GraphicsPath::SystemDependentData_GraphicsPath( + basegfx::SystemDependentDataManager& rSystemDependentDataManager, + std::shared_ptr<Gdiplus::GraphicsPath>& rpGraphicsPath, + bool bNoLineJoin, + const std::vector< double >* pStroke) +: basegfx::SystemDependentData(rSystemDependentDataManager), + mpGraphicsPath(rpGraphicsPath), + mbNoLineJoin(bNoLineJoin), + maStroke() +{ + if(nullptr != pStroke) + { + maStroke = *pStroke; + } +} + +sal_Int64 SystemDependentData_GraphicsPath::estimateUsageInBytes() const +{ + sal_Int64 nRetval(0); + + if(mpGraphicsPath) + { + const INT nPointCount(mpGraphicsPath->GetPointCount()); + + if(0 != nPointCount) + { + // Each point has + // - 2 x sizeof(Gdiplus::REAL) + // - 1 byte (see GetPathTypes in docu) + nRetval = nPointCount * ((2 * sizeof(Gdiplus::REAL)) + 1); + } + } + + return nRetval; +} + +bool WinSalGraphicsImpl::drawPolyPolygon( + const basegfx::B2DHomMatrix& rObjectToDevice, + const basegfx::B2DPolyPolygon& rPolyPolygon, + double fTransparency) +{ + const sal_uInt32 nCount(rPolyPolygon.count()); + + if(!mbBrush || 0 == nCount || fTransparency < 0.0 || fTransparency > 1.0) + { + return true; + } + + Gdiplus::Graphics aGraphics(mrParent.getHDC()); + const sal_uInt8 aTrans(sal_uInt8(255) - static_cast<sal_uInt8>(basegfx::fround(fTransparency * 255.0))); + const Gdiplus::Color aTestColor(aTrans, maFillColor.GetRed(), maFillColor.GetGreen(), maFillColor.GetBlue()); + const Gdiplus::SolidBrush aSolidBrush(aTestColor.GetValue()); + + // Set full (Object-to-Device) transformation - if used + if(rObjectToDevice.isIdentity()) + { + aGraphics.ResetTransform(); + } + else + { + Gdiplus::Matrix aMatrix; + + aMatrix.SetElements( + rObjectToDevice.get(0, 0), + rObjectToDevice.get(1, 0), + rObjectToDevice.get(0, 1), + rObjectToDevice.get(1, 1), + rObjectToDevice.get(0, 2), + rObjectToDevice.get(1, 2)); + aGraphics.SetTransform(&aMatrix); + } + + // prepare local instance of Gdiplus::GraphicsPath + std::shared_ptr<Gdiplus::GraphicsPath> pGraphicsPath; + + // try to access buffered data + std::shared_ptr<SystemDependentData_GraphicsPath> pSystemDependentData_GraphicsPath( + rPolyPolygon.getSystemDependentData<SystemDependentData_GraphicsPath>()); + + if(pSystemDependentData_GraphicsPath) + { + // copy buffered data + pGraphicsPath = pSystemDependentData_GraphicsPath->getGraphicsPath(); + } + else + { + // Note: In principle we could use the same buffered geometry at line + // and fill polygons. Checked that in a first try, used + // GraphicsPath::AddPath from Gdiplus combined with below used + // StartFigure/CloseFigure, worked well (thus the line-draw version + // may create non-closed partial Polygon data). + // + // But in current reality it gets not used due to e.g. + // SdrPathPrimitive2D::create2DDecomposition creating transformed + // line and fill polygon-primitives (what could be changed). + // + // There will probably be more hindrances here in other rendering paths + // which could all be found - intention to do this would be: Use more + // transformations, less modifications of B2DPolygons/B2DPolyPolygons. + // + // A fix for SdrPathPrimitive2D would be to create the sub-geometry + // and embed into a TransformPrimitive2D containing the transformation. + // + // A 2nd problem is that the NoLineJoin mode (basegfx::B2DLineJoin::NONE + // && !bIsHairline) creates polygon fill infos that are not reusable + // for the fill case (see ::drawPolyLine below) - thus we would need a + // bool and/or two system-dependent paths buffered - doable, but complicated. + // + // All in all: Make B2DPolyPolygon a SystemDependentDataProvider and buffer + // the whole to-be-filled PolyPolygon independent from evtl. line-polygon + // (at least for now...) + + // create data + pGraphicsPath = std::make_shared<Gdiplus::GraphicsPath>(); + + for(sal_uInt32 a(0); a < nCount; a++) + { + if(0 != a) + { + // #i101491# not needed for first run + pGraphicsPath->StartFigure(); + } + + impAddB2DPolygonToGDIPlusGraphicsPathReal( + *pGraphicsPath, + rPolyPolygon.getB2DPolygon(a), + rObjectToDevice, // not used due to the two 'false' values below, but to not forget later + false, + false); + + pGraphicsPath->CloseFigure(); + } + + // add to buffering mechanism + rPolyPolygon.addOrReplaceSystemDependentData<SystemDependentData_GraphicsPath>( + ImplGetSystemDependentDataManager(), + pGraphicsPath, + false, + nullptr); + } + + if(mrParent.getAntiAlias()) + { + aGraphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias); + } + else + { + aGraphics.SetSmoothingMode(Gdiplus::SmoothingModeNone); + } + + if(mrParent.isPrinter()) + { + // #i121591# + // Normally GdiPlus should not be used for printing at all since printers cannot + // print transparent filled polygon geometry and normally this does not happen + // since OutputDevice::RemoveTransparenciesFromMetaFile is used as preparation + // and no transparent parts should remain for printing. But this can be overridden + // by the user and thus happens. This call can only come (currently) from + // OutputDevice::DrawTransparent, see comments there with the same TaskID. + // If it is used, the mapping for the printer is wrong and needs to be corrected. I + // checked that there is *no* transformation set and estimated that a stable factor + // dependent of the printer's DPI is used. Create and set a transformation here to + // correct this. + const Gdiplus::REAL aDpiX(aGraphics.GetDpiX()); + const Gdiplus::REAL aDpiY(aGraphics.GetDpiY()); + + // Now the transformation maybe/is already used (see above), so do + // modify it without resetting to not destroy it. + // I double-checked with MS docu that Gdiplus::MatrixOrderAppend does what + // we need - in our notation, would be a multiply from left to execute + // current transform first and this scale last. + // I tried to trigger this code using Print from the menu and various + // targets, but got no hit, thus maybe obsolete anyways. If someone knows + // more, feel free to remove it. + // One more hint: This *may* also be needed now in ::drawPolyLine below + // since it also uses transformations now. + // + // aGraphics.ResetTransform(); + + aGraphics.ScaleTransform( + Gdiplus::REAL(100.0) / aDpiX, + Gdiplus::REAL(100.0) / aDpiY, + Gdiplus::MatrixOrderAppend); + } + + // use created or buffered data + aGraphics.FillPath( + &aSolidBrush, + &(*pGraphicsPath)); + + return true; +} + +bool WinSalGraphicsImpl::drawPolyLine( + const basegfx::B2DHomMatrix& rObjectToDevice, + const basegfx::B2DPolygon& rPolygon, + double fTransparency, + double fLineWidth, + const std::vector< double >* pStroke, // MM01 + basegfx::B2DLineJoin eLineJoin, + css::drawing::LineCap eLineCap, + double fMiterMinimumAngle, + bool bPixelSnapHairline) +{ + // MM01 check done for simple reasons + if(!mbPen || !rPolygon.count() || fTransparency < 0.0 || fTransparency > 1.0) + { + return true; + } + + // need to check/handle LineWidth when ObjectToDevice transformation is used + const bool bObjectToDeviceIsIdentity(rObjectToDevice.isIdentity()); + const bool bIsHairline(fLineWidth == 0); + + // tdf#124848 calculate-back logical LineWidth for a hairline + // since this implementation hands over the transformation to + // the graphic sub-system + if(bIsHairline) + { + fLineWidth = 1.0; + + if(!bObjectToDeviceIsIdentity) + { + basegfx::B2DHomMatrix aObjectToDeviceInv(rObjectToDevice); + aObjectToDeviceInv.invert(); + fLineWidth = (aObjectToDeviceInv * basegfx::B2DVector(fLineWidth, 0)).getLength(); + } + } + + Gdiplus::Graphics aGraphics(mrParent.getHDC()); + const sal_uInt8 aTrans = static_cast<sal_uInt8>(basegfx::fround( 255 * (1.0 - fTransparency) )); + const Gdiplus::Color aTestColor(aTrans, maLineColor.GetRed(), maLineColor.GetGreen(), maLineColor.GetBlue()); + Gdiplus::Pen aPen(aTestColor.GetValue(), Gdiplus::REAL(fLineWidth)); + bool bNoLineJoin(false); + + // Set full (Object-to-Device) transformation - if used + if(bObjectToDeviceIsIdentity) + { + aGraphics.ResetTransform(); + } + else + { + Gdiplus::Matrix aMatrix; + + aMatrix.SetElements( + rObjectToDevice.get(0, 0), + rObjectToDevice.get(1, 0), + rObjectToDevice.get(0, 1), + rObjectToDevice.get(1, 1), + rObjectToDevice.get(0, 2), + rObjectToDevice.get(1, 2)); + aGraphics.SetTransform(&aMatrix); + } + + switch(eLineJoin) + { + case basegfx::B2DLineJoin::NONE : + { + if(!bIsHairline) + { + bNoLineJoin = true; + } + break; + } + case basegfx::B2DLineJoin::Bevel : + { + aPen.SetLineJoin(Gdiplus::LineJoinBevel); + break; + } + case basegfx::B2DLineJoin::Miter : + { + const Gdiplus::REAL aMiterLimit(1.0/sin(fMiterMinimumAngle/2.0)); + + aPen.SetMiterLimit(aMiterLimit); + // tdf#99165 MS's LineJoinMiter creates non standard conform miter additional + // graphics, somewhere clipped in some distance from the edge point, dependent + // of MiterLimit. The more default-like option is LineJoinMiterClipped, so use + // that instead + aPen.SetLineJoin(Gdiplus::LineJoinMiterClipped); + break; + } + case basegfx::B2DLineJoin::Round : + { + aPen.SetLineJoin(Gdiplus::LineJoinRound); + break; + } + } + + switch(eLineCap) + { + default: /*css::drawing::LineCap_BUTT*/ + { + // nothing to do + break; + } + case css::drawing::LineCap_ROUND: + { + aPen.SetStartCap(Gdiplus::LineCapRound); + aPen.SetEndCap(Gdiplus::LineCapRound); + break; + } + case css::drawing::LineCap_SQUARE: + { + aPen.SetStartCap(Gdiplus::LineCapSquare); + aPen.SetEndCap(Gdiplus::LineCapSquare); + break; + } + } + + // prepare local instance of Gdiplus::GraphicsPath + std::shared_ptr<Gdiplus::GraphicsPath> pGraphicsPath; + + // try to access buffered data + std::shared_ptr<SystemDependentData_GraphicsPath> pSystemDependentData_GraphicsPath( + rPolygon.getSystemDependentData<SystemDependentData_GraphicsPath>()); + + // MM01 need to do line dashing as fallback stuff here now + const double fDotDashLength(nullptr != pStroke ? std::accumulate(pStroke->begin(), pStroke->end(), 0.0) : 0.0); + const bool bStrokeUsed(0.0 != fDotDashLength); + assert(!bStrokeUsed || (bStrokeUsed && pStroke)); + + // MM01 decide if to stroke directly + static bool bDoDirectGDIPlusStroke(true); + + // activate to stroke directly + if(bDoDirectGDIPlusStroke && bStrokeUsed) + { + // tdf#124848 the fix of tdf#130478 that was needed here before + // gets much easier when already handling the hairline case above, + // the back-calculated logical linewidth is already here, just use it. + // Still be careful - a zero LineWidth *should* not happen, but... + std::vector<Gdiplus::REAL> aDashArray(pStroke->size()); + const double fFactor(fLineWidth == 0 ? 1.0 : 1.0 / fLineWidth); + + // tdf#134128. ODF adds caps to the dashes and dots, but GDI makes caps from the + // dash or dot 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>( + ImplGetSystemDependentDataManager(), + pGraphicsPath, + bNoLineJoin, + pStroke); + } + } + + if(mrParent.getAntiAlias()) + { + aGraphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias); + } + else + { + aGraphics.SetSmoothingMode(Gdiplus::SmoothingModeNone); + } + + if(mrParent.isPrinter()) + { + // tdf#122384 As mentioned above in WinSalGraphicsImpl::drawPolyPolygon + // (look for 'One more hint: This *may* also be needed now in'...). + // See comments in same spot above *urgently* before doing changes here, + // these comments are *still fully valid* at this place (!) + const Gdiplus::REAL aDpiX(aGraphics.GetDpiX()); + const Gdiplus::REAL aDpiY(aGraphics.GetDpiY()); + + aGraphics.ScaleTransform( + Gdiplus::REAL(100.0) / aDpiX, + Gdiplus::REAL(100.0) / aDpiY, + Gdiplus::MatrixOrderAppend); + } + + aGraphics.DrawPath( + &aPen, + &(*pGraphicsPath)); + + return true; +} + +static void paintToGdiPlus( + Gdiplus::Graphics& rGraphics, + const SalTwoRect& rTR, + Gdiplus::Bitmap& rBitmap) +{ + // only parts of source are used + Gdiplus::PointF aDestPoints[3]; + Gdiplus::ImageAttributes aAttributes; + + // define target region as parallelogram + aDestPoints[0].X = Gdiplus::REAL(rTR.mnDestX); + aDestPoints[0].Y = Gdiplus::REAL(rTR.mnDestY); + aDestPoints[1].X = Gdiplus::REAL(rTR.mnDestX + rTR.mnDestWidth); + aDestPoints[1].Y = Gdiplus::REAL(rTR.mnDestY); + aDestPoints[2].X = Gdiplus::REAL(rTR.mnDestX); + aDestPoints[2].Y = Gdiplus::REAL(rTR.mnDestY + rTR.mnDestHeight); + + aAttributes.SetWrapMode(Gdiplus::WrapModeTileFlipXY); + + rGraphics.DrawImage( + &rBitmap, + aDestPoints, + 3, + Gdiplus::REAL(rTR.mnSrcX), + Gdiplus::REAL(rTR.mnSrcY), + Gdiplus::REAL(rTR.mnSrcWidth), + Gdiplus::REAL(rTR.mnSrcHeight), + Gdiplus::UnitPixel, + &aAttributes); +} + +static void setInterpolationMode( + Gdiplus::Graphics& rGraphics, + tools::Long rSrcWidth, + tools::Long rDestWidth, + tools::Long rSrcHeight, + tools::Long rDestHeight) +{ + const bool bSameWidth(rSrcWidth == rDestWidth); + const bool bSameHeight(rSrcHeight == rDestHeight); + + if(bSameWidth && bSameHeight) + { + rGraphics.SetInterpolationMode(Gdiplus::InterpolationModeInvalid); + } + else if(rDestWidth > rSrcWidth && rDestHeight > rSrcHeight) + { + rGraphics.SetInterpolationMode(Gdiplus::InterpolationModeDefault); + } + else if(rDestWidth < rSrcWidth && rDestHeight < rSrcHeight) + { + rGraphics.SetInterpolationMode(Gdiplus::InterpolationModeBicubic); + } + else + { + rGraphics.SetInterpolationMode(Gdiplus::InterpolationModeDefault); + } +} + +bool WinSalGraphicsImpl::TryDrawBitmapGDIPlus(const SalTwoRect& rTR, const SalBitmap& rSrcBitmap) +{ + if(rTR.mnSrcWidth && rTR.mnSrcHeight && rTR.mnDestWidth && rTR.mnDestHeight) + { + assert(dynamic_cast<const WinSalBitmap*>(&rSrcBitmap)); + + const WinSalBitmap& rSalBitmap = static_cast< const WinSalBitmap& >(rSrcBitmap); + std::shared_ptr< Gdiplus::Bitmap > aARGB(rSalBitmap.ImplGetGdiPlusBitmap()); + + if(aARGB) + { + Gdiplus::Graphics aGraphics(mrParent.getHDC()); + + setInterpolationMode( + aGraphics, + rTR.mnSrcWidth, + rTR.mnDestWidth, + rTR.mnSrcHeight, + rTR.mnDestHeight); + + paintToGdiPlus( + aGraphics, + rTR, + *aARGB); + + return true; + } + } + + return false; +} + +bool WinSalGraphicsImpl::blendBitmap( + const SalTwoRect&, + const SalBitmap&) +{ + return false; +} + +bool WinSalGraphicsImpl::blendAlphaBitmap( + const SalTwoRect&, + const SalBitmap&, + const SalBitmap&, + const SalBitmap&) +{ + return false; +} + +bool WinSalGraphicsImpl::drawAlphaBitmap( + const SalTwoRect& rTR, + const SalBitmap& rSrcBitmap, + const SalBitmap& rAlphaBmp) +{ + if(rTR.mnSrcWidth && rTR.mnSrcHeight && rTR.mnDestWidth && rTR.mnDestHeight) + { + assert(dynamic_cast<const WinSalBitmap*>(&rSrcBitmap)); + assert(dynamic_cast<const WinSalBitmap*>(&rAlphaBmp)); + + const WinSalBitmap& rSalBitmap = static_cast< const WinSalBitmap& >(rSrcBitmap); + const WinSalBitmap& rSalAlpha = static_cast< const WinSalBitmap& >(rAlphaBmp); + std::shared_ptr< Gdiplus::Bitmap > aARGB(rSalBitmap.ImplGetGdiPlusBitmap(&rSalAlpha)); + + if(aARGB) + { + Gdiplus::Graphics aGraphics(mrParent.getHDC()); + + setInterpolationMode( + aGraphics, + rTR.mnSrcWidth, + rTR.mnDestWidth, + rTR.mnSrcHeight, + rTR.mnDestHeight); + + paintToGdiPlus( + aGraphics, + rTR, + *aARGB); + + return true; + } + } + + return false; +} + +bool WinSalGraphicsImpl::drawTransformedBitmap( + const basegfx::B2DPoint& rNull, + const basegfx::B2DPoint& rX, + const basegfx::B2DPoint& rY, + const SalBitmap& rSourceBitmap, + const SalBitmap* pAlphaBitmap, + 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_cast< const WinSalBitmap& >(rSourceBitmap); + const WinSalBitmap* pSalAlpha = static_cast< const WinSalBitmap* >(pAlphaBitmap); + std::shared_ptr< Gdiplus::Bitmap > aARGB(rSalBitmap.ImplGetGdiPlusBitmap(pSalAlpha)); + + if(aARGB) + { + const tools::Long nSrcWidth(aARGB->GetWidth()); + const tools::Long nSrcHeight(aARGB->GetHeight()); + + if(nSrcWidth && nSrcHeight) + { + const tools::Long nDestWidth(basegfx::fround(basegfx::B2DVector(rX - rNull).getLength())); + const tools::Long nDestHeight(basegfx::fround(basegfx::B2DVector(rY - rNull).getLength())); + + if(nDestWidth && nDestHeight) + { + Gdiplus::Graphics aGraphics(mrParent.getHDC()); + Gdiplus::PointF aDestPoints[3]; + Gdiplus::ImageAttributes aAttributes; + + setInterpolationMode( + aGraphics, + nSrcWidth, + nDestWidth, + nSrcHeight, + nDestHeight); + + // this mode is only capable of drawing the whole bitmap to a parallelogram + aDestPoints[0].X = Gdiplus::REAL(rNull.getX()); + aDestPoints[0].Y = Gdiplus::REAL(rNull.getY()); + aDestPoints[1].X = Gdiplus::REAL(rX.getX()); + aDestPoints[1].Y = Gdiplus::REAL(rX.getY()); + aDestPoints[2].X = Gdiplus::REAL(rY.getX()); + aDestPoints[2].Y = Gdiplus::REAL(rY.getY()); + + aAttributes.SetWrapMode(Gdiplus::WrapModeTileFlipXY); + + aGraphics.DrawImage( + aARGB.get(), + aDestPoints, + 3, + Gdiplus::REAL(0.0), + Gdiplus::REAL(0.0), + Gdiplus::REAL(nSrcWidth), + Gdiplus::REAL(nSrcHeight), + Gdiplus::UnitPixel, + &aAttributes); + } + } + + return true; + } + + return false; +} + +bool WinSalGraphicsImpl::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 +{ + static bool bAllowForTest(true); + bool bRet = false; + + switch (eType) + { + case OutDevSupportType::TransparentRect: + bRet = mrParent.mbVirDev || mrParent.mbWindow; + break; + case OutDevSupportType::B2DDraw: + bRet = bAllowForTest; + break; + default: + break; + } + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |