1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include <sal/types.h>
21 #include <osl/diagnose.h>
22 #include <rtl/math.hxx>
23 #include <basegfx/polygon/b2dpolygontools.hxx>
24 #include <tools/helpers.hxx>
25 #include <officecfg/Office/Common.hxx>
27 #include <vcl/BitmapTools.hxx>
28 #include <vcl/metaact.hxx>
29 #include <vcl/print.hxx>
30 #include <vcl/settings.hxx>
31 #include <vcl/svapp.hxx>
32 #include <vcl/virdev.hxx>
33 #include <vcl/BitmapWriteAccess.hxx>
34 #include <pdf/pdfwriter_impl.hxx>
40 #define MAX_TILE_WIDTH 1024
41 #define MAX_TILE_HEIGHT 1024
46 * Perform a safe approximation of a polygon from double-precision
47 * coordinates to integer coordinates, to ensure that it has at least 2
48 * pixels in both X and Y directions.
50 tools::Polygon
toPolygon( const basegfx::B2DPolygon
& rPoly
)
52 basegfx::B2DRange aRange
= rPoly
.getB2DRange();
53 double fW
= aRange
.getWidth(), fH
= aRange
.getHeight();
54 if (0.0 < fW
&& 0.0 < fH
&& (fW
<= 1.0 || fH
<= 1.0))
56 // This polygon not empty but is too small to display. Approximate it
57 // with a rectangle large enough to be displayed.
58 double nX
= aRange
.getMinX(), nY
= aRange
.getMinY();
59 double nW
= std::max
<double>(1.0, rtl::math::round(fW
));
60 double nH
= std::max
<double>(1.0, rtl::math::round(fH
));
62 tools::Polygon aTarget
;
63 aTarget
.Insert(0, Point(nX
, nY
));
64 aTarget
.Insert(1, Point(nX
+nW
, nY
));
65 aTarget
.Insert(2, Point(nX
+nW
, nY
+nH
));
66 aTarget
.Insert(3, Point(nX
, nY
+nH
));
67 aTarget
.Insert(4, Point(nX
, nY
));
70 return tools::Polygon(rPoly
);
73 tools::PolyPolygon
toPolyPolygon( const basegfx::B2DPolyPolygon
& rPolyPoly
)
75 tools::PolyPolygon aTarget
;
76 for (auto const& rB2DPolygon
: rPolyPoly
)
77 aTarget
.Insert(toPolygon(rB2DPolygon
));
83 // Caution: This method is nearly the same as
84 // void OutputDevice::DrawPolyPolygon( const basegfx::B2DPolyPolygon& rB2DPolyPoly )
85 // so when changes are made here do not forget to make changes there, too
87 void OutputDevice::DrawTransparent(
88 const basegfx::B2DHomMatrix
& rObjectTransform
,
89 const basegfx::B2DPolyPolygon
& rB2DPolyPoly
,
92 assert(!is_double_buffered_window());
94 // AW: Do NOT paint empty PolyPolygons
95 if(!rB2DPolyPoly
.count())
99 if( !mpGraphics
&& !AcquireGraphics() )
103 if( mbInitClipRegion
)
106 if( mbOutputClipped
)
109 if( mbInitLineColor
)
112 if( mbInitFillColor
)
115 if (RasterOp::OverPaint
== GetRasterOp())
117 // b2dpolygon support not implemented yet on non-UNX platforms
118 basegfx::B2DPolyPolygon
aB2DPolyPolygon(rB2DPolyPoly
);
120 // ensure it is closed
121 if(!aB2DPolyPolygon
.isClosed())
123 // maybe assert, prevents buffering due to making a copy
124 aB2DPolyPolygon
.setClosed( true );
127 // create ObjectToDevice transformation
128 const basegfx::B2DHomMatrix
aFullTransform(ImplGetDeviceTransformation() * rObjectTransform
);
129 // TODO: this must not drop transparency for mpAlphaVDev case, but instead use premultiplied
130 // alpha... but that requires using premultiplied alpha also for already drawn data
131 const double fAdjustedTransparency
= mpAlphaVDev
? 0 : fTransparency
;
135 mpGraphics
->DrawPolyPolygon(
138 fAdjustedTransparency
,
144 const bool bPixelSnapHairline(mnAntialiasing
& AntialiasingFlags::PixelSnapHairline
);
146 for(auto const& rPolygon
: std::as_const(aB2DPolyPolygon
))
148 mpGraphics
->DrawPolyLine(
151 fAdjustedTransparency
,
152 0.0, // tdf#124848 hairline
154 basegfx::B2DLineJoin::NONE
,
155 css::drawing::LineCap_BUTT
,
156 basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default
164 // tdf#119843 need transformed Polygon here
165 basegfx::B2DPolyPolygon
aB2DPolyPoly(rB2DPolyPoly
);
166 aB2DPolyPoly
.transform(rObjectTransform
);
167 mpMetaFile
->AddAction(
168 new MetaTransparentAction(
169 tools::PolyPolygon(aB2DPolyPoly
),
170 static_cast< sal_uInt16
>(fTransparency
* 100.0)));
174 mpAlphaVDev
->DrawTransparent(rObjectTransform
, rB2DPolyPoly
, fTransparency
);
179 // fallback to old polygon drawing if needed
180 // tdf#119843 need transformed Polygon here
181 basegfx::B2DPolyPolygon
aB2DPolyPoly(rB2DPolyPoly
);
182 aB2DPolyPoly
.transform(rObjectTransform
);
184 toPolyPolygon(aB2DPolyPoly
),
185 static_cast<sal_uInt16
>(fTransparency
* 100.0));
188 bool OutputDevice::DrawTransparentNatively ( const tools::PolyPolygon
& rPolyPoly
,
189 sal_uInt16 nTransparencePercent
)
191 assert(!is_double_buffered_window());
196 #if defined UNX && ! defined MACOSX && ! defined IOS
200 // workaround bad dithering on remote displaying when using GDI+ with toolbar button highlighting
201 && !rPolyPoly
.IsRect()
205 // prepare the graphics device
206 if( mbInitClipRegion
)
209 if( mbOutputClipped
)
212 if( mbInitLineColor
)
215 if( mbInitFillColor
)
218 // get the polygon in device coordinates
219 basegfx::B2DPolyPolygon
aB2DPolyPolygon(rPolyPoly
.getB2DPolyPolygon());
220 const basegfx::B2DHomMatrix
aTransform(ImplGetDeviceTransformation());
222 const double fTransparency
= 0.01 * nTransparencePercent
;
226 // CAUTION: Only non printing (pixel-renderer) VCL commands from OutputDevices
227 // should be used when printing. Normally this is avoided by the printer being
228 // non-AAed and thus e.g. on WIN GdiPlus calls are not used. It may be necessary
229 // to figure out a way of moving this code to its own function that is
230 // overridden by the Print class, which will mean we deliberately override the
231 // functionality and we use the fallback some lines below (which is not very good,
232 // though. For now, WinSalGraphics::drawPolyPolygon will detect printer usage and
233 // correct the wrong mapping (see there for details)
234 mpGraphics
->DrawPolyPolygon(
244 // disable the fill color for now
245 mpGraphics
->SetFillColor();
247 // draw the border line
248 const bool bPixelSnapHairline(mnAntialiasing
& AntialiasingFlags::PixelSnapHairline
);
250 for(auto const& rPolygon
: std::as_const(aB2DPolyPolygon
))
252 bDrawn
= mpGraphics
->DrawPolyLine(
256 0.0, // tdf#124848 hairline
258 basegfx::B2DLineJoin::NONE
,
259 css::drawing::LineCap_BUTT
,
260 basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default
265 // prepare to restore the fill color
266 mbInitFillColor
= mbFillColor
;
273 void OutputDevice::EmulateDrawTransparent ( const tools::PolyPolygon
& rPolyPoly
,
274 sal_uInt16 nTransparencePercent
)
276 // #110958# Disable alpha VDev, we perform the necessary
277 VirtualDevice
* pOldAlphaVDev
= mpAlphaVDev
;
279 // operation explicitly further below.
281 mpAlphaVDev
= nullptr;
283 GDIMetaFile
* pOldMetaFile
= mpMetaFile
;
284 mpMetaFile
= nullptr;
286 tools::PolyPolygon
aPolyPoly( LogicToPixel( rPolyPoly
) );
287 tools::Rectangle
aPolyRect( aPolyPoly
.GetBoundRect() );
288 tools::Rectangle
aDstRect( Point(), GetOutputSizePixel() );
290 aDstRect
.Intersection( aPolyRect
);
292 ClipToPaintRegion( aDstRect
);
294 if( !aDstRect
.IsEmpty() )
298 // #i66849# Added fast path for exactly rectangular
300 // #i83087# Naturally, system alpha blending cannot
301 // work with separate alpha VDev
302 if( !mpAlphaVDev
&& aPolyPoly
.IsRect() )
304 // setup Graphics only here (other cases delegate
305 // to basic OutDev methods)
306 if ( mbInitClipRegion
)
309 if ( mbInitLineColor
)
312 if ( mbInitFillColor
)
315 tools::Rectangle
aLogicPolyRect( rPolyPoly
.GetBoundRect() );
316 tools::Rectangle
aPixelRect( ImplLogicToDevicePixel( aLogicPolyRect
) );
318 if( !mbOutputClipped
)
320 bDrawn
= mpGraphics
->DrawAlphaRect( aPixelRect
.Left(), aPixelRect
.Top(),
321 // #i98405# use methods with small g, else one pixel too much will be painted.
322 // This is because the source is a polygon which when painted would not paint
323 // the rightmost and lowest pixel line(s), so use one pixel less for the
325 aPixelRect
.getOpenWidth(), aPixelRect
.getOpenHeight(),
326 sal::static_int_cast
<sal_uInt8
>(nTransparencePercent
),
337 ScopedVclPtrInstance
< VirtualDevice
> aVDev(*this);
338 const Size
aDstSz( aDstRect
.GetSize() );
339 const sal_uInt8 cTrans
= basegfx::fround
<sal_uInt8
>(nTransparencePercent
* 2.55);
341 if( aDstRect
.Left() || aDstRect
.Top() )
342 aPolyPoly
.Move( -aDstRect
.Left(), -aDstRect
.Top() );
344 if( aVDev
->SetOutputSizePixel( aDstSz
) )
346 const bool bOldMap
= mbMap
;
348 EnableMapMode( false );
350 aVDev
->SetLineColor( COL_BLACK
);
351 aVDev
->SetFillColor( COL_BLACK
);
352 aVDev
->DrawPolyPolygon( aPolyPoly
);
354 Bitmap
aPaint( GetBitmap( aDstRect
.TopLeft(), aDstSz
) );
355 Bitmap
aPolyMask( aVDev
->GetBitmap( Point(), aDstSz
) );
357 // #107766# check for non-empty bitmaps before accessing them
358 if( !aPaint
.IsEmpty() && !aPolyMask
.IsEmpty() )
360 BitmapScopedWriteAccess
pW(aPaint
);
361 BitmapScopedReadAccess
pR(aPolyMask
);
366 const BitmapColor
aFillCol( GetFillColor() );
367 const BitmapColor
aBlack( pR
->GetBestMatchingColor( COL_BLACK
) );
368 const tools::Long nWidth
= pW
->Width();
369 const tools::Long nHeight
= pW
->Height();
370 const tools::Long nR
= aFillCol
.GetRed();
371 const tools::Long nG
= aFillCol
.GetGreen();
372 const tools::Long nB
= aFillCol
.GetBlue();
375 if (vcl::isPalettePixelFormat(aPaint
.getPixelFormat()))
377 const BitmapPalette
& rPal
= pW
->GetPalette();
378 const sal_uInt16 nCount
= rPal
.GetEntryCount();
379 std::unique_ptr
<sal_uInt8
[]> xMap(new sal_uInt8
[ nCount
* sizeof( BitmapColor
)]);
380 BitmapColor
* pMap
= reinterpret_cast<BitmapColor
*>(xMap
.get());
382 for( sal_uInt16 i
= 0; i
< nCount
; i
++ )
384 BitmapColor
aCol( rPal
[ i
] );
385 aCol
.Merge( aFillCol
, cTrans
);
386 pMap
[ i
] = BitmapColor( static_cast<sal_uInt8
>(rPal
.GetBestIndex( aCol
)) );
389 if( pR
->GetScanlineFormat() == ScanlineFormat::N1BitMsbPal
&&
390 pW
->GetScanlineFormat() == ScanlineFormat::N8BitPal
)
392 const sal_uInt8 cBlack
= aBlack
.GetIndex();
394 for( nY
= 0; nY
< nHeight
; nY
++ )
396 Scanline pWScan
= pW
->GetScanline( nY
);
397 Scanline pRScan
= pR
->GetScanline( nY
);
398 sal_uInt8 cBit
= 128;
400 for( nX
= 0; nX
< nWidth
; nX
++, cBit
>>= 1, pWScan
++ )
407 if( ( *pRScan
& cBit
) == cBlack
)
409 *pWScan
= pMap
[ *pWScan
].GetIndex();
416 for( nY
= 0; nY
< nHeight
; nY
++ )
418 Scanline pScanline
= pW
->GetScanline(nY
);
419 Scanline pScanlineRead
= pR
->GetScanline(nY
);
420 for( nX
= 0; nX
< nWidth
; nX
++ )
422 if( pR
->GetPixelFromData( pScanlineRead
, nX
) == aBlack
)
424 pW
->SetPixelOnData( pScanline
, nX
, pMap
[ pW
->GetIndexFromData( pScanline
, nX
) ] );
432 if( pR
->GetScanlineFormat() == ScanlineFormat::N1BitMsbPal
&&
433 pW
->GetScanlineFormat() == ScanlineFormat::N24BitTcBgr
)
435 const sal_uInt8 cBlack
= aBlack
.GetIndex();
437 for( nY
= 0; nY
< nHeight
; nY
++ )
439 Scanline pWScan
= pW
->GetScanline( nY
);
440 Scanline pRScan
= pR
->GetScanline( nY
);
441 sal_uInt8 cBit
= 128;
443 for( nX
= 0; nX
< nWidth
; nX
++, cBit
>>= 1, pWScan
+= 3 )
450 if( ( *pRScan
& cBit
) == cBlack
)
452 pWScan
[ 0 ] = color::ColorChannelMerge( pWScan
[ 0 ], nB
, cTrans
);
453 pWScan
[ 1 ] = color::ColorChannelMerge( pWScan
[ 1 ], nG
, cTrans
);
454 pWScan
[ 2 ] = color::ColorChannelMerge( pWScan
[ 2 ], nR
, cTrans
);
461 for( nY
= 0; nY
< nHeight
; nY
++ )
463 Scanline pScanline
= pW
->GetScanline(nY
);
464 Scanline pScanlineRead
= pR
->GetScanline(nY
);
465 for( nX
= 0; nX
< nWidth
; nX
++ )
467 if( pR
->GetPixelFromData( pScanlineRead
, nX
) == aBlack
)
469 aPixCol
= pW
->GetColor( nY
, nX
);
470 aPixCol
.Merge(aFillCol
, cTrans
);
471 pW
->SetPixelOnData(pScanline
, nX
, aPixCol
);
482 DrawBitmap( aDstRect
.TopLeft(), aPaint
);
484 EnableMapMode( bOldMap
);
488 Push( vcl::PushFlags::FILLCOLOR
);
490 DrawPolyPolygon( rPolyPoly
);
497 DrawPolyPolygon( rPolyPoly
);
502 mpMetaFile
= pOldMetaFile
;
504 // #110958# Restore disabled alpha VDev
505 mpAlphaVDev
= pOldAlphaVDev
;
508 void OutputDevice::DrawTransparent( const tools::PolyPolygon
& rPolyPoly
,
509 sal_uInt16 nTransparencePercent
)
511 assert(!is_double_buffered_window());
513 // short circuit for drawing an opaque polygon
514 if( (nTransparencePercent
< 1) || (mnDrawMode
& DrawModeFlags::NoTransparency
) )
516 DrawPolyPolygon( rPolyPoly
);
520 // short circuit for drawing an invisible polygon
521 if( (!mbFillColor
&& !mbLineColor
) || (nTransparencePercent
>= 100) )
522 return; // tdf#84294: do not record it in metafile
524 // handle metafile recording
526 mpMetaFile
->AddAction( new MetaTransparentAction( rPolyPoly
, nTransparencePercent
) );
528 bool bDrawn
= !IsDeviceOutputNecessary() || ImplIsRecordLayout();
532 // get the device graphics as drawing target
533 if( !mpGraphics
&& !AcquireGraphics() )
537 // try hard to draw it directly, because the emulation layers are slower
538 bDrawn
= DrawTransparentNatively( rPolyPoly
, nTransparencePercent
);
541 EmulateDrawTransparent( rPolyPoly
, nTransparencePercent
);
543 // #110958# Apply alpha value also to VDev alpha channel
546 const Color
aFillCol( mpAlphaVDev
->GetFillColor() );
547 sal_uInt8 nAlpha
= 255 - sal::static_int_cast
<sal_uInt8
>(255*nTransparencePercent
/100);
548 mpAlphaVDev
->SetFillColor( Color(nAlpha
, nAlpha
, nAlpha
) );
550 mpAlphaVDev
->DrawTransparent( rPolyPoly
, nTransparencePercent
);
552 mpAlphaVDev
->SetFillColor( aFillCol
);
556 void OutputDevice::DrawTransparent( const GDIMetaFile
& rMtf
, const Point
& rPos
,
557 const Size
& rSize
, const Gradient
& rTransparenceGradient
)
559 DrawTransparent( rMtf
, rPos
, rSize
, rPos
, rSize
, rTransparenceGradient
);
562 void OutputDevice::DrawTransparent( const GDIMetaFile
& rMtf
, const Point
& rPos
, const Size
& rSize
,
563 const Point
& rMtfPos
, const Size
& rMtfSize
,
564 const Gradient
& rTransparenceGradient
)
566 assert(!is_double_buffered_window());
568 const Color
aBlack( COL_BLACK
);
572 // missing here is to map the data using the DeviceTransformation
573 mpMetaFile
->AddAction( new MetaFloatTransparentAction( rMtf
, rPos
, rSize
, rTransparenceGradient
) );
576 if ( !IsDeviceOutputNecessary() )
579 if( ( rTransparenceGradient
.GetStartColor() == aBlack
&& rTransparenceGradient
.GetEndColor() == aBlack
) ||
580 ( mnDrawMode
& DrawModeFlags::NoTransparency
) )
582 const_cast<GDIMetaFile
&>(rMtf
).WindStart();
583 const_cast<GDIMetaFile
&>(rMtf
).Play(*this, rMtfPos
, rMtfSize
);
584 const_cast<GDIMetaFile
&>(rMtf
).WindStart();
588 GDIMetaFile
* pOldMetaFile
= mpMetaFile
;
589 tools::Rectangle
aOutRect( LogicToPixel( tools::Rectangle(rPos
, rSize
) ) );
591 tools::Rectangle
aDstRect( aPoint
, GetOutputSizePixel() );
593 mpMetaFile
= nullptr;
594 aDstRect
.Intersection( aOutRect
);
596 ClipToPaintRegion( aDstRect
);
598 if( !aDstRect
.IsEmpty() )
600 // Create transparent buffer
601 ScopedVclPtrInstance
<VirtualDevice
> xVDev(DeviceFormat::WITH_ALPHA
);
603 xVDev
->mnDPIX
= mnDPIX
;
604 xVDev
->mnDPIY
= mnDPIY
;
606 if( xVDev
->SetOutputSizePixel( aDstRect
.GetSize(), true, true ) )
608 // tdf#150610 fix broken rendering of text meta actions
609 // Even when drawing to a VirtualDevice that has antialiasing
610 // disabled, text will still be drawn with some antialiased
611 // pixels on HiDPI displays. So, use the antialiasing enabled
612 // code to render if there are any text meta actions in the
614 if(GetAntialiasing() != AntialiasingFlags::NONE
|| rPos
!= rMtfPos
|| rSize
!= rMtfSize
)
617 // For MetaFile replay (see task) it may now be necessary to take
618 // into account that the content is AntiAlialiased and needs to be masked
619 // like that. Instead of masking, i will use a copy-modify-paste cycle
620 // here (as i already use in the VclPrimiziveRenderer with success)
621 xVDev
->SetAntialiasing(GetAntialiasing());
623 // create MapMode for buffer (offset needed) and set
624 MapMode
aMap(GetMapMode());
625 const Point
aOutPos(PixelToLogic(aDstRect
.TopLeft()));
626 aMap
.SetOrigin(Point(-aOutPos
.X(), -aOutPos
.Y()));
627 xVDev
->SetMapMode(aMap
);
629 // copy MapMode state and disable for target
630 const bool bOrigMapModeEnabled(IsMapModeEnabled());
631 EnableMapMode(false);
633 // copy MapMode state and disable for buffer
634 const bool bBufferMapModeEnabled(xVDev
->IsMapModeEnabled());
635 xVDev
->EnableMapMode(false);
637 // copy content from original to buffer
638 xVDev
->DrawOutDev( aPoint
, xVDev
->GetOutputSizePixel(), // dest
639 aDstRect
.TopLeft(), xVDev
->GetOutputSizePixel(), // source
642 // draw MetaFile to buffer
643 xVDev
->EnableMapMode(bBufferMapModeEnabled
);
644 const_cast<GDIMetaFile
&>(rMtf
).WindStart();
645 const_cast<GDIMetaFile
&>(rMtf
).Play(*xVDev
, rMtfPos
, rMtfSize
);
646 const_cast<GDIMetaFile
&>(rMtf
).WindStart();
648 // get content bitmap from buffer
649 xVDev
->EnableMapMode(false);
651 const BitmapEx
aPaint(xVDev
->GetBitmapEx(aPoint
, xVDev
->GetOutputSizePixel()));
653 // create alpha mask from gradient and get as Bitmap
654 xVDev
->EnableMapMode(bBufferMapModeEnabled
);
655 xVDev
->SetDrawMode(DrawModeFlags::GrayGradient
);
656 // Related tdf#150610 draw gradient to VirtualDevice bounds
657 // If we are here and the metafile bounds differs from the
658 // VirtualDevice bounds so that we apply the transparency
659 // gradient to any pixels drawn outside of the metafile
661 xVDev
->DrawGradient(tools::Rectangle(rPos
, rSize
), rTransparenceGradient
);
662 xVDev
->SetDrawMode(DrawModeFlags::Default
);
663 xVDev
->EnableMapMode(false);
665 AlphaMask
aAlpha(xVDev
->GetBitmap(aPoint
, xVDev
->GetOutputSizePixel()));
666 const AlphaMask
& aPaintAlpha(aPaint
.GetAlphaMask());
667 // The alpha mask is inverted from what
668 // is expected so invert it again
669 aAlpha
.Invert(); // convert to alpha
670 aAlpha
.BlendWith(aPaintAlpha
);
672 xVDev
.disposeAndClear();
674 // draw masked content to target and restore MapMode
675 DrawBitmapEx(aDstRect
.TopLeft(), BitmapEx(aPaint
.GetBitmap(), aAlpha
));
676 EnableMapMode(bOrigMapModeEnabled
);
680 MapMode
aMap( GetMapMode() );
681 Point
aOutPos( PixelToLogic( aDstRect
.TopLeft() ) );
682 const bool bOldMap
= mbMap
;
684 aMap
.SetOrigin( Point( -aOutPos
.X(), -aOutPos
.Y() ) );
685 xVDev
->SetMapMode( aMap
);
686 const bool bVDevOldMap
= xVDev
->IsMapModeEnabled();
688 // create paint bitmap
689 const_cast<GDIMetaFile
&>(rMtf
).WindStart();
690 const_cast<GDIMetaFile
&>(rMtf
).Play(*xVDev
, rMtfPos
, rMtfSize
);
691 const_cast<GDIMetaFile
&>(rMtf
).WindStart();
692 xVDev
->EnableMapMode( false );
693 BitmapEx aPaint
= xVDev
->GetBitmapEx(Point(), xVDev
->GetOutputSizePixel());
694 xVDev
->EnableMapMode( bVDevOldMap
); // #i35331#: MUST NOT use EnableMapMode( sal_True ) here!
696 // create alpha mask from gradient
697 xVDev
->SetDrawMode( DrawModeFlags::GrayGradient
);
698 xVDev
->DrawGradient( tools::Rectangle( rMtfPos
, rMtfSize
), rTransparenceGradient
);
699 xVDev
->SetDrawMode( DrawModeFlags::Default
);
700 xVDev
->EnableMapMode( false );
702 AlphaMask
aAlpha(xVDev
->GetBitmap(Point(), xVDev
->GetOutputSizePixel()));
703 const AlphaMask
& aPaintAlpha(aPaint
.GetAlphaMask());
704 // The alpha mask is inverted from what
705 // is expected so invert it again
706 aAlpha
.Invert(); // convert to alpha
707 aAlpha
.BlendWith(aPaintAlpha
);
709 xVDev
.disposeAndClear();
711 EnableMapMode( false );
712 DrawBitmapEx(aDstRect
.TopLeft(), BitmapEx(aPaint
.GetBitmap(), aAlpha
));
713 EnableMapMode( bOldMap
);
718 mpMetaFile
= pOldMetaFile
;
722 typedef ::std::pair
< MetaAction
*, int > Component
; // MetaAction plus index in metafile
726 // List of (intersecting) actions, plus overall bounds
727 struct ConnectedComponents
729 ConnectedComponents() :
734 bIsFullyTransparent(false)
737 ::std::list
< Component
> aComponentList
;
738 tools::Rectangle aBounds
;
741 bool bIsFullyTransparent
;
748 /** Determines whether the action can handle transparency correctly
749 (i.e. when painted on white background, does the action still look
752 bool DoesActionHandleTransparency( const MetaAction
& rAct
)
754 // MetaActionType::FLOATTRANSPARENT can contain a whole metafile,
755 // which is to be rendered with the given transparent gradient. We
756 // currently cannot emulate transparent painting on a white
757 // background reliably.
759 // the remainder can handle printing itself correctly on a uniform
761 switch( rAct
.GetType() )
763 case MetaActionType::Transparent
:
764 case MetaActionType::BMPEX
:
765 case MetaActionType::BMPEXSCALE
:
766 case MetaActionType::BMPEXSCALEPART
:
774 bool doesRectCoverWithUniformColor(
775 tools::Rectangle
const & rPrevRect
,
776 tools::Rectangle
const & rCurrRect
,
777 OutputDevice
const & rMapModeVDev
)
779 // shape needs to fully cover previous content, and have uniform
781 return (rMapModeVDev
.LogicToPixel(rCurrRect
).Contains(rPrevRect
) &&
782 rMapModeVDev
.IsFillColor());
785 /** Check whether rCurrRect rectangle fully covers io_rPrevRect - if
786 yes, return true and update o_rBgColor
788 bool checkRect( tools::Rectangle
& io_rPrevRect
,
790 const tools::Rectangle
& rCurrRect
,
791 OutputDevice
const & rMapModeVDev
)
793 bool bRet
= doesRectCoverWithUniformColor(io_rPrevRect
, rCurrRect
, rMapModeVDev
);
797 io_rPrevRect
= rCurrRect
;
798 o_rBgColor
= rMapModeVDev
.GetFillColor();
804 /** #107169# Convert BitmapEx to Bitmap with appropriately blended
805 color. Convert MetaTransparentAction to plain polygon,
806 appropriately colored
809 Add converted actions to this metafile
811 void ImplConvertTransparentAction( GDIMetaFile
& o_rMtf
,
812 const MetaAction
& rAct
,
813 const OutputDevice
& rStateOutDev
,
816 if (rAct
.GetType() == MetaActionType::Transparent
)
818 const MetaTransparentAction
* pTransAct
= static_cast<const MetaTransparentAction
*>(&rAct
);
819 sal_uInt16
nTransparency( pTransAct
->GetTransparence() );
821 // #i10613# Respect transparency for draw color
824 o_rMtf
.AddAction(new MetaPushAction(vcl::PushFlags::LINECOLOR
|vcl::PushFlags::FILLCOLOR
));
826 // assume white background for alpha blending
827 Color
aLineColor(rStateOutDev
.GetLineColor());
828 aLineColor
.SetRed(static_cast<sal_uInt8
>((255*nTransparency
+ (100 - nTransparency
) * aLineColor
.GetRed()) / 100));
829 aLineColor
.SetGreen(static_cast<sal_uInt8
>((255*nTransparency
+ (100 - nTransparency
) * aLineColor
.GetGreen()) / 100));
830 aLineColor
.SetBlue(static_cast<sal_uInt8
>((255*nTransparency
+ (100 - nTransparency
) * aLineColor
.GetBlue()) / 100));
831 o_rMtf
.AddAction(new MetaLineColorAction(aLineColor
, true));
833 Color
aFillColor(rStateOutDev
.GetFillColor());
834 aFillColor
.SetRed(static_cast<sal_uInt8
>((255*nTransparency
+ (100 - nTransparency
)*aFillColor
.GetRed()) / 100));
835 aFillColor
.SetGreen(static_cast<sal_uInt8
>((255*nTransparency
+ (100 - nTransparency
)*aFillColor
.GetGreen()) / 100));
836 aFillColor
.SetBlue(static_cast<sal_uInt8
>((255*nTransparency
+ (100 - nTransparency
)*aFillColor
.GetBlue()) / 100));
837 o_rMtf
.AddAction(new MetaFillColorAction(aFillColor
, true));
840 o_rMtf
.AddAction(new MetaPolyPolygonAction(pTransAct
->GetPolyPolygon()));
843 o_rMtf
.AddAction(new MetaPopAction());
849 switch (rAct
.GetType())
851 case MetaActionType::BMPEX
:
852 aBmpEx
= static_cast<const MetaBmpExAction
&>(rAct
).GetBitmapEx();
855 case MetaActionType::BMPEXSCALE
:
856 case MetaActionType::BMPEXSCALEPART
:
857 aBmpEx
= static_cast<const MetaBmpExScaleAction
&>(rAct
).GetBitmapEx();
860 case MetaActionType::Transparent
:
863 OSL_FAIL("Printer::GetPreparedMetafile impossible state reached");
867 Bitmap
aBmp(aBmpEx
.GetBitmap());
868 if (aBmpEx
.IsAlpha())
870 // blend with alpha channel
871 aBmp
.Convert(BmpConversion::N24Bit
);
872 aBmp
.Blend(aBmpEx
.GetAlphaMask(), aBgColor
);
875 // add corresponding action
876 switch (rAct
.GetType())
878 case MetaActionType::BMPEX
:
879 o_rMtf
.AddAction(new MetaBmpAction(
880 static_cast<const MetaBmpExAction
&>(rAct
).GetPoint(),
883 case MetaActionType::BMPEXSCALE
:
884 o_rMtf
.AddAction(new MetaBmpScaleAction(
885 static_cast<const MetaBmpExScaleAction
&>(rAct
).GetPoint(),
886 static_cast<const MetaBmpExScaleAction
&>(rAct
).GetSize(),
889 case MetaActionType::BMPEXSCALEPART
:
890 o_rMtf
.AddAction(new MetaBmpScalePartAction(
891 static_cast<const MetaBmpExScalePartAction
&>(rAct
).GetDestPoint(),
892 static_cast<const MetaBmpExScalePartAction
&>(rAct
).GetDestSize(),
893 static_cast<const MetaBmpExScalePartAction
&>(rAct
).GetSrcPoint(),
894 static_cast<const MetaBmpExScalePartAction
&>(rAct
).GetSrcSize(),
898 OSL_FAIL("Unexpected case");
904 // #i10613# Extracted from ImplCheckRect::ImplCreate
905 // Returns true, if given action creates visible (i.e. non-transparent) output
906 bool ImplIsNotTransparent( const MetaAction
& rAct
, const OutputDevice
& rOut
)
908 const bool bLineTransparency( !rOut
.IsLineColor() || rOut
.GetLineColor().IsFullyTransparent() );
909 const bool bFillTransparency( !rOut
.IsFillColor() || rOut
.GetFillColor().IsFullyTransparent() );
912 switch( rAct
.GetType() )
914 case MetaActionType::POINT
:
915 case MetaActionType::LINE
:
916 case MetaActionType::POLYLINE
:
917 if( !bLineTransparency
)
921 case MetaActionType::RECT
:
922 if( !bLineTransparency
|| !bFillTransparency
)
926 case MetaActionType::ROUNDRECT
:
927 if( !bLineTransparency
|| !bFillTransparency
)
931 case MetaActionType::ELLIPSE
:
932 if( !bLineTransparency
|| !bFillTransparency
)
936 case MetaActionType::ARC
:
937 if( !bLineTransparency
|| !bFillTransparency
)
941 case MetaActionType::PIE
:
942 if( !bLineTransparency
|| !bFillTransparency
)
946 case MetaActionType::CHORD
:
947 if( !bLineTransparency
|| !bFillTransparency
)
951 case MetaActionType::POLYGON
:
952 if( !bLineTransparency
|| !bFillTransparency
)
956 case MetaActionType::POLYPOLYGON
:
957 if( !bLineTransparency
|| !bFillTransparency
)
961 case MetaActionType::TEXT
:
963 const MetaTextAction
& rTextAct
= static_cast<const MetaTextAction
&>(rAct
);
964 const OUString
aString( rTextAct
.GetText().copy(rTextAct
.GetIndex(), rTextAct
.GetLen()) );
965 if (!aString
.isEmpty())
970 case MetaActionType::TEXTARRAY
:
972 const MetaTextArrayAction
& rTextAct
= static_cast<const MetaTextArrayAction
&>(rAct
);
973 const OUString
aString( rTextAct
.GetText().copy(rTextAct
.GetIndex(), rTextAct
.GetLen()) );
974 if (!aString
.isEmpty())
979 case MetaActionType::PIXEL
:
980 case MetaActionType::BMP
:
981 case MetaActionType::BMPSCALE
:
982 case MetaActionType::BMPSCALEPART
:
983 case MetaActionType::BMPEX
:
984 case MetaActionType::BMPEXSCALE
:
985 case MetaActionType::BMPEXSCALEPART
:
986 case MetaActionType::MASK
:
987 case MetaActionType::MASKSCALE
:
988 case MetaActionType::MASKSCALEPART
:
989 case MetaActionType::GRADIENT
:
990 case MetaActionType::GRADIENTEX
:
991 case MetaActionType::HATCH
:
992 case MetaActionType::WALLPAPER
:
993 case MetaActionType::Transparent
:
994 case MetaActionType::FLOATTRANSPARENT
:
995 case MetaActionType::EPS
:
996 case MetaActionType::TEXTRECT
:
997 case MetaActionType::STRETCHTEXT
:
998 case MetaActionType::TEXTLINE
:
999 // all other actions: generate non-transparent output
1010 // #i10613# Extracted from ImplCheckRect::ImplCreate
1011 tools::Rectangle
ImplCalcActionBounds( const MetaAction
& rAct
, const OutputDevice
& rOut
)
1013 tools::Rectangle aActionBounds
;
1015 switch( rAct
.GetType() )
1017 case MetaActionType::PIXEL
:
1018 aActionBounds
= tools::Rectangle( static_cast<const MetaPixelAction
&>(rAct
).GetPoint(), Size( 1, 1 ) );
1021 case MetaActionType::POINT
:
1022 aActionBounds
= tools::Rectangle( static_cast<const MetaPointAction
&>(rAct
).GetPoint(), Size( 1, 1 ) );
1025 case MetaActionType::LINE
:
1027 const MetaLineAction
& rMetaLineAction
= static_cast<const MetaLineAction
&>(rAct
);
1028 aActionBounds
= tools::Rectangle( rMetaLineAction
.GetStartPoint(), rMetaLineAction
.GetEndPoint() );
1029 aActionBounds
.Normalize();
1030 const tools::Long
nLineWidth(rMetaLineAction
.GetLineInfo().GetWidth());
1033 const tools::Long
nHalfLineWidth((nLineWidth
+ 1) / 2);
1034 aActionBounds
.AdjustLeft( -nHalfLineWidth
);
1035 aActionBounds
.AdjustTop( -nHalfLineWidth
);
1036 aActionBounds
.AdjustRight(nHalfLineWidth
);
1037 aActionBounds
.AdjustBottom(nHalfLineWidth
);
1042 case MetaActionType::RECT
:
1043 aActionBounds
= static_cast<const MetaRectAction
&>(rAct
).GetRect();
1046 case MetaActionType::ROUNDRECT
:
1047 aActionBounds
= tools::Polygon( static_cast<const MetaRoundRectAction
&>(rAct
).GetRect(),
1048 static_cast<const MetaRoundRectAction
&>(rAct
).GetHorzRound(),
1049 static_cast<const MetaRoundRectAction
&>(rAct
).GetVertRound() ).GetBoundRect();
1052 case MetaActionType::ELLIPSE
:
1054 const tools::Rectangle
& rRect
= static_cast<const MetaEllipseAction
&>(rAct
).GetRect();
1055 aActionBounds
= tools::Polygon( rRect
.Center(),
1056 rRect
.GetWidth() >> 1,
1057 rRect
.GetHeight() >> 1 ).GetBoundRect();
1061 case MetaActionType::ARC
:
1062 aActionBounds
= tools::Polygon( static_cast<const MetaArcAction
&>(rAct
).GetRect(),
1063 static_cast<const MetaArcAction
&>(rAct
).GetStartPoint(),
1064 static_cast<const MetaArcAction
&>(rAct
).GetEndPoint(), PolyStyle::Arc
).GetBoundRect();
1067 case MetaActionType::PIE
:
1068 aActionBounds
= tools::Polygon( static_cast<const MetaPieAction
&>(rAct
).GetRect(),
1069 static_cast<const MetaPieAction
&>(rAct
).GetStartPoint(),
1070 static_cast<const MetaPieAction
&>(rAct
).GetEndPoint(), PolyStyle::Pie
).GetBoundRect();
1073 case MetaActionType::CHORD
:
1074 aActionBounds
= tools::Polygon( static_cast<const MetaChordAction
&>(rAct
).GetRect(),
1075 static_cast<const MetaChordAction
&>(rAct
).GetStartPoint(),
1076 static_cast<const MetaChordAction
&>(rAct
).GetEndPoint(), PolyStyle::Chord
).GetBoundRect();
1079 case MetaActionType::POLYLINE
:
1081 const MetaPolyLineAction
& rMetaPolyLineAction
= static_cast<const MetaPolyLineAction
&>(rAct
);
1082 aActionBounds
= rMetaPolyLineAction
.GetPolygon().GetBoundRect();
1083 const tools::Long
nLineWidth(rMetaPolyLineAction
.GetLineInfo().GetWidth());
1086 const tools::Long
nHalfLineWidth((nLineWidth
+ 1) / 2);
1087 aActionBounds
.AdjustLeft( -nHalfLineWidth
);
1088 aActionBounds
.AdjustTop( -nHalfLineWidth
);
1089 aActionBounds
.AdjustRight(nHalfLineWidth
);
1090 aActionBounds
.AdjustBottom(nHalfLineWidth
);
1095 case MetaActionType::POLYGON
:
1096 aActionBounds
= static_cast<const MetaPolygonAction
&>(rAct
).GetPolygon().GetBoundRect();
1099 case MetaActionType::POLYPOLYGON
:
1100 aActionBounds
= static_cast<const MetaPolyPolygonAction
&>(rAct
).GetPolyPolygon().GetBoundRect();
1103 case MetaActionType::BMP
:
1104 aActionBounds
= tools::Rectangle( static_cast<const MetaBmpAction
&>(rAct
).GetPoint(),
1105 rOut
.PixelToLogic( static_cast<const MetaBmpAction
&>(rAct
).GetBitmap().GetSizePixel() ) );
1108 case MetaActionType::BMPSCALE
:
1109 aActionBounds
= tools::Rectangle( static_cast<const MetaBmpScaleAction
&>(rAct
).GetPoint(),
1110 static_cast<const MetaBmpScaleAction
&>(rAct
).GetSize() );
1113 case MetaActionType::BMPSCALEPART
:
1114 aActionBounds
= tools::Rectangle( static_cast<const MetaBmpScalePartAction
&>(rAct
).GetDestPoint(),
1115 static_cast<const MetaBmpScalePartAction
&>(rAct
).GetDestSize() );
1118 case MetaActionType::BMPEX
:
1119 aActionBounds
= tools::Rectangle( static_cast<const MetaBmpExAction
&>(rAct
).GetPoint(),
1120 rOut
.PixelToLogic( static_cast<const MetaBmpExAction
&>(rAct
).GetBitmapEx().GetSizePixel() ) );
1123 case MetaActionType::BMPEXSCALE
:
1124 aActionBounds
= tools::Rectangle( static_cast<const MetaBmpExScaleAction
&>(rAct
).GetPoint(),
1125 static_cast<const MetaBmpExScaleAction
&>(rAct
).GetSize() );
1128 case MetaActionType::BMPEXSCALEPART
:
1129 aActionBounds
= tools::Rectangle( static_cast<const MetaBmpExScalePartAction
&>(rAct
).GetDestPoint(),
1130 static_cast<const MetaBmpExScalePartAction
&>(rAct
).GetDestSize() );
1133 case MetaActionType::MASK
:
1134 aActionBounds
= tools::Rectangle( static_cast<const MetaMaskAction
&>(rAct
).GetPoint(),
1135 rOut
.PixelToLogic( static_cast<const MetaMaskAction
&>(rAct
).GetBitmap().GetSizePixel() ) );
1138 case MetaActionType::MASKSCALE
:
1139 aActionBounds
= tools::Rectangle( static_cast<const MetaMaskScaleAction
&>(rAct
).GetPoint(),
1140 static_cast<const MetaMaskScaleAction
&>(rAct
).GetSize() );
1143 case MetaActionType::MASKSCALEPART
:
1144 aActionBounds
= tools::Rectangle( static_cast<const MetaMaskScalePartAction
&>(rAct
).GetDestPoint(),
1145 static_cast<const MetaMaskScalePartAction
&>(rAct
).GetDestSize() );
1148 case MetaActionType::GRADIENT
:
1149 aActionBounds
= static_cast<const MetaGradientAction
&>(rAct
).GetRect();
1152 case MetaActionType::GRADIENTEX
:
1153 aActionBounds
= static_cast<const MetaGradientExAction
&>(rAct
).GetPolyPolygon().GetBoundRect();
1156 case MetaActionType::HATCH
:
1157 aActionBounds
= static_cast<const MetaHatchAction
&>(rAct
).GetPolyPolygon().GetBoundRect();
1160 case MetaActionType::WALLPAPER
:
1161 aActionBounds
= static_cast<const MetaWallpaperAction
&>(rAct
).GetRect();
1164 case MetaActionType::Transparent
:
1165 aActionBounds
= static_cast<const MetaTransparentAction
&>(rAct
).GetPolyPolygon().GetBoundRect();
1168 case MetaActionType::FLOATTRANSPARENT
:
1169 aActionBounds
= tools::Rectangle( static_cast<const MetaFloatTransparentAction
&>(rAct
).GetPoint(),
1170 static_cast<const MetaFloatTransparentAction
&>(rAct
).GetSize() );
1173 case MetaActionType::EPS
:
1174 aActionBounds
= tools::Rectangle( static_cast<const MetaEPSAction
&>(rAct
).GetPoint(),
1175 static_cast<const MetaEPSAction
&>(rAct
).GetSize() );
1178 case MetaActionType::TEXT
:
1180 const MetaTextAction
& rTextAct
= static_cast<const MetaTextAction
&>(rAct
);
1181 const OUString
aString( rTextAct
.GetText().copy(rTextAct
.GetIndex(), rTextAct
.GetLen()) );
1183 if (!aString
.isEmpty())
1185 const Point
aPtLog( rTextAct
.GetPoint() );
1187 // #105987# Use API method instead of Impl* methods
1188 // #107490# Set base parameter equal to index parameter
1189 rOut
.GetTextBoundRect( aActionBounds
, rTextAct
.GetText(), rTextAct
.GetIndex(),
1190 rTextAct
.GetIndex(), rTextAct
.GetLen() );
1191 aActionBounds
.Move( aPtLog
.X(), aPtLog
.Y() );
1196 case MetaActionType::TEXTARRAY
:
1198 const MetaTextArrayAction
& rTextAct
= static_cast<const MetaTextArrayAction
&>(rAct
);
1199 const OUString
aString( rTextAct
.GetText().copy(rTextAct
.GetIndex(), rTextAct
.GetLen()) );
1201 if( !aString
.isEmpty() )
1203 // #105987# ImplLayout takes everything in logical coordinates
1204 std::unique_ptr
<SalLayout
> pSalLayout
;
1205 if (rTextAct
.GetLayoutContextIndex() >= 0)
1207 pSalLayout
= rOut
.ImplLayout(
1208 rTextAct
.GetText(), rTextAct
.GetLayoutContextIndex(),
1209 rTextAct
.GetLayoutContextLen(), rTextAct
.GetPoint(), 0,
1210 rTextAct
.GetDXArray(), rTextAct
.GetKashidaArray(), SalLayoutFlags::NONE
,
1211 /*pTextLayoutCache=*/nullptr,
1212 /*pGlyphs=*/nullptr,
1213 /*nDrawOriginCluster=*/rTextAct
.GetIndex(),
1214 /*nDrawMinCharPos=*/rTextAct
.GetIndex(),
1215 /*nDrawEndCharPos=*/rTextAct
.GetIndex() + rTextAct
.GetLen());
1219 pSalLayout
= rOut
.ImplLayout(rTextAct
.GetText(), rTextAct
.GetIndex(),
1220 rTextAct
.GetLen(), rTextAct
.GetPoint(), 0,
1221 rTextAct
.GetDXArray(), rTextAct
.GetKashidaArray());
1226 tools::Rectangle
aBoundRect( rOut
.ImplGetTextBoundRect( *pSalLayout
) );
1227 aActionBounds
= rOut
.PixelToLogic( aBoundRect
);
1233 case MetaActionType::TEXTRECT
:
1234 aActionBounds
= static_cast<const MetaTextRectAction
&>(rAct
).GetRect();
1237 case MetaActionType::STRETCHTEXT
:
1239 const MetaStretchTextAction
& rTextAct
= static_cast<const MetaStretchTextAction
&>(rAct
);
1240 const OUString
aString( rTextAct
.GetText().copy(rTextAct
.GetIndex(), rTextAct
.GetLen()) );
1242 // #i16195# Literate copy from TextArray action, the
1243 // semantics for the ImplLayout call are copied from the
1244 // OutDev::DrawStretchText() code. Unfortunately, also in
1245 // this case, public outdev methods such as GetTextWidth()
1246 // don't provide enough info.
1247 if( !aString
.isEmpty() )
1249 // #105987# ImplLayout takes everything in logical coordinates
1250 std::unique_ptr
<SalLayout
> pSalLayout
= rOut
.ImplLayout( rTextAct
.GetText(), rTextAct
.GetIndex(),
1251 rTextAct
.GetLen(), rTextAct
.GetPoint(),
1252 rTextAct
.GetWidth() );
1255 tools::Rectangle
aBoundRect( rOut
.ImplGetTextBoundRect( *pSalLayout
) );
1256 aActionBounds
= rOut
.PixelToLogic( aBoundRect
);
1262 case MetaActionType::TEXTLINE
:
1263 OSL_FAIL("MetaActionType::TEXTLINE not supported");
1270 if( !aActionBounds
.IsEmpty() )
1272 // fdo#40421 limit current action's output to clipped area
1273 if( rOut
.IsClipRegion() )
1274 return rOut
.LogicToPixel(
1275 rOut
.GetClipRegion().GetBoundRect().Intersection( aActionBounds
) );
1277 return rOut
.LogicToPixel( aActionBounds
);
1280 return tools::Rectangle();
1283 } // end anon namespace
1285 // TODO: this massive function operates on metafiles, so eventually it should probably
1286 // be shifted to the GDIMetaFile class
1287 bool OutputDevice::RemoveTransparenciesFromMetaFile( const GDIMetaFile
& rInMtf
, GDIMetaFile
& rOutMtf
,
1288 tools::Long nMaxBmpDPIX
, tools::Long nMaxBmpDPIY
,
1289 bool bReduceTransparency
, bool bTransparencyAutoMode
,
1290 bool bDownsampleBitmaps
,
1291 const Color
& rBackground
1294 MetaAction
* pCurrAct
;
1295 bool bTransparent( false );
1300 // tdf#164354 skip unnecessary transparency removal
1301 // On macOS, there are no known problems drawing semi-transparent
1302 // shapes, fill, text, and bitmaps so only do this sometimes very
1303 // expensive step when both reduce transparency and no
1304 // transparency are true.
1305 if(bReduceTransparency
&& !bTransparencyAutoMode
)
1307 if(!bReduceTransparency
|| bTransparencyAutoMode
)
1309 bTransparent
= rInMtf
.HasTransparentActions();
1311 // #i10613# Determine set of connected components containing transparent objects. These are
1312 // then processed as bitmaps, the original actions are removed from the metafile.
1315 // nothing transparent -> just copy
1321 // This works as follows: we want a number of distinct sets of
1322 // connected components, where each set contains metafile
1323 // actions that are intersecting (note: there are possibly
1324 // more actions contained as are directly intersecting,
1325 // because we can only produce rectangular bitmaps later
1326 // on. Thus, each set of connected components is the smallest
1327 // enclosing, axis-aligned rectangle that completely bounds a
1328 // number of intersecting metafile actions, plus any action
1329 // that would otherwise be cut in two). Therefore, we
1330 // iteratively add metafile actions from the original metafile
1331 // to this connected components list (aCCList), by checking
1332 // each element's bounding box against intersection with the
1333 // metaaction at hand.
1334 // All those intersecting elements are removed from aCCList
1335 // and collected in a temporary list (aCCMergeList). After all
1336 // elements have been checked, the aCCMergeList elements are
1337 // merged with the metaaction at hand into one resulting
1338 // connected component, with one big bounding box, and
1339 // inserted into aCCList again.
1340 // The time complexity of this algorithm is O(n^3), where n is
1341 // the number of metafile actions, and it finds all distinct
1342 // regions of rectangle-bounded connected components. This
1343 // algorithm was designed by AF.
1345 // STAGE 1: Detect background
1347 // Receives uniform background content, and is _not_ merged
1348 // nor checked for intersection against other aCCList elements
1349 ConnectedComponents aBackgroundComponent
;
1351 // Read the configuration value of minimal object area where transparency will be removed
1352 double fReduceTransparencyMinArea
= officecfg::Office::Common::VCL::ReduceTransparencyMinArea::get() / 100.0;
1353 SAL_WARN_IF(fReduceTransparencyMinArea
> 1.0, "vcl",
1354 "Value of ReduceTransparencyMinArea config option is too high");
1355 SAL_WARN_IF(fReduceTransparencyMinArea
< 0.0, "vcl",
1356 "Value of ReduceTransparencyMinArea config option is too low");
1357 fReduceTransparencyMinArea
= std::clamp(fReduceTransparencyMinArea
, 0.0, 1.0);
1359 // create an OutputDevice to record mapmode changes and the like
1360 ScopedVclPtrInstance
< VirtualDevice
> aMapModeVDev
;
1361 aMapModeVDev
->mnDPIX
= mnDPIX
;
1362 aMapModeVDev
->mnDPIY
= mnDPIY
;
1363 aMapModeVDev
->EnableOutput(false);
1365 // weed out page-filling background objects (if they are
1366 // uniformly coloured). Keeping them outside the other
1367 // connected components often prevents whole-page bitmap
1369 bool bStillBackground
=true; // true until first non-bg action
1370 int nActionNum
= 0, nLastBgAction
= -1;
1371 pCurrAct
=const_cast<GDIMetaFile
&>(rInMtf
).FirstAction();
1372 if( rBackground
!= COL_TRANSPARENT
)
1374 aBackgroundComponent
.aBgColor
= rBackground
;
1375 aBackgroundComponent
.aBounds
= GetBackgroundComponentBounds();
1377 while( pCurrAct
&& bStillBackground
)
1379 switch( pCurrAct
->GetType() )
1381 case MetaActionType::RECT
:
1384 aBackgroundComponent
.aBounds
,
1385 aBackgroundComponent
.aBgColor
,
1386 static_cast<const MetaRectAction
*>(pCurrAct
)->GetRect(),
1388 bStillBackground
=false; // incomplete occlusion of background
1390 nLastBgAction
=nActionNum
; // this _is_ background
1393 case MetaActionType::POLYGON
:
1395 const tools::Polygon
aPoly(
1396 static_cast<const MetaPolygonAction
*>(pCurrAct
)->GetPolygon());
1397 if( !basegfx::utils::isRectangle(
1398 aPoly
.getB2DPolygon()) ||
1400 aBackgroundComponent
.aBounds
,
1401 aBackgroundComponent
.aBgColor
,
1402 aPoly
.GetBoundRect(),
1404 bStillBackground
=false; // incomplete occlusion of background
1406 nLastBgAction
=nActionNum
; // this _is_ background
1409 case MetaActionType::POLYPOLYGON
:
1411 const tools::PolyPolygon
aPoly(
1412 static_cast<const MetaPolyPolygonAction
*>(pCurrAct
)->GetPolyPolygon());
1413 if( aPoly
.Count() != 1 ||
1414 !basegfx::utils::isRectangle(
1415 aPoly
[0].getB2DPolygon()) ||
1417 aBackgroundComponent
.aBounds
,
1418 aBackgroundComponent
.aBgColor
,
1419 aPoly
.GetBoundRect(),
1421 bStillBackground
=false; // incomplete occlusion of background
1423 nLastBgAction
=nActionNum
; // this _is_ background
1426 case MetaActionType::WALLPAPER
:
1429 aBackgroundComponent
.aBounds
,
1430 aBackgroundComponent
.aBgColor
,
1431 static_cast<const MetaWallpaperAction
*>(pCurrAct
)->GetRect(),
1433 bStillBackground
=false; // incomplete occlusion of background
1435 nLastBgAction
=nActionNum
; // this _is_ background
1440 if( ImplIsNotTransparent( *pCurrAct
,
1442 bStillBackground
=false; // non-transparent action, possibly
1445 // extend current bounds (next uniform action
1446 // needs to fully cover this area)
1447 aBackgroundComponent
.aBounds
.Union(
1448 ImplCalcActionBounds(*pCurrAct
, *aMapModeVDev
) );
1453 // execute action to get correct MapModes etc.
1454 pCurrAct
->Execute( aMapModeVDev
.get() );
1456 pCurrAct
=const_cast<GDIMetaFile
&>(rInMtf
).NextAction();
1460 if (nLastBgAction
!= -1)
1462 size_t nActionSize
= rInMtf
.GetActionSize();
1463 // tdf#134736 move nLastBgAction to also include any trailing pops
1464 for (size_t nPostLastBgAction
= nLastBgAction
+ 1; nPostLastBgAction
< nActionSize
; ++nPostLastBgAction
)
1466 if (rInMtf
.GetAction(nPostLastBgAction
)->GetType() != MetaActionType::POP
)
1468 nLastBgAction
= nPostLastBgAction
;
1472 aMapModeVDev
->ClearStack(); // clean up aMapModeVDev
1474 // fast-forward until one after the last background action
1475 // (need to reconstruct map mode vdev state)
1477 pCurrAct
=const_cast<GDIMetaFile
&>(rInMtf
).FirstAction();
1478 while( pCurrAct
&& nActionNum
<=nLastBgAction
)
1480 // up to and including last ink-generating background
1481 // action go to background component
1482 aBackgroundComponent
.aComponentList
.emplace_back(
1483 pCurrAct
, nActionNum
);
1485 // execute action to get correct MapModes etc.
1486 pCurrAct
->Execute( aMapModeVDev
.get() );
1487 pCurrAct
=const_cast<GDIMetaFile
&>(rInMtf
).NextAction();
1491 // STAGE 2: Generate connected components list
1493 ::std::vector
<ConnectedComponents
> aCCList
; // contains distinct sets of connected components as elements.
1495 // iterate over all actions (start where background action
1499 pCurrAct
=const_cast<GDIMetaFile
&>(rInMtf
).NextAction(), ++nActionNum
)
1501 // execute action to get correct MapModes etc.
1502 pCurrAct
->Execute( aMapModeVDev
.get() );
1504 // cache bounds of current action
1505 const tools::Rectangle
aBBCurrAct( ImplCalcActionBounds(*pCurrAct
, *aMapModeVDev
) );
1507 // accumulate collected bounds here, initialize with current action
1508 tools::Rectangle
aTotalBounds( aBBCurrAct
); // thus, aTotalComponents.aBounds is empty
1509 // for non-output-generating actions
1510 bool bTreatSpecial( false );
1511 ConnectedComponents aTotalComponents
;
1513 // STAGE 2.1: Search for intersecting cc entries
1515 // if aBBCurrAct is empty, it will intersect with no
1516 // aCCList member. Thus, we can save the check.
1517 // Furthermore, this ensures that non-output-generating
1518 // actions get their own aCCList entry, which is necessary
1519 // when copying them to the output metafile (see stage 4
1522 // #107169# Wholly transparent objects need
1523 // not be considered for connected components,
1524 // too. Just put each of them into a separate
1526 aTotalComponents
.bIsFullyTransparent
= !ImplIsNotTransparent(*pCurrAct
, *aMapModeVDev
);
1528 if( !aBBCurrAct
.IsEmpty() &&
1529 !aTotalComponents
.bIsFullyTransparent
)
1531 if( !aBackgroundComponent
.aComponentList
.empty() &&
1532 !aBackgroundComponent
.aBounds
.Contains(aTotalBounds
) )
1534 // it seems the background is not large enough. to
1535 // be on the safe side, combine with this component.
1536 aTotalBounds
.Union( aBackgroundComponent
.aBounds
);
1538 // extract all aCurr actions to aTotalComponents
1539 aTotalComponents
.aComponentList
.splice( aTotalComponents
.aComponentList
.end(),
1540 aBackgroundComponent
.aComponentList
);
1542 if( aBackgroundComponent
.bIsSpecial
)
1543 bTreatSpecial
= true;
1546 bool bSomeComponentsChanged
;
1548 // now, this is unfortunate: since changing anyone of
1549 // the aCCList elements (e.g. by merging or addition
1550 // of an action) might generate new intersection with
1551 // other aCCList elements, have to repeat the whole
1552 // element scanning, until nothing changes anymore.
1553 // Thus, this loop here makes us O(n^3) in the worst
1557 // only loop here if 'intersects' branch below was hit
1558 bSomeComponentsChanged
= false;
1560 // iterate over all current members of aCCList
1561 for( auto aCurrCC
=aCCList
.begin(); aCurrCC
!= aCCList
.end(); )
1563 // first check if current element's bounds are
1564 // empty. This ensures that empty actions are not
1565 // merged into one component, as a matter of fact,
1566 // they have no position.
1568 // #107169# Wholly transparent objects need
1569 // not be considered for connected components,
1570 // too. Just put each of them into a separate
1572 if( !aCurrCC
->aBounds
.IsEmpty() &&
1573 !aCurrCC
->bIsFullyTransparent
&&
1574 aCurrCC
->aBounds
.Overlaps( aTotalBounds
) )
1576 // union the intersecting aCCList element into aTotalComponents
1578 // calc union bounding box
1579 aTotalBounds
.Union( aCurrCC
->aBounds
);
1581 // extract all aCurr actions to aTotalComponents
1582 aTotalComponents
.aComponentList
.splice( aTotalComponents
.aComponentList
.end(),
1583 aCurrCC
->aComponentList
);
1585 if( aCurrCC
->bIsSpecial
)
1586 bTreatSpecial
= true;
1588 // remove and delete aCurrCC element from list (we've now merged its content)
1589 aCurrCC
= aCCList
.erase( aCurrCC
);
1591 // at least one component changed, need to rescan everything
1592 bSomeComponentsChanged
= true;
1600 while( bSomeComponentsChanged
);
1603 // STAGE 2.2: Determine special state for cc element
1605 // now test whether the whole connected component must be
1606 // treated specially (i.e. rendered as a bitmap): if the
1607 // added action is the very first action, or all actions
1608 // before it are completely transparent, the connected
1609 // component need not be treated specially, not even if
1610 // the added action contains transparency. This is because
1611 // painting of transparent objects on _white background_
1612 // works without alpha compositing (you just calculate the
1613 // color). Note that for the test "all objects before me
1614 // are transparent" no sorting is necessary, since the
1615 // added metaaction pCurrAct is always in the order the
1616 // metafile is painted. Generally, the order of the
1617 // metaactions in the ConnectedComponents are not
1618 // guaranteed to be the same as in the metafile.
1621 // prev component(s) special -> this one, too
1622 aTotalComponents
.bIsSpecial
= true;
1624 else if(!pCurrAct
->IsTransparent())
1626 // added action and none of prev components special ->
1627 // this one normal, too
1628 aTotalComponents
.bIsSpecial
= false;
1632 // added action is special and none of prev components
1633 // special -> do the detailed tests
1635 // can the action handle transparency correctly
1636 // (i.e. when painted on white background, does the
1637 // action still look correct)?
1638 if( !DoesActionHandleTransparency( *pCurrAct
) )
1640 // no, action cannot handle its transparency on
1641 // a printer device, render to bitmap
1642 aTotalComponents
.bIsSpecial
= true;
1646 // yes, action can handle its transparency, so
1647 // check whether we're on white background
1648 if( aTotalComponents
.aComponentList
.empty() )
1650 // nothing between pCurrAct and page
1651 // background -> don't be special
1652 aTotalComponents
.bIsSpecial
= false;
1656 // #107169# Fixes above now ensure that _no_
1657 // object in the list is fully transparent. Thus,
1658 // if the component list is not empty above, we
1659 // must assume that we have to treat this
1660 // component special.
1662 // there are non-transparent objects between
1663 // pCurrAct and the empty sheet of paper -> be
1665 aTotalComponents
.bIsSpecial
= true;
1670 // STAGE 2.3: Add newly generated CC list element
1672 // set new bounds and add action to list
1673 aTotalComponents
.aBounds
= aTotalBounds
;
1674 aTotalComponents
.aComponentList
.emplace_back(
1675 pCurrAct
, nActionNum
);
1677 // add aTotalComponents as a new entry to aCCList
1678 aCCList
.push_back( aTotalComponents
);
1680 SAL_WARN_IF( aTotalComponents
.aComponentList
.empty(), "vcl",
1681 "Printer::GetPreparedMetaFile empty component" );
1682 SAL_WARN_IF( aTotalComponents
.aBounds
.IsEmpty() && (aTotalComponents
.aComponentList
.size() != 1), "vcl",
1683 "Printer::GetPreparedMetaFile non-output generating actions must be solitary");
1684 SAL_WARN_IF( aTotalComponents
.bIsFullyTransparent
&& (aTotalComponents
.aComponentList
.size() != 1), "vcl",
1685 "Printer::GetPreparedMetaFile fully transparent actions must be solitary");
1688 // well now, we've got the list of disjunct connected
1689 // components. Now we've got to create a map, which contains
1690 // the corresponding aCCList element for every
1691 // metaaction. Later on, we always process the complete
1692 // metafile for each bitmap to be generated, but switch on
1693 // output only for actions contained in the then current
1694 // aCCList element. This ensures correct mapmode and attribute
1695 // settings for all cases.
1697 // maps mtf actions to CC list entries
1698 ::std::vector
< const ConnectedComponents
* > aCCList_MemberMap( rInMtf
.GetActionSize() );
1700 // iterate over all aCCList members and their contained metaactions
1701 for (auto const& currentItem
: aCCList
)
1703 for (auto const& currentAction
: currentItem
.aComponentList
)
1705 // set pointer to aCCList element for corresponding index
1706 aCCList_MemberMap
[ currentAction
.second
] = ¤tItem
;
1710 // STAGE 3.1: Output background mtf actions (if there are any)
1712 for (auto & component
: aBackgroundComponent
.aComponentList
)
1714 // simply add this action (above, we inserted the actions
1715 // starting at index 0 up to and including nLastBgAction)
1716 rOutMtf
.AddAction( component
.first
);
1719 // STAGE 3.2: Generate banded bitmaps for special regions
1722 Size
aTmpSize( GetOutputSizePixel() );
1723 if( meOutDevType
== OUTDEV_PDF
)
1725 auto pPdfWriter
= static_cast<vcl::PDFWriterImpl
*>(this);
1726 aTmpSize
= LogicToPixel(pPdfWriter
->getCurPageSize(), MapMode(MapUnit::MapPoint
));
1728 // also add error code to PDFWriter
1729 pPdfWriter
->insertError(vcl::PDFWriter::Warning_Transparency_Converted
);
1731 else if( meOutDevType
== OUTDEV_PRINTER
)
1733 Printer
* pThis
= dynamic_cast<Printer
*>(this);
1735 aPageOffset
= pThis
->GetPageOffsetPixel();
1736 aPageOffset
= Point( 0, 0 ) - aPageOffset
;
1737 aTmpSize
= pThis
->GetPaperSizePixel();
1739 const tools::Rectangle
aOutputRect( aPageOffset
, aTmpSize
);
1740 bool bTiling
= dynamic_cast<Printer
*>(this) != nullptr;
1742 // iterate over all aCCList members and generate bitmaps for the special ones
1743 for (auto & currentItem
: aCCList
)
1745 if( currentItem
.bIsSpecial
)
1747 tools::Rectangle
aBoundRect( currentItem
.aBounds
);
1748 aBoundRect
.Intersection( aOutputRect
);
1750 const double fBmpArea( static_cast<double>(aBoundRect
.GetWidth()) * aBoundRect
.GetHeight() );
1751 const double fOutArea( static_cast<double>(aOutputRect
.GetWidth()) * aOutputRect
.GetHeight() );
1753 // check if output doesn't exceed given size
1754 if( bReduceTransparency
&& bTransparencyAutoMode
&& ( fBmpArea
> ( fReduceTransparencyMinArea
* fOutArea
) ) )
1756 // output normally. Therefore, we simply clear the
1757 // special attribute, as everything non-special is
1758 // copied to rOutMtf further below.
1759 currentItem
.bIsSpecial
= false;
1763 // create new bitmap action first
1764 if( aBoundRect
.GetWidth() && aBoundRect
.GetHeight() )
1766 Point
aDstPtPix( aBoundRect
.TopLeft() );
1769 ScopedVclPtrInstance
<VirtualDevice
> aMapVDev
; // here, we record only mapmode information
1770 aMapVDev
->EnableOutput(false);
1772 ScopedVclPtrInstance
<VirtualDevice
> aPaintVDev
; // into this one, we render.
1773 aPaintVDev
->SetBackground( aBackgroundComponent
.aBgColor
);
1775 rOutMtf
.AddAction( new MetaPushAction( vcl::PushFlags::MAPMODE
) );
1776 rOutMtf
.AddAction( new MetaMapModeAction() );
1778 aPaintVDev
->SetDrawMode( GetDrawMode() );
1780 while( aDstPtPix
.Y() <= aBoundRect
.Bottom() )
1782 aDstPtPix
.setX( aBoundRect
.Left() );
1783 aDstSzPix
= bTiling
? Size( MAX_TILE_WIDTH
, MAX_TILE_HEIGHT
) : aBoundRect
.GetSize();
1785 if( ( aDstPtPix
.Y() + aDstSzPix
.Height() - 1 ) > aBoundRect
.Bottom() )
1786 aDstSzPix
.setHeight( aBoundRect
.Bottom() - aDstPtPix
.Y() + 1 );
1788 while( aDstPtPix
.X() <= aBoundRect
.Right() )
1790 if( ( aDstPtPix
.X() + aDstSzPix
.Width() - 1 ) > aBoundRect
.Right() )
1791 aDstSzPix
.setWidth( aBoundRect
.Right() - aDstPtPix
.X() + 1 );
1793 if( !tools::Rectangle( aDstPtPix
, aDstSzPix
).Intersection( aBoundRect
).IsEmpty() &&
1794 aPaintVDev
->SetOutputSizePixel( aDstSzPix
) )
1799 aMapVDev
->mnDPIX
= aPaintVDev
->mnDPIX
= mnDPIX
;
1800 aMapVDev
->mnDPIY
= aPaintVDev
->mnDPIY
= mnDPIY
;
1802 aPaintVDev
->EnableOutput(false);
1804 // iterate over all actions
1805 for( pCurrAct
=const_cast<GDIMetaFile
&>(rInMtf
).FirstAction(), nActionNum
=0;
1807 pCurrAct
=const_cast<GDIMetaFile
&>(rInMtf
).NextAction(), ++nActionNum
)
1809 // enable output only for
1810 // actions that are members of
1811 // the current aCCList element
1813 if( aCCList_MemberMap
[nActionNum
] == ¤tItem
)
1814 aPaintVDev
->EnableOutput();
1816 // but process every action
1817 const MetaActionType
nType( pCurrAct
->GetType() );
1819 if( MetaActionType::MAPMODE
== nType
)
1821 pCurrAct
->Execute( aMapVDev
.get() );
1823 MapMode
aMtfMap( aMapVDev
->GetMapMode() );
1824 const Point
aNewOrg( aMapVDev
->PixelToLogic( aDstPtPix
) );
1826 aMtfMap
.SetOrigin( Point( -aNewOrg
.X(), -aNewOrg
.Y() ) );
1827 aPaintVDev
->SetMapMode( aMtfMap
);
1829 else if( ( MetaActionType::PUSH
== nType
) || MetaActionType::POP
== nType
)
1831 pCurrAct
->Execute( aMapVDev
.get() );
1832 pCurrAct
->Execute( aPaintVDev
.get() );
1834 else if( MetaActionType::GRADIENT
== nType
)
1836 MetaGradientAction
* pGradientAction
= static_cast<MetaGradientAction
*>(pCurrAct
);
1837 Printer
* pPrinter
= dynamic_cast< Printer
* >(this);
1839 pPrinter
->DrawGradientEx( aPaintVDev
.get(), pGradientAction
->GetRect(), pGradientAction
->GetGradient() );
1841 DrawGradient( pGradientAction
->GetRect(), pGradientAction
->GetGradient() );
1845 pCurrAct
->Execute( aPaintVDev
.get() );
1848 Application::Reschedule( true );
1851 const bool bOldMap
= mbMap
;
1852 mbMap
= aPaintVDev
->mbMap
= false;
1854 Bitmap
aBandBmp( aPaintVDev
->GetBitmap( Point(), aDstSzPix
) );
1856 // scale down bitmap, if requested
1857 if( bDownsampleBitmaps
)
1858 aBandBmp
= vcl::bitmap::GetDownsampledBitmap(PixelToLogic(LogicToPixel(aDstSzPix
), MapMode(MapUnit::MapTwip
)),
1859 Point(), aBandBmp
.GetSizePixel(),
1860 aBandBmp
, nMaxBmpDPIX
, nMaxBmpDPIY
);
1862 rOutMtf
.AddAction( new MetaCommentAction( "PRNSPOOL_TRANSPARENTBITMAP_BEGIN"_ostr
) );
1863 rOutMtf
.AddAction( new MetaBmpScaleAction( aDstPtPix
, aDstSzPix
, aBandBmp
) );
1864 rOutMtf
.AddAction( new MetaCommentAction( "PRNSPOOL_TRANSPARENTBITMAP_END"_ostr
) );
1866 aPaintVDev
->mbMap
= true;
1872 // overlapping bands to avoid missing lines (e.g. PostScript)
1873 aDstPtPix
.AdjustX(aDstSzPix
.Width() );
1876 // overlapping bands to avoid missing lines (e.g. PostScript)
1877 aDstPtPix
.AdjustY(aDstSzPix
.Height() );
1880 rOutMtf
.AddAction( new MetaPopAction() );
1886 aMapModeVDev
->ClearStack(); // clean up aMapModeVDev
1888 // STAGE 4: Copy actions to output metafile
1890 // iterate over all actions and duplicate the ones not in a
1891 // special aCCList member into rOutMtf
1892 for( pCurrAct
=const_cast<GDIMetaFile
&>(rInMtf
).FirstAction(), nActionNum
=0;
1894 pCurrAct
=const_cast<GDIMetaFile
&>(rInMtf
).NextAction(), ++nActionNum
)
1896 const ConnectedComponents
* pCurrAssociatedComponent
= aCCList_MemberMap
[nActionNum
];
1898 // NOTE: This relies on the fact that map-mode or draw
1899 // mode changing actions are solitary aCCList elements and
1900 // have empty bounding boxes, see comment on stage 2.1
1902 if( pCurrAssociatedComponent
&&
1903 (pCurrAssociatedComponent
->aBounds
.IsEmpty() ||
1904 !pCurrAssociatedComponent
->bIsSpecial
) )
1906 // #107169# Treat transparent bitmaps special, if they
1907 // are the first (or sole) action in their bounds
1908 // list. Note that we previously ensured that no
1909 // fully-transparent objects are before us here.
1910 if( DoesActionHandleTransparency( *pCurrAct
) &&
1911 pCurrAssociatedComponent
->aComponentList
.begin()->first
== pCurrAct
)
1913 // convert actions, where masked-out parts are of
1914 // given background color
1915 ImplConvertTransparentAction(rOutMtf
,
1918 aBackgroundComponent
.aBgColor
);
1922 // simply add this action
1923 rOutMtf
.AddAction( pCurrAct
);
1926 pCurrAct
->Execute(aMapModeVDev
.get());
1930 rOutMtf
.SetPrefMapMode( rInMtf
.GetPrefMapMode() );
1931 rOutMtf
.SetPrefSize( rInMtf
.GetPrefSize() );
1933 #if OSL_DEBUG_LEVEL > 1
1934 // iterate over all aCCList members and generate rectangles for the bounding boxes
1935 rOutMtf
.AddAction( new MetaFillColorAction( COL_WHITE
, false ) );
1936 for(auto const& aCurr
:aCCList
)
1938 if( aCurr
.bIsSpecial
)
1939 rOutMtf
.AddAction( new MetaLineColorAction( COL_RED
, true) );
1941 rOutMtf
.AddAction( new MetaLineColorAction( COL_BLUE
, true) );
1943 rOutMtf
.AddAction( new MetaRectAction( aMapModeVDev
->PixelToLogic( aCurr
.aBounds
) ) );
1947 return bTransparent
;
1950 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */