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(const Matrix
& aTransform
, const SkPath
& aPath
,
24 GfxMatrixToSkiaMatrix(aTransform
, matrix
);
25 mPath
.transform(matrix
);
26 SetFillRule(aFillRule
);
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 TransformedCopyToBuilder(Matrix(), aFillRule
);
102 already_AddRefed
<PathBuilder
> PathSkia::TransformedCopyToBuilder(
103 const Matrix
& aTransform
, FillRule aFillRule
) const {
104 RefPtr
<PathBuilderSkia
> builder
=
105 MakeAndAddRef
<PathBuilderSkia
>(aTransform
, mPath
, aFillRule
);
107 builder
->mCurrentPoint
= aTransform
.TransformPoint(mCurrentPoint
);
108 builder
->mBeginPoint
= aTransform
.TransformPoint(mBeginPoint
);
110 return builder
.forget();
113 static bool SkPathContainsPoint(const SkPath
& aPath
, const Point
& aPoint
,
114 const Matrix
& aTransform
) {
115 Matrix inverse
= aTransform
;
116 if (!inverse
.Invert()) {
120 SkPoint point
= PointToSkPoint(inverse
.TransformPoint(aPoint
));
121 return aPath
.contains(point
.fX
, point
.fY
);
124 bool PathSkia::ContainsPoint(const Point
& aPoint
,
125 const Matrix
& aTransform
) const {
126 if (!mPath
.isFinite()) {
130 return SkPathContainsPoint(mPath
, aPoint
, aTransform
);
133 bool PathSkia::GetFillPath(const StrokeOptions
& aStrokeOptions
,
134 const Matrix
& aTransform
, SkPath
& aFillPath
,
135 const Maybe
<Rect
>& aClipRect
) const {
137 if (!StrokeOptionsToPaint(paint
, aStrokeOptions
)) {
142 GfxMatrixToSkiaMatrix(aTransform
, skiaMatrix
);
144 Maybe
<SkRect
> cullRect
;
145 if (aClipRect
.isSome()) {
146 cullRect
= Some(RectToSkRect(aClipRect
.ref()));
149 return skpathutils::FillPathWithPaint(mPath
, paint
, &aFillPath
,
150 cullRect
.ptrOr(nullptr), skiaMatrix
);
153 bool PathSkia::StrokeContainsPoint(const StrokeOptions
& aStrokeOptions
,
155 const Matrix
& aTransform
) const {
156 if (!mPath
.isFinite()) {
161 if (!GetFillPath(aStrokeOptions
, aTransform
, strokePath
)) {
165 return SkPathContainsPoint(strokePath
, aPoint
, aTransform
);
168 Rect
PathSkia::GetBounds(const Matrix
& aTransform
) const {
169 if (!mPath
.isFinite()) {
173 Rect bounds
= SkRectToRect(mPath
.computeTightBounds());
174 return aTransform
.TransformBounds(bounds
);
177 Rect
PathSkia::GetStrokedBounds(const StrokeOptions
& aStrokeOptions
,
178 const Matrix
& aTransform
) const {
179 if (!mPath
.isFinite()) {
184 if (!GetFillPath(aStrokeOptions
, aTransform
, fillPath
)) {
188 Rect bounds
= SkRectToRect(fillPath
.computeTightBounds());
189 return aTransform
.TransformBounds(bounds
);
192 Rect
PathSkia::GetFastBounds(const Matrix
& aTransform
,
193 const StrokeOptions
* aStrokeOptions
) const {
194 if (!mPath
.isFinite()) {
197 SkRect bounds
= mPath
.getBounds();
198 if (aStrokeOptions
) {
199 // If the path is stroked, ensure that the bounds are inflated by any
200 // relevant options such as line width. Avoid using dash path effects
201 // for performance and to ensure computeFastStrokeBounds succeeds.
203 if (!StrokeOptionsToPaint(paint
, *aStrokeOptions
, false)) {
206 SkRect outBounds
= SkRect::MakeEmpty();
207 bounds
= paint
.computeFastStrokeBounds(bounds
, &outBounds
);
209 return aTransform
.TransformBounds(SkRectToRect(bounds
));
212 int ConvertConicToQuads(const Point
& aP0
, const Point
& aP1
, const Point
& aP2
,
213 float aWeight
, std::vector
<Point
>& aQuads
) {
214 SkConic
conic(PointToSkPoint(aP0
), PointToSkPoint(aP1
), PointToSkPoint(aP2
),
216 int pow2
= conic
.computeQuadPOW2(0.25f
);
217 aQuads
.resize(1 + 2 * (1 << pow2
));
219 conic
.chopIntoQuadsPOW2(reinterpret_cast<SkPoint
*>(&aQuads
[0]), pow2
);
220 if (numQuads
< 1 << pow2
) {
221 aQuads
.resize(1 + 2 * numQuads
);
226 void PathSkia::StreamToSink(PathSink
* aSink
) const {
227 SkPath::RawIter
iter(mPath
);
230 SkPath::Verb currentVerb
;
231 while ((currentVerb
= iter
.next(points
)) != SkPath::kDone_Verb
) {
232 switch (currentVerb
) {
233 case SkPath::kMove_Verb
:
234 aSink
->MoveTo(SkPointToPoint(points
[0]));
236 case SkPath::kLine_Verb
:
237 aSink
->LineTo(SkPointToPoint(points
[1]));
239 case SkPath::kCubic_Verb
:
240 aSink
->BezierTo(SkPointToPoint(points
[1]), SkPointToPoint(points
[2]),
241 SkPointToPoint(points
[3]));
243 case SkPath::kQuad_Verb
:
244 aSink
->QuadraticBezierTo(SkPointToPoint(points
[1]),
245 SkPointToPoint(points
[2]));
247 case SkPath::kConic_Verb
: {
248 std::vector
<Point
> quads
;
249 int numQuads
= ConvertConicToQuads(
250 SkPointToPoint(points
[0]), SkPointToPoint(points
[1]),
251 SkPointToPoint(points
[2]), iter
.conicWeight(), quads
);
252 for (int i
= 0; i
< numQuads
; i
++) {
253 aSink
->QuadraticBezierTo(quads
[2 * i
+ 1], quads
[2 * i
+ 2]);
257 case SkPath::kClose_Verb
:
262 // Unexpected verb found in path!
267 Maybe
<Rect
> PathSkia::AsRect() const {
269 if (mPath
.isRect(&rect
)) {
270 return Some(SkRectToRect(rect
));
275 bool PathSkia::IsEmpty() const {
276 // Move/Close/Done segments are not included in the mask so as long as any
277 // flag is set, we know that the path is non-empty.
278 return mPath
.getSegmentMasks() == 0;
281 } // namespace mozilla::gfx