summaryrefslogtreecommitdiff
path: root/drawinglayer/source/processor2d/d2dpixelprocessor2d.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'drawinglayer/source/processor2d/d2dpixelprocessor2d.cxx')
-rw-r--r--drawinglayer/source/processor2d/d2dpixelprocessor2d.cxx2177
1 files changed, 2177 insertions, 0 deletions
diff --git a/drawinglayer/source/processor2d/d2dpixelprocessor2d.cxx b/drawinglayer/source/processor2d/d2dpixelprocessor2d.cxx
new file mode 100644
index 000000000000..82f51fdc4356
--- /dev/null
+++ b/drawinglayer/source/processor2d/d2dpixelprocessor2d.cxx
@@ -0,0 +1,2177 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+// win-specific
+#include <prewin.h>
+#include <d2d1.h>
+#include <d2d1_1.h>
+#include <postwin.h>
+
+#include <drawinglayer/processor2d/d2dpixelprocessor2d.hxx>
+#include <drawinglayer/processor2d/SDPRProcessor2dTools.hxx>
+#include <sal/log.hxx>
+#include <vcl/alpha.hxx>
+#include <vcl/outdev.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx>
+#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx>
+#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx>
+#include <drawinglayer/primitive2d/bitmapprimitive2d.hxx>
+#include <drawinglayer/primitive2d/fillgraphicprimitive2d.hxx>
+#include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx>
+#include <drawinglayer/primitive2d/backgroundcolorprimitive2d.hxx>
+#include <drawinglayer/primitive2d/baseprimitive2d.hxx>
+#include <drawinglayer/primitive2d/markerarrayprimitive2d.hxx>
+#include <drawinglayer/primitive2d/maskprimitive2d.hxx>
+#include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx>
+#include <drawinglayer/primitive2d/pointarrayprimitive2d.hxx>
+#include <drawinglayer/primitive2d/PolygonStrokePrimitive2D.hxx>
+#include <drawinglayer/primitive2d/Tools.hxx>
+#include <drawinglayer/primitive2d/transformprimitive2d.hxx>
+#include <drawinglayer/primitive2d/transparenceprimitive2d.hxx>
+#include <drawinglayer/primitive2d/invertprimitive2d.hxx>
+#include <drawinglayer/primitive2d/fillgradientprimitive2d.hxx>
+#include <drawinglayer/converters.hxx>
+#include <basegfx/curve/b2dcubicbezier.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <basegfx/utils/systemdependentdata.hxx>
+#include <vcl/BitmapReadAccess.hxx>
+#include <vcl/svapp.hxx>
+
+using namespace com::sun::star;
+
+namespace
+{
+class ID2D1GlobalFactoryProvider
+{
+ sal::systools::COMReference<ID2D1Factory> mpD2DFactory;
+
+public:
+ ID2D1GlobalFactoryProvider()
+ : mpD2DFactory(nullptr)
+ {
+ const HRESULT hr(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED,
+ __uuidof(ID2D1Factory), nullptr,
+ reinterpret_cast<void**>(&mpD2DFactory)));
+
+ if (!SUCCEEDED(hr))
+ mpD2DFactory.clear();
+ }
+
+ sal::systools::COMReference<ID2D1Factory>& getID2D1Factory() { return mpD2DFactory; }
+};
+
+ID2D1GlobalFactoryProvider aID2D1GlobalFactoryProvider;
+
+class ID2D1GlobalRenderTargetProvider
+{
+ sal::systools::COMReference<ID2D1DCRenderTarget> mpID2D1DCRenderTarget;
+
+public:
+ ID2D1GlobalRenderTargetProvider()
+ : mpID2D1DCRenderTarget()
+ {
+ }
+
+ sal::systools::COMReference<ID2D1DCRenderTarget>& getID2D1DCRenderTarget()
+ {
+ if (!mpID2D1DCRenderTarget && aID2D1GlobalFactoryProvider.getID2D1Factory())
+ {
+ const D2D1_RENDER_TARGET_PROPERTIES aRTProps(D2D1::RenderTargetProperties(
+ D2D1_RENDER_TARGET_TYPE_DEFAULT,
+ D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM,
+ D2D1_ALPHA_MODE_IGNORE), //D2D1_ALPHA_MODE_PREMULTIPLIED),
+ 0, 0, D2D1_RENDER_TARGET_USAGE_NONE, D2D1_FEATURE_LEVEL_DEFAULT));
+
+ const HRESULT hr(aID2D1GlobalFactoryProvider.getID2D1Factory()->CreateDCRenderTarget(
+ &aRTProps, &mpID2D1DCRenderTarget));
+
+ // interestingly this ID2D1DCRenderTarget already works and can hold
+ // created ID2D1Bitmap(s) in RenderTarget-specific form, *without*
+ // any call to "BindDC", thus *without* the need of a real HDC - nice :-)
+ // When that would be needed, Application::GetDefaultDevice() would need
+ // to have a HDC that is valid during LO's lifetime.
+
+ if (!SUCCEEDED(hr))
+ mpID2D1DCRenderTarget.clear();
+ }
+
+ return mpID2D1DCRenderTarget;
+ }
+};
+
+ID2D1GlobalRenderTargetProvider aID2D1GlobalRenderTargetProvider;
+
+class SystemDependentData_ID2D1PathGeometry : public basegfx::SystemDependentData
+{
+private:
+ sal::systools::COMReference<ID2D1PathGeometry> mpID2D1PathGeometry;
+
+public:
+ SystemDependentData_ID2D1PathGeometry(
+ sal::systools::COMReference<ID2D1PathGeometry>& rID2D1PathGeometry)
+ : basegfx::SystemDependentData(Application::GetSystemDependentDataManager(),
+ basegfx::SDD_Type::SDDType_ID2D1PathGeometry)
+ , mpID2D1PathGeometry(rID2D1PathGeometry)
+ {
+ }
+
+ const sal::systools::COMReference<ID2D1PathGeometry>& getID2D1PathGeometry() const
+ {
+ return mpID2D1PathGeometry;
+ }
+ virtual sal_Int64 estimateUsageInBytes() const override;
+};
+
+sal_Int64 SystemDependentData_ID2D1PathGeometry::estimateUsageInBytes() const
+{
+ sal_Int64 aRetval(0);
+
+ if (getID2D1PathGeometry())
+ {
+ UINT32 nCount(0);
+ const HRESULT hr(getID2D1PathGeometry()->GetSegmentCount(&nCount));
+
+ if (SUCCEEDED(hr))
+ {
+ // without completely receiving and tracing the GeometrySink
+ // do a rough estimation - each segment is 2D, so has two doubles.
+ // Some are beziers, so add some guessed buffer for two additional
+ // control points
+ aRetval = static_cast<sal_Int64>(nCount) * (6 * sizeof(double));
+ }
+ }
+
+ return aRetval;
+}
+
+basegfx::B2DPoint impPixelSnap(const basegfx::B2DPolygon& rPolygon,
+ const drawinglayer::geometry::ViewInformation2D& rViewInformation,
+ sal_uInt32 nIndex)
+{
+ const sal_uInt32 nCount(rPolygon.count());
+
+ // get the data
+ const basegfx::B2ITuple aPrevTuple(
+ basegfx::fround(rViewInformation.getObjectToViewTransformation()
+ * rPolygon.getB2DPoint((nIndex + nCount - 1) % nCount)));
+ const basegfx::B2DPoint aCurrPoint(rViewInformation.getObjectToViewTransformation()
+ * rPolygon.getB2DPoint(nIndex));
+ const basegfx::B2ITuple aCurrTuple(basegfx::fround(aCurrPoint));
+ const basegfx::B2ITuple aNextTuple(
+ basegfx::fround(rViewInformation.getObjectToViewTransformation()
+ * rPolygon.getB2DPoint((nIndex + 1) % nCount)));
+
+ // get the states
+ const bool bPrevVertical(aPrevTuple.getX() == aCurrTuple.getX());
+ const bool bNextVertical(aNextTuple.getX() == aCurrTuple.getX());
+ const bool bPrevHorizontal(aPrevTuple.getY() == aCurrTuple.getY());
+ const bool bNextHorizontal(aNextTuple.getY() == aCurrTuple.getY());
+ const bool bSnapX(bPrevVertical || bNextVertical);
+ const bool bSnapY(bPrevHorizontal || bNextHorizontal);
+
+ if (bSnapX || bSnapY)
+ {
+ basegfx::B2DPoint aSnappedPoint(bSnapX ? aCurrTuple.getX() : aCurrPoint.getX(),
+ bSnapY ? aCurrTuple.getY() : aCurrPoint.getY());
+
+ aSnappedPoint *= rViewInformation.getInverseObjectToViewTransformation();
+
+ return aSnappedPoint;
+ }
+
+ return rPolygon.getB2DPoint(nIndex);
+}
+
+void addB2DPolygonToPathGeometry(sal::systools::COMReference<ID2D1GeometrySink>& rSink,
+ const basegfx::B2DPolygon& rPolygon,
+ const drawinglayer::geometry::ViewInformation2D* pViewInformation)
+{
+ const sal_uInt32 nPointCount(rPolygon.count());
+ const sal_uInt32 nEdgeCount(rPolygon.isClosed() ? nPointCount : nPointCount - 1);
+ basegfx::B2DCubicBezier aEdge;
+
+ for (sal_uInt32 a(0); a < nEdgeCount; a++)
+ {
+ rPolygon.getBezierSegment(a, aEdge);
+
+ const basegfx::B2DPoint aEndPoint(
+ nullptr == pViewInformation
+ ? aEdge.getEndPoint()
+ : impPixelSnap(rPolygon, *pViewInformation, (a + 1) % nPointCount));
+
+ if (aEdge.isBezier())
+ {
+ rSink->AddBezier(
+ D2D1::BezierSegment(D2D1::Point2F(aEdge.getControlPointA().getX(),
+ aEdge.getControlPointA().getY()), //C1
+ D2D1::Point2F(aEdge.getControlPointB().getX(),
+ aEdge.getControlPointB().getY()), //c2
+ D2D1::Point2F(aEndPoint.getX(), aEndPoint.getY()))); //end
+ }
+ else
+ {
+ rSink->AddLine(D2D1::Point2F(aEndPoint.getX(), aEndPoint.getY()));
+ }
+ }
+}
+
+std::shared_ptr<SystemDependentData_ID2D1PathGeometry>
+getOrCreatePathGeometry(const basegfx::B2DPolygon& rPolygon,
+ const drawinglayer::geometry::ViewInformation2D& rViewInformation)
+{
+ // try to access buffered data
+ std::shared_ptr<SystemDependentData_ID2D1PathGeometry> pSystemDependentData_ID2D1PathGeometry(
+ rPolygon.getSystemDependentData<SystemDependentData_ID2D1PathGeometry>(
+ basegfx::SDD_Type::SDDType_ID2D1PathGeometry));
+
+ if (pSystemDependentData_ID2D1PathGeometry)
+ {
+ if (rViewInformation.getPixelSnapHairline())
+ {
+ // do not buffer when PixelSnap is active
+ pSystemDependentData_ID2D1PathGeometry.reset();
+ }
+ else
+ {
+ // use and return buffered data
+ return pSystemDependentData_ID2D1PathGeometry;
+ }
+ }
+
+ sal::systools::COMReference<ID2D1PathGeometry> pID2D1PathGeometry;
+ HRESULT hr(
+ aID2D1GlobalFactoryProvider.getID2D1Factory()->CreatePathGeometry(&pID2D1PathGeometry));
+ const sal_uInt32 nPointCount(rPolygon.count());
+
+ if (SUCCEEDED(hr) && nPointCount)
+ {
+ sal::systools::COMReference<ID2D1GeometrySink> pSink;
+ hr = pID2D1PathGeometry->Open(&pSink);
+
+ if (SUCCEEDED(hr) && pSink)
+ {
+ const basegfx::B2DPoint aStart(rViewInformation.getPixelSnapHairline()
+ ? rPolygon.getB2DPoint(0)
+ : impPixelSnap(rPolygon, rViewInformation, 0));
+
+ pSink->BeginFigure(D2D1::Point2F(aStart.getX(), aStart.getY()),
+ D2D1_FIGURE_BEGIN_HOLLOW);
+ addB2DPolygonToPathGeometry(pSink, rPolygon, &rViewInformation);
+ pSink->EndFigure(rPolygon.isClosed() ? D2D1_FIGURE_END_CLOSED : D2D1_FIGURE_END_OPEN);
+ pSink->Close();
+ }
+ }
+
+ // add to buffering mechanism
+ if (pID2D1PathGeometry)
+ {
+ if (rViewInformation.getPixelSnapHairline() || nPointCount <= 4)
+ {
+ // do not buffer when PixelSnap is active or small polygon
+ return std::make_shared<SystemDependentData_ID2D1PathGeometry>(pID2D1PathGeometry);
+ }
+ else
+ {
+ return rPolygon.addOrReplaceSystemDependentData<SystemDependentData_ID2D1PathGeometry>(
+ pID2D1PathGeometry);
+ }
+ }
+
+ return std::shared_ptr<SystemDependentData_ID2D1PathGeometry>();
+}
+
+std::shared_ptr<SystemDependentData_ID2D1PathGeometry>
+getOrCreateFillGeometry(const basegfx::B2DPolyPolygon& rPolyPolygon)
+{
+ // try to access buffered data
+ std::shared_ptr<SystemDependentData_ID2D1PathGeometry> pSystemDependentData_ID2D1PathGeometry(
+ rPolyPolygon.getSystemDependentData<SystemDependentData_ID2D1PathGeometry>(
+ basegfx::SDD_Type::SDDType_ID2D1PathGeometry));
+
+ if (pSystemDependentData_ID2D1PathGeometry)
+ {
+ // use and return buffered data
+ return pSystemDependentData_ID2D1PathGeometry;
+ }
+
+ sal::systools::COMReference<ID2D1PathGeometry> pID2D1PathGeometry;
+ HRESULT hr(
+ aID2D1GlobalFactoryProvider.getID2D1Factory()->CreatePathGeometry(&pID2D1PathGeometry));
+ const sal_uInt32 nCount(rPolyPolygon.count());
+
+ if (SUCCEEDED(hr) && nCount)
+ {
+ sal::systools::COMReference<ID2D1GeometrySink> pSink;
+ hr = pID2D1PathGeometry->Open(&pSink);
+
+ if (SUCCEEDED(hr) && pSink)
+ {
+ for (sal_uInt32 a(0); a < nCount; a++)
+ {
+ const basegfx::B2DPolygon& rPolygon(rPolyPolygon.getB2DPolygon(a));
+ const sal_uInt32 nPointCount(rPolygon.count());
+
+ if (nPointCount)
+ {
+ const basegfx::B2DPoint aStart(rPolygon.getB2DPoint(0));
+
+ pSink->BeginFigure(D2D1::Point2F(aStart.getX(), aStart.getY()),
+ D2D1_FIGURE_BEGIN_FILLED);
+ addB2DPolygonToPathGeometry(pSink, rPolygon, nullptr);
+ pSink->EndFigure(D2D1_FIGURE_END_CLOSED);
+ }
+ }
+
+ pSink->Close();
+ }
+ }
+
+ // add to buffering mechanism
+ if (pID2D1PathGeometry)
+ {
+ return rPolyPolygon.addOrReplaceSystemDependentData<SystemDependentData_ID2D1PathGeometry>(
+ pID2D1PathGeometry);
+ }
+
+ return std::shared_ptr<SystemDependentData_ID2D1PathGeometry>();
+}
+
+class SystemDependentData_ID2D1Bitmap : public basegfx::SystemDependentData
+{
+private:
+ sal::systools::COMReference<ID2D1Bitmap> mpD2DBitmap;
+
+public:
+ SystemDependentData_ID2D1Bitmap(sal::systools::COMReference<ID2D1Bitmap>& rD2DBitmap)
+ : basegfx::SystemDependentData(Application::GetSystemDependentDataManager(),
+ basegfx::SDD_Type::SDDType_ID2D1Bitmap)
+ , mpD2DBitmap(rD2DBitmap)
+ {
+ }
+
+ const sal::systools::COMReference<ID2D1Bitmap>& getID2D1Bitmap() const { return mpD2DBitmap; }
+
+ virtual sal_Int64 estimateUsageInBytes() const override;
+};
+
+sal_Int64 SystemDependentData_ID2D1Bitmap::estimateUsageInBytes() const
+{
+ sal_Int64 aRetval(0);
+
+ if (getID2D1Bitmap())
+ {
+ // use factor 4 for RGBA_8 as estimation
+ const D2D1_SIZE_U aSizePixel(getID2D1Bitmap()->GetPixelSize());
+ aRetval = static_cast<sal_Int64>(aSizePixel.width)
+ * static_cast<sal_Int64>(aSizePixel.height) * 4;
+ }
+
+ return aRetval;
+}
+
+sal::systools::COMReference<ID2D1Bitmap> createB2DBitmap(const Bitmap& rBitmap)
+{
+ const Size& rSizePixel(rBitmap.GetSizePixel());
+ const bool bAlpha(rBitmap.HasAlpha());
+ const sal_uInt32 nPixelCount(rSizePixel.Width() * rSizePixel.Height());
+ std::unique_ptr<sal_uInt32[]> aData(new sal_uInt32[nPixelCount]);
+ sal_uInt32* pTarget = aData.get();
+
+ {
+ BitmapScopedReadAccess pReadAccess(rBitmap);
+ const tools::Long nHeight(pReadAccess->Height());
+ const tools::Long nWidth(pReadAccess->Width());
+
+ for (tools::Long y = 0; y < nHeight; ++y)
+ {
+ for (tools::Long x = 0; x < nWidth; ++x)
+ {
+ const BitmapColor aColor(pReadAccess->GetColor(y, x));
+ *pTarget++ = sal_uInt32(aColor);
+ }
+ }
+ }
+
+ // use GlobalRenderTarget to allow usage combined with
+ // the Direct2D CreateSharedBitmap-mechanism. This is needed
+ // since ID2D1Bitmap is a ID2D1RenderTarget-dependent resource
+ // and thus - in principle - would have to be re-created for
+ // *each* new ID2D1RenderTarget, that means for *each* new
+ // target HDC, resp. OutputDevice
+ sal::systools::COMReference<ID2D1Bitmap> pID2D1Bitmap;
+
+ if (aID2D1GlobalRenderTargetProvider.getID2D1DCRenderTarget())
+ {
+ const HRESULT hr(aID2D1GlobalRenderTargetProvider.getID2D1DCRenderTarget()->CreateBitmap(
+ D2D1::SizeU(rSizePixel.Width(), rSizePixel.Height()), &aData[0],
+ rSizePixel.Width() * sizeof(sal_uInt32),
+ D2D1::BitmapProperties(
+ D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, // DXGI_FORMAT
+ bAlpha ? D2D1_ALPHA_MODE_PREMULTIPLIED
+ : D2D1_ALPHA_MODE_IGNORE)), // D2D1_ALPHA_MODE
+ &pID2D1Bitmap));
+
+ if (!SUCCEEDED(hr))
+ pID2D1Bitmap.clear();
+ }
+
+ return pID2D1Bitmap;
+}
+
+sal::systools::COMReference<ID2D1Bitmap>
+getOrCreateB2DBitmap(sal::systools::COMReference<ID2D1RenderTarget>& rRT, const Bitmap& rBitmap)
+{
+ const basegfx::SystemDependentDataHolder* pHolder(rBitmap.accessSystemDependentDataHolder());
+ std::shared_ptr<SystemDependentData_ID2D1Bitmap> pSystemDependentData_ID2D1Bitmap;
+
+ if (nullptr != pHolder)
+ {
+ // try to access SystemDependentDataHolder and buffered data
+ pSystemDependentData_ID2D1Bitmap
+ = std::static_pointer_cast<SystemDependentData_ID2D1Bitmap>(
+ pHolder->getSystemDependentData(basegfx::SDD_Type::SDDType_ID2D1Bitmap));
+ }
+
+ if (!pSystemDependentData_ID2D1Bitmap)
+ {
+ // have to create newly
+ sal::systools::COMReference<ID2D1Bitmap> pID2D1Bitmap(createB2DBitmap(rBitmap));
+
+ if (pID2D1Bitmap)
+ {
+ // creation worked, create SystemDependentData_ID2D1Bitmap
+ pSystemDependentData_ID2D1Bitmap
+ = std::make_shared<SystemDependentData_ID2D1Bitmap>(pID2D1Bitmap);
+
+ // only add if feasible
+ if (nullptr != pHolder
+ && pSystemDependentData_ID2D1Bitmap->calculateCombinedHoldCyclesInSeconds() > 0)
+ {
+ basegfx::SystemDependentData_SharedPtr r2(pSystemDependentData_ID2D1Bitmap);
+ const_cast<basegfx::SystemDependentDataHolder*>(pHolder)
+ ->addOrReplaceSystemDependentData(r2);
+ }
+ }
+ }
+
+ sal::systools::COMReference<ID2D1Bitmap> pWrappedD2DBitmap;
+
+ if (pSystemDependentData_ID2D1Bitmap)
+ {
+ // embed to CreateSharedBitmap, that makes it usable on
+ // the specified RenderTarget
+ const HRESULT hr(rRT->CreateSharedBitmap(
+ __uuidof(ID2D1Bitmap),
+ static_cast<void*>(pSystemDependentData_ID2D1Bitmap->getID2D1Bitmap()), nullptr,
+ &pWrappedD2DBitmap));
+
+ if (!SUCCEEDED(hr))
+ pWrappedD2DBitmap.clear();
+ }
+
+ return pWrappedD2DBitmap;
+}
+
+// This is a simple local derivation of D2DPixelProcessor2D to be used
+// when sub-content needs to be rendered to pixels. Hand over the adapted
+// ViewInformation2D, a pixel size and the parent RenderTarget. It will
+// locally create and use a ID2D1BitmapRenderTarget to render the stuff
+// (you need to call process() with the primitives to be painted of
+// course). Then use the local helper getID2D1Bitmap() to access the
+// ID2D1Bitmap which was the target of that operation.
+class D2DBitmapPixelProcessor2D final : public drawinglayer::processor2d::D2DPixelProcessor2D
+{
+ // the local ID2D1BitmapRenderTarget
+ sal::systools::COMReference<ID2D1BitmapRenderTarget> mpBitmapRenderTarget;
+
+public:
+ // helper class to create another instance of D2DPixelProcessor2D for
+ // creating helper-ID2D1Bitmap's for a given ID2D1RenderTarget
+ D2DBitmapPixelProcessor2D(const drawinglayer::geometry::ViewInformation2D& rViewInformation,
+ sal_uInt32 nWidth, sal_uInt32 nHeight,
+ const sal::systools::COMReference<ID2D1RenderTarget>& rParent)
+ : drawinglayer::processor2d::D2DPixelProcessor2D(rViewInformation)
+ , mpBitmapRenderTarget()
+ {
+ if (0 == nWidth || 0 == nHeight)
+ {
+ // no width/height, done
+ increaseError();
+ }
+
+ if (!hasError())
+ {
+ // Allocate compatible RGBA render target
+ const D2D1_SIZE_U aRenderTargetSizePixel(D2D1::SizeU(nWidth, nHeight));
+ const HRESULT hr(rParent->CreateCompatibleRenderTarget(
+ nullptr, &aRenderTargetSizePixel, nullptr,
+ D2D1_COMPATIBLE_RENDER_TARGET_OPTIONS_NONE, &mpBitmapRenderTarget));
+
+ if (!SUCCEEDED(hr) || nullptr == mpBitmapRenderTarget)
+ {
+ // did not work, done
+ increaseError();
+ }
+ else
+ {
+ sal::systools::COMReference<ID2D1RenderTarget> pRT;
+ mpBitmapRenderTarget->QueryInterface(__uuidof(ID2D1RenderTarget),
+ reinterpret_cast<void**>(&pRT));
+ setRenderTarget(pRT);
+ }
+ }
+
+ if (hasRenderTarget())
+ {
+ // set Viewort if none was given. We have a fixed pixel target, s we know the
+ // exact Viewport to work on
+ if (getViewInformation2D().getViewport().isEmpty())
+ {
+ drawinglayer::geometry::ViewInformation2D aViewInformation(getViewInformation2D());
+ basegfx::B2DRange aViewport(0.0, 0.0, nWidth, nHeight);
+ basegfx::B2DHomMatrix aInvViewTransform(aViewInformation.getViewTransformation());
+
+ aInvViewTransform.invert();
+ aViewport.transform(aInvViewTransform);
+ aViewInformation.setViewport(aViewport);
+ setViewInformation2D(aViewInformation);
+ }
+
+ // clear as render preparation
+ getRenderTarget()->BeginDraw();
+ getRenderTarget()->Clear(D2D1::ColorF(0.0f, 0.0f, 0.0f, 0.0f));
+ getRenderTarget()->EndDraw();
+ }
+ }
+
+ sal::systools::COMReference<ID2D1Bitmap> getID2D1Bitmap() const
+ {
+ sal::systools::COMReference<ID2D1Bitmap> pResult;
+
+ // access the resulting bitmap if exists
+ if (mpBitmapRenderTarget)
+ {
+ mpBitmapRenderTarget->GetBitmap(&pResult);
+ }
+
+ return pResult;
+ }
+};
+
+bool createBitmapSubContent(sal::systools::COMReference<ID2D1Bitmap>& rResult,
+ basegfx::B2DRange& rDiscreteVisibleRange,
+ const drawinglayer::primitive2d::Primitive2DContainer& rContent,
+ const drawinglayer::geometry::ViewInformation2D& rViewInformation2D,
+ const sal::systools::COMReference<ID2D1RenderTarget>& rRenderTarget)
+{
+ if (rContent.empty() || !rRenderTarget)
+ {
+ // no content or no render target, done
+ return false;
+ }
+
+ drawinglayer::processor2d::calculateDiscreteVisibleRange(
+ rDiscreteVisibleRange, rContent.getB2DRange(rViewInformation2D), rViewInformation2D);
+
+ if (rDiscreteVisibleRange.isEmpty())
+ {
+ // not visible, done
+ return false;
+ }
+
+ // Use a temporary second instance of a D2DBitmapPixelProcessor2D with adapted
+ // ViewInformation2D, it will create the needed ID2D1BitmapRenderTarget
+ // locally and Clear() it.
+ drawinglayer::geometry::ViewInformation2D aAdaptedViewInformation2D(rViewInformation2D);
+ const double fTargetWidth(ceil(rDiscreteVisibleRange.getWidth()));
+ const double fTargetHeight(ceil(rDiscreteVisibleRange.getHeight()));
+
+ {
+ // create adapted ViewTransform, needs to be offset in discrete coordinates,
+ // so multiply from left
+ basegfx::B2DHomMatrix aAdapted(
+ basegfx::utils::createTranslateB2DHomMatrix(-rDiscreteVisibleRange.getMinX(),
+ -rDiscreteVisibleRange.getMinY())
+ * rViewInformation2D.getViewTransformation());
+ aAdaptedViewInformation2D.setViewTransformation(aAdapted);
+
+ // reset Viewport (world coordinates), so the helper renderer will create it's
+ // own based on it's given internal discrete size
+ aAdaptedViewInformation2D.setViewport(basegfx::B2DRange());
+ }
+
+ D2DBitmapPixelProcessor2D aSubContentRenderer(aAdaptedViewInformation2D, fTargetWidth,
+ fTargetHeight, rRenderTarget);
+
+ if (!aSubContentRenderer.valid())
+ {
+ // did not work, done
+ return false;
+ }
+
+ // render sub-content recursively
+ aSubContentRenderer.process(rContent);
+
+ // grab Bitmap & prepare results from RGBA content rendering
+ rResult = aSubContentRenderer.getID2D1Bitmap();
+ return true;
+}
+}
+
+namespace drawinglayer::processor2d
+{
+D2DPixelProcessor2D::D2DPixelProcessor2D(const geometry::ViewInformation2D& rViewInformation)
+ : BaseProcessor2D(rViewInformation)
+ , maBColorModifierStack()
+ , mpRT()
+ , mnRecursionCounter(0)
+ , mnErrorCounter(0)
+{
+}
+
+D2DPixelProcessor2D::D2DPixelProcessor2D(const geometry::ViewInformation2D& rViewInformation,
+ HDC aHdc)
+ : BaseProcessor2D(rViewInformation)
+ , maBColorModifierStack()
+ , mpRT()
+ , mnRecursionCounter(0)
+ , mnErrorCounter(0)
+{
+ sal::systools::COMReference<ID2D1DCRenderTarget> pDCRT;
+ tools::Long aOutWidth(0), aOutHeight(0);
+
+ if (aHdc)
+ {
+ aOutWidth = GetDeviceCaps(aHdc, HORZRES);
+ aOutHeight = GetDeviceCaps(aHdc, VERTRES);
+ }
+
+ if (aOutWidth > 0 && aOutHeight > 0 && aID2D1GlobalFactoryProvider.getID2D1Factory())
+ {
+ const D2D1_RENDER_TARGET_PROPERTIES aRTProps(D2D1::RenderTargetProperties(
+ D2D1_RENDER_TARGET_TYPE_DEFAULT,
+ D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM,
+ D2D1_ALPHA_MODE_IGNORE), //D2D1_ALPHA_MODE_PREMULTIPLIED),
+ 0, 0, D2D1_RENDER_TARGET_USAGE_NONE, D2D1_FEATURE_LEVEL_DEFAULT));
+
+ const HRESULT hr(
+ aID2D1GlobalFactoryProvider.getID2D1Factory()->CreateDCRenderTarget(&aRTProps, &pDCRT));
+
+ if (!SUCCEEDED(hr))
+ pDCRT.clear();
+ }
+
+ if (pDCRT)
+ {
+ const RECT rc(
+ { 0, 0, o3tl::narrowing<LONG>(aOutWidth), o3tl::narrowing<LONG>(aOutHeight) });
+ const HRESULT hr(pDCRT->BindDC(aHdc, &rc));
+
+ if (!SUCCEEDED(hr))
+ pDCRT.clear();
+ }
+
+ if (pDCRT)
+ {
+ if (rViewInformation.getUseAntiAliasing())
+ {
+ D2D1_ANTIALIAS_MODE eAAMode = D2D1_ANTIALIAS_MODE_PER_PRIMITIVE;
+ pDCRT->SetAntialiasMode(eAAMode);
+ }
+ else
+ {
+ D2D1_ANTIALIAS_MODE eAAMode = D2D1_ANTIALIAS_MODE_ALIASED;
+ pDCRT->SetAntialiasMode(eAAMode);
+ }
+
+ // since ID2D1DCRenderTarget depends on the transformation
+ // set at hdc, be careful and reset it to identity
+ XFORM aXForm;
+ aXForm.eM11 = 1.0;
+ aXForm.eM12 = 0.0;
+ aXForm.eM21 = 0.0;
+ aXForm.eM22 = 1.0;
+ aXForm.eDx = 0.0;
+ aXForm.eDy = 0.0;
+ SetWorldTransform(aHdc, &aXForm);
+ }
+
+ if (pDCRT)
+ {
+ sal::systools::COMReference<ID2D1RenderTarget> pRT;
+ pDCRT->QueryInterface(__uuidof(ID2D1RenderTarget), reinterpret_cast<void**>(&pRT));
+ setRenderTarget(pRT);
+ }
+ else
+ {
+ increaseError();
+ }
+}
+
+void D2DPixelProcessor2D::processPolygonHairlinePrimitive2D(
+ const primitive2d::PolygonHairlinePrimitive2D& rPolygonHairlinePrimitive2D)
+{
+ const basegfx::B2DPolygon& rPolygon(rPolygonHairlinePrimitive2D.getB2DPolygon());
+
+ if (!rPolygon.count())
+ {
+ // no geometry, done
+ return;
+ }
+
+ bool bDone(false);
+ std::shared_ptr<SystemDependentData_ID2D1PathGeometry> pSystemDependentData_ID2D1PathGeometry(
+ getOrCreatePathGeometry(rPolygon, getViewInformation2D()));
+
+ if (pSystemDependentData_ID2D1PathGeometry)
+ {
+ sal::systools::COMReference<ID2D1TransformedGeometry> pTransformedGeometry;
+ const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0);
+ const basegfx::B2DHomMatrix& rObjectToView(
+ getViewInformation2D().getObjectToViewTransformation());
+ HRESULT hr(aID2D1GlobalFactoryProvider.getID2D1Factory()->CreateTransformedGeometry(
+ pSystemDependentData_ID2D1PathGeometry->getID2D1PathGeometry(),
+ D2D1::Matrix3x2F(rObjectToView.a(), rObjectToView.b(), rObjectToView.c(),
+ rObjectToView.d(), rObjectToView.e() + fAAOffset,
+ rObjectToView.f() + fAAOffset),
+ &pTransformedGeometry));
+
+ if (SUCCEEDED(hr) && pTransformedGeometry)
+ {
+ const basegfx::BColor aHairlineColor(
+ maBColorModifierStack.getModifiedColor(rPolygonHairlinePrimitive2D.getBColor()));
+ const D2D1::ColorF aD2DColor(aHairlineColor.getRed(), aHairlineColor.getGreen(),
+ aHairlineColor.getBlue());
+ sal::systools::COMReference<ID2D1SolidColorBrush> pColorBrush;
+ hr = getRenderTarget()->CreateSolidColorBrush(aD2DColor, &pColorBrush);
+
+ if (SUCCEEDED(hr) && pColorBrush)
+ {
+ getRenderTarget()->SetTransform(D2D1::Matrix3x2F::Identity());
+ // TODO: Unfortunately Direct2D paint of one pixel wide lines does not
+ // correctly and completely blend 100% over the background. Experimenting
+ // shows that a value around/slightly below 2.0 is needed which hints that
+ // alpha blending the half-shifted lines (see fAAOffset above) is involved.
+ // To get correct blending I try to use just wider hairlines for now. This
+ // may need to be improved - or balanced (trying sqrt(2) now...)
+ getRenderTarget()->DrawGeometry(pTransformedGeometry, pColorBrush, 1.44f);
+ bDone = true;
+ }
+ }
+ }
+
+ if (!bDone)
+ increaseError();
+}
+
+bool D2DPixelProcessor2D::drawPolyPolygonColorTransformed(
+ const basegfx::B2DHomMatrix& rTansformation, const basegfx::B2DPolyPolygon& rPolyPolygon,
+ const basegfx::BColor& rColor)
+{
+ std::shared_ptr<SystemDependentData_ID2D1PathGeometry> pSystemDependentData_ID2D1PathGeometry(
+ getOrCreateFillGeometry(rPolyPolygon));
+
+ if (pSystemDependentData_ID2D1PathGeometry)
+ {
+ sal::systools::COMReference<ID2D1TransformedGeometry> pTransformedGeometry;
+ const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0);
+ basegfx::B2DHomMatrix aTansformation(getViewInformation2D().getObjectToViewTransformation()
+ * rTansformation);
+ HRESULT hr(aID2D1GlobalFactoryProvider.getID2D1Factory()->CreateTransformedGeometry(
+ pSystemDependentData_ID2D1PathGeometry->getID2D1PathGeometry(),
+ D2D1::Matrix3x2F(aTansformation.a(), aTansformation.b(), aTansformation.c(),
+ aTansformation.d(), aTansformation.e() + fAAOffset,
+ aTansformation.f() + fAAOffset),
+ &pTransformedGeometry));
+
+ if (SUCCEEDED(hr) && pTransformedGeometry)
+ {
+ const basegfx::BColor aFillColor(maBColorModifierStack.getModifiedColor(rColor));
+ const D2D1::ColorF aD2DColor(aFillColor.getRed(), aFillColor.getGreen(),
+ aFillColor.getBlue());
+
+ sal::systools::COMReference<ID2D1SolidColorBrush> pColorBrush;
+ hr = getRenderTarget()->CreateSolidColorBrush(aD2DColor, &pColorBrush);
+
+ if (SUCCEEDED(hr) && pColorBrush)
+ {
+ getRenderTarget()->SetTransform(D2D1::Matrix3x2F::Identity());
+ getRenderTarget()->FillGeometry(pTransformedGeometry, pColorBrush);
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+void D2DPixelProcessor2D::processPolyPolygonColorPrimitive2D(
+ const primitive2d::PolyPolygonColorPrimitive2D& rPolyPolygonColorPrimitive2D)
+{
+ const basegfx::B2DPolyPolygon& rPolyPolygon(rPolyPolygonColorPrimitive2D.getB2DPolyPolygon());
+ const sal_uInt32 nCount(rPolyPolygon.count());
+
+ if (!nCount)
+ {
+ // no geometry, done
+ return;
+ }
+
+ const bool bDone(drawPolyPolygonColorTransformed(basegfx::B2DHomMatrix(), rPolyPolygon,
+ rPolyPolygonColorPrimitive2D.getBColor()));
+
+ if (!bDone)
+ increaseError();
+}
+
+void D2DPixelProcessor2D::processBitmapPrimitive2D(
+ const primitive2d::BitmapPrimitive2D& rBitmapCandidate)
+{
+ // check if graphic content is inside discrete local ViewPort
+ if (!getViewInformation2D().getDiscreteViewport().isEmpty())
+ {
+ // calculate logic object range, remember: the helper below will
+ // transform using getObjectToViewTransformation, so the bitmap-local
+ // transform would be missing
+ basegfx::B2DRange aDiscreteVisibleRange(basegfx::B2DRange::getUnitB2DRange());
+ aDiscreteVisibleRange.transform(rBitmapCandidate.getTransform());
+
+ // calculate visible range
+ calculateDiscreteVisibleRange(aDiscreteVisibleRange, aDiscreteVisibleRange,
+ getViewInformation2D());
+
+ if (aDiscreteVisibleRange.isEmpty())
+ {
+ // not visible, done
+ return;
+ }
+ }
+
+ Bitmap aBitmap(rBitmapCandidate.getBitmap());
+
+ if (aBitmap.IsEmpty() || aBitmap.GetSizePixel().IsEmpty())
+ {
+ // no pixel data, done
+ return;
+ }
+
+ if (maBColorModifierStack.count())
+ {
+ // need to apply ColorModifier to Bitmap data
+ aBitmap = aBitmap.Modify(maBColorModifierStack);
+
+ if (aBitmap.IsEmpty())
+ {
+ // color gets completely replaced, get it (any input works)
+ const basegfx::BColor aModifiedColor(
+ maBColorModifierStack.getModifiedColor(basegfx::BColor()));
+
+ // use unit geometry as fallback object geometry. Do *not*
+ // transform, the below used method will use the already
+ // correctly initialized local ViewInformation
+ basegfx::B2DPolygon aPolygon(basegfx::utils::createUnitPolygon());
+
+ rtl::Reference<primitive2d::PolyPolygonColorPrimitive2D> aTemp(
+ new primitive2d::PolyPolygonColorPrimitive2D(basegfx::B2DPolyPolygon(aPolygon),
+ aModifiedColor));
+
+ // draw as Polygon, done
+ processPolyPolygonColorPrimitive2D(*aTemp);
+ return;
+ }
+ }
+
+ bool bDone(false);
+ sal::systools::COMReference<ID2D1Bitmap> pD2DBitmap(
+ getOrCreateB2DBitmap(getRenderTarget(), aBitmap));
+
+ if (pD2DBitmap)
+ {
+ const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0);
+ const basegfx::B2DHomMatrix aLocalTransform(
+ getViewInformation2D().getObjectToViewTransformation()
+ * rBitmapCandidate.getTransform());
+ getRenderTarget()->SetTransform(D2D1::Matrix3x2F(
+ aLocalTransform.a(), aLocalTransform.b(), aLocalTransform.c(), aLocalTransform.d(),
+ aLocalTransform.e() + fAAOffset, aLocalTransform.f() + fAAOffset));
+
+ // destinationRectangle is part of transformation above, so use UnitRange
+ getRenderTarget()->DrawBitmap(pD2DBitmap, D2D1::RectF(0.0, 0.0, 1.0, 1.0));
+ bDone = true;
+ }
+
+ if (!bDone)
+ increaseError();
+}
+
+sal::systools::COMReference<ID2D1Bitmap> D2DPixelProcessor2D::implCreateAlpha_Direct(
+ const primitive2d::TransparencePrimitive2D& rTransCandidate)
+{
+ // Try if we can use ID2D1DeviceContext/d2d1_1 by querying for interface.
+ // Only then can we use ID2D1Effect/CLSID_D2D1LuminanceToAlpha and it makes
+ // sense to try to do it this way in this implementation
+ sal::systools::COMReference<ID2D1DeviceContext> pID2D1DeviceContext;
+ getRenderTarget()->QueryInterface(__uuidof(ID2D1DeviceContext),
+ reinterpret_cast<void**>(&pID2D1DeviceContext));
+ sal::systools::COMReference<ID2D1Bitmap> pRetval;
+
+ if (!pID2D1DeviceContext)
+ {
+ // no, done - tell caller to use fallback by returning empty - we have
+ // not the preconditions for this
+ return pRetval;
+ }
+
+ // Release early
+ pID2D1DeviceContext.clear();
+ basegfx::B2DRange aDiscreteVisibleRange;
+
+ if (!createBitmapSubContent(pRetval, aDiscreteVisibleRange, rTransCandidate.getTransparence(),
+ getViewInformation2D(), getRenderTarget())
+ || !pRetval)
+ {
+ // return of false means no display needed, return
+ return pRetval;
+ }
+
+ // Now we need a target to render this to, using the ID2D1Effect tooling.
+ // We can directly apply the effect to an alpha-only 8bit target here,
+ // so create one (no RGBA needed for this).
+ // We need another render target: I tried to render pInBetweenResult
+ // to pContent again, but that does not work due to the bitmap
+ // fetched being probably only an internal reference to the
+ // ID2D1BitmapRenderTarget, thus it would draw onto itself -> chaos
+ sal::systools::COMReference<ID2D1BitmapRenderTarget> pContent;
+ const D2D1_PIXEL_FORMAT aAlphaFormat(
+ D2D1::PixelFormat(DXGI_FORMAT_A8_UNORM, D2D1_ALPHA_MODE_STRAIGHT));
+ const D2D1_SIZE_U aRenderTargetSizePixel(D2D1::SizeU(ceil(aDiscreteVisibleRange.getWidth()),
+ ceil(aDiscreteVisibleRange.getHeight())));
+ const HRESULT hr(getRenderTarget()->CreateCompatibleRenderTarget(
+ nullptr, &aRenderTargetSizePixel, &aAlphaFormat, D2D1_COMPATIBLE_RENDER_TARGET_OPTIONS_NONE,
+ &pContent));
+
+ if (SUCCEEDED(hr) && pContent)
+ {
+ // try to access ID2D1DeviceContext of that target, we need that *now*
+ pContent->QueryInterface(__uuidof(ID2D1DeviceContext),
+ reinterpret_cast<void**>(&pID2D1DeviceContext));
+
+ if (pID2D1DeviceContext)
+ {
+ // create the effect
+ sal::systools::COMReference<ID2D1Effect> pLuminanceToAlpha;
+ pID2D1DeviceContext->CreateEffect(CLSID_D2D1LuminanceToAlpha, &pLuminanceToAlpha);
+
+ if (pLuminanceToAlpha)
+ {
+ // chain effect stuff together & paint it
+ pLuminanceToAlpha->SetInput(0, pRetval);
+
+ pID2D1DeviceContext->BeginDraw();
+ pID2D1DeviceContext->Clear(D2D1::ColorF(0.0f, 0.0f, 0.0f, 0.0f));
+ pID2D1DeviceContext->DrawImage(pLuminanceToAlpha);
+ pID2D1DeviceContext->EndDraw();
+
+ // grab result
+ pContent->GetBitmap(&pRetval);
+ }
+ }
+ }
+
+ return pRetval;
+}
+
+sal::systools::COMReference<ID2D1Bitmap> D2DPixelProcessor2D::implCreateAlpha_B2DBitmap(
+ const primitive2d::TransparencePrimitive2D& rTransCandidate,
+ const basegfx::B2DRange& rVisibleRange, D2D1_MATRIX_3X2_F& rMaskScale)
+{
+ // Use this fallback that will also use a pixel processor indirectly,
+ // but allows to get the AlphaMask as vcl Bitmap using existing tooling
+ const sal_uInt32 nDiscreteClippedWidth(ceil(rVisibleRange.getWidth()));
+ const sal_uInt32 nDiscreteClippedHeight(ceil(rVisibleRange.getHeight()));
+ const sal_uInt32 nMaximumQuadraticPixels(250000);
+
+ // Embed content graphics to TransformPrimitive2D
+ const basegfx::B2DHomMatrix aAlphaEmbedding(
+ basegfx::utils::createTranslateB2DHomMatrix(-rVisibleRange.getMinX(),
+ -rVisibleRange.getMinY())
+ * getViewInformation2D().getObjectToViewTransformation());
+ const primitive2d::Primitive2DReference xAlphaEmbedRef(new primitive2d::TransformPrimitive2D(
+ aAlphaEmbedding,
+ drawinglayer::primitive2d::Primitive2DContainer(rTransCandidate.getTransparence())));
+ drawinglayer::primitive2d::Primitive2DContainer xEmbedSeq{ xAlphaEmbedRef };
+
+ // use empty ViewInformation to have neutral transformation
+ const geometry::ViewInformation2D aEmptyViewInformation2D;
+
+ // use new mode to create AlphaChannel (not just AlphaMask) for transparency channel
+ const AlphaMask aAlpha(::drawinglayer::createAlphaMask(
+ std::move(xEmbedSeq), aEmptyViewInformation2D, nDiscreteClippedWidth,
+ nDiscreteClippedHeight, nMaximumQuadraticPixels, true));
+ sal::systools::COMReference<ID2D1Bitmap> pRetval;
+
+ if (aAlpha.IsEmpty())
+ {
+ // if we have no content we are done
+ return pRetval;
+ }
+
+ // use alpha data to create the ID2D1Bitmap
+ const Size& rSizePixel(aAlpha.GetSizePixel());
+ const sal_uInt32 nPixelCount(rSizePixel.Width() * rSizePixel.Height());
+ std::unique_ptr<sal_uInt8[]> aData(new sal_uInt8[nPixelCount]);
+ sal_uInt8* pTarget = aData.get();
+ Bitmap aSrcAlpha(aAlpha.GetBitmap());
+ BitmapScopedReadAccess pReadAccess(aSrcAlpha);
+ const tools::Long nHeight(pReadAccess->Height());
+ const tools::Long nWidth(pReadAccess->Width());
+
+ for (tools::Long y = 0; y < nHeight; ++y)
+ {
+ for (tools::Long x = 0; x < nWidth; ++x)
+ {
+ const BitmapColor aColor(pReadAccess->GetColor(y, x));
+ *pTarget++ = aColor.GetLuminance();
+ }
+ }
+
+ const D2D1_BITMAP_PROPERTIES aBmProps(D2D1::BitmapProperties(
+ D2D1::PixelFormat(DXGI_FORMAT_A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED)));
+ const HRESULT hr(getRenderTarget()->CreateBitmap(
+ D2D1::SizeU(rSizePixel.Width(), rSizePixel.Height()), &aData[0],
+ rSizePixel.Width() * sizeof(sal_uInt8), &aBmProps, &pRetval));
+
+ if (!SUCCEEDED(hr) || !pRetval)
+ {
+ // did not work, done
+ return pRetval;
+ }
+
+ // create needed adapted transformation for alpha brush.
+ // We may have to take a corrective scaling into account when the
+ // MaximumQuadraticPixel limit was used/triggered
+ const Size& rBitmapSizePixel(aAlpha.GetSizePixel());
+
+ if (static_cast<sal_uInt32>(rBitmapSizePixel.Width()) != nDiscreteClippedWidth
+ || static_cast<sal_uInt32>(rBitmapSizePixel.Height()) != nDiscreteClippedHeight)
+ {
+ // scale in X and Y should be the same (see fReduceFactor in createAlphaMask),
+ // so adapt numerically to a single scale value, they are integer rounded values
+ const double fScaleX(static_cast<double>(rBitmapSizePixel.Width())
+ / static_cast<double>(nDiscreteClippedWidth));
+ const double fScaleY(static_cast<double>(rBitmapSizePixel.Height())
+ / static_cast<double>(nDiscreteClippedHeight));
+
+ const double fScale(1.0 / ((fScaleX + fScaleY) * 0.5));
+ rMaskScale = D2D1::Matrix3x2F::Scale(fScale, fScale);
+ }
+
+ return pRetval;
+}
+
+void D2DPixelProcessor2D::processTransparencePrimitive2D(
+ const primitive2d::TransparencePrimitive2D& rTransCandidate)
+{
+ if (rTransCandidate.getChildren().empty())
+ {
+ // no content, done
+ return;
+ }
+
+ if (rTransCandidate.getTransparence().empty())
+ {
+ // no mask (so nothing visible), done
+ return;
+ }
+
+ // calculate visible range, create only for that range
+ basegfx::B2DRange aDiscreteVisibleRange;
+ calculateDiscreteVisibleRange(aDiscreteVisibleRange,
+ rTransCandidate.getChildren().getB2DRange(getViewInformation2D()),
+ getViewInformation2D());
+
+ if (aDiscreteVisibleRange.isEmpty())
+ {
+ // not visible, done
+ return;
+ }
+
+ // try to create directly, this needs the current mpRT to be a ID2D1DeviceContext/d2d1_1
+ // what is not guaranteed but usually works for more modern windows (after 7)
+ sal::systools::COMReference<ID2D1Bitmap> pAlphaBitmap(implCreateAlpha_Direct(rTransCandidate));
+ D2D1_MATRIX_3X2_F aMaskScale(D2D1::Matrix3x2F::Identity());
+
+ if (!pAlphaBitmap)
+ {
+ // did not work, use more expensive fallback to existing tooling
+ pAlphaBitmap
+ = implCreateAlpha_B2DBitmap(rTransCandidate, aDiscreteVisibleRange, aMaskScale);
+ }
+
+ if (!pAlphaBitmap)
+ {
+ // could not create alpha channel, error
+ increaseError();
+ return;
+ }
+
+ sal::systools::COMReference<ID2D1Layer> pLayer;
+ HRESULT hr(getRenderTarget()->CreateLayer(nullptr, &pLayer));
+ bool bDone(false);
+
+ if (SUCCEEDED(hr) && pLayer)
+ {
+ sal::systools::COMReference<ID2D1BitmapBrush> pBitmapBrush;
+ hr = getRenderTarget()->CreateBitmapBrush(pAlphaBitmap, &pBitmapBrush);
+
+ if (SUCCEEDED(hr) && pBitmapBrush)
+ {
+ // apply MaskScale to Brush, maybe used if implCreateAlpha_B2DBitmap was needed
+ pBitmapBrush->SetTransform(aMaskScale);
+
+ // need to set transform offset for Layer initialization, we work
+ // in discrete device coordinates
+ getRenderTarget()->SetTransform(D2D1::Matrix3x2F::Translation(
+ floor(aDiscreteVisibleRange.getMinX()), floor(aDiscreteVisibleRange.getMinY())));
+
+ getRenderTarget()->PushLayer(D2D1::LayerParameters(D2D1::InfiniteRect(), nullptr,
+ D2D1_ANTIALIAS_MODE_PER_PRIMITIVE,
+ D2D1::Matrix3x2F::Identity(), 1.0,
+ pBitmapBrush),
+ pLayer);
+
+ // ... but need to reset to paint content unchanged
+ getRenderTarget()->SetTransform(D2D1::Matrix3x2F::Identity());
+
+ // draw content recursively
+ process(rTransCandidate.getChildren());
+
+ getRenderTarget()->PopLayer();
+ bDone = true;
+ }
+ }
+
+ if (!bDone)
+ increaseError();
+}
+
+void D2DPixelProcessor2D::processUnifiedTransparencePrimitive2D(
+ const primitive2d::UnifiedTransparencePrimitive2D& rTransCandidate)
+{
+ if (rTransCandidate.getChildren().empty())
+ {
+ // no content, done
+ return;
+ }
+
+ if (0.0 == rTransCandidate.getTransparence())
+ {
+ // not transparent at all, use content
+ process(rTransCandidate.getChildren());
+ return;
+ }
+
+ if (rTransCandidate.getTransparence() < 0.0 || rTransCandidate.getTransparence() > 1.0)
+ {
+ // invalid transparence, done
+ return;
+ }
+
+ // calculate visible range
+ basegfx::B2DRange aDiscreteVisibleRange;
+ calculateDiscreteVisibleRange(aDiscreteVisibleRange,
+ rTransCandidate.getChildren().getB2DRange(getViewInformation2D()),
+ getViewInformation2D());
+
+ if (aDiscreteVisibleRange.isEmpty())
+ {
+ // not visible, done
+ return;
+ }
+
+ bool bDone(false);
+ sal::systools::COMReference<ID2D1Layer> pLayer;
+ const HRESULT hr(getRenderTarget()->CreateLayer(nullptr, &pLayer));
+
+ if (SUCCEEDED(hr) && pLayer)
+ {
+ // need to set correct transform for Layer initialization, we work
+ // in discrete device coordinates
+ getRenderTarget()->SetTransform(D2D1::Matrix3x2F::Identity());
+ getRenderTarget()->PushLayer(
+ D2D1::LayerParameters(D2D1::InfiniteRect(), nullptr, D2D1_ANTIALIAS_MODE_PER_PRIMITIVE,
+ D2D1::IdentityMatrix(),
+ 1.0 - rTransCandidate.getTransparence()), // opacity
+ pLayer);
+ process(rTransCandidate.getChildren());
+ getRenderTarget()->PopLayer();
+ bDone = true;
+ }
+
+ if (!bDone)
+ increaseError();
+}
+
+void D2DPixelProcessor2D::processMaskPrimitive2D(const primitive2d::MaskPrimitive2D& rMaskCandidate)
+{
+ if (rMaskCandidate.getChildren().empty())
+ {
+ // no content, done
+ return;
+ }
+
+ basegfx::B2DPolyPolygon aMask(rMaskCandidate.getMask());
+
+ if (!aMask.count())
+ {
+ // no mask (so nothing inside), done
+ return;
+ }
+
+ // calculate visible range
+ basegfx::B2DRange aDiscreteVisibleRange;
+ calculateDiscreteVisibleRange(aDiscreteVisibleRange, aMask.getB2DRange(),
+ getViewInformation2D());
+
+ if (aDiscreteVisibleRange.isEmpty())
+ {
+ // not visible, done
+ return;
+ }
+
+ bool bDone(false);
+ std::shared_ptr<SystemDependentData_ID2D1PathGeometry> pSystemDependentData_ID2D1MaskGeometry(
+ getOrCreateFillGeometry(rMaskCandidate.getMask()));
+
+ if (pSystemDependentData_ID2D1MaskGeometry)
+ {
+ sal::systools::COMReference<ID2D1TransformedGeometry> pTransformedMaskGeometry;
+ const basegfx::B2DHomMatrix& rObjectToView(
+ getViewInformation2D().getObjectToViewTransformation());
+ HRESULT hr(aID2D1GlobalFactoryProvider.getID2D1Factory()->CreateTransformedGeometry(
+ pSystemDependentData_ID2D1MaskGeometry->getID2D1PathGeometry(),
+ D2D1::Matrix3x2F(rObjectToView.a(), rObjectToView.b(), rObjectToView.c(),
+ rObjectToView.d(), rObjectToView.e(), rObjectToView.f()),
+ &pTransformedMaskGeometry));
+
+ if (SUCCEEDED(hr) && pTransformedMaskGeometry)
+ {
+ sal::systools::COMReference<ID2D1Layer> pLayer;
+ hr = getRenderTarget()->CreateLayer(nullptr, &pLayer);
+
+ if (SUCCEEDED(hr) && pLayer)
+ {
+ // need to set correct transform for Layer initialization, we work
+ // in discrete device coordinates
+ getRenderTarget()->SetTransform(D2D1::Matrix3x2F::Identity());
+ getRenderTarget()->PushLayer(
+ D2D1::LayerParameters(D2D1::InfiniteRect(), pTransformedMaskGeometry), pLayer);
+ process(rMaskCandidate.getChildren());
+ getRenderTarget()->PopLayer();
+ bDone = true;
+ }
+ }
+ }
+
+ if (!bDone)
+ increaseError();
+}
+
+void D2DPixelProcessor2D::processPointArrayPrimitive2D(
+ const primitive2d::PointArrayPrimitive2D& rPointArrayCandidate)
+{
+ const std::vector<basegfx::B2DPoint>& rPositions(rPointArrayCandidate.getPositions());
+
+ if (rPositions.empty())
+ {
+ // no geometry, done
+ return;
+ }
+
+ const basegfx::BColor aPointColor(
+ maBColorModifierStack.getModifiedColor(rPointArrayCandidate.getRGBColor()));
+ sal::systools::COMReference<ID2D1SolidColorBrush> pColorBrush;
+ D2D1::ColorF aD2DColor(aPointColor.getRed(), aPointColor.getGreen(), aPointColor.getBlue());
+ const HRESULT hr(getRenderTarget()->CreateSolidColorBrush(aD2DColor, &pColorBrush));
+ bool bDone(false);
+
+ if (SUCCEEDED(hr) && pColorBrush)
+ {
+ getRenderTarget()->SetTransform(D2D1::Matrix3x2F::Identity());
+
+ // To really paint a single pixel I found nothing better than
+ // switch off AA and draw a pixel-aligned rectangle
+ const D2D1_ANTIALIAS_MODE aOldAAMode(getRenderTarget()->GetAntialiasMode());
+ getRenderTarget()->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED);
+
+ for (auto const& pos : rPositions)
+ {
+ const basegfx::B2DPoint aDiscretePos(
+ getViewInformation2D().getObjectToViewTransformation() * pos);
+ const double fX(ceil(aDiscretePos.getX()));
+ const double fY(ceil(aDiscretePos.getY()));
+ const D2D1_RECT_F rect = { FLOAT(fX), FLOAT(fY), FLOAT(fX), FLOAT(fY) };
+
+ getRenderTarget()->DrawRectangle(&rect, pColorBrush);
+ }
+
+ getRenderTarget()->SetAntialiasMode(aOldAAMode);
+ bDone = true;
+ }
+
+ if (!bDone)
+ increaseError();
+}
+
+void D2DPixelProcessor2D::processMarkerArrayPrimitive2D(
+ const primitive2d::MarkerArrayPrimitive2D& rMarkerArrayCandidate)
+{
+ const std::vector<basegfx::B2DPoint>& rPositions(rMarkerArrayCandidate.getPositions());
+
+ if (rPositions.empty())
+ {
+ // no geometry, done
+ return;
+ }
+
+ Bitmap rMarker(rMarkerArrayCandidate.getMarker());
+
+ if (rMarker.IsEmpty())
+ {
+ // no marker defined, done
+ return;
+ }
+
+ sal::systools::COMReference<ID2D1Bitmap> pD2DBitmap(
+ getOrCreateB2DBitmap(getRenderTarget(), rMarker));
+ bool bDone(false);
+
+ if (pD2DBitmap)
+ {
+ getRenderTarget()->SetTransform(D2D1::Matrix3x2F::Identity());
+ const Size& rSizePixel(rMarker.GetSizePixel());
+ const tools::Long nMiX((rSizePixel.Width() / 2) + 1);
+ const tools::Long nMiY((rSizePixel.Height() / 2) + 1);
+ const tools::Long nPlX(rSizePixel.Width() - nMiX);
+ const tools::Long nPlY(rSizePixel.Height() - nMiY);
+
+ // draw with non-AA to show unhampered, clear, non-scaled marker
+ const D2D1_ANTIALIAS_MODE aOldAAMode(getRenderTarget()->GetAntialiasMode());
+ getRenderTarget()->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED);
+
+ for (auto const& pos : rPositions)
+ {
+ const basegfx::B2DPoint aDiscretePos(
+ getViewInformation2D().getObjectToViewTransformation() * pos);
+ const double fX(ceil(aDiscretePos.getX()));
+ const double fY(ceil(aDiscretePos.getY()));
+ const D2D1_RECT_F rect
+ = { FLOAT(fX - nMiX), FLOAT(fY - nMiY), FLOAT(fX + nPlX), FLOAT(fY + nPlY) };
+
+ getRenderTarget()->DrawBitmap(pD2DBitmap, &rect);
+ }
+
+ getRenderTarget()->SetAntialiasMode(aOldAAMode);
+ bDone = true;
+ }
+
+ if (!bDone)
+ increaseError();
+}
+
+void D2DPixelProcessor2D::processBackgroundColorPrimitive2D(
+ const primitive2d::BackgroundColorPrimitive2D& rBackgroundColorCandidate)
+{
+ // check for allowed range [0.0 .. 1.0[
+ if (rBackgroundColorCandidate.getTransparency() < 0.0
+ || rBackgroundColorCandidate.getTransparency() >= 1.0)
+ return;
+
+ const D2D1::ColorF aD2DColor(rBackgroundColorCandidate.getBColor().getRed(),
+ rBackgroundColorCandidate.getBColor().getGreen(),
+ rBackgroundColorCandidate.getBColor().getBlue(),
+ 1.0 - rBackgroundColorCandidate.getTransparency());
+
+ getRenderTarget()->Clear(aD2DColor);
+}
+
+void D2DPixelProcessor2D::processModifiedColorPrimitive2D(
+ const primitive2d::ModifiedColorPrimitive2D& rModifiedCandidate)
+{
+ if (!rModifiedCandidate.getChildren().empty())
+ {
+ maBColorModifierStack.push(rModifiedCandidate.getColorModifier());
+ process(rModifiedCandidate.getChildren());
+ maBColorModifierStack.pop();
+ }
+}
+
+void D2DPixelProcessor2D::processTransformPrimitive2D(
+ const primitive2d::TransformPrimitive2D& rTransformCandidate)
+{
+ // remember current transformation and ViewInformation
+ const geometry::ViewInformation2D aLastViewInformation2D(getViewInformation2D());
+
+ // create new transformations for local ViewInformation2D
+ geometry::ViewInformation2D aViewInformation2D(getViewInformation2D());
+ aViewInformation2D.setObjectTransformation(getViewInformation2D().getObjectTransformation()
+ * rTransformCandidate.getTransformation());
+ setViewInformation2D(aViewInformation2D);
+
+ // process content
+ process(rTransformCandidate.getChildren());
+
+ // restore transformations
+ setViewInformation2D(aLastViewInformation2D);
+}
+
+void D2DPixelProcessor2D::processPolygonStrokePrimitive2D(
+ const primitive2d::PolygonStrokePrimitive2D& rPolygonStrokeCandidate)
+{
+ const basegfx::B2DPolygon& rPolygon(rPolygonStrokeCandidate.getB2DPolygon());
+ const attribute::LineAttribute& rLineAttribute(rPolygonStrokeCandidate.getLineAttribute());
+
+ if (!rPolygon.count() || rLineAttribute.getWidth() < 0.0)
+ {
+ // no geometry, done
+ return;
+ }
+
+ // get some values early that might be used for decisions
+ const bool bHairline(0.0 == rLineAttribute.getWidth());
+ const basegfx::B2DHomMatrix& rObjectToView(
+ getViewInformation2D().getObjectToViewTransformation());
+ const double fDiscreteLineWidth(
+ bHairline
+ ? 1.0
+ : (rObjectToView * basegfx::B2DVector(rLineAttribute.getWidth(), 0.0)).getLength());
+
+ // Here for every combination which the system-specific implementation is not
+ // capable of visualizing, use the (for decomposable Primitives always possible)
+ // fallback to the decomposition.
+ if (basegfx::B2DLineJoin::NONE == rLineAttribute.getLineJoin() && fDiscreteLineWidth > 1.5)
+ {
+ // basegfx::B2DLineJoin::NONE is special for our office, no other GraphicSystem
+ // knows that (so far), so fallback to decomposition. This is only needed if
+ // LineJoin will be used, so also check for discrete LineWidth before falling back
+ process(rPolygonStrokeCandidate);
+ return;
+ }
+
+ // This is a method every system-specific implementation of a decomposable Primitive
+ // can use to allow simple optical control of paint implementation:
+ // Create a copy, e.g. change color to 'red' as here and paint before the system
+ // paints it using the decomposition. That way you can - if active - directly
+ // optically compare if the system-specific solution is geometrically identical to
+ // the decomposition (which defines our interpretation that we need to visualize).
+ // Look below in the impl for bRenderDecomposeForCompareInRed to see that in that case
+ // we create a half-transparent paint to better support visual control
+ static bool bRenderDecomposeForCompareInRed(false);
+
+ if (bRenderDecomposeForCompareInRed)
+ {
+ const attribute::LineAttribute aRed(
+ basegfx::BColor(1.0, 0.0, 0.0), rLineAttribute.getWidth(), rLineAttribute.getLineJoin(),
+ rLineAttribute.getLineCap(), rLineAttribute.getMiterMinimumAngle());
+ rtl::Reference<primitive2d::PolygonStrokePrimitive2D> aCopy(
+ new primitive2d::PolygonStrokePrimitive2D(
+ rPolygonStrokeCandidate.getB2DPolygon(), aRed,
+ rPolygonStrokeCandidate.getStrokeAttribute()));
+ process(*aCopy);
+ }
+
+ bool bDone(false);
+ std::shared_ptr<SystemDependentData_ID2D1PathGeometry> pSystemDependentData_ID2D1PathGeometry(
+ getOrCreatePathGeometry(rPolygon, getViewInformation2D()));
+
+ if (pSystemDependentData_ID2D1PathGeometry)
+ {
+ sal::systools::COMReference<ID2D1TransformedGeometry> pTransformedGeometry;
+ const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0);
+ HRESULT hr(aID2D1GlobalFactoryProvider.getID2D1Factory()->CreateTransformedGeometry(
+ pSystemDependentData_ID2D1PathGeometry->getID2D1PathGeometry(),
+ D2D1::Matrix3x2F(rObjectToView.a(), rObjectToView.b(), rObjectToView.c(),
+ rObjectToView.d(), rObjectToView.e() + fAAOffset,
+ rObjectToView.f() + fAAOffset),
+ &pTransformedGeometry));
+
+ if (SUCCEEDED(hr) && pTransformedGeometry)
+ {
+ const basegfx::BColor aLineColor(
+ maBColorModifierStack.getModifiedColor(rLineAttribute.getColor()));
+ D2D1::ColorF aD2DColor(aLineColor.getRed(), aLineColor.getGreen(),
+ aLineColor.getBlue());
+
+ if (bRenderDecomposeForCompareInRed)
+ {
+ aD2DColor.a = 0.5;
+ }
+
+ sal::systools::COMReference<ID2D1SolidColorBrush> pColorBrush;
+ hr = getRenderTarget()->CreateSolidColorBrush(aD2DColor, &pColorBrush);
+
+ if (SUCCEEDED(hr) && pColorBrush)
+ {
+ sal::systools::COMReference<ID2D1StrokeStyle> pStrokeStyle;
+ D2D1_CAP_STYLE aCapStyle(D2D1_CAP_STYLE_FLAT);
+ D2D1_LINE_JOIN aLineJoin(D2D1_LINE_JOIN_MITER);
+ const attribute::StrokeAttribute& rStrokeAttribute(
+ rPolygonStrokeCandidate.getStrokeAttribute());
+ const bool bDashUsed(!rStrokeAttribute.isDefault()
+ && !rStrokeAttribute.getDotDashArray().empty()
+ && 0.0 < rStrokeAttribute.getFullDotDashLen());
+ D2D1_DASH_STYLE aDashStyle(bDashUsed ? D2D1_DASH_STYLE_CUSTOM
+ : D2D1_DASH_STYLE_SOLID);
+ std::vector<float> dashes;
+ float miterLimit(1.0);
+
+ switch (rLineAttribute.getLineCap())
+ {
+ case css::drawing::LineCap_ROUND:
+ aCapStyle = D2D1_CAP_STYLE_ROUND;
+ break;
+ case css::drawing::LineCap_SQUARE:
+ aCapStyle = D2D1_CAP_STYLE_SQUARE;
+ break;
+ default:
+ break;
+ }
+
+ switch (rLineAttribute.getLineJoin())
+ {
+ case basegfx::B2DLineJoin::NONE:
+ break;
+ case basegfx::B2DLineJoin::Bevel:
+ aLineJoin = D2D1_LINE_JOIN_BEVEL;
+ break;
+ case basegfx::B2DLineJoin::Miter:
+ {
+ // for basegfx::B2DLineJoin::Miter there are two problems:
+ // (1) MS uses D2D1_LINE_JOIN_MITER which handles the cut-off when MiterLimit is hit not by
+ // fallback to Bevel, but by cutting miter geometry at the defined distance. That is
+ // nice, but not what we need or is the standard for other graphic systems. Luckily there
+ // is also D2D1_LINE_JOIN_MITER_OR_BEVEL and (after some search) the page
+ // https://learn.microsoft.com/en-us/windows/win32/api/d2d1/ne-d2d1-d2d1_line_join
+ // which gives some explanation, so that is what we need to use here.
+ // (2) Instead of using an angle in radians (15 deg default) MS uses
+ // "miterLimit is relative to 1/2 LineWidth", so a length. After some experimenting
+ // it shows that the (better understandable) angle has to be converted to the length
+ // that a miter prolongation would have at that angle, so use some trigonometry.
+ // Unfortunately there is also some'precision' problem (probably), so I had to
+ // experimentally come to a correction value around 0.9925. Since that seems to
+ // be no obvious numerical value involved somehow (and as long as I find no other
+ // explanation) I will have to use that.
+ // NOTE: To find that correction value I usd that handy bRenderDecomposeForCompareInRed
+ // and changes in debugger - as work tipp
+ // With both done I can use Direct2D for Miter completely - what is good for speed.
+ aLineJoin = D2D1_LINE_JOIN_MITER_OR_BEVEL;
+
+ // snap absolute value of angle in radians to [0.0 .. PI]
+ double fVal(::basegfx::snapToZeroRange(
+ fabs(rLineAttribute.getMiterMinimumAngle()), M_PI));
+
+ // cut at 0.0 and PI since sin would be zero ('endless' miter)
+ const double fSmallValue(M_PI * 0.0000001);
+ fVal = std::max(fSmallValue, fVal);
+ fVal = std::min(M_PI - fSmallValue, fVal);
+
+ // get relative length
+ fVal = 1.0 / sin(fVal);
+
+ // use for miterLimit, we need factor 2.0 (relative to double LineWidth)
+ // and the correction mentioned in (2) above
+ const double fCorrector(2.0 * 0.9925);
+
+ miterLimit = fVal * fCorrector;
+ break;
+ }
+ case basegfx::B2DLineJoin::Round:
+ aLineJoin = D2D1_LINE_JOIN_ROUND;
+ break;
+ default:
+ break;
+ }
+
+ if (bDashUsed)
+ {
+ // dashes need to be discrete and relative to LineWidth
+ for (auto& value : rStrokeAttribute.getDotDashArray())
+ {
+ dashes.push_back(
+ (rObjectToView * basegfx::B2DVector(value, 0.0)).getLength()
+ / fDiscreteLineWidth);
+ }
+ }
+
+ hr = aID2D1GlobalFactoryProvider.getID2D1Factory()->CreateStrokeStyle(
+ D2D1::StrokeStyleProperties(aCapStyle, // startCap
+ aCapStyle, // endCap
+ aCapStyle, // dashCap
+ aLineJoin, // lineJoin
+ miterLimit, // miterLimit
+ aDashStyle, // dashStyle
+ 0.0f), // dashOffset
+ bDashUsed ? dashes.data() : nullptr, bDashUsed ? dashes.size() : 0,
+ &pStrokeStyle);
+
+ if (SUCCEEDED(hr) && pStrokeStyle)
+ {
+ getRenderTarget()->SetTransform(D2D1::Matrix3x2F::Identity());
+ getRenderTarget()->DrawGeometry(
+ pTransformedGeometry, pColorBrush,
+ // TODO: Hairline LineWidth, see comment at processPolygonHairlinePrimitive2D
+ bHairline ? 1.44 : fDiscreteLineWidth, pStrokeStyle);
+ bDone = true;
+ }
+ }
+ }
+ }
+
+ if (!bDone)
+ {
+ // fallback to decomposition
+ process(rPolygonStrokeCandidate);
+ }
+}
+
+void D2DPixelProcessor2D::processLineRectanglePrimitive2D(
+ const primitive2d::LineRectanglePrimitive2D& rLineRectanglePrimitive2D)
+{
+ if (rLineRectanglePrimitive2D.getB2DRange().isEmpty())
+ {
+ // no geometry, done
+ return;
+ }
+
+ const basegfx::BColor aHairlineColor(
+ maBColorModifierStack.getModifiedColor(rLineRectanglePrimitive2D.getBColor()));
+ const D2D1::ColorF aD2DColor(aHairlineColor.getRed(), aHairlineColor.getGreen(),
+ aHairlineColor.getBlue());
+ sal::systools::COMReference<ID2D1SolidColorBrush> pColorBrush;
+ const HRESULT hr(getRenderTarget()->CreateSolidColorBrush(aD2DColor, &pColorBrush));
+ bool bDone(false);
+
+ if (SUCCEEDED(hr) && pColorBrush)
+ {
+ const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0);
+ const basegfx::B2DHomMatrix aLocalTransform(
+ getViewInformation2D().getObjectToViewTransformation());
+ getRenderTarget()->SetTransform(D2D1::Matrix3x2F(
+ aLocalTransform.a(), aLocalTransform.b(), aLocalTransform.c(), aLocalTransform.d(),
+ aLocalTransform.e() + fAAOffset, aLocalTransform.f() + fAAOffset));
+ const basegfx::B2DRange& rRange(rLineRectanglePrimitive2D.getB2DRange());
+ const D2D1_RECT_F rect = { FLOAT(rRange.getMinX()), FLOAT(rRange.getMinY()),
+ FLOAT(rRange.getMaxX()), FLOAT(rRange.getMaxY()) };
+ const double fDiscreteLineWidth(
+ (getViewInformation2D().getInverseObjectToViewTransformation()
+ * basegfx::B2DVector(1.44, 0.0))
+ .getLength());
+
+ getRenderTarget()->DrawRectangle(&rect, pColorBrush, fDiscreteLineWidth);
+ bDone = true;
+ }
+
+ if (!bDone)
+ increaseError();
+}
+
+void D2DPixelProcessor2D::processFilledRectanglePrimitive2D(
+ const primitive2d::FilledRectanglePrimitive2D& rFilledRectanglePrimitive2D)
+{
+ if (rFilledRectanglePrimitive2D.getB2DRange().isEmpty())
+ {
+ // no geometry, done
+ return;
+ }
+
+ const basegfx::BColor aFillColor(
+ maBColorModifierStack.getModifiedColor(rFilledRectanglePrimitive2D.getBColor()));
+ const D2D1::ColorF aD2DColor(aFillColor.getRed(), aFillColor.getGreen(), aFillColor.getBlue());
+ sal::systools::COMReference<ID2D1SolidColorBrush> pColorBrush;
+ const HRESULT hr(getRenderTarget()->CreateSolidColorBrush(aD2DColor, &pColorBrush));
+ bool bDone(false);
+
+ if (SUCCEEDED(hr) && pColorBrush)
+ {
+ const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0);
+ const basegfx::B2DHomMatrix aLocalTransform(
+ getViewInformation2D().getObjectToViewTransformation());
+ getRenderTarget()->SetTransform(D2D1::Matrix3x2F(
+ aLocalTransform.a(), aLocalTransform.b(), aLocalTransform.c(), aLocalTransform.d(),
+ aLocalTransform.e() + fAAOffset, aLocalTransform.f() + fAAOffset));
+ const basegfx::B2DRange& rRange(rFilledRectanglePrimitive2D.getB2DRange());
+ const D2D1_RECT_F rect = { FLOAT(rRange.getMinX()), FLOAT(rRange.getMinY()),
+ FLOAT(rRange.getMaxX()), FLOAT(rRange.getMaxY()) };
+
+ getRenderTarget()->FillRectangle(&rect, pColorBrush);
+ bDone = true;
+ }
+
+ if (!bDone)
+ increaseError();
+}
+
+void D2DPixelProcessor2D::processSingleLinePrimitive2D(
+ const primitive2d::SingleLinePrimitive2D& rSingleLinePrimitive2D)
+{
+ const basegfx::BColor aLineColor(
+ maBColorModifierStack.getModifiedColor(rSingleLinePrimitive2D.getBColor()));
+ const D2D1::ColorF aD2DColor(aLineColor.getRed(), aLineColor.getGreen(), aLineColor.getBlue());
+ sal::systools::COMReference<ID2D1SolidColorBrush> pColorBrush;
+ const HRESULT hr(getRenderTarget()->CreateSolidColorBrush(aD2DColor, &pColorBrush));
+ bool bDone(false);
+
+ if (SUCCEEDED(hr) && pColorBrush)
+ {
+ const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0);
+ basegfx::B2DHomMatrix aLocalTransform(
+ getViewInformation2D().getObjectToViewTransformation());
+ const basegfx::B2DPoint aStart(aLocalTransform * rSingleLinePrimitive2D.getStart());
+ const basegfx::B2DPoint aEnd(aLocalTransform * rSingleLinePrimitive2D.getEnd());
+
+ getRenderTarget()->SetTransform(D2D1::Matrix3x2F::Identity());
+ const D2D1_POINT_2F aD2D1Start
+ = { FLOAT(aStart.getX() + fAAOffset), FLOAT(aStart.getY() + fAAOffset) };
+ const D2D1_POINT_2F aD2D1End
+ = { FLOAT(aEnd.getX() + fAAOffset), FLOAT(aEnd.getY() + fAAOffset) };
+
+ getRenderTarget()->DrawLine(aD2D1Start, aD2D1End, pColorBrush, 1.44f);
+ bDone = true;
+ }
+
+ if (!bDone)
+ increaseError();
+}
+
+void D2DPixelProcessor2D::processFillGraphicPrimitive2D(
+ const primitive2d::FillGraphicPrimitive2D& rFillGraphicPrimitive2D)
+{
+ if (rFillGraphicPrimitive2D.getTransparency() < 0.0
+ || rFillGraphicPrimitive2D.getTransparency() > 1.0)
+ {
+ // invalid transparence, done
+ return;
+ }
+
+ if (rFillGraphicPrimitive2D.hasTransparency())
+ {
+ // cannot handle yet, use decomposition
+ process(rFillGraphicPrimitive2D);
+ return;
+ }
+
+ Bitmap aPreparedBitmap;
+ basegfx::B2DRange aFillUnitRange(rFillGraphicPrimitive2D.getFillGraphic().getGraphicRange());
+ constexpr double fBigDiscreteArea(300.0 * 300.0);
+
+ // use tooling to do various checks and prepare tiled rendering, see
+ // description of method, parameters and return value there
+ if (!prepareBitmapForDirectRender(rFillGraphicPrimitive2D, getViewInformation2D(),
+ aPreparedBitmap, aFillUnitRange, fBigDiscreteArea))
+ {
+ // no output needed, done
+ return;
+ }
+
+ if (aPreparedBitmap.IsEmpty())
+ {
+ // output needed and Bitmap data empty, so no bitmap data based
+ // tiled rendering is suggested. Use fallback for paint (decomposition)
+ process(rFillGraphicPrimitive2D);
+ return;
+ }
+
+ // render tiled using the prepared Bitmap data
+ if (maBColorModifierStack.count())
+ {
+ // need to apply ColorModifier to Bitmap data
+ aPreparedBitmap = aPreparedBitmap.Modify(maBColorModifierStack);
+
+ if (aPreparedBitmap.IsEmpty())
+ {
+ // color gets completely replaced, get it (any input works)
+ const basegfx::BColor aModifiedColor(
+ maBColorModifierStack.getModifiedColor(basegfx::BColor()));
+
+ // use unit geometry as fallback object geometry. Do *not*
+ // transform, the below used method will use the already
+ // correctly initialized local ViewInformation
+ basegfx::B2DPolygon aPolygon(basegfx::utils::createUnitPolygon());
+
+ // what we still need to apply is the object transform from the
+ // local primitive, that is not part of DisplayInfo yet
+ aPolygon.transform(rFillGraphicPrimitive2D.getTransformation());
+
+ rtl::Reference<primitive2d::PolyPolygonColorPrimitive2D> aTemp(
+ new primitive2d::PolyPolygonColorPrimitive2D(basegfx::B2DPolyPolygon(aPolygon),
+ aModifiedColor));
+
+ // draw as colored Polygon, done
+ processPolyPolygonColorPrimitive2D(*aTemp);
+ return;
+ }
+ }
+
+ bool bDone(false);
+ sal::systools::COMReference<ID2D1Bitmap> pD2DBitmap(
+ getOrCreateB2DBitmap(getRenderTarget(), aPreparedBitmap));
+
+ if (pD2DBitmap)
+ {
+ sal::systools::COMReference<ID2D1BitmapBrush> pBitmapBrush;
+ const HRESULT hr(getRenderTarget()->CreateBitmapBrush(pD2DBitmap, &pBitmapBrush));
+
+ if (SUCCEEDED(hr) && pBitmapBrush)
+ {
+ // set extended to repeat/wrap AKA tiling
+ pBitmapBrush->SetExtendModeX(D2D1_EXTEND_MODE_WRAP);
+ pBitmapBrush->SetExtendModeY(D2D1_EXTEND_MODE_WRAP);
+
+ // set interpolation mode
+ // NOTE: This uses D2D1_BITMAP_INTERPOLATION_MODE, but there seem to be
+ // advanced modes when using D2D1_INTERPOLATION_MODE, but that needs
+ // D2D1_BITMAP_BRUSH_PROPERTIES1 and ID2D1BitmapBrush1
+ sal::systools::COMReference<ID2D1BitmapBrush1> pBrush1;
+ pBitmapBrush->QueryInterface(__uuidof(ID2D1BitmapBrush1),
+ reinterpret_cast<void**>(&pBrush1));
+
+ if (pBrush1)
+ {
+ pBrush1->SetInterpolationMode1(D2D1_INTERPOLATION_MODE_MULTI_SAMPLE_LINEAR);
+ }
+ else
+ {
+ pBitmapBrush->SetInterpolationMode(D2D1_BITMAP_INTERPOLATION_MODE_LINEAR);
+ }
+
+ // set BitmapBrush transformation relative to it's PixelSize and
+ // the used FillUnitRange. Since we use unit coordinates here this
+ // is pretty simple
+ const D2D1_SIZE_U aBMSPixel(pD2DBitmap->GetPixelSize());
+ const double fScaleX((aFillUnitRange.getMaxX() - aFillUnitRange.getMinX())
+ / aBMSPixel.width);
+ const double fScaleY((aFillUnitRange.getMaxY() - aFillUnitRange.getMinY())
+ / aBMSPixel.height);
+ const D2D1_MATRIX_3X2_F aBTrans(D2D1::Matrix3x2F(
+ fScaleX, 0.0, 0.0, fScaleY, aFillUnitRange.getMinX(), aFillUnitRange.getMinY()));
+ pBitmapBrush->SetTransform(&aBTrans);
+
+ // set transform to ObjectToWorld to be able to paint in unit coordinates, so
+ // evtl. shear/rotate in that transform is used and does not influence the
+ // orthogonal and unit-oriented brush handling
+ const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0);
+ const basegfx::B2DHomMatrix aLocalTransform(
+ getViewInformation2D().getObjectToViewTransformation()
+ * rFillGraphicPrimitive2D.getTransformation());
+ getRenderTarget()->SetTransform(D2D1::Matrix3x2F(
+ aLocalTransform.a(), aLocalTransform.b(), aLocalTransform.c(), aLocalTransform.d(),
+ aLocalTransform.e() + fAAOffset, aLocalTransform.f() + fAAOffset));
+
+ // use unit rectangle, transformation is already set to include ObjectToWorld
+ const D2D1_RECT_F rect = { FLOAT(0.0), FLOAT(0.0), FLOAT(1.0), FLOAT(1.0) };
+
+ // draw as unit rectangle as brush filled rectangle
+ getRenderTarget()->FillRectangle(&rect, pBitmapBrush);
+ bDone = true;
+ }
+ }
+
+ if (!bDone)
+ increaseError();
+}
+
+void D2DPixelProcessor2D::processFillGradientPrimitive2D(
+ const primitive2d::FillGradientPrimitive2D& rFillGradientPrimitive2D)
+{
+ if (rFillGradientPrimitive2D.hasAlphaGradient() || rFillGradientPrimitive2D.hasTransparency())
+ {
+ // SDPR: As long as direct alpha is not supported by this
+ // renderer we need to work on the decomposition, so call it
+ process(rFillGradientPrimitive2D);
+ return;
+ }
+
+ // draw all-covering initial BG polygon 1st
+ bool bDone(drawPolyPolygonColorTransformed(
+ basegfx::B2DHomMatrix(),
+ basegfx::B2DPolyPolygon(
+ basegfx::utils::createPolygonFromRect(rFillGradientPrimitive2D.getOutputRange())),
+ rFillGradientPrimitive2D.getOuterColor()));
+
+ if (bDone)
+ {
+ const basegfx::B2DPolyPolygon aForm(rFillGradientPrimitive2D.getUnitPolygon());
+
+ // paint solid fill steps by providing callback as lambda
+ auto aCallback([&aForm, &bDone, this](const basegfx::B2DHomMatrix& rMatrix,
+ const basegfx::BColor& rColor) {
+ if (bDone)
+ {
+ bDone = drawPolyPolygonColorTransformed(rMatrix, aForm, rColor);
+ }
+ });
+
+ // call value generator to trigger callbacks
+ rFillGradientPrimitive2D.generateMatricesAndColors(aCallback);
+ }
+
+ if (!bDone)
+ increaseError();
+}
+
+void D2DPixelProcessor2D::processInvertPrimitive2D(
+ const primitive2d::InvertPrimitive2D& rInvertPrimitive2D)
+{
+ if (rInvertPrimitive2D.getChildren().empty())
+ {
+ // no content, done
+ return;
+ }
+
+ // Try if we can use ID2D1DeviceContext/d2d1_1 by querying for interface.
+ // Only with ID2D1DeviceContext we can use ::DrawImage which supports
+ // D2D1_COMPOSITE_MODE_XOR
+ sal::systools::COMReference<ID2D1DeviceContext> pID2D1DeviceContext;
+ getRenderTarget()->QueryInterface(__uuidof(ID2D1DeviceContext),
+ reinterpret_cast<void**>(&pID2D1DeviceContext));
+
+ if (!pID2D1DeviceContext)
+ {
+ // TODO: We have *no* ID2D1DeviceContext and cannot use D2D1_COMPOSITE_MODE_XOR,
+ // so there is currently no (simple?) way to solve this, there is no 'Invert' method.
+ // It may be possible to convert to a WICBitmap (gets read access) and do the invert
+ // there, but that needs experimenting and is probably not performant - but doable.
+ increaseError();
+ return;
+ }
+
+ sal::systools::COMReference<ID2D1Bitmap> pInBetweenResult;
+ basegfx::B2DRange aDiscreteVisibleRange;
+
+ // create in-between result in discrete coordinates, clipped against visible
+ // part of ViewInformation (if available)
+ if (!createBitmapSubContent(pInBetweenResult, aDiscreteVisibleRange,
+ rInvertPrimitive2D.getChildren(), getViewInformation2D(),
+ getRenderTarget()))
+ {
+ // return of false means no display needed, return
+ return;
+ }
+
+ bool bDone(false);
+
+ if (pInBetweenResult)
+ {
+ getRenderTarget()->SetTransform(D2D1::Matrix3x2F::Identity());
+ const D2D1_POINT_2F aTopLeft = { FLOAT(floor(aDiscreteVisibleRange.getMinX())),
+ FLOAT(floor(aDiscreteVisibleRange.getMinY())) };
+
+ pID2D1DeviceContext->DrawImage(pInBetweenResult, aTopLeft, D2D1_INTERPOLATION_MODE_LINEAR,
+ D2D1_COMPOSITE_MODE_XOR);
+ bDone = true;
+ }
+
+ if (!bDone)
+ increaseError();
+}
+
+void D2DPixelProcessor2D::processBasePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate)
+{
+ if (0 == mnRecursionCounter)
+ getRenderTarget()->BeginDraw();
+ mnRecursionCounter++;
+
+ switch (rCandidate.getPrimitive2DID())
+ {
+ // Geometry that *has* to be processed
+ //
+ // These Primitives have *no* decompose implementation, so these are the basic ones
+ // Just four to go to make a processor work completely (but not optimized)
+ // NOTE: This *could* theoretically be reduced to one and all could implement
+ // a decompose to pixel data, but that seemed not to make sense to me when
+ // I designed this. Thus these four are the lowest-level best representation
+ // from my POV
+ case PRIMITIVE2D_ID_BITMAPPRIMITIVE2D:
+ {
+ processBitmapPrimitive2D(
+ static_cast<const primitive2d::BitmapPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_POINTARRAYPRIMITIVE2D:
+ {
+ processPointArrayPrimitive2D(
+ static_cast<const primitive2d::PointArrayPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D:
+ {
+ processPolygonHairlinePrimitive2D(
+ static_cast<const primitive2d::PolygonHairlinePrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D:
+ {
+ processPolyPolygonColorPrimitive2D(
+ static_cast<const primitive2d::PolyPolygonColorPrimitive2D&>(rCandidate));
+ break;
+ }
+
+ // Embedding/groups that *have* to be processed
+ //
+ // These represent qualifiers for freely defined content, e.g. making
+ // any combination of primitives freely transformed or transparent
+ // NOTE: PRIMITIVE2D_ID_MODIFIEDCOLORPRIMITIVE2D and
+ // PRIMITIVE2D_ID_TRANSFORMPRIMITIVE2D are pretty much default-
+ // implementations that can and are re-used in all processors.
+ // So - with these and PRIMITIVE2D_ID_INVERTPRIMITIVE2D marked to
+ // be removed in the future - just three really to be implemented
+ // locally specifically
+ case PRIMITIVE2D_ID_TRANSPARENCEPRIMITIVE2D:
+ {
+ processTransparencePrimitive2D(
+ static_cast<const primitive2d::TransparencePrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_INVERTPRIMITIVE2D:
+ {
+ // We urgently should get rid of XOR paint, modern graphic systems
+ // allow no read access to the pixel targets, but that's naturally
+ // a precondition for XOR. While we can do that for the office's
+ // visualization, we can in principle *not* fully avoid getting
+ // stuff that needs/defines XOR paint, e.g. EMF/WMF imports, so
+ // we *have* to support it (for now - sigh)...
+ processInvertPrimitive2D(
+ static_cast<const primitive2d::InvertPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_MASKPRIMITIVE2D:
+ {
+ processMaskPrimitive2D(static_cast<const primitive2d::MaskPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_MODIFIEDCOLORPRIMITIVE2D:
+ {
+ processModifiedColorPrimitive2D(
+ static_cast<const primitive2d::ModifiedColorPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_TRANSFORMPRIMITIVE2D:
+ {
+ processTransformPrimitive2D(
+ static_cast<const primitive2d::TransformPrimitive2D&>(rCandidate));
+ break;
+ }
+
+ // Geometry that *may* be processed due to being able to do it better
+ // then using the decomposition.
+ // NOTE: In these implementations you could always call what the default
+ // case below does - call process(rCandidate) to use the decomposition.
+ // So these impls should only do something if they can do it better/
+ // faster that the decomposition. So some of them check if they could
+ // - and if not - use exactly that.
+ case PRIMITIVE2D_ID_UNIFIEDTRANSPARENCEPRIMITIVE2D:
+ {
+ // transparence with a fixed alpha for all content, can be done
+ // significally faster
+ processUnifiedTransparencePrimitive2D(
+ static_cast<const primitive2d::UnifiedTransparencePrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_MARKERARRAYPRIMITIVE2D:
+ {
+ // can be done simpler and without AA better locally
+ processMarkerArrayPrimitive2D(
+ static_cast<const primitive2d::MarkerArrayPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_BACKGROUNDCOLORPRIMITIVE2D:
+ {
+ // reset to a color, can be done more effectively locally, would
+ // else decompose to a polygon fill
+ processBackgroundColorPrimitive2D(
+ static_cast<const primitive2d::BackgroundColorPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_POLYGONSTROKEPRIMITIVE2D:
+ {
+ // fat and stroked lines - much better doable locally, would decompose
+ // to the full line geometry creation (tessellation)
+ processPolygonStrokePrimitive2D(
+ static_cast<const primitive2d::PolygonStrokePrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_LINERECTANGLEPRIMITIVE2D:
+ {
+ // simple primitive to support future fast callbacks from OutputDevice
+ // (see 'Example POC' in Gerrit), decomposes to polygon primitive
+ processLineRectanglePrimitive2D(
+ static_cast<const primitive2d::LineRectanglePrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_FILLEDRECTANGLEPRIMITIVE2D:
+ {
+ // simple primitive to support future fast callbacks from OutputDevice
+ // (see 'Example POC' in Gerrit), decomposes to filled polygon primitive
+ processFilledRectanglePrimitive2D(
+ static_cast<const primitive2d::FilledRectanglePrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_SINGLELINEPRIMITIVE2D:
+ {
+ // simple primitive to support future fast callbacks from OutputDevice
+ // (see 'Example POC' in Gerrit), decomposes to polygon primitive
+ processSingleLinePrimitive2D(
+ static_cast<const primitive2d::SingleLinePrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_FILLGRAPHICPRIMITIVE2D:
+ {
+ processFillGraphicPrimitive2D(
+ static_cast<const primitive2d::FillGraphicPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_FILLGRADIENTPRIMITIVE2D:
+ {
+ processFillGradientPrimitive2D(
+ static_cast<const primitive2d::FillGradientPrimitive2D&>(rCandidate));
+ break;
+ }
+
+ // continue with decompose as fallback
+ default:
+ {
+ SAL_INFO("drawinglayer", "default case for " << drawinglayer::primitive2d::idToString(
+ rCandidate.getPrimitive2DID()));
+ // process recursively
+ process(rCandidate);
+ break;
+ }
+ }
+
+ mnRecursionCounter--;
+ if (0 == mnRecursionCounter)
+ getRenderTarget()->EndDraw();
+}
+} // end of namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */