office-gobmx/drawinglayer/source/processor2d/d2dpixelprocessor2d.cxx
Armin Le Grand (Collabora) 4461173a4d CairoSDPR: Support direct transparency for gradients
FillGradientPrimitive2D/ and PolyPolygonGradientPrimitive2D
now support both alphas, a gradientAlpha and a unified one.
This allows a processor to directly handle a gradient with
a unifiedAlpha if he wants. Adapted other places accordingly.

NOTE: In VclMetafileProcessor2D handling of the primitive
PolyPolygonGradientPrimitive2D has to do a local compromize,
see comment there.

Change-Id: I536f4935dafde0369f768dbd281d547b7bb08eb4
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/171052
Reviewed-by: Armin Le Grand <Armin.Le.Grand@me.com>
Tested-by: Jenkins
2024-07-26 10:56:33 +02:00

2214 lines
88 KiB
C++

/* -*- 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/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;
const std::shared_ptr<SalBitmap> maAssociatedAlpha;
public:
SystemDependentData_ID2D1Bitmap(sal::systools::COMReference<ID2D1Bitmap>& rD2DBitmap,
const std::shared_ptr<SalBitmap>& rAssociatedAlpha)
: basegfx::SystemDependentData(Application::GetSystemDependentDataManager(),
basegfx::SDD_Type::SDDType_ID2D1Bitmap)
, mpD2DBitmap(rD2DBitmap)
, maAssociatedAlpha(rAssociatedAlpha)
{
}
const sal::systools::COMReference<ID2D1Bitmap>& getID2D1Bitmap() const { return mpD2DBitmap; }
const std::shared_ptr<SalBitmap>& getAssociatedAlpha() const { return maAssociatedAlpha; }
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 BitmapEx& rBitmapEx)
{
const Size& rSizePixel(rBitmapEx.GetSizePixel());
const bool bAlpha(rBitmapEx.IsAlpha());
const sal_uInt32 nPixelCount(rSizePixel.Width() * rSizePixel.Height());
std::unique_ptr<sal_uInt32[]> aData(new sal_uInt32[nPixelCount]);
sal_uInt32* pTarget = aData.get();
if (bAlpha)
{
Bitmap aSrcAlpha(rBitmapEx.GetAlphaMask().GetBitmap());
BitmapScopedReadAccess pReadAccess(rBitmapEx.GetBitmap());
BitmapScopedReadAccess pAlphaReadAccess(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));
const BitmapColor aAlpha(pAlphaReadAccess->GetColor(y, x));
const sal_uInt16 nAlpha(aAlpha.GetRed());
*pTarget++ = sal_uInt32(BitmapColor(
ColorAlpha, sal_uInt8((sal_uInt16(aColor.GetRed()) * nAlpha) >> 8),
sal_uInt8((sal_uInt16(aColor.GetGreen()) * nAlpha) >> 8),
sal_uInt8((sal_uInt16(aColor.GetBlue()) * nAlpha) >> 8), aAlpha.GetRed()));
}
}
}
else
{
BitmapScopedReadAccess pReadAccess(const_cast<Bitmap&>(rBitmapEx.GetBitmap()));
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 BitmapEx& rBitmapEx)
{
const basegfx::SystemDependentDataHolder* pHolder(
rBitmapEx.GetBitmap().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));
// check data validity for associated Alpha
if (pSystemDependentData_ID2D1Bitmap
&& pSystemDependentData_ID2D1Bitmap->getAssociatedAlpha()
!= rBitmapEx.GetAlphaMask().GetBitmap().ImplGetSalBitmap())
{
// AssociatedAlpha did change, data invalid
pSystemDependentData_ID2D1Bitmap.reset();
}
}
if (!pSystemDependentData_ID2D1Bitmap)
{
// have to create newly
sal::systools::COMReference<ID2D1Bitmap> pID2D1Bitmap(createB2DBitmap(rBitmapEx));
if (pID2D1Bitmap)
{
// creation worked, create SystemDependentData_ID2D1Bitmap
pSystemDependentData_ID2D1Bitmap = std::make_shared<SystemDependentData_ID2D1Bitmap>(
pID2D1Bitmap, rBitmapEx.GetAlphaMask().GetBitmap().ImplGetSalBitmap());
// 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);
updateViewInformation(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;
}
}
BitmapEx aBitmapEx(rBitmapCandidate.getBitmap());
if (aBitmapEx.IsEmpty() || aBitmapEx.GetSizePixel().IsEmpty())
{
// no pixel data, done
return;
}
if (maBColorModifierStack.count())
{
// need to apply ColorModifier to Bitmap data
aBitmapEx = aBitmapEx.ModifyBitmapEx(maBColorModifierStack);
if (aBitmapEx.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(), aBitmapEx));
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& rBitmapExSizePixel(aAlpha.GetSizePixel());
if (static_cast<sal_uInt32>(rBitmapExSizePixel.Width()) != nDiscreteClippedWidth
|| static_cast<sal_uInt32>(rBitmapExSizePixel.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>(rBitmapExSizePixel.Width())
/ static_cast<double>(nDiscreteClippedWidth));
const double fScaleY(static_cast<double>(rBitmapExSizePixel.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;
}
const BitmapEx& 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());
updateViewInformation(aViewInformation2D);
// process content
process(rTransformCandidate.getChildren());
// restore transformations
updateViewInformation(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;
}
BitmapEx 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.ModifyBitmapEx(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: */