Version 7.6.3.2-android, tag libreoffice-7.6.3.2-android
[LibreOffice.git] / drawinglayer / source / processor2d / d2dpixelprocessor2d.cxx
blob783060c2be4c86f8274b9d6dc6abd7ba7edb826b
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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>
22 // win-specific
23 #include <prewin.h>
24 #include <d2d1.h>
25 #include <d2d1_1.h>
26 #include <postwin.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;
61 namespace
63 class ID2D1GlobalFactoryProvider
65 sal::systools::COMReference<ID2D1Factory> mpD2DFactory;
67 public:
68 ID2D1GlobalFactoryProvider()
69 : mpD2DFactory(nullptr)
71 const HRESULT hr(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED,
72 __uuidof(ID2D1Factory), nullptr,
73 reinterpret_cast<void**>(&mpD2DFactory)));
75 if (!SUCCEEDED(hr))
76 mpD2DFactory.clear();
79 sal::systools::COMReference<ID2D1Factory>& getID2D1Factory() { return mpD2DFactory; }
82 ID2D1GlobalFactoryProvider aID2D1GlobalFactoryProvider;
84 class ID2D1GlobalRenderTargetProvider
86 sal::systools::COMReference<ID2D1DCRenderTarget> mpID2D1DCRenderTarget;
88 public:
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.
113 if (!SUCCEEDED(hr))
114 mpID2D1DCRenderTarget.clear();
117 return mpID2D1DCRenderTarget;
121 ID2D1GlobalRenderTargetProvider aID2D1GlobalRenderTargetProvider;
123 class SystemDependentData_ID2D1PathGeometry : public basegfx::SystemDependentData
125 private:
126 sal::systools::COMReference<ID2D1PathGeometry> mpID2D1PathGeometry;
128 public:
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())
149 UINT32 nCount(0);
150 const HRESULT hr(getID2D1PathGeometry()->GetSegmentCount(&nCount));
152 if (SUCCEEDED(hr))
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
157 // control points
158 aRetval = static_cast<sal_Int64>(nCount) * (6 * sizeof(double));
162 return aRetval;
165 basegfx::B2DPoint impPixelSnap(const basegfx::B2DPolygon& rPolygon,
166 const drawinglayer::geometry::ViewInformation2D& rViewInformation,
167 sal_uInt32 nIndex)
169 const sal_uInt32 nCount(rPolygon.count());
171 // get the data
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)));
182 // get the states
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())
222 rSink->AddBezier(
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
229 else
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();
251 else
253 // use and return buffered data
254 return pSystemDependentData_ID2D1PathGeometry;
258 sal::systools::COMReference<ID2D1PathGeometry> pID2D1PathGeometry;
259 HRESULT hr(
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);
278 pSink->Close();
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);
290 else
292 return rPolygon.addOrReplaceSystemDependentData<SystemDependentData_ID2D1PathGeometry>(
293 pID2D1PathGeometry);
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;
314 HRESULT hr(
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());
330 if (nPointCount)
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);
341 pSink->Close();
345 // add to buffering mechanism
346 if (pID2D1PathGeometry)
348 return rPolyPolygon.addOrReplaceSystemDependentData<SystemDependentData_ID2D1PathGeometry>(
349 pID2D1PathGeometry);
352 return std::shared_ptr<SystemDependentData_ID2D1PathGeometry>();
355 class SystemDependentData_ID2D1Bitmap : public basegfx::SystemDependentData
357 private:
358 sal::systools::COMReference<ID2D1Bitmap> mpD2DBitmap;
359 const std::shared_ptr<SalBitmap> maAssociatedAlpha;
361 public:
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;
388 return aRetval;
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();
399 if (bAlpha)
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()));
422 else
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
455 &pID2D1Bitmap));
457 if (!SUCCEEDED(hr))
458 pID2D1Bitmap.clear();
461 return pID2D1Bitmap;
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));
494 if (pID2D1Bitmap)
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));
522 if (!SUCCEEDED(hr))
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;
541 public:
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
553 increaseError();
556 if (!hasError())
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
567 increaseError();
569 else
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);
611 return 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
624 return false;
627 drawinglayer::processor2d::calculateDiscreteVisibleRange(
628 rDiscreteVisibleRange, rContent.getB2DRange(rViewInformation2D), rViewInformation2D);
630 if (rDiscreteVisibleRange.isEmpty())
632 // not visible, done
633 return false;
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
663 return false;
666 // render sub-content recursively
667 aSubContentRenderer.process(rContent);
669 // grab Bitmap & prepare results from RGBA content rendering
670 rResult = aSubContentRenderer.getID2D1Bitmap();
671 return true;
675 namespace drawinglayer::processor2d
677 D2DPixelProcessor2D::D2DPixelProcessor2D(const geometry::ViewInformation2D& rViewInformation)
678 : BaseProcessor2D(rViewInformation)
679 , maBColorModifierStack()
680 , mpRT()
681 , mnRecursionCounter(0)
682 , mnErrorCounter(0)
686 D2DPixelProcessor2D::D2DPixelProcessor2D(const geometry::ViewInformation2D& rViewInformation,
687 HDC aHdc)
688 : BaseProcessor2D(rViewInformation)
689 , maBColorModifierStack()
690 , mpRT()
691 , mnRecursionCounter(0)
692 , mnErrorCounter(0)
694 sal::systools::COMReference<ID2D1DCRenderTarget> pDCRT;
695 tools::Long aOutWidth(0), aOutHeight(0);
697 if (aHdc)
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));
711 const HRESULT hr(
712 aID2D1GlobalFactoryProvider.getID2D1Factory()->CreateDCRenderTarget(&aRTProps, &pDCRT));
714 if (!SUCCEEDED(hr))
715 pDCRT.clear();
718 if (pDCRT)
720 const RECT rc(
721 { 0, 0, o3tl::narrowing<LONG>(aOutWidth), o3tl::narrowing<LONG>(aOutHeight) });
722 const HRESULT hr(pDCRT->BindDC(aHdc, &rc));
724 if (!SUCCEEDED(hr))
725 pDCRT.clear();
728 if (pDCRT)
730 if (rViewInformation.getUseAntiAliasing())
732 D2D1_ANTIALIAS_MODE eAAMode = D2D1_ANTIALIAS_MODE_PER_PRIMITIVE;
733 pDCRT->SetAntialiasMode(eAAMode);
735 else
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
743 XFORM aXForm;
744 aXForm.eM11 = 1.0;
745 aXForm.eM12 = 0.0;
746 aXForm.eM21 = 0.0;
747 aXForm.eM22 = 1.0;
748 aXForm.eDx = 0.0;
749 aXForm.eDy = 0.0;
750 SetWorldTransform(aHdc, &aXForm);
753 if (pDCRT)
755 sal::systools::COMReference<ID2D1RenderTarget> pRT;
756 pDCRT->QueryInterface(__uuidof(ID2D1RenderTarget), reinterpret_cast<void**>(&pRT));
757 setRenderTarget(pRT);
759 else
761 increaseError();
765 void D2DPixelProcessor2D::processPolygonHairlinePrimitive2D(
766 const primitive2d::PolygonHairlinePrimitive2D& rPolygonHairlinePrimitive2D)
768 const basegfx::B2DPolygon& rPolygon(rPolygonHairlinePrimitive2D.getB2DPolygon());
770 if (!rPolygon.count())
772 // no geometry, done
773 return;
776 bool bDone(false);
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);
812 bDone = true;
817 if (!bDone)
818 increaseError();
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()
833 * rTansformation);
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);
854 return true;
859 return false;
862 void D2DPixelProcessor2D::processPolyPolygonColorPrimitive2D(
863 const primitive2d::PolyPolygonColorPrimitive2D& rPolyPolygonColorPrimitive2D)
865 const basegfx::B2DPolyPolygon& rPolyPolygon(rPolyPolygonColorPrimitive2D.getB2DPolyPolygon());
866 const sal_uInt32 nCount(rPolyPolygon.count());
868 if (!nCount)
870 // no geometry, done
871 return;
874 const bool bDone(drawPolyPolygonColorTransformed(basegfx::B2DHomMatrix(), rPolyPolygon,
875 rPolyPolygonColorPrimitive2D.getBColor()));
877 if (!bDone)
878 increaseError();
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())
899 // not visible, done
900 return;
904 BitmapEx aBitmapEx(rBitmapCandidate.getBitmap());
906 if (aBitmapEx.IsEmpty() || aBitmapEx.GetSizePixel().IsEmpty())
908 // no pixel data, done
909 return;
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),
930 aModifiedColor));
932 // draw as Polygon, done
933 processPolyPolygonColorPrimitive2D(*aTemp);
934 return;
938 bool bDone(false);
939 sal::systools::COMReference<ID2D1Bitmap> pD2DBitmap(
940 getOrCreateB2DBitmap(getRenderTarget(), aBitmapEx));
942 if (pD2DBitmap)
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));
954 bDone = true;
957 if (!bDone)
958 increaseError();
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
976 return pRetval;
979 // Release early
980 pID2D1DeviceContext.clear();
981 basegfx::B2DRange aDiscreteVisibleRange;
983 if (!createBitmapSubContent(pRetval, aDiscreteVisibleRange, rTransCandidate.getTransparence(),
984 getViewInformation2D(), getRenderTarget())
985 || !pRetval)
987 // return of false means no display needed, return
988 return pRetval;
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,
1005 &pContent));
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();
1029 // grab result
1030 pContent->GetBitmap(&pRetval);
1035 return 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(
1054 aAlphaEmbedding,
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
1070 return pRetval;
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
1101 return pRetval;
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);
1123 return pRetval;
1126 void D2DPixelProcessor2D::processTransparencePrimitive2D(
1127 const primitive2d::TransparencePrimitive2D& rTransCandidate)
1129 if (rTransCandidate.getChildren().empty())
1131 // no content, done
1132 return;
1135 if (rTransCandidate.getTransparence().empty())
1137 // no mask (so nothing visible), done
1138 return;
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
1150 return;
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());
1158 if (!pAlphaBitmap)
1160 // did not work, use more expensive fallback to existing tooling
1161 pAlphaBitmap
1162 = implCreateAlpha_B2DBitmap(rTransCandidate, aDiscreteVisibleRange, aMaskScale);
1165 if (!pAlphaBitmap)
1167 // could not create alpha channel, error
1168 increaseError();
1169 return;
1172 sal::systools::COMReference<ID2D1Layer> pLayer;
1173 HRESULT hr(getRenderTarget()->CreateLayer(nullptr, &pLayer));
1174 bool bDone(false);
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,
1194 pBitmapBrush),
1195 pLayer);
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();
1204 bDone = true;
1208 if (!bDone)
1209 increaseError();
1212 void D2DPixelProcessor2D::processUnifiedTransparencePrimitive2D(
1213 const primitive2d::UnifiedTransparencePrimitive2D& rTransCandidate)
1215 if (rTransCandidate.getChildren().empty())
1217 // no content, done
1218 return;
1221 if (0.0 == rTransCandidate.getTransparence())
1223 // not transparent at all, use content
1224 process(rTransCandidate.getChildren());
1225 return;
1228 if (rTransCandidate.getTransparence() < 0.0 || rTransCandidate.getTransparence() > 1.0)
1230 // invalid transparence, done
1231 return;
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
1243 return;
1246 bool bDone(false);
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
1259 pLayer);
1260 process(rTransCandidate.getChildren());
1261 getRenderTarget()->PopLayer();
1262 bDone = true;
1265 if (!bDone)
1266 increaseError();
1269 void D2DPixelProcessor2D::processMaskPrimitive2DPixel(
1270 const primitive2d::MaskPrimitive2D& rMaskCandidate)
1272 if (rMaskCandidate.getChildren().empty())
1274 // no content, done
1275 return;
1278 basegfx::B2DPolyPolygon aMask(rMaskCandidate.getMask());
1280 if (!aMask.count())
1282 // no mask (so nothing inside), done
1283 return;
1286 // calculate visible range
1287 basegfx::B2DRange aDiscreteVisibleRange;
1288 calculateDiscreteVisibleRange(aDiscreteVisibleRange, aMask.getB2DRange(),
1289 getViewInformation2D());
1291 if (aDiscreteVisibleRange.isEmpty())
1293 // not visible, done
1294 return;
1297 bool bDone(false);
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();
1326 bDone = true;
1331 if (!bDone)
1332 increaseError();
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
1343 return;
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));
1351 bool bDone(false);
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);
1374 bDone = true;
1377 if (!bDone)
1378 increaseError();
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
1389 return;
1392 const BitmapEx& rMarker(rMarkerArrayCandidate.getMarker());
1394 if (rMarker.IsEmpty())
1396 // no marker defined, done
1397 return;
1400 sal::systools::COMReference<ID2D1Bitmap> pD2DBitmap(
1401 getOrCreateB2DBitmap(getRenderTarget(), rMarker));
1402 bool bDone(false);
1404 if (pD2DBitmap)
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);
1430 bDone = true;
1433 if (!bDone)
1434 increaseError();
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)
1443 return;
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);
1476 // process content
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
1492 return;
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(
1500 bHairline
1501 ? 1.0
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);
1513 return;
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()));
1535 process(*aCopy);
1538 bool bDone(false);
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)
1562 aD2DColor.a = 0.5;
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;
1587 break;
1588 case css::drawing::LineCap_SQUARE:
1589 aCapStyle = D2D1_CAP_STYLE_SQUARE;
1590 break;
1591 default:
1592 break;
1595 switch (rLineAttribute.getLineJoin())
1597 case basegfx::B2DLineJoin::NONE:
1598 break;
1599 case basegfx::B2DLineJoin::Bevel:
1600 aLineJoin = D2D1_LINE_JOIN_BEVEL;
1601 break;
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;
1641 break;
1643 case basegfx::B2DLineJoin::Round:
1644 aLineJoin = D2D1_LINE_JOIN_ROUND;
1645 break;
1646 default:
1647 break;
1650 if (bDashUsed)
1652 // dashes need to be discrete and relative to LineWidth
1653 for (auto& value : rStrokeAttribute.getDotDashArray())
1655 dashes.push_back(
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,
1670 &pStrokeStyle);
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);
1679 bDone = true;
1685 if (!bDone)
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
1698 return;
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));
1707 bool bDone(false);
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))
1723 .getLength());
1725 getRenderTarget()->DrawRectangle(&rect, pColorBrush, fDiscreteLineWidth);
1726 bDone = true;
1729 if (!bDone)
1730 increaseError();
1733 void D2DPixelProcessor2D::processFilledRectanglePrimitive2D(
1734 const primitive2d::FilledRectanglePrimitive2D& rFilledRectanglePrimitive2D)
1736 if (rFilledRectanglePrimitive2D.getB2DRange().isEmpty())
1738 // no geometry, done
1739 return;
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));
1747 bool bDone(false);
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);
1762 bDone = true;
1765 if (!bDone)
1766 increaseError();
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));
1777 bool bDone(false);
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);
1794 bDone = true;
1797 if (!bDone)
1798 increaseError();
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
1814 return;
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);
1822 return;
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),
1848 aModifiedColor));
1850 // draw as colored Polygon, done
1851 processPolyPolygonColorPrimitive2D(*aTemp);
1852 return;
1856 bool bDone(false);
1857 sal::systools::COMReference<ID2D1Bitmap> pD2DBitmap(
1858 getOrCreateB2DBitmap(getRenderTarget(), aPreparedBitmap));
1860 if (pD2DBitmap)
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));
1879 if (pBrush1)
1881 pBrush1->SetInterpolationMode1(D2D1_INTERPOLATION_MODE_MULTI_SAMPLE_LINEAR);
1883 else
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
1890 // is pretty simple
1891 const D2D1_SIZE_U aBMSPixel(pD2DBitmap->GetPixelSize());
1892 const double fScaleX((aFillUnitRange.getMaxX() - aFillUnitRange.getMinX())
1893 / aBMSPixel.width);
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);
1916 bDone = true;
1920 if (!bDone)
1921 increaseError();
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()));
1934 if (bDone)
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) {
1941 if (bDone)
1943 bDone = drawPolyPolygonColorTransformed(rMatrix, aForm, rColor);
1947 // call value generator to trigger callbacks
1948 rFillGradientPrimitive2D.generateMatricesAndColors(aCallback);
1951 if (!bDone)
1952 increaseError();
1955 void D2DPixelProcessor2D::processInvertPrimitive2D(
1956 const primitive2d::InvertPrimitive2D& rInvertPrimitive2D)
1958 if (rInvertPrimitive2D.getChildren().empty())
1960 // no content, done
1961 return;
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.
1977 increaseError();
1978 return;
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(),
1988 getRenderTarget()))
1990 // return of false means no display needed, return
1991 return;
1994 bool bDone(false);
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);
2004 bDone = true;
2007 if (!bDone)
2008 increaseError();
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
2026 // from my POV
2027 case PRIMITIVE2D_ID_BITMAPPRIMITIVE2D:
2029 processBitmapPrimitive2D(
2030 static_cast<const primitive2d::BitmapPrimitive2D&>(rCandidate));
2031 break;
2033 case PRIMITIVE2D_ID_POINTARRAYPRIMITIVE2D:
2035 processPointArrayPrimitive2D(
2036 static_cast<const primitive2d::PointArrayPrimitive2D&>(rCandidate));
2037 break;
2039 case PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D:
2041 processPolygonHairlinePrimitive2D(
2042 static_cast<const primitive2d::PolygonHairlinePrimitive2D&>(rCandidate));
2043 break;
2045 case PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D:
2047 processPolyPolygonColorPrimitive2D(
2048 static_cast<const primitive2d::PolyPolygonColorPrimitive2D&>(rCandidate));
2049 break;
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));
2066 break;
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));
2078 break;
2080 case PRIMITIVE2D_ID_MASKPRIMITIVE2D:
2082 processMaskPrimitive2DPixel(
2083 static_cast<const primitive2d::MaskPrimitive2D&>(rCandidate));
2084 break;
2086 case PRIMITIVE2D_ID_MODIFIEDCOLORPRIMITIVE2D:
2088 processModifiedColorPrimitive2D(
2089 static_cast<const primitive2d::ModifiedColorPrimitive2D&>(rCandidate));
2090 break;
2092 case PRIMITIVE2D_ID_TRANSFORMPRIMITIVE2D:
2094 processTransformPrimitive2D(
2095 static_cast<const primitive2d::TransformPrimitive2D&>(rCandidate));
2096 break;
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));
2112 break;
2114 case PRIMITIVE2D_ID_MARKERARRAYPRIMITIVE2D:
2116 // can be done simpler and without AA better locally
2117 processMarkerArrayPrimitive2D(
2118 static_cast<const primitive2d::MarkerArrayPrimitive2D&>(rCandidate));
2119 break;
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));
2127 break;
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));
2135 break;
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));
2143 break;
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));
2151 break;
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));
2159 break;
2161 case PRIMITIVE2D_ID_FILLGRAPHICPRIMITIVE2D:
2163 processFillGraphicPrimitive2D(
2164 static_cast<const primitive2d::FillGraphicPrimitive2D&>(rCandidate));
2165 break;
2167 case PRIMITIVE2D_ID_FILLGRADIENTPRIMITIVE2D:
2169 processFillGradientPrimitive2D(
2170 static_cast<const primitive2d::FillGradientPrimitive2D&>(rCandidate));
2171 break;
2174 // continue with decompose as fallback
2175 default:
2177 SAL_INFO("drawinglayer", "default case for " << drawinglayer::primitive2d::idToString(
2178 rCandidate.getPrimitive2DID()));
2179 // process recursively
2180 process(rCandidate);
2181 break;
2185 mnRecursionCounter--;
2186 if (0 == mnRecursionCounter)
2187 getRenderTarget()->EndDraw();
2189 } // end of namespace
2191 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */