diff options
Diffstat (limited to 'gfx/2d/PathD2D.cpp')
-rw-r--r-- | gfx/2d/PathD2D.cpp | 523 |
1 files changed, 523 insertions, 0 deletions
diff --git a/gfx/2d/PathD2D.cpp b/gfx/2d/PathD2D.cpp new file mode 100644 index 000000000..b10e456e1 --- /dev/null +++ b/gfx/2d/PathD2D.cpp @@ -0,0 +1,523 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * 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/. */ + +#include "PathD2D.h" +#include "HelpersD2D.h" +#include <math.h> +#include "DrawTargetD2D1.h" +#include "Logging.h" + +namespace mozilla { +namespace gfx { + +// This class exists as a wrapper for ID2D1SimplifiedGeometry sink, it allows +// a geometry to be duplicated into a geometry sink, while removing the final +// figure end and thus allowing a figure that was implicitly closed to be +// continued. +class OpeningGeometrySink : public ID2D1SimplifiedGeometrySink +{ +public: + OpeningGeometrySink(ID2D1SimplifiedGeometrySink *aSink) + : mSink(aSink) + , mNeedsFigureEnded(false) + { + } + + HRESULT STDMETHODCALLTYPE QueryInterface(const IID &aIID, void **aPtr) + { + if (!aPtr) { + return E_POINTER; + } + + if (aIID == IID_IUnknown) { + *aPtr = static_cast<IUnknown*>(this); + return S_OK; + } else if (aIID == IID_ID2D1SimplifiedGeometrySink) { + *aPtr = static_cast<ID2D1SimplifiedGeometrySink*>(this); + return S_OK; + } + + return E_NOINTERFACE; + } + + ULONG STDMETHODCALLTYPE AddRef() + { + return 1; + } + + ULONG STDMETHODCALLTYPE Release() + { + return 1; + } + + // We ignore SetFillMode, the copier will decide. + STDMETHOD_(void, SetFillMode)(D2D1_FILL_MODE aMode) + { EnsureFigureEnded(); return; } + STDMETHOD_(void, BeginFigure)(D2D1_POINT_2F aPoint, D2D1_FIGURE_BEGIN aBegin) + { EnsureFigureEnded(); return mSink->BeginFigure(aPoint, aBegin); } + STDMETHOD_(void, AddLines)(const D2D1_POINT_2F *aLines, UINT aCount) + { EnsureFigureEnded(); return mSink->AddLines(aLines, aCount); } + STDMETHOD_(void, AddBeziers)(const D2D1_BEZIER_SEGMENT *aSegments, UINT aCount) + { EnsureFigureEnded(); return mSink->AddBeziers(aSegments, aCount); } + STDMETHOD(Close)() + { /* Should never be called! */ return S_OK; } + STDMETHOD_(void, SetSegmentFlags)(D2D1_PATH_SEGMENT aFlags) + { return mSink->SetSegmentFlags(aFlags); } + + // This function is special - it's the reason this class exists. + // It needs to intercept the very last endfigure. So that a user can + // continue writing to this sink as if they never stopped. + STDMETHOD_(void, EndFigure)(D2D1_FIGURE_END aEnd) + { + if (aEnd == D2D1_FIGURE_END_CLOSED) { + return mSink->EndFigure(aEnd); + } else { + mNeedsFigureEnded = true; + } + } +private: + void EnsureFigureEnded() + { + if (mNeedsFigureEnded) { + mSink->EndFigure(D2D1_FIGURE_END_OPEN); + mNeedsFigureEnded = false; + } + } + + ID2D1SimplifiedGeometrySink *mSink; + bool mNeedsFigureEnded; +}; + +class MOZ_STACK_CLASS AutoRestoreFP +{ +public: + AutoRestoreFP() + { + // save the current floating point control word + _controlfp_s(&savedFPSetting, 0, 0); + UINT unused; + // set the floating point control word to its default value + _controlfp_s(&unused, _CW_DEFAULT, MCW_PC); + } + ~AutoRestoreFP() + { + UINT unused; + // restore the saved floating point control word + _controlfp_s(&unused, savedFPSetting, MCW_PC); + } +private: + UINT savedFPSetting; +}; + +// Note that overrides of ID2D1SimplifiedGeometrySink methods in this class may +// get called from D2D with nonstandard floating point settings (see comments in +// bug 1134549) - use AutoRestoreFP to reset the floating point control word to +// what we expect +class StreamingGeometrySink : public ID2D1SimplifiedGeometrySink +{ +public: + StreamingGeometrySink(PathSink *aSink) + : mSink(aSink) + { + } + + HRESULT STDMETHODCALLTYPE QueryInterface(const IID &aIID, void **aPtr) + { + if (!aPtr) { + return E_POINTER; + } + + if (aIID == IID_IUnknown) { + *aPtr = static_cast<IUnknown*>(this); + return S_OK; + } else if (aIID == IID_ID2D1SimplifiedGeometrySink) { + *aPtr = static_cast<ID2D1SimplifiedGeometrySink*>(this); + return S_OK; + } + + return E_NOINTERFACE; + } + + ULONG STDMETHODCALLTYPE AddRef() + { + return 1; + } + + ULONG STDMETHODCALLTYPE Release() + { + return 1; + } + + // We ignore SetFillMode, this depends on the destination sink. + STDMETHOD_(void, SetFillMode)(D2D1_FILL_MODE aMode) + { return; } + STDMETHOD_(void, BeginFigure)(D2D1_POINT_2F aPoint, D2D1_FIGURE_BEGIN aBegin) + { + AutoRestoreFP resetFloatingPoint; + mSink->MoveTo(ToPoint(aPoint)); + } + STDMETHOD_(void, AddLines)(const D2D1_POINT_2F *aLines, UINT aCount) + { + AutoRestoreFP resetFloatingPoint; + for (UINT i = 0; i < aCount; i++) { mSink->LineTo(ToPoint(aLines[i])); } + } + STDMETHOD_(void, AddBeziers)(const D2D1_BEZIER_SEGMENT *aSegments, UINT aCount) + { + AutoRestoreFP resetFloatingPoint; + for (UINT i = 0; i < aCount; i++) { + mSink->BezierTo(ToPoint(aSegments[i].point1), ToPoint(aSegments[i].point2), ToPoint(aSegments[i].point3)); + } + } + STDMETHOD(Close)() + { /* Should never be called! */ return S_OK; } + STDMETHOD_(void, SetSegmentFlags)(D2D1_PATH_SEGMENT aFlags) + { /* Should never be called! */ } + + STDMETHOD_(void, EndFigure)(D2D1_FIGURE_END aEnd) + { + AutoRestoreFP resetFloatingPoint; + if (aEnd == D2D1_FIGURE_END_CLOSED) { + return mSink->Close(); + } + } +private: + + PathSink *mSink; +}; + +PathBuilderD2D::~PathBuilderD2D() +{ +} + +void +PathBuilderD2D::MoveTo(const Point &aPoint) +{ + if (mFigureActive) { + mSink->EndFigure(D2D1_FIGURE_END_OPEN); + mFigureActive = false; + } + EnsureActive(aPoint); + mCurrentPoint = aPoint; +} + +void +PathBuilderD2D::LineTo(const Point &aPoint) +{ + EnsureActive(aPoint); + mSink->AddLine(D2DPoint(aPoint)); + + mCurrentPoint = aPoint; +} + +void +PathBuilderD2D::BezierTo(const Point &aCP1, + const Point &aCP2, + const Point &aCP3) + { + EnsureActive(aCP1); + mSink->AddBezier(D2D1::BezierSegment(D2DPoint(aCP1), + D2DPoint(aCP2), + D2DPoint(aCP3))); + + mCurrentPoint = aCP3; +} + +void +PathBuilderD2D::QuadraticBezierTo(const Point &aCP1, + const Point &aCP2) +{ + EnsureActive(aCP1); + mSink->AddQuadraticBezier(D2D1::QuadraticBezierSegment(D2DPoint(aCP1), + D2DPoint(aCP2))); + + mCurrentPoint = aCP2; +} + +void +PathBuilderD2D::Close() +{ + if (mFigureActive) { + mSink->EndFigure(D2D1_FIGURE_END_CLOSED); + + mFigureActive = false; + + EnsureActive(mBeginPoint); + } +} + +void +PathBuilderD2D::Arc(const Point &aOrigin, Float aRadius, Float aStartAngle, + Float aEndAngle, bool aAntiClockwise) +{ + MOZ_ASSERT(aRadius >= 0); + + if (aAntiClockwise && aStartAngle < aEndAngle) { + // D2D does things a little differently, and draws the arc by specifying an + // beginning and an end point. This means the circle will be the wrong way + // around if the start angle is smaller than the end angle. It might seem + // tempting to invert aAntiClockwise but that would change the sweeping + // direction of the arc so instead we exchange start/begin. + Float oldStart = aStartAngle; + aStartAngle = aEndAngle; + aEndAngle = oldStart; + } + + // XXX - Workaround for now, D2D does not appear to do the desired thing when + // the angle sweeps a complete circle. + bool fullCircle = false; + if (aEndAngle - aStartAngle >= 2 * M_PI) { + fullCircle = true; + aEndAngle = Float(aStartAngle + M_PI * 1.9999); + } else if (aStartAngle - aEndAngle >= 2 * M_PI) { + fullCircle = true; + aStartAngle = Float(aEndAngle + M_PI * 1.9999); + } + + Point startPoint; + startPoint.x = aOrigin.x + aRadius * cos(aStartAngle); + startPoint.y = aOrigin.y + aRadius * sin(aStartAngle); + + if (!mFigureActive) { + EnsureActive(startPoint); + } else { + mSink->AddLine(D2DPoint(startPoint)); + } + + Point endPoint; + endPoint.x = aOrigin.x + aRadius * cosf(aEndAngle); + endPoint.y = aOrigin.y + aRadius * sinf(aEndAngle); + + D2D1_ARC_SIZE arcSize = D2D1_ARC_SIZE_SMALL; + D2D1_SWEEP_DIRECTION direction = + aAntiClockwise ? D2D1_SWEEP_DIRECTION_COUNTER_CLOCKWISE : + D2D1_SWEEP_DIRECTION_CLOCKWISE; + + // if startPoint and endPoint of our circle are too close there are D2D issues + // with drawing the circle as a single arc + const Float kEpsilon = 1e-5f; + if (!fullCircle || + (std::abs(startPoint.x - endPoint.x) + + std::abs(startPoint.y - endPoint.y) > kEpsilon)) { + + if (aAntiClockwise) { + if (aStartAngle - aEndAngle > M_PI) { + arcSize = D2D1_ARC_SIZE_LARGE; + } + } else { + if (aEndAngle - aStartAngle > M_PI) { + arcSize = D2D1_ARC_SIZE_LARGE; + } + } + + mSink->AddArc(D2D1::ArcSegment(D2DPoint(endPoint), + D2D1::SizeF(aRadius, aRadius), + 0.0f, + direction, + arcSize)); + } + else { + // our first workaround attempt didn't work, so instead draw the circle as + // two half-circles + Float midAngle = aEndAngle > aStartAngle ? + Float(aStartAngle + M_PI) : Float(aEndAngle + M_PI); + Point midPoint; + midPoint.x = aOrigin.x + aRadius * cosf(midAngle); + midPoint.y = aOrigin.y + aRadius * sinf(midAngle); + + mSink->AddArc(D2D1::ArcSegment(D2DPoint(midPoint), + D2D1::SizeF(aRadius, aRadius), + 0.0f, + direction, + arcSize)); + + // if the adjusted endPoint computed above is used here and endPoint != + // startPoint then this half of the circle won't render... + mSink->AddArc(D2D1::ArcSegment(D2DPoint(startPoint), + D2D1::SizeF(aRadius, aRadius), + 0.0f, + direction, + arcSize)); + } + + mCurrentPoint = endPoint; +} + +Point +PathBuilderD2D::CurrentPoint() const +{ + return mCurrentPoint; +} + +void +PathBuilderD2D::EnsureActive(const Point &aPoint) +{ + if (!mFigureActive) { + mSink->BeginFigure(D2DPoint(aPoint), D2D1_FIGURE_BEGIN_FILLED); + mBeginPoint = aPoint; + mFigureActive = true; + } +} + +already_AddRefed<Path> +PathBuilderD2D::Finish() +{ + if (mFigureActive) { + mSink->EndFigure(D2D1_FIGURE_END_OPEN); + } + + HRESULT hr = mSink->Close(); + if (FAILED(hr)) { + gfxCriticalNote << "Failed to close PathSink. Code: " << hexa(hr); + return nullptr; + } + + return MakeAndAddRef<PathD2D>(mGeometry, mFigureActive, mCurrentPoint, mFillRule, mBackendType); +} + +already_AddRefed<PathBuilder> +PathD2D::CopyToBuilder(FillRule aFillRule) const +{ + return TransformedCopyToBuilder(Matrix(), aFillRule); +} + +already_AddRefed<PathBuilder> +PathD2D::TransformedCopyToBuilder(const Matrix &aTransform, FillRule aFillRule) const +{ + RefPtr<ID2D1PathGeometry> path; + HRESULT hr = DrawTargetD2D1::factory()->CreatePathGeometry(getter_AddRefs(path)); + + if (FAILED(hr)) { + gfxWarning() << "Failed to create PathGeometry. Code: " << hexa(hr); + return nullptr; + } + + RefPtr<ID2D1GeometrySink> sink; + hr = path->Open(getter_AddRefs(sink)); + if (FAILED(hr)) { + gfxWarning() << "Failed to open Geometry for writing. Code: " << hexa(hr); + return nullptr; + } + + if (aFillRule == FillRule::FILL_WINDING) { + sink->SetFillMode(D2D1_FILL_MODE_WINDING); + } + + if (mEndedActive) { + OpeningGeometrySink wrapSink(sink); + hr = mGeometry->Simplify(D2D1_GEOMETRY_SIMPLIFICATION_OPTION_CUBICS_AND_LINES, + D2DMatrix(aTransform), + &wrapSink); + } else { + hr = mGeometry->Simplify(D2D1_GEOMETRY_SIMPLIFICATION_OPTION_CUBICS_AND_LINES, + D2DMatrix(aTransform), + sink); + } + if (FAILED(hr)) { + gfxWarning() << "Failed to simplify PathGeometry to tranformed copy. Code: " << hexa(hr) << " Active: " << mEndedActive; + return nullptr; + } + + RefPtr<PathBuilderD2D> pathBuilder = new PathBuilderD2D(sink, path, aFillRule, mBackendType); + + pathBuilder->mCurrentPoint = aTransform.TransformPoint(mEndPoint); + + if (mEndedActive) { + pathBuilder->mFigureActive = true; + } + + return pathBuilder.forget(); +} + +void +PathD2D::StreamToSink(PathSink *aSink) const +{ + HRESULT hr; + + StreamingGeometrySink sink(aSink); + + hr = mGeometry->Simplify(D2D1_GEOMETRY_SIMPLIFICATION_OPTION_CUBICS_AND_LINES, + D2D1::IdentityMatrix(), &sink); + + if (FAILED(hr)) { + gfxWarning() << "Failed to stream D2D path to sink. Code: " << hexa(hr); + return; + } +} + +bool +PathD2D::ContainsPoint(const Point &aPoint, const Matrix &aTransform) const +{ + BOOL result; + + HRESULT hr = mGeometry->FillContainsPoint(D2DPoint(aPoint), D2DMatrix(aTransform), 0.001f, &result); + + if (FAILED(hr)) { + // Log + return false; + } + + return !!result; +} + +bool +PathD2D::StrokeContainsPoint(const StrokeOptions &aStrokeOptions, + const Point &aPoint, + const Matrix &aTransform) const +{ + BOOL result; + + RefPtr<ID2D1StrokeStyle> strokeStyle = CreateStrokeStyleForOptions(aStrokeOptions); + HRESULT hr = mGeometry->StrokeContainsPoint(D2DPoint(aPoint), + aStrokeOptions.mLineWidth, + strokeStyle, + D2DMatrix(aTransform), + &result); + + if (FAILED(hr)) { + // Log + return false; + } + + return !!result; +} + +Rect +PathD2D::GetBounds(const Matrix &aTransform) const +{ + D2D1_RECT_F d2dBounds; + + HRESULT hr = mGeometry->GetBounds(D2DMatrix(aTransform), &d2dBounds); + + Rect bounds = ToRect(d2dBounds); + if (FAILED(hr) || !bounds.IsFinite()) { + gfxWarning() << "Failed to get stroked bounds for path. Code: " << hexa(hr); + return Rect(); + } + + return bounds; +} + +Rect +PathD2D::GetStrokedBounds(const StrokeOptions &aStrokeOptions, + const Matrix &aTransform) const +{ + D2D1_RECT_F d2dBounds; + + RefPtr<ID2D1StrokeStyle> strokeStyle = CreateStrokeStyleForOptions(aStrokeOptions); + HRESULT hr = + mGeometry->GetWidenedBounds(aStrokeOptions.mLineWidth, strokeStyle, + D2DMatrix(aTransform), &d2dBounds); + + Rect bounds = ToRect(d2dBounds); + if (FAILED(hr) || !bounds.IsFinite()) { + gfxWarning() << "Failed to get stroked bounds for path. Code: " << hexa(hr); + return Rect(); + } + + return bounds; +} + +} +} |