1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 #include "HelpersSkia.h"
9 #include "PathHelpers.h"
10 #include "mozilla/UniquePtr.h"
11 #include "skia/include/core/SkPathUtils.h"
12 #include "skia/src/core/SkGeometry.h"
14 namespace mozilla::gfx
{
16 already_AddRefed
<PathBuilder
> PathBuilderSkia::Create(FillRule aFillRule
) {
17 return MakeAndAddRef
<PathBuilderSkia
>(aFillRule
);
20 PathBuilderSkia::PathBuilderSkia(SkPath
&& aPath
, FillRule aFillRule
,
21 const Point
& aCurrentPoint
,
22 const Point
& aBeginPoint
)
24 SetFillRule(aFillRule
);
25 SetCurrentPoint(aCurrentPoint
);
26 SetBeginPoint(aBeginPoint
);
29 PathBuilderSkia::PathBuilderSkia(FillRule aFillRule
) { SetFillRule(aFillRule
); }
31 void PathBuilderSkia::SetFillRule(FillRule aFillRule
) {
32 mFillRule
= aFillRule
;
33 if (mFillRule
== FillRule::FILL_WINDING
) {
34 mPath
.setFillType(SkPathFillType::kWinding
);
36 mPath
.setFillType(SkPathFillType::kEvenOdd
);
40 void PathBuilderSkia::MoveTo(const Point
& aPoint
) {
41 mPath
.moveTo(SkFloatToScalar(aPoint
.x
), SkFloatToScalar(aPoint
.y
));
42 mCurrentPoint
= aPoint
;
46 void PathBuilderSkia::LineTo(const Point
& aPoint
) {
47 if (!mPath
.countPoints()) {
50 mPath
.lineTo(SkFloatToScalar(aPoint
.x
), SkFloatToScalar(aPoint
.y
));
52 mCurrentPoint
= aPoint
;
55 void PathBuilderSkia::BezierTo(const Point
& aCP1
, const Point
& aCP2
,
57 if (!mPath
.countPoints()) {
60 mPath
.cubicTo(SkFloatToScalar(aCP1
.x
), SkFloatToScalar(aCP1
.y
),
61 SkFloatToScalar(aCP2
.x
), SkFloatToScalar(aCP2
.y
),
62 SkFloatToScalar(aCP3
.x
), SkFloatToScalar(aCP3
.y
));
66 void PathBuilderSkia::QuadraticBezierTo(const Point
& aCP1
, const Point
& aCP2
) {
67 if (!mPath
.countPoints()) {
70 mPath
.quadTo(SkFloatToScalar(aCP1
.x
), SkFloatToScalar(aCP1
.y
),
71 SkFloatToScalar(aCP2
.x
), SkFloatToScalar(aCP2
.y
));
75 void PathBuilderSkia::Close() {
77 mCurrentPoint
= mBeginPoint
;
80 void PathBuilderSkia::Arc(const Point
& aOrigin
, float aRadius
,
81 float aStartAngle
, float aEndAngle
,
82 bool aAntiClockwise
) {
83 ArcToBezier(this, aOrigin
, Size(aRadius
, aRadius
), aStartAngle
, aEndAngle
,
87 already_AddRefed
<Path
> PathBuilderSkia::Finish() {
89 MakeAndAddRef
<PathSkia
>(mPath
, mFillRule
, mCurrentPoint
, mBeginPoint
);
90 mCurrentPoint
= Point(0.0, 0.0);
91 mBeginPoint
= Point(0.0, 0.0);
95 void PathBuilderSkia::AppendPath(const SkPath
& aPath
) { mPath
.addPath(aPath
); }
97 already_AddRefed
<PathBuilder
> PathSkia::CopyToBuilder(
98 FillRule aFillRule
) const {
99 return MakeAndAddRef
<PathBuilderSkia
>(SkPath(mPath
), aFillRule
, mCurrentPoint
,
103 already_AddRefed
<PathBuilder
> PathSkia::TransformedCopyToBuilder(
104 const Matrix
& aTransform
, FillRule aFillRule
) const {
106 GfxMatrixToSkiaMatrix(aTransform
, matrix
);
108 path
.transform(matrix
);
109 return MakeAndAddRef
<PathBuilderSkia
>(
110 std::move(path
), aFillRule
, aTransform
.TransformPoint(mCurrentPoint
),
111 aTransform
.TransformPoint(mBeginPoint
));
114 already_AddRefed
<PathBuilder
> PathSkia::MoveToBuilder(FillRule aFillRule
) {
115 return MakeAndAddRef
<PathBuilderSkia
>(std::move(mPath
), aFillRule
,
116 mCurrentPoint
, mBeginPoint
);
119 already_AddRefed
<PathBuilder
> PathSkia::TransformedMoveToBuilder(
120 const Matrix
& aTransform
, FillRule aFillRule
) {
122 GfxMatrixToSkiaMatrix(aTransform
, matrix
);
123 mPath
.transform(matrix
);
124 return MakeAndAddRef
<PathBuilderSkia
>(
125 std::move(mPath
), aFillRule
, aTransform
.TransformPoint(mCurrentPoint
),
126 aTransform
.TransformPoint(mBeginPoint
));
129 static bool SkPathContainsPoint(const SkPath
& aPath
, const Point
& aPoint
,
130 const Matrix
& aTransform
) {
131 Matrix inverse
= aTransform
;
132 if (!inverse
.Invert()) {
136 SkPoint point
= PointToSkPoint(inverse
.TransformPoint(aPoint
));
137 return aPath
.contains(point
.fX
, point
.fY
);
140 bool PathSkia::ContainsPoint(const Point
& aPoint
,
141 const Matrix
& aTransform
) const {
142 if (!mPath
.isFinite()) {
146 return SkPathContainsPoint(mPath
, aPoint
, aTransform
);
149 bool PathSkia::GetFillPath(const StrokeOptions
& aStrokeOptions
,
150 const Matrix
& aTransform
, SkPath
& aFillPath
,
151 const Maybe
<Rect
>& aClipRect
) const {
153 if (!StrokeOptionsToPaint(paint
, aStrokeOptions
)) {
158 GfxMatrixToSkiaMatrix(aTransform
, skiaMatrix
);
160 Maybe
<SkRect
> cullRect
;
161 if (aClipRect
.isSome()) {
162 cullRect
= Some(RectToSkRect(aClipRect
.ref()));
165 return skpathutils::FillPathWithPaint(mPath
, paint
, &aFillPath
,
166 cullRect
.ptrOr(nullptr), skiaMatrix
);
169 bool PathSkia::StrokeContainsPoint(const StrokeOptions
& aStrokeOptions
,
171 const Matrix
& aTransform
) const {
172 if (!mPath
.isFinite()) {
177 if (!GetFillPath(aStrokeOptions
, aTransform
, strokePath
)) {
181 return SkPathContainsPoint(strokePath
, aPoint
, aTransform
);
184 Rect
PathSkia::GetBounds(const Matrix
& aTransform
) const {
185 if (!mPath
.isFinite()) {
189 Rect bounds
= SkRectToRect(mPath
.computeTightBounds());
190 return aTransform
.TransformBounds(bounds
);
193 Rect
PathSkia::GetStrokedBounds(const StrokeOptions
& aStrokeOptions
,
194 const Matrix
& aTransform
) const {
195 if (!mPath
.isFinite()) {
200 if (!GetFillPath(aStrokeOptions
, aTransform
, fillPath
)) {
204 Rect bounds
= SkRectToRect(fillPath
.computeTightBounds());
205 return aTransform
.TransformBounds(bounds
);
208 Rect
PathSkia::GetFastBounds(const Matrix
& aTransform
,
209 const StrokeOptions
* aStrokeOptions
) const {
210 if (!mPath
.isFinite()) {
213 SkRect bounds
= mPath
.getBounds();
214 if (aStrokeOptions
) {
215 // If the path is stroked, ensure that the bounds are inflated by any
216 // relevant options such as line width. Avoid using dash path effects
217 // for performance and to ensure computeFastStrokeBounds succeeds.
219 if (!StrokeOptionsToPaint(paint
, *aStrokeOptions
, false)) {
222 SkRect outBounds
= SkRect::MakeEmpty();
223 bounds
= paint
.computeFastStrokeBounds(bounds
, &outBounds
);
225 return aTransform
.TransformBounds(SkRectToRect(bounds
));
228 int ConvertConicToQuads(const Point
& aP0
, const Point
& aP1
, const Point
& aP2
,
229 float aWeight
, std::vector
<Point
>& aQuads
) {
230 SkConic
conic(PointToSkPoint(aP0
), PointToSkPoint(aP1
), PointToSkPoint(aP2
),
232 int pow2
= conic
.computeQuadPOW2(0.25f
);
233 aQuads
.resize(1 + 2 * (1 << pow2
));
235 conic
.chopIntoQuadsPOW2(reinterpret_cast<SkPoint
*>(&aQuads
[0]), pow2
);
236 if (numQuads
< 1 << pow2
) {
237 aQuads
.resize(1 + 2 * numQuads
);
242 void PathSkia::StreamToSink(PathSink
* aSink
) const {
243 SkPath::RawIter
iter(mPath
);
246 SkPath::Verb currentVerb
;
247 while ((currentVerb
= iter
.next(points
)) != SkPath::kDone_Verb
) {
248 switch (currentVerb
) {
249 case SkPath::kMove_Verb
:
250 aSink
->MoveTo(SkPointToPoint(points
[0]));
252 case SkPath::kLine_Verb
:
253 aSink
->LineTo(SkPointToPoint(points
[1]));
255 case SkPath::kCubic_Verb
:
256 aSink
->BezierTo(SkPointToPoint(points
[1]), SkPointToPoint(points
[2]),
257 SkPointToPoint(points
[3]));
259 case SkPath::kQuad_Verb
:
260 aSink
->QuadraticBezierTo(SkPointToPoint(points
[1]),
261 SkPointToPoint(points
[2]));
263 case SkPath::kConic_Verb
: {
264 std::vector
<Point
> quads
;
265 int numQuads
= ConvertConicToQuads(
266 SkPointToPoint(points
[0]), SkPointToPoint(points
[1]),
267 SkPointToPoint(points
[2]), iter
.conicWeight(), quads
);
268 for (int i
= 0; i
< numQuads
; i
++) {
269 aSink
->QuadraticBezierTo(quads
[2 * i
+ 1], quads
[2 * i
+ 2]);
273 case SkPath::kClose_Verb
:
278 // Unexpected verb found in path!
283 Maybe
<Rect
> PathSkia::AsRect() const {
285 if (mPath
.isRect(&skiaRect
)) {
286 Rect rect
= SkRectToRect(skiaRect
);
287 // Ensure that the conversion between Skia rect and Moz2D rect is not lossy
288 // due to floating-point precision errors.
289 if (RectToSkRect(rect
) == skiaRect
) {
296 bool PathSkia::IsEmpty() const {
297 // Move/Close/Done segments are not included in the mask so as long as any
298 // flag is set, we know that the path is non-empty.
299 return mPath
.getSegmentMasks() == 0;
302 } // namespace mozilla::gfx