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/config.h>
28 #include <drawinglayer/processor2d/d2dpixelprocessor2d.hxx>
29 #include <drawinglayer/processor2d/SDPRProcessor2dTools.hxx>
30 #include <sal/log.hxx>
31 #include <vcl/outdev.hxx>
32 #include <basegfx/polygon/b2dpolygontools.hxx>
33 #include <basegfx/polygon/b2dpolypolygontools.hxx>
34 #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx>
35 #include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx>
36 #include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx>
37 #include <drawinglayer/primitive2d/bitmapprimitive2d.hxx>
38 #include <drawinglayer/primitive2d/fillgraphicprimitive2d.hxx>
39 #include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx>
40 #include <drawinglayer/primitive2d/backgroundcolorprimitive2d.hxx>
41 #include <drawinglayer/primitive2d/baseprimitive2d.hxx>
42 #include <drawinglayer/primitive2d/markerarrayprimitive2d.hxx>
43 #include <drawinglayer/primitive2d/maskprimitive2d.hxx>
44 #include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx>
45 #include <drawinglayer/primitive2d/pointarrayprimitive2d.hxx>
46 #include <drawinglayer/primitive2d/PolygonStrokePrimitive2D.hxx>
47 #include <drawinglayer/primitive2d/Tools.hxx>
48 #include <drawinglayer/primitive2d/transformprimitive2d.hxx>
49 #include <drawinglayer/primitive2d/transparenceprimitive2d.hxx>
50 #include <drawinglayer/primitive2d/invertprimitive2d.hxx>
51 #include <drawinglayer/primitive2d/fillgradientprimitive2d.hxx>
52 #include <drawinglayer/converters.hxx>
53 #include <basegfx/curve/b2dcubicbezier.hxx>
54 #include <basegfx/matrix/b2dhommatrixtools.hxx>
55 #include <basegfx/utils/systemdependentdata.hxx>
56 #include <vcl/BitmapReadAccess.hxx>
57 #include <vcl/svapp.hxx>
59 using namespace com::sun::star
;
63 class ID2D1GlobalFactoryProvider
65 sal::systools::COMReference
<ID2D1Factory
> mpD2DFactory
;
68 ID2D1GlobalFactoryProvider()
69 : mpD2DFactory(nullptr)
71 const HRESULT
hr(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED
,
72 __uuidof(ID2D1Factory
), nullptr,
73 reinterpret_cast<void**>(&mpD2DFactory
)));
79 sal::systools::COMReference
<ID2D1Factory
>& getID2D1Factory() { return mpD2DFactory
; }
82 ID2D1GlobalFactoryProvider aID2D1GlobalFactoryProvider
;
84 class ID2D1GlobalRenderTargetProvider
86 sal::systools::COMReference
<ID2D1DCRenderTarget
> mpID2D1DCRenderTarget
;
89 ID2D1GlobalRenderTargetProvider()
90 : mpID2D1DCRenderTarget()
94 sal::systools::COMReference
<ID2D1DCRenderTarget
>& getID2D1DCRenderTarget()
96 if (!mpID2D1DCRenderTarget
&& aID2D1GlobalFactoryProvider
.getID2D1Factory())
98 const D2D1_RENDER_TARGET_PROPERTIES
aRTProps(D2D1::RenderTargetProperties(
99 D2D1_RENDER_TARGET_TYPE_DEFAULT
,
100 D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM
,
101 D2D1_ALPHA_MODE_IGNORE
), //D2D1_ALPHA_MODE_PREMULTIPLIED),
102 0, 0, D2D1_RENDER_TARGET_USAGE_NONE
, D2D1_FEATURE_LEVEL_DEFAULT
));
104 const HRESULT
hr(aID2D1GlobalFactoryProvider
.getID2D1Factory()->CreateDCRenderTarget(
105 &aRTProps
, &mpID2D1DCRenderTarget
));
107 // interestingly this ID2D1DCRenderTarget already works and can hold
108 // created ID2D1Bitmap(s) in RenderTarget-specific form, *without*
109 // any call to "BindDC", thus *without* the need of a real HDC - nice :-)
110 // When that would be needed, Application::GetDefaultDevice() would need
111 // to have a HDC that is valid during LO's lifetime.
114 mpID2D1DCRenderTarget
.clear();
117 return mpID2D1DCRenderTarget
;
121 ID2D1GlobalRenderTargetProvider aID2D1GlobalRenderTargetProvider
;
123 class SystemDependentData_ID2D1PathGeometry
: public basegfx::SystemDependentData
126 sal::systools::COMReference
<ID2D1PathGeometry
> mpID2D1PathGeometry
;
129 SystemDependentData_ID2D1PathGeometry(
130 sal::systools::COMReference
<ID2D1PathGeometry
>& rID2D1PathGeometry
)
131 : basegfx::SystemDependentData(Application::GetSystemDependentDataManager())
132 , mpID2D1PathGeometry(rID2D1PathGeometry
)
136 const sal::systools::COMReference
<ID2D1PathGeometry
>& getID2D1PathGeometry() const
138 return mpID2D1PathGeometry
;
140 virtual sal_Int64
estimateUsageInBytes() const override
;
143 sal_Int64
SystemDependentData_ID2D1PathGeometry::estimateUsageInBytes() const
145 sal_Int64
aRetval(0);
147 if (getID2D1PathGeometry())
150 const HRESULT
hr(getID2D1PathGeometry()->GetSegmentCount(&nCount
));
154 // without completely receiving and tracing the GeometrySink
155 // do a rough estimation - each segment is 2D, so has two doubles.
156 // Some are beziers, so add some guessed buffer for two additional
158 aRetval
= static_cast<sal_Int64
>(nCount
) * (6 * sizeof(double));
165 basegfx::B2DPoint
impPixelSnap(const basegfx::B2DPolygon
& rPolygon
,
166 const drawinglayer::geometry::ViewInformation2D
& rViewInformation
,
169 const sal_uInt32
nCount(rPolygon
.count());
172 const basegfx::B2ITuple
aPrevTuple(
173 basegfx::fround(rViewInformation
.getObjectToViewTransformation()
174 * rPolygon
.getB2DPoint((nIndex
+ nCount
- 1) % nCount
)));
175 const basegfx::B2DPoint
aCurrPoint(rViewInformation
.getObjectToViewTransformation()
176 * rPolygon
.getB2DPoint(nIndex
));
177 const basegfx::B2ITuple
aCurrTuple(basegfx::fround(aCurrPoint
));
178 const basegfx::B2ITuple
aNextTuple(
179 basegfx::fround(rViewInformation
.getObjectToViewTransformation()
180 * rPolygon
.getB2DPoint((nIndex
+ 1) % nCount
)));
183 const bool bPrevVertical(aPrevTuple
.getX() == aCurrTuple
.getX());
184 const bool bNextVertical(aNextTuple
.getX() == aCurrTuple
.getX());
185 const bool bPrevHorizontal(aPrevTuple
.getY() == aCurrTuple
.getY());
186 const bool bNextHorizontal(aNextTuple
.getY() == aCurrTuple
.getY());
187 const bool bSnapX(bPrevVertical
|| bNextVertical
);
188 const bool bSnapY(bPrevHorizontal
|| bNextHorizontal
);
190 if (bSnapX
|| bSnapY
)
192 basegfx::B2DPoint
aSnappedPoint(bSnapX
? aCurrTuple
.getX() : aCurrPoint
.getX(),
193 bSnapY
? aCurrTuple
.getY() : aCurrPoint
.getY());
195 aSnappedPoint
*= rViewInformation
.getInverseObjectToViewTransformation();
197 return aSnappedPoint
;
200 return rPolygon
.getB2DPoint(nIndex
);
203 void addB2DPolygonToPathGeometry(sal::systools::COMReference
<ID2D1GeometrySink
>& rSink
,
204 const basegfx::B2DPolygon
& rPolygon
,
205 const drawinglayer::geometry::ViewInformation2D
* pViewInformation
)
207 const sal_uInt32
nPointCount(rPolygon
.count());
208 const sal_uInt32
nEdgeCount(rPolygon
.isClosed() ? nPointCount
: nPointCount
- 1);
209 basegfx::B2DCubicBezier aEdge
;
211 for (sal_uInt32
a(0); a
< nEdgeCount
; a
++)
213 rPolygon
.getBezierSegment(a
, aEdge
);
215 const basegfx::B2DPoint
aEndPoint(
216 nullptr == pViewInformation
217 ? aEdge
.getEndPoint()
218 : impPixelSnap(rPolygon
, *pViewInformation
, (a
+ 1) % nPointCount
));
220 if (aEdge
.isBezier())
223 D2D1::BezierSegment(D2D1::Point2F(aEdge
.getControlPointA().getX(),
224 aEdge
.getControlPointA().getY()), //C1
225 D2D1::Point2F(aEdge
.getControlPointB().getX(),
226 aEdge
.getControlPointB().getY()), //c2
227 D2D1::Point2F(aEndPoint
.getX(), aEndPoint
.getY()))); //end
231 rSink
->AddLine(D2D1::Point2F(aEndPoint
.getX(), aEndPoint
.getY()));
236 std::shared_ptr
<SystemDependentData_ID2D1PathGeometry
>
237 getOrCreatePathGeometry(const basegfx::B2DPolygon
& rPolygon
,
238 const drawinglayer::geometry::ViewInformation2D
& rViewInformation
)
240 // try to access buffered data
241 std::shared_ptr
<SystemDependentData_ID2D1PathGeometry
> pSystemDependentData_ID2D1PathGeometry(
242 rPolygon
.getSystemDependentData
<SystemDependentData_ID2D1PathGeometry
>());
244 if (pSystemDependentData_ID2D1PathGeometry
)
246 if (rViewInformation
.getPixelSnapHairline())
248 // do not buffer when PixelSnap is active
249 pSystemDependentData_ID2D1PathGeometry
.reset();
253 // use and return buffered data
254 return pSystemDependentData_ID2D1PathGeometry
;
258 sal::systools::COMReference
<ID2D1PathGeometry
> pID2D1PathGeometry
;
260 aID2D1GlobalFactoryProvider
.getID2D1Factory()->CreatePathGeometry(&pID2D1PathGeometry
));
261 const sal_uInt32
nPointCount(rPolygon
.count());
263 if (SUCCEEDED(hr
) && nPointCount
)
265 sal::systools::COMReference
<ID2D1GeometrySink
> pSink
;
266 hr
= pID2D1PathGeometry
->Open(&pSink
);
268 if (SUCCEEDED(hr
) && pSink
)
270 const basegfx::B2DPoint
aStart(rViewInformation
.getPixelSnapHairline()
271 ? rPolygon
.getB2DPoint(0)
272 : impPixelSnap(rPolygon
, rViewInformation
, 0));
274 pSink
->BeginFigure(D2D1::Point2F(aStart
.getX(), aStart
.getY()),
275 D2D1_FIGURE_BEGIN_HOLLOW
);
276 addB2DPolygonToPathGeometry(pSink
, rPolygon
, &rViewInformation
);
277 pSink
->EndFigure(rPolygon
.isClosed() ? D2D1_FIGURE_END_CLOSED
: D2D1_FIGURE_END_OPEN
);
282 // add to buffering mechanism
283 if (pID2D1PathGeometry
)
285 if (rViewInformation
.getPixelSnapHairline() || nPointCount
<= 4)
287 // do not buffer when PixelSnap is active or small polygon
288 return std::make_shared
<SystemDependentData_ID2D1PathGeometry
>(pID2D1PathGeometry
);
292 return rPolygon
.addOrReplaceSystemDependentData
<SystemDependentData_ID2D1PathGeometry
>(
297 return std::shared_ptr
<SystemDependentData_ID2D1PathGeometry
>();
300 std::shared_ptr
<SystemDependentData_ID2D1PathGeometry
>
301 getOrCreateFillGeometry(const basegfx::B2DPolyPolygon
& rPolyPolygon
)
303 // try to access buffered data
304 std::shared_ptr
<SystemDependentData_ID2D1PathGeometry
> pSystemDependentData_ID2D1PathGeometry(
305 rPolyPolygon
.getSystemDependentData
<SystemDependentData_ID2D1PathGeometry
>());
307 if (pSystemDependentData_ID2D1PathGeometry
)
309 // use and return buffered data
310 return pSystemDependentData_ID2D1PathGeometry
;
313 sal::systools::COMReference
<ID2D1PathGeometry
> pID2D1PathGeometry
;
315 aID2D1GlobalFactoryProvider
.getID2D1Factory()->CreatePathGeometry(&pID2D1PathGeometry
));
316 const sal_uInt32
nCount(rPolyPolygon
.count());
318 if (SUCCEEDED(hr
) && nCount
)
320 sal::systools::COMReference
<ID2D1GeometrySink
> pSink
;
321 hr
= pID2D1PathGeometry
->Open(&pSink
);
323 if (SUCCEEDED(hr
) && pSink
)
325 for (sal_uInt32
a(0); a
< nCount
; a
++)
327 const basegfx::B2DPolygon
& rPolygon(rPolyPolygon
.getB2DPolygon(a
));
328 const sal_uInt32
nPointCount(rPolygon
.count());
332 const basegfx::B2DPoint
aStart(rPolygon
.getB2DPoint(0));
334 pSink
->BeginFigure(D2D1::Point2F(aStart
.getX(), aStart
.getY()),
335 D2D1_FIGURE_BEGIN_FILLED
);
336 addB2DPolygonToPathGeometry(pSink
, rPolygon
, nullptr);
337 pSink
->EndFigure(D2D1_FIGURE_END_CLOSED
);
345 // add to buffering mechanism
346 if (pID2D1PathGeometry
)
348 return rPolyPolygon
.addOrReplaceSystemDependentData
<SystemDependentData_ID2D1PathGeometry
>(
352 return std::shared_ptr
<SystemDependentData_ID2D1PathGeometry
>();
355 class SystemDependentData_ID2D1Bitmap
: public basegfx::SystemDependentData
358 sal::systools::COMReference
<ID2D1Bitmap
> mpD2DBitmap
;
359 const std::shared_ptr
<SalBitmap
> maAssociatedAlpha
;
362 SystemDependentData_ID2D1Bitmap(sal::systools::COMReference
<ID2D1Bitmap
>& rD2DBitmap
,
363 const std::shared_ptr
<SalBitmap
>& rAssociatedAlpha
)
364 : basegfx::SystemDependentData(Application::GetSystemDependentDataManager())
365 , mpD2DBitmap(rD2DBitmap
)
366 , maAssociatedAlpha(rAssociatedAlpha
)
370 const sal::systools::COMReference
<ID2D1Bitmap
>& getID2D1Bitmap() const { return mpD2DBitmap
; }
371 const std::shared_ptr
<SalBitmap
>& getAssociatedAlpha() const { return maAssociatedAlpha
; }
373 virtual sal_Int64
estimateUsageInBytes() const override
;
376 sal_Int64
SystemDependentData_ID2D1Bitmap::estimateUsageInBytes() const
378 sal_Int64
aRetval(0);
380 if (getID2D1Bitmap())
382 // use factor 4 for RGBA_8 as estimation
383 const D2D1_SIZE_U
aSizePixel(getID2D1Bitmap()->GetPixelSize());
384 aRetval
= static_cast<sal_Int64
>(aSizePixel
.width
)
385 * static_cast<sal_Int64
>(aSizePixel
.height
) * 4;
391 sal::systools::COMReference
<ID2D1Bitmap
> createB2DBitmap(const BitmapEx
& rBitmapEx
)
393 const Size
& rSizePixel(rBitmapEx
.GetSizePixel());
394 const bool bAlpha(rBitmapEx
.IsAlpha());
395 const sal_uInt32
nPixelCount(rSizePixel
.Width() * rSizePixel
.Height());
396 std::unique_ptr
<sal_uInt32
[]> aData(new sal_uInt32
[nPixelCount
]);
397 sal_uInt32
* pTarget
= aData
.get();
401 Bitmap
aSrcAlpha(rBitmapEx
.GetAlphaMask().GetBitmap());
402 Bitmap::ScopedReadAccess
pReadAccess(const_cast<Bitmap
&>(rBitmapEx
.GetBitmap()));
403 Bitmap::ScopedReadAccess
pAlphaReadAccess(aSrcAlpha
.AcquireReadAccess(), aSrcAlpha
);
404 const tools::Long
nHeight(pReadAccess
->Height());
405 const tools::Long
nWidth(pReadAccess
->Width());
407 for (tools::Long y
= 0; y
< nHeight
; ++y
)
409 for (tools::Long x
= 0; x
< nWidth
; ++x
)
411 const BitmapColor
aColor(pReadAccess
->GetColor(y
, x
));
412 const BitmapColor
aAlpha(pAlphaReadAccess
->GetColor(y
, x
));
413 const sal_uInt16
nAlpha(255 - aAlpha
.GetRed());
415 *pTarget
++ = sal_uInt32(BitmapColor(
416 ColorAlpha
, sal_uInt8((sal_uInt16(aColor
.GetRed()) * nAlpha
) >> 8),
417 sal_uInt8((sal_uInt16(aColor
.GetGreen()) * nAlpha
) >> 8),
418 sal_uInt8((sal_uInt16(aColor
.GetBlue()) * nAlpha
) >> 8), aAlpha
.GetRed()));
424 Bitmap::ScopedReadAccess
pReadAccess(const_cast<Bitmap
&>(rBitmapEx
.GetBitmap()));
425 const tools::Long
nHeight(pReadAccess
->Height());
426 const tools::Long
nWidth(pReadAccess
->Width());
428 for (tools::Long y
= 0; y
< nHeight
; ++y
)
430 for (tools::Long x
= 0; x
< nWidth
; ++x
)
432 const BitmapColor
aColor(pReadAccess
->GetColor(y
, x
));
433 *pTarget
++ = sal_uInt32(aColor
);
438 // use GlobalRenderTarget to allow usage combined with
439 // the Direct2D CreateSharedBitmap-mechanism. This is needed
440 // since ID2D1Bitmap is a ID2D1RenderTarget-dependent resource
441 // and thus - in principle - would have to be re-created for
442 // *each* new ID2D1RenderTarget, that means for *each* new
443 // target HDC, resp. OutputDevice
444 sal::systools::COMReference
<ID2D1Bitmap
> pID2D1Bitmap
;
446 if (aID2D1GlobalRenderTargetProvider
.getID2D1DCRenderTarget())
448 const HRESULT
hr(aID2D1GlobalRenderTargetProvider
.getID2D1DCRenderTarget()->CreateBitmap(
449 D2D1::SizeU(rSizePixel
.Width(), rSizePixel
.Height()), &aData
[0],
450 rSizePixel
.Width() * sizeof(sal_uInt32
),
451 D2D1::BitmapProperties(
452 D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM
, // DXGI_FORMAT
453 bAlpha
? D2D1_ALPHA_MODE_PREMULTIPLIED
454 : D2D1_ALPHA_MODE_IGNORE
)), // D2D1_ALPHA_MODE
458 pID2D1Bitmap
.clear();
464 sal::systools::COMReference
<ID2D1Bitmap
>
465 getOrCreateB2DBitmap(sal::systools::COMReference
<ID2D1RenderTarget
>& rRT
, const BitmapEx
& rBitmapEx
)
467 const basegfx::SystemDependentDataHolder
* pHolder(
468 rBitmapEx
.GetBitmap().accessSystemDependentDataHolder());
469 std::shared_ptr
<SystemDependentData_ID2D1Bitmap
> pSystemDependentData_ID2D1Bitmap
;
471 if (nullptr != pHolder
)
473 // try to access SystemDependentDataHolder and buffered data
474 pSystemDependentData_ID2D1Bitmap
475 = std::static_pointer_cast
<SystemDependentData_ID2D1Bitmap
>(
476 pHolder
->getSystemDependentData(
477 typeid(SystemDependentData_ID2D1Bitmap
).hash_code()));
479 // check data validity for associated Alpha
480 if (pSystemDependentData_ID2D1Bitmap
481 && pSystemDependentData_ID2D1Bitmap
->getAssociatedAlpha()
482 != rBitmapEx
.GetAlphaMask().GetBitmap().ImplGetSalBitmap())
484 // AssociatedAlpha did change, data invalid
485 pSystemDependentData_ID2D1Bitmap
.reset();
489 if (!pSystemDependentData_ID2D1Bitmap
)
491 // have to create newly
492 sal::systools::COMReference
<ID2D1Bitmap
> pID2D1Bitmap(createB2DBitmap(rBitmapEx
));
496 // creation worked, create SystemDependentData_ID2D1Bitmap
497 pSystemDependentData_ID2D1Bitmap
= std::make_shared
<SystemDependentData_ID2D1Bitmap
>(
498 pID2D1Bitmap
, rBitmapEx
.GetAlphaMask().GetBitmap().ImplGetSalBitmap());
500 // only add if feasible
501 if (nullptr != pHolder
502 && pSystemDependentData_ID2D1Bitmap
->calculateCombinedHoldCyclesInSeconds() > 0)
504 basegfx::SystemDependentData_SharedPtr
r2(pSystemDependentData_ID2D1Bitmap
);
505 const_cast<basegfx::SystemDependentDataHolder
*>(pHolder
)
506 ->addOrReplaceSystemDependentData(r2
);
511 sal::systools::COMReference
<ID2D1Bitmap
> pWrappedD2DBitmap
;
513 if (pSystemDependentData_ID2D1Bitmap
)
515 // embed to CreateSharedBitmap, that makes it usable on
516 // the specified RenderTarget
517 const HRESULT
hr(rRT
->CreateSharedBitmap(
518 __uuidof(ID2D1Bitmap
),
519 static_cast<void*>(pSystemDependentData_ID2D1Bitmap
->getID2D1Bitmap()), nullptr,
520 &pWrappedD2DBitmap
));
523 pWrappedD2DBitmap
.clear();
526 return pWrappedD2DBitmap
;
529 // This is a simple local derivation of D2DPixelProcessor2D to be used
530 // when sub-content needs to be rendered to pixels. Hand over the adapted
531 // ViewInformation2D, a pixel size and the parent RenderTarget. It will
532 // locally create and use a ID2D1BitmapRenderTarget to render the stuff
533 // (you need to call process() with the primitives to be painted of
534 // course). Then use the local helper getID2D1Bitmap() to access the
535 // ID2D1Bitmap which was the target of that operation.
536 class D2DBitmapPixelProcessor2D final
: public drawinglayer::processor2d::D2DPixelProcessor2D
538 // the local ID2D1BitmapRenderTarget
539 sal::systools::COMReference
<ID2D1BitmapRenderTarget
> mpBitmapRenderTarget
;
542 // helper class to create another instance of D2DPixelProcessor2D for
543 // creating helper-ID2D1Bitmap's for a given ID2D1RenderTarget
544 D2DBitmapPixelProcessor2D(const drawinglayer::geometry::ViewInformation2D
& rViewInformation
,
545 sal_uInt32 nWidth
, sal_uInt32 nHeight
,
546 const sal::systools::COMReference
<ID2D1RenderTarget
>& rParent
)
547 : drawinglayer::processor2d::D2DPixelProcessor2D(rViewInformation
)
548 , mpBitmapRenderTarget()
550 if (0 == nWidth
|| 0 == nHeight
)
552 // no width/height, done
558 // Allocate compatible RGBA render target
559 const D2D1_SIZE_U
aRenderTargetSizePixel(D2D1::SizeU(nWidth
, nHeight
));
560 const HRESULT
hr(rParent
->CreateCompatibleRenderTarget(
561 nullptr, &aRenderTargetSizePixel
, nullptr,
562 D2D1_COMPATIBLE_RENDER_TARGET_OPTIONS_NONE
, &mpBitmapRenderTarget
));
564 if (!SUCCEEDED(hr
) || nullptr == mpBitmapRenderTarget
)
566 // did not work, done
571 sal::systools::COMReference
<ID2D1RenderTarget
> pRT
;
572 mpBitmapRenderTarget
->QueryInterface(__uuidof(ID2D1RenderTarget
),
573 reinterpret_cast<void**>(&pRT
));
574 setRenderTarget(pRT
);
578 if (hasRenderTarget())
580 // set Viewort if none was given. We have a fixed pixel target, s we know the
581 // exact Viewport to work on
582 if (getViewInformation2D().getViewport().isEmpty())
584 drawinglayer::geometry::ViewInformation2D
aViewInformation(getViewInformation2D());
585 basegfx::B2DRange
aViewport(0.0, 0.0, nWidth
, nHeight
);
586 basegfx::B2DHomMatrix
aInvViewTransform(aViewInformation
.getViewTransformation());
588 aInvViewTransform
.invert();
589 aViewport
.transform(aInvViewTransform
);
590 aViewInformation
.setViewport(aViewport
);
591 updateViewInformation(aViewInformation
);
594 // clear as render preparation
595 getRenderTarget()->BeginDraw();
596 getRenderTarget()->Clear(D2D1::ColorF(0.0f
, 0.0f
, 0.0f
, 0.0f
));
597 getRenderTarget()->EndDraw();
601 sal::systools::COMReference
<ID2D1Bitmap
> getID2D1Bitmap() const
603 sal::systools::COMReference
<ID2D1Bitmap
> pResult
;
605 // access the resulting bitmap if exists
606 if (mpBitmapRenderTarget
)
608 mpBitmapRenderTarget
->GetBitmap(&pResult
);
615 bool createBitmapSubContent(sal::systools::COMReference
<ID2D1Bitmap
>& rResult
,
616 basegfx::B2DRange
& rDiscreteVisibleRange
,
617 const drawinglayer::primitive2d::Primitive2DContainer
& rContent
,
618 const drawinglayer::geometry::ViewInformation2D
& rViewInformation2D
,
619 const sal::systools::COMReference
<ID2D1RenderTarget
>& rRenderTarget
)
621 if (rContent
.empty() || !rRenderTarget
)
623 // no content or no render target, done
627 drawinglayer::processor2d::calculateDiscreteVisibleRange(
628 rDiscreteVisibleRange
, rContent
.getB2DRange(rViewInformation2D
), rViewInformation2D
);
630 if (rDiscreteVisibleRange
.isEmpty())
636 // Use a temporary second instance of a D2DBitmapPixelProcessor2D with adapted
637 // ViewInformation2D, it will create the needed ID2D1BitmapRenderTarget
638 // locally and Clear() it.
639 drawinglayer::geometry::ViewInformation2D
aAdaptedViewInformation2D(rViewInformation2D
);
640 const double fTargetWidth(ceil(rDiscreteVisibleRange
.getWidth()));
641 const double fTargetHeight(ceil(rDiscreteVisibleRange
.getHeight()));
644 // create adapted ViewTransform, needs to be offset in discrete coordinates,
645 // so multiply from left
646 basegfx::B2DHomMatrix
aAdapted(
647 basegfx::utils::createTranslateB2DHomMatrix(-rDiscreteVisibleRange
.getMinX(),
648 -rDiscreteVisibleRange
.getMinY())
649 * rViewInformation2D
.getViewTransformation());
650 aAdaptedViewInformation2D
.setViewTransformation(aAdapted
);
652 // reset Viewport (world coordinates), so the helper renderer will create it's
653 // own based on it's given internal discrete size
654 aAdaptedViewInformation2D
.setViewport(basegfx::B2DRange());
657 D2DBitmapPixelProcessor2D
aSubContentRenderer(aAdaptedViewInformation2D
, fTargetWidth
,
658 fTargetHeight
, rRenderTarget
);
660 if (!aSubContentRenderer
.valid())
662 // did not work, done
666 // render sub-content recursively
667 aSubContentRenderer
.process(rContent
);
669 // grab Bitmap & prepare results from RGBA content rendering
670 rResult
= aSubContentRenderer
.getID2D1Bitmap();
675 namespace drawinglayer::processor2d
677 D2DPixelProcessor2D::D2DPixelProcessor2D(const geometry::ViewInformation2D
& rViewInformation
)
678 : BaseProcessor2D(rViewInformation
)
679 , maBColorModifierStack()
681 , mnRecursionCounter(0)
686 D2DPixelProcessor2D::D2DPixelProcessor2D(const geometry::ViewInformation2D
& rViewInformation
,
688 : BaseProcessor2D(rViewInformation
)
689 , maBColorModifierStack()
691 , mnRecursionCounter(0)
694 sal::systools::COMReference
<ID2D1DCRenderTarget
> pDCRT
;
695 tools::Long
aOutWidth(0), aOutHeight(0);
699 aOutWidth
= GetDeviceCaps(aHdc
, HORZRES
);
700 aOutHeight
= GetDeviceCaps(aHdc
, VERTRES
);
703 if (aOutWidth
> 0 && aOutHeight
> 0 && aID2D1GlobalFactoryProvider
.getID2D1Factory())
705 const D2D1_RENDER_TARGET_PROPERTIES
aRTProps(D2D1::RenderTargetProperties(
706 D2D1_RENDER_TARGET_TYPE_DEFAULT
,
707 D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM
,
708 D2D1_ALPHA_MODE_IGNORE
), //D2D1_ALPHA_MODE_PREMULTIPLIED),
709 0, 0, D2D1_RENDER_TARGET_USAGE_NONE
, D2D1_FEATURE_LEVEL_DEFAULT
));
712 aID2D1GlobalFactoryProvider
.getID2D1Factory()->CreateDCRenderTarget(&aRTProps
, &pDCRT
));
721 { 0, 0, o3tl::narrowing
<LONG
>(aOutWidth
), o3tl::narrowing
<LONG
>(aOutHeight
) });
722 const HRESULT
hr(pDCRT
->BindDC(aHdc
, &rc
));
730 if (rViewInformation
.getUseAntiAliasing())
732 D2D1_ANTIALIAS_MODE eAAMode
= D2D1_ANTIALIAS_MODE_PER_PRIMITIVE
;
733 pDCRT
->SetAntialiasMode(eAAMode
);
737 D2D1_ANTIALIAS_MODE eAAMode
= D2D1_ANTIALIAS_MODE_ALIASED
;
738 pDCRT
->SetAntialiasMode(eAAMode
);
741 // since ID2D1DCRenderTarget depends on the transformation
742 // set at hdc, be careful and reset it to identity
750 SetWorldTransform(aHdc
, &aXForm
);
755 sal::systools::COMReference
<ID2D1RenderTarget
> pRT
;
756 pDCRT
->QueryInterface(__uuidof(ID2D1RenderTarget
), reinterpret_cast<void**>(&pRT
));
757 setRenderTarget(pRT
);
765 void D2DPixelProcessor2D::processPolygonHairlinePrimitive2D(
766 const primitive2d::PolygonHairlinePrimitive2D
& rPolygonHairlinePrimitive2D
)
768 const basegfx::B2DPolygon
& rPolygon(rPolygonHairlinePrimitive2D
.getB2DPolygon());
770 if (!rPolygon
.count())
777 std::shared_ptr
<SystemDependentData_ID2D1PathGeometry
> pSystemDependentData_ID2D1PathGeometry(
778 getOrCreatePathGeometry(rPolygon
, getViewInformation2D()));
780 if (pSystemDependentData_ID2D1PathGeometry
)
782 sal::systools::COMReference
<ID2D1TransformedGeometry
> pTransformedGeometry
;
783 const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0);
784 const basegfx::B2DHomMatrix
& rObjectToView(
785 getViewInformation2D().getObjectToViewTransformation());
786 HRESULT
hr(aID2D1GlobalFactoryProvider
.getID2D1Factory()->CreateTransformedGeometry(
787 pSystemDependentData_ID2D1PathGeometry
->getID2D1PathGeometry(),
788 D2D1::Matrix3x2F(rObjectToView
.a(), rObjectToView
.b(), rObjectToView
.c(),
789 rObjectToView
.d(), rObjectToView
.e() + fAAOffset
,
790 rObjectToView
.f() + fAAOffset
),
791 &pTransformedGeometry
));
793 if (SUCCEEDED(hr
) && pTransformedGeometry
)
795 const basegfx::BColor
aHairlineColor(
796 maBColorModifierStack
.getModifiedColor(rPolygonHairlinePrimitive2D
.getBColor()));
797 const D2D1::ColorF
aD2DColor(aHairlineColor
.getRed(), aHairlineColor
.getGreen(),
798 aHairlineColor
.getBlue());
799 sal::systools::COMReference
<ID2D1SolidColorBrush
> pColorBrush
;
800 hr
= getRenderTarget()->CreateSolidColorBrush(aD2DColor
, &pColorBrush
);
802 if (SUCCEEDED(hr
) && pColorBrush
)
804 getRenderTarget()->SetTransform(D2D1::Matrix3x2F::Identity());
805 // TODO: Unfortunately Direct2D paint of one pixel wide lines does not
806 // correctly and completely blend 100% over the background. Experimenting
807 // shows that a value around/slightly below 2.0 is needed which hints that
808 // alpha blending the half-shifted lines (see fAAOffset above) is involved.
809 // To get correct blending I try to use just wider hairlines for now. This
810 // may need to be improved - or balanced (trying sqrt(2) now...)
811 getRenderTarget()->DrawGeometry(pTransformedGeometry
, pColorBrush
, 1.44f
);
821 bool D2DPixelProcessor2D::drawPolyPolygonColorTransformed(
822 const basegfx::B2DHomMatrix
& rTansformation
, const basegfx::B2DPolyPolygon
& rPolyPolygon
,
823 const basegfx::BColor
& rColor
)
825 std::shared_ptr
<SystemDependentData_ID2D1PathGeometry
> pSystemDependentData_ID2D1PathGeometry(
826 getOrCreateFillGeometry(rPolyPolygon
));
828 if (pSystemDependentData_ID2D1PathGeometry
)
830 sal::systools::COMReference
<ID2D1TransformedGeometry
> pTransformedGeometry
;
831 const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0);
832 basegfx::B2DHomMatrix
aTansformation(getViewInformation2D().getObjectToViewTransformation()
834 HRESULT
hr(aID2D1GlobalFactoryProvider
.getID2D1Factory()->CreateTransformedGeometry(
835 pSystemDependentData_ID2D1PathGeometry
->getID2D1PathGeometry(),
836 D2D1::Matrix3x2F(aTansformation
.a(), aTansformation
.b(), aTansformation
.c(),
837 aTansformation
.d(), aTansformation
.e() + fAAOffset
,
838 aTansformation
.f() + fAAOffset
),
839 &pTransformedGeometry
));
841 if (SUCCEEDED(hr
) && pTransformedGeometry
)
843 const basegfx::BColor
aFillColor(maBColorModifierStack
.getModifiedColor(rColor
));
844 const D2D1::ColorF
aD2DColor(aFillColor
.getRed(), aFillColor
.getGreen(),
845 aFillColor
.getBlue());
847 sal::systools::COMReference
<ID2D1SolidColorBrush
> pColorBrush
;
848 hr
= getRenderTarget()->CreateSolidColorBrush(aD2DColor
, &pColorBrush
);
850 if (SUCCEEDED(hr
) && pColorBrush
)
852 getRenderTarget()->SetTransform(D2D1::Matrix3x2F::Identity());
853 getRenderTarget()->FillGeometry(pTransformedGeometry
, pColorBrush
);
862 void D2DPixelProcessor2D::processPolyPolygonColorPrimitive2D(
863 const primitive2d::PolyPolygonColorPrimitive2D
& rPolyPolygonColorPrimitive2D
)
865 const basegfx::B2DPolyPolygon
& rPolyPolygon(rPolyPolygonColorPrimitive2D
.getB2DPolyPolygon());
866 const sal_uInt32
nCount(rPolyPolygon
.count());
874 const bool bDone(drawPolyPolygonColorTransformed(basegfx::B2DHomMatrix(), rPolyPolygon
,
875 rPolyPolygonColorPrimitive2D
.getBColor()));
881 void D2DPixelProcessor2D::processBitmapPrimitive2D(
882 const primitive2d::BitmapPrimitive2D
& rBitmapCandidate
)
884 // check if graphic content is inside discrete local ViewPort
885 if (!getViewInformation2D().getDiscreteViewport().isEmpty())
887 // calculate logic object range, remember: the helper below will
888 // transform using getObjectToViewTransformation, so the bitmap-local
889 // transform would be missing
890 basegfx::B2DRange
aDiscreteVisibleRange(basegfx::B2DRange::getUnitB2DRange());
891 aDiscreteVisibleRange
.transform(rBitmapCandidate
.getTransform());
893 // calculate visible range
894 calculateDiscreteVisibleRange(aDiscreteVisibleRange
, aDiscreteVisibleRange
,
895 getViewInformation2D());
897 if (aDiscreteVisibleRange
.isEmpty())
904 BitmapEx
aBitmapEx(rBitmapCandidate
.getBitmap());
906 if (aBitmapEx
.IsEmpty() || aBitmapEx
.GetSizePixel().IsEmpty())
908 // no pixel data, done
912 if (maBColorModifierStack
.count())
914 // need to apply ColorModifier to Bitmap data
915 aBitmapEx
= aBitmapEx
.ModifyBitmapEx(maBColorModifierStack
);
917 if (aBitmapEx
.IsEmpty())
919 // color gets completely replaced, get it (any input works)
920 const basegfx::BColor
aModifiedColor(
921 maBColorModifierStack
.getModifiedColor(basegfx::BColor()));
923 // use unit geometry as fallback object geometry. Do *not*
924 // transform, the below used method will use the already
925 // correctly initialized local ViewInformation
926 basegfx::B2DPolygon
aPolygon(basegfx::utils::createUnitPolygon());
928 rtl::Reference
<primitive2d::PolyPolygonColorPrimitive2D
> aTemp(
929 new primitive2d::PolyPolygonColorPrimitive2D(basegfx::B2DPolyPolygon(aPolygon
),
932 // draw as Polygon, done
933 processPolyPolygonColorPrimitive2D(*aTemp
);
939 sal::systools::COMReference
<ID2D1Bitmap
> pD2DBitmap(
940 getOrCreateB2DBitmap(getRenderTarget(), aBitmapEx
));
944 const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0);
945 const basegfx::B2DHomMatrix
aLocalTransform(
946 getViewInformation2D().getObjectToViewTransformation()
947 * rBitmapCandidate
.getTransform());
948 getRenderTarget()->SetTransform(D2D1::Matrix3x2F(
949 aLocalTransform
.a(), aLocalTransform
.b(), aLocalTransform
.c(), aLocalTransform
.d(),
950 aLocalTransform
.e() + fAAOffset
, aLocalTransform
.f() + fAAOffset
));
952 // destinationRectangle is part of transformation above, so use UnitRange
953 getRenderTarget()->DrawBitmap(pD2DBitmap
, D2D1::RectF(0.0, 0.0, 1.0, 1.0));
961 sal::systools::COMReference
<ID2D1Bitmap
> D2DPixelProcessor2D::implCreateAlpha_Direct(
962 const primitive2d::TransparencePrimitive2D
& rTransCandidate
)
964 // Try if we can use ID2D1DeviceContext/d2d1_1 by querying for interface.
965 // Only then can we use ID2D1Effect/CLSID_D2D1LuminanceToAlpha and it makes
966 // sense to try to do it this way in this implementation
967 sal::systools::COMReference
<ID2D1DeviceContext
> pID2D1DeviceContext
;
968 getRenderTarget()->QueryInterface(__uuidof(ID2D1DeviceContext
),
969 reinterpret_cast<void**>(&pID2D1DeviceContext
));
970 sal::systools::COMReference
<ID2D1Bitmap
> pRetval
;
972 if (!pID2D1DeviceContext
)
974 // no, done - tell caller to use fallback by returning empty - we have
975 // not the preconditions for this
980 pID2D1DeviceContext
.clear();
981 basegfx::B2DRange aDiscreteVisibleRange
;
983 if (!createBitmapSubContent(pRetval
, aDiscreteVisibleRange
, rTransCandidate
.getTransparence(),
984 getViewInformation2D(), getRenderTarget())
987 // return of false means no display needed, return
991 // Now we need a target to render this to, using the ID2D1Effect tooling.
992 // We can directly apply the effect to an alpha-only 8bit target here,
993 // so create one (no RGBA needed for this).
994 // We need another render target: I tried to render pInBetweenResult
995 // to pContent again, but that does not work due to the bitmap
996 // fetched being probably only an internal reference to the
997 // ID2D1BitmapRenderTarget, thus it would draw onto itself -> chaos
998 sal::systools::COMReference
<ID2D1BitmapRenderTarget
> pContent
;
999 const D2D1_PIXEL_FORMAT
aAlphaFormat(
1000 D2D1::PixelFormat(DXGI_FORMAT_A8_UNORM
, D2D1_ALPHA_MODE_STRAIGHT
));
1001 const D2D1_SIZE_U
aRenderTargetSizePixel(D2D1::SizeU(ceil(aDiscreteVisibleRange
.getWidth()),
1002 ceil(aDiscreteVisibleRange
.getHeight())));
1003 const HRESULT
hr(getRenderTarget()->CreateCompatibleRenderTarget(
1004 nullptr, &aRenderTargetSizePixel
, &aAlphaFormat
, D2D1_COMPATIBLE_RENDER_TARGET_OPTIONS_NONE
,
1007 if (SUCCEEDED(hr
) && pContent
)
1009 // try to access ID2D1DeviceContext of that target, we need that *now*
1010 pContent
->QueryInterface(__uuidof(ID2D1DeviceContext
),
1011 reinterpret_cast<void**>(&pID2D1DeviceContext
));
1013 if (pID2D1DeviceContext
)
1015 // create the effect
1016 sal::systools::COMReference
<ID2D1Effect
> pLuminanceToAlpha
;
1017 pID2D1DeviceContext
->CreateEffect(CLSID_D2D1LuminanceToAlpha
, &pLuminanceToAlpha
);
1019 if (pLuminanceToAlpha
)
1021 // chain effect stuff together & paint it
1022 pLuminanceToAlpha
->SetInput(0, pRetval
);
1024 pID2D1DeviceContext
->BeginDraw();
1025 pID2D1DeviceContext
->Clear(D2D1::ColorF(0.0f
, 0.0f
, 0.0f
, 0.0f
));
1026 pID2D1DeviceContext
->DrawImage(pLuminanceToAlpha
);
1027 pID2D1DeviceContext
->EndDraw();
1030 pContent
->GetBitmap(&pRetval
);
1038 sal::systools::COMReference
<ID2D1Bitmap
> D2DPixelProcessor2D::implCreateAlpha_B2DBitmap(
1039 const primitive2d::TransparencePrimitive2D
& rTransCandidate
,
1040 const basegfx::B2DRange
& rVisibleRange
, D2D1_MATRIX_3X2_F
& rMaskScale
)
1042 // Use this fallback that will also use a pixel processor indirectly,
1043 // but allows to get the AlphaMask as vcl Bitmap using existing tooling
1044 const sal_uInt32
nDiscreteClippedWidth(ceil(rVisibleRange
.getWidth()));
1045 const sal_uInt32
nDiscreteClippedHeight(ceil(rVisibleRange
.getHeight()));
1046 const sal_uInt32
nMaximumQuadraticPixels(250000);
1048 // Embed content graphics to TransformPrimitive2D
1049 const basegfx::B2DHomMatrix
aAlphaEmbedding(
1050 basegfx::utils::createTranslateB2DHomMatrix(-rVisibleRange
.getMinX(),
1051 -rVisibleRange
.getMinY())
1052 * getViewInformation2D().getObjectToViewTransformation());
1053 const primitive2d::Primitive2DReference
xAlphaEmbedRef(new primitive2d::TransformPrimitive2D(
1055 drawinglayer::primitive2d::Primitive2DContainer(rTransCandidate
.getTransparence())));
1056 drawinglayer::primitive2d::Primitive2DContainer xEmbedSeq
{ xAlphaEmbedRef
};
1058 // use empty ViewInformation to have neutral transformation
1059 const geometry::ViewInformation2D aEmptyViewInformation2D
;
1061 // use new mode to create AlphaChannel (not just AlphaMask) for transparency channel
1062 const AlphaMask
aAlpha(::drawinglayer::createAlphaMask(
1063 std::move(xEmbedSeq
), aEmptyViewInformation2D
, nDiscreteClippedWidth
,
1064 nDiscreteClippedHeight
, nMaximumQuadraticPixels
, true));
1065 sal::systools::COMReference
<ID2D1Bitmap
> pRetval
;
1067 if (aAlpha
.IsEmpty())
1069 // if we have no content we are done
1073 // use alpha data to create the ID2D1Bitmap
1074 const Size
& rSizePixel(aAlpha
.GetSizePixel());
1075 const sal_uInt32
nPixelCount(rSizePixel
.Width() * rSizePixel
.Height());
1076 std::unique_ptr
<sal_uInt8
[]> aData(new sal_uInt8
[nPixelCount
]);
1077 sal_uInt8
* pTarget
= aData
.get();
1078 Bitmap
aSrcAlpha(aAlpha
.GetBitmap());
1079 Bitmap::ScopedReadAccess
pReadAccess(aSrcAlpha
.AcquireReadAccess(), aSrcAlpha
);
1080 const tools::Long
nHeight(pReadAccess
->Height());
1081 const tools::Long
nWidth(pReadAccess
->Width());
1083 for (tools::Long y
= 0; y
< nHeight
; ++y
)
1085 for (tools::Long x
= 0; x
< nWidth
; ++x
)
1087 const BitmapColor
aColor(pReadAccess
->GetColor(y
, x
));
1088 *pTarget
++ = aColor
.GetLuminance();
1092 const D2D1_BITMAP_PROPERTIES
aBmProps(D2D1::BitmapProperties(
1093 D2D1::PixelFormat(DXGI_FORMAT_A8_UNORM
, D2D1_ALPHA_MODE_PREMULTIPLIED
)));
1094 const HRESULT
hr(getRenderTarget()->CreateBitmap(
1095 D2D1::SizeU(rSizePixel
.Width(), rSizePixel
.Height()), &aData
[0],
1096 rSizePixel
.Width() * sizeof(sal_uInt8
), &aBmProps
, &pRetval
));
1098 if (!SUCCEEDED(hr
) || !pRetval
)
1100 // did not work, done
1104 // create needed adapted transformation for alpha brush.
1105 // We may have to take a corrective scaling into account when the
1106 // MaximumQuadraticPixel limit was used/triggered
1107 const Size
& rBitmapExSizePixel(aAlpha
.GetSizePixel());
1109 if (static_cast<sal_uInt32
>(rBitmapExSizePixel
.Width()) != nDiscreteClippedWidth
1110 || static_cast<sal_uInt32
>(rBitmapExSizePixel
.Height()) != nDiscreteClippedHeight
)
1112 // scale in X and Y should be the same (see fReduceFactor in createAlphaMask),
1113 // so adapt numerically to a single scale value, they are integer rounded values
1114 const double fScaleX(static_cast<double>(rBitmapExSizePixel
.Width())
1115 / static_cast<double>(nDiscreteClippedWidth
));
1116 const double fScaleY(static_cast<double>(rBitmapExSizePixel
.Height())
1117 / static_cast<double>(nDiscreteClippedHeight
));
1119 const double fScale(1.0 / ((fScaleX
+ fScaleY
) * 0.5));
1120 rMaskScale
= D2D1::Matrix3x2F::Scale(fScale
, fScale
);
1126 void D2DPixelProcessor2D::processTransparencePrimitive2D(
1127 const primitive2d::TransparencePrimitive2D
& rTransCandidate
)
1129 if (rTransCandidate
.getChildren().empty())
1135 if (rTransCandidate
.getTransparence().empty())
1137 // no mask (so nothing visible), done
1141 // calculate visible range, create only for that range
1142 basegfx::B2DRange aDiscreteVisibleRange
;
1143 calculateDiscreteVisibleRange(aDiscreteVisibleRange
,
1144 rTransCandidate
.getChildren().getB2DRange(getViewInformation2D()),
1145 getViewInformation2D());
1147 if (aDiscreteVisibleRange
.isEmpty())
1149 // not visible, done
1153 // try to create directly, this needs the current mpRT to be a ID2D1DeviceContext/d2d1_1
1154 // what is not guaranteed but usually works for more modern windows (after 7)
1155 sal::systools::COMReference
<ID2D1Bitmap
> pAlphaBitmap(implCreateAlpha_Direct(rTransCandidate
));
1156 D2D1_MATRIX_3X2_F
aMaskScale(D2D1::Matrix3x2F::Identity());
1160 // did not work, use more expensive fallback to existing tooling
1162 = implCreateAlpha_B2DBitmap(rTransCandidate
, aDiscreteVisibleRange
, aMaskScale
);
1167 // could not create alpha channel, error
1172 sal::systools::COMReference
<ID2D1Layer
> pLayer
;
1173 HRESULT
hr(getRenderTarget()->CreateLayer(nullptr, &pLayer
));
1176 if (SUCCEEDED(hr
) && pLayer
)
1178 sal::systools::COMReference
<ID2D1BitmapBrush
> pBitmapBrush
;
1179 hr
= getRenderTarget()->CreateBitmapBrush(pAlphaBitmap
, &pBitmapBrush
);
1181 if (SUCCEEDED(hr
) && pBitmapBrush
)
1183 // apply MaskScale to Brush, maybe used if implCreateAlpha_B2DBitmap was needed
1184 pBitmapBrush
->SetTransform(aMaskScale
);
1186 // need to set transform offset for Layer initialization, we work
1187 // in discrete device coordinates
1188 getRenderTarget()->SetTransform(D2D1::Matrix3x2F::Translation(
1189 floor(aDiscreteVisibleRange
.getMinX()), floor(aDiscreteVisibleRange
.getMinY())));
1191 getRenderTarget()->PushLayer(D2D1::LayerParameters(D2D1::InfiniteRect(), nullptr,
1192 D2D1_ANTIALIAS_MODE_PER_PRIMITIVE
,
1193 D2D1::Matrix3x2F::Identity(), 1.0,
1197 // ... but need to reset to paint content unchanged
1198 getRenderTarget()->SetTransform(D2D1::Matrix3x2F::Identity());
1200 // draw content recursively
1201 process(rTransCandidate
.getChildren());
1203 getRenderTarget()->PopLayer();
1212 void D2DPixelProcessor2D::processUnifiedTransparencePrimitive2D(
1213 const primitive2d::UnifiedTransparencePrimitive2D
& rTransCandidate
)
1215 if (rTransCandidate
.getChildren().empty())
1221 if (0.0 == rTransCandidate
.getTransparence())
1223 // not transparent at all, use content
1224 process(rTransCandidate
.getChildren());
1228 if (rTransCandidate
.getTransparence() < 0.0 || rTransCandidate
.getTransparence() > 1.0)
1230 // invalid transparence, done
1234 // calculate visible range
1235 basegfx::B2DRange aDiscreteVisibleRange
;
1236 calculateDiscreteVisibleRange(aDiscreteVisibleRange
,
1237 rTransCandidate
.getChildren().getB2DRange(getViewInformation2D()),
1238 getViewInformation2D());
1240 if (aDiscreteVisibleRange
.isEmpty())
1242 // not visible, done
1247 sal::systools::COMReference
<ID2D1Layer
> pLayer
;
1248 const HRESULT
hr(getRenderTarget()->CreateLayer(nullptr, &pLayer
));
1250 if (SUCCEEDED(hr
) && pLayer
)
1252 // need to set correct transform for Layer initialization, we work
1253 // in discrete device coordinates
1254 getRenderTarget()->SetTransform(D2D1::Matrix3x2F::Identity());
1255 getRenderTarget()->PushLayer(
1256 D2D1::LayerParameters(D2D1::InfiniteRect(), nullptr, D2D1_ANTIALIAS_MODE_PER_PRIMITIVE
,
1257 D2D1::IdentityMatrix(),
1258 1.0 - rTransCandidate
.getTransparence()), // opacity
1260 process(rTransCandidate
.getChildren());
1261 getRenderTarget()->PopLayer();
1269 void D2DPixelProcessor2D::processMaskPrimitive2DPixel(
1270 const primitive2d::MaskPrimitive2D
& rMaskCandidate
)
1272 if (rMaskCandidate
.getChildren().empty())
1278 basegfx::B2DPolyPolygon
aMask(rMaskCandidate
.getMask());
1282 // no mask (so nothing inside), done
1286 // calculate visible range
1287 basegfx::B2DRange aDiscreteVisibleRange
;
1288 calculateDiscreteVisibleRange(aDiscreteVisibleRange
, aMask
.getB2DRange(),
1289 getViewInformation2D());
1291 if (aDiscreteVisibleRange
.isEmpty())
1293 // not visible, done
1298 std::shared_ptr
<SystemDependentData_ID2D1PathGeometry
> pSystemDependentData_ID2D1MaskGeometry(
1299 getOrCreateFillGeometry(rMaskCandidate
.getMask()));
1301 if (pSystemDependentData_ID2D1MaskGeometry
)
1303 sal::systools::COMReference
<ID2D1TransformedGeometry
> pTransformedMaskGeometry
;
1304 const basegfx::B2DHomMatrix
& rObjectToView(
1305 getViewInformation2D().getObjectToViewTransformation());
1306 HRESULT
hr(aID2D1GlobalFactoryProvider
.getID2D1Factory()->CreateTransformedGeometry(
1307 pSystemDependentData_ID2D1MaskGeometry
->getID2D1PathGeometry(),
1308 D2D1::Matrix3x2F(rObjectToView
.a(), rObjectToView
.b(), rObjectToView
.c(),
1309 rObjectToView
.d(), rObjectToView
.e(), rObjectToView
.f()),
1310 &pTransformedMaskGeometry
));
1312 if (SUCCEEDED(hr
) && pTransformedMaskGeometry
)
1314 sal::systools::COMReference
<ID2D1Layer
> pLayer
;
1315 hr
= getRenderTarget()->CreateLayer(nullptr, &pLayer
);
1317 if (SUCCEEDED(hr
) && pLayer
)
1319 // need to set correct transform for Layer initialization, we work
1320 // in discrete device coordinates
1321 getRenderTarget()->SetTransform(D2D1::Matrix3x2F::Identity());
1322 getRenderTarget()->PushLayer(
1323 D2D1::LayerParameters(D2D1::InfiniteRect(), pTransformedMaskGeometry
), pLayer
);
1324 process(rMaskCandidate
.getChildren());
1325 getRenderTarget()->PopLayer();
1335 void D2DPixelProcessor2D::processPointArrayPrimitive2D(
1336 const primitive2d::PointArrayPrimitive2D
& rPointArrayCandidate
)
1338 const std::vector
<basegfx::B2DPoint
>& rPositions(rPointArrayCandidate
.getPositions());
1340 if (rPositions
.empty())
1342 // no geometry, done
1346 const basegfx::BColor
aPointColor(
1347 maBColorModifierStack
.getModifiedColor(rPointArrayCandidate
.getRGBColor()));
1348 sal::systools::COMReference
<ID2D1SolidColorBrush
> pColorBrush
;
1349 D2D1::ColorF
aD2DColor(aPointColor
.getRed(), aPointColor
.getGreen(), aPointColor
.getBlue());
1350 const HRESULT
hr(getRenderTarget()->CreateSolidColorBrush(aD2DColor
, &pColorBrush
));
1353 if (SUCCEEDED(hr
) && pColorBrush
)
1355 getRenderTarget()->SetTransform(D2D1::Matrix3x2F::Identity());
1357 // To really paint a single pixel I found nothing better than
1358 // switch off AA and draw a pixel-aligned rectangle
1359 const D2D1_ANTIALIAS_MODE
aOldAAMode(getRenderTarget()->GetAntialiasMode());
1360 getRenderTarget()->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED
);
1362 for (auto const& pos
: rPositions
)
1364 const basegfx::B2DPoint
aDiscretePos(
1365 getViewInformation2D().getObjectToViewTransformation() * pos
);
1366 const double fX(ceil(aDiscretePos
.getX()));
1367 const double fY(ceil(aDiscretePos
.getY()));
1368 const D2D1_RECT_F rect
= { FLOAT(fX
), FLOAT(fY
), FLOAT(fX
), FLOAT(fY
) };
1370 getRenderTarget()->DrawRectangle(&rect
, pColorBrush
);
1373 getRenderTarget()->SetAntialiasMode(aOldAAMode
);
1381 void D2DPixelProcessor2D::processMarkerArrayPrimitive2D(
1382 const primitive2d::MarkerArrayPrimitive2D
& rMarkerArrayCandidate
)
1384 const std::vector
<basegfx::B2DPoint
>& rPositions(rMarkerArrayCandidate
.getPositions());
1386 if (rPositions
.empty())
1388 // no geometry, done
1392 const BitmapEx
& rMarker(rMarkerArrayCandidate
.getMarker());
1394 if (rMarker
.IsEmpty())
1396 // no marker defined, done
1400 sal::systools::COMReference
<ID2D1Bitmap
> pD2DBitmap(
1401 getOrCreateB2DBitmap(getRenderTarget(), rMarker
));
1406 getRenderTarget()->SetTransform(D2D1::Matrix3x2F::Identity());
1407 const Size
& rSizePixel(rMarker
.GetSizePixel());
1408 const tools::Long
nMiX((rSizePixel
.Width() / 2) + 1);
1409 const tools::Long
nMiY((rSizePixel
.Height() / 2) + 1);
1410 const tools::Long
nPlX(rSizePixel
.Width() - nMiX
);
1411 const tools::Long
nPlY(rSizePixel
.Height() - nMiY
);
1413 // draw with non-AA to show unhampered, clear, non-scaled marker
1414 const D2D1_ANTIALIAS_MODE
aOldAAMode(getRenderTarget()->GetAntialiasMode());
1415 getRenderTarget()->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED
);
1417 for (auto const& pos
: rPositions
)
1419 const basegfx::B2DPoint
aDiscretePos(
1420 getViewInformation2D().getObjectToViewTransformation() * pos
);
1421 const double fX(ceil(aDiscretePos
.getX()));
1422 const double fY(ceil(aDiscretePos
.getY()));
1423 const D2D1_RECT_F rect
1424 = { FLOAT(fX
- nMiX
), FLOAT(fY
- nMiY
), FLOAT(fX
+ nPlX
), FLOAT(fY
+ nPlY
) };
1426 getRenderTarget()->DrawBitmap(pD2DBitmap
, &rect
);
1429 getRenderTarget()->SetAntialiasMode(aOldAAMode
);
1437 void D2DPixelProcessor2D::processBackgroundColorPrimitive2D(
1438 const primitive2d::BackgroundColorPrimitive2D
& rBackgroundColorCandidate
)
1440 // check for allowed range [0.0 .. 1.0[
1441 if (rBackgroundColorCandidate
.getTransparency() < 0.0
1442 || rBackgroundColorCandidate
.getTransparency() >= 1.0)
1445 const D2D1::ColorF
aD2DColor(rBackgroundColorCandidate
.getBColor().getRed(),
1446 rBackgroundColorCandidate
.getBColor().getGreen(),
1447 rBackgroundColorCandidate
.getBColor().getBlue(),
1448 1.0 - rBackgroundColorCandidate
.getTransparency());
1450 getRenderTarget()->Clear(aD2DColor
);
1453 void D2DPixelProcessor2D::processModifiedColorPrimitive2D(
1454 const primitive2d::ModifiedColorPrimitive2D
& rModifiedCandidate
)
1456 if (!rModifiedCandidate
.getChildren().empty())
1458 maBColorModifierStack
.push(rModifiedCandidate
.getColorModifier());
1459 process(rModifiedCandidate
.getChildren());
1460 maBColorModifierStack
.pop();
1464 void D2DPixelProcessor2D::processTransformPrimitive2D(
1465 const primitive2d::TransformPrimitive2D
& rTransformCandidate
)
1467 // remember current transformation and ViewInformation
1468 const geometry::ViewInformation2D
aLastViewInformation2D(getViewInformation2D());
1470 // create new transformations for local ViewInformation2D
1471 geometry::ViewInformation2D
aViewInformation2D(getViewInformation2D());
1472 aViewInformation2D
.setObjectTransformation(getViewInformation2D().getObjectTransformation()
1473 * rTransformCandidate
.getTransformation());
1474 updateViewInformation(aViewInformation2D
);
1477 process(rTransformCandidate
.getChildren());
1479 // restore transformations
1480 updateViewInformation(aLastViewInformation2D
);
1483 void D2DPixelProcessor2D::processPolygonStrokePrimitive2D(
1484 const primitive2d::PolygonStrokePrimitive2D
& rPolygonStrokeCandidate
)
1486 const basegfx::B2DPolygon
& rPolygon(rPolygonStrokeCandidate
.getB2DPolygon());
1487 const attribute::LineAttribute
& rLineAttribute(rPolygonStrokeCandidate
.getLineAttribute());
1489 if (!rPolygon
.count() || rLineAttribute
.getWidth() < 0.0)
1491 // no geometry, done
1495 // get some values early that might be used for decisions
1496 const bool bHairline(0.0 == rLineAttribute
.getWidth());
1497 const basegfx::B2DHomMatrix
& rObjectToView(
1498 getViewInformation2D().getObjectToViewTransformation());
1499 const double fDiscreteLineWidth(
1502 : (rObjectToView
* basegfx::B2DVector(rLineAttribute
.getWidth(), 0.0)).getLength());
1504 // Here for every combination which the system-specific implementation is not
1505 // capable of visualizing, use the (for decomposable Primitives always possible)
1506 // fallback to the decomposition.
1507 if (basegfx::B2DLineJoin::NONE
== rLineAttribute
.getLineJoin() && fDiscreteLineWidth
> 1.5)
1509 // basegfx::B2DLineJoin::NONE is special for our office, no other GraphicSystem
1510 // knows that (so far), so fallback to decomposition. This is only needed if
1511 // LineJoin will be used, so also check for discrete LineWidth before falling back
1512 process(rPolygonStrokeCandidate
);
1516 // This is a method every system-specific implementation of a decomposable Primitive
1517 // can use to allow simple optical control of paint implementation:
1518 // Create a copy, e.g. change color to 'red' as here and paint before the system
1519 // paints it using the decomposition. That way you can - if active - directly
1520 // optically compare if the system-specific solution is geometrically identical to
1521 // the decomposition (which defines our interpretation that we need to visualize).
1522 // Look below in the impl for bRenderDecomposeForCompareInRed to see that in that case
1523 // we create a half-transparent paint to better support visual control
1524 static bool bRenderDecomposeForCompareInRed(false);
1526 if (bRenderDecomposeForCompareInRed
)
1528 const attribute::LineAttribute
aRed(
1529 basegfx::BColor(1.0, 0.0, 0.0), rLineAttribute
.getWidth(), rLineAttribute
.getLineJoin(),
1530 rLineAttribute
.getLineCap(), rLineAttribute
.getMiterMinimumAngle());
1531 rtl::Reference
<primitive2d::PolygonStrokePrimitive2D
> aCopy(
1532 new primitive2d::PolygonStrokePrimitive2D(
1533 rPolygonStrokeCandidate
.getB2DPolygon(), aRed
,
1534 rPolygonStrokeCandidate
.getStrokeAttribute()));
1539 std::shared_ptr
<SystemDependentData_ID2D1PathGeometry
> pSystemDependentData_ID2D1PathGeometry(
1540 getOrCreatePathGeometry(rPolygon
, getViewInformation2D()));
1542 if (pSystemDependentData_ID2D1PathGeometry
)
1544 sal::systools::COMReference
<ID2D1TransformedGeometry
> pTransformedGeometry
;
1545 const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0);
1546 HRESULT
hr(aID2D1GlobalFactoryProvider
.getID2D1Factory()->CreateTransformedGeometry(
1547 pSystemDependentData_ID2D1PathGeometry
->getID2D1PathGeometry(),
1548 D2D1::Matrix3x2F(rObjectToView
.a(), rObjectToView
.b(), rObjectToView
.c(),
1549 rObjectToView
.d(), rObjectToView
.e() + fAAOffset
,
1550 rObjectToView
.f() + fAAOffset
),
1551 &pTransformedGeometry
));
1553 if (SUCCEEDED(hr
) && pTransformedGeometry
)
1555 const basegfx::BColor
aLineColor(
1556 maBColorModifierStack
.getModifiedColor(rLineAttribute
.getColor()));
1557 D2D1::ColorF
aD2DColor(aLineColor
.getRed(), aLineColor
.getGreen(),
1558 aLineColor
.getBlue());
1560 if (bRenderDecomposeForCompareInRed
)
1565 sal::systools::COMReference
<ID2D1SolidColorBrush
> pColorBrush
;
1566 hr
= getRenderTarget()->CreateSolidColorBrush(aD2DColor
, &pColorBrush
);
1568 if (SUCCEEDED(hr
) && pColorBrush
)
1570 sal::systools::COMReference
<ID2D1StrokeStyle
> pStrokeStyle
;
1571 D2D1_CAP_STYLE
aCapStyle(D2D1_CAP_STYLE_FLAT
);
1572 D2D1_LINE_JOIN
aLineJoin(D2D1_LINE_JOIN_MITER
);
1573 const attribute::StrokeAttribute
& rStrokeAttribute(
1574 rPolygonStrokeCandidate
.getStrokeAttribute());
1575 const bool bDashUsed(!rStrokeAttribute
.isDefault()
1576 && !rStrokeAttribute
.getDotDashArray().empty()
1577 && 0.0 < rStrokeAttribute
.getFullDotDashLen());
1578 D2D1_DASH_STYLE
aDashStyle(bDashUsed
? D2D1_DASH_STYLE_CUSTOM
1579 : D2D1_DASH_STYLE_SOLID
);
1580 std::vector
<float> dashes
;
1581 float miterLimit(1.0);
1583 switch (rLineAttribute
.getLineCap())
1585 case css::drawing::LineCap_ROUND
:
1586 aCapStyle
= D2D1_CAP_STYLE_ROUND
;
1588 case css::drawing::LineCap_SQUARE
:
1589 aCapStyle
= D2D1_CAP_STYLE_SQUARE
;
1595 switch (rLineAttribute
.getLineJoin())
1597 case basegfx::B2DLineJoin::NONE
:
1599 case basegfx::B2DLineJoin::Bevel
:
1600 aLineJoin
= D2D1_LINE_JOIN_BEVEL
;
1602 case basegfx::B2DLineJoin::Miter
:
1604 // for basegfx::B2DLineJoin::Miter there are two problems:
1605 // (1) MS uses D2D1_LINE_JOIN_MITER which handles the cut-off when MiterLimit is hit not by
1606 // fallback to Bevel, but by cutting miter geometry at the defined distance. That is
1607 // nice, but not what we need or is the standard for other graphic systems. Luckily there
1608 // is also D2D1_LINE_JOIN_MITER_OR_BEVEL and (after some search) the page
1609 // https://learn.microsoft.com/en-us/windows/win32/api/d2d1/ne-d2d1-d2d1_line_join
1610 // which gives some explanation, so that is what we need to use here.
1611 // (2) Instead of using an angle in radians (15 deg default) MS uses
1612 // "miterLimit is relative to 1/2 LineWidth", so a length. After some experimenting
1613 // it shows that the (better understandable) angle has to be converted to the length
1614 // that a miter prolongation would have at that angle, so use some trigonometry.
1615 // Unfortunately there is also some'precision' problem (probably), so I had to
1616 // experimentally come to a correction value around 0.9925. Since that seems to
1617 // be no obvious numerical value involved somehow (and as long as I find no other
1618 // explanation) I will have to use that.
1619 // NOTE: To find that correction value I usd that handy bRenderDecomposeForCompareInRed
1620 // and changes in debugger - as work tipp
1621 // With both done I can use Direct2D for Miter completely - what is good for speed.
1622 aLineJoin
= D2D1_LINE_JOIN_MITER_OR_BEVEL
;
1624 // snap absolute value of angle in radians to [0.0 .. PI]
1625 double fVal(::basegfx::snapToZeroRange(
1626 fabs(rLineAttribute
.getMiterMinimumAngle()), M_PI
));
1628 // cut at 0.0 and PI since sin would be zero ('endless' miter)
1629 const double fSmallValue(M_PI
* 0.0000001);
1630 fVal
= std::max(fSmallValue
, fVal
);
1631 fVal
= std::min(M_PI
- fSmallValue
, fVal
);
1633 // get relative length
1634 fVal
= 1.0 / sin(fVal
);
1636 // use for miterLimit, we need factor 2.0 (relative to double LineWidth)
1637 // and the correction mentioned in (2) above
1638 const double fCorrector(2.0 * 0.9925);
1640 miterLimit
= fVal
* fCorrector
;
1643 case basegfx::B2DLineJoin::Round
:
1644 aLineJoin
= D2D1_LINE_JOIN_ROUND
;
1652 // dashes need to be discrete and relative to LineWidth
1653 for (auto& value
: rStrokeAttribute
.getDotDashArray())
1656 (rObjectToView
* basegfx::B2DVector(value
, 0.0)).getLength()
1657 / fDiscreteLineWidth
);
1661 hr
= aID2D1GlobalFactoryProvider
.getID2D1Factory()->CreateStrokeStyle(
1662 D2D1::StrokeStyleProperties(aCapStyle
, // startCap
1663 aCapStyle
, // endCap
1664 aCapStyle
, // dashCap
1665 aLineJoin
, // lineJoin
1666 miterLimit
, // miterLimit
1667 aDashStyle
, // dashStyle
1668 0.0f
), // dashOffset
1669 bDashUsed
? dashes
.data() : nullptr, bDashUsed
? dashes
.size() : 0,
1672 if (SUCCEEDED(hr
) && pStrokeStyle
)
1674 getRenderTarget()->SetTransform(D2D1::Matrix3x2F::Identity());
1675 getRenderTarget()->DrawGeometry(
1676 pTransformedGeometry
, pColorBrush
,
1677 // TODO: Hairline LineWidth, see comment at processPolygonHairlinePrimitive2D
1678 bHairline
? 1.44 : fDiscreteLineWidth
, pStrokeStyle
);
1687 // fallback to decomposition
1688 process(rPolygonStrokeCandidate
);
1692 void D2DPixelProcessor2D::processLineRectanglePrimitive2D(
1693 const primitive2d::LineRectanglePrimitive2D
& rLineRectanglePrimitive2D
)
1695 if (rLineRectanglePrimitive2D
.getB2DRange().isEmpty())
1697 // no geometry, done
1701 const basegfx::BColor
aHairlineColor(
1702 maBColorModifierStack
.getModifiedColor(rLineRectanglePrimitive2D
.getBColor()));
1703 const D2D1::ColorF
aD2DColor(aHairlineColor
.getRed(), aHairlineColor
.getGreen(),
1704 aHairlineColor
.getBlue());
1705 sal::systools::COMReference
<ID2D1SolidColorBrush
> pColorBrush
;
1706 const HRESULT
hr(getRenderTarget()->CreateSolidColorBrush(aD2DColor
, &pColorBrush
));
1709 if (SUCCEEDED(hr
) && pColorBrush
)
1711 const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0);
1712 const basegfx::B2DHomMatrix
aLocalTransform(
1713 getViewInformation2D().getObjectToViewTransformation());
1714 getRenderTarget()->SetTransform(D2D1::Matrix3x2F(
1715 aLocalTransform
.a(), aLocalTransform
.b(), aLocalTransform
.c(), aLocalTransform
.d(),
1716 aLocalTransform
.e() + fAAOffset
, aLocalTransform
.f() + fAAOffset
));
1717 const basegfx::B2DRange
& rRange(rLineRectanglePrimitive2D
.getB2DRange());
1718 const D2D1_RECT_F rect
= { FLOAT(rRange
.getMinX()), FLOAT(rRange
.getMinY()),
1719 FLOAT(rRange
.getMaxX()), FLOAT(rRange
.getMaxY()) };
1720 const double fDiscreteLineWidth(
1721 (getViewInformation2D().getInverseObjectToViewTransformation()
1722 * basegfx::B2DVector(1.44, 0.0))
1725 getRenderTarget()->DrawRectangle(&rect
, pColorBrush
, fDiscreteLineWidth
);
1733 void D2DPixelProcessor2D::processFilledRectanglePrimitive2D(
1734 const primitive2d::FilledRectanglePrimitive2D
& rFilledRectanglePrimitive2D
)
1736 if (rFilledRectanglePrimitive2D
.getB2DRange().isEmpty())
1738 // no geometry, done
1742 const basegfx::BColor
aFillColor(
1743 maBColorModifierStack
.getModifiedColor(rFilledRectanglePrimitive2D
.getBColor()));
1744 const D2D1::ColorF
aD2DColor(aFillColor
.getRed(), aFillColor
.getGreen(), aFillColor
.getBlue());
1745 sal::systools::COMReference
<ID2D1SolidColorBrush
> pColorBrush
;
1746 const HRESULT
hr(getRenderTarget()->CreateSolidColorBrush(aD2DColor
, &pColorBrush
));
1749 if (SUCCEEDED(hr
) && pColorBrush
)
1751 const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0);
1752 const basegfx::B2DHomMatrix
aLocalTransform(
1753 getViewInformation2D().getObjectToViewTransformation());
1754 getRenderTarget()->SetTransform(D2D1::Matrix3x2F(
1755 aLocalTransform
.a(), aLocalTransform
.b(), aLocalTransform
.c(), aLocalTransform
.d(),
1756 aLocalTransform
.e() + fAAOffset
, aLocalTransform
.f() + fAAOffset
));
1757 const basegfx::B2DRange
& rRange(rFilledRectanglePrimitive2D
.getB2DRange());
1758 const D2D1_RECT_F rect
= { FLOAT(rRange
.getMinX()), FLOAT(rRange
.getMinY()),
1759 FLOAT(rRange
.getMaxX()), FLOAT(rRange
.getMaxY()) };
1761 getRenderTarget()->FillRectangle(&rect
, pColorBrush
);
1769 void D2DPixelProcessor2D::processSingleLinePrimitive2D(
1770 const primitive2d::SingleLinePrimitive2D
& rSingleLinePrimitive2D
)
1772 const basegfx::BColor
aLineColor(
1773 maBColorModifierStack
.getModifiedColor(rSingleLinePrimitive2D
.getBColor()));
1774 const D2D1::ColorF
aD2DColor(aLineColor
.getRed(), aLineColor
.getGreen(), aLineColor
.getBlue());
1775 sal::systools::COMReference
<ID2D1SolidColorBrush
> pColorBrush
;
1776 const HRESULT
hr(getRenderTarget()->CreateSolidColorBrush(aD2DColor
, &pColorBrush
));
1779 if (SUCCEEDED(hr
) && pColorBrush
)
1781 const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0);
1782 basegfx::B2DHomMatrix
aLocalTransform(
1783 getViewInformation2D().getObjectToViewTransformation());
1784 const basegfx::B2DPoint
aStart(aLocalTransform
* rSingleLinePrimitive2D
.getStart());
1785 const basegfx::B2DPoint
aEnd(aLocalTransform
* rSingleLinePrimitive2D
.getEnd());
1787 getRenderTarget()->SetTransform(D2D1::Matrix3x2F::Identity());
1788 const D2D1_POINT_2F aD2D1Start
1789 = { FLOAT(aStart
.getX() + fAAOffset
), FLOAT(aStart
.getY() + fAAOffset
) };
1790 const D2D1_POINT_2F aD2D1End
1791 = { FLOAT(aEnd
.getX() + fAAOffset
), FLOAT(aEnd
.getY() + fAAOffset
) };
1793 getRenderTarget()->DrawLine(aD2D1Start
, aD2D1End
, pColorBrush
, 1.44f
);
1801 void D2DPixelProcessor2D::processFillGraphicPrimitive2D(
1802 const primitive2d::FillGraphicPrimitive2D
& rFillGraphicPrimitive2D
)
1804 BitmapEx aPreparedBitmap
;
1805 basegfx::B2DRange
aFillUnitRange(rFillGraphicPrimitive2D
.getFillGraphic().getGraphicRange());
1806 static double fBigDiscreteArea(300.0 * 300.0);
1808 // use tooling to do various checks and prepare tiled rendering, see
1809 // description of method, parameters and return value there
1810 if (!prepareBitmapForDirectRender(rFillGraphicPrimitive2D
, getViewInformation2D(),
1811 aPreparedBitmap
, aFillUnitRange
, fBigDiscreteArea
))
1813 // no output needed, done
1817 if (aPreparedBitmap
.IsEmpty())
1819 // output needed and Bitmap data empty, so no bitmap data based
1820 // tiled rendering is suggested. Use fallback for paint (decomposition)
1821 process(rFillGraphicPrimitive2D
);
1825 // render tiled using the prepared Bitmap data
1826 if (maBColorModifierStack
.count())
1828 // need to apply ColorModifier to Bitmap data
1829 aPreparedBitmap
= aPreparedBitmap
.ModifyBitmapEx(maBColorModifierStack
);
1831 if (aPreparedBitmap
.IsEmpty())
1833 // color gets completely replaced, get it (any input works)
1834 const basegfx::BColor
aModifiedColor(
1835 maBColorModifierStack
.getModifiedColor(basegfx::BColor()));
1837 // use unit geometry as fallback object geometry. Do *not*
1838 // transform, the below used method will use the already
1839 // correctly initialized local ViewInformation
1840 basegfx::B2DPolygon
aPolygon(basegfx::utils::createUnitPolygon());
1842 // what we still need to apply is the object transform from the
1843 // local primitive, that is not part of DisplayInfo yet
1844 aPolygon
.transform(rFillGraphicPrimitive2D
.getTransformation());
1846 rtl::Reference
<primitive2d::PolyPolygonColorPrimitive2D
> aTemp(
1847 new primitive2d::PolyPolygonColorPrimitive2D(basegfx::B2DPolyPolygon(aPolygon
),
1850 // draw as colored Polygon, done
1851 processPolyPolygonColorPrimitive2D(*aTemp
);
1857 sal::systools::COMReference
<ID2D1Bitmap
> pD2DBitmap(
1858 getOrCreateB2DBitmap(getRenderTarget(), aPreparedBitmap
));
1862 sal::systools::COMReference
<ID2D1BitmapBrush
> pBitmapBrush
;
1863 const HRESULT
hr(getRenderTarget()->CreateBitmapBrush(pD2DBitmap
, &pBitmapBrush
));
1865 if (SUCCEEDED(hr
) && pBitmapBrush
)
1867 // set extended to repeat/wrap AKA tiling
1868 pBitmapBrush
->SetExtendModeX(D2D1_EXTEND_MODE_WRAP
);
1869 pBitmapBrush
->SetExtendModeY(D2D1_EXTEND_MODE_WRAP
);
1871 // set interpolation mode
1872 // NOTE: This uses D2D1_BITMAP_INTERPOLATION_MODE, but there seem to be
1873 // advanced modes when using D2D1_INTERPOLATION_MODE, but that needs
1874 // D2D1_BITMAP_BRUSH_PROPERTIES1 and ID2D1BitmapBrush1
1875 sal::systools::COMReference
<ID2D1BitmapBrush1
> pBrush1
;
1876 pBitmapBrush
->QueryInterface(__uuidof(ID2D1BitmapBrush1
),
1877 reinterpret_cast<void**>(&pBrush1
));
1881 pBrush1
->SetInterpolationMode1(D2D1_INTERPOLATION_MODE_MULTI_SAMPLE_LINEAR
);
1885 pBitmapBrush
->SetInterpolationMode(D2D1_BITMAP_INTERPOLATION_MODE_LINEAR
);
1888 // set BitmapBrush transformation relative to it's PixelSize and
1889 // the used FillUnitRange. Since we use unit coordinates here this
1891 const D2D1_SIZE_U
aBMSPixel(pD2DBitmap
->GetPixelSize());
1892 const double fScaleX((aFillUnitRange
.getMaxX() - aFillUnitRange
.getMinX())
1894 const double fScaleY((aFillUnitRange
.getMaxY() - aFillUnitRange
.getMinY())
1895 / aBMSPixel
.height
);
1896 const D2D1_MATRIX_3X2_F
aBTrans(D2D1::Matrix3x2F(
1897 fScaleX
, 0.0, 0.0, fScaleY
, aFillUnitRange
.getMinX(), aFillUnitRange
.getMinY()));
1898 pBitmapBrush
->SetTransform(&aBTrans
);
1900 // set transform to ObjectToWorld to be able to paint in unit coordinates, so
1901 // evtl. shear/rotate in that transform is used and does not influence the
1902 // orthogonal and unit-oriented brush handling
1903 const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0);
1904 const basegfx::B2DHomMatrix
aLocalTransform(
1905 getViewInformation2D().getObjectToViewTransformation()
1906 * rFillGraphicPrimitive2D
.getTransformation());
1907 getRenderTarget()->SetTransform(D2D1::Matrix3x2F(
1908 aLocalTransform
.a(), aLocalTransform
.b(), aLocalTransform
.c(), aLocalTransform
.d(),
1909 aLocalTransform
.e() + fAAOffset
, aLocalTransform
.f() + fAAOffset
));
1911 // use unit rectangle, transformation is already set to include ObjectToWorld
1912 const D2D1_RECT_F rect
= { FLOAT(0.0), FLOAT(0.0), FLOAT(1.0), FLOAT(1.0) };
1914 // draw as unit rectangle as brush filled rectangle
1915 getRenderTarget()->FillRectangle(&rect
, pBitmapBrush
);
1924 void D2DPixelProcessor2D::processFillGradientPrimitive2D(
1925 const primitive2d::FillGradientPrimitive2D
& rFillGradientPrimitive2D
)
1927 // draw all-covering initial BG polygon 1st
1928 bool bDone(drawPolyPolygonColorTransformed(
1929 basegfx::B2DHomMatrix(),
1930 basegfx::B2DPolyPolygon(
1931 basegfx::utils::createPolygonFromRect(rFillGradientPrimitive2D
.getOutputRange())),
1932 rFillGradientPrimitive2D
.getOuterColor()));
1936 const basegfx::B2DPolyPolygon
aForm(rFillGradientPrimitive2D
.getUnitPolygon());
1938 // paint solid fill steps by providing callback as lambda
1939 auto aCallback([&aForm
, &bDone
, this](const basegfx::B2DHomMatrix
& rMatrix
,
1940 const basegfx::BColor
& rColor
) {
1943 bDone
= drawPolyPolygonColorTransformed(rMatrix
, aForm
, rColor
);
1947 // call value generator to trigger callbacks
1948 rFillGradientPrimitive2D
.generateMatricesAndColors(aCallback
);
1955 void D2DPixelProcessor2D::processInvertPrimitive2D(
1956 const primitive2d::InvertPrimitive2D
& rInvertPrimitive2D
)
1958 if (rInvertPrimitive2D
.getChildren().empty())
1964 // Try if we can use ID2D1DeviceContext/d2d1_1 by querying for interface.
1965 // Only with ID2D1DeviceContext we can use ::DrawImage which supports
1966 // D2D1_COMPOSITE_MODE_XOR
1967 sal::systools::COMReference
<ID2D1DeviceContext
> pID2D1DeviceContext
;
1968 getRenderTarget()->QueryInterface(__uuidof(ID2D1DeviceContext
),
1969 reinterpret_cast<void**>(&pID2D1DeviceContext
));
1971 if (!pID2D1DeviceContext
)
1973 // TODO: We have *no* ID2D1DeviceContext and cannot use D2D1_COMPOSITE_MODE_XOR,
1974 // so there is currently no (simple?) way to solve this, there is no 'Invert' method.
1975 // It may be possible to convert to a WICBitmap (gets read access) and do the invert
1976 // there, but that needs experimenting and is probably not performant - but doable.
1981 sal::systools::COMReference
<ID2D1Bitmap
> pInBetweenResult
;
1982 basegfx::B2DRange aDiscreteVisibleRange
;
1984 // create in-between result in discrete coordinates, clipped against visible
1985 // part of ViewInformation (if available)
1986 if (!createBitmapSubContent(pInBetweenResult
, aDiscreteVisibleRange
,
1987 rInvertPrimitive2D
.getChildren(), getViewInformation2D(),
1990 // return of false means no display needed, return
1996 if (pInBetweenResult
)
1998 getRenderTarget()->SetTransform(D2D1::Matrix3x2F::Identity());
1999 const D2D1_POINT_2F aTopLeft
= { FLOAT(floor(aDiscreteVisibleRange
.getMinX())),
2000 FLOAT(floor(aDiscreteVisibleRange
.getMinY())) };
2002 pID2D1DeviceContext
->DrawImage(pInBetweenResult
, aTopLeft
, D2D1_INTERPOLATION_MODE_LINEAR
,
2003 D2D1_COMPOSITE_MODE_XOR
);
2011 void D2DPixelProcessor2D::processBasePrimitive2D(const primitive2d::BasePrimitive2D
& rCandidate
)
2013 if (0 == mnRecursionCounter
)
2014 getRenderTarget()->BeginDraw();
2015 mnRecursionCounter
++;
2017 switch (rCandidate
.getPrimitive2DID())
2019 // Geometry that *has* to be processed
2021 // These Primitives have *no* decompose implementation, so these are the basic ones
2022 // Just four to go to make a processor work completely (but not optimized)
2023 // NOTE: This *could* theoretically be reduced to one and all could implement
2024 // a decompose to pixel data, but that seemed not to make sense to me when
2025 // I designed this. Thus these four are the lowest-level best representation
2027 case PRIMITIVE2D_ID_BITMAPPRIMITIVE2D
:
2029 processBitmapPrimitive2D(
2030 static_cast<const primitive2d::BitmapPrimitive2D
&>(rCandidate
));
2033 case PRIMITIVE2D_ID_POINTARRAYPRIMITIVE2D
:
2035 processPointArrayPrimitive2D(
2036 static_cast<const primitive2d::PointArrayPrimitive2D
&>(rCandidate
));
2039 case PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D
:
2041 processPolygonHairlinePrimitive2D(
2042 static_cast<const primitive2d::PolygonHairlinePrimitive2D
&>(rCandidate
));
2045 case PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D
:
2047 processPolyPolygonColorPrimitive2D(
2048 static_cast<const primitive2d::PolyPolygonColorPrimitive2D
&>(rCandidate
));
2052 // Embedding/groups that *have* to be processed
2054 // These represent qualifiers for freely defined content, e.g. making
2055 // any combination of primitives freely transformed or transparent
2056 // NOTE: PRIMITIVE2D_ID_MODIFIEDCOLORPRIMITIVE2D and
2057 // PRIMITIVE2D_ID_TRANSFORMPRIMITIVE2D are pretty much default-
2058 // implementations that can and are re-used in all processors.
2059 // So - with these and PRIMITIVE2D_ID_INVERTPRIMITIVE2D marked to
2060 // be removed in the future - just three really to be implemented
2061 // locally specifically
2062 case PRIMITIVE2D_ID_TRANSPARENCEPRIMITIVE2D
:
2064 processTransparencePrimitive2D(
2065 static_cast<const primitive2d::TransparencePrimitive2D
&>(rCandidate
));
2068 case PRIMITIVE2D_ID_INVERTPRIMITIVE2D
:
2070 // We urgently should get rid of XOR paint, modern graphic systems
2071 // allow no read access to the pixel targets, but that's naturally
2072 // a precondition for XOR. While we can do that for the office's
2073 // visualization, we can in principle *not* fully avoid getting
2074 // stuff that needs/defines XOR paint, e.g. EMF/WMF imports, so
2075 // we *have* to support it (for now - sigh)...
2076 processInvertPrimitive2D(
2077 static_cast<const primitive2d::InvertPrimitive2D
&>(rCandidate
));
2080 case PRIMITIVE2D_ID_MASKPRIMITIVE2D
:
2082 processMaskPrimitive2DPixel(
2083 static_cast<const primitive2d::MaskPrimitive2D
&>(rCandidate
));
2086 case PRIMITIVE2D_ID_MODIFIEDCOLORPRIMITIVE2D
:
2088 processModifiedColorPrimitive2D(
2089 static_cast<const primitive2d::ModifiedColorPrimitive2D
&>(rCandidate
));
2092 case PRIMITIVE2D_ID_TRANSFORMPRIMITIVE2D
:
2094 processTransformPrimitive2D(
2095 static_cast<const primitive2d::TransformPrimitive2D
&>(rCandidate
));
2099 // Geometry that *may* be processed due to being able to do it better
2100 // then using the decomposition.
2101 // NOTE: In these implementations you could always call what the default
2102 // case below does - call process(rCandidate) to use the decomposition.
2103 // So these impls should only do something if they can do it better/
2104 // faster that the decomposition. So some of them check if they could
2105 // - and if not - use exactly that.
2106 case PRIMITIVE2D_ID_UNIFIEDTRANSPARENCEPRIMITIVE2D
:
2108 // transparence with a fixed alpha for all content, can be done
2109 // significally faster
2110 processUnifiedTransparencePrimitive2D(
2111 static_cast<const primitive2d::UnifiedTransparencePrimitive2D
&>(rCandidate
));
2114 case PRIMITIVE2D_ID_MARKERARRAYPRIMITIVE2D
:
2116 // can be done simpler and without AA better locally
2117 processMarkerArrayPrimitive2D(
2118 static_cast<const primitive2d::MarkerArrayPrimitive2D
&>(rCandidate
));
2121 case PRIMITIVE2D_ID_BACKGROUNDCOLORPRIMITIVE2D
:
2123 // reset to a color, can be done more effectively locally, would
2124 // else decompose to a polygon fill
2125 processBackgroundColorPrimitive2D(
2126 static_cast<const primitive2d::BackgroundColorPrimitive2D
&>(rCandidate
));
2129 case PRIMITIVE2D_ID_POLYGONSTROKEPRIMITIVE2D
:
2131 // fat and stroked lines - much better doable locally, would decompose
2132 // to the full line geometry creation (tessellation)
2133 processPolygonStrokePrimitive2D(
2134 static_cast<const primitive2d::PolygonStrokePrimitive2D
&>(rCandidate
));
2137 case PRIMITIVE2D_ID_LINERECTANGLEPRIMITIVE2D
:
2139 // simple primitive to support future fast callbacks from OutputDevice
2140 // (see 'Example POC' in Gerrit), decomposes to polygon primitive
2141 processLineRectanglePrimitive2D(
2142 static_cast<const primitive2d::LineRectanglePrimitive2D
&>(rCandidate
));
2145 case PRIMITIVE2D_ID_FILLEDRECTANGLEPRIMITIVE2D
:
2147 // simple primitive to support future fast callbacks from OutputDevice
2148 // (see 'Example POC' in Gerrit), decomposes to filled polygon primitive
2149 processFilledRectanglePrimitive2D(
2150 static_cast<const primitive2d::FilledRectanglePrimitive2D
&>(rCandidate
));
2153 case PRIMITIVE2D_ID_SINGLELINEPRIMITIVE2D
:
2155 // simple primitive to support future fast callbacks from OutputDevice
2156 // (see 'Example POC' in Gerrit), decomposes to polygon primitive
2157 processSingleLinePrimitive2D(
2158 static_cast<const primitive2d::SingleLinePrimitive2D
&>(rCandidate
));
2161 case PRIMITIVE2D_ID_FILLGRAPHICPRIMITIVE2D
:
2163 processFillGraphicPrimitive2D(
2164 static_cast<const primitive2d::FillGraphicPrimitive2D
&>(rCandidate
));
2167 case PRIMITIVE2D_ID_FILLGRADIENTPRIMITIVE2D
:
2169 processFillGradientPrimitive2D(
2170 static_cast<const primitive2d::FillGradientPrimitive2D
&>(rCandidate
));
2174 // continue with decompose as fallback
2177 SAL_INFO("drawinglayer", "default case for " << drawinglayer::primitive2d::idToString(
2178 rCandidate
.getPrimitive2DID()));
2179 // process recursively
2180 process(rCandidate
);
2185 mnRecursionCounter
--;
2186 if (0 == mnRecursionCounter
)
2187 getRenderTarget()->EndDraw();
2189 } // end of namespace
2191 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */