1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
4 *
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
18 */
19
20 #include <sal/config.h>
21
22 #include <cstdlib>
23 #include <memory>
24 #include <numeric>
25
26 #include <svsys.h>
27
28 #include "gdiimpl.hxx"
29
30 #include <string.h>
31 #include <rtl/strbuf.hxx>
32 #include <sal/log.hxx>
33 #include <tools/poly.hxx>
34 #include <basegfx/polygon/b2dpolygon.hxx>
35 #include <basegfx/polygon/b2dpolygontools.hxx>
36 #include <basegfx/polygon/b2dpolypolygontools.hxx>
37 #include <comphelper/windowserrorstring.hxx>
38 #include <win/wincomp.hxx>
39 #include <win/saldata.hxx>
40 #include <win/salgdi.h>
41 #include <win/salbmp.h>
42 #include <win/scoped_gdi.hxx>
43 #include <vcl/BitmapAccessMode.hxx>
44 #include <vcl/BitmapBuffer.hxx>
45 #include <vcl/BitmapPalette.hxx>
46 #include <win/salframe.h>
47 #include <basegfx/matrix/b2dhommatrixtools.hxx>
48 #include <basegfx/utils/systemdependentdata.hxx>
49
50 #include <win/salids.hrc>
51 #include <ControlCacheKey.hxx>
52
53 #include <prewin.h>
54
55 #include <gdiplus.h>
56 #include <gdiplusenums.h>
57 #include <gdipluscolor.h>
58 #include <Gdipluspixelformats.h>
59
60 #include <postwin.h>
61
62 #define SAL_POLYPOLYCOUNT_STACKBUF 8
63 #define SAL_POLYPOLYPOINTS_STACKBUF 64
64
65 #define SAL_POLY_STACKBUF 32
66
67 namespace {
68
69 // #100127# Fill point and flag memory from array of points which
70 // might also contain bezier control points for the PolyDraw() GDI method
71 // Make sure pWinPointAry and pWinFlagAry are big enough
ImplPreparePolyDraw(sal_uLong nPoly,const sal_uInt32 * pPoints,const Point * const * pPtAry,const PolyFlags * const * pFlgAry,POINT * pWinPointAry,BYTE * pWinFlagAry)72 void ImplPreparePolyDraw( sal_uLong nPoly,
73 const sal_uInt32* pPoints,
74 const Point* const* pPtAry,
75 const PolyFlags* const* pFlgAry,
76 POINT* pWinPointAry,
77 BYTE* pWinFlagAry )
78 {
79 sal_uLong nCurrPoly;
80 for( nCurrPoly=0; nCurrPoly<nPoly; ++nCurrPoly )
81 {
82 const Point* pCurrPoint = *pPtAry++;
83 const PolyFlags* pCurrFlag = *pFlgAry++;
84 const sal_uInt32 nCurrPoints = *pPoints++;
85 const bool bHaveFlagArray( pCurrFlag );
86 sal_uLong nCurrPoint;
87
88 if( nCurrPoints )
89 {
90 // start figure
91 *pWinPointAry++ = POINT { static_cast<LONG>(pCurrPoint->getX()), static_cast<LONG>(pCurrPoint->getY()) };
92 pCurrPoint++;
93 *pWinFlagAry++ = PT_MOVETO;
94 ++pCurrFlag;
95
96 for( nCurrPoint=1; nCurrPoint<nCurrPoints; )
97 {
98 // #102067# Check existence of flag array
99 if( bHaveFlagArray &&
100 ( nCurrPoint + 2 ) < nCurrPoints )
101 {
102 PolyFlags P4( pCurrFlag[ 2 ] );
103
104 if( ( PolyFlags::Control == pCurrFlag[ 0 ] ) &&
105 ( PolyFlags::Control == pCurrFlag[ 1 ] ) &&
106 ( PolyFlags::Normal == P4 || PolyFlags::Smooth == P4 || PolyFlags::Symmetric == P4 ) )
107 {
108 // control point one
109 *pWinPointAry++ = POINT { static_cast<LONG>(pCurrPoint->getX()), static_cast<LONG>(pCurrPoint->getY()) };
110 pCurrPoint++;
111 *pWinFlagAry++ = PT_BEZIERTO;
112
113 // control point two
114 *pWinPointAry++ = POINT { static_cast<LONG>(pCurrPoint->getX()), static_cast<LONG>(pCurrPoint->getY()) };
115 pCurrPoint++;
116 *pWinFlagAry++ = PT_BEZIERTO;
117
118 // end point
119 *pWinPointAry++ = POINT { static_cast<LONG>(pCurrPoint->getX()), static_cast<LONG>(pCurrPoint->getY()) };
120 pCurrPoint++;
121 *pWinFlagAry++ = PT_BEZIERTO;
122
123 nCurrPoint += 3;
124 pCurrFlag += 3;
125 continue;
126 }
127 }
128
129 // regular line point
130 *pWinPointAry++ = POINT { static_cast<LONG>(pCurrPoint->getX()), static_cast<LONG>(pCurrPoint->getY()) };
131 pCurrPoint++;
132 *pWinFlagAry++ = PT_LINETO;
133 ++pCurrFlag;
134 ++nCurrPoint;
135 }
136
137 // end figure
138 pWinFlagAry[-1] |= PT_CLOSEFIGURE;
139 }
140 }
141 }
142
ImplGetROPColor(SalROPColor nROPColor)143 Color ImplGetROPColor( SalROPColor nROPColor )
144 {
145 Color nColor;
146 if ( nROPColor == SalROPColor::N0 )
147 nColor = Color( 0, 0, 0 );
148 else
149 nColor = Color( 255, 255, 255 );
150 return nColor;
151 }
152
153 } // namespace
154
WinSalGraphicsImpl(WinSalGraphics & rParent)155 WinSalGraphicsImpl::WinSalGraphicsImpl(WinSalGraphics& rParent):
156 mrParent(rParent),
157 mbXORMode(false),
158 mbPen(false),
159 mhPen(nullptr),
160 mbStockPen(false),
161 mbBrush(false),
162 mbStockBrush(false),
163 mhBrush(nullptr)
164 {
165 }
166
~WinSalGraphicsImpl()167 WinSalGraphicsImpl::~WinSalGraphicsImpl()
168 {
169 if ( mhPen )
170 {
171 if ( !mbStockPen )
172 DeletePen( mhPen );
173 }
174
175 if ( mhBrush )
176 {
177 if ( !mbStockBrush )
178 DeleteBrush( mhBrush );
179 }
180 }
181
copyBits(const SalTwoRect & rPosAry,SalGraphics * pSrcGraphics)182 void WinSalGraphicsImpl::copyBits( const SalTwoRect& rPosAry, SalGraphics* pSrcGraphics )
183 {
184 HDC hSrcDC;
185 DWORD nRop;
186
187 if ( pSrcGraphics )
188 hSrcDC = static_cast<WinSalGraphics*>(pSrcGraphics)->getHDC();
189 else
190 hSrcDC = mrParent.getHDC();
191
192 if ( mbXORMode )
193 nRop = SRCINVERT;
194 else
195 nRop = SRCCOPY;
196
197 if ( (rPosAry.mnSrcWidth == rPosAry.mnDestWidth) &&
198 (rPosAry.mnSrcHeight == rPosAry.mnDestHeight) )
199 {
200 BitBlt( mrParent.getHDC(),
201 static_cast<int>(rPosAry.mnDestX), static_cast<int>(rPosAry.mnDestY),
202 static_cast<int>(rPosAry.mnDestWidth), static_cast<int>(rPosAry.mnDestHeight),
203 hSrcDC,
204 static_cast<int>(rPosAry.mnSrcX), static_cast<int>(rPosAry.mnSrcY),
205 nRop );
206 }
207 else
208 {
209 int nOldStretchMode = SetStretchBltMode( mrParent.getHDC(), STRETCH_DELETESCANS );
210 StretchBlt( mrParent.getHDC(),
211 static_cast<int>(rPosAry.mnDestX), static_cast<int>(rPosAry.mnDestY),
212 static_cast<int>(rPosAry.mnDestWidth), static_cast<int>(rPosAry.mnDestHeight),
213 hSrcDC,
214 static_cast<int>(rPosAry.mnSrcX), static_cast<int>(rPosAry.mnSrcY),
215 static_cast<int>(rPosAry.mnSrcWidth), static_cast<int>(rPosAry.mnSrcHeight),
216 nRop );
217 SetStretchBltMode( mrParent.getHDC(), nOldStretchMode );
218 }
219 }
220
221 namespace
222 {
223
MakeInvisibleArea(const RECT & rSrcRect,int nLeft,int nTop,int nRight,int nBottom,HRGN & rhInvalidateRgn)224 void MakeInvisibleArea(const RECT& rSrcRect,
225 int nLeft, int nTop, int nRight, int nBottom,
226 HRGN& rhInvalidateRgn)
227 {
228 if (!rhInvalidateRgn)
229 {
230 rhInvalidateRgn = CreateRectRgnIndirect(&rSrcRect);
231 }
232
233 ScopedHRGN hTempRgn(CreateRectRgn(nLeft, nTop, nRight, nBottom));
234 CombineRgn(rhInvalidateRgn, rhInvalidateRgn, hTempRgn.get(), RGN_DIFF);
235 }
236
ImplCalcOutSideRgn(const RECT & rSrcRect,int nLeft,int nTop,int nRight,int nBottom,HRGN & rhInvalidateRgn)237 void ImplCalcOutSideRgn( const RECT& rSrcRect,
238 int nLeft, int nTop, int nRight, int nBottom,
239 HRGN& rhInvalidateRgn )
240 {
241 // calculate area outside the visible region
242 if (rSrcRect.left < nLeft)
243 {
244 MakeInvisibleArea(rSrcRect, -31999, 0, nLeft, 31999, rhInvalidateRgn);
245 }
246 if (rSrcRect.top < nTop)
247 {
248 MakeInvisibleArea(rSrcRect, 0, -31999, 31999, nTop, rhInvalidateRgn);
249 }
250 if (rSrcRect.right > nRight)
251 {
252 MakeInvisibleArea(rSrcRect, nRight, 0, 31999, 31999, rhInvalidateRgn);
253 }
254 if (rSrcRect.bottom > nBottom)
255 {
256 MakeInvisibleArea(rSrcRect, 0, nBottom, 31999, 31999, rhInvalidateRgn);
257 }
258 }
259
260 } // namespace
261
copyArea(tools::Long nDestX,tools::Long nDestY,tools::Long nSrcX,tools::Long nSrcY,tools::Long nSrcWidth,tools::Long nSrcHeight,bool bWindowInvalidate)262 void WinSalGraphicsImpl::copyArea( tools::Long nDestX, tools::Long nDestY,
263 tools::Long nSrcX, tools::Long nSrcY,
264 tools::Long nSrcWidth, tools::Long nSrcHeight,
265 bool bWindowInvalidate )
266 {
267 bool bRestoreClipRgn = false;
268 HRGN hOldClipRgn = nullptr;
269 int nOldClipRgnType = ERROR;
270 HRGN hInvalidateRgn = nullptr;
271
272 // do we have to invalidate also the overlapping regions?
273 if ( bWindowInvalidate && mrParent.isWindow() )
274 {
275 // compute and invalidate those parts that were either off-screen or covered by other windows
276 // while performing the above BitBlt
277 // those regions then have to be invalidated as they contain useless/wrong data
278 RECT aSrcRect;
279 RECT aClipRect;
280 RECT aTempRect;
281 RECT aTempRect2;
282 HRGN hTempRgn;
283 HWND hWnd;
284
285 // restrict srcRect to this window (calc intersection)
286 aSrcRect.left = static_cast<int>(nSrcX);
287 aSrcRect.top = static_cast<int>(nSrcY);
288 aSrcRect.right = aSrcRect.left+static_cast<int>(nSrcWidth);
289 aSrcRect.bottom = aSrcRect.top+static_cast<int>(nSrcHeight);
290 GetClientRect( mrParent.gethWnd(), &aClipRect );
291 if ( IntersectRect( &aSrcRect, &aSrcRect, &aClipRect ) )
292 {
293 // transform srcRect to screen coordinates
294 POINT aPt;
295 aPt.x = 0;
296 aPt.y = 0;
297 ClientToScreen( mrParent.gethWnd(), &aPt );
298 aSrcRect.left += aPt.x;
299 aSrcRect.top += aPt.y;
300 aSrcRect.right += aPt.x;
301 aSrcRect.bottom += aPt.y;
302 hInvalidateRgn = nullptr;
303
304 // compute the parts that are off screen (ie invisible)
305 RECT theScreen;
306 ImplSalGetWorkArea( nullptr, &theScreen, nullptr ); // find the screen area taking multiple monitors into account
307 ImplCalcOutSideRgn( aSrcRect, theScreen.left, theScreen.top, theScreen.right, theScreen.bottom, hInvalidateRgn );
308
309 // calculate regions that are covered by other windows
310 HRGN hTempRgn2 = nullptr;
311 HWND hWndTopWindow = mrParent.gethWnd();
312 // Find the TopLevel Window, because only Windows which are in
313 // in the foreground of our TopLevel window must be considered
314 if ( GetWindowStyle( hWndTopWindow ) & WS_CHILD )
315 {
316 RECT aTempRect3 = aSrcRect;
317 do
318 {
319 hWndTopWindow = ::GetParent( hWndTopWindow );
320
321 // Test if the Parent clips our window
322 GetClientRect( hWndTopWindow, &aTempRect );
323 POINT aPt2;
324 aPt2.x = 0;
325 aPt2.y = 0;
326 ClientToScreen( hWndTopWindow, &aPt2 );
327 aTempRect.left += aPt2.x;
328 aTempRect.top += aPt2.y;
329 aTempRect.right += aPt2.x;
330 aTempRect.bottom += aPt2.y;
331 IntersectRect( &aTempRect3, &aTempRect3, &aTempRect );
332 }
333 while ( GetWindowStyle( hWndTopWindow ) & WS_CHILD );
334
335 // If one or more Parents clip our window, then we must
336 // calculate the outside area
337 if ( !EqualRect( &aSrcRect, &aTempRect3 ) )
338 {
339 ImplCalcOutSideRgn( aSrcRect,
340 aTempRect3.left, aTempRect3.top,
341 aTempRect3.right, aTempRect3.bottom,
342 hInvalidateRgn );
343 }
344 }
345 // retrieve the top-most (z-order) child window
346 hWnd = GetWindow( GetDesktopWindow(), GW_CHILD );
347 while ( hWnd )
348 {
349 if ( hWnd == hWndTopWindow )
350 break;
351 if ( IsWindowVisible( hWnd ) && !IsIconic( hWnd ) )
352 {
353 GetWindowRect( hWnd, &aTempRect );
354 if ( IntersectRect( &aTempRect2, &aSrcRect, &aTempRect ) )
355 {
356 // hWnd covers part or all of aSrcRect
357 if ( !hInvalidateRgn )
358 hInvalidateRgn = CreateRectRgnIndirect( &aSrcRect );
359
360 // get full bounding box of hWnd
361 hTempRgn = CreateRectRgnIndirect( &aTempRect );
362
363 // get region of hWnd (the window may be shaped)
364 if ( !hTempRgn2 )
365 hTempRgn2 = CreateRectRgn( 0, 0, 0, 0 );
366 int nRgnType = GetWindowRgn( hWnd, hTempRgn2 );
367 if ( (nRgnType != ERROR) && (nRgnType != NULLREGION) )
368 {
369 // convert window region to screen coordinates
370 OffsetRgn( hTempRgn2, aTempRect.left, aTempRect.top );
371 // and intersect with the window's bounding box
372 CombineRgn( hTempRgn, hTempRgn, hTempRgn2, RGN_AND );
373 }
374 // finally compute that part of aSrcRect which is not covered by any parts of hWnd
375 CombineRgn( hInvalidateRgn, hInvalidateRgn, hTempRgn, RGN_DIFF );
376 DeleteRegion( hTempRgn );
377 }
378 }
379 // retrieve the next window in the z-order, i.e. the window below hwnd
380 hWnd = GetWindow( hWnd, GW_HWNDNEXT );
381 }
382 if ( hTempRgn2 )
383 DeleteRegion( hTempRgn2 );
384 if ( hInvalidateRgn )
385 {
386 // hInvalidateRgn contains the fully visible parts of the original srcRect
387 hTempRgn = CreateRectRgnIndirect( &aSrcRect );
388 // subtract it from the original rect to get the occluded parts
389 int nRgnType = CombineRgn( hInvalidateRgn, hTempRgn, hInvalidateRgn, RGN_DIFF );
390 DeleteRegion( hTempRgn );
391
392 if ( (nRgnType != ERROR) && (nRgnType != NULLREGION) )
393 {
394 // move the occluded parts to the destination pos
395 int nOffX = static_cast<int>(nDestX-nSrcX);
396 int nOffY = static_cast<int>(nDestY-nSrcY);
397 OffsetRgn( hInvalidateRgn, nOffX-aPt.x, nOffY-aPt.y );
398
399 // by excluding hInvalidateRgn from the system's clip region
400 // we will prevent bitblt from copying useless data
401 // especially now shadows from overlapping windows will appear (#i36344)
402 hOldClipRgn = CreateRectRgn( 0, 0, 0, 0 );
403 nOldClipRgnType = GetClipRgn( mrParent.getHDC(), hOldClipRgn );
404
405 bRestoreClipRgn = true; // indicate changed clipregion and force invalidate
406 ExtSelectClipRgn( mrParent.getHDC(), hInvalidateRgn, RGN_DIFF );
407 }
408 }
409 }
410 }
411
412 BitBlt( mrParent.getHDC(),
413 static_cast<int>(nDestX), static_cast<int>(nDestY),
414 static_cast<int>(nSrcWidth), static_cast<int>(nSrcHeight),
415 mrParent.getHDC(),
416 static_cast<int>(nSrcX), static_cast<int>(nSrcY),
417 SRCCOPY );
418
419 if( bRestoreClipRgn )
420 {
421 // restore old clip region
422 if( nOldClipRgnType != ERROR )
423 SelectClipRgn( mrParent.getHDC(), hOldClipRgn);
424 DeleteRegion( hOldClipRgn );
425
426 // invalidate regions that were not copied
427 bool bInvalidate = true;
428
429 // Combine Invalidate vcl::Region with existing ClipRegion
430 HRGN hTempRgn = CreateRectRgn( 0, 0, 0, 0 );
431 if ( GetClipRgn( mrParent.getHDC(), hTempRgn ) == 1 )
432 {
433 int nRgnType = CombineRgn( hInvalidateRgn, hTempRgn, hInvalidateRgn, RGN_AND );
434 if ( (nRgnType == ERROR) || (nRgnType == NULLREGION) )
435 bInvalidate = false;
436 }
437 DeleteRegion( hTempRgn );
438
439 if ( bInvalidate )
440 {
441 InvalidateRgn( mrParent.gethWnd(), hInvalidateRgn, TRUE );
442 // here we only initiate an update if this is the MainThread,
443 // so that there is no deadlock when handling the Paint event,
444 // as the SolarMutex is already held by this Thread
445 SalData* pSalData = GetSalData();
446 DWORD nCurThreadId = GetCurrentThreadId();
447 if ( pSalData->mnAppThreadId == nCurThreadId )
448 UpdateWindow( mrParent.gethWnd() );
449 }
450
451 DeleteRegion( hInvalidateRgn );
452 }
453
454 }
455
456 namespace {
457
ImplDrawBitmap(HDC hDC,const SalTwoRect & rPosAry,const WinSalBitmap & rSalBitmap,bool bPrinter,int nDrawMode)458 void ImplDrawBitmap( HDC hDC, const SalTwoRect& rPosAry, const WinSalBitmap& rSalBitmap,
459 bool bPrinter, int nDrawMode )
460 {
461 if( !hDC )
462 return;
463
464 HGLOBAL hDrawDIB;
465 HBITMAP hDrawDDB = rSalBitmap.ImplGethDDB();
466 std::optional<WinSalBitmap> xTmpSalBmp;
467 bool bPrintDDB = ( bPrinter && hDrawDDB );
468
469 if( bPrintDDB )
470 {
471 xTmpSalBmp.emplace();
472 xTmpSalBmp->Create(rSalBitmap, vcl::bitDepthToPixelFormat(rSalBitmap.GetBitCount()));
473 hDrawDIB = xTmpSalBmp->ImplGethDIB();
474 }
475 else
476 hDrawDIB = rSalBitmap.ImplGethDIB();
477
478 if( hDrawDIB )
479 {
480 if (PBITMAPINFO pBI = static_cast<PBITMAPINFO>(GlobalLock( hDrawDIB )))
481 {
482 PBYTE pBits = reinterpret_cast<PBYTE>(pBI) + pBI->bmiHeader.biSize +
483 WinSalBitmap::ImplGetDIBColorCount( hDrawDIB ) * sizeof( RGBQUAD );
484 const int nOldStretchMode = SetStretchBltMode( hDC, STRETCH_DELETESCANS );
485
486 StretchDIBits( hDC,
487 static_cast<int>(rPosAry.mnDestX), static_cast<int>(rPosAry.mnDestY),
488 static_cast<int>(rPosAry.mnDestWidth), static_cast<int>(rPosAry.mnDestHeight),
489 static_cast<int>(rPosAry.mnSrcX), static_cast<int>(pBI->bmiHeader.biHeight - rPosAry.mnSrcHeight - rPosAry.mnSrcY),
490 static_cast<int>(rPosAry.mnSrcWidth), static_cast<int>(rPosAry.mnSrcHeight),
491 pBits, pBI, DIB_RGB_COLORS, nDrawMode );
492
493 GlobalUnlock( hDrawDIB );
494 SetStretchBltMode( hDC, nOldStretchMode );
495 }
496 }
497 else if( hDrawDDB && !bPrintDDB )
498 {
499 ScopedCachedHDC<CACHED_HDC_DRAW> hBmpDC(hDrawDDB);
500
501 COLORREF nOldBkColor = RGB(0xFF,0xFF,0xFF);
502 COLORREF nOldTextColor = RGB(0,0,0);
503 bool bMono = ( rSalBitmap.GetBitCount() == 1 );
504
505 if( bMono )
506 {
507 COLORREF nBkColor = RGB( 0xFF, 0xFF, 0xFF );
508 COLORREF nTextColor = RGB( 0x00, 0x00, 0x00 );
509 //fdo#33455 handle 1 bit depth pngs with palette entries
510 //to set fore/back colors
511 if (BitmapBuffer* pBitmapBuffer = const_cast<WinSalBitmap&>(rSalBitmap).AcquireBuffer(BitmapAccessMode::Info))
512 {
513 const BitmapPalette& rPalette = pBitmapBuffer->maPalette;
514 if (rPalette.GetEntryCount() == 2)
515 {
516 Color nCol = rPalette[0];
517 nTextColor = RGB( nCol.GetRed(), nCol.GetGreen(), nCol.GetBlue() );
518 nCol = rPalette[1];
519 nBkColor = RGB( nCol.GetRed(), nCol.GetGreen(), nCol.GetBlue() );
520 }
521 const_cast<WinSalBitmap&>(rSalBitmap).ReleaseBuffer(pBitmapBuffer, BitmapAccessMode::Info);
522 }
523 nOldBkColor = SetBkColor( hDC, nBkColor );
524 nOldTextColor = ::SetTextColor( hDC, nTextColor );
525 }
526
527 if ( (rPosAry.mnSrcWidth == rPosAry.mnDestWidth) &&
528 (rPosAry.mnSrcHeight == rPosAry.mnDestHeight) )
529 {
530 BitBlt( hDC,
531 static_cast<int>(rPosAry.mnDestX), static_cast<int>(rPosAry.mnDestY),
532 static_cast<int>(rPosAry.mnDestWidth), static_cast<int>(rPosAry.mnDestHeight),
533 hBmpDC.get(),
534 static_cast<int>(rPosAry.mnSrcX), static_cast<int>(rPosAry.mnSrcY),
535 nDrawMode );
536 }
537 else
538 {
539 const int nOldStretchMode = SetStretchBltMode( hDC, STRETCH_DELETESCANS );
540
541 StretchBlt( hDC,
542 static_cast<int>(rPosAry.mnDestX), static_cast<int>(rPosAry.mnDestY),
543 static_cast<int>(rPosAry.mnDestWidth), static_cast<int>(rPosAry.mnDestHeight),
544 hBmpDC.get(),
545 static_cast<int>(rPosAry.mnSrcX), static_cast<int>(rPosAry.mnSrcY),
546 static_cast<int>(rPosAry.mnSrcWidth), static_cast<int>(rPosAry.mnSrcHeight),
547 nDrawMode );
548
549 SetStretchBltMode( hDC, nOldStretchMode );
550 }
551
552 if( bMono )
553 {
554 SetBkColor( hDC, nOldBkColor );
555 ::SetTextColor( hDC, nOldTextColor );
556 }
557 }
558 }
559
560 } // namespace
561
drawBitmap(const SalTwoRect & rPosAry,const SalBitmap & rSalBitmap)562 void WinSalGraphicsImpl::drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap)
563 {
564 bool bTryDirectPaint(!mrParent.isPrinter() && !mbXORMode);
565
566 if(bTryDirectPaint)
567 {
568 // only paint direct when no scaling and no MapMode, else the
569 // more expensive conversions may be done for short-time Bitmap/BitmapEx
570 // used for buffering only
571 if(rPosAry.mnSrcWidth == rPosAry.mnDestWidth && rPosAry.mnSrcHeight == rPosAry.mnDestHeight)
572 {
573 bTryDirectPaint = false;
574 }
575 }
576
577 // try to draw using GdiPlus directly
578 if(bTryDirectPaint && TryDrawBitmapGDIPlus(rPosAry, rSalBitmap))
579 {
580 return;
581 }
582
583 // fall back old stuff
584 assert(dynamic_cast<const WinSalBitmap*>(&rSalBitmap));
585
586 ImplDrawBitmap(mrParent.getHDC(), rPosAry, static_cast<const WinSalBitmap&>(rSalBitmap),
587 mrParent.isPrinter(),
588 mbXORMode ? SRCINVERT : SRCCOPY );
589 }
590
drawBitmap(const SalTwoRect & rPosAry,const SalBitmap & rSSalBitmap,const SalBitmap & rSTransparentBitmap)591 void WinSalGraphicsImpl::drawBitmap( const SalTwoRect& rPosAry,
592 const SalBitmap& rSSalBitmap,
593 const SalBitmap& rSTransparentBitmap )
594 {
595 SAL_WARN_IF( mrParent.isPrinter(), "vcl", "No transparency print possible!" );
596 bool bTryDirectPaint(!mrParent.isPrinter() && !mbXORMode);
597
598 // try to draw using GdiPlus directly
599 if(bTryDirectPaint && drawAlphaBitmap(rPosAry, rSSalBitmap, rSTransparentBitmap))
600 {
601 return;
602 }
603
604 assert(dynamic_cast<const WinSalBitmap*>(&rSSalBitmap));
605 assert(dynamic_cast<const WinSalBitmap*>(&rSTransparentBitmap));
606
607 const WinSalBitmap& rSalBitmap = static_cast<const WinSalBitmap&>(rSSalBitmap);
608 const WinSalBitmap& rTransparentBitmap = static_cast<const WinSalBitmap&>(rSTransparentBitmap);
609
610 SalTwoRect aPosAry = rPosAry;
611 int nDstX = static_cast<int>(aPosAry.mnDestX);
612 int nDstY = static_cast<int>(aPosAry.mnDestY);
613 int nDstWidth = static_cast<int>(aPosAry.mnDestWidth);
614 int nDstHeight = static_cast<int>(aPosAry.mnDestHeight);
615 HDC hDC = mrParent.getHDC();
616
617 ScopedHBITMAP hMemBitmap;
618 ScopedHBITMAP hMaskBitmap;
619
620 if( ( nDstWidth > CACHED_HDC_DEFEXT ) || ( nDstHeight > CACHED_HDC_DEFEXT ) )
621 {
622 hMemBitmap.reset(CreateCompatibleBitmap(hDC, nDstWidth, nDstHeight));
623 hMaskBitmap.reset(CreateCompatibleBitmap(hDC, nDstWidth, nDstHeight));
624 }
625
626 ScopedCachedHDC<CACHED_HDC_1> hMemDC(hMemBitmap.get());
627 ScopedCachedHDC<CACHED_HDC_2> hMaskDC(hMaskBitmap.get());
628
629 aPosAry.mnDestX = aPosAry.mnDestY = 0;
630 BitBlt( hMemDC.get(), 0, 0, nDstWidth, nDstHeight, hDC, nDstX, nDstY, SRCCOPY );
631
632 // WIN/WNT seems to have a minor problem mapping the correct color of the
633 // mask to the palette if we draw the DIB directly ==> draw DDB
634 if( ( GetBitCount() <= 8 ) && rTransparentBitmap.ImplGethDIB() && rTransparentBitmap.GetBitCount() == 1 )
635 {
636 WinSalBitmap aTmp;
637
638 if( aTmp.Create( rTransparentBitmap, &mrParent ) )
639 ImplDrawBitmap( hMaskDC.get(), aPosAry, aTmp, false, SRCCOPY );
640 }
641 else
642 ImplDrawBitmap( hMaskDC.get(), aPosAry, rTransparentBitmap, false, SRCCOPY );
643
644 // now MemDC contains background, MaskDC the transparency mask
645
646 // #105055# Respect XOR mode
647 if( mbXORMode )
648 {
649 ImplDrawBitmap( hMaskDC.get(), aPosAry, rSalBitmap, false, SRCERASE );
650 // now MaskDC contains the bitmap area with black background
651 BitBlt( hMemDC.get(), 0, 0, nDstWidth, nDstHeight, hMaskDC.get(), 0, 0, SRCINVERT );
652 // now MemDC contains background XORed bitmap area on top
653 }
654 else
655 {
656 BitBlt( hMemDC.get(), 0, 0, nDstWidth, nDstHeight, hMaskDC.get(), 0, 0, SRCAND );
657 // now MemDC contains background with masked-out bitmap area
658 ImplDrawBitmap( hMaskDC.get(), aPosAry, rSalBitmap, false, SRCERASE );
659 // now MaskDC contains the bitmap area with black background
660 BitBlt( hMemDC.get(), 0, 0, nDstWidth, nDstHeight, hMaskDC.get(), 0, 0, SRCPAINT );
661 // now MemDC contains background and bitmap merged together
662 }
663 // copy to output DC
664 BitBlt( hDC, nDstX, nDstY, nDstWidth, nDstHeight, hMemDC.get(), 0, 0, SRCCOPY );
665 }
666
drawAlphaRect(tools::Long nX,tools::Long nY,tools::Long nWidth,tools::Long nHeight,sal_uInt8 nTransparency)667 bool WinSalGraphicsImpl::drawAlphaRect( tools::Long nX, tools::Long nY, tools::Long nWidth,
668 tools::Long nHeight, sal_uInt8 nTransparency )
669 {
670 if( mbPen || !mbBrush || mbXORMode )
671 return false; // can only perform solid fills without XOR.
672
673 ScopedCachedHDC<CACHED_HDC_1> hMemDC(nullptr);
674 SetPixel( hMemDC.get(), int(0), int(0), mnBrushColor );
675
676 BLENDFUNCTION aFunc = {
677 AC_SRC_OVER,
678 0,
679 sal::static_int_cast<sal_uInt8>(255 - 255L*nTransparency/100),
680 0
681 };
682
683 // hMemDC contains a 1x1 bitmap of the right color - stretch-blit
684 // that to dest hdc
685 bool bRet = GdiAlphaBlend(mrParent.getHDC(), nX, nY, nWidth, nHeight,
686 hMemDC.get(), 0,0,1,1,
687 aFunc ) == TRUE;
688
689 return bRet;
690 }
691
drawMask(const SalTwoRect & rPosAry,const SalBitmap & rSSalBitmap,Color nMaskColor)692 void WinSalGraphicsImpl::drawMask(const SalTwoRect& rPosAry,
693 const SalBitmap& rSSalBitmap,
694 Color nMaskColor)
695 {
696 SAL_WARN_IF( mrParent.isPrinter(), "vcl", "No transparency print possible!" );
697
698 assert(dynamic_cast<const WinSalBitmap*>(&rSSalBitmap));
699
700 const WinSalBitmap& rSalBitmap = static_cast<const WinSalBitmap&>(rSSalBitmap);
701
702 SalTwoRect aPosAry = rPosAry;
703 const HDC hDC = mrParent.getHDC();
704
705 ScopedSelectedHBRUSH hBrush(hDC, CreateSolidBrush(RGB(nMaskColor.GetRed(),
706 nMaskColor.GetGreen(),
707 nMaskColor.GetBlue())));
708
709 // WIN/WNT seems to have a minor problem mapping the correct color of the
710 // mask to the palette if we draw the DIB directly ==> draw DDB
711 if( ( GetBitCount() <= 8 ) && rSalBitmap.ImplGethDIB() && rSalBitmap.GetBitCount() == 1 )
712 {
713 WinSalBitmap aTmp;
714
715 if( aTmp.Create( rSalBitmap, &mrParent ) )
716 ImplDrawBitmap( hDC, aPosAry, aTmp, false, 0x00B8074AUL ); // raster operation PSDPxax
717 }
718 else
719 ImplDrawBitmap( hDC, aPosAry, rSalBitmap, false, 0x00B8074AUL );// raster operation PSDPxax
720 }
721
getBitmap(tools::Long nX,tools::Long nY,tools::Long nDX,tools::Long nDY,bool bWithoutAlpha)722 std::shared_ptr<SalBitmap> WinSalGraphicsImpl::getBitmap( tools::Long nX, tools::Long nY, tools::Long nDX, tools::Long nDY, bool bWithoutAlpha )
723 {
724 SAL_WARN_IF( mrParent.isPrinter(), "vcl", "No ::GetBitmap() from printer possible!" );
725
726 std::shared_ptr<WinSalBitmap> pSalBitmap;
727
728 nDX = std::abs( nDX );
729 nDY = std::abs( nDY );
730
731 HDC hDC = mrParent.getHDC();
732 HBITMAP hBmpBitmap;
733 if (bWithoutAlpha && GetBitCount() == 32)
734 {
735 BITMAPINFO aBitmapInfo;
736 aBitmapInfo.bmiHeader.biSize = sizeof( BITMAPINFOHEADER );
737 aBitmapInfo.bmiHeader.biWidth = nDX;
738 aBitmapInfo.bmiHeader.biHeight = -nDY; // negative for top down
739 aBitmapInfo.bmiHeader.biPlanes = 1;
740 aBitmapInfo.bmiHeader.biBitCount = 24;
741 aBitmapInfo.bmiHeader.biCompression = BI_RGB;
742 aBitmapInfo.bmiHeader.biSizeImage = 0;
743 aBitmapInfo.bmiHeader.biXPelsPerMeter = 0;
744 aBitmapInfo.bmiHeader.biYPelsPerMeter = 0;
745 aBitmapInfo.bmiHeader.biClrUsed = 0;
746 aBitmapInfo.bmiHeader.biClrImportant = 0;
747
748 void* pData;
749 hBmpBitmap = CreateDIBSection( hDC, &aBitmapInfo,
750 DIB_RGB_COLORS, &pData, nullptr,
751 0 );
752 SAL_WARN_IF( !hBmpBitmap, "vcl", "CreateDIBSection failed: " << comphelper::WindowsErrorString( GetLastError() ) );
753 }
754 else
755 hBmpBitmap = CreateCompatibleBitmap( hDC, nDX, nDY );
756
757 bool bRet;
758 {
759 ScopedCachedHDC<CACHED_HDC_1> hBmpDC(hBmpBitmap);
760 bRet = BitBlt(hBmpDC.get(), 0, 0,
761 static_cast<int>(nDX), static_cast<int>(nDY), hDC,
762 static_cast<int>(nX), static_cast<int>(nY), SRCCOPY) ? TRUE : FALSE;
763 }
764
765 if( bRet )
766 {
767 pSalBitmap = std::make_shared<WinSalBitmap>();
768
769 if( !pSalBitmap->Create( hBmpBitmap ) )
770 {
771 pSalBitmap.reset();
772 }
773 }
774 else
775 {
776 // #124826# avoid resource leak! Happens when running without desktop access (remote desktop, service, may be screensavers)
777 DeleteBitmap( hBmpBitmap );
778 }
779
780 return pSalBitmap;
781 }
782
getPixel(tools::Long nX,tools::Long nY)783 Color WinSalGraphicsImpl::getPixel( tools::Long nX, tools::Long nY )
784 {
785 // The only way to read a pixel color with alpha is via GDI+
786 // This is all hideously inefficient, but we only really use it for unit tests.
787 Gdiplus::Bitmap screenPixel(1, 1, PixelFormat32bppARGB);
788 Gdiplus::Graphics gdest(&screenPixel);
789 auto hDestDC = gdest.GetHDC();
790 BOOL retval = BitBlt(hDestDC, 0, 0, 1, 1,
791 mrParent.getHDC(), static_cast<int>(nX), static_cast<int>(nY),
792 SRCCOPY);
793 gdest.ReleaseHDC(hDestDC);
794
795 if (!retval)
796 {
797 SAL_WARN("vcl", "GetPixel failed1");
798 return Color(0, 0, 0);
799 }
800 Gdiplus::Color nGdiColor;
801 if (screenPixel.GetPixel(0, 0, &nGdiColor) != Gdiplus::Ok)
802 {
803 SAL_WARN("vcl", "GetPixel failed2");
804 return Color(0, 0, 0);
805 }
806 // seems to be returning premultiplied color, despite the pixel format that I pass in the constructor
807 if (nGdiColor.GetAlpha() == 0)
808 return Color(ColorAlpha, nGdiColor.GetAlpha(),
809 nGdiColor.GetRed(),
810 nGdiColor.GetGreen(),
811 nGdiColor.GetBlue());
812 else
813 return Color(ColorAlpha, nGdiColor.GetAlpha(),
814 nGdiColor.GetRed() * 255 / nGdiColor.GetAlpha(),
815 nGdiColor.GetGreen() * 255 / nGdiColor.GetAlpha(),
816 nGdiColor.GetBlue() * 255 / nGdiColor.GetAlpha());
817 }
818
819 namespace
820 {
821
Get50PercentBrush()822 HBRUSH Get50PercentBrush()
823 {
824 SalData* pSalData = GetSalData();
825 if ( !pSalData->mh50Brush )
826 {
827 if ( !pSalData->mh50Bmp )
828 pSalData->mh50Bmp = ImplLoadSalBitmap( SAL_RESID_BITMAP_50 );
829 pSalData->mh50Brush = CreatePatternBrush( pSalData->mh50Bmp );
830 }
831
832 return pSalData->mh50Brush;
833 }
834
835 } // namespace
836
invert(tools::Long nX,tools::Long nY,tools::Long nWidth,tools::Long nHeight,SalInvert nFlags)837 void WinSalGraphicsImpl::invert( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, SalInvert nFlags )
838 {
839 if ( nFlags & SalInvert::TrackFrame )
840 {
841 HPEN hDotPen = CreatePen( PS_DOT, 0, 0 );
842 HPEN hOldPen = SelectPen( mrParent.getHDC(), hDotPen );
843 HBRUSH hOldBrush = SelectBrush( mrParent.getHDC(), GetStockBrush( NULL_BRUSH ) );
844 int nOldROP = SetROP2( mrParent.getHDC(), R2_NOT );
845
846 Rectangle( mrParent.getHDC(), static_cast<int>(nX), static_cast<int>(nY), static_cast<int>(nX+nWidth), static_cast<int>(nY+nHeight) );
847
848 SetROP2( mrParent.getHDC(), nOldROP );
849 SelectPen( mrParent.getHDC(), hOldPen );
850 SelectBrush( mrParent.getHDC(), hOldBrush );
851 DeletePen( hDotPen );
852 }
853 else if ( nFlags & SalInvert::N50 )
854 {
855 COLORREF nOldTextColor = ::SetTextColor( mrParent.getHDC(), 0 );
856 HBRUSH hOldBrush = SelectBrush( mrParent.getHDC(), Get50PercentBrush() );
857 PatBlt( mrParent.getHDC(), nX, nY, nWidth, nHeight, PATINVERT );
858 ::SetTextColor( mrParent.getHDC(), nOldTextColor );
859 SelectBrush( mrParent.getHDC(), hOldBrush );
860 }
861 else
862 {
863 RECT aRect;
864 aRect.left = static_cast<int>(nX);
865 aRect.top = static_cast<int>(nY);
866 aRect.right = static_cast<int>(nX)+nWidth;
867 aRect.bottom = static_cast<int>(nY)+nHeight;
868 ::InvertRect( mrParent.getHDC(), &aRect );
869 }
870 }
871
invert(sal_uInt32 nPoints,const Point * pPtAry,SalInvert nSalFlags)872 void WinSalGraphicsImpl::invert( sal_uInt32 nPoints, const Point* pPtAry, SalInvert nSalFlags )
873 {
874 HPEN hPen;
875 HPEN hOldPen;
876 HBRUSH hBrush;
877 HBRUSH hOldBrush = nullptr;
878 COLORREF nOldTextColor RGB(0,0,0);
879 int nOldROP = SetROP2( mrParent.getHDC(), R2_NOT );
880
881 if ( nSalFlags & SalInvert::TrackFrame )
882 hPen = CreatePen( PS_DOT, 0, 0 );
883 else
884 {
885
886 if ( nSalFlags & SalInvert::N50 )
887 hBrush = Get50PercentBrush();
888 else
889 hBrush = GetStockBrush( BLACK_BRUSH );
890
891 hPen = GetStockPen( NULL_PEN );
892 nOldTextColor = ::SetTextColor( mrParent.getHDC(), 0 );
893 hOldBrush = SelectBrush( mrParent.getHDC(), hBrush );
894 }
895 hOldPen = SelectPen( mrParent.getHDC(), hPen );
896
897 std::unique_ptr<POINT[]> pWinPtAry(new POINT[nPoints]);
898 for (sal_uInt32 i=0; i<nPoints; ++i)
899 pWinPtAry[i] = POINT { static_cast<LONG>(pPtAry[i].getX()), static_cast<LONG>(pPtAry[i].getY()) };
900
901 if ( nSalFlags & SalInvert::TrackFrame )
902 Polyline( mrParent.getHDC(), pWinPtAry.get(), static_cast<int>(nPoints) );
903 else
904 Polygon( mrParent.getHDC(), pWinPtAry.get(), static_cast<int>(nPoints) );
905
906 SetROP2( mrParent.getHDC(), nOldROP );
907 SelectPen( mrParent.getHDC(), hOldPen );
908
909 if ( nSalFlags & SalInvert::TrackFrame )
910 DeletePen( hPen );
911 else
912 {
913 ::SetTextColor( mrParent.getHDC(), nOldTextColor );
914 SelectBrush( mrParent.getHDC(), hOldBrush );
915 }
916 }
917
GetBitCount() const918 sal_uInt16 WinSalGraphicsImpl::GetBitCount() const
919 {
920 return static_cast<sal_uInt16>(GetDeviceCaps( mrParent.getHDC(), BITSPIXEL ));
921 }
922
GetGraphicsWidth() const923 tools::Long WinSalGraphicsImpl::GetGraphicsWidth() const
924 {
925 if( !mrParent.gethWnd() || !IsWindow( mrParent.gethWnd() ) )
926 return 0;
927
928 WinSalFrame* pFrame = GetWindowPtr( mrParent.gethWnd() );
929 if( !pFrame )
930 return 0;
931
932 if (pFrame->GetWidth())
933 return pFrame->GetWidth();
934
935 // TODO: perhaps not needed, width should always be up-to-date
936 RECT aRect;
937 GetClientRect( mrParent.gethWnd(), &aRect );
938 return aRect.right;
939 }
940
ResetClipRegion()941 void WinSalGraphicsImpl::ResetClipRegion()
942 {
943 if ( mrParent.mhRegion )
944 {
945 DeleteRegion( mrParent.mhRegion );
946 mrParent.mhRegion = nullptr;
947 }
948
949 SelectClipRgn( mrParent.getHDC(), nullptr );
950 }
951
containsOnlyHorizontalAndVerticalEdges(const basegfx::B2DPolygon & rCandidate)952 static bool containsOnlyHorizontalAndVerticalEdges(const basegfx::B2DPolygon& rCandidate)
953 {
954 if(rCandidate.areControlPointsUsed())
955 return false;
956
957 const sal_uInt32 nPointCount(rCandidate.count());
958
959 if(nPointCount < 2)
960 return true;
961
962 const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount + 1 : nPointCount);
963 basegfx::B2DPoint aLast(rCandidate.getB2DPoint(0));
964
965 for(sal_uInt32 a(1); a < nEdgeCount; a++)
966 {
967 const sal_uInt32 nNextIndex(a % nPointCount);
968 const basegfx::B2DPoint aCurrent(rCandidate.getB2DPoint(nNextIndex));
969
970 if(!basegfx::fTools::equal(aLast.getX(), aCurrent.getX()) && !basegfx::fTools::equal(aLast.getY(), aCurrent.getY()))
971 {
972 return false;
973 }
974
975 aLast = aCurrent;
976 }
977
978 return true;
979 }
980
containsOnlyHorizontalAndVerticalEdges(const basegfx::B2DPolyPolygon & rCandidate)981 static bool containsOnlyHorizontalAndVerticalEdges(const basegfx::B2DPolyPolygon& rCandidate)
982 {
983 if(rCandidate.areControlPointsUsed())
984 return false;
985
986 for(auto const& rPolygon : rCandidate)
987 {
988 if(!containsOnlyHorizontalAndVerticalEdges(rPolygon))
989 {
990 return false;
991 }
992 }
993
994 return true;
995 }
996
setClipRegion(const vcl::Region & i_rClip)997 void WinSalGraphicsImpl::setClipRegion( const vcl::Region& i_rClip )
998 {
999 if ( mrParent.mhRegion )
1000 {
1001 DeleteRegion( mrParent.mhRegion );
1002 mrParent.mhRegion = nullptr;
1003 }
1004
1005 bool bUsePolygon(i_rClip.HasPolyPolygonOrB2DPolyPolygon());
1006 static bool bTryToAvoidPolygon(true);
1007
1008 // #i122149# try to avoid usage of tools::PolyPolygon ClipRegions when tools::PolyPolygon is no curve
1009 // and only contains horizontal/vertical edges. In that case, use the fallback
1010 // in GetRegionRectangles which will use vcl::Region::GetAsRegionBand() which will do
1011 // the correct polygon-to-RegionBand transformation.
1012 // Background is that when using the same Rectangle as rectangle or as Polygon
1013 // clip region will lead to different results; the polygon-based one will be
1014 // one pixel less to the right and down (see GDI docu for CreatePolygonRgn). This
1015 // again is because of the polygon-nature and it's classic handling when filling.
1016 // This also means that all cases which use a 'true' polygon-based incarnation of
1017 // a vcl::Region should know what they do - it may lead to repaint errors.
1018 if(bUsePolygon && bTryToAvoidPolygon)
1019 {
1020 const basegfx::B2DPolyPolygon aPolyPolygon( i_rClip.GetAsB2DPolyPolygon() );
1021
1022 if(!aPolyPolygon.areControlPointsUsed())
1023 {
1024 if(containsOnlyHorizontalAndVerticalEdges(aPolyPolygon))
1025 {
1026 bUsePolygon = false;
1027 }
1028 }
1029 }
1030
1031 if(bUsePolygon)
1032 {
1033 // #i122149# check the comment above to know that this may lead to potential repaint
1034 // problems. It may be solved (if needed) by scaling the polygon by one in X
1035 // and Y. Currently the workaround to only use it if really unavoidable will
1036 // solve most cases. When someone is really using polygon-based Regions he
1037 // should know what he is doing.
1038 // Added code to do that scaling to check if it works, testing it.
1039 const basegfx::B2DPolyPolygon aPolyPolygon( i_rClip.GetAsB2DPolyPolygon() );
1040 const sal_uInt32 nCount(aPolyPolygon.count());
1041
1042 if( nCount )
1043 {
1044 std::vector< POINT > aPolyPoints;
1045 aPolyPoints.reserve( 1024 );
1046 std::vector< INT > aPolyCounts( nCount, 0 );
1047 basegfx::B2DHomMatrix aExpand;
1048 sal_uInt32 nTargetCount(0);
1049 static bool bExpandByOneInXandY(true);
1050
1051 if(bExpandByOneInXandY)
1052 {
1053 const basegfx::B2DRange aRangeS(aPolyPolygon.getB2DRange());
1054 const basegfx::B2DRange aRangeT(aRangeS.getMinimum(), aRangeS.getMaximum() + basegfx::B2DTuple(1.0, 1.0));
1055 aExpand = basegfx::utils::createSourceRangeTargetRangeTransform(aRangeS, aRangeT);
1056 }
1057
1058 for(auto const& rPolygon : aPolyPolygon)
1059 {
1060 const basegfx::B2DPolygon aPoly(
1061 basegfx::utils::adaptiveSubdivideByDistance(
1062 rPolygon,
1063 1));
1064 const sal_uInt32 nPoints(aPoly.count());
1065
1066 // tdf#40863 For CustomShapes there is a hack (see
1067 // f64ef72743e55389e446e0d4bc6febd475011023) that adds polygons
1068 // with a single point in top-left and bottom-right corner
1069 // of the BoundRect to be able to determine the correct BoundRect
1070 // in the slideshow. Unfortunately, CreatePolyPolygonRgn below
1071 // fails with polygons containing a single pixel, so clipping is
1072 // lost. For now, use only polygons with more than two points - the
1073 // ones that may have an area.
1074 // Note: polygons with one point which are curves may have an area,
1075 // but the polygon is already subdivided here, so no need to test
1076 // this.
1077 if(nPoints > 2)
1078 {
1079 aPolyCounts[nTargetCount] = nPoints;
1080 nTargetCount++;
1081
1082 for( sal_uInt32 b = 0; b < nPoints; b++ )
1083 {
1084 basegfx::B2DPoint aPt(aPoly.getB2DPoint(b));
1085
1086 if(bExpandByOneInXandY)
1087 {
1088 aPt = aExpand * aPt;
1089 }
1090
1091 POINT aPOINT;
1092 // #i122149# do correct rounding
1093 aPOINT.x = basegfx::fround(aPt.getX());
1094 aPOINT.y = basegfx::fround(aPt.getY());
1095 aPolyPoints.push_back( aPOINT );
1096 }
1097 }
1098 }
1099
1100 if(nTargetCount)
1101 {
1102 mrParent.mhRegion = CreatePolyPolygonRgn( aPolyPoints.data(), aPolyCounts.data(), nTargetCount, ALTERNATE );
1103 }
1104 }
1105 }
1106 else
1107 {
1108 RectangleVector aRectangles;
1109 i_rClip.GetRegionRectangles(aRectangles);
1110
1111 sal_uLong nRectBufSize = sizeof(RECT)*aRectangles.size();
1112 if ( aRectangles.size() < SAL_CLIPRECT_COUNT )
1113 {
1114 if ( !mrParent.mpStdClipRgnData )
1115 mrParent.mpStdClipRgnData = reinterpret_cast<RGNDATA*>(new BYTE[sizeof(RGNDATA)-1+(SAL_CLIPRECT_COUNT*sizeof(RECT))]);
1116 mrParent.mpClipRgnData = mrParent.mpStdClipRgnData;
1117 }
1118 else
1119 mrParent.mpClipRgnData = reinterpret_cast<RGNDATA*>(new BYTE[sizeof(RGNDATA)-1+nRectBufSize]);
1120 mrParent.mpClipRgnData->rdh.dwSize = sizeof( RGNDATAHEADER );
1121 mrParent.mpClipRgnData->rdh.iType = RDH_RECTANGLES;
1122 mrParent.mpClipRgnData->rdh.nCount = aRectangles.size();
1123 mrParent.mpClipRgnData->rdh.nRgnSize = nRectBufSize;
1124 RECT* pBoundRect = &(mrParent.mpClipRgnData->rdh.rcBound);
1125 SetRectEmpty( pBoundRect );
1126 RECT* pNextClipRect = reinterpret_cast<RECT*>(&(mrParent.mpClipRgnData->Buffer));
1127 bool bFirstClipRect = true;
1128
1129 for (auto const& rectangle : aRectangles)
1130 {
1131 const tools::Long nW(rectangle.GetWidth());
1132 const tools::Long nH(rectangle.GetHeight());
1133
1134 if(nW && nH)
1135 {
1136 const tools::Long nRight(rectangle.Left() + nW);
1137 const tools::Long nBottom(rectangle.Top() + nH);
1138
1139 if(bFirstClipRect)
1140 {
1141 pBoundRect->left = rectangle.Left();
1142 pBoundRect->top = rectangle.Top();
1143 pBoundRect->right = nRight;
1144 pBoundRect->bottom = nBottom;
1145 bFirstClipRect = false;
1146 }
1147 else
1148 {
1149 if(rectangle.Left() < pBoundRect->left)
1150 {
1151 pBoundRect->left = static_cast<int>(rectangle.Left());
1152 }
1153
1154 if(rectangle.Top() < pBoundRect->top)
1155 {
1156 pBoundRect->top = static_cast<int>(rectangle.Top());
1157 }
1158
1159 if(nRight > pBoundRect->right)
1160 {
1161 pBoundRect->right = static_cast<int>(nRight);
1162 }
1163
1164 if(nBottom > pBoundRect->bottom)
1165 {
1166 pBoundRect->bottom = static_cast<int>(nBottom);
1167 }
1168 }
1169
1170 pNextClipRect->left = static_cast<int>(rectangle.Left());
1171 pNextClipRect->top = static_cast<int>(rectangle.Top());
1172 pNextClipRect->right = static_cast<int>(nRight);
1173 pNextClipRect->bottom = static_cast<int>(nBottom);
1174 pNextClipRect++;
1175 }
1176 else
1177 {
1178 mrParent.mpClipRgnData->rdh.nCount--;
1179 mrParent.mpClipRgnData->rdh.nRgnSize -= sizeof( RECT );
1180 }
1181 }
1182
1183 // create clip region from ClipRgnData
1184 if(0 == mrParent.mpClipRgnData->rdh.nCount)
1185 {
1186 // #i123585# region is empty; this may happen when e.g. a tools::PolyPolygon is given
1187 // that contains no polygons or only empty ones (no width/height). This is
1188 // perfectly fine and we are done, except setting it (see end of method)
1189 }
1190 else if(1 == mrParent.mpClipRgnData->rdh.nCount)
1191 {
1192 RECT* pRect = &(mrParent.mpClipRgnData->rdh.rcBound);
1193 mrParent.mhRegion = CreateRectRgn( pRect->left, pRect->top,
1194 pRect->right, pRect->bottom );
1195 }
1196 else if(mrParent.mpClipRgnData->rdh.nCount > 1)
1197 {
1198 sal_uLong nSize = mrParent.mpClipRgnData->rdh.nRgnSize+sizeof(RGNDATAHEADER);
1199 mrParent.mhRegion = ExtCreateRegion( nullptr, nSize, mrParent.mpClipRgnData );
1200
1201 // if ExtCreateRegion(...) is not supported
1202 if( !mrParent.mhRegion )
1203 {
1204 RGNDATAHEADER const & pHeader = mrParent.mpClipRgnData->rdh;
1205
1206 if( pHeader.nCount )
1207 {
1208 RECT* pRect = reinterpret_cast<RECT*>(mrParent.mpClipRgnData->Buffer);
1209 mrParent.mhRegion = CreateRectRgn( pRect->left, pRect->top, pRect->right, pRect->bottom );
1210 pRect++;
1211
1212 for( sal_uLong n = 1; n < pHeader.nCount; n++, pRect++ )
1213 {
1214 ScopedHRGN hRgn(CreateRectRgn(pRect->left, pRect->top, pRect->right, pRect->bottom));
1215 CombineRgn( mrParent.mhRegion, mrParent.mhRegion, hRgn.get(), RGN_OR );
1216 }
1217 }
1218 }
1219
1220 if ( mrParent.mpClipRgnData != mrParent.mpStdClipRgnData )
1221 delete [] reinterpret_cast<BYTE*>(mrParent.mpClipRgnData);
1222 }
1223 }
1224
1225 if( mrParent.mhRegion )
1226 {
1227 SelectClipRgn( mrParent.getHDC(), mrParent.mhRegion );
1228
1229 // debug code if you want to check range of the newly applied ClipRegion
1230 //RECT aBound;
1231 //const int aRegionType = GetRgnBox(mrParent.mhRegion, &aBound);
1232 }
1233 else
1234 {
1235 // #i123585# See above, this is a valid case, execute it
1236 SelectClipRgn( mrParent.getHDC(), nullptr );
1237 }
1238 }
1239
SetLineColor()1240 void WinSalGraphicsImpl::SetLineColor()
1241 {
1242 ResetPen(GetStockPen(NULL_PEN));
1243
1244 // set new data
1245 mbPen = false;
1246 mbStockPen = true;
1247 }
1248
SetLineColor(Color nColor)1249 void WinSalGraphicsImpl::SetLineColor(Color nColor)
1250 {
1251 COLORREF nPenColor = PALETTERGB(nColor.GetRed(),
1252 nColor.GetGreen(),
1253 nColor.GetBlue());
1254 bool bStockPen = false;
1255
1256 HPEN hNewPen = SearchStockPen(nPenColor);
1257 if (hNewPen)
1258 bStockPen = true;
1259 else
1260 hNewPen = MakePen(nColor);
1261
1262 ResetPen(hNewPen);
1263
1264 // set new data
1265 mnPenColor = nPenColor;
1266 maLineColor = nColor;
1267 mbPen = true;
1268 mbStockPen = bStockPen;
1269 }
1270
SearchStockPen(COLORREF nPenColor)1271 HPEN WinSalGraphicsImpl::SearchStockPen(COLORREF nPenColor)
1272 {
1273 // Only screen, because printer has problems, when we use stock objects.
1274 if (mrParent.isPrinter())
1275 return nullptr;
1276
1277 const SalData* pSalData = GetSalData();
1278
1279 for (sal_uInt16 i = 0; i < pSalData->mnStockPenCount; i++)
1280 {
1281 if (nPenColor == pSalData->maStockPenColorAry[i])
1282 return pSalData->mhStockPenAry[i];
1283 }
1284
1285 return nullptr;
1286 }
1287
MakePen(Color nColor)1288 HPEN WinSalGraphicsImpl::MakePen(Color nColor)
1289 {
1290 COLORREF nPenColor = RGB(nColor.GetRed(),
1291 nColor.GetGreen(),
1292 nColor.GetBlue());
1293
1294 return CreatePen(PS_SOLID, mrParent.mnPenWidth, nPenColor);
1295 }
1296
ResetPen(HPEN hNewPen)1297 void WinSalGraphicsImpl::ResetPen(HPEN hNewPen)
1298 {
1299 HPEN hOldPen = SelectPen(mrParent.getHDC(), hNewPen);
1300
1301 if (mhPen)
1302 {
1303 if (!mbStockPen)
1304 {
1305 DeletePen(mhPen);
1306 }
1307 }
1308 else
1309 {
1310 mrParent.mhDefPen = hOldPen;
1311 }
1312
1313 mhPen = hNewPen;
1314 }
1315
SetFillColor()1316 void WinSalGraphicsImpl::SetFillColor()
1317 {
1318 ResetBrush(GetStockBrush(NULL_BRUSH));
1319
1320 // set new data
1321 mbBrush = false;
1322 mbStockBrush = true;
1323 }
1324
SetFillColor(Color nColor)1325 void WinSalGraphicsImpl::SetFillColor(Color nColor)
1326 {
1327 COLORREF nBrushColor = PALETTERGB(nColor.GetRed(),
1328 nColor.GetGreen(),
1329 nColor.GetBlue());
1330 bool bStockBrush = false;
1331
1332 HBRUSH hNewBrush = SearchStockBrush(nBrushColor);
1333 if (hNewBrush)
1334 bStockBrush = true;
1335 else
1336 hNewBrush = MakeBrush(nColor);
1337
1338 ResetBrush(hNewBrush);
1339
1340 // set new data
1341 mnBrushColor = nBrushColor;
1342 maFillColor = nColor;
1343 mbBrush = true;
1344 mbStockBrush = bStockBrush;
1345 }
1346
SearchStockBrush(COLORREF nBrushColor)1347 HBRUSH WinSalGraphicsImpl::SearchStockBrush(COLORREF nBrushColor)
1348 {
1349 // Only screen, because printer has problems, when we use stock objects.
1350 if (!mrParent.isPrinter())
1351 {
1352 const SalData* pSalData = GetSalData();
1353
1354 for (sal_uInt16 i = 0; i < pSalData->mnStockBrushCount; i++)
1355 {
1356 if (nBrushColor == pSalData->maStockBrushColorAry[i])
1357 return pSalData->mhStockBrushAry[i];
1358 }
1359 }
1360
1361 return nullptr;
1362 }
1363
MakeBrush(Color nColor)1364 HBRUSH WinSalGraphicsImpl::MakeBrush(Color nColor)
1365 {
1366 const BYTE nRed = nColor.GetRed();
1367 const BYTE nGreen = nColor.GetGreen();
1368 const BYTE nBlue = nColor.GetBlue();
1369 const COLORREF nBrushColor = RGB(nRed, nGreen, nBlue);
1370
1371 return CreateSolidBrush(nBrushColor);
1372 }
1373
ResetBrush(HBRUSH hNewBrush)1374 void WinSalGraphicsImpl::ResetBrush(HBRUSH hNewBrush)
1375 {
1376 HBRUSH hOldBrush = SelectBrush(mrParent.getHDC(), hNewBrush);
1377
1378 if (mhBrush)
1379 {
1380 if (!mbStockBrush)
1381 {
1382 DeleteBrush(mhBrush);
1383 }
1384 }
1385 else
1386 {
1387 mrParent.mhDefBrush = hOldBrush;
1388 }
1389
1390 mhBrush = hNewBrush;
1391 }
1392
SetXORMode(bool bSet,bool)1393 void WinSalGraphicsImpl::SetXORMode( bool bSet, bool )
1394 {
1395 mbXORMode = bSet;
1396 ::SetROP2( mrParent.getHDC(), bSet ? R2_XORPEN : R2_COPYPEN );
1397 }
1398
SetROPLineColor(SalROPColor nROPColor)1399 void WinSalGraphicsImpl::SetROPLineColor( SalROPColor nROPColor )
1400 {
1401 SetLineColor( ImplGetROPColor( nROPColor ) );
1402 }
1403
SetROPFillColor(SalROPColor nROPColor)1404 void WinSalGraphicsImpl::SetROPFillColor( SalROPColor nROPColor )
1405 {
1406 SetFillColor( ImplGetROPColor( nROPColor ) );
1407 }
1408
DrawPixelImpl(tools::Long nX,tools::Long nY,COLORREF crColor)1409 void WinSalGraphicsImpl::DrawPixelImpl( tools::Long nX, tools::Long nY, COLORREF crColor )
1410 {
1411 const HDC hDC = mrParent.getHDC();
1412
1413 if (!mbXORMode)
1414 {
1415 SetPixel(hDC, static_cast<int>(nX), static_cast<int>(nY), crColor);
1416 return;
1417 }
1418
1419 ScopedSelectedHBRUSH hBrush(hDC, CreateSolidBrush(crColor));
1420 PatBlt(hDC, static_cast<int>(nX), static_cast<int>(nY), int(1), int(1), PATINVERT);
1421 }
1422
drawPixel(tools::Long nX,tools::Long nY)1423 void WinSalGraphicsImpl::drawPixel( tools::Long nX, tools::Long nY )
1424 {
1425 DrawPixelImpl( nX, nY, mnPenColor );
1426 }
1427
drawPixel(tools::Long nX,tools::Long nY,Color nColor)1428 void WinSalGraphicsImpl::drawPixel( tools::Long nX, tools::Long nY, Color nColor )
1429 {
1430 COLORREF nColorRef = RGB( nColor.GetRed(),
1431 nColor.GetGreen(),
1432 nColor.GetBlue() );
1433
1434 if (nColor.GetAlpha() != 255)
1435 {
1436 const HDC hDC = mrParent.getHDC();
1437
1438 if (!mbXORMode)
1439 {
1440 // the only way to draw a pixel with alpha is via GDI+
1441 Gdiplus::Graphics g (hDC);
1442 g.SetCompositingMode(Gdiplus::CompositingModeSourceCopy);
1443 Gdiplus::SolidBrush brush(Gdiplus::Color(nColor.GetAlpha(), nColor.GetRed(), nColor.GetGreen(), nColor.GetBlue()));
1444 g.FillRectangle(&brush, static_cast<int>(nX), static_cast<int>(nY), 1, 1);
1445 }
1446 else
1447 {
1448 assert(false && "I am not even sure what it would mean to use alpha in this mode");
1449 ScopedSelectedHBRUSH hBrush(hDC, CreateSolidBrush(nColorRef));
1450 PatBlt(hDC, static_cast<int>(nX), static_cast<int>(nY), int(1), int(1), PATINVERT);
1451 }
1452 }
1453 else
1454 {
1455 const HDC hDC = mrParent.getHDC();
1456
1457 if (!mbXORMode)
1458 {
1459 SetPixel(hDC, static_cast<int>(nX), static_cast<int>(nY), nColorRef);
1460 return;
1461 }
1462
1463 ScopedSelectedHBRUSH hBrush(hDC, CreateSolidBrush(nColorRef));
1464 PatBlt(hDC, static_cast<int>(nX), static_cast<int>(nY), int(1), int(1), PATINVERT);
1465 }
1466 }
1467
drawLine(tools::Long nX1,tools::Long nY1,tools::Long nX2,tools::Long nY2)1468 void WinSalGraphicsImpl::drawLine( tools::Long nX1, tools::Long nY1, tools::Long nX2, tools::Long nY2 )
1469 {
1470 MoveToEx( mrParent.getHDC(), static_cast<int>(nX1), static_cast<int>(nY1), nullptr );
1471
1472 LineTo( mrParent.getHDC(), static_cast<int>(nX2), static_cast<int>(nY2) );
1473
1474 // LineTo doesn't draw the last pixel
1475 if ( !mrParent.isPrinter() )
1476 DrawPixelImpl( nX2, nY2, mnPenColor );
1477 }
1478
drawRect(tools::Long nX,tools::Long nY,tools::Long nWidth,tools::Long nHeight)1479 void WinSalGraphicsImpl::drawRect( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight )
1480 {
1481 if ( !mbPen )
1482 {
1483 if ( !mrParent.isPrinter() )
1484 {
1485 if (maFillColor.GetAlpha() == 255)
1486 {
1487 PatBlt( mrParent.getHDC(), static_cast<int>(nX), static_cast<int>(nY), static_cast<int>(nWidth), static_cast<int>(nHeight),
1488 mbXORMode ? PATINVERT : PATCOPY );
1489 }
1490 else if (mbXORMode)
1491 assert(false && "don't even know what this would mean");
1492 else
1493 {
1494 // the only way to draw with alpha is via GDI+
1495 Gdiplus::Graphics g(mrParent.getHDC());
1496 if (g.SetCompositingMode(Gdiplus::CompositingModeSourceCopy) != Gdiplus::Ok)
1497 SAL_WARN("vcl", "SetCompositingMode failed");
1498 Gdiplus::SolidBrush brush(
1499 Gdiplus::Color(maFillColor.GetAlpha(), maFillColor.GetRed(),
1500 maFillColor.GetGreen(), maFillColor.GetBlue()));
1501 g.FillRectangle(&brush, static_cast<int>(nX), static_cast<int>(nY), nWidth, nHeight);
1502 }
1503 }
1504 else
1505 {
1506 RECT aWinRect;
1507 aWinRect.left = nX;
1508 aWinRect.top = nY;
1509 aWinRect.right = nX+nWidth;
1510 aWinRect.bottom = nY+nHeight;
1511 ::FillRect( mrParent.getHDC(), &aWinRect, mhBrush );
1512 }
1513 }
1514 else
1515 {
1516 if (maFillColor.GetAlpha() == 255)
1517 Rectangle(mrParent.getHDC(), static_cast<int>(nX), static_cast<int>(nY),
1518 static_cast<int>(nX + nWidth), static_cast<int>(nY + nHeight));
1519 else if (mbXORMode)
1520 assert(false && "don't even know what this would mean");
1521 else
1522 {
1523 // the only way to draw with alpha is via GDI+
1524 Gdiplus::Graphics g(mrParent.getHDC());
1525 if (g.SetCompositingMode(Gdiplus::CompositingModeSourceCopy) != Gdiplus::Ok)
1526 SAL_WARN("vcl", "SetCompositingMode failed");
1527 Gdiplus::SolidBrush brush(
1528 Gdiplus::Color(maFillColor.GetAlpha(), maFillColor.GetRed(),
1529 maFillColor.GetGreen(), maFillColor.GetBlue()));
1530 g.FillRectangle(&brush, static_cast<int>(nX), static_cast<int>(nY), nWidth,
1531 nHeight);
1532 }
1533 }
1534 }
1535
drawPolyLine(sal_uInt32 nPoints,const Point * pPtAry)1536 void WinSalGraphicsImpl::drawPolyLine( sal_uInt32 nPoints, const Point* pPtAry )
1537 {
1538 std::unique_ptr<POINT[]> pWinPtAry(new POINT[nPoints]);
1539 for (sal_uInt32 i=0; i<nPoints; ++i)
1540 pWinPtAry[i] = POINT { static_cast<LONG>(pPtAry[i].getX()), static_cast<LONG>(pPtAry[i].getY()) };
1541
1542 Polyline( mrParent.getHDC(), pWinPtAry.get(), static_cast<int>(nPoints) );
1543
1544 // Polyline seems to uses LineTo, which doesn't paint the last pixel (see 87eb8f8ee)
1545 if ( !mrParent.isPrinter() )
1546 DrawPixelImpl( pWinPtAry[nPoints-1].x, pWinPtAry[nPoints-1].y, mnPenColor );
1547 }
1548
drawPolygon(sal_uInt32 nPoints,const Point * pPtAry)1549 void WinSalGraphicsImpl::drawPolygon( sal_uInt32 nPoints, const Point* pPtAry )
1550 {
1551 std::unique_ptr<POINT[]> pWinPtAry(new POINT[nPoints]);
1552 for (sal_uInt32 i=0; i<nPoints; ++i)
1553 pWinPtAry[i] = POINT { static_cast<LONG>(pPtAry[i].getX()), static_cast<LONG>(pPtAry[i].getY()) };
1554
1555 Polygon( mrParent.getHDC(), pWinPtAry.get(), static_cast<int>(nPoints) );
1556 }
1557
drawPolyPolygon(sal_uInt32 nPoly,const sal_uInt32 * pPoints,const Point ** pPtAry)1558 void WinSalGraphicsImpl::drawPolyPolygon( sal_uInt32 nPoly, const sal_uInt32* pPoints,
1559 const Point** pPtAry )
1560 {
1561 UINT aWinPointAry[SAL_POLYPOLYCOUNT_STACKBUF];
1562 UINT* pWinPointAry;
1563 UINT nPolyPolyPoints = 0;
1564 UINT nPoints;
1565 UINT i;
1566
1567 if ( nPoly <= SAL_POLYPOLYCOUNT_STACKBUF )
1568 pWinPointAry = aWinPointAry;
1569 else
1570 pWinPointAry = new UINT[nPoly];
1571
1572 for ( i = 0; i < static_cast<UINT>(nPoly); i++ )
1573 {
1574 nPoints = static_cast<UINT>(pPoints[i])+1;
1575 pWinPointAry[i] = nPoints;
1576 nPolyPolyPoints += nPoints;
1577 }
1578
1579 POINT aWinPointAryAry[SAL_POLYPOLYPOINTS_STACKBUF];
1580 POINT* pWinPointAryAry;
1581 if ( nPolyPolyPoints <= SAL_POLYPOLYPOINTS_STACKBUF )
1582 pWinPointAryAry = aWinPointAryAry;
1583 else
1584 pWinPointAryAry = new POINT[nPolyPolyPoints];
1585 UINT n = 0;
1586 for ( i = 0; i < static_cast<UINT>(nPoly); i++ )
1587 {
1588 nPoints = pWinPointAry[i];
1589 const Point* pPolyAry = pPtAry[i];
1590 for (sal_uInt32 j=0; j<nPoints-1; ++j)
1591 pWinPointAryAry[n+j] = POINT { static_cast<LONG>(pPolyAry[j].getX()), static_cast<LONG>(pPolyAry[j].getY()) };
1592 pWinPointAryAry[n+nPoints-1] = pWinPointAryAry[n];
1593 n += nPoints;
1594 }
1595
1596 PolyPolygon( mrParent.getHDC(), pWinPointAryAry, reinterpret_cast<int*>(pWinPointAry), static_cast<UINT>(nPoly) );
1597
1598 if ( pWinPointAry != aWinPointAry )
1599 delete [] pWinPointAry;
1600 if ( pWinPointAryAry != aWinPointAryAry )
1601 delete [] pWinPointAryAry;
1602 }
1603
drawPolyLineBezier(sal_uInt32 nPoints,const Point * pPtAry,const PolyFlags * pFlgAry)1604 bool WinSalGraphicsImpl::drawPolyLineBezier( sal_uInt32 nPoints, const Point* pPtAry, const PolyFlags* pFlgAry )
1605 {
1606 // #100127# draw an array of points which might also contain bezier control points
1607 if (!nPoints)
1608 return true;
1609
1610 const HDC hdc = mrParent.getHDC();
1611
1612 // TODO: profile whether the following options are faster:
1613 // a) look ahead and draw consecutive bezier or line segments by PolyBezierTo/PolyLineTo resp.
1614 // b) convert our flag array to window's and use PolyDraw
1615 MoveToEx(hdc, static_cast<LONG>(pPtAry->getX()), static_cast<LONG>(pPtAry->getY()), nullptr);
1616 ++pPtAry;
1617 ++pFlgAry;
1618
1619 for(sal_uInt32 i = 1; i < nPoints; ++i)
1620 {
1621 if(*pFlgAry != PolyFlags::Control)
1622 {
1623 LineTo(hdc, pPtAry->getX(), pPtAry->getY());
1624 }
1625 else if(nPoints - i > 2)
1626 {
1627 POINT bezierPoints[] = {
1628 POINT { static_cast<LONG>(pPtAry[0].getX()), static_cast<LONG>(pPtAry[0].getY()) },
1629 POINT { static_cast<LONG>(pPtAry[1].getX()), static_cast<LONG>(pPtAry[1].getY()) },
1630 POINT { static_cast<LONG>(pPtAry[2].getX()), static_cast<LONG>(pPtAry[2].getY()) },
1631 };
1632 PolyBezierTo(hdc, bezierPoints, 3);
1633 i += 2;
1634 pPtAry += 2;
1635 pFlgAry += 2;
1636 }
1637
1638 ++pPtAry;
1639 ++pFlgAry;
1640 }
1641
1642 return true;
1643 }
1644
drawPolygonBezier(sal_uInt32 nPoints,const Point * pPtAry,const PolyFlags * pFlgAry)1645 bool WinSalGraphicsImpl::drawPolygonBezier( sal_uInt32 nPoints, const Point* pPtAry, const PolyFlags* pFlgAry )
1646 {
1647 POINT aStackAry1[SAL_POLY_STACKBUF];
1648 BYTE aStackAry2[SAL_POLY_STACKBUF];
1649 POINT* pWinPointAry;
1650 BYTE* pWinFlagAry;
1651 if( nPoints > SAL_POLY_STACKBUF )
1652 {
1653 pWinPointAry = new POINT[ nPoints ];
1654 pWinFlagAry = new BYTE[ nPoints ];
1655 }
1656 else
1657 {
1658 pWinPointAry = aStackAry1;
1659 pWinFlagAry = aStackAry2;
1660 }
1661
1662 sal_uInt32 nPoints_i32(nPoints);
1663 ImplPreparePolyDraw(1, &nPoints_i32, &pPtAry, &pFlgAry, pWinPointAry, pWinFlagAry);
1664
1665 bool bRet( false );
1666
1667 if( BeginPath( mrParent.getHDC() ) )
1668 {
1669 PolyDraw(mrParent.getHDC(), pWinPointAry, pWinFlagAry, nPoints);
1670
1671 if( EndPath( mrParent.getHDC() ) )
1672 {
1673 if( StrokeAndFillPath( mrParent.getHDC() ) )
1674 bRet = true;
1675 }
1676 }
1677
1678 if( pWinPointAry != aStackAry1 )
1679 {
1680 delete [] pWinPointAry;
1681 delete [] pWinFlagAry;
1682 }
1683
1684 return bRet;
1685 }
1686
drawPolyPolygonBezier(sal_uInt32 nPoly,const sal_uInt32 * pPoints,const Point * const * pPtAry,const PolyFlags * const * pFlgAry)1687 bool WinSalGraphicsImpl::drawPolyPolygonBezier( sal_uInt32 nPoly, const sal_uInt32* pPoints,
1688 const Point* const* pPtAry, const PolyFlags* const* pFlgAry )
1689 {
1690 sal_uLong nCurrPoly, nTotalPoints;
1691 const sal_uInt32* pCurrPoints = pPoints;
1692 for( nCurrPoly=0, nTotalPoints=0; nCurrPoly<nPoly; ++nCurrPoly )
1693 nTotalPoints += *pCurrPoints++;
1694
1695 POINT aStackAry1[SAL_POLY_STACKBUF];
1696 BYTE aStackAry2[SAL_POLY_STACKBUF];
1697 POINT* pWinPointAry;
1698 BYTE* pWinFlagAry;
1699 if( nTotalPoints > SAL_POLY_STACKBUF )
1700 {
1701 pWinPointAry = new POINT[ nTotalPoints ];
1702 pWinFlagAry = new BYTE[ nTotalPoints ];
1703 }
1704 else
1705 {
1706 pWinPointAry = aStackAry1;
1707 pWinFlagAry = aStackAry2;
1708 }
1709
1710 ImplPreparePolyDraw(nPoly, pPoints, pPtAry, pFlgAry, pWinPointAry, pWinFlagAry);
1711
1712 bool bRet( false );
1713
1714 if( BeginPath( mrParent.getHDC() ) )
1715 {
1716 PolyDraw(mrParent.getHDC(), pWinPointAry, pWinFlagAry, nTotalPoints);
1717
1718 if( EndPath( mrParent.getHDC() ) )
1719 {
1720 if( StrokeAndFillPath( mrParent.getHDC() ) )
1721 bRet = true;
1722 }
1723 }
1724
1725 if( pWinPointAry != aStackAry1 )
1726 {
1727 delete [] pWinPointAry;
1728 delete [] pWinFlagAry;
1729 }
1730
1731 return bRet;
1732 }
1733
impPixelSnap(const basegfx::B2DPolygon & rPolygon,const basegfx::B2DHomMatrix & rObjectToDevice,basegfx::B2DHomMatrix & rObjectToDeviceInv,sal_uInt32 nIndex)1734 static basegfx::B2DPoint impPixelSnap(
1735 const basegfx::B2DPolygon& rPolygon,
1736 const basegfx::B2DHomMatrix& rObjectToDevice,
1737 basegfx::B2DHomMatrix& rObjectToDeviceInv,
1738 sal_uInt32 nIndex)
1739 {
1740 const sal_uInt32 nCount(rPolygon.count());
1741
1742 // get the data
1743 const basegfx::B2ITuple aPrevTuple(basegfx::fround(rObjectToDevice * rPolygon.getB2DPoint((nIndex + nCount - 1) % nCount)));
1744 const basegfx::B2DPoint aCurrPoint(rObjectToDevice * rPolygon.getB2DPoint(nIndex));
1745 const basegfx::B2ITuple aCurrTuple(basegfx::fround(aCurrPoint));
1746 const basegfx::B2ITuple aNextTuple(basegfx::fround(rObjectToDevice * rPolygon.getB2DPoint((nIndex + 1) % nCount)));
1747
1748 // get the states
1749 const bool bPrevVertical(aPrevTuple.getX() == aCurrTuple.getX());
1750 const bool bNextVertical(aNextTuple.getX() == aCurrTuple.getX());
1751 const bool bPrevHorizontal(aPrevTuple.getY() == aCurrTuple.getY());
1752 const bool bNextHorizontal(aNextTuple.getY() == aCurrTuple.getY());
1753 const bool bSnapX(bPrevVertical || bNextVertical);
1754 const bool bSnapY(bPrevHorizontal || bNextHorizontal);
1755
1756 if(bSnapX || bSnapY)
1757 {
1758 basegfx::B2DPoint aSnappedPoint(
1759 bSnapX ? aCurrTuple.getX() : aCurrPoint.getX(),
1760 bSnapY ? aCurrTuple.getY() : aCurrPoint.getY());
1761
1762 if(rObjectToDeviceInv.isIdentity())
1763 {
1764 rObjectToDeviceInv = rObjectToDevice;
1765 rObjectToDeviceInv.invert();
1766 }
1767
1768 aSnappedPoint *= rObjectToDeviceInv;
1769
1770 return aSnappedPoint;
1771 }
1772
1773 return rPolygon.getB2DPoint(nIndex);
1774 }
1775
impAddB2DPolygonToGDIPlusGraphicsPathReal(Gdiplus::GraphicsPath & rGraphicsPath,const basegfx::B2DPolygon & rPolygon,const basegfx::B2DHomMatrix & rObjectToDevice,bool bNoLineJoin,bool bPixelSnapHairline)1776 static void impAddB2DPolygonToGDIPlusGraphicsPathReal(
1777 Gdiplus::GraphicsPath& rGraphicsPath,
1778 const basegfx::B2DPolygon& rPolygon,
1779 const basegfx::B2DHomMatrix& rObjectToDevice,
1780 bool bNoLineJoin,
1781 bool bPixelSnapHairline)
1782 {
1783 sal_uInt32 nCount(rPolygon.count());
1784 if(nCount == 0)
1785 return;
1786
1787 const sal_uInt32 nEdgeCount(rPolygon.isClosed() ? nCount : nCount - 1);
1788 if(nEdgeCount == 0)
1789 return;
1790
1791 const bool bControls(rPolygon.areControlPointsUsed());
1792 basegfx::B2DPoint aCurr(rPolygon.getB2DPoint(0));
1793 basegfx::B2DHomMatrix aObjectToDeviceInv;
1794
1795 if(bPixelSnapHairline)
1796 {
1797 aCurr = impPixelSnap(rPolygon, rObjectToDevice, aObjectToDeviceInv, 0);
1798 }
1799
1800 for(sal_uInt32 a(0); a < nEdgeCount; a++)
1801 {
1802 const sal_uInt32 nNextIndex((a + 1) % nCount);
1803 basegfx::B2DPoint aNext(rPolygon.getB2DPoint(nNextIndex));
1804 const bool b1stControlPointUsed(bControls && rPolygon.isNextControlPointUsed(a));
1805 const bool b2ndControlPointUsed(bControls && rPolygon.isPrevControlPointUsed(nNextIndex));
1806
1807 if(bPixelSnapHairline)
1808 {
1809 aNext = impPixelSnap(rPolygon, rObjectToDevice, aObjectToDeviceInv, nNextIndex);
1810 }
1811
1812 if(b1stControlPointUsed || b2ndControlPointUsed)
1813 {
1814 basegfx::B2DPoint aCa(rPolygon.getNextControlPoint(a));
1815 basegfx::B2DPoint aCb(rPolygon.getPrevControlPoint(nNextIndex));
1816
1817 // tdf#99165 MS Gdiplus cannot handle creating correct extra geometry for fat lines
1818 // with LineCap or LineJoin when a bezier segment starts or ends trivial, e.g. has
1819 // no 1st or 2nd control point, despite that these are mathematically correct definitions
1820 // (basegfx can handle that).
1821 // Caution: This error (and it's correction) might be necessary for other graphical
1822 // sub-systems in a similar way.
1823 // tdf#101026 The 1st attempt to create a mathematically correct replacement control
1824 // vector was wrong. Best alternative is one as close as possible which means short.
1825 if(!b1stControlPointUsed)
1826 {
1827 aCa = aCurr + ((aCb - aCurr) * 0.0005);
1828 }
1829 else if(!b2ndControlPointUsed)
1830 {
1831 aCb = aNext + ((aCa - aNext) * 0.0005);
1832 }
1833
1834 rGraphicsPath.AddBezier(
1835 static_cast< Gdiplus::REAL >(aCurr.getX()), static_cast< Gdiplus::REAL >(aCurr.getY()),
1836 static_cast< Gdiplus::REAL >(aCa.getX()), static_cast< Gdiplus::REAL >(aCa.getY()),
1837 static_cast< Gdiplus::REAL >(aCb.getX()), static_cast< Gdiplus::REAL >(aCb.getY()),
1838 static_cast< Gdiplus::REAL >(aNext.getX()), static_cast< Gdiplus::REAL >(aNext.getY()));
1839 }
1840 else
1841 {
1842 rGraphicsPath.AddLine(
1843 static_cast< Gdiplus::REAL >(aCurr.getX()), static_cast< Gdiplus::REAL >(aCurr.getY()),
1844 static_cast< Gdiplus::REAL >(aNext.getX()), static_cast< Gdiplus::REAL >(aNext.getY()));
1845 }
1846
1847 if(a + 1 < nEdgeCount)
1848 {
1849 aCurr = aNext;
1850
1851 if(bNoLineJoin)
1852 {
1853 rGraphicsPath.StartFigure();
1854 }
1855 }
1856 }
1857 }
1858
1859 namespace {
1860
1861 class SystemDependentData_GraphicsPath : public basegfx::SystemDependentData
1862 {
1863 private:
1864 // the path data itself
1865 std::shared_ptr<Gdiplus::GraphicsPath> mpGraphicsPath;
1866
1867 // all other values the triangulation is based on and
1868 // need to be compared with to check for data validity
1869 bool mbNoLineJoin;
1870 std::vector< double > maStroke;
1871
1872 public:
1873 SystemDependentData_GraphicsPath(
1874 std::shared_ptr<Gdiplus::GraphicsPath>& rpGraphicsPath,
1875 bool bNoLineJoin,
1876 const std::vector< double >* pStroke); // MM01
1877
1878 // read access
getGraphicsPath()1879 std::shared_ptr<Gdiplus::GraphicsPath>& getGraphicsPath() { return mpGraphicsPath; }
getNoLineJoin() const1880 bool getNoLineJoin() const { return mbNoLineJoin; }
getStroke() const1881 const std::vector< double >& getStroke() const { return maStroke; }
1882
1883 virtual sal_Int64 estimateUsageInBytes() const override;
1884 };
1885
1886 }
1887
SystemDependentData_GraphicsPath(std::shared_ptr<Gdiplus::GraphicsPath> & rpGraphicsPath,bool bNoLineJoin,const std::vector<double> * pStroke)1888 SystemDependentData_GraphicsPath::SystemDependentData_GraphicsPath(
1889 std::shared_ptr<Gdiplus::GraphicsPath>& rpGraphicsPath,
1890 bool bNoLineJoin,
1891 const std::vector< double >* pStroke)
1892 : basegfx::SystemDependentData(
1893 Application::GetSystemDependentDataManager(),
1894 basegfx::SDD_Type::SDDType_GraphicsPath),
1895 mpGraphicsPath(rpGraphicsPath),
1896 mbNoLineJoin(bNoLineJoin),
1897 maStroke()
1898 {
1899 if(nullptr != pStroke)
1900 {
1901 maStroke = *pStroke;
1902 }
1903 }
1904
estimateUsageInBytes() const1905 sal_Int64 SystemDependentData_GraphicsPath::estimateUsageInBytes() const
1906 {
1907 sal_Int64 nRetval(0);
1908
1909 if(!mpGraphicsPath)
1910 return 0;
1911
1912 const INT nPointCount(mpGraphicsPath->GetPointCount());
1913
1914 if(0 != nPointCount)
1915 {
1916 // Each point has
1917 // - 2 x sizeof(Gdiplus::REAL)
1918 // - 1 byte (see GetPathTypes in docu)
1919 nRetval = nPointCount * ((2 * sizeof(Gdiplus::REAL)) + 1);
1920 }
1921
1922 return nRetval;
1923 }
1924
drawPolyPolygon(const basegfx::B2DHomMatrix & rObjectToDevice,const basegfx::B2DPolyPolygon & rPolyPolygon,double fTransparency)1925 void WinSalGraphicsImpl::drawPolyPolygon(
1926 const basegfx::B2DHomMatrix& rObjectToDevice,
1927 const basegfx::B2DPolyPolygon& rPolyPolygon,
1928 double fTransparency)
1929 {
1930 const sal_uInt32 nCount(rPolyPolygon.count());
1931
1932 if(!mbBrush || 0 == nCount || fTransparency < 0.0 || fTransparency > 1.0)
1933 {
1934 return;
1935 }
1936
1937 Gdiplus::Graphics aGraphics(mrParent.getHDC());
1938 const sal_uInt8 nAlpha(sal_uInt8(255) - static_cast<sal_uInt8>(basegfx::fround(fTransparency * 255.0)));
1939 const Gdiplus::Color aTestColor(nAlpha, maFillColor.GetRed(), maFillColor.GetGreen(), maFillColor.GetBlue());
1940 const Gdiplus::SolidBrush aSolidBrush(aTestColor.GetValue());
1941
1942 // Set full (Object-to-Device) transformation - if used
1943 if(rObjectToDevice.isIdentity())
1944 {
1945 aGraphics.ResetTransform();
1946 }
1947 else
1948 {
1949 Gdiplus::Matrix aMatrix;
1950
1951 aMatrix.SetElements(
1952 rObjectToDevice.get(0, 0),
1953 rObjectToDevice.get(1, 0),
1954 rObjectToDevice.get(0, 1),
1955 rObjectToDevice.get(1, 1),
1956 rObjectToDevice.get(0, 2),
1957 rObjectToDevice.get(1, 2));
1958 aGraphics.SetTransform(&aMatrix);
1959 }
1960
1961 // prepare local instance of Gdiplus::GraphicsPath
1962 std::shared_ptr<Gdiplus::GraphicsPath> pGraphicsPath;
1963
1964 // try to access buffered data
1965 std::shared_ptr<SystemDependentData_GraphicsPath> pSystemDependentData_GraphicsPath(
1966 rPolyPolygon.getSystemDependentData<SystemDependentData_GraphicsPath>(basegfx::SDD_Type::SDDType_GraphicsPath));
1967
1968 if(pSystemDependentData_GraphicsPath)
1969 {
1970 // copy buffered data
1971 pGraphicsPath = pSystemDependentData_GraphicsPath->getGraphicsPath();
1972 }
1973 else
1974 {
1975 // Note: In principle we could use the same buffered geometry at line
1976 // and fill polygons. Checked that in a first try, used
1977 // GraphicsPath::AddPath from Gdiplus combined with below used
1978 // StartFigure/CloseFigure, worked well (thus the line-draw version
1979 // may create non-closed partial Polygon data).
1980 //
1981 // But in current reality it gets not used due to e.g.
1982 // SdrPathPrimitive2D::create2DDecomposition creating transformed
1983 // line and fill polygon-primitives (what could be changed).
1984 //
1985 // There will probably be more hindrances here in other rendering paths
1986 // which could all be found - intention to do this would be: Use more
1987 // transformations, less modifications of B2DPolygons/B2DPolyPolygons.
1988 //
1989 // A fix for SdrPathPrimitive2D would be to create the sub-geometry
1990 // and embed into a TransformPrimitive2D containing the transformation.
1991 //
1992 // A 2nd problem is that the NoLineJoin mode (basegfx::B2DLineJoin::NONE
1993 // && !bIsHairline) creates polygon fill infos that are not reusable
1994 // for the fill case (see ::drawPolyLine below) - thus we would need a
1995 // bool and/or two system-dependent paths buffered - doable, but complicated.
1996 //
1997 // All in all: Make B2DPolyPolygon a SystemDependentDataProvider and buffer
1998 // the whole to-be-filled PolyPolygon independent from evtl. line-polygon
1999 // (at least for now...)
2000
2001 // create data
2002 pGraphicsPath = std::make_shared<Gdiplus::GraphicsPath>();
2003
2004 for(sal_uInt32 a(0); a < nCount; a++)
2005 {
2006 if(0 != a)
2007 {
2008 // #i101491# not needed for first run
2009 pGraphicsPath->StartFigure();
2010 }
2011
2012 impAddB2DPolygonToGDIPlusGraphicsPathReal(
2013 *pGraphicsPath,
2014 rPolyPolygon.getB2DPolygon(a),
2015 rObjectToDevice, // not used due to the two 'false' values below, but to not forget later
2016 false,
2017 false);
2018
2019 pGraphicsPath->CloseFigure();
2020 }
2021
2022 // add to buffering mechanism
2023 rPolyPolygon.addOrReplaceSystemDependentData<SystemDependentData_GraphicsPath>(
2024 pGraphicsPath,
2025 false,
2026 nullptr);
2027 }
2028
2029 if(mrParent.getAntiAlias())
2030 {
2031 aGraphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
2032 }
2033 else
2034 {
2035 aGraphics.SetSmoothingMode(Gdiplus::SmoothingModeNone);
2036 }
2037
2038 if(mrParent.isPrinter())
2039 {
2040 // #i121591#
2041 // Normally GdiPlus should not be used for printing at all since printers cannot
2042 // print transparent filled polygon geometry and normally this does not happen
2043 // since OutputDevice::RemoveTransparenciesFromMetaFile is used as preparation
2044 // and no transparent parts should remain for printing. But this can be overridden
2045 // by the user and thus happens. This call can only come (currently) from
2046 // OutputDevice::DrawTransparent, see comments there with the same TaskID.
2047 // If it is used, the mapping for the printer is wrong and needs to be corrected. I
2048 // checked that there is *no* transformation set and estimated that a stable factor
2049 // dependent of the printer's DPI is used. Create and set a transformation here to
2050 // correct this.
2051 const Gdiplus::REAL aDpiX(aGraphics.GetDpiX());
2052 const Gdiplus::REAL aDpiY(aGraphics.GetDpiY());
2053
2054 // Now the transformation maybe/is already used (see above), so do
2055 // modify it without resetting to not destroy it.
2056 // I double-checked with MS docu that Gdiplus::MatrixOrderAppend does what
2057 // we need - in our notation, would be a multiply from left to execute
2058 // current transform first and this scale last.
2059 // I tried to trigger this code using Print from the menu and various
2060 // targets, but got no hit, thus maybe obsolete anyways. If someone knows
2061 // more, feel free to remove it.
2062 // One more hint: This *may* also be needed now in ::drawPolyLine below
2063 // since it also uses transformations now.
2064 //
2065 // aGraphics.ResetTransform();
2066
2067 aGraphics.ScaleTransform(
2068 Gdiplus::REAL(100.0) / aDpiX,
2069 Gdiplus::REAL(100.0) / aDpiY,
2070 Gdiplus::MatrixOrderAppend);
2071 }
2072
2073 // use created or buffered data
2074 aGraphics.FillPath(
2075 &aSolidBrush,
2076 &(*pGraphicsPath));
2077 }
2078
drawPolyLine(const basegfx::B2DHomMatrix & rObjectToDevice,const basegfx::B2DPolygon & rPolygon,double fTransparency,double fLineWidth,const std::vector<double> * pStroke,basegfx::B2DLineJoin eLineJoin,css::drawing::LineCap eLineCap,double fMiterMinimumAngle,bool bPixelSnapHairline)2079 bool WinSalGraphicsImpl::drawPolyLine(
2080 const basegfx::B2DHomMatrix& rObjectToDevice,
2081 const basegfx::B2DPolygon& rPolygon,
2082 double fTransparency,
2083 double fLineWidth,
2084 const std::vector< double >* pStroke, // MM01
2085 basegfx::B2DLineJoin eLineJoin,
2086 css::drawing::LineCap eLineCap,
2087 double fMiterMinimumAngle,
2088 bool bPixelSnapHairline)
2089 {
2090 // MM01 check done for simple reasons
2091 if(!mbPen || !rPolygon.count() || fTransparency < 0.0 || fTransparency > 1.0)
2092 {
2093 return true;
2094 }
2095
2096 // need to check/handle LineWidth when ObjectToDevice transformation is used
2097 const bool bObjectToDeviceIsIdentity(rObjectToDevice.isIdentity());
2098 const bool bIsHairline(fLineWidth == 0);
2099
2100 // tdf#124848 calculate-back logical LineWidth for a hairline
2101 // since this implementation hands over the transformation to
2102 // the graphic sub-system
2103 if(bIsHairline)
2104 {
2105 fLineWidth = 1.0;
2106
2107 if(!bObjectToDeviceIsIdentity)
2108 {
2109 basegfx::B2DHomMatrix aObjectToDeviceInv(rObjectToDevice);
2110 aObjectToDeviceInv.invert();
2111 fLineWidth = (aObjectToDeviceInv * basegfx::B2DVector(fLineWidth, 0)).getLength();
2112 }
2113 }
2114
2115 Gdiplus::Graphics aGraphics(mrParent.getHDC());
2116 const sal_uInt8 nAlpha = static_cast<sal_uInt8>(basegfx::fround( 255 * (1.0 - fTransparency) ));
2117 const Gdiplus::Color aTestColor(nAlpha, maLineColor.GetRed(), maLineColor.GetGreen(), maLineColor.GetBlue());
2118 Gdiplus::Pen aPen(aTestColor.GetValue(), Gdiplus::REAL(fLineWidth));
2119 bool bNoLineJoin(false);
2120
2121 // Set full (Object-to-Device) transformation - if used
2122 if(bObjectToDeviceIsIdentity)
2123 {
2124 aGraphics.ResetTransform();
2125 }
2126 else
2127 {
2128 Gdiplus::Matrix aMatrix;
2129
2130 aMatrix.SetElements(
2131 rObjectToDevice.get(0, 0),
2132 rObjectToDevice.get(1, 0),
2133 rObjectToDevice.get(0, 1),
2134 rObjectToDevice.get(1, 1),
2135 rObjectToDevice.get(0, 2),
2136 rObjectToDevice.get(1, 2));
2137 aGraphics.SetTransform(&aMatrix);
2138 }
2139
2140 switch(eLineJoin)
2141 {
2142 case basegfx::B2DLineJoin::NONE :
2143 {
2144 if(!bIsHairline)
2145 {
2146 bNoLineJoin = true;
2147 }
2148 break;
2149 }
2150 case basegfx::B2DLineJoin::Bevel :
2151 {
2152 aPen.SetLineJoin(Gdiplus::LineJoinBevel);
2153 break;
2154 }
2155 case basegfx::B2DLineJoin::Miter :
2156 {
2157 const Gdiplus::REAL aMiterLimit(1.0/sin(fMiterMinimumAngle/2.0));
2158
2159 aPen.SetMiterLimit(aMiterLimit);
2160 // tdf#99165 MS's LineJoinMiter creates non standard conform miter additional
2161 // graphics, somewhere clipped in some distance from the edge point, dependent
2162 // of MiterLimit. The more default-like option is LineJoinMiterClipped, so use
2163 // that instead
2164 aPen.SetLineJoin(Gdiplus::LineJoinMiterClipped);
2165 break;
2166 }
2167 case basegfx::B2DLineJoin::Round :
2168 {
2169 aPen.SetLineJoin(Gdiplus::LineJoinRound);
2170 break;
2171 }
2172 }
2173
2174 switch(eLineCap)
2175 {
2176 default: /*css::drawing::LineCap_BUTT*/
2177 {
2178 // nothing to do
2179 break;
2180 }
2181 case css::drawing::LineCap_ROUND:
2182 {
2183 aPen.SetStartCap(Gdiplus::LineCapRound);
2184 aPen.SetEndCap(Gdiplus::LineCapRound);
2185 break;
2186 }
2187 case css::drawing::LineCap_SQUARE:
2188 {
2189 aPen.SetStartCap(Gdiplus::LineCapSquare);
2190 aPen.SetEndCap(Gdiplus::LineCapSquare);
2191 break;
2192 }
2193 }
2194
2195 // prepare local instance of Gdiplus::GraphicsPath
2196 std::shared_ptr<Gdiplus::GraphicsPath> pGraphicsPath;
2197
2198 // try to access buffered data
2199 std::shared_ptr<SystemDependentData_GraphicsPath> pSystemDependentData_GraphicsPath(
2200 rPolygon.getSystemDependentData<SystemDependentData_GraphicsPath>(basegfx::SDD_Type::SDDType_GraphicsPath));
2201
2202 // MM01 need to do line dashing as fallback stuff here now
2203 const double fDotDashLength(nullptr != pStroke ? std::accumulate(pStroke->begin(), pStroke->end(), 0.0) : 0.0);
2204 const bool bStrokeUsed(0.0 != fDotDashLength);
2205 assert(!bStrokeUsed || (bStrokeUsed && pStroke));
2206
2207 // MM01 decide if to stroke directly
2208 static bool bDoDirectGDIPlusStroke(true);
2209
2210 // activate to stroke directly
2211 if(bDoDirectGDIPlusStroke && bStrokeUsed)
2212 {
2213 // tdf#124848 the fix of tdf#130478 that was needed here before
2214 // gets much easier when already handling the hairline case above,
2215 // the back-calculated logical linewidth is already here, just use it.
2216 // Still be careful - a zero LineWidth *should* not happen, but...
2217 std::vector<Gdiplus::REAL> aDashArray(pStroke->size());
2218 const double fFactor(fLineWidth == 0 ? 1.0 : 1.0 / fLineWidth);
2219
2220 // tdf#134128. ODF adds caps to the dashes and dots, but GDI makes caps from the
2221 // dash or dot themselves. We tweak aDashArray to look the same in GDI (e.g. Impress edit mode)
2222 // and other renders (e.g. Impress slide show), while keeping the total length of the
2223 // pattern.
2224 // Patterns are always a sequence dash space dash space ...
2225 if (eLineCap != css::drawing::LineCap_BUTT)
2226 {
2227 size_t nSize = pStroke->size();
2228 // We want to treat dash and space in pairs. There should be no odd size. If so, we ignore
2229 // last item.
2230 nSize /= 2;
2231 for(size_t a(0); a < nSize; a++)
2232 {
2233 double fDashLengthRel = (*pStroke)[2 * a] * fFactor;
2234 double fSpaceLengthRel = (*pStroke)[2 * a + 1] * fFactor;
2235 // GDI allows only positive lengths for space, Skia negative lengths too. Thus the
2236 // appearance is different, in case space is too small.
2237 double fCorrect = fSpaceLengthRel - 1.0 <= 0 ? fSpaceLengthRel - 0.01 : 1.0;
2238 aDashArray[2 * a] = Gdiplus::REAL(fDashLengthRel + fCorrect);
2239 aDashArray[2 * a + 1] = Gdiplus::REAL(fSpaceLengthRel - fCorrect);
2240 }
2241 }
2242 else
2243 {
2244 for(size_t a(0); a < pStroke->size(); a++)
2245 {
2246 aDashArray[a] = Gdiplus::REAL((*pStroke)[a] * fFactor);
2247 }
2248 }
2249 if (eLineCap == css::drawing::LineCap_ROUND)
2250 aPen.SetDashCap(Gdiplus::DashCapRound);
2251 else
2252 aPen.SetDashCap(Gdiplus::DashCapFlat); // "square" doesn't exist in Gdiplus
2253 aPen.SetDashOffset(Gdiplus::REAL(0.0));
2254 aPen.SetDashPattern(aDashArray.data(), aDashArray.size());
2255 }
2256
2257 if(!bDoDirectGDIPlusStroke && pSystemDependentData_GraphicsPath)
2258 {
2259 // MM01 - check on stroke change. Used against not used, or if oth used,
2260 // equal or different? Triangulation geometry creation depends heavily
2261 // on stroke, independent of being transformation independent
2262 const bool bStrokeWasUsed(!pSystemDependentData_GraphicsPath->getStroke().empty());
2263
2264 if(bStrokeWasUsed != bStrokeUsed
2265 || (bStrokeUsed && *pStroke != pSystemDependentData_GraphicsPath->getStroke()))
2266 {
2267 // data invalid, forget
2268 pSystemDependentData_GraphicsPath.reset();
2269 }
2270 }
2271
2272 if(pSystemDependentData_GraphicsPath)
2273 {
2274 // check data validity
2275 if (pSystemDependentData_GraphicsPath->getNoLineJoin() != bNoLineJoin
2276 || bPixelSnapHairline /*tdf#124700*/)
2277 {
2278 // data invalid, forget
2279 pSystemDependentData_GraphicsPath.reset();
2280 }
2281 }
2282
2283 if(pSystemDependentData_GraphicsPath)
2284 {
2285 // copy buffered data
2286 pGraphicsPath = pSystemDependentData_GraphicsPath->getGraphicsPath();
2287 }
2288 else
2289 {
2290 // fill data of buffered data
2291 pGraphicsPath = std::make_shared<Gdiplus::GraphicsPath>();
2292
2293 if(!bDoDirectGDIPlusStroke && bStrokeUsed)
2294 {
2295 // MM01 need to do line dashing as fallback stuff here now
2296 basegfx::B2DPolyPolygon aPolyPolygonLine;
2297
2298 // apply LineStyle
2299 basegfx::utils::applyLineDashing(
2300 rPolygon, // source
2301 *pStroke, // pattern
2302 &aPolyPolygonLine, // target for lines
2303 nullptr, // target for gaps
2304 fDotDashLength); // full length if available
2305
2306 // MM01 checked/verified, ok
2307 for(sal_uInt32 a(0); a < aPolyPolygonLine.count(); a++)
2308 {
2309 const basegfx::B2DPolygon aPolyLine(aPolyPolygonLine.getB2DPolygon(a));
2310 pGraphicsPath->StartFigure();
2311 impAddB2DPolygonToGDIPlusGraphicsPathReal(
2312 *pGraphicsPath,
2313 aPolyLine,
2314 rObjectToDevice,
2315 bNoLineJoin,
2316 bPixelSnapHairline);
2317 }
2318 }
2319 else
2320 {
2321 // no line dashing or direct stroke, just copy
2322 impAddB2DPolygonToGDIPlusGraphicsPathReal(
2323 *pGraphicsPath,
2324 rPolygon,
2325 rObjectToDevice,
2326 bNoLineJoin,
2327 bPixelSnapHairline);
2328
2329 if(rPolygon.isClosed() && !bNoLineJoin)
2330 {
2331 // #i101491# needed to create the correct line joins
2332 pGraphicsPath->CloseFigure();
2333 }
2334 }
2335
2336 // add to buffering mechanism
2337 if (!bPixelSnapHairline /*tdf#124700*/)
2338 {
2339 rPolygon.addOrReplaceSystemDependentData<SystemDependentData_GraphicsPath>(
2340 pGraphicsPath,
2341 bNoLineJoin,
2342 pStroke);
2343 }
2344 }
2345
2346 if(mrParent.getAntiAlias())
2347 {
2348 aGraphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
2349 }
2350 else
2351 {
2352 aGraphics.SetSmoothingMode(Gdiplus::SmoothingModeNone);
2353 }
2354
2355 if(mrParent.isPrinter())
2356 {
2357 // tdf#122384 As mentioned above in WinSalGraphicsImpl::drawPolyPolygon
2358 // (look for 'One more hint: This *may* also be needed now in'...).
2359 // See comments in same spot above *urgently* before doing changes here,
2360 // these comments are *still fully valid* at this place (!)
2361 const Gdiplus::REAL aDpiX(aGraphics.GetDpiX());
2362 const Gdiplus::REAL aDpiY(aGraphics.GetDpiY());
2363
2364 aGraphics.ScaleTransform(
2365 Gdiplus::REAL(100.0) / aDpiX,
2366 Gdiplus::REAL(100.0) / aDpiY,
2367 Gdiplus::MatrixOrderAppend);
2368 }
2369
2370 aGraphics.DrawPath(
2371 &aPen,
2372 &(*pGraphicsPath));
2373
2374 return true;
2375 }
2376
paintToGdiPlus(Gdiplus::Graphics & rGraphics,const SalTwoRect & rTR,Gdiplus::Bitmap & rBitmap)2377 static void paintToGdiPlus(
2378 Gdiplus::Graphics& rGraphics,
2379 const SalTwoRect& rTR,
2380 Gdiplus::Bitmap& rBitmap)
2381 {
2382 Gdiplus::Rect aDestRect{ INT(rTR.mnDestX), INT(rTR.mnDestY),
2383 INT(rTR.mnDestWidth), INT(rTR.mnDestHeight) };
2384
2385 rGraphics.DrawImage(
2386 &rBitmap,
2387 aDestRect,
2388 rTR.mnSrcX, rTR.mnSrcY, rTR.mnSrcWidth, rTR.mnSrcHeight,
2389 Gdiplus::UnitPixel,
2390 nullptr);
2391 }
2392
setInterpolationMode(Gdiplus::Graphics & rGraphics,tools::Long rSrcWidth,tools::Long rDestWidth,tools::Long rSrcHeight,tools::Long rDestHeight)2393 static void setInterpolationMode(
2394 Gdiplus::Graphics& rGraphics,
2395 tools::Long rSrcWidth,
2396 tools::Long rDestWidth,
2397 tools::Long rSrcHeight,
2398 tools::Long rDestHeight)
2399 {
2400 const bool bSameWidth(rSrcWidth == rDestWidth);
2401 const bool bSameHeight(rSrcHeight == rDestHeight);
2402
2403 if(bSameWidth && bSameHeight)
2404 {
2405 rGraphics.SetInterpolationMode(Gdiplus::InterpolationModeInvalid);
2406 }
2407 else if(rDestWidth > rSrcWidth && rDestHeight > rSrcHeight)
2408 {
2409 rGraphics.SetInterpolationMode(Gdiplus::InterpolationModeDefault);
2410 }
2411 else if(rDestWidth < rSrcWidth && rDestHeight < rSrcHeight)
2412 {
2413 rGraphics.SetInterpolationMode(Gdiplus::InterpolationModeBicubic);
2414 }
2415 else
2416 {
2417 rGraphics.SetInterpolationMode(Gdiplus::InterpolationModeDefault);
2418 }
2419 }
2420
TryDrawBitmapGDIPlus(const SalTwoRect & rTR,const SalBitmap & rSrcBitmap)2421 bool WinSalGraphicsImpl::TryDrawBitmapGDIPlus(const SalTwoRect& rTR, const SalBitmap& rSrcBitmap)
2422 {
2423 if(!rTR.mnSrcWidth || !rTR.mnSrcHeight || !rTR.mnDestWidth || !rTR.mnDestHeight)
2424 return false;
2425
2426 assert(dynamic_cast<const WinSalBitmap*>(&rSrcBitmap));
2427
2428 const WinSalBitmap& rSalBitmap = static_cast< const WinSalBitmap& >(rSrcBitmap);
2429 std::shared_ptr< Gdiplus::Bitmap > aARGB(rSalBitmap.ImplGetGdiPlusBitmap());
2430
2431 if(!aARGB)
2432 return false;
2433
2434 Gdiplus::Graphics aGraphics(mrParent.getHDC());
2435
2436 setInterpolationMode(
2437 aGraphics,
2438 rTR.mnSrcWidth,
2439 rTR.mnDestWidth,
2440 rTR.mnSrcHeight,
2441 rTR.mnDestHeight);
2442
2443 paintToGdiPlus(
2444 aGraphics,
2445 rTR,
2446 *aARGB);
2447
2448 return true;
2449 }
2450
drawAlphaBitmap(const SalTwoRect & rTR,const SalBitmap & rSrcBitmap,const SalBitmap & rAlphaBmp)2451 bool WinSalGraphicsImpl::drawAlphaBitmap(
2452 const SalTwoRect& rTR,
2453 const SalBitmap& rSrcBitmap,
2454 const SalBitmap& rAlphaBmp)
2455 {
2456 if(!rTR.mnSrcWidth || !rTR.mnSrcHeight || !rTR.mnDestWidth || !rTR.mnDestHeight)
2457 return false;
2458
2459 // neither GDI nor GDI+ can properly blend to a surface that has alpha
2460 if (GetBitCount() == 32)
2461 return false;
2462
2463 assert(dynamic_cast<const WinSalBitmap*>(&rSrcBitmap));
2464 assert(dynamic_cast<const WinSalBitmap*>(&rAlphaBmp));
2465
2466 const WinSalBitmap& rSalBitmap = static_cast< const WinSalBitmap& >(rSrcBitmap);
2467 const WinSalBitmap& rSalAlpha = static_cast< const WinSalBitmap& >(rAlphaBmp);
2468 std::shared_ptr< Gdiplus::Bitmap > aARGB(rSalBitmap.ImplGetGdiPlusBitmap(&rSalAlpha));
2469
2470 if(!aARGB)
2471 return false;
2472
2473 Gdiplus::Graphics aGraphics(mrParent.getHDC());
2474
2475 setInterpolationMode(
2476 aGraphics,
2477 rTR.mnSrcWidth,
2478 rTR.mnDestWidth,
2479 rTR.mnSrcHeight,
2480 rTR.mnDestHeight);
2481
2482 paintToGdiPlus(
2483 aGraphics,
2484 rTR,
2485 *aARGB);
2486
2487 return true;
2488 }
2489
drawTransformedBitmap(const basegfx::B2DPoint & rNull,const basegfx::B2DPoint & rX,const basegfx::B2DPoint & rY,const SalBitmap & rSourceBitmap,const SalBitmap * pAlphaBitmap,double fAlpha)2490 bool WinSalGraphicsImpl::drawTransformedBitmap(
2491 const basegfx::B2DPoint& rNull,
2492 const basegfx::B2DPoint& rX,
2493 const basegfx::B2DPoint& rY,
2494 const SalBitmap& rSourceBitmap,
2495 const SalBitmap* pAlphaBitmap,
2496 double fAlpha)
2497 {
2498 assert(dynamic_cast<const WinSalBitmap*>(&rSourceBitmap));
2499 assert(!pAlphaBitmap || dynamic_cast<const WinSalBitmap*>(pAlphaBitmap));
2500
2501 if( fAlpha != 1.0 )
2502 return false;
2503
2504 const WinSalBitmap& rSalBitmap = static_cast< const WinSalBitmap& >(rSourceBitmap);
2505 const WinSalBitmap* pSalAlpha = static_cast< const WinSalBitmap* >(pAlphaBitmap);
2506 std::shared_ptr< Gdiplus::Bitmap > aARGB(rSalBitmap.ImplGetGdiPlusBitmap(pSalAlpha));
2507
2508 if(!aARGB)
2509 return false;
2510
2511 const tools::Long nSrcWidth(aARGB->GetWidth());
2512 const tools::Long nSrcHeight(aARGB->GetHeight());
2513
2514 if(!nSrcWidth || !nSrcHeight)
2515 return true;
2516
2517 const tools::Long nDestWidth(basegfx::fround<tools::Long>(basegfx::B2DVector(rX - rNull).getLength()));
2518 const tools::Long nDestHeight(basegfx::fround<tools::Long>(basegfx::B2DVector(rY - rNull).getLength()));
2519
2520 if(!nDestWidth || !nDestHeight)
2521 return true;
2522
2523 Gdiplus::Graphics aGraphics(mrParent.getHDC());
2524 Gdiplus::PointF aDestPoints[3];
2525 Gdiplus::ImageAttributes aAttributes;
2526
2527 setInterpolationMode(
2528 aGraphics,
2529 nSrcWidth,
2530 nDestWidth,
2531 nSrcHeight,
2532 nDestHeight);
2533
2534 // this mode is only capable of drawing the whole bitmap to a parallelogram
2535 aDestPoints[0].X = Gdiplus::REAL(rNull.getX());
2536 aDestPoints[0].Y = Gdiplus::REAL(rNull.getY());
2537 aDestPoints[1].X = Gdiplus::REAL(rX.getX());
2538 aDestPoints[1].Y = Gdiplus::REAL(rX.getY());
2539 aDestPoints[2].X = Gdiplus::REAL(rY.getX());
2540 aDestPoints[2].Y = Gdiplus::REAL(rY.getY());
2541
2542 aAttributes.SetWrapMode(Gdiplus::WrapModeTileFlipXY);
2543
2544 aGraphics.DrawImage(
2545 aARGB.get(),
2546 aDestPoints,
2547 3,
2548 Gdiplus::REAL(0.0),
2549 Gdiplus::REAL(0.0),
2550 Gdiplus::REAL(nSrcWidth),
2551 Gdiplus::REAL(nSrcHeight),
2552 Gdiplus::UnitPixel,
2553 &aAttributes);
2554
2555 return true;
2556 }
2557
hasFastDrawTransformedBitmap() const2558 bool WinSalGraphicsImpl::hasFastDrawTransformedBitmap() const
2559 {
2560 return false;
2561 }
2562
drawGradient(const tools::PolyPolygon &,const Gradient &)2563 bool WinSalGraphicsImpl::drawGradient(const tools::PolyPolygon& /*rPolygon*/,
2564 const Gradient& /*rGradient*/)
2565 {
2566 return false;
2567 }
2568
implDrawGradient(basegfx::B2DPolyPolygon const &,SalGradient const &)2569 bool WinSalGraphicsImpl::implDrawGradient(basegfx::B2DPolyPolygon const & /*rPolyPolygon*/,
2570 SalGradient const & /*rGradient*/)
2571 {
2572 return false;
2573 }
2574
supportsOperation(OutDevSupportType) const2575 bool WinSalGraphicsImpl::supportsOperation(OutDevSupportType /*eType*/) const
2576 {
2577 return false;
2578 }
2579
2580 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
2581