Flutter Engine
The Flutter Engine
SkDashPathEffect.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2006 The Android Open Source Project
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
9
13#include "include/core/SkPath.h"
16#include "include/core/SkRect.h"
27
28#include <algorithm>
29#include <cstdint>
30#include <cstring>
31
32using namespace skia_private;
33
34SkDashImpl::SkDashImpl(const SkScalar intervals[], int count, SkScalar phase)
35 : fPhase(0)
36 , fInitialDashLength(-1)
37 , fInitialDashIndex(0)
38 , fIntervalLength(0) {
39 SkASSERT(intervals);
41
42 fIntervals = (SkScalar*)sk_malloc_throw(sizeof(SkScalar) * count);
43 fCount = count;
44 for (int i = 0; i < count; i++) {
45 fIntervals[i] = intervals[i];
46 }
47
48 // set the internal data members
49 SkDashPath::CalcDashParameters(phase, fIntervals, fCount,
50 &fInitialDashLength, &fInitialDashIndex, &fIntervalLength, &fPhase);
51}
52
54 sk_free(fIntervals);
55}
56
58 const SkRect* cullRect, const SkMatrix&) const {
59 return SkDashPath::InternalFilter(dst, src, rec, cullRect, fIntervals, fCount,
60 fInitialDashLength, fInitialDashIndex, fIntervalLength,
61 fPhase);
62}
63
64static void outset_for_stroke(SkRect* rect, const SkStrokeRec& rec) {
65 SkScalar radius = SkScalarHalf(rec.getWidth());
66 if (0 == radius) {
67 radius = SK_Scalar1; // hairlines
68 }
69 if (SkPaint::kMiter_Join == rec.getJoin()) {
70 radius *= rec.getMiter();
71 }
72 rect->outset(radius, radius);
73}
74
75// Attempt to trim the line to minimally cover the cull rect (currently
76// only works for horizontal and vertical lines).
77// Return true if processing should continue; false otherwise.
78static bool cull_line(SkPoint* pts, const SkStrokeRec& rec,
79 const SkMatrix& ctm, const SkRect* cullRect,
80 const SkScalar intervalLength) {
81 if (nullptr == cullRect) {
82 SkASSERT(false); // Shouldn't ever occur in practice
83 return false;
84 }
85
86 SkScalar dx = pts[1].x() - pts[0].x();
87 SkScalar dy = pts[1].y() - pts[0].y();
88
89 if ((dx && dy) || (!dx && !dy)) {
90 return false;
91 }
92
93 SkRect bounds = *cullRect;
95
96 // cullRect is in device space while pts are in the local coordinate system
97 // defined by the ctm. We want our answer in the local coordinate system.
98
101 if (!ctm.invert(&inv)) {
102 return false;
103 }
104
105 inv.mapRect(&bounds);
106
107 if (dx) {
108 SkASSERT(dx && !dy);
109 SkScalar minX = pts[0].fX;
110 SkScalar maxX = pts[1].fX;
111
112 if (dx < 0) {
113 using std::swap;
114 swap(minX, maxX);
115 }
116
117 SkASSERT(minX < maxX);
118 if (maxX <= bounds.fLeft || minX >= bounds.fRight) {
119 return false;
120 }
121
122 // Now we actually perform the chop, removing the excess to the left and
123 // right of the bounds (keeping our new line "in phase" with the dash,
124 // hence the (mod intervalLength).
125
126 if (minX < bounds.fLeft) {
127 minX = bounds.fLeft - SkScalarMod(bounds.fLeft - minX, intervalLength);
128 }
129 if (maxX > bounds.fRight) {
130 maxX = bounds.fRight + SkScalarMod(maxX - bounds.fRight, intervalLength);
131 }
132
133 SkASSERT(maxX > minX);
134 if (dx < 0) {
135 using std::swap;
136 swap(minX, maxX);
137 }
138 pts[0].fX = minX;
139 pts[1].fX = maxX;
140 } else {
141 SkASSERT(dy && !dx);
142 SkScalar minY = pts[0].fY;
143 SkScalar maxY = pts[1].fY;
144
145 if (dy < 0) {
146 using std::swap;
147 swap(minY, maxY);
148 }
149
150 SkASSERT(minY < maxY);
151 if (maxY <= bounds.fTop || minY >= bounds.fBottom) {
152 return false;
153 }
154
155 // Now we actually perform the chop, removing the excess to the top and
156 // bottom of the bounds (keeping our new line "in phase" with the dash,
157 // hence the (mod intervalLength).
158
159 if (minY < bounds.fTop) {
160 minY = bounds.fTop - SkScalarMod(bounds.fTop - minY, intervalLength);
161 }
162 if (maxY > bounds.fBottom) {
163 maxY = bounds.fBottom + SkScalarMod(maxY - bounds.fBottom, intervalLength);
164 }
165
166 SkASSERT(maxY > minY);
167 if (dy < 0) {
168 using std::swap;
169 swap(minY, maxY);
170 }
171 pts[0].fY = minY;
172 pts[1].fY = maxY;
173 }
174
175 return true;
176}
177
178// Currently asPoints is more restrictive then it needs to be. In the future
179// we need to:
180// allow kRound_Cap capping (could allow rotations in the matrix with this)
181// allow paths to be returned
182bool SkDashImpl::onAsPoints(PointData* results, const SkPath& src, const SkStrokeRec& rec,
183 const SkMatrix& matrix, const SkRect* cullRect) const {
184 // width < 0 -> fill && width == 0 -> hairline so requiring width > 0 rules both out
185 if (0 >= rec.getWidth()) {
186 return false;
187 }
188
189 // TODO: this next test could be eased up. We could allow any number of
190 // intervals as long as all the ons match and all the offs match.
191 // Additionally, they do not necessarily need to be integers.
192 // We cannot allow arbitrary intervals since we want the returned points
193 // to be uniformly sized.
194 if (fCount != 2 ||
195 !SkScalarNearlyEqual(fIntervals[0], fIntervals[1]) ||
196 !SkScalarIsInt(fIntervals[0]) ||
197 !SkScalarIsInt(fIntervals[1])) {
198 return false;
199 }
200
201 SkPoint pts[2];
202
203 if (!src.isLine(pts)) {
204 return false;
205 }
206
207 // TODO: this test could be eased up to allow circles
208 if (SkPaint::kButt_Cap != rec.getCap()) {
209 return false;
210 }
211
212 // TODO: this test could be eased up for circles. Rotations could be allowed.
213 if (!matrix.rectStaysRect()) {
214 return false;
215 }
216
217 // See if the line can be limited to something plausible.
218 if (!cull_line(pts, rec, matrix, cullRect, fIntervalLength)) {
219 return false;
220 }
221
222 SkScalar length = SkPoint::Distance(pts[1], pts[0]);
223
224 SkVector tangent = pts[1] - pts[0];
225 if (tangent.isZero()) {
226 return false;
227 }
228
229 tangent.scale(SkScalarInvert(length));
230
231 // TODO: make this test for horizontal & vertical lines more robust
232 bool isXAxis = true;
233 if (SkScalarNearlyEqual(SK_Scalar1, tangent.fX) ||
234 SkScalarNearlyEqual(-SK_Scalar1, tangent.fX)) {
235 results->fSize.set(SkScalarHalf(fIntervals[0]), SkScalarHalf(rec.getWidth()));
236 } else if (SkScalarNearlyEqual(SK_Scalar1, tangent.fY) ||
237 SkScalarNearlyEqual(-SK_Scalar1, tangent.fY)) {
238 results->fSize.set(SkScalarHalf(rec.getWidth()), SkScalarHalf(fIntervals[0]));
239 isXAxis = false;
240 } else if (SkPaint::kRound_Cap != rec.getCap()) {
241 // Angled lines don't have axis-aligned boxes.
242 return false;
243 }
244
245 if (results) {
246 results->fFlags = 0;
247 SkScalar clampedInitialDashLength = std::min(length, fInitialDashLength);
248
249 if (SkPaint::kRound_Cap == rec.getCap()) {
251 }
252
253 results->fNumPoints = 0;
254 SkScalar len2 = length;
255 if (clampedInitialDashLength > 0 || 0 == fInitialDashIndex) {
256 SkASSERT(len2 >= clampedInitialDashLength);
257 if (0 == fInitialDashIndex) {
258 if (clampedInitialDashLength > 0) {
259 if (clampedInitialDashLength >= fIntervals[0]) {
260 ++results->fNumPoints; // partial first dash
261 }
262 len2 -= clampedInitialDashLength;
263 }
264 len2 -= fIntervals[1]; // also skip first space
265 if (len2 < 0) {
266 len2 = 0;
267 }
268 } else {
269 len2 -= clampedInitialDashLength; // skip initial partial empty
270 }
271 }
272 // Too many midpoints can cause results->fNumPoints to overflow or
273 // otherwise cause the results->fPoints allocation below to OOM.
274 // Cap it to a sane value.
275 SkScalar numIntervals = len2 / fIntervalLength;
276 if (!SkIsFinite(numIntervals) || numIntervals > SkDashPath::kMaxDashCount) {
277 return false;
278 }
279 int numMidPoints = SkScalarFloorToInt(numIntervals);
280 results->fNumPoints += numMidPoints;
281 len2 -= numMidPoints * fIntervalLength;
282 bool partialLast = false;
283 if (len2 > 0) {
284 if (len2 < fIntervals[0]) {
285 partialLast = true;
286 } else {
287 ++numMidPoints;
288 ++results->fNumPoints;
289 }
290 }
291
292 results->fPoints = new SkPoint[results->fNumPoints];
293
294 SkScalar distance = 0;
295 int curPt = 0;
296
297 if (clampedInitialDashLength > 0 || 0 == fInitialDashIndex) {
298 SkASSERT(clampedInitialDashLength <= length);
299
300 if (0 == fInitialDashIndex) {
301 if (clampedInitialDashLength > 0) {
302 // partial first block
303 SkASSERT(SkPaint::kRound_Cap != rec.getCap()); // can't handle partial circles
304 SkScalar x = pts[0].fX + tangent.fX * SkScalarHalf(clampedInitialDashLength);
305 SkScalar y = pts[0].fY + tangent.fY * SkScalarHalf(clampedInitialDashLength);
306 SkScalar halfWidth, halfHeight;
307 if (isXAxis) {
308 halfWidth = SkScalarHalf(clampedInitialDashLength);
309 halfHeight = SkScalarHalf(rec.getWidth());
310 } else {
311 halfWidth = SkScalarHalf(rec.getWidth());
312 halfHeight = SkScalarHalf(clampedInitialDashLength);
313 }
314 if (clampedInitialDashLength < fIntervals[0]) {
315 // This one will not be like the others
316 results->fFirst.addRect(x - halfWidth, y - halfHeight,
317 x + halfWidth, y + halfHeight);
318 } else {
319 SkASSERT(curPt < results->fNumPoints);
320 results->fPoints[curPt].set(x, y);
321 ++curPt;
322 }
323
324 distance += clampedInitialDashLength;
325 }
326
327 distance += fIntervals[1]; // skip over the next blank block too
328 } else {
329 distance += clampedInitialDashLength;
330 }
331 }
332
333 if (0 != numMidPoints) {
334 distance += SkScalarHalf(fIntervals[0]);
335
336 for (int i = 0; i < numMidPoints; ++i) {
337 SkScalar x = pts[0].fX + tangent.fX * distance;
338 SkScalar y = pts[0].fY + tangent.fY * distance;
339
340 SkASSERT(curPt < results->fNumPoints);
341 results->fPoints[curPt].set(x, y);
342 ++curPt;
343
344 distance += fIntervalLength;
345 }
346
347 distance -= SkScalarHalf(fIntervals[0]);
348 }
349
350 if (partialLast) {
351 // partial final block
352 SkASSERT(SkPaint::kRound_Cap != rec.getCap()); // can't handle partial circles
353 SkScalar temp = length - distance;
354 SkASSERT(temp < fIntervals[0]);
355 SkScalar x = pts[0].fX + tangent.fX * (distance + SkScalarHalf(temp));
356 SkScalar y = pts[0].fY + tangent.fY * (distance + SkScalarHalf(temp));
357 SkScalar halfWidth, halfHeight;
358 if (isXAxis) {
359 halfWidth = SkScalarHalf(temp);
360 halfHeight = SkScalarHalf(rec.getWidth());
361 } else {
362 halfWidth = SkScalarHalf(rec.getWidth());
363 halfHeight = SkScalarHalf(temp);
364 }
365 results->fLast.addRect(x - halfWidth, y - halfHeight,
366 x + halfWidth, y + halfHeight);
367 }
368
369 SkASSERT(curPt == results->fNumPoints);
370 }
371
372 return true;
373}
374
376 if (info) {
377 if (info->fCount >= fCount && info->fIntervals) {
378 memcpy(info->fIntervals, fIntervals, fCount * sizeof(SkScalar));
379 }
380 info->fCount = fCount;
381 info->fPhase = fPhase;
382 }
383 return kDash_DashType;
384}
385
387 buffer.writeScalar(fPhase);
388 buffer.writeScalarArray(fIntervals, fCount);
389}
390
391sk_sp<SkFlattenable> SkDashImpl::CreateProc(SkReadBuffer& buffer) {
392 const SkScalar phase = buffer.readScalar();
393 uint32_t count = buffer.getArrayCount();
394
395 // Don't allocate gigantic buffers if there's not data for them.
396 if (!buffer.validateCanReadN<SkScalar>(count)) {
397 return nullptr;
398 }
399
401 if (buffer.readScalarArray(intervals.get(), count)) {
402 return SkDashPathEffect::Make(intervals.get(), SkToInt(count), phase);
403 }
404 return nullptr;
405}
406
407//////////////////////////////////////////////////////////////////////////////////////////////////
408
410 if (!SkDashPath::ValidDashPath(phase, intervals, count)) {
411 return nullptr;
412 }
413 return sk_sp<SkPathEffect>(new SkDashImpl(intervals, count, phase));
414}
static SkM44 inv(const SkM44 &m)
Definition: 3d.cpp:26
static void info(const char *fmt,...) SK_PRINTF_LIKE(1
Definition: DM.cpp:213
SkScalar fPhase
Definition: DashOp.cpp:189
int count
Definition: FontMgrTest.cpp:50
static constexpr bool SkIsAlign2(T x)
Definition: SkAlign.h:19
#define SkASSERT(cond)
Definition: SkAssert.h:116
static void outset_for_stroke(SkRect *rect, const SkStrokeRec &rec)
static bool cull_line(SkPoint *pts, const SkStrokeRec &rec, const SkMatrix &ctm, const SkRect *cullRect, const SkScalar intervalLength)
static bool SkIsFinite(T x, Pack... values)
SK_API void sk_free(void *)
static void * sk_malloc_throw(size_t size)
Definition: SkMalloc.h:67
void swap(sk_sp< T > &a, sk_sp< T > &b)
Definition: SkRefCnt.h:341
#define SkScalarInvert(x)
Definition: SkScalar.h:73
#define SkScalarMod(x, y)
Definition: SkScalar.h:41
static bool SkScalarNearlyEqual(SkScalar x, SkScalar y, SkScalar tolerance=SK_ScalarNearlyZero)
Definition: SkScalar.h:107
#define SK_Scalar1
Definition: SkScalar.h:18
#define SkScalarHalf(a)
Definition: SkScalar.h:75
static bool SkScalarIsInt(SkScalar x)
Definition: SkScalar.h:80
#define SkScalarFloorToInt(x)
Definition: SkScalar.h:35
constexpr int SkToInt(S x)
Definition: SkTo.h:29
SkDashImpl(const SkScalar intervals[], int count, SkScalar phase)
~SkDashImpl() override
void flatten(SkWriteBuffer &) const override
DashType onAsADash(DashInfo *info) const override
bool onAsPoints(PointData *results, const SkPath &src, const SkStrokeRec &, const SkMatrix &, const SkRect *) const override
bool onFilterPath(SkPath *dst, const SkPath &src, SkStrokeRec *, const SkRect *, const SkMatrix &) const override
static sk_sp< SkPathEffect > Make(const SkScalar intervals[], int count, SkScalar phase)
bool invert(SkMatrix *inverse) const
Definition: SkMatrix.h:1206
bool rectStaysRect() const
Definition: SkMatrix.h:271
@ kRound_Cap
adds circle
Definition: SkPaint.h:335
@ kButt_Cap
no stroke extension
Definition: SkPaint.h:334
@ kMiter_Join
extends to miter limit
Definition: SkPaint.h:359
@ kDash_DashType
fills in all of the info parameter
Definition: SkPathEffect.h:62
Definition: SkPath.h:59
SkPath & addRect(const SkRect &rect, SkPathDirection dir, unsigned start)
Definition: SkPath.cpp:864
SkScalar getWidth() const
Definition: SkStrokeRec.h:42
SkPaint::Join getJoin() const
Definition: SkStrokeRec.h:45
SkPaint::Cap getCap() const
Definition: SkStrokeRec.h:44
SkScalar getMiter() const
Definition: SkStrokeRec.h:43
float SkScalar
Definition: extension.cpp:12
static float min(float r, float g, float b)
Definition: hsl.cpp:48
size_t length
double y
double x
const SkScalar kMaxDashCount
bool InternalFilter(SkPath *dst, const SkPath &src, SkStrokeRec *rec, const SkRect *cullRect, const SkScalar aIntervals[], int32_t count, SkScalar initialDashLength, int32_t initialDashIndex, SkScalar intervalLength, SkScalar startPhase, StrokeRecApplication=StrokeRecApplication::kAllow)
Definition: SkDashPath.cpp:309
void CalcDashParameters(SkScalar phase, const SkScalar intervals[], int32_t count, SkScalar *initialDashLength, int32_t *initialDashIndex, SkScalar *intervalLength, SkScalar *adjustedPhase=nullptr)
Definition: SkDashPath.cpp:54
bool ValidDashPath(SkScalar phase, const SkScalar intervals[], int32_t count)
Definition: SkDashPath.cpp:476
unsigned useCenter Optional< SkMatrix > matrix
Definition: SkRecords.h:258
Optional< SkRect > bounds
Definition: SkRecords.h:189
sk_sp< SkBlender > blender SkRect rect
Definition: SkRecords.h:350
skia_private::AutoTArray< sk_sp< SkImageFilter > > filters TypedMatrix matrix TypedMatrix matrix SkScalar dx
Definition: SkRecords.h:208
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir Path to the cache directory This is different from the persistent_cache_path in embedder which is used for Skia shader cache icu native lib Path to the library file that exports the ICU data vm service The hostname IP address on which the Dart VM Service should be served If not defaults to or::depending on whether ipv6 is specified vm service A custom Dart VM Service port The default is to pick a randomly available open port disable vm Disable the Dart VM Service The Dart VM Service is never available in release mode disable vm service Disable mDNS Dart VM Service publication Bind to the IPv6 localhost address for the Dart VM Service Ignored if vm service host is set endless trace buffer
Definition: switches.h:126
dst
Definition: cp.py:12
bool isZero() const
Definition: SkPoint_impl.h:193
float fX
x-axis value
Definition: SkPoint_impl.h:164
void set(float x, float y)
Definition: SkPoint_impl.h:200
static float Distance(const SkPoint &a, const SkPoint &b)
Definition: SkPoint_impl.h:508
void scale(float scale, SkPoint *dst) const
Definition: SkPoint.cpp:17
float fY
y-axis value
Definition: SkPoint_impl.h:165
constexpr float y() const
Definition: SkPoint_impl.h:187
constexpr float x() const
Definition: SkPoint_impl.h:181