/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* This file incorporates work covered by the following license notice:
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright
* ownership. The ASF licenses this file to you under the Apache
* License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
#include <svsys.h>
#include "gdiimpl.hxx"
#include <string.h>
#include <rtl/strbuf.hxx>
#include <sal/log.hxx>
#include <tools/poly.hxx>
#include <basegfx/polygon/b2dpolygon.hxx>
#include <basegfx/polygon/b2dpolygontools.hxx>
#include <basegfx/polygon/b2dpolypolygontools.hxx>
#include <win/wincomp.hxx>
#include <win/saldata.hxx>
#include <win/salgdi.h>
#include <win/salbmp.h>
#include <vcl/salbtype.hxx>
#include <win/salframe.h>
#include <basegfx/matrix/b2dhommatrixtools.hxx>
#include <basegfx/utils/systemdependentdata.hxx>
#include <outdata.hxx>
#include <win/salids.hrc>
#include <ControlCacheKey.hxx>
#if defined _MSC_VER
#ifndef min
#define min(a,b) (((a) < (b)) ? (a) : (b))
#endif
#ifndef max
#define max(a,b) (((a) > (b)) ? (a) : (b))
#endif
#endif
#include <prewin.h>
#include <gdiplus.h>
#include <gdiplusenums.h>
#include <gdipluscolor.h>
#include <postwin.h>
#define SAL_POLYPOLYCOUNT_STACKBUF 8
#define SAL_POLYPOLYPOINTS_STACKBUF 64
#define DITHER_PAL_DELTA 51
#define DITHER_MAX_SYSCOLOR 16
#define DMAP( _def_nVal, _def_nThres ) ((pDitherDiff[_def_nVal]>(_def_nThres))?pDitherHigh[_def_nVal]:pDitherLow[_def_nVal])
#define SAL_POLY_STACKBUF 32
namespace {
// #100127# draw an array of points which might also contain bezier control points
void ImplRenderPath( HDC hdc, sal_uLong nPoints, const SalPoint* pPtAry, const PolyFlags* pFlgAry )
{
if( nPoints )
{
// TODO: profile whether the following options are faster:
// a) look ahead and draw consecutive bezier or line segments by PolyBezierTo/PolyLineTo resp.
// b) convert our flag array to window's and use PolyDraw
MoveToEx( hdc, pPtAry->mnX, pPtAry->mnY, nullptr );
++pPtAry; ++pFlgAry;
for( sal_uLong i=1; i<nPoints; ++i, ++pPtAry, ++pFlgAry )
{
if( *pFlgAry != PolyFlags::Control )
{
LineTo( hdc, pPtAry->mnX, pPtAry->mnY );
}
else if( nPoints - i > 2 )
{
PolyBezierTo( hdc, reinterpret_cast<const POINT*>(pPtAry), 3 );
i += 2; pPtAry += 2; pFlgAry += 2;
}
}
}
}
// #100127# Fill point and flag memory from array of points which
// might also contain bezier control points for the PolyDraw() GDI method
// Make sure pWinPointAry and pWinFlagAry are big enough
void ImplPreparePolyDraw( bool bCloseFigures,
sal_uLong nPoly,
const sal_uInt32* pPoints,
const SalPoint* const* pPtAry,
const PolyFlags* const* pFlgAry,
POINT* pWinPointAry,
BYTE* pWinFlagAry )
{
sal_uLong nCurrPoly;
for( nCurrPoly=0; nCurrPoly<nPoly; ++nCurrPoly )
{
const POINT* pCurrPoint = reinterpret_cast<const POINT*>( *pPtAry++ );
const PolyFlags* pCurrFlag = *pFlgAry++;
const sal_uInt32 nCurrPoints = *pPoints++;
const bool bHaveFlagArray( pCurrFlag );
sal_uLong nCurrPoint;
if( nCurrPoints )
{
// start figure
*pWinPointAry++ = *pCurrPoint++;
*pWinFlagAry++ = PT_MOVETO;
++pCurrFlag;
for( nCurrPoint=1; nCurrPoint<nCurrPoints; )
{
// #102067# Check existence of flag array
if( bHaveFlagArray &&
( nCurrPoint + 2 ) < nCurrPoints )
{
PolyFlags P4( pCurrFlag[ 2 ] );
if( ( PolyFlags::Control == pCurrFlag[ 0 ] ) &&
( PolyFlags::Control == pCurrFlag[ 1 ] ) &&
( PolyFlags::Normal == P4 || PolyFlags::Smooth == P4 || PolyFlags::Symmetric == P4 ) )
{
// control point one
*pWinPointAry++ = *pCurrPoint++;
*pWinFlagAry++ = PT_BEZIERTO;
// control point two
*pWinPointAry++ = *pCurrPoint++;
*pWinFlagAry++ = PT_BEZIERTO;
// end point
*pWinPointAry++ = *pCurrPoint++;
*pWinFlagAry++ = PT_BEZIERTO;
nCurrPoint += 3;
pCurrFlag += 3;
continue;
}
}
// regular line point
*pWinPointAry++ = *pCurrPoint++;
*pWinFlagAry++ = PT_LINETO;
++pCurrFlag;
++nCurrPoint;
}
// end figure?
if( bCloseFigures )
pWinFlagAry[-1] |= PT_CLOSEFIGURE;
}
}
}
static PALETTEENTRY aImplSalSysPalEntryAry[ DITHER_MAX_SYSCOLOR ] =
{
{ 0, 0, 0, 0 },
{ 0, 0, 0x80, 0 },
{ 0, 0x80, 0, 0 },
{ 0, 0x80, 0x80, 0 },
{ 0x80, 0, 0, 0 },
{ 0x80, 0, 0x80, 0 },
{ 0x80, 0x80, 0, 0 },
{ 0x80, 0x80, 0x80, 0 },
{ 0xC0, 0xC0, 0xC0, 0 },
{ 0, 0, 0xFF, 0 },
{ 0, 0xFF, 0, 0 },
{ 0, 0xFF, 0xFF, 0 },
{ 0xFF, 0, 0, 0 },
{ 0xFF, 0, 0xFF, 0 },
{ 0xFF, 0xFF, 0, 0 },
{ 0xFF, 0xFF, 0xFF, 0 }
};
static PALETTEENTRY aImplExtraColor1 =
{
0, 184, 255, 0
};
static BYTE aOrdDither8Bit[8][8] =
{
{ 0, 38, 9, 48, 2, 40, 12, 50 },
{ 25, 12, 35, 22, 28, 15, 37, 24 },
{ 6, 44, 3, 41, 8, 47, 5, 44 },
{ 32, 19, 28, 16, 34, 21, 31, 18 },
{ 1, 40, 11, 49, 0, 39, 10, 48 },
{ 27, 14, 36, 24, 26, 13, 36, 23 },
{ 8, 46, 4, 43, 7, 45, 4, 42 },
{ 33, 20, 30, 17, 32, 20, 29, 16 }
};
static BYTE aOrdDither16Bit[8][8] =
{
{ 0, 6, 1, 7, 0, 6, 1, 7 },
{ 4, 2, 5, 3, 4, 2, 5, 3 },
{ 1, 7, 0, 6, 1, 7, 0, 6 },
{ 5, 3, 4, 2, 5, 3, 4, 2 },
{ 0, 6, 1, 7, 0, 6, 1, 7 },
{ 4, 2, 5, 3, 4, 2, 5, 3 },
{ 1, 7, 0, 6, 1, 7, 0, 6 },
{ 5, 3, 4, 2, 5, 3, 4, 2 }
};
Color ImplGetROPColor( SalROPColor nROPColor )
{
Color nColor;
if ( nROPColor == SalROPColor::N0 )
nColor = Color( 0, 0, 0 );
else
nColor = Color( 255, 255, 255 );
return nColor;
}
int ImplIsPaletteEntry( BYTE nRed, BYTE nGreen, BYTE nBlue )
{
// dither color?
if ( !(nRed % DITHER_PAL_DELTA) && !(nGreen % DITHER_PAL_DELTA) && !(nBlue % DITHER_PAL_DELTA) )
return TRUE;
PALETTEENTRY* pPalEntry = aImplSalSysPalEntryAry;
// standard palette color?
for ( sal_uInt16 i = 0; i < DITHER_MAX_SYSCOLOR; i++, pPalEntry++ )
{
if( pPalEntry->peRed == nRed && pPalEntry->peGreen == nGreen && pPalEntry->peBlue == nBlue )
return TRUE;
}
// extra color?
if ( aImplExtraColor1.peRed == nRed &&
aImplExtraColor1.peGreen == nGreen &&
aImplExtraColor1.peBlue == nBlue )
{
return TRUE;
}
return FALSE;
}
}
WinSalGraphicsImpl::WinSalGraphicsImpl(WinSalGraphics& rParent):
mrParent(rParent),
mbXORMode(false),
mbPen(false),
mhPen(nullptr),
mbStockPen(false),
mbBrush(false),
mbStockBrush(false),
mhBrush(nullptr)
{
}
WinSalGraphicsImpl::~WinSalGraphicsImpl()
{
if ( mhPen )
{
if ( !mbStockPen )
DeletePen( mhPen );
}
if ( mhBrush )
{
if ( !mbStockBrush )
DeleteBrush( mhBrush );
}
}
void WinSalGraphicsImpl::Init()
{
}
void WinSalGraphicsImpl::freeResources()
{
}
bool WinSalGraphicsImpl::drawEPS(long, long, long, long, void*, sal_uLong)
{
return false;
}
void WinSalGraphicsImpl::copyBits( const SalTwoRect& rPosAry, SalGraphics* pSrcGraphics )
{
HDC hSrcDC;
DWORD nRop;
if ( pSrcGraphics )
hSrcDC = static_cast<WinSalGraphics*>(pSrcGraphics)->getHDC();
else
hSrcDC = mrParent.getHDC();
if ( mbXORMode )
nRop = SRCINVERT;
else
nRop = SRCCOPY;
if ( (rPosAry.mnSrcWidth == rPosAry.mnDestWidth) &&
(rPosAry.mnSrcHeight == rPosAry.mnDestHeight) )
{
BitBlt( mrParent.getHDC(),
static_cast<int>(rPosAry.mnDestX), static_cast<int>(rPosAry.mnDestY),
static_cast<int>(rPosAry.mnDestWidth), static_cast<int>(rPosAry.mnDestHeight),
hSrcDC,
static_cast<int>(rPosAry.mnSrcX), static_cast<int>(rPosAry.mnSrcY),
nRop );
}
else
{
int nOldStretchMode = SetStretchBltMode( mrParent.getHDC(), STRETCH_DELETESCANS );
StretchBlt( mrParent.getHDC(),
static_cast<int>(rPosAry.mnDestX), static_cast<int>(rPosAry.mnDestY),
static_cast<int>(rPosAry.mnDestWidth), static_cast<int>(rPosAry.mnDestHeight),
hSrcDC,
static_cast<int>(rPosAry.mnSrcX), static_cast<int>(rPosAry.mnSrcY),
static_cast<int>(rPosAry.mnSrcWidth), static_cast<int>(rPosAry.mnSrcHeight),
nRop );
SetStretchBltMode( mrParent.getHDC(), nOldStretchMode );
}
}
void ImplCalcOutSideRgn( const RECT& rSrcRect,
int nLeft, int nTop, int nRight, int nBottom,
HRGN& rhInvalidateRgn )
{
HRGN hTempRgn;
// calculate area outside the visible region
if ( rSrcRect.left < nLeft )
{
if ( !rhInvalidateRgn )
rhInvalidateRgn = CreateRectRgnIndirect( &rSrcRect );
hTempRgn = CreateRectRgn( -31999, 0, nLeft, 31999 );
CombineRgn( rhInvalidateRgn, rhInvalidateRgn, hTempRgn, RGN_DIFF );
DeleteRegion( hTempRgn );
}
if ( rSrcRect.top < nTop )
{
if ( !rhInvalidateRgn )
rhInvalidateRgn = CreateRectRgnIndirect( &rSrcRect );
hTempRgn = CreateRectRgn( 0, -31999, 31999, nTop );
CombineRgn( rhInvalidateRgn, rhInvalidateRgn, hTempRgn, RGN_DIFF );
DeleteRegion( hTempRgn );
}
if ( rSrcRect.right > nRight )
{
if ( !rhInvalidateRgn )
rhInvalidateRgn = CreateRectRgnIndirect( &rSrcRect );
hTempRgn = CreateRectRgn( nRight, 0, 31999, 31999 );
CombineRgn( rhInvalidateRgn, rhInvalidateRgn, hTempRgn, RGN_DIFF );
DeleteRegion( hTempRgn );
}
if ( rSrcRect.bottom > nBottom )
{
if ( !rhInvalidateRgn )
rhInvalidateRgn = CreateRectRgnIndirect( &rSrcRect );
hTempRgn = CreateRectRgn( 0, nBottom, 31999, 31999 );
CombineRgn( rhInvalidateRgn, rhInvalidateRgn, hTempRgn, RGN_DIFF );
DeleteRegion( hTempRgn );
}
}
void WinSalGraphicsImpl::copyArea( long nDestX, long nDestY,
long nSrcX, long nSrcY,
long nSrcWidth, long nSrcHeight,
bool bWindowInvalidate )
{
bool bRestoreClipRgn = false;
HRGN hOldClipRgn = nullptr;
int nOldClipRgnType = ERROR;
HRGN hInvalidateRgn = nullptr;
// do we have to invalidate also the overlapping regions?
if ( bWindowInvalidate && mrParent.isWindow() )
{
// compute and invalidate those parts that were either off-screen or covered by other windows
// while performing the above BitBlt
// those regions then have to be invalidated as they contain useless/wrong data
RECT aSrcRect;
RECT aClipRect;
RECT aTempRect;
RECT aTempRect2;
HRGN hTempRgn;
HWND hWnd;
// restrict srcRect to this window (calc intersection)
aSrcRect.left = static_cast<int>(nSrcX);
aSrcRect.top = static_cast<int>(nSrcY);
aSrcRect.right = aSrcRect.left+static_cast<int>(nSrcWidth);
aSrcRect.bottom = aSrcRect.top+static_cast<int>(nSrcHeight);
GetClientRect( mrParent.gethWnd(), &aClipRect );
if ( IntersectRect( &aSrcRect, &aSrcRect, &aClipRect ) )
{
// transform srcRect to screen coordinates
POINT aPt;
aPt.x = 0;
aPt.y = 0;
ClientToScreen( mrParent.gethWnd(), &aPt );
aSrcRect.left += aPt.x;
aSrcRect.top += aPt.y;
aSrcRect.right += aPt.x;
aSrcRect.bottom += aPt.y;
hInvalidateRgn = nullptr;
// compute the parts that are off screen (ie invisible)
RECT theScreen;
ImplSalGetWorkArea( nullptr, &theScreen, nullptr ); // find the screen area taking multiple monitors into account
ImplCalcOutSideRgn( aSrcRect, theScreen.left, theScreen.top, theScreen.right, theScreen.bottom, hInvalidateRgn );
// calculate regions that are covered by other windows
HRGN hTempRgn2 = nullptr;
HWND hWndTopWindow = mrParent.gethWnd();
// Find the TopLevel Window, because only Windows which are in
// in the foreground of our TopLevel window must be considered
if ( GetWindowStyle( hWndTopWindow ) & WS_CHILD )
{
RECT aTempRect3 = aSrcRect;
do
{
hWndTopWindow = ::GetParent( hWndTopWindow );
// Test if the Parent clips our window
GetClientRect( hWndTopWindow, &aTempRect );
POINT aPt2;
aPt2.x = 0;
aPt2.y = 0;
ClientToScreen( hWndTopWindow, &aPt2 );
aTempRect.left += aPt2.x;
aTempRect.top += aPt2.y;
aTempRect.right += aPt2.x;
aTempRect.bottom += aPt2.y;
IntersectRect( &aTempRect3, &aTempRect3, &aTempRect );
}
while ( GetWindowStyle( hWndTopWindow ) & WS_CHILD );
// If one or more Parents clip our window, than we must
// calculate the outside area
if ( !EqualRect( &aSrcRect, &aTempRect3 ) )
{
ImplCalcOutSideRgn( aSrcRect,
aTempRect3.left, aTempRect3.top,
aTempRect3.right, aTempRect3.bottom,
hInvalidateRgn );
}
}
// retrieve the top-most (z-order) child window
hWnd = GetWindow( GetDesktopWindow(), GW_CHILD );
while ( hWnd )
{
if ( hWnd == hWndTopWindow )
break;
if ( IsWindowVisible( hWnd ) && !IsIconic( hWnd ) )
{
GetWindowRect( hWnd, &aTempRect );
if ( IntersectRect( &aTempRect2, &aSrcRect, &aTempRect ) )
{
// hWnd covers part or all of aSrcRect
if ( !hInvalidateRgn )
hInvalidateRgn = CreateRectRgnIndirect( &aSrcRect );
// get full bounding box of hWnd
hTempRgn = CreateRectRgnIndirect( &aTempRect );
// get region of hWnd (the window may be shaped)
if ( !hTempRgn2 )
hTempRgn2 = CreateRectRgn( 0, 0, 0, 0 );
int nRgnType = GetWindowRgn( hWnd, hTempRgn2 );
if ( (nRgnType != ERROR) && (nRgnType != NULLREGION) )
{
// convert window region to screen coordinates
OffsetRgn( hTempRgn2, aTempRect.left, aTempRect.top );
// and intersect with the window's bounding box
CombineRgn( hTempRgn, hTempRgn, hTempRgn2, RGN_AND );
}
// finally compute that part of aSrcRect which is not covered by any parts of hWnd
CombineRgn( hInvalidateRgn, hInvalidateRgn, hTempRgn, RGN_DIFF );
DeleteRegion( hTempRgn );
}
}
// retrieve the next window in the z-order, i.e. the window below hwnd
hWnd = GetWindow( hWnd, GW_HWNDNEXT );
}
if ( hTempRgn2 )
DeleteRegion( hTempRgn2 );
if ( hInvalidateRgn )
{
// hInvalidateRgn contains the fully visible parts of the original srcRect
hTempRgn = CreateRectRgnIndirect( &aSrcRect );
// subtract it from the original rect to get the occluded parts
int nRgnType = CombineRgn( hInvalidateRgn, hTempRgn, hInvalidateRgn, RGN_DIFF );
DeleteRegion( hTempRgn );
if ( (nRgnType != ERROR) && (nRgnType != NULLREGION) )
{
// move the occluded parts to the destination pos
int nOffX = static_cast<int>(nDestX-nSrcX);
int nOffY = static_cast<int>(nDestY-nSrcY);
OffsetRgn( hInvalidateRgn, nOffX-aPt.x, nOffY-aPt.y );
// by excluding hInvalidateRgn from the system's clip region
// we will prevent bitblt from copying useless data
// especially now shadows from overlapping windows will appear (#i36344)
hOldClipRgn = CreateRectRgn( 0, 0, 0, 0 );
nOldClipRgnType = GetClipRgn( mrParent.getHDC(), hOldClipRgn );
bRestoreClipRgn = TRUE; // indicate changed clipregion and force invalidate
ExtSelectClipRgn( mrParent.getHDC(), hInvalidateRgn, RGN_DIFF );
}
}
}
}
BitBlt( mrParent.getHDC(),
static_cast<int>(nDestX), static_cast<int>(nDestY),
static_cast<int>(nSrcWidth), static_cast<int>(nSrcHeight),
mrParent.getHDC(),
static_cast<int>(nSrcX), static_cast<int>(nSrcY),
SRCCOPY );
if( bRestoreClipRgn )
{
// restore old clip region
if( nOldClipRgnType != ERROR )
SelectClipRgn( mrParent.getHDC(), hOldClipRgn);
DeleteRegion( hOldClipRgn );
// invalidate regions that were not copied
bool bInvalidate = true;
// Combine Invalidate vcl::Region with existing ClipRegion
HRGN hTempRgn = CreateRectRgn( 0, 0, 0, 0 );
if ( GetClipRgn( mrParent.getHDC(), hTempRgn ) == 1 )
{
int nRgnType = CombineRgn( hInvalidateRgn, hTempRgn, hInvalidateRgn, RGN_AND );
if ( (nRgnType == ERROR) || (nRgnType == NULLREGION) )
bInvalidate = false;
}
DeleteRegion( hTempRgn );
if ( bInvalidate )
{
InvalidateRgn( mrParent.gethWnd(), hInvalidateRgn, TRUE );
// here we only initiate an update if this is the MainThread,
// so that there is no deadlock when handling the Paint event,
// as the SolarMutex is already held by this Thread
SalData* pSalData = GetSalData();
DWORD nCurThreadId = GetCurrentThreadId();
if ( pSalData->mnAppThreadId == nCurThreadId )
UpdateWindow( mrParent.gethWnd() );
}
DeleteRegion( hInvalidateRgn );
}
}
namespace {
void ImplDrawBitmap( HDC hDC, const SalTwoRect& rPosAry, const WinSalBitmap& rSalBitmap,
bool bPrinter, int nDrawMode )
{
if( hDC )
{
HGLOBAL hDrawDIB;
HBITMAP hDrawDDB = rSalBitmap.ImplGethDDB();
WinSalBitmap* pTmpSalBmp = nullptr;
bool bPrintDDB = ( bPrinter && hDrawDDB );
if( bPrintDDB )
{
pTmpSalBmp = new WinSalBitmap;
pTmpSalBmp->Create( rSalBitmap, rSalBitmap.GetBitCount() );
hDrawDIB = pTmpSalBmp->ImplGethDIB();
}
else
hDrawDIB = rSalBitmap.ImplGethDIB();
if( hDrawDIB )
{
PBITMAPINFO pBI = static_cast<PBITMAPINFO>(GlobalLock( hDrawDIB ));
PBYTE pBits = reinterpret_cast<PBYTE>(pBI) + pBI->bmiHeader.biSize +
WinSalBitmap::ImplGetDIBColorCount( hDrawDIB ) * sizeof( RGBQUAD );
const int nOldStretchMode = SetStretchBltMode( hDC, STRETCH_DELETESCANS );
StretchDIBits( hDC,
static_cast<int>(rPosAry.mnDestX), static_cast<int>(rPosAry.mnDestY),
static_cast<int>(rPosAry.mnDestWidth), static_cast<int>(rPosAry.mnDestHeight),
static_cast<int>(rPosAry.mnSrcX), static_cast<int>(pBI->bmiHeader.biHeight - rPosAry.mnSrcHeight - rPosAry.mnSrcY),
static_cast<int>(rPosAry.mnSrcWidth), static_cast<int>(rPosAry.mnSrcHeight),
pBits, pBI, DIB_RGB_COLORS, nDrawMode );
GlobalUnlock( hDrawDIB );
SetStretchBltMode( hDC, nOldStretchMode );
}
else if( hDrawDDB && !bPrintDDB )
{
HDC hBmpDC = ImplGetCachedDC( CACHED_HDC_DRAW, hDrawDDB );
COLORREF nOldBkColor = RGB(0xFF,0xFF,0xFF);
COLORREF nOldTextColor = RGB(0,0,0);
bool bMono = ( rSalBitmap.GetBitCount() == 1 );
if( bMono )
{
COLORREF nBkColor = RGB( 0xFF, 0xFF, 0xFF );
COLORREF nTextColor = RGB( 0x00, 0x00, 0x00 );
//fdo#33455 handle 1 bit depth pngs with palette entries
//to set fore/back colors
if (BitmapBuffer* pBitmapBuffer = const_cast<WinSalBitmap&>(rSalBitmap).AcquireBuffer(BitmapAccessMode::Info))
{
const BitmapPalette& rPalette = pBitmapBuffer->maPalette;
if (rPalette.GetEntryCount() == 2)
{
Color nCol = rPalette[0].GetColor();
nTextColor = RGB( nCol.GetRed(), nCol.GetGreen(), nCol.GetBlue() );
nCol = rPalette[1].GetColor();
nBkColor = RGB( nCol.GetRed(), nCol.GetGreen(), nCol.GetBlue() );
}
const_cast<WinSalBitmap&>(rSalBitmap).ReleaseBuffer(pBitmapBuffer, BitmapAccessMode::Info);
}
nOldBkColor = SetBkColor( hDC, nBkColor );
nOldTextColor = ::SetTextColor( hDC, nTextColor );
}
if ( (rPosAry.mnSrcWidth == rPosAry.mnDestWidth) &&
(rPosAry.mnSrcHeight == rPosAry.mnDestHeight) )
{
BitBlt( hDC,
static_cast<int>(rPosAry.mnDestX), static_cast<int>(rPosAry.mnDestY),
static_cast<int>(rPosAry.mnDestWidth), static_cast<int>(rPosAry.mnDestHeight),
hBmpDC,
static_cast<int>(rPosAry.mnSrcX), static_cast<int>(rPosAry.mnSrcY),
nDrawMode );
}
else
{
const int nOldStretchMode = SetStretchBltMode( hDC, STRETCH_DELETESCANS );
StretchBlt( hDC,
static_cast<int>(rPosAry.mnDestX), static_cast<int>(rPosAry.mnDestY),
static_cast<int>(rPosAry.mnDestWidth), static_cast<int>(rPosAry.mnDestHeight),
hBmpDC,
static_cast<int>(rPosAry.mnSrcX), static_cast<int>(rPosAry.mnSrcY),
static_cast<int>(rPosAry.mnSrcWidth), static_cast<int>(rPosAry.mnSrcHeight),
nDrawMode );
SetStretchBltMode( hDC, nOldStretchMode );
}
if( bMono )
{
SetBkColor( hDC, nOldBkColor );
::SetTextColor( hDC, nOldTextColor );
}
ImplReleaseCachedDC( CACHED_HDC_DRAW );
}
if( bPrintDDB )
delete pTmpSalBmp;
}
}
}
void WinSalGraphicsImpl::drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap)
{
bool bTryDirectPaint(!mrParent.isPrinter() && !mbXORMode);
if(bTryDirectPaint)
{
// only paint direct when no scaling and no MapMode, else the
// more expensive conversions may be done for short-time Bitmap/BitmapEx
// used for buffering only
if(rPosAry.mnSrcWidth == rPosAry.mnDestWidth && rPosAry.mnSrcHeight == rPosAry.mnDestHeight)
{
bTryDirectPaint = false;
}
}
// try to draw using GdiPlus directly
if(bTryDirectPaint && tryDrawBitmapGdiPlus(rPosAry, rSalBitmap))
{
return;
}
// fall back old stuff
assert(dynamic_cast<const WinSalBitmap*>(&rSalBitmap));
ImplDrawBitmap(mrParent.getHDC(), rPosAry, static_cast<const WinSalBitmap&>(rSalBitmap),
mrParent.isPrinter(),
mbXORMode ? SRCINVERT : SRCCOPY );
}
void WinSalGraphicsImpl::drawBitmap( const SalTwoRect& rPosAry,
const SalBitmap& rSSalBitmap,
const SalBitmap& rSTransparentBitmap )
{
SAL_WARN_IF( mrParent.isPrinter(), "vcl", "No transparency print possible!" );
bool bTryDirectPaint(!mrParent.isPrinter() && !mbXORMode);
// try to draw using GdiPlus directly
if(bTryDirectPaint && drawAlphaBitmap(rPosAry, rSSalBitmap, rSTransparentBitmap))
{
return;
}
assert(dynamic_cast<const WinSalBitmap*>(&rSSalBitmap));
assert(dynamic_cast<const WinSalBitmap*>(&rSTransparentBitmap));
const WinSalBitmap& rSalBitmap = static_cast<const WinSalBitmap&>(rSSalBitmap);
const WinSalBitmap& rTransparentBitmap = static_cast<const WinSalBitmap&>(rSTransparentBitmap);
SalTwoRect aPosAry = rPosAry;
int nDstX = static_cast<int>(aPosAry.mnDestX);
int nDstY = static_cast<int>(aPosAry.mnDestY);
int nDstWidth = static_cast<int>(aPosAry.mnDestWidth);
int nDstHeight = static_cast<int>(aPosAry.mnDestHeight);
HDC hDC = mrParent.getHDC();
HBITMAP hMemBitmap = nullptr;
HBITMAP hMaskBitmap = nullptr;
if( ( nDstWidth > CACHED_HDC_DEFEXT ) || ( nDstHeight > CACHED_HDC_DEFEXT ) )
{
hMemBitmap = CreateCompatibleBitmap( hDC, nDstWidth, nDstHeight );
hMaskBitmap = CreateCompatibleBitmap( hDC, nDstWidth, nDstHeight );
}
HDC hMemDC = ImplGetCachedDC( CACHED_HDC_1, hMemBitmap );
HDC hMaskDC = ImplGetCachedDC( CACHED_HDC_2, hMaskBitmap );
aPosAry.mnDestX = aPosAry.mnDestY = 0;
BitBlt( hMemDC, 0, 0, nDstWidth, nDstHeight, hDC, nDstX, nDstY, SRCCOPY );
// WIN/WNT seems to have a minor problem mapping the correct color of the
// mask to the palette if we draw the DIB directly ==> draw DDB
if( ( GetBitCount() <= 8 ) && rTransparentBitmap.ImplGethDIB() && rTransparentBitmap.GetBitCount() == 1 )
{
WinSalBitmap aTmp;
if( aTmp.Create( rTransparentBitmap, &mrParent ) )
ImplDrawBitmap( hMaskDC, aPosAry, aTmp, false, SRCCOPY );
}
else
ImplDrawBitmap( hMaskDC, aPosAry, rTransparentBitmap, false, SRCCOPY );
// now MemDC contains background, MaskDC the transparency mask
// #105055# Respect XOR mode
if( mbXORMode )
{
ImplDrawBitmap( hMaskDC, aPosAry, rSalBitmap, false, SRCERASE );
// now MaskDC contains the bitmap area with black background
BitBlt( hMemDC, 0, 0, nDstWidth, nDstHeight, hMaskDC, 0, 0, SRCINVERT );
// now MemDC contains background XORed bitmap area ontop
}
else
{
BitBlt( hMemDC, 0, 0, nDstWidth, nDstHeight, hMaskDC, 0, 0, SRCAND );
// now MemDC contains background with masked-out bitmap area
ImplDrawBitmap( hMaskDC, aPosAry, rSalBitmap, false, SRCERASE );
// now MaskDC contains the bitmap area with black background
BitBlt( hMemDC, 0, 0, nDstWidth, nDstHeight, hMaskDC, 0, 0, SRCPAINT );
// now MemDC contains background and bitmap merged together
}
// copy to output DC
BitBlt( hDC, nDstX, nDstY, nDstWidth, nDstHeight, hMemDC, 0, 0, SRCCOPY );
ImplReleaseCachedDC( CACHED_HDC_1 );
ImplReleaseCachedDC( CACHED_HDC_2 );
// hMemBitmap != 0 ==> hMaskBitmap != 0
if( hMemBitmap )
{
DeleteObject( hMemBitmap );
DeleteObject( hMaskBitmap );
}
}
bool WinSalGraphicsImpl::drawAlphaRect( long nX, long nY, long nWidth,
long nHeight, sal_uInt8 nTransparency )
{
if( mbPen || !mbBrush || mbXORMode )
return false; // can only perform solid fills without XOR.
HDC hMemDC = ImplGetCachedDC( CACHED_HDC_1 );
SetPixel( hMemDC, int(0), int(0), mnBrushColor );
BLENDFUNCTION aFunc = {
AC_SRC_OVER,
0,
sal::static_int_cast<sal_uInt8>(255 - 255L*nTransparency/100),
0
};
// hMemDC contains a 1x1 bitmap of the right color - stretch-blit
// that to dest hdc
bool bRet = GdiAlphaBlend(mrParent.getHDC(), nX, nY, nWidth, nHeight,
hMemDC, 0,0,1,1,
aFunc ) == TRUE;
ImplReleaseCachedDC( CACHED_HDC_1 );
return bRet;
}
void WinSalGraphicsImpl::drawMask( const SalTwoRect& rPosAry,
const SalBitmap& rSSalBitmap,
Color nMaskColor )
{
SAL_WARN_IF( mrParent.isPrinter(), "vcl", "No transparency print possible!" );
assert(dynamic_cast<const WinSalBitmap*>(&rSSalBitmap));
const WinSalBitmap& rSalBitmap = static_cast<const WinSalBitmap&>(rSSalBitmap);
SalTwoRect aPosAry = rPosAry;
const BYTE cRed = nMaskColor.GetRed();
const BYTE cGreen = nMaskColor.GetGreen();
const BYTE cBlue = nMaskColor.GetBlue();
HDC hDC = mrParent.getHDC();
HBRUSH hMaskBrush = CreateSolidBrush( RGB( cRed, cGreen, cBlue ) );
HBRUSH hOldBrush = SelectBrush( hDC, hMaskBrush );
// WIN/WNT seems to have a minor problem mapping the correct color of the
// mask to the palette if we draw the DIB directly ==> draw DDB
if( ( GetBitCount() <= 8 ) && rSalBitmap.ImplGethDIB() && rSalBitmap.GetBitCount() == 1 )
{
WinSalBitmap aTmp;
if( aTmp.Create( rSalBitmap, &mrParent ) )
ImplDrawBitmap( hDC, aPosAry, aTmp, false, 0x00B8074AUL );
}
else
ImplDrawBitmap( hDC, aPosAry, rSalBitmap, false, 0x00B8074AUL );
SelectBrush( hDC, hOldBrush );
DeleteBrush( hMaskBrush );
}
std::shared_ptr<SalBitmap> WinSalGraphicsImpl::getBitmap( long nX, long nY, long nDX, long nDY )
{
SAL_WARN_IF( mrParent.isPrinter(), "vcl", "No ::GetBitmap() from printer possible!" );
std::shared_ptr<WinSalBitmap> pSalBitmap;
nDX = labs( nDX );
nDY = labs( nDY );
HDC hDC = mrParent.getHDC();
HBITMAP hBmpBitmap = CreateCompatibleBitmap( hDC, nDX, nDY );
HDC hBmpDC = ImplGetCachedDC( CACHED_HDC_1, hBmpBitmap );
bool bRet;
bRet = BitBlt( hBmpDC, 0, 0, static_cast<int>(nDX), static_cast<int>(nDY), hDC, static_cast<int>(nX), static_cast<int>(nY), SRCCOPY ) ? TRUE : FALSE;
ImplReleaseCachedDC( CACHED_HDC_1 );
if( bRet )
{
pSalBitmap = std::make_shared<WinSalBitmap>();
if( !pSalBitmap->Create( hBmpBitmap, FALSE, FALSE ) )
{
pSalBitmap.reset();
}
}
else
{
// #124826# avoid resource leak! Happens when running without desktop access (remote desktop, service, may be screensavers)
DeleteBitmap( hBmpBitmap );
}
return pSalBitmap;
}
Color WinSalGraphicsImpl::getPixel( long nX, long nY )
{
COLORREF aWinCol = ::GetPixel( mrParent.getHDC(), static_cast<int>(nX), static_cast<int>(nY) );
if ( CLR_INVALID == aWinCol )
return Color( 0, 0, 0 );
else
return Color( GetRValue( aWinCol ),
GetGValue( aWinCol ),
GetBValue( aWinCol ) );
}
void WinSalGraphicsImpl::invert( long nX, long nY, long nWidth, long nHeight, SalInvert nFlags )
{
if ( nFlags & SalInvert::TrackFrame )
{
HPEN hDotPen = CreatePen( PS_DOT, 0, 0 );
HPEN hOldPen = SelectPen( mrParent.getHDC(), hDotPen );
HBRUSH hOldBrush = SelectBrush( mrParent.getHDC(), GetStockBrush( NULL_BRUSH ) );
int nOldROP = SetROP2( mrParent.getHDC(), R2_NOT );
Rectangle( mrParent.getHDC(), static_cast<int>(nX), static_cast<int>(nY), static_cast<int>(nX+nWidth), static_cast<int>(nY+nHeight) );
SetROP2( mrParent.getHDC(), nOldROP );
SelectPen( mrParent.getHDC(), hOldPen );
SelectBrush( mrParent.getHDC(), hOldBrush );
DeletePen( hDotPen );
}
else if ( nFlags & SalInvert::N50 )
{
SalData* pSalData = GetSalData();
if ( !pSalData->mh50Brush )
{
if ( !pSalData->mh50Bmp )
pSalData->mh50Bmp = ImplLoadSalBitmap( SAL_RESID_BITMAP_50 );
pSalData->mh50Brush = CreatePatternBrush( pSalData->mh50Bmp );
}
COLORREF nOldTextColor = ::SetTextColor( mrParent.getHDC(), 0 );
HBRUSH hOldBrush = SelectBrush( mrParent.getHDC(), pSalData->mh50Brush );
PatBlt( mrParent.getHDC(), nX, nY, nWidth, nHeight, PATINVERT );
::SetTextColor( mrParent.getHDC(), nOldTextColor );
SelectBrush( mrParent.getHDC(), hOldBrush );
}
else
{
RECT aRect;
aRect.left = static_cast<int>(nX);
aRect.top = static_cast<int>(nY);
aRect.right = static_cast<int>(nX)+nWidth;
aRect.bottom = static_cast<int>(nY)+nHeight;
::InvertRect( mrParent.getHDC(), &aRect );
}
}
void WinSalGraphicsImpl::invert( sal_uInt32 nPoints, const SalPoint* pPtAry, SalInvert nSalFlags )
{
HPEN hPen;
HPEN hOldPen;
HBRUSH hBrush;
HBRUSH hOldBrush = nullptr;
COLORREF nOldTextColor RGB(0,0,0);
int nOldROP = SetROP2( mrParent.getHDC(), R2_NOT );
if ( nSalFlags & SalInvert::TrackFrame )
hPen = CreatePen( PS_DOT, 0, 0 );
else
{
if ( nSalFlags & SalInvert::N50 )
{
SalData* pSalData = GetSalData();
if ( !pSalData->mh50Brush )
{
if ( !pSalData->mh50Bmp )
pSalData->mh50Bmp = ImplLoadSalBitmap( SAL_RESID_BITMAP_50 );
pSalData->mh50Brush = CreatePatternBrush( pSalData->mh50Bmp );
}
hBrush = pSalData->mh50Brush;
}
else
hBrush = GetStockBrush( BLACK_BRUSH );
hPen = GetStockPen( NULL_PEN );
nOldTextColor = ::SetTextColor( mrParent.getHDC(), 0 );
hOldBrush = SelectBrush( mrParent.getHDC(), hBrush );
}
hOldPen = SelectPen( mrParent.getHDC(), hPen );
POINT const * pWinPtAry;
// for NT, we can handover the array directly
static_assert( sizeof( POINT ) == sizeof( SalPoint ), "must be the same size" );
pWinPtAry = reinterpret_cast<POINT const *>(pPtAry);
// for Windows 95 and its maximum number of points
if ( nSalFlags & SalInvert::TrackFrame )
{
if ( !Polyline( mrParent.getHDC(), pWinPtAry, static_cast<int>(nPoints) ) && (nPoints > MAX_64KSALPOINTS) )
Polyline( mrParent.getHDC(), pWinPtAry, MAX_64KSALPOINTS );
}
else
{
if ( !Polygon( mrParent.getHDC(), pWinPtAry, static_cast<int>(nPoints) ) && (nPoints > MAX_64KSALPOINTS) )
Polygon( mrParent.getHDC(), pWinPtAry, MAX_64KSALPOINTS );
}
SetROP2( mrParent.getHDC(), nOldROP );
SelectPen( mrParent.getHDC(), hOldPen );
if ( nSalFlags & SalInvert::TrackFrame )
DeletePen( hPen );
else
{
::SetTextColor( mrParent.getHDC(), nOldTextColor );
SelectBrush( mrParent.getHDC(), hOldBrush );
}
}
sal_uInt16 WinSalGraphicsImpl::GetBitCount() const
{
return static_cast<sal_uInt16>(GetDeviceCaps( mrParent.getHDC(), BITSPIXEL ));
}
long WinSalGraphicsImpl::GetGraphicsWidth() const
{
if( mrParent.gethWnd() && IsWindow( mrParent.gethWnd() ) )
{
WinSalFrame* pFrame = GetWindowPtr( mrParent.gethWnd() );
if( pFrame )
{
if( pFrame->maGeometry.nWidth )
return pFrame->maGeometry.nWidth;
else
{
// TODO: perhaps not needed, maGeometry should always be up-to-date
RECT aRect;
GetClientRect( mrParent.gethWnd(), &aRect );
return aRect.right;
}
}
}
return 0;
}
void WinSalGraphicsImpl::ResetClipRegion()
{
if ( mrParent.mhRegion )
{
DeleteRegion( mrParent.mhRegion );
mrParent.mhRegion = nullptr;
}
SelectClipRgn( mrParent.getHDC(), nullptr );
}
static bool containsOnlyHorizontalAndVerticalEdges(const basegfx::B2DPolygon& rCandidate)
{
if(rCandidate.areControlPointsUsed())
{
return false;
}
const sal_uInt32 nPointCount(rCandidate.count());
if(nPointCount < 2)
{
return true;
}
const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount + 1 : nPointCount);
basegfx::B2DPoint aLast(rCandidate.getB2DPoint(0));
for(sal_uInt32 a(1); a < nEdgeCount; a++)
{
const sal_uInt32 nNextIndex(a % nPointCount);
const basegfx::B2DPoint aCurrent(rCandidate.getB2DPoint(nNextIndex));
if(!basegfx::fTools::equal(aLast.getX(), aCurrent.getX()) && !basegfx::fTools::equal(aLast.getY(), aCurrent.getY()))
{
return false;
}
aLast = aCurrent;
}
return true;
}
static bool containsOnlyHorizontalAndVerticalEdges(const basegfx::B2DPolyPolygon& rCandidate)
{
if(rCandidate.areControlPointsUsed())
{
return false;
}
for(sal_uInt32 a(0); a < rCandidate.count(); a++)
{
if(!containsOnlyHorizontalAndVerticalEdges(rCandidate.getB2DPolygon(a)))
{
return false;
}
}
return true;
}
bool WinSalGraphicsImpl::setClipRegion( const vcl::Region& i_rClip )
{
if ( mrParent.mhRegion )
{
DeleteRegion( mrParent.mhRegion );
mrParent.mhRegion = nullptr;
}
bool bUsePolygon(i_rClip.HasPolyPolygonOrB2DPolyPolygon());
static bool bTryToAvoidPolygon(true);
// #i122149# try to avoid usage of tools::PolyPolygon ClipRegions when tools::PolyPolygon is no curve
// and only contains horizontal/vertical edges. In that case, use the fallback
// in GetRegionRectangles which will use vcl::Region::GetAsRegionBand() which will do
// the correct polygon-to-RegionBand transformation.
// Background is that when using the same Rectangle as rectangle or as Polygon
// clip region will lead to different results; the polygon-based one will be
// one pixel less to the right and down (see GDI docu for CreatePolygonRgn). This
// again is because of the polygon-nature and it's classic handling when filling.
// This also means that all cases which use a 'true' polygon-based incarnation of
// a vcl::Region should know what they do - it may lead to repaint errors.
if(bUsePolygon && bTryToAvoidPolygon)
{
const basegfx::B2DPolyPolygon aPolyPolygon( i_rClip.GetAsB2DPolyPolygon() );
if(!aPolyPolygon.areControlPointsUsed())
{
if(containsOnlyHorizontalAndVerticalEdges(aPolyPolygon))
{
bUsePolygon = false;
}
}
}
if(bUsePolygon)
{
// #i122149# check the comment above to know that this may lead to potential repaint
// problems. It may be solved (if needed) by scaling the polygon by one in X
// and Y. Currently the workaround to only use it if really unavoidable will
// solve most cases. When someone is really using polygon-based Regions he
// should know what he is doing.
// Added code to do that scaling to check if it works, testing it.
const basegfx::B2DPolyPolygon aPolyPolygon( i_rClip.GetAsB2DPolyPolygon() );
const sal_uInt32 nCount(aPolyPolygon.count());
if( nCount )
{
std::vector< POINT > aPolyPoints;
aPolyPoints.reserve( 1024 );
std::vector< INT > aPolyCounts( nCount, 0 );
basegfx::B2DHomMatrix aExpand;
sal_uInt32 nTargetCount(0);
static bool bExpandByOneInXandY(true);
if(bExpandByOneInXandY)
{
const basegfx::B2DRange aRangeS(aPolyPolygon.getB2DRange());
const basegfx::B2DRange aRangeT(aRangeS.getMinimum(), aRangeS.getMaximum() + basegfx::B2DTuple(1.0, 1.0));
aExpand = basegfx::B2DHomMatrix(basegfx::utils::createSourceRangeTargetRangeTransform(aRangeS, aRangeT));
}
for(sal_uInt32 a(0); a < nCount; a++)
{
const basegfx::B2DPolygon aPoly(
basegfx::utils::adaptiveSubdivideByDistance(
aPolyPolygon.getB2DPolygon(a),
1));
const sal_uInt32 nPoints(aPoly.count());
// tdf#40863 For CustomShapes there is a hack (see
// f64ef72743e55389e446e0d4bc6febd475011023) that adds polygons
// with a single point in top-left and bottom-right corner
// of the BoundRect to be able to determine the correct BoundRect
// in the slideshow. Unfortunately, CreatePolyPolygonRgn below
// fails with polygons containing a single pixel, so clipping is
// lost. For now, use only polygons with more than two points - the
// ones that may have an area.
// Note: polygons with one point which are curves may have an area,
// but the polygon is already subdivided here, so no need to test
// this.
if(nPoints > 2)
{
aPolyCounts[nTargetCount] = nPoints;
nTargetCount++;
for( sal_uInt32 b = 0; b < nPoints; b++ )
{
basegfx::B2DPoint aPt(aPoly.getB2DPoint(b));
if(bExpandByOneInXandY)
{
aPt = aExpand * aPt;
}
POINT aPOINT;
// #i122149# do correct rounding
aPOINT.x = basegfx::fround(aPt.getX());
aPOINT.y = basegfx::fround(aPt.getY());
aPolyPoints.push_back( aPOINT );
}
}
}
if(nTargetCount)
{
mrParent.mhRegion = CreatePolyPolygonRgn( &aPolyPoints[0], &aPolyCounts[0], nTargetCount, ALTERNATE );
}
}
}
else
{
RectangleVector aRectangles;
i_rClip.GetRegionRectangles(aRectangles);
sal_uLong nRectBufSize = sizeof(RECT)*aRectangles.size();
if ( aRectangles.size() < SAL_CLIPRECT_COUNT )
{
if ( !mrParent.mpStdClipRgnData )
mrParent.mpStdClipRgnData = reinterpret_cast<RGNDATA*>(new BYTE[sizeof(RGNDATA)-1+(SAL_CLIPRECT_COUNT*sizeof(RECT))]);
mrParent.mpClipRgnData = mrParent.mpStdClipRgnData;
}
else
mrParent.mpClipRgnData = reinterpret_cast<RGNDATA*>(new BYTE[sizeof(RGNDATA)-1+nRectBufSize]);
mrParent.mpClipRgnData->rdh.dwSize = sizeof( RGNDATAHEADER );
mrParent.mpClipRgnData->rdh.iType = RDH_RECTANGLES;
mrParent.mpClipRgnData->rdh.nCount = aRectangles.size();
mrParent.mpClipRgnData->rdh.nRgnSize = nRectBufSize;
RECT* pBoundRect = &(mrParent.mpClipRgnData->rdh.rcBound);
SetRectEmpty( pBoundRect );
RECT* pNextClipRect = reinterpret_cast<RECT*>(&(mrParent.mpClipRgnData->Buffer));
bool bFirstClipRect = true;
for (auto const& rectangle : aRectangles)
{
const long nW(rectangle.GetWidth());
const long nH(rectangle.GetHeight());
if(nW && nH)
{
const long nRight(rectangle.Left() + nW);
const long nBottom(rectangle.Top() + nH);
if(bFirstClipRect)
{
pBoundRect->left = rectangle.Left();
pBoundRect->top = rectangle.Top();
pBoundRect->right = nRight;
pBoundRect->bottom = nBottom;
bFirstClipRect = false;
}
else
{
if(rectangle.Left() < pBoundRect->left)
{
pBoundRect->left = static_cast<int>(rectangle.Left());
}
if(rectangle.Top() < pBoundRect->top)
{
pBoundRect->top = static_cast<int>(rectangle.Top());
}
if(nRight > pBoundRect->right)
{
pBoundRect->right = static_cast<int>(nRight);
}
if(nBottom > pBoundRect->bottom)
{
pBoundRect->bottom = static_cast<int>(nBottom);
}
}
pNextClipRect->left = static_cast<int>(rectangle.Left());
pNextClipRect->top = static_cast<int>(rectangle.Top());
pNextClipRect->right = static_cast<int>(nRight);
pNextClipRect->bottom = static_cast<int>(nBottom);
pNextClipRect++;
}
else
{
mrParent.mpClipRgnData->rdh.nCount--;
mrParent.mpClipRgnData->rdh.nRgnSize -= sizeof( RECT );
}
}
// create clip region from ClipRgnData
if(0 == mrParent.mpClipRgnData->rdh.nCount)
{
// #i123585# region is empty; this may happen when e.g. a tools::PolyPolygon is given
// that contains no polygons or only empty ones (no width/height). This is
// perfectly fine and we are done, except setting it (see end of method)
}
else if(1 == mrParent.mpClipRgnData->rdh.nCount)
{
RECT* pRect = &(mrParent.mpClipRgnData->rdh.rcBound);
mrParent.mhRegion = CreateRectRgn( pRect->left, pRect->top,
pRect->right, pRect->bottom );
}
else if(mrParent.mpClipRgnData->rdh.nCount > 1)
{
sal_uLong nSize = mrParent.mpClipRgnData->rdh.nRgnSize+sizeof(RGNDATAHEADER);
mrParent.mhRegion = ExtCreateRegion( nullptr, nSize, mrParent.mpClipRgnData );
// if ExtCreateRegion(...) is not supported
if( !mrParent.mhRegion )
{
RGNDATAHEADER const & pHeader = mrParent.mpClipRgnData->rdh;
if( pHeader.nCount )
{
RECT* pRect = reinterpret_cast<RECT*>(mrParent.mpClipRgnData->Buffer);
mrParent.mhRegion = CreateRectRgn( pRect->left, pRect->top, pRect->right, pRect->bottom );
pRect++;
for( sal_uLong n = 1; n < pHeader.nCount; n++, pRect++ )
{
HRGN hRgn = CreateRectRgn( pRect->left, pRect->top, pRect->right, pRect->bottom );
CombineRgn( mrParent.mhRegion, mrParent.mhRegion, hRgn, RGN_OR );
DeleteRegion( hRgn );
}
}
}
if ( mrParent.mpClipRgnData != mrParent.mpStdClipRgnData )
delete [] reinterpret_cast<BYTE*>(mrParent.mpClipRgnData);
}
}
if( mrParent.mhRegion )
{
SelectClipRgn( mrParent.getHDC(), mrParent.mhRegion );
// debug code if you want to check range of the newly applied ClipRegion
//RECT aBound;
//const int aRegionType = GetRgnBox(mrParent.mhRegion, &aBound);
//bool bBla = true;
}
else
{
// #i123585# See above, this is a valid case, execute it
SelectClipRgn( mrParent.getHDC(), nullptr );
}
// #i123585# retval no longer dependent of mrParent.mhRegion, see TaskId comments above
return true;
}
void WinSalGraphicsImpl::SetLineColor()
{
// create and select new pen
HPEN hNewPen = GetStockPen( NULL_PEN );
HPEN hOldPen = SelectPen( mrParent.getHDC(), hNewPen );
// destroy or save old pen
if ( mhPen )
{
if ( !mbStockPen )
DeletePen( mhPen );
}
else
mrParent.mhDefPen = hOldPen;
// set new data
mhPen = hNewPen;
mbPen = FALSE;
mbStockPen = TRUE;
}
void WinSalGraphicsImpl::SetLineColor( Color nColor )
{
maLineColor = nColor;
COLORREF nPenColor = PALETTERGB( nColor.GetRed(),
nColor.GetGreen(),
nColor.GetBlue() );
HPEN hNewPen = nullptr;
bool bStockPen = FALSE;
// search for stock pen (only screen, because printer have problems,
// when we use stock objects)
if ( !mrParent.isPrinter() )
{
SalData* pSalData = GetSalData();
for ( sal_uInt16 i = 0; i < pSalData->mnStockPenCount; i++ )
{
if ( nPenColor == pSalData->maStockPenColorAry[i] )
{
hNewPen = pSalData->mhStockPenAry[i];
bStockPen = TRUE;
break;
}
}
}
// create new pen
if ( !hNewPen )
{
if ( !mrParent.isPrinter() )
{
if ( GetSalData()->mhDitherPal && ImplIsSysColorEntry( nColor ) )
nPenColor = PALRGB_TO_RGB( nPenColor );
}
hNewPen = CreatePen( PS_SOLID, mrParent.mnPenWidth, nPenColor );
bStockPen = FALSE;
}
// select new pen
HPEN hOldPen = SelectPen( mrParent.getHDC(), hNewPen );
// destroy or save old pen
if ( mhPen )
{
if ( !mbStockPen )
DeletePen( mhPen );
}
else
mrParent.mhDefPen = hOldPen;
// set new data
mnPenColor = nPenColor;
mhPen = hNewPen;
mbPen = TRUE;
mbStockPen = bStockPen;
}
void WinSalGraphicsImpl::SetFillColor()
{
// create and select new brush
HBRUSH hNewBrush = GetStockBrush( NULL_BRUSH );
HBRUSH hOldBrush = SelectBrush( mrParent.getHDC(), hNewBrush );
// destroy or save old brush
if ( mhBrush )
{
if ( !mbStockBrush )
DeleteBrush( mhBrush );
}
else
mrParent.mhDefBrush = hOldBrush;
// set new data
mhBrush = hNewBrush;
mbBrush = FALSE;
mbStockBrush = TRUE;
}
void WinSalGraphicsImpl::SetFillColor( Color nColor )
{
maFillColor = nColor;
SalData* pSalData = GetSalData();
BYTE nRed = nColor.GetRed();
BYTE nGreen = nColor.GetGreen();
BYTE nBlue = nColor.GetBlue();
COLORREF nBrushColor = PALETTERGB( nRed, nGreen, nBlue );
HBRUSH hNewBrush = nullptr;
bool bStockBrush = FALSE;
// search for stock brush (only screen, because printer have problems,
// when we use stock objects)
if ( !mrParent.isPrinter() )
{
for ( sal_uInt16 i = 0; i < pSalData->mnStockBrushCount; i++ )
{
if ( nBrushColor == pSalData->maStockBrushColorAry[ i ] )
{
hNewBrush = pSalData->mhStockBrushAry[i];
bStockBrush = TRUE;
break;
}
}
}
// create new brush
if ( !hNewBrush )
{
if ( mrParent.isPrinter() || !pSalData->mhDitherDIB )
hNewBrush = CreateSolidBrush( nBrushColor );
else
{
if ( 24 == reinterpret_cast<BITMAPINFOHEADER*>(pSalData->mpDitherDIB)->biBitCount )
{
BYTE* pTmp = pSalData->mpDitherDIBData;
long* pDitherDiff = pSalData->mpDitherDiff;
BYTE* pDitherLow = pSalData->mpDitherLow;
BYTE* pDitherHigh = pSalData->mpDitherHigh;
for( long nY = 0L; nY < 8L; nY++ )
{
for( long nX = 0L; nX < 8L; nX++ )
{
const long nThres = aOrdDither16Bit[ nY ][ nX ];
*pTmp++ = DMAP( nBlue, nThres );
*pTmp++ = DMAP( nGreen, nThres );
*pTmp++ = DMAP( nRed, nThres );
}
}
hNewBrush = CreateDIBPatternBrush( pSalData->mhDitherDIB, DIB_RGB_COLORS );
}
else if ( ImplIsSysColorEntry( nColor ) )
{
nBrushColor = PALRGB_TO_RGB( nBrushColor );
hNewBrush = CreateSolidBrush( nBrushColor );
}
else if ( ImplIsPaletteEntry( nRed, nGreen, nBlue ) )
hNewBrush = CreateSolidBrush( nBrushColor );
else
{
BYTE* pTmp = pSalData->mpDitherDIBData;
long* pDitherDiff = pSalData->mpDitherDiff;
BYTE* pDitherLow = pSalData->mpDitherLow;
BYTE* pDitherHigh = pSalData->mpDitherHigh;
for ( long nY = 0L; nY < 8L; nY++ )
{
for ( long nX = 0L; nX < 8L; nX++ )
{
const long nThres = aOrdDither8Bit[ nY ][ nX ];
*pTmp = DMAP( nRed, nThres ) + DMAP( nGreen, nThres ) * 6 + DMAP( nBlue, nThres ) * 36;
pTmp++;
}
}
hNewBrush = CreateDIBPatternBrush( pSalData->mhDitherDIB, DIB_PAL_COLORS );
}
}
bStockBrush = FALSE;
}
// select new brush
HBRUSH hOldBrush = SelectBrush( mrParent.getHDC(), hNewBrush );
// destroy or save old brush
if ( mhBrush )
{
if ( !mbStockBrush )
DeleteBrush( mhBrush );
}
else
mrParent.mhDefBrush = hOldBrush;
// set new data
mnBrushColor = nBrushColor;
mhBrush = hNewBrush;
mbBrush = TRUE;
mbStockBrush = bStockBrush;
}
void WinSalGraphicsImpl::SetXORMode( bool bSet)
{
mbXORMode = bSet;
::SetROP2( mrParent.getHDC(), bSet ? R2_XORPEN : R2_COPYPEN );
}
void WinSalGraphicsImpl::SetROPLineColor( SalROPColor nROPColor )
{
SetLineColor( ImplGetROPColor( nROPColor ) );
}
void WinSalGraphicsImpl::SetROPFillColor( SalROPColor nROPColor )
{
SetFillColor( ImplGetROPColor( nROPColor ) );
}
void WinSalGraphicsImpl::drawPixelImpl( long nX, long nY, COLORREF crColor )
{
if ( mbXORMode )
{
HBRUSH hBrush = CreateSolidBrush( crColor );
HBRUSH hOldBrush = SelectBrush( mrParent.getHDC(), hBrush );
PatBlt( mrParent.getHDC(), static_cast<int>(nX), static_cast<int>(nY), int(1), int(1), PATINVERT );
SelectBrush( mrParent.getHDC(), hOldBrush );
DeleteBrush( hBrush );
}
else
SetPixel( mrParent.getHDC(), static_cast<int>(nX), static_cast<int>(nY), crColor );
}
void WinSalGraphicsImpl::drawPixel( long nX, long nY )
{
drawPixelImpl( nX, nY, mnPenColor );
}
void WinSalGraphicsImpl::drawPixel( long nX, long nY, Color nColor )
{
COLORREF nCol = PALETTERGB( nColor.GetRed(),
nColor.GetGreen(),
nColor.GetBlue() );
if ( !mrParent.isPrinter() &&
GetSalData()->mhDitherPal &&
ImplIsSysColorEntry( nColor ) )
nCol = PALRGB_TO_RGB( nCol );
drawPixelImpl( nX, nY, nCol );
}
void WinSalGraphicsImpl::drawLine( long nX1, long nY1, long nX2, long nY2 )
{
MoveToEx( mrParent.getHDC(), static_cast<int>(nX1), static_cast<int>(nY1), nullptr );
LineTo( mrParent.getHDC(), static_cast<int>(nX2), static_cast<int>(nY2) );
// LineTo doesn't draw the last pixel
if ( !mrParent.isPrinter() )
drawPixelImpl( nX2, nY2, mnPenColor );
}
void WinSalGraphicsImpl::drawRect( long nX, long nY, long nWidth, long nHeight )
{
if ( !mbPen )
{
if ( !mrParent.isPrinter() )
{
PatBlt( mrParent.getHDC(), static_cast<int>(nX), static_cast<int>(nY), static_cast<int>(nWidth), static_cast<int>(nHeight),
mbXORMode ? PATINVERT : PATCOPY );
}
else
{
RECT aWinRect;
aWinRect.left = nX;
aWinRect.top = nY;
aWinRect.right = nX+nWidth;
aWinRect.bottom = nY+nHeight;
::FillRect( mrParent.getHDC(), &aWinRect, mhBrush );
}
}
else
Rectangle( mrParent.getHDC(), static_cast<int>(nX), static_cast<int>(nY), static_cast<int>(nX+nWidth), static_cast<int>(nY+nHeight) );
}
void WinSalGraphicsImpl::drawPolyLine( sal_uInt32 nPoints, const SalPoint* pPtAry )
{
// for NT, we can handover the array directly
static_assert( sizeof( POINT ) == sizeof( SalPoint ), "must be the same size" );
POINT const * pWinPtAry = reinterpret_cast<POINT const *>(pPtAry);
// for Windows 95 and its maximum number of points
if ( !Polyline( mrParent.getHDC(), pWinPtAry, static_cast<int>(nPoints) ) && (nPoints > MAX_64KSALPOINTS) )
Polyline( mrParent.getHDC(), pWinPtAry, MAX_64KSALPOINTS );
// Polyline seems to uses LineTo, which doesn't paint the last pixel (see 87eb8f8ee)
if ( !mrParent.isPrinter() )
drawPixelImpl( pWinPtAry[nPoints-1].x, pWinPtAry[nPoints-1].y, mnPenColor );
}
void WinSalGraphicsImpl::drawPolygon( sal_uInt32 nPoints, const SalPoint* pPtAry )
{
// for NT, we can handover the array directly
static_assert( sizeof( POINT ) == sizeof( SalPoint ), "must be the same size" );
POINT const * pWinPtAry = reinterpret_cast<POINT const *>(pPtAry);
// for Windows 95 and its maximum number of points
if ( !Polygon( mrParent.getHDC(), pWinPtAry, static_cast<int>(nPoints) ) && (nPoints > MAX_64KSALPOINTS) )
Polygon( mrParent.getHDC(), pWinPtAry, MAX_64KSALPOINTS );
}
void WinSalGraphicsImpl::drawPolyPolygon( sal_uInt32 nPoly, const sal_uInt32* pPoints,
PCONSTSALPOINT* pPtAry )
{
UINT aWinPointAry[SAL_POLYPOLYCOUNT_STACKBUF];
UINT* pWinPointAry;
UINT nPolyPolyPoints = 0;
UINT nPoints;
UINT i;
if ( nPoly <= SAL_POLYPOLYCOUNT_STACKBUF )
pWinPointAry = aWinPointAry;
else
pWinPointAry = new UINT[nPoly];
for ( i = 0; i < static_cast<UINT>(nPoly); i++ )
{
nPoints = static_cast<UINT>(pPoints[i])+1;
pWinPointAry[i] = nPoints;
nPolyPolyPoints += nPoints;
}
POINT aWinPointAryAry[SAL_POLYPOLYPOINTS_STACKBUF];
POINT* pWinPointAryAry;
if ( nPolyPolyPoints <= SAL_POLYPOLYPOINTS_STACKBUF )
pWinPointAryAry = aWinPointAryAry;
else
pWinPointAryAry = new POINT[nPolyPolyPoints];
// for NT, we can handover the array directly
static_assert( sizeof( POINT ) == sizeof( SalPoint ), "must be the same size" );
UINT n = 0;
for ( i = 0; i < static_cast<UINT>(nPoly); i++ )
{
nPoints = pWinPointAry[i];
const SalPoint* pPolyAry = pPtAry[i];
memcpy( pWinPointAryAry+n, pPolyAry, (nPoints-1)*sizeof(POINT) );
pWinPointAryAry[n+nPoints-1] = pWinPointAryAry[n];
n += nPoints;
}
if ( !PolyPolygon( mrParent.getHDC(), pWinPointAryAry, reinterpret_cast<int*>(pWinPointAry), static_cast<UINT>(nPoly) ) &&
(nPolyPolyPoints > MAX_64KSALPOINTS) )
{
nPolyPolyPoints = 0;
nPoly = 0;
do
{
nPolyPolyPoints += pWinPointAry[static_cast<UINT>(nPoly)];
nPoly++;
}
while ( nPolyPolyPoints < MAX_64KSALPOINTS );
nPoly--;
if ( pWinPointAry[static_cast<UINT>(nPoly)] > MAX_64KSALPOINTS )
pWinPointAry[static_cast<UINT>(nPoly)] = MAX_64KSALPOINTS;
if ( nPoly == 1 )
Polygon( mrParent.getHDC(), pWinPointAryAry, *pWinPointAry );
else
PolyPolygon( mrParent.getHDC(), pWinPointAryAry, reinterpret_cast<int*>(pWinPointAry), nPoly );
}
if ( pWinPointAry != aWinPointAry )
delete [] pWinPointAry;
if ( pWinPointAryAry != aWinPointAryAry )
delete [] pWinPointAryAry;
}
bool WinSalGraphicsImpl::drawPolyLineBezier( sal_uInt32 nPoints, const SalPoint* pPtAry, const PolyFlags* pFlgAry )
{
static_assert( sizeof( POINT ) == sizeof( SalPoint ), "must be the same size" );
ImplRenderPath( mrParent.getHDC(), nPoints, pPtAry, pFlgAry );
return true;
}
bool WinSalGraphicsImpl::drawPolygonBezier( sal_uInt32 nPoints, const SalPoint* pPtAry, const PolyFlags* pFlgAry )
{
static_assert( sizeof( POINT ) == sizeof( SalPoint ), "must be the same size" );
POINT aStackAry1[SAL_POLY_STACKBUF];
BYTE aStackAry2[SAL_POLY_STACKBUF];
POINT* pWinPointAry;
BYTE* pWinFlagAry;
if( nPoints > SAL_POLY_STACKBUF )
{
pWinPointAry = new POINT[ nPoints ];
pWinFlagAry = new BYTE[ nPoints ];
}
else
{
pWinPointAry = aStackAry1;
pWinFlagAry = aStackAry2;
}
sal_uInt32 nPoints_i32(nPoints);
ImplPreparePolyDraw(true, 1, &nPoints_i32, &pPtAry, &pFlgAry, pWinPointAry, pWinFlagAry);
bool bRet( false );
if( BeginPath( mrParent.getHDC() ) )
{
PolyDraw(mrParent.getHDC(), pWinPointAry, pWinFlagAry, nPoints);
if( EndPath( mrParent.getHDC() ) )
{
if( StrokeAndFillPath( mrParent.getHDC() ) )
bRet = true;
}
}
if( pWinPointAry != aStackAry1 )
{
delete [] pWinPointAry;
delete [] pWinFlagAry;
}
return bRet;
}
bool WinSalGraphicsImpl::drawPolyPolygonBezier( sal_uInt32 nPoly, const sal_uInt32* pPoints,
const SalPoint* const* pPtAry, const PolyFlags* const* pFlgAry )
{
static_assert( sizeof( POINT ) == sizeof( SalPoint ), "must be the same size" );
sal_uLong nCurrPoly, nTotalPoints;
const sal_uInt32* pCurrPoints = pPoints;
for( nCurrPoly=0, nTotalPoints=0; nCurrPoly<nPoly; ++nCurrPoly )
nTotalPoints += *pCurrPoints++;
POINT aStackAry1[SAL_POLY_STACKBUF];
BYTE aStackAry2[SAL_POLY_STACKBUF];
POINT* pWinPointAry;
BYTE* pWinFlagAry;
if( nTotalPoints > SAL_POLY_STACKBUF )
{
pWinPointAry = new POINT[ nTotalPoints ];
pWinFlagAry = new BYTE[ nTotalPoints ];
}
else
{
pWinPointAry = aStackAry1;
pWinFlagAry = aStackAry2;
}
ImplPreparePolyDraw(true, nPoly, pPoints, pPtAry, pFlgAry, pWinPointAry, pWinFlagAry);
bool bRet( false );
if( BeginPath( mrParent.getHDC() ) )
{
PolyDraw(mrParent.getHDC(), pWinPointAry, pWinFlagAry, nTotalPoints);
if( EndPath( mrParent.getHDC() ) )
{
if( StrokeAndFillPath( mrParent.getHDC() ) )
bRet = true;
}
}
if( pWinPointAry != aStackAry1 )
{
delete [] pWinPointAry;
delete [] pWinFlagAry;
}
return bRet;
}
basegfx::B2DPoint impPixelSnap(
const basegfx::B2DPolygon& rPolygon,
const basegfx::B2DHomMatrix& rObjectToDevice,
basegfx::B2DHomMatrix& rObjectToDeviceInv,
sal_uInt32 nIndex)
{
const sal_uInt32 nCount(rPolygon.count());
// get the data
const basegfx::B2ITuple aPrevTuple(basegfx::fround(rObjectToDevice * rPolygon.getB2DPoint((nIndex + nCount - 1) % nCount)));
const basegfx::B2DPoint aCurrPoint(rObjectToDevice * rPolygon.getB2DPoint(nIndex));
const basegfx::B2ITuple aCurrTuple(basegfx::fround(aCurrPoint));
const basegfx::B2ITuple aNextTuple(basegfx::fround(rObjectToDevice * rPolygon.getB2DPoint((nIndex + 1) % nCount)));
// get the states
const bool bPrevVertical(aPrevTuple.getX() == aCurrTuple.getX());
const bool bNextVertical(aNextTuple.getX() == aCurrTuple.getX());
const bool bPrevHorizontal(aPrevTuple.getY() == aCurrTuple.getY());
const bool bNextHorizontal(aNextTuple.getY() == aCurrTuple.getY());
const bool bSnapX(bPrevVertical || bNextVertical);
const bool bSnapY(bPrevHorizontal || bNextHorizontal);
if(bSnapX || bSnapY)
{
basegfx::B2DPoint aSnappedPoint(
bSnapX ? aCurrTuple.getX() : aCurrPoint.getX(),
bSnapY ? aCurrTuple.getY() : aCurrPoint.getY());
if(rObjectToDeviceInv.isIdentity())
{
rObjectToDeviceInv = rObjectToDevice;
rObjectToDeviceInv.invert();
}
aSnappedPoint *= rObjectToDeviceInv;
return aSnappedPoint;
}
return rPolygon.getB2DPoint(nIndex);
}
void impAddB2DPolygonToGDIPlusGraphicsPathReal(
Gdiplus::GraphicsPath& rGraphicsPath,
const basegfx::B2DPolygon& rPolygon,
const basegfx::B2DHomMatrix& rObjectToDevice,
bool bNoLineJoin,
bool bPixelSnapHairline)
{
sal_uInt32 nCount(rPolygon.count());
if(nCount)
{
const sal_uInt32 nEdgeCount(rPolygon.isClosed() ? nCount : nCount - 1);
if(nEdgeCount)
{
const bool bControls(rPolygon.areControlPointsUsed());
basegfx::B2DPoint aCurr(rPolygon.getB2DPoint(0));
basegfx::B2DHomMatrix aObjectToDeviceInv;
if(bPixelSnapHairline)
{
aCurr = impPixelSnap(rPolygon, rObjectToDevice, aObjectToDeviceInv, 0);
}
for(sal_uInt32 a(0); a < nEdgeCount; a++)
{
const sal_uInt32 nNextIndex((a + 1) % nCount);
basegfx::B2DPoint aNext(rPolygon.getB2DPoint(nNextIndex));
const bool b1stControlPointUsed(bControls && rPolygon.isNextControlPointUsed(a));
const bool b2ndControlPointUsed(bControls && rPolygon.isPrevControlPointUsed(nNextIndex));
if(bPixelSnapHairline)
{
aNext = impPixelSnap(rPolygon, rObjectToDevice, aObjectToDeviceInv, nNextIndex);
}
if(b1stControlPointUsed || b2ndControlPointUsed)
{
basegfx::B2DPoint aCa(rPolygon.getNextControlPoint(a));
basegfx::B2DPoint aCb(rPolygon.getPrevControlPoint(nNextIndex));
// tdf#99165 MS Gdiplus cannot handle creating correct extra geometry for fat lines
// with LineCap or LineJoin when a bezier segment starts or ends trivial, e.g. has
// no 1st or 2nd control point, despite that these are mathematicaly correct definitions
// (basegfx can handle that).
// Caution: This error (and it's correction) might be necessary for other graphical
// sub-systems in a similar way.
// tdf#101026 The 1st attempt to create a mathematically correct replacement control
// vector was wrong. Best alternative is one as close as possible which means short.
if(!b1stControlPointUsed)
{
aCa = aCurr + ((aCb - aCurr) * 0.0005);
}
else if(!b2ndControlPointUsed)
{
aCb = aNext + ((aCa - aNext) * 0.0005);
}
rGraphicsPath.AddBezier(
static_cast< Gdiplus::REAL >(aCurr.getX()), static_cast< Gdiplus::REAL >(aCurr.getY()),
static_cast< Gdiplus::REAL >(aCa.getX()), static_cast< Gdiplus::REAL >(aCa.getY()),
static_cast< Gdiplus::REAL >(aCb.getX()), static_cast< Gdiplus::REAL >(aCb.getY()),
static_cast< Gdiplus::REAL >(aNext.getX()), static_cast< Gdiplus::REAL >(aNext.getY()));
}
else
{
rGraphicsPath.AddLine(
static_cast< Gdiplus::REAL >(aCurr.getX()), static_cast< Gdiplus::REAL >(aCurr.getY()),
static_cast< Gdiplus::REAL >(aNext.getX()), static_cast< Gdiplus::REAL >(aNext.getY()));
}
if(a + 1 < nEdgeCount)
{
aCurr = aNext;
if(bNoLineJoin)
{
rGraphicsPath.StartFigure();
}
}
}
}
}
}
class SystemDependentData_GraphicsPath : public basegfx::SystemDependentData
{
private:
Gdiplus::GraphicsPath maGraphicsPath;
bool mbPixelSnapHairline;
public:
SystemDependentData_GraphicsPath(
basegfx::SystemDependentDataManager& rSystemDependentDataManager);
Gdiplus::GraphicsPath& getGraphicsPath() { return maGraphicsPath; }
bool getPixelSnapHairline() const { return mbPixelSnapHairline; }
void setPixelSnapHairline(bool bNew) { mbPixelSnapHairline = bNew; }
};
SystemDependentData_GraphicsPath::SystemDependentData_GraphicsPath(
basegfx::SystemDependentDataManager& rSystemDependentDataManager)
: basegfx::SystemDependentData(rSystemDependentDataManager),
maGraphicsPath(),
mbPixelSnapHairline(false)
{
}
bool WinSalGraphicsImpl::drawPolyPolygon(
const basegfx::B2DHomMatrix& rObjectToDevice,
const basegfx::B2DPolyPolygon& rPolyPolygon,
double fTransparency)
{
const sal_uInt32 nCount(rPolyPolygon.count());
if(!mbBrush || 0 == nCount || fTransparency < 0.0 || fTransparency > 1.0)
{
return true;
}
Gdiplus::Graphics aGraphics(mrParent.getHDC());
const sal_uInt8 aTrans(sal_uInt8(255) - static_cast<sal_uInt8>(basegfx::fround(fTransparency * 255.0)));
const Gdiplus::Color aTestColor(aTrans, maFillColor.GetRed(), maFillColor.GetGreen(), maFillColor.GetBlue());
const Gdiplus::SolidBrush aSolidBrush(aTestColor.GetValue());
// Set full (Object-to-Device) transformation - if used
if(rObjectToDevice.isIdentity())
{
aGraphics.ResetTransform();
}
else
{
Gdiplus::Matrix aMatrix;
aMatrix.SetElements(
rObjectToDevice.get(0, 0),
rObjectToDevice.get(1, 0),
rObjectToDevice.get(0, 1),
rObjectToDevice.get(1, 1),
rObjectToDevice.get(0, 2),
rObjectToDevice.get(1, 2));
aGraphics.SetTransform(&aMatrix);
}
// try to access buffered data
std::shared_ptr<SystemDependentData_GraphicsPath> pSystemDependentData_GraphicsPath(
rPolyPolygon.getSystemDependentData<SystemDependentData_GraphicsPath>());
if(!pSystemDependentData_GraphicsPath)
{
// add to buffering mechanism
pSystemDependentData_GraphicsPath = rPolyPolygon.addOrReplaceSystemDependentData<SystemDependentData_GraphicsPath>(
ImplGetSystemDependentDataManager());
// Note: In principle we could use the same buffered geometry at line
// and fill polygons. Checked that in a first try, used
// GraphicsPath::AddPath from Gdiplus combined with below used
// StartFigure/CloseFigure, worked well (thus the line-draw version
// may create non-cloded partial Polygon data).
//
// But in current reality it gets not used due to e.g.
// SdrPathPrimitive2D::create2DDecomposition creating transformed
// line and fill polygon-primitives (what could be changed).
//
// There will probably be more hindrances here in other rendering paths
// which could all be found - intention to do this would be: Use more
// transformations, less modifications of B2DPolygons/B2DPolyPolygons.
//
// A fix for SdrPathPrimitive2D would be to create the sub-geometry
// and embed into a TransformPrimitive2D containing the transformation.
//
// A 2nd problem is that the NoLineJoin mode (basegfx::B2DLineJoin::NONE
// && rLineWidths > 0.0) creates polygon fill infos that are not reusable
// for the fill case (see ::drawPolyLine bnelow) - thus we would need a
// bool and/or two system-dependent paths buffered - doable, but complicated.
//
// All in all: Make B2DPolyPolygon a SystemDependentDataProvider and buffer
// the whole to-be-filled PolyPolygon independent from evtl. line-polygon
// (at least for now...)
// create data
for(sal_uInt32 a(0); a < nCount; a++)
{
if(0 != a)
{
// #i101491# not needed for first run
pSystemDependentData_GraphicsPath->getGraphicsPath().StartFigure();
}
impAddB2DPolygonToGDIPlusGraphicsPathReal(
pSystemDependentData_GraphicsPath->getGraphicsPath(),
rPolyPolygon.getB2DPolygon(a),
rObjectToDevice, // not used due to the two 'false' values below, but to not forget later
false,
false);
pSystemDependentData_GraphicsPath->getGraphicsPath().CloseFigure();
}
}
if(mrParent.getAntiAliasB2DDraw())
{
aGraphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
}
else
{
aGraphics.SetSmoothingMode(Gdiplus::SmoothingModeNone);
}
if(mrParent.isPrinter())
{
// #i121591#
// Normally GdiPlus should not be used for printing at all since printers cannot
// print transparent filled polygon geometry and normally this does not happen
// since OutputDevice::RemoveTransparenciesFromMetaFile is used as preparation
// and no transparent parts should remain for printing. But this can be overridden
// by the user and thus happens. This call can only come (currently) from
// OutputDevice::DrawTransparent, see comments there with the same TaskID.
// If it is used, the mapping for the printer is wrong and needs to be corrected. I
// checked that there is *no* transformation set and estimated that a stable factor
// dependent of the printer's DPI is used. Create and set a transformation here to
// correct this.
const Gdiplus::REAL aDpiX(aGraphics.GetDpiX());
const Gdiplus::REAL aDpiY(aGraphics.GetDpiY());
// Now the transformation maybe/is already used (see above), so do
// modify it without resetting to not destroy it.
// I double-checked with MS docu that Gdiplus::MatrixOrderAppend does what
// we need - in our notation, would be a multiply from left to execute
// current transform first and this scale last.
// I tried to trigger this code using Print from the menu and various
// targets, but got no hit, thus maybe obsolete anyways. If someone knows
// more, feel free to remove it.
// One more hint: This *may* also be needed now in ::drawPolyLine below
// since it also uses transformations now.
//
// aGraphics.ResetTransform();
aGraphics.ScaleTransform(
Gdiplus::REAL(100.0) / aDpiX,
Gdiplus::REAL(100.0) / aDpiY,
Gdiplus::MatrixOrderAppend);
}
// use created or buffered data
aGraphics.FillPath(
&aSolidBrush,
&pSystemDependentData_GraphicsPath->getGraphicsPath());
return true;
}
bool WinSalGraphicsImpl::drawPolyLine(
const basegfx::B2DHomMatrix& rObjectToDevice,
const basegfx::B2DPolygon& rPolygon,
double fTransparency,
const basegfx::B2DVector& rLineWidths,
basegfx::B2DLineJoin eLineJoin,
css::drawing::LineCap eLineCap,
double fMiterMinimumAngle,
bool bPixelSnapHairline)
{
if(!mbPen || 0 == rPolygon.count())
{
return true;
}
Gdiplus::Graphics aGraphics(mrParent.getHDC());
const sal_uInt8 aTrans = static_cast<sal_uInt8>(basegfx::fround( 255 * (1.0 - fTransparency) ));
const Gdiplus::Color aTestColor(aTrans, maLineColor.GetRed(), maLineColor.GetGreen(), maLineColor.GetBlue());
Gdiplus::Pen aPen(aTestColor.GetValue(), Gdiplus::REAL(rLineWidths.getX()));
bool bNoLineJoin(false);
Gdiplus::Matrix aMatrix;
// Set full (Object-to-Device) transformation - if used
if(rObjectToDevice.isIdentity())
{
aGraphics.ResetTransform();
}
else
{
Gdiplus::Matrix aMatrix;
aMatrix.SetElements(
rObjectToDevice.get(0, 0),
rObjectToDevice.get(1, 0),
rObjectToDevice.get(0, 1),
rObjectToDevice.get(1, 1),
rObjectToDevice.get(0, 2),
rObjectToDevice.get(1, 2));
aGraphics.SetTransform(&aMatrix);
}
switch(eLineJoin)
{
case basegfx::B2DLineJoin::NONE :
{
if(basegfx::fTools::more(rLineWidths.getX(), 0.0))
{
bNoLineJoin = true;
}
break;
}
case basegfx::B2DLineJoin::Bevel :
{
aPen.SetLineJoin(Gdiplus::LineJoinBevel);
break;
}
case basegfx::B2DLineJoin::Miter :
{
const Gdiplus::REAL aMiterLimit(1.0/sin(fMiterMinimumAngle/2.0));
aPen.SetMiterLimit(aMiterLimit);
// tdf#99165 MS's LineJoinMiter creates non standard conform miter additional
// graphics, somewhere clipped in some distance from the edge point, dependent
// of MiterLimit. The more default-like option is LineJoinMiterClipped, so use
// that instead
aPen.SetLineJoin(Gdiplus::LineJoinMiterClipped);
break;
}
case basegfx::B2DLineJoin::Round :
{
aPen.SetLineJoin(Gdiplus::LineJoinRound);
break;
}
}
switch(eLineCap)
{
default: /*css::drawing::LineCap_BUTT*/
{
// nothing to do
break;
}
case css::drawing::LineCap_ROUND:
{
aPen.SetStartCap(Gdiplus::LineCapRound);
aPen.SetEndCap(Gdiplus::LineCapRound);
break;
}
case css::drawing::LineCap_SQUARE:
{
aPen.SetStartCap(Gdiplus::LineCapSquare);
aPen.SetEndCap(Gdiplus::LineCapSquare);
break;
}
}
// try to access buffered data
std::shared_ptr<SystemDependentData_GraphicsPath> pSystemDependentData_GraphicsPath(
rPolygon.getSystemDependentData<SystemDependentData_GraphicsPath>());
if(pSystemDependentData_GraphicsPath)
{
// check data validity
if(pSystemDependentData_GraphicsPath->getPixelSnapHairline() != bPixelSnapHairline)
{
// data invalid, forget
pSystemDependentData_GraphicsPath.reset();
}
}
if(!pSystemDependentData_GraphicsPath)
{
// add to buffering mechanism
pSystemDependentData_GraphicsPath = rPolygon.addOrReplaceSystemDependentData<SystemDependentData_GraphicsPath>(
ImplGetSystemDependentDataManager());
// fill data of buffered data
pSystemDependentData_GraphicsPath->setPixelSnapHairline(bPixelSnapHairline);
impAddB2DPolygonToGDIPlusGraphicsPathReal(
pSystemDependentData_GraphicsPath->getGraphicsPath(),
rPolygon,
rObjectToDevice,
bNoLineJoin,
bPixelSnapHairline);
if(rPolygon.isClosed() && !bNoLineJoin)
{
// #i101491# needed to create the correct line joins
pSystemDependentData_GraphicsPath->getGraphicsPath().CloseFigure();
}
}
if(mrParent.getAntiAliasB2DDraw())
{
aGraphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
}
else
{
aGraphics.SetSmoothingMode(Gdiplus::SmoothingModeNone);
}
aGraphics.DrawPath(
&aPen,
&pSystemDependentData_GraphicsPath->getGraphicsPath());
return true;
}
void paintToGdiPlus(
Gdiplus::Graphics& rGraphics,
const SalTwoRect& rTR,
Gdiplus::Bitmap& rBitmap)
{
// only parts of source are used
Gdiplus::PointF aDestPoints[3];
Gdiplus::ImageAttributes aAttributes;
// define target region as paralellogram
aDestPoints[0].X = Gdiplus::REAL(rTR.mnDestX);
aDestPoints[0].Y = Gdiplus::REAL(rTR.mnDestY);
aDestPoints[1].X = Gdiplus::REAL(rTR.mnDestX + rTR.mnDestWidth);
aDestPoints[1].Y = Gdiplus::REAL(rTR.mnDestY);
aDestPoints[2].X = Gdiplus::REAL(rTR.mnDestX);
aDestPoints[2].Y = Gdiplus::REAL(rTR.mnDestY + rTR.mnDestHeight);
aAttributes.SetWrapMode(Gdiplus::WrapModeTileFlipXY);
rGraphics.DrawImage(
&rBitmap,
aDestPoints,
3,
Gdiplus::REAL(rTR.mnSrcX),
Gdiplus::REAL(rTR.mnSrcY),
Gdiplus::REAL(rTR.mnSrcWidth),
Gdiplus::REAL(rTR.mnSrcHeight),
Gdiplus::UnitPixel,
&aAttributes);
}
void setInterpolationMode(
Gdiplus::Graphics& rGraphics,
long rSrcWidth,
long rDestWidth,
long rSrcHeight,
long rDestHeight)
{
const bool bSameWidth(rSrcWidth == rDestWidth);
const bool bSameHeight(rSrcHeight == rDestHeight);
if(bSameWidth && bSameHeight)
{
rGraphics.SetInterpolationMode(Gdiplus::InterpolationModeInvalid);
}
else if(rDestWidth > rSrcWidth && rDestHeight > rSrcHeight)
{
rGraphics.SetInterpolationMode(Gdiplus::InterpolationModeDefault);
}
else if(rDestWidth < rSrcWidth && rDestHeight < rSrcHeight)
{
rGraphics.SetInterpolationMode(Gdiplus::InterpolationModeBicubic);
}
else
{
rGraphics.SetInterpolationMode(Gdiplus::InterpolationModeDefault);
}
}
bool WinSalGraphicsImpl::tryDrawBitmapGdiPlus(const SalTwoRect& rTR, const SalBitmap& rSrcBitmap)
{
if(rTR.mnSrcWidth && rTR.mnSrcHeight && rTR.mnDestWidth && rTR.mnDestHeight)
{
assert(dynamic_cast<const WinSalBitmap*>(&rSrcBitmap));
const WinSalBitmap& rSalBitmap = static_cast< const WinSalBitmap& >(rSrcBitmap);
std::shared_ptr< Gdiplus::Bitmap > aARGB(rSalBitmap.ImplGetGdiPlusBitmap());
if(aARGB.get())
{
Gdiplus::Graphics aGraphics(mrParent.getHDC());
setInterpolationMode(
aGraphics,
rTR.mnSrcWidth,
rTR.mnDestWidth,
rTR.mnSrcHeight,
rTR.mnDestHeight);
paintToGdiPlus(
aGraphics,
rTR,
*aARGB.get());
return true;
}
}
return false;
}
bool WinSalGraphicsImpl::blendBitmap(
const SalTwoRect&,
const SalBitmap&)
{
return false;
}
bool WinSalGraphicsImpl::blendAlphaBitmap(
const SalTwoRect&,
const SalBitmap&,
const SalBitmap&,
const SalBitmap&)
{
return false;
}
bool WinSalGraphicsImpl::drawAlphaBitmap(
const SalTwoRect& rTR,
const SalBitmap& rSrcBitmap,
const SalBitmap& rAlphaBmp)
{
if(rTR.mnSrcWidth && rTR.mnSrcHeight && rTR.mnDestWidth && rTR.mnDestHeight)
{
assert(dynamic_cast<const WinSalBitmap*>(&rSrcBitmap));
assert(dynamic_cast<const WinSalBitmap*>(&rAlphaBmp));
const WinSalBitmap& rSalBitmap = static_cast< const WinSalBitmap& >(rSrcBitmap);
const WinSalBitmap& rSalAlpha = static_cast< const WinSalBitmap& >(rAlphaBmp);
std::shared_ptr< Gdiplus::Bitmap > aARGB(rSalBitmap.ImplGetGdiPlusBitmap(&rSalAlpha));
if(aARGB.get())
{
Gdiplus::Graphics aGraphics(mrParent.getHDC());
setInterpolationMode(
aGraphics,
rTR.mnSrcWidth,
rTR.mnDestWidth,
rTR.mnSrcHeight,
rTR.mnDestHeight);
paintToGdiPlus(
aGraphics,
rTR,
*aARGB.get());
return true;
}
}
return false;
}
bool WinSalGraphicsImpl::drawTransformedBitmap(
const basegfx::B2DPoint& rNull,
const basegfx::B2DPoint& rX,
const basegfx::B2DPoint& rY,
const SalBitmap& rSourceBitmap,
const SalBitmap* pAlphaBitmap)
{
assert(dynamic_cast<const WinSalBitmap*>(&rSourceBitmap));
assert(!pAlphaBitmap || dynamic_cast<const WinSalBitmap*>(pAlphaBitmap));
const WinSalBitmap& rSalBitmap = static_cast< const WinSalBitmap& >(rSourceBitmap);
const WinSalBitmap* pSalAlpha = static_cast< const WinSalBitmap* >(pAlphaBitmap);
std::shared_ptr< Gdiplus::Bitmap > aARGB(rSalBitmap.ImplGetGdiPlusBitmap(pSalAlpha));
if(aARGB.get())
{
const long nSrcWidth(aARGB->GetWidth());
const long nSrcHeight(aARGB->GetHeight());
if(nSrcWidth && nSrcHeight)
{
const long nDestWidth(basegfx::fround(basegfx::B2DVector(rX - rNull).getLength()));
const long nDestHeight(basegfx::fround(basegfx::B2DVector(rY - rNull).getLength()));
if(nDestWidth && nDestHeight)
{
Gdiplus::Graphics aGraphics(mrParent.getHDC());
Gdiplus::PointF aDestPoints[3];
Gdiplus::ImageAttributes aAttributes;
setInterpolationMode(
aGraphics,
nSrcWidth,
nDestWidth,
nSrcHeight,
nDestHeight);
// this mode is only capable of drawing the whole bitmap to a paralellogram
aDestPoints[0].X = Gdiplus::REAL(rNull.getX());
aDestPoints[0].Y = Gdiplus::REAL(rNull.getY());
aDestPoints[1].X = Gdiplus::REAL(rX.getX());
aDestPoints[1].Y = Gdiplus::REAL(rX.getY());
aDestPoints[2].X = Gdiplus::REAL(rY.getX());
aDestPoints[2].Y = Gdiplus::REAL(rY.getY());
aAttributes.SetWrapMode(Gdiplus::WrapModeTileFlipXY);
aGraphics.DrawImage(
aARGB.get(),
aDestPoints,
3,
Gdiplus::REAL(0.0),
Gdiplus::REAL(0.0),
Gdiplus::REAL(nSrcWidth),
Gdiplus::REAL(nSrcHeight),
Gdiplus::UnitPixel,
&aAttributes);
}
}
return true;
}
return false;
}
bool WinSalGraphicsImpl::drawGradient(const tools::PolyPolygon& /*rPolygon*/,
const Gradient& /*rGradient*/)
{
return false;
}
bool WinSalGraphicsImpl::TryRenderCachedNativeControl(ControlCacheKey& /*rControlCacheKey*/, int /*nX*/, int /*nY*/)
{
return false;
}
bool WinSalGraphicsImpl::RenderAndCacheNativeControl(OpenGLCompatibleDC& /*rWhite*/, OpenGLCompatibleDC& /*rBlack*/,
int /*nX*/, int /*nY*/ , ControlCacheKey& /*aControlCacheKey*/)
{
return false;
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
↑ V773 Visibility scope of the 'pTmpSalBmp' pointer was exited without releasing the memory. A memory leak is possible.
↑ V730 Not all members of a class are initialized inside the constructor. Consider inspecting: mnPenColor, mnBrushColor.