diff options
Diffstat (limited to 'drawinglayer/source/processor2d/d2dpixelprocessor2d.cxx')
-rw-r--r-- | drawinglayer/source/processor2d/d2dpixelprocessor2d.cxx | 2177 |
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: */ |