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 "vclpixelprocessor2d.hxx"
21 #include "vclhelperbufferdevice.hxx"
22 #include "helperwrongspellrenderer.hxx"
23 
24 #include <sal/log.hxx>
25 #include <tools/stream.hxx>
26 #include <vcl/BitmapBasicMorphologyFilter.hxx>
27 #include <vcl/BitmapFilterStackBlur.hxx>
28 #include <vcl/outdev.hxx>
29 #include <vcl/dibtools.hxx>
30 #include <vcl/hatch.hxx>
31 #include <basegfx/polygon/b2dpolygontools.hxx>
32 
33 #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx>
34 #include <drawinglayer/primitive2d/Tools.hxx>
35 #include <drawinglayer/primitive2d/textprimitive2d.hxx>
36 #include <drawinglayer/primitive2d/PolyPolygonHairlinePrimitive2D.hxx>
37 #include <drawinglayer/primitive2d/PolyPolygonMarkerPrimitive2D.hxx>
38 #include <drawinglayer/primitive2d/PolyPolygonStrokePrimitive2D.hxx>
39 #include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx>
40 #include <drawinglayer/primitive2d/PolyPolygonGradientPrimitive2D.hxx>
41 #include <drawinglayer/primitive2d/PolyPolygonHatchPrimitive2D.hxx>
42 #include <drawinglayer/primitive2d/PolyPolygonGraphicPrimitive2D.hxx>
43 #include <drawinglayer/primitive2d/PolyPolygonSelectionPrimitive2D.hxx>
44 #include <drawinglayer/primitive2d/polygonprimitive2d.hxx>
45 #include <drawinglayer/primitive2d/bitmapprimitive2d.hxx>
46 #include <drawinglayer/primitive2d/fillgraphicprimitive2d.hxx>
47 #include <drawinglayer/primitive2d/maskprimitive2d.hxx>
48 #include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx>
49 #include <drawinglayer/primitive2d/transparenceprimitive2d.hxx>
50 #include <drawinglayer/primitive2d/transformprimitive2d.hxx>
51 #include <drawinglayer/primitive2d/markerarrayprimitive2d.hxx>
52 #include <drawinglayer/primitive2d/glowprimitive2d.hxx>
53 #include <drawinglayer/primitive2d/wrongspellprimitive2d.hxx>
54 #include <drawinglayer/primitive2d/controlprimitive2d.hxx>
55 #include <drawinglayer/primitive2d/borderlineprimitive2d.hxx>
56 #include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx>
57 #include <drawinglayer/primitive2d/pagepreviewprimitive2d.hxx>
58 #include <drawinglayer/primitive2d/backgroundcolorprimitive2d.hxx>
59 #include <drawinglayer/primitive2d/svggradientprimitive2d.hxx>
60 #include <drawinglayer/primitive2d/pointarrayprimitive2d.hxx>
61 #include <drawinglayer/primitive2d/fillhatchprimitive2d.hxx>
62 #include <drawinglayer/primitive2d/epsprimitive2d.hxx>
63 #include <drawinglayer/primitive2d/softedgeprimitive2d.hxx>
64 #include <drawinglayer/primitive2d/shadowprimitive2d.hxx>
65 
66 #include <com/sun/star/awt/XWindow2.hpp>
67 #include <com/sun/star/awt/XControl.hpp>
68 
69 using namespace com::sun::star;
70 
71 namespace drawinglayer::processor2d
72 {
73 struct VclPixelProcessor2D::Impl
74 {
75     AntialiasingFlags m_nOrigAntiAliasing;
76 
77     explicit Impl(OutputDevice const& rOutDev)
78         : m_nOrigAntiAliasing(rOutDev.GetAntialiasing())
79     {
80     }
81 };
82 
83 VclPixelProcessor2D::VclPixelProcessor2D(const geometry::ViewInformation2D& rViewInformation,
84                                          OutputDevice& rOutDev)
85     : VclProcessor2D(rViewInformation, rOutDev)
86     , m_pImpl(new Impl(rOutDev))
87 {
88     // prepare maCurrentTransformation matrix with viewTransformation to target directly to pixels
89     maCurrentTransformation = rViewInformation.getObjectToViewTransformation();
90 
91     // prepare output directly to pixels
92     mpOutputDevice->Push(PushFlags::MAPMODE);
93     mpOutputDevice->SetMapMode();
94 
95     // react on AntiAliasing settings
96     if (getOptionsDrawinglayer().IsAntiAliasing())
97     {
98         mpOutputDevice->SetAntialiasing(m_pImpl->m_nOrigAntiAliasing
99                                         | AntialiasingFlags::EnableB2dDraw);
100     }
101     else
102     {
103         mpOutputDevice->SetAntialiasing(m_pImpl->m_nOrigAntiAliasing
104                                         & ~AntialiasingFlags::EnableB2dDraw);
105     }
106 }
107 
108 VclPixelProcessor2D::~VclPixelProcessor2D()
109 {
110     // restore MapMode
111     mpOutputDevice->Pop();
112 
113     // restore AntiAliasing
114     mpOutputDevice->SetAntialiasing(m_pImpl->m_nOrigAntiAliasing);
115 }
116 
117 void VclPixelProcessor2D::tryDrawPolyPolygonColorPrimitive2DDirect(
118     const drawinglayer::primitive2d::PolyPolygonColorPrimitive2D& rSource, double fTransparency)
119 {
120     if (!rSource.getB2DPolyPolygon().count() || fTransparency < 0.0 || fTransparency >= 1.0)
121     {
122         // no geometry, done
123         return;
124     }
125 
126     const basegfx::BColor aPolygonColor(
127         maBColorModifierStack.getModifiedColor(rSource.getBColor()));
128 
129     mpOutputDevice->SetFillColor(Color(aPolygonColor));
130     mpOutputDevice->SetLineColor();
131     mpOutputDevice->DrawTransparent(maCurrentTransformation, rSource.getB2DPolyPolygon(),
132                                     fTransparency);
133 }
134 
135 bool VclPixelProcessor2D::tryDrawPolygonHairlinePrimitive2DDirect(
136     const drawinglayer::primitive2d::PolygonHairlinePrimitive2D& rSource, double fTransparency)
137 {
138     const basegfx::B2DPolygon& rLocalPolygon(rSource.getB2DPolygon());
139 
140     if (!rLocalPolygon.count() || fTransparency < 0.0 || fTransparency >= 1.0)
141     {
142         // no geometry, done
143         return true;
144     }
145 
146     const basegfx::BColor aLineColor(maBColorModifierStack.getModifiedColor(rSource.getBColor()));
147 
148     mpOutputDevice->SetFillColor();
149     mpOutputDevice->SetLineColor(Color(aLineColor));
150     //aLocalPolygon.transform(maCurrentTransformation);
151 
152     // try drawing; if it did not work, use standard fallback
153     return mpOutputDevice->DrawPolyLineDirect(maCurrentTransformation, rLocalPolygon, 0.0,
154                                               fTransparency);
155 }
156 
157 bool VclPixelProcessor2D::tryDrawPolygonStrokePrimitive2DDirect(
158     const drawinglayer::primitive2d::PolygonStrokePrimitive2D& rSource, double fTransparency)
159 {
160     const basegfx::B2DPolygon& rLocalPolygon(rSource.getB2DPolygon());
161 
162     if (!rLocalPolygon.count() || fTransparency < 0.0 || fTransparency >= 1.0)
163     {
164         // no geometry, done
165         return true;
166     }
167 
168     if (basegfx::B2DLineJoin::NONE == rSource.getLineAttribute().getLineJoin()
169         && css::drawing::LineCap_BUTT != rSource.getLineAttribute().getLineCap())
170     {
171         // better use decompose to get that combination done for now, see discussion
172         // at https://bugs.documentfoundation.org/show_bug.cgi?id=130478#c17 and ff
173         return false;
174     }
175 
176     // MM01: Radically change here - no dismantle/applyLineDashing,
177     // let that happen low-level at DrawPolyLineDirect implementations
178     // to open up for buffering and evtl. direct draw with sys-dep
179     // graphic systems. Check for stroke is in use
180     const bool bStrokeAttributeNotUsed(rSource.getStrokeAttribute().isDefault()
181                                        || 0.0 == rSource.getStrokeAttribute().getFullDotDashLen());
182 
183     const basegfx::BColor aLineColor(
184         maBColorModifierStack.getModifiedColor(rSource.getLineAttribute().getColor()));
185 
186     mpOutputDevice->SetFillColor();
187     mpOutputDevice->SetLineColor(Color(aLineColor));
188 
189     // MM01 draw direct, hand over dash data if available
190     return mpOutputDevice->DrawPolyLineDirect(
191         maCurrentTransformation, rLocalPolygon,
192         // tdf#124848 use LineWidth direct, do not try to solve for zero-case (aka hairline)
193         rSource.getLineAttribute().getWidth(), fTransparency,
194         bStrokeAttributeNotUsed ? nullptr : &rSource.getStrokeAttribute().getDotDashArray(),
195         rSource.getLineAttribute().getLineJoin(), rSource.getLineAttribute().getLineCap(),
196         rSource.getLineAttribute().getMiterMinimumAngle()
197         /* false bBypassAACheck, default*/);
198 }
199 
200 void VclPixelProcessor2D::processBasePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate)
201 {
202     switch (rCandidate.getPrimitive2DID())
203     {
204         case PRIMITIVE2D_ID_WRONGSPELLPRIMITIVE2D:
205         {
206             processWrongSpellPrimitive2D(
207                 static_cast<const primitive2d::WrongSpellPrimitive2D&>(rCandidate));
208             break;
209         }
210         case PRIMITIVE2D_ID_TEXTSIMPLEPORTIONPRIMITIVE2D:
211         {
212             processTextSimplePortionPrimitive2D(
213                 static_cast<const primitive2d::TextSimplePortionPrimitive2D&>(rCandidate));
214             break;
215         }
216         case PRIMITIVE2D_ID_TEXTDECORATEDPORTIONPRIMITIVE2D:
217         {
218             processTextDecoratedPortionPrimitive2D(
219                 static_cast<const primitive2d::TextSimplePortionPrimitive2D&>(rCandidate));
220             break;
221         }
222         case PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D:
223         {
224             processPolygonHairlinePrimitive2D(
225                 static_cast<const primitive2d::PolygonHairlinePrimitive2D&>(rCandidate));
226             break;
227         }
228         case PRIMITIVE2D_ID_BITMAPPRIMITIVE2D:
229         {
230             // direct draw of transformed BitmapEx primitive
231             processBitmapPrimitive2D(
232                 static_cast<const primitive2d::BitmapPrimitive2D&>(rCandidate));
233             break;
234         }
235         case PRIMITIVE2D_ID_FILLGRAPHICPRIMITIVE2D:
236         {
237             // direct draw of fillBitmapPrimitive
238             RenderFillGraphicPrimitive2D(
239                 static_cast<const primitive2d::FillGraphicPrimitive2D&>(rCandidate));
240             break;
241         }
242         case PRIMITIVE2D_ID_POLYPOLYGONGRADIENTPRIMITIVE2D:
243         {
244             processPolyPolygonGradientPrimitive2D(
245                 static_cast<const primitive2d::PolyPolygonGradientPrimitive2D&>(rCandidate));
246             break;
247         }
248         case PRIMITIVE2D_ID_POLYPOLYGONGRAPHICPRIMITIVE2D:
249         {
250             // direct draw of bitmap
251             RenderPolyPolygonGraphicPrimitive2D(
252                 static_cast<const primitive2d::PolyPolygonGraphicPrimitive2D&>(rCandidate));
253             break;
254         }
255         case PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D:
256         {
257             processPolyPolygonColorPrimitive2D(
258                 static_cast<const primitive2d::PolyPolygonColorPrimitive2D&>(rCandidate));
259             break;
260         }
261         case PRIMITIVE2D_ID_METAFILEPRIMITIVE2D:
262         {
263             processMetaFilePrimitive2D(rCandidate);
264             break;
265         }
266         case PRIMITIVE2D_ID_MASKPRIMITIVE2D:
267         {
268             // mask group.
269             RenderMaskPrimitive2DPixel(
270                 static_cast<const primitive2d::MaskPrimitive2D&>(rCandidate));
271             break;
272         }
273         case PRIMITIVE2D_ID_MODIFIEDCOLORPRIMITIVE2D:
274         {
275             // modified color group. Force output to unified color.
276             RenderModifiedColorPrimitive2D(
277                 static_cast<const primitive2d::ModifiedColorPrimitive2D&>(rCandidate));
278             break;
279         }
280         case PRIMITIVE2D_ID_UNIFIEDTRANSPARENCEPRIMITIVE2D:
281         {
282             processUnifiedTransparencePrimitive2D(
283                 static_cast<const primitive2d::UnifiedTransparencePrimitive2D&>(rCandidate));
284             break;
285         }
286         case PRIMITIVE2D_ID_TRANSPARENCEPRIMITIVE2D:
287         {
288             // sub-transparence group. Draw to VDev first.
289             RenderTransparencePrimitive2D(
290                 static_cast<const primitive2d::TransparencePrimitive2D&>(rCandidate));
291             break;
292         }
293         case PRIMITIVE2D_ID_TRANSFORMPRIMITIVE2D:
294         {
295             // transform group.
296             RenderTransformPrimitive2D(
297                 static_cast<const primitive2d::TransformPrimitive2D&>(rCandidate));
298             break;
299         }
300         case PRIMITIVE2D_ID_PAGEPREVIEWPRIMITIVE2D:
301         {
302             // new XDrawPage for ViewInformation2D
303             RenderPagePreviewPrimitive2D(
304                 static_cast<const primitive2d::PagePreviewPrimitive2D&>(rCandidate));
305             break;
306         }
307         case PRIMITIVE2D_ID_MARKERARRAYPRIMITIVE2D:
308         {
309             // marker array
310             RenderMarkerArrayPrimitive2D(
311                 static_cast<const primitive2d::MarkerArrayPrimitive2D&>(rCandidate));
312             break;
313         }
314         case PRIMITIVE2D_ID_POINTARRAYPRIMITIVE2D:
315         {
316             // point array
317             RenderPointArrayPrimitive2D(
318                 static_cast<const primitive2d::PointArrayPrimitive2D&>(rCandidate));
319             break;
320         }
321         case PRIMITIVE2D_ID_CONTROLPRIMITIVE2D:
322         {
323             processControlPrimitive2D(
324                 static_cast<const primitive2d::ControlPrimitive2D&>(rCandidate));
325             break;
326         }
327         case PRIMITIVE2D_ID_POLYGONSTROKEPRIMITIVE2D:
328         {
329             processPolygonStrokePrimitive2D(
330                 static_cast<const primitive2d::PolygonStrokePrimitive2D&>(rCandidate));
331             break;
332         }
333         case PRIMITIVE2D_ID_FILLHATCHPRIMITIVE2D:
334         {
335             processFillHatchPrimitive2D(
336                 static_cast<const primitive2d::FillHatchPrimitive2D&>(rCandidate));
337             break;
338         }
339         case PRIMITIVE2D_ID_BACKGROUNDCOLORPRIMITIVE2D:
340         {
341             processBackgroundColorPrimitive2D(
342                 static_cast<const primitive2d::BackgroundColorPrimitive2D&>(rCandidate));
343             break;
344         }
345         case PRIMITIVE2D_ID_TEXTHIERARCHYEDITPRIMITIVE2D:
346         {
347             // #i97628#
348             // This primitive means that the content is derived from an active text edit,
349             // not from model data itself. Some renderers need to suppress this content, e.g.
350             // the pixel renderer used for displaying the edit view (like this one). It's
351             // not to be suppressed by the MetaFile renderers, so that the edited text is
352             // part of the MetaFile, e.g. needed for presentation previews.
353             // Action: Ignore here, do nothing.
354             break;
355         }
356         case PRIMITIVE2D_ID_INVERTPRIMITIVE2D:
357         {
358             processInvertPrimitive2D(rCandidate);
359             break;
360         }
361         case PRIMITIVE2D_ID_EPSPRIMITIVE2D:
362         {
363             RenderEpsPrimitive2D(static_cast<const primitive2d::EpsPrimitive2D&>(rCandidate));
364             break;
365         }
366         case PRIMITIVE2D_ID_SVGLINEARATOMPRIMITIVE2D:
367         {
368             RenderSvgLinearAtomPrimitive2D(
369                 static_cast<const primitive2d::SvgLinearAtomPrimitive2D&>(rCandidate));
370             break;
371         }
372         case PRIMITIVE2D_ID_SVGRADIALATOMPRIMITIVE2D:
373         {
374             RenderSvgRadialAtomPrimitive2D(
375                 static_cast<const primitive2d::SvgRadialAtomPrimitive2D&>(rCandidate));
376             break;
377         }
378         case PRIMITIVE2D_ID_BORDERLINEPRIMITIVE2D:
379         {
380             processBorderLinePrimitive2D(
381                 static_cast<const drawinglayer::primitive2d::BorderLinePrimitive2D&>(rCandidate));
382             break;
383         }
384         case PRIMITIVE2D_ID_GLOWPRIMITIVE2D:
385         {
386             processGlowPrimitive2D(
387                 static_cast<const drawinglayer::primitive2d::GlowPrimitive2D&>(rCandidate));
388             break;
389         }
390         case PRIMITIVE2D_ID_SOFTEDGEPRIMITIVE2D:
391         {
392             processSoftEdgePrimitive2D(
393                 static_cast<const drawinglayer::primitive2d::SoftEdgePrimitive2D&>(rCandidate));
394             break;
395         }
396         case PRIMITIVE2D_ID_SHADOWPRIMITIVE2D:
397         {
398             processShadowPrimitive2D(
399                 static_cast<const drawinglayer::primitive2d::ShadowPrimitive2D&>(rCandidate));
400             break;
401         }
402         default:
403         {
404             SAL_INFO("drawinglayer", "default case for " << drawinglayer::primitive2d::idToString(
405                                          rCandidate.getPrimitive2DID()));
406             // process recursively
407             process(rCandidate);
408             break;
409         }
410     }
411 }
412 
413 void VclPixelProcessor2D::processWrongSpellPrimitive2D(
414     const primitive2d::WrongSpellPrimitive2D& rWrongSpellPrimitive)
415 {
416     if (!renderWrongSpellPrimitive2D(rWrongSpellPrimitive, *mpOutputDevice, maCurrentTransformation,
417                                      maBColorModifierStack))
418     {
419         // fallback to decomposition (MetaFile)
420         process(rWrongSpellPrimitive);
421     }
422 }
423 
424 void VclPixelProcessor2D::processTextSimplePortionPrimitive2D(
425     const primitive2d::TextSimplePortionPrimitive2D& rCandidate)
426 {
427     // Adapt evtl. used special DrawMode
428     const DrawModeFlags nOriginalDrawMode(mpOutputDevice->GetDrawMode());
429     adaptTextToFillDrawMode();
430 
431     if (getOptionsDrawinglayer().IsRenderSimpleTextDirect())
432     {
433         RenderTextSimpleOrDecoratedPortionPrimitive2D(rCandidate);
434     }
435     else
436     {
437         process(rCandidate);
438     }
439 
440     // restore DrawMode
441     mpOutputDevice->SetDrawMode(nOriginalDrawMode);
442 }
443 
444 void VclPixelProcessor2D::processTextDecoratedPortionPrimitive2D(
445     const primitive2d::TextSimplePortionPrimitive2D& rCandidate)
446 {
447     // Adapt evtl. used special DrawMode
448     const DrawModeFlags nOriginalDrawMode(mpOutputDevice->GetDrawMode());
449     adaptTextToFillDrawMode();
450 
451     if (getOptionsDrawinglayer().IsRenderDecoratedTextDirect())
452     {
453         RenderTextSimpleOrDecoratedPortionPrimitive2D(rCandidate);
454     }
455     else
456     {
457         process(rCandidate);
458     }
459 
460     // restore DrawMode
461     mpOutputDevice->SetDrawMode(nOriginalDrawMode);
462 }
463 
464 void VclPixelProcessor2D::processPolygonHairlinePrimitive2D(
465     const primitive2d::PolygonHairlinePrimitive2D& rPolygonHairlinePrimitive2D)
466 {
467     if (tryDrawPolygonHairlinePrimitive2DDirect(rPolygonHairlinePrimitive2D, 0.0))
468     {
469         return;
470     }
471 
472     // direct draw of hairline
473     RenderPolygonHairlinePrimitive2D(rPolygonHairlinePrimitive2D, true);
474 }
475 
476 void VclPixelProcessor2D::processBitmapPrimitive2D(
477     const primitive2d::BitmapPrimitive2D& rBitmapCandidate)
478 {
479     // check if graphic content is inside discrete local ViewPort
480     const basegfx::B2DRange& rDiscreteViewPort(getViewInformation2D().getDiscreteViewport());
481     const basegfx::B2DHomMatrix aLocalTransform(maCurrentTransformation
482                                                 * rBitmapCandidate.getTransform());
483 
484     if (!rDiscreteViewPort.isEmpty())
485     {
486         basegfx::B2DRange aUnitRange(0.0, 0.0, 1.0, 1.0);
487 
488         aUnitRange.transform(aLocalTransform);
489 
490         if (!aUnitRange.overlaps(rDiscreteViewPort))
491         {
492             // content is outside discrete local ViewPort
493             return;
494         }
495     }
496 
497     RenderBitmapPrimitive2D(rBitmapCandidate);
498 }
499 
500 void VclPixelProcessor2D::processPolyPolygonGradientPrimitive2D(
501     const primitive2d::PolyPolygonGradientPrimitive2D& rPolygonCandidate)
502 {
503     // direct draw of gradient
504     const attribute::FillGradientAttribute& rGradient(rPolygonCandidate.getFillGradient());
505     basegfx::BColor aStartColor(maBColorModifierStack.getModifiedColor(rGradient.getStartColor()));
506     basegfx::BColor aEndColor(maBColorModifierStack.getModifiedColor(rGradient.getEndColor()));
507     basegfx::B2DPolyPolygon aLocalPolyPolygon(rPolygonCandidate.getB2DPolyPolygon());
508 
509     if (!aLocalPolyPolygon.count())
510         return;
511 
512     if (aStartColor == aEndColor)
513     {
514         // no gradient at all, draw as polygon in AA and non-AA case
515         aLocalPolyPolygon.transform(maCurrentTransformation);
516         mpOutputDevice->SetLineColor();
517         mpOutputDevice->SetFillColor(Color(aStartColor));
518         mpOutputDevice->DrawPolyPolygon(aLocalPolyPolygon);
519     }
520     else
521     {
522         // use the primitive decomposition of the metafile
523         process(rPolygonCandidate);
524     }
525 }
526 
527 void VclPixelProcessor2D::processPolyPolygonColorPrimitive2D(
528     const primitive2d::PolyPolygonColorPrimitive2D& rPolyPolygonColorPrimitive2D)
529 {
530     // try to use directly
531     basegfx::B2DPolyPolygon aLocalPolyPolygon;
532 
533     tryDrawPolyPolygonColorPrimitive2DDirect(rPolyPolygonColorPrimitive2D, 0.0);
534     // okay, done. In this case no gaps should have to be repaired, too
535 
536     // when AA is on and this filled polygons are the result of stroked line geometry,
537     // draw the geometry once extra as lines to avoid AA 'gaps' between partial polygons
538     // Caution: This is needed in both cases (!)
539     if (!(mnPolygonStrokePrimitive2D && getOptionsDrawinglayer().IsAntiAliasing()
540           && (mpOutputDevice->GetAntialiasing() & AntialiasingFlags::EnableB2dDraw)))
541         return;
542 
543     const basegfx::BColor aPolygonColor(
544         maBColorModifierStack.getModifiedColor(rPolyPolygonColorPrimitive2D.getBColor()));
545     sal_uInt32 nCount(aLocalPolyPolygon.count());
546 
547     if (!nCount)
548     {
549         aLocalPolyPolygon = rPolyPolygonColorPrimitive2D.getB2DPolyPolygon();
550         aLocalPolyPolygon.transform(maCurrentTransformation);
551         nCount = aLocalPolyPolygon.count();
552     }
553 
554     mpOutputDevice->SetFillColor();
555     mpOutputDevice->SetLineColor(Color(aPolygonColor));
556 
557     for (sal_uInt32 a(0); a < nCount; a++)
558     {
559         mpOutputDevice->DrawPolyLine(aLocalPolyPolygon.getB2DPolygon(a), 0.0);
560     }
561 }
562 
563 void VclPixelProcessor2D::processUnifiedTransparencePrimitive2D(
564     const primitive2d::UnifiedTransparencePrimitive2D& rUniTransparenceCandidate)
565 {
566     // Detect if a single PolyPolygonColorPrimitive2D is contained; in that case,
567     // use the faster OutputDevice::DrawTransparent method
568     const primitive2d::Primitive2DContainer& rContent = rUniTransparenceCandidate.getChildren();
569 
570     if (rContent.empty())
571         return;
572 
573     if (0.0 == rUniTransparenceCandidate.getTransparence())
574     {
575         // not transparent at all, use content
576         process(rUniTransparenceCandidate.getChildren());
577     }
578     else if (rUniTransparenceCandidate.getTransparence() > 0.0
579              && rUniTransparenceCandidate.getTransparence() < 1.0)
580     {
581         bool bDrawTransparentUsed(false);
582 
583         if (1 == rContent.size())
584         {
585             const primitive2d::Primitive2DReference xReference(rContent[0]);
586             const primitive2d::BasePrimitive2D* pBasePrimitive
587                 = dynamic_cast<const primitive2d::BasePrimitive2D*>(xReference.get());
588 
589             if (pBasePrimitive)
590             {
591                 switch (pBasePrimitive->getPrimitive2DID())
592                 {
593                     case PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D:
594                     {
595                         // single transparent tools::PolyPolygon identified, use directly
596                         const primitive2d::PolyPolygonColorPrimitive2D* pPoPoColor
597                             = static_cast<const primitive2d::PolyPolygonColorPrimitive2D*>(
598                                 pBasePrimitive);
599                         SAL_WARN_IF(!pPoPoColor, "drawinglayer",
600                                     "OOps, PrimitiveID and PrimitiveType do not match (!)");
601                         bDrawTransparentUsed = true;
602                         tryDrawPolyPolygonColorPrimitive2DDirect(
603                             *pPoPoColor, rUniTransparenceCandidate.getTransparence());
604                         break;
605                     }
606                     case PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D:
607                     {
608                         // single transparent PolygonHairlinePrimitive2D identified, use directly
609                         const primitive2d::PolygonHairlinePrimitive2D* pPoHair
610                             = static_cast<const primitive2d::PolygonHairlinePrimitive2D*>(
611                                 pBasePrimitive);
612                         SAL_WARN_IF(!pPoHair, "drawinglayer",
613                                     "OOps, PrimitiveID and PrimitiveType do not match (!)");
614 
615                         // do no tallow by default - problem is that self-overlapping parts of this geometry will
616                         // not be in an all-same transparency but will already alpha-cover themselves with blending.
617                         // This is not what the UnifiedTransparencePrimitive2D defines: It requires all its
618                         // content to be uniformly transparent.
619                         // For hairline the effect is pretty minimal, but still not correct.
620                         bDrawTransparentUsed = false;
621                         break;
622                     }
623                     case PRIMITIVE2D_ID_POLYGONSTROKEPRIMITIVE2D:
624                     {
625                         // single transparent PolygonStrokePrimitive2D identified, use directly
626                         const primitive2d::PolygonStrokePrimitive2D* pPoStroke
627                             = static_cast<const primitive2d::PolygonStrokePrimitive2D*>(
628                                 pBasePrimitive);
629                         SAL_WARN_IF(!pPoStroke, "drawinglayer",
630                                     "OOps, PrimitiveID and PrimitiveType do not match (!)");
631 
632                         // do no tallow by default - problem is that self-overlapping parts of this geometry will
633                         // not be in an all-same transparency but will already alpha-cover themselves with blending.
634                         // This is not what the UnifiedTransparencePrimitive2D defines: It requires all its
635                         // content to be uniformly transparent.
636                         // To check, activate and draw a wide transparent self-crossing line/curve
637                         bDrawTransparentUsed = false;
638                         break;
639                     }
640                     default:
641                         SAL_INFO("drawinglayer",
642                                  "default case for " << drawinglayer::primitive2d::idToString(
643                                      rUniTransparenceCandidate.getPrimitive2DID()));
644                         break;
645                 }
646             }
647         }
648 
649         if (!bDrawTransparentUsed)
650         {
651             // unified sub-transparence. Draw to VDev first.
652             RenderUnifiedTransparencePrimitive2D(rUniTransparenceCandidate);
653         }
654     }
655 }
656 
657 void VclPixelProcessor2D::processControlPrimitive2D(
658     const primitive2d::ControlPrimitive2D& rControlPrimitive)
659 {
660     // control primitive
661     const uno::Reference<awt::XControl>& rXControl(rControlPrimitive.getXControl());
662 
663     try
664     {
665         // remember old graphics and create new
666         uno::Reference<awt::XView> xControlView(rXControl, uno::UNO_QUERY_THROW);
667         const uno::Reference<awt::XGraphics> xOriginalGraphics(xControlView->getGraphics());
668         const uno::Reference<awt::XGraphics> xNewGraphics(mpOutputDevice->CreateUnoGraphics());
669 
670         if (xNewGraphics.is())
671         {
672             // link graphics and view
673             xControlView->setGraphics(xNewGraphics);
674 
675             // get position
676             const basegfx::B2DHomMatrix aObjectToPixel(maCurrentTransformation
677                                                        * rControlPrimitive.getTransform());
678             const basegfx::B2DPoint aTopLeftPixel(aObjectToPixel * basegfx::B2DPoint(0.0, 0.0));
679 
680             // find out if the control is already visualized as a VCL-ChildWindow. If yes,
681             // it does not need to be painted at all.
682             uno::Reference<awt::XWindow2> xControlWindow(rXControl, uno::UNO_QUERY_THROW);
683             const bool bControlIsVisibleAsChildWindow(rXControl->getPeer().is()
684                                                       && xControlWindow->isVisible());
685 
686             if (!bControlIsVisibleAsChildWindow)
687             {
688                 // draw it. Do not forget to use the evtl. offsetted origin of the target device,
689                 // e.g. when used with mask/transparence buffer device
690                 const Point aOrigin(mpOutputDevice->GetMapMode().GetOrigin());
691                 xControlView->draw(aOrigin.X() + basegfx::fround(aTopLeftPixel.getX()),
692                                    aOrigin.Y() + basegfx::fround(aTopLeftPixel.getY()));
693             }
694 
695             // restore original graphics
696             xControlView->setGraphics(xOriginalGraphics);
697         }
698     }
699     catch (const uno::Exception&)
700     {
701         // #i116763# removing since there is a good alternative when the xControlView
702         // is not found and it is allowed to happen
703         // DBG_UNHANDLED_EXCEPTION();
704 
705         // process recursively and use the decomposition as Bitmap
706         process(rControlPrimitive);
707     }
708 }
709 
710 void VclPixelProcessor2D::processPolygonStrokePrimitive2D(
711     const primitive2d::PolygonStrokePrimitive2D& rPolygonStrokePrimitive2D)
712 {
713     // try to use directly
714     if (tryDrawPolygonStrokePrimitive2DDirect(rPolygonStrokePrimitive2D, 0.0))
715     {
716         return;
717     }
718 
719     // the stroke primitive may be decomposed to filled polygons. To keep
720     // evtl. set DrawModes aka DrawModeFlags::BlackLine, DrawModeFlags::GrayLine,
721     // DrawModeFlags::GhostedLine, DrawModeFlags::WhiteLine or DrawModeFlags::SettingsLine
722     // working, these need to be copied to the corresponding fill modes
723     const DrawModeFlags nOriginalDrawMode(mpOutputDevice->GetDrawMode());
724     adaptLineToFillDrawMode();
725 
726     // polygon stroke primitive
727 
728     // Lines with 1 and 2 pixel width without AA need special treatment since their visualization
729     // as filled polygons is geometrically correct but looks wrong since polygon filling avoids
730     // the right and bottom pixels. The used method evaluates that and takes the correct action,
731     // including calling recursively with decomposition if line is wide enough
732     RenderPolygonStrokePrimitive2D(rPolygonStrokePrimitive2D);
733 
734     // restore DrawMode
735     mpOutputDevice->SetDrawMode(nOriginalDrawMode);
736 }
737 
738 void VclPixelProcessor2D::processFillHatchPrimitive2D(
739     const primitive2d::FillHatchPrimitive2D& rFillHatchPrimitive)
740 {
741     if (getOptionsDrawinglayer().IsAntiAliasing())
742     {
743         // if AA is used (or ignore smoothing is on), there is no need to smooth
744         // hatch painting, use decomposition
745         process(rFillHatchPrimitive);
746     }
747     else
748     {
749         // without AA, use VCL to draw the hatch. It snaps hatch distances to the next pixel
750         // and forces hatch distance to be >= 3 pixels to make the hatch display look smoother.
751         // This is wrong in principle, but looks nicer. This could also be done here directly
752         // without VCL usage if needed
753         const attribute::FillHatchAttribute& rFillHatchAttributes
754             = rFillHatchPrimitive.getFillHatch();
755 
756         // create hatch polygon in range size and discrete coordinates
757         basegfx::B2DRange aHatchRange(rFillHatchPrimitive.getOutputRange());
758         aHatchRange.transform(maCurrentTransformation);
759         const basegfx::B2DPolygon aHatchPolygon(basegfx::utils::createPolygonFromRect(aHatchRange));
760 
761         if (rFillHatchAttributes.isFillBackground())
762         {
763             // #i111846# background fill is active; draw fill polygon
764             const basegfx::BColor aPolygonColor(
765                 maBColorModifierStack.getModifiedColor(rFillHatchPrimitive.getBColor()));
766 
767             mpOutputDevice->SetFillColor(Color(aPolygonColor));
768             mpOutputDevice->SetLineColor();
769             mpOutputDevice->DrawPolygon(aHatchPolygon);
770         }
771 
772         // set hatch line color
773         const basegfx::BColor aHatchColor(
774             maBColorModifierStack.getModifiedColor(rFillHatchPrimitive.getBColor()));
775         mpOutputDevice->SetFillColor();
776         mpOutputDevice->SetLineColor(Color(aHatchColor));
777 
778         // get hatch style
779         HatchStyle eHatchStyle(HatchStyle::Single);
780 
781         switch (rFillHatchAttributes.getStyle())
782         {
783             default: // HatchStyle::Single
784             {
785                 break;
786             }
787             case attribute::HatchStyle::Double:
788             {
789                 eHatchStyle = HatchStyle::Double;
790                 break;
791             }
792             case attribute::HatchStyle::Triple:
793             {
794                 eHatchStyle = HatchStyle::Triple;
795                 break;
796             }
797         }
798 
799         // create hatch
800         const basegfx::B2DVector aDiscreteDistance(
801             maCurrentTransformation * basegfx::B2DVector(rFillHatchAttributes.getDistance(), 0.0));
802         const sal_uInt32 nDistance(basegfx::fround(aDiscreteDistance.getLength()));
803         const sal_uInt16 nAngle10(
804             static_cast<sal_uInt16>(basegfx::fround(rFillHatchAttributes.getAngle() / F_PI1800)));
805         ::Hatch aVCLHatch(eHatchStyle, Color(rFillHatchAttributes.getColor()), nDistance, nAngle10);
806 
807         // draw hatch using VCL
808         mpOutputDevice->DrawHatch(::tools::PolyPolygon(::tools::Polygon(aHatchPolygon)), aVCLHatch);
809     }
810 }
811 
812 void VclPixelProcessor2D::processBackgroundColorPrimitive2D(
813     const primitive2d::BackgroundColorPrimitive2D& rPrimitive)
814 {
815     // #i98404# Handle directly, especially when AA is active
816     const AntialiasingFlags nOriginalAA(mpOutputDevice->GetAntialiasing());
817 
818     // switch AA off in all cases
819     mpOutputDevice->SetAntialiasing(mpOutputDevice->GetAntialiasing()
820                                     & ~AntialiasingFlags::EnableB2dDraw);
821 
822     // create color for fill
823     const basegfx::BColor aPolygonColor(
824         maBColorModifierStack.getModifiedColor(rPrimitive.getBColor()));
825     Color aFillColor(aPolygonColor);
826     aFillColor.SetTransparency(sal_uInt8((rPrimitive.getTransparency() * 255.0) + 0.5));
827     mpOutputDevice->SetFillColor(aFillColor);
828     mpOutputDevice->SetLineColor();
829 
830     // create rectangle for fill
831     const basegfx::B2DRange& aViewport(getViewInformation2D().getDiscreteViewport());
832     const ::tools::Rectangle aRectangle(static_cast<sal_Int32>(floor(aViewport.getMinX())),
833                                         static_cast<sal_Int32>(floor(aViewport.getMinY())),
834                                         static_cast<sal_Int32>(ceil(aViewport.getMaxX())),
835                                         static_cast<sal_Int32>(ceil(aViewport.getMaxY())));
836     mpOutputDevice->DrawRect(aRectangle);
837 
838     // restore AA setting
839     mpOutputDevice->SetAntialiasing(nOriginalAA);
840 }
841 
842 void VclPixelProcessor2D::processBorderLinePrimitive2D(
843     const drawinglayer::primitive2d::BorderLinePrimitive2D& rBorder)
844 {
845     // Process recursively, but switch off AntiAliasing for
846     // horizontal/vertical lines (*not* diagonal lines).
847     // Checked using AntialiasingFlags::PixelSnapHairline instead,
848     // but with AntiAliasing on the display really is too 'ghosty' when
849     // using fine stroking. Correct, but 'ghosty'.
850 
851     // It has shown that there are quite some problems here:
852     // - vcl OutDev renderer methods still use fallbacks to paint
853     //   multiple single lines between discrete sizes of < 3.5 what
854     //   looks bad and does not match
855     // - mix of filled Polygons and Lines is bad when AA switched off
856     // - Alignment of AA with non-AA may be bad in diverse different
857     //   renderers
858     //
859     // Due to these reasons I change the strategy: Always draw AAed, but
860     // allow fallback to test/check and if needed. The normal case
861     // where BorderLines will be system-dependently snapped to have at
862     // least a single discrete width per partial line (there may be up to
863     // three) works well nowadays due to most renderers moving the AA stuff
864     // by 0.5 pixels (discrete units) to match well with the non-AAed parts.
865     //
866     // Env-Switch for steering this, default is off.
867     // Enable by setting at all (and to something)
868     static const char* pSwitchOffAntiAliasingForHorVerBorderlines(
869         getenv("SAL_SWITCH_OFF_ANTIALIASING_FOR_HOR_VER_BORTDERLINES"));
870     static bool bSwitchOffAntiAliasingForHorVerBorderlines(
871         nullptr != pSwitchOffAntiAliasingForHorVerBorderlines);
872 
873     if (bSwitchOffAntiAliasingForHorVerBorderlines
874         && rBorder.isHorizontalOrVertical(getViewInformation2D()))
875     {
876         AntialiasingFlags nAntiAliasing = mpOutputDevice->GetAntialiasing();
877         mpOutputDevice->SetAntialiasing(nAntiAliasing & ~AntialiasingFlags::EnableB2dDraw);
878         process(rBorder);
879         mpOutputDevice->SetAntialiasing(nAntiAliasing);
880     }
881     else
882     {
883         process(rBorder);
884     }
885 }
886 
887 void VclPixelProcessor2D::processInvertPrimitive2D(const primitive2d::BasePrimitive2D& rCandidate)
888 {
889     // invert primitive (currently only used for HighContrast fallback for selection in SW and SC).
890     // (Not true, also used at least for the drawing of dragged column and row boundaries in SC.)
891     // Set OutDev to XOR and switch AA off (XOR does not work with AA)
892     mpOutputDevice->Push();
893     mpOutputDevice->SetRasterOp(RasterOp::Xor);
894     const AntialiasingFlags nAntiAliasing(mpOutputDevice->GetAntialiasing());
895     mpOutputDevice->SetAntialiasing(nAntiAliasing & ~AntialiasingFlags::EnableB2dDraw);
896 
897     // process content recursively
898     process(rCandidate);
899 
900     // restore OutDev
901     mpOutputDevice->Pop();
902     mpOutputDevice->SetAntialiasing(nAntiAliasing);
903 }
904 
905 void VclPixelProcessor2D::processMetaFilePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate)
906 {
907     // #i98289#
908     const bool bForceLineSnap(getOptionsDrawinglayer().IsAntiAliasing()
909                               && getOptionsDrawinglayer().IsSnapHorVerLinesToDiscrete());
910     const AntialiasingFlags nOldAntiAliase(mpOutputDevice->GetAntialiasing());
911 
912     if (bForceLineSnap)
913     {
914         mpOutputDevice->SetAntialiasing(nOldAntiAliase | AntialiasingFlags::PixelSnapHairline);
915     }
916 
917     process(rCandidate);
918 
919     if (bForceLineSnap)
920     {
921         mpOutputDevice->SetAntialiasing(nOldAntiAliase);
922     }
923 }
924 
925 namespace
926 {
927 /* Returns 8-bit alpha mask created from passed mask.
928 
929    Negative fErodeDilateRadius values mean erode, positive - dilate.
930    nTransparency defines minimal transparency level.
931 */
932 AlphaMask ProcessAndBlurAlphaMask(const Bitmap& rMask, double fErodeDilateRadius,
933                                   double fBlurRadius, sal_uInt8 nTransparency)
934 {
935     // Only completely white pixels on the initial mask must be considered for transparency. Any
936     // other color must be treated as black. This creates 1-bit B&W bitmap.
937     BitmapEx mask(rMask.CreateMask(COL_WHITE));
938 
939     // Scaling down increases performance without noticeable quality loss. Additionally,
940     // current blur implementation can only handle blur radius between 2 and 254.
941     Size aSize = mask.GetSizePixel();
942     double fScale = 1.0;
943     while (fBlurRadius > 254 || aSize.Height() > 1000 || aSize.Width() > 1000)
944     {
945         fScale /= 2;
946         fBlurRadius /= 2;
947         fErodeDilateRadius /= 2;
948         aSize.setHeight(aSize.Height() / 2);
949         aSize.setWidth(aSize.Width() / 2);
950     }
951 
952     // BmpScaleFlag::Fast is important for following color replacement
953     mask.Scale(fScale, fScale, BmpScaleFlag::Fast);
954 
955     if (fErodeDilateRadius > 0)
956         BitmapFilter::Filter(mask, BitmapDilateFilter(fErodeDilateRadius));
957     else if (fErodeDilateRadius < 0)
958         BitmapFilter::Filter(mask, BitmapErodeFilter(-fErodeDilateRadius, 0xFF));
959 
960     if (nTransparency)
961     {
962         const Color aTransparency(nTransparency, nTransparency, nTransparency);
963         mask.Replace(COL_BLACK, aTransparency);
964     }
965 
966     // We need 8-bit grey mask for blurring
967     mask.Convert(BmpConversion::N8BitGreys);
968 
969     // calculate blurry effect
970     BitmapFilter::Filter(mask, BitmapFilterStackBlur(fBlurRadius));
971 
972     mask.Scale(rMask.GetSizePixel());
973 
974     return AlphaMask(mask.GetBitmap());
975 }
976 }
977 
978 void VclPixelProcessor2D::processGlowPrimitive2D(const primitive2d::GlowPrimitive2D& rCandidate)
979 {
980     basegfx::B2DRange aRange(rCandidate.getB2DRange(getViewInformation2D()));
981     aRange.transform(maCurrentTransformation);
982     basegfx::B2DVector aGlowRadiusVector(rCandidate.getGlowRadius(), 0);
983     // Calculate the pixel size of glow radius in current transformation
984     aGlowRadiusVector *= maCurrentTransformation;
985     // Glow radius is the size of the halo from each side of the object. The halo is the
986     // border of glow color that fades from glow transparency level to fully transparent
987     // When blurring a sharp boundary (our case), it gets 50% of original intensity, and
988     // fades to both sides by the blur radius; thus blur radius is half of glow radius.
989     const double fBlurRadius = aGlowRadiusVector.getLength() / 2;
990     // Consider glow transparency (initial transparency near the object edge)
991     const sal_uInt8 nTransparency = rCandidate.getGlowColor().GetTransparency();
992 
993     impBufferDevice aBufferDevice(*mpOutputDevice, aRange, true);
994     if (aBufferDevice.isVisible())
995     {
996         // remember last OutDev and set to content
997         OutputDevice* pLastOutputDevice = mpOutputDevice;
998         mpOutputDevice = &aBufferDevice.getContent();
999         // We don't need antialiased mask here, which would only make effect thicker
1000         const auto aPrevAA = mpOutputDevice->GetAntialiasing();
1001         mpOutputDevice->SetAntialiasing(AntialiasingFlags::NONE);
1002         mpOutputDevice->Erase();
1003         process(rCandidate);
1004         const tools::Rectangle aRect(static_cast<long>(std::floor(aRange.getMinX())),
1005                                      static_cast<long>(std::floor(aRange.getMinY())),
1006                                      static_cast<long>(std::ceil(aRange.getMaxX())),
1007                                      static_cast<long>(std::ceil(aRange.getMaxY())));
1008         BitmapEx bmpEx = mpOutputDevice->GetBitmapEx(aRect.TopLeft(), aRect.GetSize());
1009         mpOutputDevice->SetAntialiasing(aPrevAA);
1010 
1011         AlphaMask mask
1012             = ProcessAndBlurAlphaMask(bmpEx.GetAlpha(), fBlurRadius, fBlurRadius, nTransparency);
1013 
1014         // The end result is the bitmap filled with glow color and blurred 8-bit alpha mask
1015         const basegfx::BColor aGlowColor(
1016             maBColorModifierStack.getModifiedColor(rCandidate.getGlowColor().getBColor()));
1017         Bitmap bmp = bmpEx.GetBitmap();
1018         bmp.Erase(Color(aGlowColor));
1019         BitmapEx result(bmp, mask);
1020 
1021         // back to old OutDev
1022         mpOutputDevice = pLastOutputDevice;
1023         mpOutputDevice->DrawBitmapEx(aRect.TopLeft(), result);
1024     }
1025     else
1026         SAL_WARN("drawinglayer", "Temporary buffered virtual device is not visible");
1027 }
1028 
1029 void VclPixelProcessor2D::processSoftEdgePrimitive2D(
1030     const primitive2d::SoftEdgePrimitive2D& rCandidate)
1031 {
1032     // TODO: don't limit the object at view range. This is needed to not blur objects at window
1033     // borders, where they don't end. Ideally, process the full object once at maximal reasonable
1034     // resolution, and store the resulting alpha mask in primitive's cache; then reuse it later,
1035     // applying the transform.
1036     basegfx::B2DRange aRange(rCandidate.getB2DRange(getViewInformation2D()));
1037     aRange.transform(maCurrentTransformation);
1038     basegfx::B2DVector aRadiusVector(rCandidate.getRadius(), 0);
1039     // Calculate the pixel size of soft edge radius in current transformation
1040     aRadiusVector *= maCurrentTransformation;
1041     // Blur radius is equal to soft edge radius
1042     const double fBlurRadius = aRadiusVector.getLength();
1043 
1044     impBufferDevice aBufferDevice(*mpOutputDevice, aRange, true);
1045     if (aBufferDevice.isVisible())
1046     {
1047         // remember last OutDev and set to content
1048         OutputDevice* pLastOutputDevice = mpOutputDevice;
1049         mpOutputDevice = &aBufferDevice.getContent();
1050         mpOutputDevice->Erase();
1051         // Since the effect converts all children to bitmap, we can't disable antialiasing here,
1052         // because it would result in poor quality in areas not affected by the effect
1053         process(rCandidate);
1054 
1055         const tools::Rectangle aRect(static_cast<long>(std::floor(aRange.getMinX())),
1056                                      static_cast<long>(std::floor(aRange.getMinY())),
1057                                      static_cast<long>(std::ceil(aRange.getMaxX())),
1058                                      static_cast<long>(std::ceil(aRange.getMaxY())));
1059         BitmapEx bitmap = mpOutputDevice->GetBitmapEx(aRect.TopLeft(), aRect.GetSize());
1060 
1061         AlphaMask aMask = bitmap.GetAlpha();
1062         AlphaMask blurMask = ProcessAndBlurAlphaMask(aMask, -fBlurRadius, fBlurRadius, 0);
1063 
1064         aMask.BlendWith(blurMask);
1065 
1066         // The end result is the original bitmap with blurred 8-bit alpha mask
1067         BitmapEx result(bitmap.GetBitmap(), aMask);
1068 
1069         // back to old OutDev
1070         mpOutputDevice = pLastOutputDevice;
1071         mpOutputDevice->DrawBitmapEx(aRect.TopLeft(), result);
1072     }
1073     else
1074         SAL_WARN("drawinglayer", "Temporary buffered virtual device is not visible");
1075 }
1076 
1077 void VclPixelProcessor2D::processShadowPrimitive2D(const primitive2d::ShadowPrimitive2D& rCandidate)
1078 {
1079     if (rCandidate.getShadowBlur() == 0)
1080     {
1081         process(rCandidate);
1082         return;
1083     }
1084 
1085     basegfx::B2DRange aRange(rCandidate.getB2DRange(getViewInformation2D()));
1086     aRange.transform(maCurrentTransformation);
1087     basegfx::B2DVector aBlurRadiusVector(rCandidate.getShadowBlur(), 0);
1088     aBlurRadiusVector *= maCurrentTransformation;
1089     const double fBlurRadius = aBlurRadiusVector.getLength();
1090 
1091     impBufferDevice aBufferDevice(*mpOutputDevice, aRange, true);
1092     if (aBufferDevice.isVisible())
1093     {
1094         OutputDevice* pLastOutputDevice = mpOutputDevice;
1095         mpOutputDevice = &aBufferDevice.getContent();
1096         mpOutputDevice->Erase();
1097 
1098         process(rCandidate);
1099 
1100         const tools::Rectangle aRect(static_cast<long>(std::floor(aRange.getMinX())),
1101                                      static_cast<long>(std::floor(aRange.getMinY())),
1102                                      static_cast<long>(std::ceil(aRange.getMaxX())),
1103                                      static_cast<long>(std::ceil(aRange.getMaxY())));
1104 
1105         BitmapEx bitmapEx = mpOutputDevice->GetBitmapEx(aRect.TopLeft(), aRect.GetSize());
1106 
1107         AlphaMask mask = ProcessAndBlurAlphaMask(bitmapEx.GetAlpha(), 0, fBlurRadius, 0);
1108 
1109         const basegfx::BColor aShadowColor(
1110             maBColorModifierStack.getModifiedColor(rCandidate.getShadowColor()));
1111 
1112         Bitmap bitmap = bitmapEx.GetBitmap();
1113         bitmap.Erase(Color(aShadowColor));
1114         BitmapEx result(bitmap, mask);
1115 
1116         mpOutputDevice = pLastOutputDevice;
1117         mpOutputDevice->DrawBitmapEx(aRect.TopLeft(), result);
1118     }
1119     else
1120         SAL_WARN("drawinglayer", "Temporary buffered virtual device is not visible");
1121 }
1122 
1123 } // end of namespace
1124 
1125 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
1126