Flutter Engine
The Flutter Engine
SkDashPath.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2014 Google Inc.
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
11#include "include/core/SkPath.h"
15#include "include/core/SkRect.h"
23#include "src/core/SkPathPriv.h"
25
26#include <algorithm>
27#include <cmath>
28#include <cstdint>
29#include <iterator>
30
31static inline int is_even(int x) {
32 return !(x & 1);
33}
34
35static SkScalar find_first_interval(const SkScalar intervals[], SkScalar phase,
36 int32_t* index, int count) {
37 for (int i = 0; i < count; ++i) {
38 SkScalar gap = intervals[i];
39 if (phase > gap || (phase == gap && gap)) {
40 phase -= gap;
41 } else {
42 *index = i;
43 return gap - phase;
44 }
45 }
46 // If we get here, phase "appears" to be larger than our length. This
47 // shouldn't happen with perfect precision, but we can accumulate errors
48 // during the initial length computation (rounding can make our sum be too
49 // big or too small. In that event, we just have to eat the error here.
50 *index = 0;
51 return intervals[0];
52}
53
54void SkDashPath::CalcDashParameters(SkScalar phase, const SkScalar intervals[], int32_t count,
55 SkScalar* initialDashLength, int32_t* initialDashIndex,
56 SkScalar* intervalLength, SkScalar* adjustedPhase) {
57 SkScalar len = 0;
58 for (int i = 0; i < count; i++) {
59 len += intervals[i];
60 }
61 *intervalLength = len;
62 // Adjust phase to be between 0 and len, "flipping" phase if negative.
63 // e.g., if len is 100, then phase of -20 (or -120) is equivalent to 80
64 if (adjustedPhase) {
65 if (phase < 0) {
66 phase = -phase;
67 if (phase > len) {
68 phase = SkScalarMod(phase, len);
69 }
70 phase = len - phase;
71
72 // Due to finite precision, it's possible that phase == len,
73 // even after the subtract (if len >>> phase), so fix that here.
74 // This fixes http://crbug.com/124652 .
75 SkASSERT(phase <= len);
76 if (phase == len) {
77 phase = 0;
78 }
79 } else if (phase >= len) {
80 phase = SkScalarMod(phase, len);
81 }
82 *adjustedPhase = phase;
83 }
84 SkASSERT(phase >= 0 && phase < len);
85
86 *initialDashLength = find_first_interval(intervals, phase,
87 initialDashIndex, count);
88
89 SkASSERT(*initialDashLength >= 0);
90 SkASSERT(*initialDashIndex >= 0 && *initialDashIndex < count);
91}
92
93static void outset_for_stroke(SkRect* rect, const SkStrokeRec& rec) {
94 SkScalar radius = SkScalarHalf(rec.getWidth());
95 if (0 == radius) {
96 radius = SK_Scalar1; // hairlines
97 }
98 if (SkPaint::kMiter_Join == rec.getJoin()) {
99 radius *= rec.getMiter();
100 }
101 rect->outset(radius, radius);
102}
103
104// If line is zero-length, bump out the end by a tiny amount
105// to draw endcaps. The bump factor is sized so that
106// SkPoint::Distance() computes a non-zero length.
107// Offsets SK_ScalarNearlyZero or smaller create empty paths when Iter measures length.
108// Large values are scaled by SK_ScalarNearlyZero so significant bits change.
109static void adjust_zero_length_line(SkPoint pts[2]) {
110 SkASSERT(pts[0] == pts[1]);
111 pts[1].fX += std::max(1.001f, pts[1].fX) * SK_ScalarNearlyZero;
112}
113
114static bool clip_line(SkPoint pts[2], const SkRect& bounds, SkScalar intervalLength,
115 SkScalar priorPhase) {
116 SkVector dxy = pts[1] - pts[0];
117
118 // only horizontal or vertical lines
119 if (dxy.fX && dxy.fY) {
120 return false;
121 }
122 int xyOffset = SkToBool(dxy.fY); // 0 to adjust horizontal, 1 to adjust vertical
123
124 SkScalar minXY = (&pts[0].fX)[xyOffset];
125 SkScalar maxXY = (&pts[1].fX)[xyOffset];
126 bool swapped = maxXY < minXY;
127 if (swapped) {
128 using std::swap;
129 swap(minXY, maxXY);
130 }
131
132 SkASSERT(minXY <= maxXY);
133 SkScalar leftTop = (&bounds.fLeft)[xyOffset];
134 SkScalar rightBottom = (&bounds.fRight)[xyOffset];
135 if (maxXY < leftTop || minXY > rightBottom) {
136 return false;
137 }
138
139 // Now we actually perform the chop, removing the excess to the left/top and
140 // right/bottom of the bounds (keeping our new line "in phase" with the dash,
141 // hence the (mod intervalLength).
142
143 if (minXY < leftTop) {
144 minXY = leftTop - SkScalarMod(leftTop - minXY, intervalLength);
145 if (!swapped) {
146 minXY -= priorPhase; // for rectangles, adjust by prior phase
147 }
148 }
149 if (maxXY > rightBottom) {
150 maxXY = rightBottom + SkScalarMod(maxXY - rightBottom, intervalLength);
151 if (swapped) {
152 maxXY += priorPhase; // for rectangles, adjust by prior phase
153 }
154 }
155
156 SkASSERT(maxXY >= minXY);
157 if (swapped) {
158 using std::swap;
159 swap(minXY, maxXY);
160 }
161 (&pts[0].fX)[xyOffset] = minXY;
162 (&pts[1].fX)[xyOffset] = maxXY;
163
164 if (minXY == maxXY) {
166 }
167 return true;
168}
169
170// Handles only lines and rects.
171// If cull_path() returns true, dstPath is the new smaller path,
172// otherwise dstPath may have been changed but you should ignore it.
173static bool cull_path(const SkPath& srcPath, const SkStrokeRec& rec,
174 const SkRect* cullRect, SkScalar intervalLength, SkPath* dstPath) {
175 if (!cullRect) {
176 SkPoint pts[2];
177 if (srcPath.isLine(pts) && pts[0] == pts[1]) {
179 dstPath->moveTo(pts[0]);
180 dstPath->lineTo(pts[1]);
181 return true;
182 }
183 return false;
184 }
185
187 bounds = *cullRect;
189
190 {
191 SkPoint pts[2];
192 if (srcPath.isLine(pts)) {
193 if (clip_line(pts, bounds, intervalLength, 0)) {
194 dstPath->moveTo(pts[0]);
195 dstPath->lineTo(pts[1]);
196 return true;
197 }
198 return false;
199 }
200 }
201
202 if (srcPath.isRect(nullptr)) {
203 // We'll break the rect into four lines, culling each separately.
204 SkPath::Iter iter(srcPath, false);
205
206 SkPoint pts[4]; // Rects are all moveTo and lineTo, so we'll only use pts[0] and pts[1].
208
209 double accum = 0; // Sum of unculled edge lengths to keep the phase correct.
210 // Intentionally a double to minimize the risk of overflow and drift.
211 while (iter.next(pts) == SkPath::kLine_Verb) {
212 // Notice this vector v and accum work with the original unclipped length.
213 SkVector v = pts[1] - pts[0];
214
215 if (clip_line(pts, bounds, intervalLength, std::fmod(accum, intervalLength))) {
216 // pts[0] may have just been changed by clip_line().
217 // If that's not where we ended the previous lineTo(), we need to moveTo() there.
218 SkPoint last;
219 if (!dstPath->getLastPt(&last) || last != pts[0]) {
220 dstPath->moveTo(pts[0]);
221 }
222 dstPath->lineTo(pts[1]);
223 }
224
225 // We either just traveled v.fX horizontally or v.fY vertically.
226 SkASSERT(v.fX == 0 || v.fY == 0);
227 accum += SkScalarAbs(v.fX + v.fY);
228 }
229 return !dstPath->isEmpty();
230 }
231
232 return false;
233}
234
236public:
237 bool init(const SkPath& src, SkPath* dst, SkStrokeRec* rec,
238 int intervalCount, SkScalar intervalLength) {
239 if (rec->isHairlineStyle() || !src.isLine(fPts)) {
240 return false;
241 }
242
243 // can relax this in the future, if we handle square and round caps
244 if (SkPaint::kButt_Cap != rec->getCap()) {
245 return false;
246 }
247
248 SkScalar pathLength = SkPoint::Distance(fPts[0], fPts[1]);
249
250 fTangent = fPts[1] - fPts[0];
251 if (fTangent.isZero()) {
252 return false;
253 }
254
255 fPathLength = pathLength;
256 fTangent.scale(sk_ieee_float_divide(1.0f, pathLength));
257 if (!SkIsFinite(fTangent.fX, fTangent.fY)) {
258 return false;
259 }
260 SkPointPriv::RotateCCW(fTangent, &fNormal);
261 fNormal.scale(SkScalarHalf(rec->getWidth()));
262
263 // now estimate how many quads will be added to the path
264 // resulting segments = pathLen * intervalCount / intervalLen
265 // resulting points = 4 * segments
266
267 SkScalar ptCount = pathLength * intervalCount / (float)intervalLength;
268 ptCount = std::min(ptCount, SkDashPath::kMaxDashCount);
269 if (SkIsNaN(ptCount)) {
270 return false;
271 }
272 int n = SkScalarCeilToInt(ptCount) << 2;
273 dst->incReserve(n);
274
275 // we will take care of the stroking
276 rec->setFillStyle();
277 return true;
278 }
279
280 void addSegment(SkScalar d0, SkScalar d1, SkPath* path) const {
281 SkASSERT(d0 <= fPathLength);
282 // clamp the segment to our length
283 if (d1 > fPathLength) {
284 d1 = fPathLength;
285 }
286
287 SkScalar x0 = fPts[0].fX + fTangent.fX * d0;
288 SkScalar x1 = fPts[0].fX + fTangent.fX * d1;
289 SkScalar y0 = fPts[0].fY + fTangent.fY * d0;
290 SkScalar y1 = fPts[0].fY + fTangent.fY * d1;
291
292 SkPoint pts[4];
293 pts[0].set(x0 + fNormal.fX, y0 + fNormal.fY); // moveTo
294 pts[1].set(x1 + fNormal.fX, y1 + fNormal.fY); // lineTo
295 pts[2].set(x1 - fNormal.fX, y1 - fNormal.fY); // lineTo
296 pts[3].set(x0 - fNormal.fX, y0 - fNormal.fY); // lineTo
297
298 path->addPoly(pts, std::size(pts), false);
299 }
300
301private:
302 SkPoint fPts[2];
303 SkVector fTangent;
304 SkVector fNormal;
305 SkScalar fPathLength;
306};
307
308
310 const SkRect* cullRect, const SkScalar aIntervals[],
311 int32_t count, SkScalar initialDashLength, int32_t initialDashIndex,
312 SkScalar intervalLength, SkScalar startPhase,
313 StrokeRecApplication strokeRecApplication) {
314 // we must always have an even number of intervals
316
317 // we do nothing if the src wants to be filled
318 SkStrokeRec::Style style = rec->getStyle();
320 return false;
321 }
322
323 const SkScalar* intervals = aIntervals;
324 SkScalar dashCount = 0;
325 int segCount = 0;
326
327 SkPath cullPathStorage;
328 const SkPath* srcPtr = &src;
329 if (cull_path(src, *rec, cullRect, intervalLength, &cullPathStorage)) {
330 // if rect is closed, starts in a dash, and ends in a dash, add the initial join
331 // potentially a better fix is described here: bug.skia.org/7445
332 if (src.isRect(nullptr) && src.isLastContourClosed() && is_even(initialDashIndex)) {
333 SkScalar pathLength = SkPathMeasure(src, false, rec->getResScale()).getLength();
334#if defined(SK_LEGACY_RECT_DASHING_BUG)
335 SkScalar endPhase = SkScalarMod(pathLength + initialDashLength, intervalLength);
336#else
337 SkScalar endPhase = SkScalarMod(pathLength + startPhase, intervalLength);
338#endif
339 int index = 0;
340 while (endPhase > intervals[index]) {
341 endPhase -= intervals[index++];
342 SkASSERT(index <= count);
343 if (index == count) {
344 // We have run out of intervals. endPhase "should" never get to this point,
345 // but it could if the subtracts underflowed. Hence we will pin it as if it
346 // perfectly ran through the intervals.
347 // See crbug.com/875494 (and skbug.com/8274)
348 endPhase = 0;
349 break;
350 }
351 }
352 // if dash ends inside "on", or ends at beginning of "off"
353 if (is_even(index) == (endPhase > 0)) {
354 SkPoint midPoint = src.getPoint(0);
355 // get vector at end of rect
356 int last = src.countPoints() - 1;
357 while (midPoint == src.getPoint(last)) {
358 --last;
359 SkASSERT(last >= 0);
360 }
361 // get vector at start of rect
362 int next = 1;
363 while (midPoint == src.getPoint(next)) {
364 ++next;
365 SkASSERT(next < last);
366 }
367 SkVector v = midPoint - src.getPoint(last);
368 const SkScalar kTinyOffset = SK_ScalarNearlyZero;
369 // scale vector to make start of tiny right angle
370 v *= kTinyOffset;
371 cullPathStorage.moveTo(midPoint - v);
372 cullPathStorage.lineTo(midPoint);
373 v = midPoint - src.getPoint(next);
374 // scale vector to make end of tiny right angle
375 v *= kTinyOffset;
376 cullPathStorage.lineTo(midPoint - v);
377 }
378 }
379 srcPtr = &cullPathStorage;
380 }
381
382 SpecialLineRec lineRec;
383 bool specialLine = (StrokeRecApplication::kAllow == strokeRecApplication) &&
384 lineRec.init(*srcPtr, dst, rec, count >> 1, intervalLength);
385
386 SkPathMeasure meas(*srcPtr, false, rec->getResScale());
387
388 do {
389 bool skipFirstSegment = meas.isClosed();
390 bool addedSegment = false;
391 SkScalar length = meas.getLength();
392 int index = initialDashIndex;
393
394 // Since the path length / dash length ratio may be arbitrarily large, we can exert
395 // significant memory pressure while attempting to build the filtered path. To avoid this,
396 // we simply give up dashing beyond a certain threshold.
397 //
398 // The original bug report (http://crbug.com/165432) is based on a path yielding more than
399 // 90 million dash segments and crashing the memory allocator. A limit of 1 million
400 // segments seems reasonable: at 2 verbs per segment * 9 bytes per verb, this caps the
401 // maximum dash memory overhead at roughly 17MB per path.
402 dashCount += length * (count >> 1) / intervalLength;
403 if (dashCount > kMaxDashCount) {
404 dst->reset();
405 return false;
406 }
407
408 // Using double precision to avoid looping indefinitely due to single precision rounding
409 // (for extreme path_length/dash_length ratios). See test_infinite_dash() unittest.
410 double distance = 0;
411 double dlen = initialDashLength;
412
413 while (distance < length) {
414 SkASSERT(dlen >= 0);
415 addedSegment = false;
416 if (is_even(index) && !skipFirstSegment) {
417 addedSegment = true;
418 ++segCount;
419
420 if (specialLine) {
423 dst);
424 } else {
427 dst, true);
428 }
429 }
430 distance += dlen;
431
432 // clear this so we only respect it the first time around
433 skipFirstSegment = false;
434
435 // wrap around our intervals array if necessary
436 index += 1;
437 SkASSERT(index <= count);
438 if (index == count) {
439 index = 0;
440 }
441
442 // fetch our next dlen
443 dlen = intervals[index];
444 }
445
446 // extend if we ended on a segment and we need to join up with the (skipped) initial segment
447 if (meas.isClosed() && is_even(initialDashIndex) &&
448 initialDashLength >= 0) {
449 meas.getSegment(0, initialDashLength, dst, !addedSegment);
450 ++segCount;
451 }
452 } while (meas.nextContour());
453
454 // TODO: do we still need this?
455 if (segCount > 1) {
457 }
458
459 return true;
460}
461
463 const SkRect* cullRect, const SkPathEffect::DashInfo& info) {
464 if (!ValidDashPath(info.fPhase, info.fIntervals, info.fCount)) {
465 return false;
466 }
467 SkScalar initialDashLength = 0;
468 int32_t initialDashIndex = 0;
469 SkScalar intervalLength = 0;
470 CalcDashParameters(info.fPhase, info.fIntervals, info.fCount,
471 &initialDashLength, &initialDashIndex, &intervalLength);
472 return InternalFilter(dst, src, rec, cullRect, info.fIntervals, info.fCount, initialDashLength,
473 initialDashIndex, intervalLength, info.fPhase);
474}
475
476bool SkDashPath::ValidDashPath(SkScalar phase, const SkScalar intervals[], int32_t count) {
477 if (count < 2 || !SkIsAlign2(count)) {
478 return false;
479 }
480 SkScalar length = 0;
481 for (int i = 0; i < count; i++) {
482 if (intervals[i] < 0) {
483 return false;
484 }
485 length += intervals[i];
486 }
487 // watch out for values that might make us go out of bounds
488 return length > 0 && SkIsFinite(phase, length);
489}
static void info(const char *fmt,...) SK_PRINTF_LIKE(1
Definition: DM.cpp:213
SkAssertResult(font.textToGlyphs("Hello", 5, SkTextEncoding::kUTF8, glyphs, std::size(glyphs))==count)
int count
Definition: FontMgrTest.cpp:50
static float next(float f)
static constexpr bool SkIsAlign2(T x)
Definition: SkAlign.h:19
#define SkASSERT(cond)
Definition: SkAssert.h:116
static void adjust_zero_length_line(SkPoint pts[2])
Definition: SkDashPath.cpp:109
static int is_even(int x)
Definition: SkDashPath.cpp:31
static bool cull_path(const SkPath &srcPath, const SkStrokeRec &rec, const SkRect *cullRect, SkScalar intervalLength, SkPath *dstPath)
Definition: SkDashPath.cpp:173
static bool clip_line(SkPoint pts[2], const SkRect &bounds, SkScalar intervalLength, SkScalar priorPhase)
Definition: SkDashPath.cpp:114
static void outset_for_stroke(SkRect *rect, const SkStrokeRec &rec)
Definition: SkDashPath.cpp:93
static SkScalar find_first_interval(const SkScalar intervals[], SkScalar phase, int32_t *index, int count)
Definition: SkDashPath.cpp:35
static constexpr bool SkIsNaN(T x)
static bool SkIsFinite(T x, Pack... values)
static constexpr float sk_ieee_float_divide(float numer, float denom)
void swap(sk_sp< T > &a, sk_sp< T > &b)
Definition: SkRefCnt.h:341
#define SkScalarMod(x, y)
Definition: SkScalar.h:41
#define SK_Scalar1
Definition: SkScalar.h:18
#define SkScalarHalf(a)
Definition: SkScalar.h:75
#define SkDoubleToScalar(x)
Definition: SkScalar.h:64
#define SkScalarCeilToInt(x)
Definition: SkScalar.h:36
#define SK_ScalarNearlyZero
Definition: SkScalar.h:99
#define SkScalarAbs(x)
Definition: SkScalar.h:39
static constexpr bool SkToBool(const T &x)
Definition: SkTo.h:35
@ kButt_Cap
no stroke extension
Definition: SkPaint.h:334
@ kMiter_Join
extends to miter limit
Definition: SkPaint.h:359
SkScalar getLength()
bool getSegment(SkScalar startD, SkScalar stopD, SkPath *dst, bool startWithMoveTo)
static void SetConvexity(const SkPath &path, SkPathConvexity c)
Definition: SkPathPriv.h:413
Verb next(SkPoint pts[4])
Definition: SkPath.cpp:1901
Definition: SkPath.h:59
bool isEmpty() const
Definition: SkPath.cpp:416
SkPath & moveTo(SkScalar x, SkScalar y)
Definition: SkPath.cpp:688
bool isLine(SkPoint line[2]) const
Definition: SkPath.cpp:398
bool getLastPt(SkPoint *lastPt) const
Definition: SkPath.cpp:580
SkPath & lineTo(SkScalar x, SkScalar y)
Definition: SkPath.cpp:728
@ kMove_Verb
Definition: SkPath.h:1466
@ kLine_Verb
Definition: SkPath.h:1467
bool isRect(SkRect *rect, bool *isClosed=nullptr, SkPathDirection *direction=nullptr) const
Definition: SkPath.cpp:516
static void RotateCCW(const SkPoint &src, SkPoint *dst)
Definition: SkPointPriv.h:72
Style getStyle() const
Definition: SkStrokeRec.cpp:71
@ kStrokeAndFill_Style
Definition: SkStrokeRec.h:36
void setFillStyle()
Definition: SkStrokeRec.cpp:81
bool isHairlineStyle() const
Definition: SkStrokeRec.h:47
SkScalar getWidth() const
Definition: SkStrokeRec.h:42
SkPaint::Join getJoin() const
Definition: SkStrokeRec.h:45
SkPaint::Cap getCap() const
Definition: SkStrokeRec.h:44
SkScalar getResScale() const
Definition: SkStrokeRec.h:71
SkScalar getMiter() const
Definition: SkStrokeRec.h:43
bool init(const SkPath &src, SkPath *dst, SkStrokeRec *rec, int intervalCount, SkScalar intervalLength)
Definition: SkDashPath.cpp:237
void addSegment(SkScalar d0, SkScalar d1, SkPath *path) const
Definition: SkDashPath.cpp:280
float SkScalar
Definition: extension.cpp:12
static float max(float r, float g, float b)
Definition: hsl.cpp:49
static float min(float r, float g, float b)
Definition: hsl.cpp:48
size_t length
double x
const SkScalar kMaxDashCount
bool FilterDashPath(SkPath *dst, const SkPath &src, SkStrokeRec *, const SkRect *, const SkPathEffect::DashInfo &info)
Definition: SkDashPath.cpp:462
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
Optional< SkRect > bounds
Definition: SkRecords.h:189
sk_sp< SkBlender > blender SkRect rect
Definition: SkRecords.h:350
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
Definition: switches.h:57
it will be possible to load the file into Perfetto s trace viewer disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive keep the shell running after the Dart script has completed enable serial On low power devices with low core running concurrent GC tasks on threads can cause them to contend with the UI thread which could potentially lead to jank This option turns off all concurrent GC activities domain network JSON encoded network policy per domain This overrides the DisallowInsecureConnections switch Embedder can specify whether to allow or disallow insecure connections at a domain level old gen heap size
Definition: switches.h:259
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