Bug 849918 - Initial support for PannerNode's 3D positional audio (equalpower panning...
[gecko.git] / gfx / 2d / PathD2D.cpp
blob69431e0c311a0d98ce9a3b38f95b11ad1a65e9b3
1 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "PathD2D.h"
7 #include "HelpersD2D.h"
8 #include <math.h>
9 #include "DrawTargetD2D.h"
10 #include "Logging.h"
11 #include "mozilla/Constants.h"
13 namespace mozilla {
14 namespace gfx {
16 // This class exists as a wrapper for ID2D1SimplifiedGeometry sink, it allows
17 // a geometry to be duplicated into a geometry sink, while removing the final
18 // figure end and thus allowing a figure that was implicitly closed to be
19 // continued.
20 class OpeningGeometrySink : public ID2D1SimplifiedGeometrySink
22 public:
23 OpeningGeometrySink(ID2D1SimplifiedGeometrySink *aSink)
24 : mSink(aSink)
25 , mNeedsFigureEnded(false)
29 HRESULT STDMETHODCALLTYPE QueryInterface(const IID &aIID, void **aPtr)
31 if (!aPtr) {
32 return E_POINTER;
35 if (aIID == IID_IUnknown) {
36 *aPtr = static_cast<IUnknown*>(this);
37 return S_OK;
38 } else if (aIID == IID_ID2D1SimplifiedGeometrySink) {
39 *aPtr = static_cast<ID2D1SimplifiedGeometrySink*>(this);
40 return S_OK;
43 return E_NOINTERFACE;
46 ULONG STDMETHODCALLTYPE AddRef()
48 return 1;
51 ULONG STDMETHODCALLTYPE Release()
53 return 1;
56 // We ignore SetFillMode, the copier will decide.
57 STDMETHOD_(void, SetFillMode)(D2D1_FILL_MODE aMode)
58 { EnsureFigureEnded(); return; }
59 STDMETHOD_(void, BeginFigure)(D2D1_POINT_2F aPoint, D2D1_FIGURE_BEGIN aBegin)
60 { EnsureFigureEnded(); return mSink->BeginFigure(aPoint, aBegin); }
61 STDMETHOD_(void, AddLines)(const D2D1_POINT_2F *aLines, UINT aCount)
62 { EnsureFigureEnded(); return mSink->AddLines(aLines, aCount); }
63 STDMETHOD_(void, AddBeziers)(const D2D1_BEZIER_SEGMENT *aSegments, UINT aCount)
64 { EnsureFigureEnded(); return mSink->AddBeziers(aSegments, aCount); }
65 STDMETHOD(Close)()
66 { /* Should never be called! */ return S_OK; }
67 STDMETHOD_(void, SetSegmentFlags)(D2D1_PATH_SEGMENT aFlags)
68 { return mSink->SetSegmentFlags(aFlags); }
70 // This function is special - it's the reason this class exists.
71 // It needs to intercept the very last endfigure. So that a user can
72 // continue writing to this sink as if they never stopped.
73 STDMETHOD_(void, EndFigure)(D2D1_FIGURE_END aEnd)
75 if (aEnd == D2D1_FIGURE_END_CLOSED) {
76 return mSink->EndFigure(aEnd);
77 } else {
78 mNeedsFigureEnded = true;
81 private:
82 void EnsureFigureEnded()
84 if (mNeedsFigureEnded) {
85 mSink->EndFigure(D2D1_FIGURE_END_OPEN);
86 mNeedsFigureEnded = false;
90 ID2D1SimplifiedGeometrySink *mSink;
91 bool mNeedsFigureEnded;
94 PathBuilderD2D::~PathBuilderD2D()
98 void
99 PathBuilderD2D::MoveTo(const Point &aPoint)
101 if (mFigureActive) {
102 mSink->EndFigure(D2D1_FIGURE_END_OPEN);
103 mFigureActive = false;
105 EnsureActive(aPoint);
106 mCurrentPoint = aPoint;
109 void
110 PathBuilderD2D::LineTo(const Point &aPoint)
112 EnsureActive(aPoint);
113 mSink->AddLine(D2DPoint(aPoint));
115 mCurrentPoint = aPoint;
118 void
119 PathBuilderD2D::BezierTo(const Point &aCP1,
120 const Point &aCP2,
121 const Point &aCP3)
123 EnsureActive(aCP1);
124 mSink->AddBezier(D2D1::BezierSegment(D2DPoint(aCP1),
125 D2DPoint(aCP2),
126 D2DPoint(aCP3)));
128 mCurrentPoint = aCP3;
131 void
132 PathBuilderD2D::QuadraticBezierTo(const Point &aCP1,
133 const Point &aCP2)
135 EnsureActive(aCP1);
136 mSink->AddQuadraticBezier(D2D1::QuadraticBezierSegment(D2DPoint(aCP1),
137 D2DPoint(aCP2)));
139 mCurrentPoint = aCP2;
142 void
143 PathBuilderD2D::Close()
145 if (mFigureActive) {
146 mSink->EndFigure(D2D1_FIGURE_END_CLOSED);
148 mFigureActive = false;
150 EnsureActive(mBeginPoint);
154 void
155 PathBuilderD2D::Arc(const Point &aOrigin, Float aRadius, Float aStartAngle,
156 Float aEndAngle, bool aAntiClockwise)
158 if (aAntiClockwise && aStartAngle < aEndAngle) {
159 // D2D does things a little differently, and draws the arc by specifying an
160 // beginning and an end point. This means the circle will be the wrong way
161 // around if the start angle is smaller than the end angle. It might seem
162 // tempting to invert aAntiClockwise but that would change the sweeping
163 // direction of the arc to instead we exchange start/begin.
164 Float oldStart = aStartAngle;
165 aStartAngle = aEndAngle;
166 aEndAngle = oldStart;
169 // XXX - Workaround for now, D2D does not appear to do the desired thing when
170 // the angle sweeps a complete circle.
171 if (aEndAngle - aStartAngle >= 2 * M_PI) {
172 aEndAngle = Float(aStartAngle + M_PI * 1.9999);
173 } else if (aStartAngle - aEndAngle >= 2 * M_PI) {
174 aStartAngle = Float(aEndAngle + M_PI * 1.9999);
177 Point startPoint;
178 startPoint.x = aOrigin.x + aRadius * cos(aStartAngle);
179 startPoint.y = aOrigin.y + aRadius * sin(aStartAngle);
181 if (!mFigureActive) {
182 EnsureActive(startPoint);
183 } else {
184 mSink->AddLine(D2DPoint(startPoint));
187 Point endPoint;
188 endPoint.x = aOrigin.x + aRadius * cos(aEndAngle);
189 endPoint.y = aOrigin.y + aRadius * sin(aEndAngle);
191 D2D1_ARC_SIZE arcSize = D2D1_ARC_SIZE_SMALL;
193 if (aAntiClockwise) {
194 if (aStartAngle - aEndAngle > M_PI) {
195 arcSize = D2D1_ARC_SIZE_LARGE;
197 } else {
198 if (aEndAngle - aStartAngle > M_PI) {
199 arcSize = D2D1_ARC_SIZE_LARGE;
203 mSink->AddArc(D2D1::ArcSegment(D2DPoint(endPoint),
204 D2D1::SizeF(aRadius, aRadius),
205 0.0f,
206 aAntiClockwise ? D2D1_SWEEP_DIRECTION_COUNTER_CLOCKWISE :
207 D2D1_SWEEP_DIRECTION_CLOCKWISE,
208 arcSize));
210 mCurrentPoint = endPoint;
213 Point
214 PathBuilderD2D::CurrentPoint() const
216 return mCurrentPoint;
219 void
220 PathBuilderD2D::EnsureActive(const Point &aPoint)
222 if (!mFigureActive) {
223 mSink->BeginFigure(D2DPoint(aPoint), D2D1_FIGURE_BEGIN_FILLED);
224 mBeginPoint = aPoint;
225 mFigureActive = true;
229 TemporaryRef<Path>
230 PathBuilderD2D::Finish()
232 if (mFigureActive) {
233 mSink->EndFigure(D2D1_FIGURE_END_OPEN);
236 HRESULT hr = mSink->Close();
237 if (FAILED(hr)) {
238 gfxDebug() << "Failed to close PathSink. Code: " << hr;
239 return nullptr;
242 return new PathD2D(mGeometry, mFigureActive, mCurrentPoint, mFillRule);
245 TemporaryRef<PathBuilder>
246 PathD2D::CopyToBuilder(FillRule aFillRule) const
248 return TransformedCopyToBuilder(Matrix(), aFillRule);
251 TemporaryRef<PathBuilder>
252 PathD2D::TransformedCopyToBuilder(const Matrix &aTransform, FillRule aFillRule) const
254 RefPtr<ID2D1PathGeometry> path;
255 HRESULT hr = DrawTargetD2D::factory()->CreatePathGeometry(byRef(path));
257 if (FAILED(hr)) {
258 gfxWarning() << "Failed to create PathGeometry. Code: " << hr;
259 return nullptr;
262 RefPtr<ID2D1GeometrySink> sink;
263 hr = path->Open(byRef(sink));
264 if (FAILED(hr)) {
265 gfxWarning() << "Failed to open Geometry for writing. Code: " << hr;
266 return nullptr;
269 if (aFillRule == FILL_WINDING) {
270 sink->SetFillMode(D2D1_FILL_MODE_WINDING);
273 if (mEndedActive) {
274 OpeningGeometrySink wrapSink(sink);
275 mGeometry->Simplify(D2D1_GEOMETRY_SIMPLIFICATION_OPTION_CUBICS_AND_LINES,
276 D2DMatrix(aTransform),
277 &wrapSink);
278 } else {
279 mGeometry->Simplify(D2D1_GEOMETRY_SIMPLIFICATION_OPTION_CUBICS_AND_LINES,
280 D2DMatrix(aTransform),
281 sink);
284 RefPtr<PathBuilderD2D> pathBuilder = new PathBuilderD2D(sink, path, mFillRule);
286 pathBuilder->mCurrentPoint = aTransform * mEndPoint;
288 if (mEndedActive) {
289 pathBuilder->mFigureActive = true;
292 return pathBuilder;
295 bool
296 PathD2D::ContainsPoint(const Point &aPoint, const Matrix &aTransform) const
298 BOOL result;
300 HRESULT hr = mGeometry->FillContainsPoint(D2DPoint(aPoint), D2DMatrix(aTransform), 0.001f, &result);
302 if (FAILED(hr)) {
303 // Log
304 return false;
307 return !!result;
310 bool
311 PathD2D::StrokeContainsPoint(const StrokeOptions &aStrokeOptions,
312 const Point &aPoint,
313 const Matrix &aTransform) const
315 BOOL result;
317 RefPtr<ID2D1StrokeStyle> strokeStyle =
318 DrawTargetD2D::CreateStrokeStyleForOptions(aStrokeOptions);
319 HRESULT hr = mGeometry->StrokeContainsPoint(D2DPoint(aPoint),
320 aStrokeOptions.mLineWidth,
321 strokeStyle,
322 D2DMatrix(aTransform),
323 &result);
325 if (FAILED(hr)) {
326 // Log
327 return false;
330 return !!result;
333 Rect
334 PathD2D::GetBounds(const Matrix &aTransform) const
336 D2D1_RECT_F bounds;
338 HRESULT hr = mGeometry->GetBounds(D2DMatrix(aTransform), &bounds);
340 if (FAILED(hr)) {
341 gfxWarning() << "Failed to get stroked bounds for path. Code: " << hr;
342 bounds.bottom = bounds.left = bounds.right = bounds.top = 0;
345 return ToRect(bounds);
348 Rect
349 PathD2D::GetStrokedBounds(const StrokeOptions &aStrokeOptions,
350 const Matrix &aTransform) const
352 D2D1_RECT_F bounds;
354 RefPtr<ID2D1StrokeStyle> strokeStyle =
355 DrawTargetD2D::CreateStrokeStyleForOptions(aStrokeOptions);
356 HRESULT hr =
357 mGeometry->GetWidenedBounds(aStrokeOptions.mLineWidth, strokeStyle,
358 D2DMatrix(aTransform), &bounds);
360 if (FAILED(hr)) {
361 gfxWarning() << "Failed to get stroked bounds for path. Code: " << hr;
362 bounds.bottom = bounds.left = bounds.right = bounds.top = 0;
365 return ToRect(bounds);