/* -*- 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 #include #include #include #include #include "gdiimpl.hxx" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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(pCurrPoint->getX()), static_cast(pCurrPoint->getY()) }; pCurrPoint++; *pWinFlagAry++ = PT_MOVETO; ++pCurrFlag; for( nCurrPoint=1; nCurrPoint(pCurrPoint->getX()), static_cast(pCurrPoint->getY()) }; pCurrPoint++; *pWinFlagAry++ = PT_BEZIERTO; // control point two *pWinPointAry++ = POINT { static_cast(pCurrPoint->getX()), static_cast(pCurrPoint->getY()) }; pCurrPoint++; *pWinFlagAry++ = PT_BEZIERTO; // end point *pWinPointAry++ = POINT { static_cast(pCurrPoint->getX()), static_cast(pCurrPoint->getY()) }; pCurrPoint++; *pWinFlagAry++ = PT_BEZIERTO; nCurrPoint += 3; pCurrFlag += 3; continue; } } // regular line point *pWinPointAry++ = POINT { static_cast(pCurrPoint->getX()), static_cast(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(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(rPosAry.mnDestX), static_cast(rPosAry.mnDestY), static_cast(rPosAry.mnDestWidth), static_cast(rPosAry.mnDestHeight), hSrcDC, static_cast(rPosAry.mnSrcX), static_cast(rPosAry.mnSrcY), nRop ); } else { int nOldStretchMode = SetStretchBltMode( mrParent.getHDC(), STRETCH_DELETESCANS ); StretchBlt( mrParent.getHDC(), static_cast(rPosAry.mnDestX), static_cast(rPosAry.mnDestY), static_cast(rPosAry.mnDestWidth), static_cast(rPosAry.mnDestHeight), hSrcDC, static_cast(rPosAry.mnSrcX), static_cast(rPosAry.mnSrcY), static_cast(rPosAry.mnSrcWidth), static_cast(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(nSrcX); aSrcRect.top = static_cast(nSrcY); aSrcRect.right = aSrcRect.left+static_cast(nSrcWidth); aSrcRect.bottom = aSrcRect.top+static_cast(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(nDestX-nSrcX); int nOffY = static_cast(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(nDestX), static_cast(nDestY), static_cast(nSrcWidth), static_cast(nSrcHeight), mrParent.getHDC(), static_cast(nSrcX), static_cast(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 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(GlobalLock( hDrawDIB )); PBYTE pBits = reinterpret_cast(pBI) + pBI->bmiHeader.biSize + WinSalBitmap::ImplGetDIBColorCount( hDrawDIB ) * sizeof( RGBQUAD ); const int nOldStretchMode = SetStretchBltMode( hDC, STRETCH_DELETESCANS ); StretchDIBits( hDC, static_cast(rPosAry.mnDestX), static_cast(rPosAry.mnDestY), static_cast(rPosAry.mnDestWidth), static_cast(rPosAry.mnDestHeight), static_cast(rPosAry.mnSrcX), static_cast(pBI->bmiHeader.biHeight - rPosAry.mnSrcHeight - rPosAry.mnSrcY), static_cast(rPosAry.mnSrcWidth), static_cast(rPosAry.mnSrcHeight), pBits, pBI, DIB_RGB_COLORS, nDrawMode ); GlobalUnlock( hDrawDIB ); SetStretchBltMode( hDC, nOldStretchMode ); } else if( hDrawDDB && !bPrintDDB ) { ScopedCachedHDC 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(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(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(rPosAry.mnDestX), static_cast(rPosAry.mnDestY), static_cast(rPosAry.mnDestWidth), static_cast(rPosAry.mnDestHeight), hBmpDC.get(), static_cast(rPosAry.mnSrcX), static_cast(rPosAry.mnSrcY), nDrawMode ); } else { const int nOldStretchMode = SetStretchBltMode( hDC, STRETCH_DELETESCANS ); StretchBlt( hDC, static_cast(rPosAry.mnDestX), static_cast(rPosAry.mnDestY), static_cast(rPosAry.mnDestWidth), static_cast(rPosAry.mnDestHeight), hBmpDC.get(), static_cast(rPosAry.mnSrcX), static_cast(rPosAry.mnSrcY), static_cast(rPosAry.mnSrcWidth), static_cast(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(&rSalBitmap)); ImplDrawBitmap(mrParent.getHDC(), rPosAry, static_cast(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(&rSSalBitmap)); assert(dynamic_cast(&rSTransparentBitmap)); const WinSalBitmap& rSalBitmap = static_cast(rSSalBitmap); const WinSalBitmap& rTransparentBitmap = static_cast(rSTransparentBitmap); SalTwoRect aPosAry = rPosAry; int nDstX = static_cast(aPosAry.mnDestX); int nDstY = static_cast(aPosAry.mnDestY); int nDstWidth = static_cast(aPosAry.mnDestWidth); int nDstHeight = static_cast(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 hMemDC(hMemBitmap.get()); ScopedCachedHDC 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 hMemDC(nullptr); SetPixel( hMemDC.get(), int(0), int(0), mnBrushColor ); BLENDFUNCTION aFunc = { AC_SRC_OVER, 0, sal::static_int_cast(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(&rSSalBitmap)); const WinSalBitmap& rSalBitmap = static_cast(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 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 pSalBitmap; nDX = std::abs( nDX ); nDY = std::abs( nDY ); HDC hDC = mrParent.getHDC(); HBITMAP hBmpBitmap = CreateCompatibleBitmap( hDC, nDX, nDY ); bool bRet; { ScopedCachedHDC hBmpDC(hBmpBitmap); bRet = BitBlt(hBmpDC.get(), 0, 0, static_cast(nDX), static_cast(nDY), hDC, static_cast(nX), static_cast(nY), SRCCOPY) ? TRUE : FALSE; } if( bRet ) { pSalBitmap = std::make_shared(); 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(nX), static_cast(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(nX), static_cast(nY), static_cast(nX+nWidth), static_cast(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(nX); aRect.top = static_cast(nY); aRect.right = static_cast(nX)+nWidth; aRect.bottom = static_cast(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 pWinPtAry(new POINT[nPoints]); for (sal_uInt32 i=0; i(pPtAry[i].getX()), static_cast(pPtAry[i].getY()) }; // for Windows 95 and its maximum number of points if ( nSalFlags & SalInvert::TrackFrame ) { if ( !Polyline( mrParent.getHDC(), pWinPtAry.get(), static_cast(nPoints) ) && (nPoints > MAX_64KSALPOINTS) ) Polyline( mrParent.getHDC(), pWinPtAry.get(), MAX_64KSALPOINTS ); } else { if ( !Polygon( mrParent.getHDC(), pWinPtAry.get(), static_cast(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(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.width()) return pFrame->maGeometry.width(); 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; } 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(new BYTE[sizeof(RGNDATA)-1+(SAL_CLIPRECT_COUNT*sizeof(RECT))]); mrParent.mpClipRgnData = mrParent.mpStdClipRgnData; } else mrParent.mpClipRgnData = reinterpret_cast(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(&(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(rectangle.Left()); } if(rectangle.Top() < pBoundRect->top) { pBoundRect->top = static_cast(rectangle.Top()); } if(nRight > pBoundRect->right) { pBoundRect->right = static_cast(nRight); } if(nBottom > pBoundRect->bottom) { pBoundRect->bottom = static_cast(nBottom); } } pNextClipRect->left = static_cast(rectangle.Left()); pNextClipRect->top = static_cast(rectangle.Top()); pNextClipRect->right = static_cast(nRight); pNextClipRect->bottom = static_cast(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(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(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 ); } } 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(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(nX), static_cast(nY), crColor); return; } ScopedSelectedHBRUSH hBrush(hDC, CreateSolidBrush(crColor)); PatBlt(hDC, static_cast(nX), static_cast(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(nX1), static_cast(nY1), nullptr ); LineTo( mrParent.getHDC(), static_cast(nX2), static_cast(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(nX), static_cast(nY), static_cast(nWidth), static_cast(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(nX), static_cast(nY), static_cast(nX+nWidth), static_cast(nY+nHeight) ); } void WinSalGraphicsImpl::drawPolyLine( sal_uInt32 nPoints, const Point* pPtAry ) { std::unique_ptr pWinPtAry(new POINT[nPoints]); for (sal_uInt32 i=0; i(pPtAry[i].getX()), static_cast(pPtAry[i].getY()) }; // for Windows 95 and its maximum number of points if ( !Polyline( mrParent.getHDC(), pWinPtAry.get(), static_cast(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 pWinPtAry(new POINT[nPoints]); for (sal_uInt32 i=0; i(pPtAry[i].getX()), static_cast(pPtAry[i].getY()) }; // for Windows 95 and its maximum number of points if ( !Polygon( mrParent.getHDC(), pWinPtAry.get(), static_cast(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(nPoly); i++ ) { nPoints = static_cast(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(nPoly); i++ ) { nPoints = pWinPointAry[i]; const Point* pPolyAry = pPtAry[i]; for (sal_uInt32 j=0; j(pPolyAry[j].getX()), static_cast(pPolyAry[j].getY()) }; pWinPointAryAry[n+nPoints-1] = pWinPointAryAry[n]; n += nPoints; } if ( !PolyPolygon( mrParent.getHDC(), pWinPointAryAry, reinterpret_cast(pWinPointAry), static_cast(nPoly) ) && (nPolyPolyPoints > MAX_64KSALPOINTS) ) { nPolyPolyPoints = 0; nPoly = 0; do { nPolyPolyPoints += pWinPointAry[static_cast(nPoly)]; nPoly++; } while ( nPolyPolyPoints < MAX_64KSALPOINTS ); nPoly--; if ( pWinPointAry[static_cast(nPoly)] > MAX_64KSALPOINTS ) pWinPointAry[static_cast(nPoly)] = MAX_64KSALPOINTS; if ( nPoly == 1 ) Polygon( mrParent.getHDC(), pWinPointAryAry, *pWinPointAry ); else PolyPolygon( mrParent.getHDC(), pWinPointAryAry, reinterpret_cast(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(pPtAry->getX()), static_cast(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(pPtAry[0].getX()), static_cast(pPtAry[0].getY()) }, POINT { static_cast(pPtAry[1].getX()), static_cast(pPtAry[1].getY()) }, POINT { static_cast(pPtAry[2].getX()), static_cast(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 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 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& rpGraphicsPath, bool bNoLineJoin, const std::vector< double >* pStroke); // MM01 // read access std::shared_ptr& 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& rpGraphicsPath, bool bNoLineJoin, const std::vector< double >* pStroke) : basegfx::SystemDependentData(Application::GetSystemDependentDataManager()), 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(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 pGraphicsPath; // try to access buffered data std::shared_ptr pSystemDependentData_GraphicsPath( rPolyPolygon.getSystemDependentData()); 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(); 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( 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(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 pGraphicsPath; // try to access buffered data std::shared_ptr pSystemDependentData_GraphicsPath( rPolygon.getSystemDependentData()); // 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 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(); 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( 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(&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(&rSrcBitmap)); assert(dynamic_cast(&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(&rSourceBitmap)); assert(!pAlphaBitmap || dynamic_cast(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: */