1 /*
2 * Copyright (C) 2012 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 #define LOG_NDEBUG 1
17
18 #define VERTEX_DEBUG 0
19
20 #if VERTEX_DEBUG
21 #define DEBUG_DUMP_ALPHA_BUFFER() \
22 for (unsigned int i = 0; i < vertexBuffer.getSize(); i++) { \
23 ALOGD("point %d at %f %f, alpha %f", i, buffer[i].x, buffer[i].y, buffer[i].alpha); \
24 }
25 #define DEBUG_DUMP_BUFFER() \
26 for (unsigned int i = 0; i < vertexBuffer.getSize(); i++) { \
27 ALOGD("point %d at %f %f", i, buffer[i].x, buffer[i].y); \
28 }
29 #else
30 #define DEBUG_DUMP_ALPHA_BUFFER()
31 #define DEBUG_DUMP_BUFFER()
32 #endif
33
34 #include "PathTessellator.h"
35
36 #include "Matrix.h"
37 #include "Vector.h"
38 #include "Vertex.h"
39 #include "utils/MathUtils.h"
40
41 #include <algorithm>
42
43 #include <SkGeometry.h> // WARNING: Internal Skia Header
44 #include <SkPaint.h>
45 #include <SkPath.h>
46 #include <SkPoint.h>
47
48 #include <stdint.h>
49 #include <stdlib.h>
50 #include <sys/types.h>
51
52 #include <utils/Log.h>
53 #include <utils/Trace.h>
54
55 namespace android {
56 namespace uirenderer {
57
58 #define OUTLINE_REFINE_THRESHOLD 0.5f
59 #define ROUND_CAP_THRESH 0.25f
60 #define PI 3.1415926535897932f
61 #define MAX_DEPTH 15
62
63 /**
64 * Extracts the x and y scale from the transform as positive values, and clamps them
65 */
extractTessellationScales(const Matrix4 & transform,float * scaleX,float * scaleY)66 void PathTessellator::extractTessellationScales(const Matrix4& transform, float* scaleX,
67 float* scaleY) {
68 if (CC_LIKELY(transform.isPureTranslate())) {
69 *scaleX = 1.0f;
70 *scaleY = 1.0f;
71 } else {
72 float m00 = transform.data[Matrix4::kScaleX];
73 float m01 = transform.data[Matrix4::kSkewY];
74 float m10 = transform.data[Matrix4::kSkewX];
75 float m11 = transform.data[Matrix4::kScaleY];
76 *scaleX = MathUtils::clampTessellationScale(sqrt(m00 * m00 + m01 * m01));
77 *scaleY = MathUtils::clampTessellationScale(sqrt(m10 * m10 + m11 * m11));
78 }
79 }
80
81 /**
82 * Produces a pseudo-normal for a vertex, given the normals of the two incoming lines. If the offset
83 * from each vertex in a perimeter is calculated, the resultant lines connecting the offset vertices
84 * will be offset by 1.0
85 *
86 * Note that we can't add and normalize the two vectors, that would result in a rectangle having an
87 * offset of (sqrt(2)/2, sqrt(2)/2) at each corner, instead of (1, 1)
88 *
89 * NOTE: assumes angles between normals 90 degrees or less
90 */
totalOffsetFromNormals(const Vector2 & normalA,const Vector2 & normalB)91 inline static Vector2 totalOffsetFromNormals(const Vector2& normalA, const Vector2& normalB) {
92 return (normalA + normalB) / (1 + fabs(normalA.dot(normalB)));
93 }
94
95 /**
96 * Structure used for storing useful information about the SkPaint and scale used for tessellating
97 */
98 struct PaintInfo {
99 public:
PaintInfoandroid::uirenderer::PaintInfo100 PaintInfo(const SkPaint* paint, const mat4& transform)
101 : style(paint->getStyle())
102 , cap(paint->getStrokeCap())
103 , isAA(paint->isAntiAlias())
104 , halfStrokeWidth(paint->getStrokeWidth() * 0.5f)
105 , maxAlpha(1.0f) {
106 // compute inverse scales
107 if (CC_LIKELY(transform.isPureTranslate())) {
108 inverseScaleX = 1.0f;
109 inverseScaleY = 1.0f;
110 } else {
111 float scaleX, scaleY;
112 PathTessellator::extractTessellationScales(transform, &scaleX, &scaleY);
113 inverseScaleX = 1.0f / scaleX;
114 inverseScaleY = 1.0f / scaleY;
115 }
116
117 if (isAA && halfStrokeWidth != 0 && inverseScaleX == inverseScaleY &&
118 2 * halfStrokeWidth < inverseScaleX) {
119 // AA, with non-hairline stroke, width < 1 pixel. Scale alpha and treat as hairline.
120 maxAlpha *= (2 * halfStrokeWidth) / inverseScaleX;
121 halfStrokeWidth = 0.0f;
122 }
123 }
124
125 SkPaint::Style style;
126 SkPaint::Cap cap;
127 bool isAA;
128 float inverseScaleX;
129 float inverseScaleY;
130 float halfStrokeWidth;
131 float maxAlpha;
132
scaleOffsetForStrokeWidthandroid::uirenderer::PaintInfo133 inline void scaleOffsetForStrokeWidth(Vector2& offset) const {
134 if (halfStrokeWidth == 0.0f) {
135 // hairline - compensate for scale
136 offset.x *= 0.5f * inverseScaleX;
137 offset.y *= 0.5f * inverseScaleY;
138 } else {
139 offset *= halfStrokeWidth;
140 }
141 }
142
143 /**
144 * NOTE: the input will not always be a normal, especially for sharp edges - it should be the
145 * result of totalOffsetFromNormals (see documentation there)
146 */
deriveAAOffsetandroid::uirenderer::PaintInfo147 inline Vector2 deriveAAOffset(const Vector2& offset) const {
148 return (Vector2){offset.x * 0.5f * inverseScaleX, offset.y * 0.5f * inverseScaleY};
149 }
150
151 /**
152 * Returns the number of cap divisions beyond the minimum 2 (kButt_Cap/kSquareCap will return 0)
153 * Should only be used when stroking and drawing caps
154 */
capExtraDivisionsandroid::uirenderer::PaintInfo155 inline int capExtraDivisions() const {
156 if (cap == SkPaint::kRound_Cap) {
157 // always use 2 points for hairline
158 if (halfStrokeWidth == 0.0f) return 2;
159
160 float threshold = std::min(inverseScaleX, inverseScaleY) * ROUND_CAP_THRESH;
161 return MathUtils::divisionsNeededToApproximateArc(halfStrokeWidth, PI, threshold);
162 }
163 return 0;
164 }
165
166 /**
167 * Outset the bounds of point data (for line endpoints or points) to account for stroke
168 * geometry.
169 *
170 * bounds are in pre-scaled space.
171 */
expandBoundsForStrokeandroid::uirenderer::PaintInfo172 void expandBoundsForStroke(Rect* bounds) const {
173 if (halfStrokeWidth == 0) {
174 // hairline, outset by (0.5f + fudge factor) in post-scaling space
175 bounds->outset(fabs(inverseScaleX) * (0.5f + Vertex::GeometryFudgeFactor()),
176 fabs(inverseScaleY) * (0.5f + Vertex::GeometryFudgeFactor()));
177 } else {
178 // non hairline, outset by half stroke width pre-scaled, and fudge factor post scaled
179 bounds->outset(halfStrokeWidth + fabs(inverseScaleX) * Vertex::GeometryFudgeFactor(),
180 halfStrokeWidth + fabs(inverseScaleY) * Vertex::GeometryFudgeFactor());
181 }
182 }
183 };
184
getFillVerticesFromPerimeter(const std::vector<Vertex> & perimeter,VertexBuffer & vertexBuffer)185 void getFillVerticesFromPerimeter(const std::vector<Vertex>& perimeter,
186 VertexBuffer& vertexBuffer) {
187 Vertex* buffer = vertexBuffer.alloc<Vertex>(perimeter.size());
188
189 int currentIndex = 0;
190 // zig zag between all previous points on the inside of the hull to create a
191 // triangle strip that fills the hull
192 int srcAindex = 0;
193 int srcBindex = perimeter.size() - 1;
194 while (srcAindex <= srcBindex) {
195 buffer[currentIndex++] = perimeter[srcAindex];
196 if (srcAindex == srcBindex) break;
197 buffer[currentIndex++] = perimeter[srcBindex];
198 srcAindex++;
199 srcBindex--;
200 }
201 }
202
203 /*
204 * Fills a vertexBuffer with non-alpha vertices, zig-zagging at each perimeter point to create a
205 * tri-strip as wide as the stroke.
206 *
207 * Uses an additional 2 vertices at the end to wrap around, closing the tri-strip
208 * (for a total of perimeter.size() * 2 + 2 vertices)
209 */
getStrokeVerticesFromPerimeter(const PaintInfo & paintInfo,const std::vector<Vertex> & perimeter,VertexBuffer & vertexBuffer)210 void getStrokeVerticesFromPerimeter(const PaintInfo& paintInfo,
211 const std::vector<Vertex>& perimeter,
212 VertexBuffer& vertexBuffer) {
213 Vertex* buffer = vertexBuffer.alloc<Vertex>(perimeter.size() * 2 + 2);
214
215 int currentIndex = 0;
216 const Vertex* last = &(perimeter[perimeter.size() - 1]);
217 const Vertex* current = &(perimeter[0]);
218 Vector2 lastNormal = {current->y - last->y, last->x - current->x};
219 lastNormal.normalize();
220 for (unsigned int i = 0; i < perimeter.size(); i++) {
221 const Vertex* next = &(perimeter[i + 1 >= perimeter.size() ? 0 : i + 1]);
222 Vector2 nextNormal = {next->y - current->y, current->x - next->x};
223 nextNormal.normalize();
224
225 Vector2 totalOffset = totalOffsetFromNormals(lastNormal, nextNormal);
226 paintInfo.scaleOffsetForStrokeWidth(totalOffset);
227
228 Vertex::set(&buffer[currentIndex++], current->x + totalOffset.x,
229 current->y + totalOffset.y);
230
231 Vertex::set(&buffer[currentIndex++], current->x - totalOffset.x,
232 current->y - totalOffset.y);
233
234 current = next;
235 lastNormal = nextNormal;
236 }
237
238 // wrap around to beginning
239 buffer[currentIndex++] = buffer[0];
240 buffer[currentIndex++] = buffer[1];
241
242 DEBUG_DUMP_BUFFER();
243 }
244
storeBeginEnd(const PaintInfo & paintInfo,const Vertex & center,const Vector2 & normal,Vertex * buffer,int & currentIndex,bool begin)245 static inline void storeBeginEnd(const PaintInfo& paintInfo, const Vertex& center,
246 const Vector2& normal, Vertex* buffer, int& currentIndex,
247 bool begin) {
248 Vector2 strokeOffset = normal;
249 paintInfo.scaleOffsetForStrokeWidth(strokeOffset);
250
251 Vector2 referencePoint = {center.x, center.y};
252 if (paintInfo.cap == SkPaint::kSquare_Cap) {
253 Vector2 rotated = {-strokeOffset.y, strokeOffset.x};
254 referencePoint += rotated * (begin ? -1 : 1);
255 }
256
257 Vertex::set(&buffer[currentIndex++], referencePoint + strokeOffset);
258 Vertex::set(&buffer[currentIndex++], referencePoint - strokeOffset);
259 }
260
261 /**
262 * Fills a vertexBuffer with non-alpha vertices similar to getStrokeVerticesFromPerimeter, except:
263 *
264 * 1 - Doesn't need to wrap around, since the input vertices are unclosed
265 *
266 * 2 - can zig-zag across 'extra' vertices at either end, to create round caps
267 */
getStrokeVerticesFromUnclosedVertices(const PaintInfo & paintInfo,const std::vector<Vertex> & vertices,VertexBuffer & vertexBuffer)268 void getStrokeVerticesFromUnclosedVertices(const PaintInfo& paintInfo,
269 const std::vector<Vertex>& vertices,
270 VertexBuffer& vertexBuffer) {
271 const int extra = paintInfo.capExtraDivisions();
272 const int allocSize = (vertices.size() + extra) * 2;
273 Vertex* buffer = vertexBuffer.alloc<Vertex>(allocSize);
274
275 const int lastIndex = vertices.size() - 1;
276 if (extra > 0) {
277 // tessellate both round caps
278 float beginTheta = atan2(-(vertices[0].x - vertices[1].x), vertices[0].y - vertices[1].y);
279 float endTheta = atan2(-(vertices[lastIndex].x - vertices[lastIndex - 1].x),
280 vertices[lastIndex].y - vertices[lastIndex - 1].y);
281 const float dTheta = PI / (extra + 1);
282
283 int capOffset;
284 for (int i = 0; i < extra; i++) {
285 if (i < extra / 2) {
286 capOffset = extra - 2 * i - 1;
287 } else {
288 capOffset = 2 * i - extra;
289 }
290
291 beginTheta += dTheta;
292 Vector2 beginRadialOffset = {cosf(beginTheta), sinf(beginTheta)};
293 paintInfo.scaleOffsetForStrokeWidth(beginRadialOffset);
294 Vertex::set(&buffer[capOffset], vertices[0].x + beginRadialOffset.x,
295 vertices[0].y + beginRadialOffset.y);
296
297 endTheta += dTheta;
298 Vector2 endRadialOffset = {cosf(endTheta), sinf(endTheta)};
299 paintInfo.scaleOffsetForStrokeWidth(endRadialOffset);
300 Vertex::set(&buffer[allocSize - 1 - capOffset],
301 vertices[lastIndex].x + endRadialOffset.x,
302 vertices[lastIndex].y + endRadialOffset.y);
303 }
304 }
305
306 int currentIndex = extra;
307 const Vertex* last = &(vertices[0]);
308 const Vertex* current = &(vertices[1]);
309 Vector2 lastNormal = {current->y - last->y, last->x - current->x};
310 lastNormal.normalize();
311
312 storeBeginEnd(paintInfo, vertices[0], lastNormal, buffer, currentIndex, true);
313
314 for (unsigned int i = 1; i < vertices.size() - 1; i++) {
315 const Vertex* next = &(vertices[i + 1]);
316 Vector2 nextNormal = {next->y - current->y, current->x - next->x};
317 nextNormal.normalize();
318
319 Vector2 strokeOffset = totalOffsetFromNormals(lastNormal, nextNormal);
320 paintInfo.scaleOffsetForStrokeWidth(strokeOffset);
321
322 Vector2 center = {current->x, current->y};
323 Vertex::set(&buffer[currentIndex++], center + strokeOffset);
324 Vertex::set(&buffer[currentIndex++], center - strokeOffset);
325
326 current = next;
327 lastNormal = nextNormal;
328 }
329
330 storeBeginEnd(paintInfo, vertices[lastIndex], lastNormal, buffer, currentIndex, false);
331
332 DEBUG_DUMP_BUFFER();
333 }
334
335 /**
336 * Populates a vertexBuffer with AlphaVertices to create an anti-aliased fill shape tessellation
337 *
338 * 1 - create the AA perimeter of unit width, by zig-zagging at each point around the perimeter of
339 * the shape (using 2 * perimeter.size() vertices)
340 *
341 * 2 - wrap around to the beginning to complete the perimeter (2 vertices)
342 *
343 * 3 - zig zag back and forth inside the shape to fill it (using perimeter.size() vertices)
344 */
getFillVerticesFromPerimeterAA(const PaintInfo & paintInfo,const std::vector<Vertex> & perimeter,VertexBuffer & vertexBuffer,float maxAlpha=1.0f)345 void getFillVerticesFromPerimeterAA(const PaintInfo& paintInfo,
346 const std::vector<Vertex>& perimeter,
347 VertexBuffer& vertexBuffer, float maxAlpha = 1.0f) {
348 AlphaVertex* buffer = vertexBuffer.alloc<AlphaVertex>(perimeter.size() * 3 + 2);
349
350 // generate alpha points - fill Alpha vertex gaps in between each point with
351 // alpha 0 vertex, offset by a scaled normal.
352 int currentIndex = 0;
353 const Vertex* last = &(perimeter[perimeter.size() - 1]);
354 const Vertex* current = &(perimeter[0]);
355 Vector2 lastNormal = {current->y - last->y, last->x - current->x};
356 lastNormal.normalize();
357 for (unsigned int i = 0; i < perimeter.size(); i++) {
358 const Vertex* next = &(perimeter[i + 1 >= perimeter.size() ? 0 : i + 1]);
359 Vector2 nextNormal = {next->y - current->y, current->x - next->x};
360 nextNormal.normalize();
361
362 // AA point offset from original point is that point's normal, such that each side is offset
363 // by .5 pixels
364 Vector2 totalOffset =
365 paintInfo.deriveAAOffset(totalOffsetFromNormals(lastNormal, nextNormal));
366
367 AlphaVertex::set(&buffer[currentIndex++], current->x + totalOffset.x,
368 current->y + totalOffset.y, 0.0f);
369 AlphaVertex::set(&buffer[currentIndex++], current->x - totalOffset.x,
370 current->y - totalOffset.y, maxAlpha);
371
372 current = next;
373 lastNormal = nextNormal;
374 }
375
376 // wrap around to beginning
377 buffer[currentIndex++] = buffer[0];
378 buffer[currentIndex++] = buffer[1];
379
380 // zig zag between all previous points on the inside of the hull to create a
381 // triangle strip that fills the hull, repeating the first inner point to
382 // create degenerate tris to start inside path
383 int srcAindex = 0;
384 int srcBindex = perimeter.size() - 1;
385 while (srcAindex <= srcBindex) {
386 buffer[currentIndex++] = buffer[srcAindex * 2 + 1];
387 if (srcAindex == srcBindex) break;
388 buffer[currentIndex++] = buffer[srcBindex * 2 + 1];
389 srcAindex++;
390 srcBindex--;
391 }
392
393 DEBUG_DUMP_BUFFER();
394 }
395
396 /**
397 * Stores geometry for a single, AA-perimeter (potentially rounded) cap
398 *
399 * For explanation of constants and general methodoloyg, see comments for
400 * getStrokeVerticesFromUnclosedVerticesAA() below.
401 */
storeCapAA(const PaintInfo & paintInfo,const std::vector<Vertex> & vertices,AlphaVertex * buffer,bool isFirst,Vector2 normal,int offset)402 inline static void storeCapAA(const PaintInfo& paintInfo, const std::vector<Vertex>& vertices,
403 AlphaVertex* buffer, bool isFirst, Vector2 normal, int offset) {
404 const int extra = paintInfo.capExtraDivisions();
405 const int extraOffset = (extra + 1) / 2;
406 const int capIndex =
407 isFirst ? 2 * offset + 6 + 2 * (extra + extraOffset) : offset + 2 + 2 * extraOffset;
408 if (isFirst) normal *= -1;
409
410 // TODO: this normal should be scaled by radialScale if extra != 0, see totalOffsetFromNormals()
411 Vector2 AAOffset = paintInfo.deriveAAOffset(normal);
412
413 Vector2 strokeOffset = normal;
414 paintInfo.scaleOffsetForStrokeWidth(strokeOffset);
415 Vector2 outerOffset = strokeOffset + AAOffset;
416 Vector2 innerOffset = strokeOffset - AAOffset;
417
418 Vector2 capAAOffset = {0, 0};
419 if (paintInfo.cap != SkPaint::kRound_Cap) {
420 // if the cap is square or butt, the inside primary cap vertices will be inset in two
421 // directions - both normal to the stroke, and parallel to it.
422 capAAOffset = (Vector2){-AAOffset.y, AAOffset.x};
423 }
424
425 // determine referencePoint, the center point for the 4 primary cap vertices
426 const Vertex& point = isFirst ? vertices.front() : vertices.back();
427 Vector2 referencePoint = {point.x, point.y};
428 if (paintInfo.cap == SkPaint::kSquare_Cap) {
429 // To account for square cap, move the primary cap vertices (that create the AA edge) by the
430 // stroke offset vector (rotated to be parallel to the stroke)
431 Vector2 rotated = {-strokeOffset.y, strokeOffset.x};
432 referencePoint += rotated;
433 }
434
435 AlphaVertex::set(&buffer[capIndex + 0], referencePoint.x + outerOffset.x + capAAOffset.x,
436 referencePoint.y + outerOffset.y + capAAOffset.y, 0.0f);
437 AlphaVertex::set(&buffer[capIndex + 1], referencePoint.x + innerOffset.x - capAAOffset.x,
438 referencePoint.y + innerOffset.y - capAAOffset.y, paintInfo.maxAlpha);
439
440 bool isRound = paintInfo.cap == SkPaint::kRound_Cap;
441
442 const int postCapIndex = (isRound && isFirst) ? (2 * extraOffset - 2) : capIndex + (2 * extra);
443 AlphaVertex::set(&buffer[postCapIndex + 2], referencePoint.x - outerOffset.x + capAAOffset.x,
444 referencePoint.y - outerOffset.y + capAAOffset.y, 0.0f);
445 AlphaVertex::set(&buffer[postCapIndex + 3], referencePoint.x - innerOffset.x - capAAOffset.x,
446 referencePoint.y - innerOffset.y - capAAOffset.y, paintInfo.maxAlpha);
447
448 if (isRound) {
449 const float dTheta = PI / (extra + 1);
450 const float radialScale = 2.0f / (1 + cos(dTheta));
451 float theta = atan2(normal.y, normal.x);
452 int capPerimIndex = capIndex + 2;
453
454 for (int i = 0; i < extra; i++) {
455 theta += dTheta;
456
457 Vector2 radialOffset = {cosf(theta), sinf(theta)};
458
459 // scale to compensate for pinching at sharp angles, see totalOffsetFromNormals()
460 radialOffset *= radialScale;
461
462 AAOffset = paintInfo.deriveAAOffset(radialOffset);
463 paintInfo.scaleOffsetForStrokeWidth(radialOffset);
464 AlphaVertex::set(&buffer[capPerimIndex++],
465 referencePoint.x + radialOffset.x + AAOffset.x,
466 referencePoint.y + radialOffset.y + AAOffset.y, 0.0f);
467 AlphaVertex::set(&buffer[capPerimIndex++],
468 referencePoint.x + radialOffset.x - AAOffset.x,
469 referencePoint.y + radialOffset.y - AAOffset.y, paintInfo.maxAlpha);
470
471 if (isFirst && i == extra - extraOffset) {
472 // copy most recent two points to first two points
473 buffer[0] = buffer[capPerimIndex - 2];
474 buffer[1] = buffer[capPerimIndex - 1];
475
476 capPerimIndex = 2; // start writing the rest of the round cap at index 2
477 }
478 }
479
480 if (isFirst) {
481 const int startCapFillIndex = capIndex + 2 * (extra - extraOffset) + 4;
482 int capFillIndex = startCapFillIndex;
483 for (int i = 0; i < extra + 2; i += 2) {
484 buffer[capFillIndex++] = buffer[1 + i];
485 // TODO: to support odd numbers of divisions, break here on the last iteration
486 buffer[capFillIndex++] = buffer[startCapFillIndex - 3 - i];
487 }
488 } else {
489 int capFillIndex = 6 * vertices.size() + 2 + 6 * extra - (extra + 2);
490 for (int i = 0; i < extra + 2; i += 2) {
491 buffer[capFillIndex++] = buffer[capIndex + 1 + i];
492 // TODO: to support odd numbers of divisions, break here on the last iteration
493 buffer[capFillIndex++] = buffer[capIndex + 3 + 2 * extra - i];
494 }
495 }
496 return;
497 }
498 if (isFirst) {
499 buffer[0] = buffer[postCapIndex + 2];
500 buffer[1] = buffer[postCapIndex + 3];
501 buffer[postCapIndex + 4] = buffer[1]; // degenerate tris (the only two!)
502 buffer[postCapIndex + 5] = buffer[postCapIndex + 1];
503 } else {
504 buffer[6 * vertices.size()] = buffer[postCapIndex + 1];
505 buffer[6 * vertices.size() + 1] = buffer[postCapIndex + 3];
506 }
507 }
508
509 /*
510 the geometry for an aa, capped stroke consists of the following:
511
512 # vertices | function
513 ----------------------------------------------------------------------
514 a) 2 | Start AA perimeter
515 b) 2, 2 * roundDivOff | First half of begin cap's perimeter
516 |
517 2 * middlePts | 'Outer' or 'Top' AA perimeter half (between caps)
518 |
519 a) 4 | End cap's
520 b) 2, 2 * roundDivs, 2 | AA perimeter
521 |
522 2 * middlePts | 'Inner' or 'bottom' AA perimeter half
523 |
524 a) 6 | Begin cap's perimeter
525 b) 2, 2*(rD - rDO + 1), | Last half of begin cap's perimeter
526 roundDivs, 2 |
527 |
528 2 * middlePts | Stroke's full opacity center strip
529 |
530 a) 2 | end stroke
531 b) 2, roundDivs | (and end cap fill, for round)
532
533 Notes:
534 * rows starting with 'a)' denote the Butt or Square cap vertex use, 'b)' denote Round
535
536 * 'middlePts' is (number of points in the unclosed input vertex list, minus 2) times two
537
538 * 'roundDivs' or 'rD' is the number of extra vertices (beyond the minimum of 2) that define the
539 round cap's shape, and is at least two. This will increase with cap size to sufficiently
540 define the cap's level of tessellation.
541
542 * 'roundDivOffset' or 'rDO' is the point about halfway along the start cap's round perimeter, where
543 the stream of vertices for the AA perimeter starts. By starting and ending the perimeter at
544 this offset, the fill of the stroke is drawn from this point with minimal extra vertices.
545
546 This means the outer perimeter starts at:
547 outerIndex = (2) OR (2 + 2 * roundDivOff)
548 the inner perimeter (since it is filled in reverse) starts at:
549 innerIndex = outerIndex + (4 * middlePts) + ((4) OR (4 + 2 * roundDivs)) - 1
550 the stroke starts at:
551 strokeIndex = innerIndex + 1 + ((6) OR (6 + 3 * roundDivs - 2 * roundDivOffset))
552
553 The total needed allocated space is either:
554 2 + 4 + 6 + 2 + 3 * (2 * middlePts) = 14 + 6 * middlePts = 2 + 6 * pts
555 or, for rounded caps:
556 (2 + 2 * rDO) + (4 + 2 * rD) + (2 * (rD - rDO + 1)
557 + roundDivs + 4) + (2 + roundDivs) + 3 * (2 * middlePts)
558 = 14 + 6 * middlePts + 6 * roundDivs
559 = 2 + 6 * pts + 6 * roundDivs
560 */
getStrokeVerticesFromUnclosedVerticesAA(const PaintInfo & paintInfo,const std::vector<Vertex> & vertices,VertexBuffer & vertexBuffer)561 void getStrokeVerticesFromUnclosedVerticesAA(const PaintInfo& paintInfo,
562 const std::vector<Vertex>& vertices,
563 VertexBuffer& vertexBuffer) {
564 const int extra = paintInfo.capExtraDivisions();
565 const int allocSize = 6 * vertices.size() + 2 + 6 * extra;
566
567 AlphaVertex* buffer = vertexBuffer.alloc<AlphaVertex>(allocSize);
568
569 const int extraOffset = (extra + 1) / 2;
570 int offset = 2 * (vertices.size() - 2);
571 // there is no outer/inner here, using them for consistency with below approach
572 int currentAAOuterIndex = 2 + 2 * extraOffset;
573 int currentAAInnerIndex = currentAAOuterIndex + (2 * offset) + 3 + (2 * extra);
574 int currentStrokeIndex = currentAAInnerIndex + 7 + (3 * extra - 2 * extraOffset);
575
576 const Vertex* last = &(vertices[0]);
577 const Vertex* current = &(vertices[1]);
578 Vector2 lastNormal = {current->y - last->y, last->x - current->x};
579 lastNormal.normalize();
580
581 // TODO: use normal from bezier traversal for cap, instead of from vertices
582 storeCapAA(paintInfo, vertices, buffer, true, lastNormal, offset);
583
584 for (unsigned int i = 1; i < vertices.size() - 1; i++) {
585 const Vertex* next = &(vertices[i + 1]);
586 Vector2 nextNormal = {next->y - current->y, current->x - next->x};
587 nextNormal.normalize();
588
589 Vector2 totalOffset = totalOffsetFromNormals(lastNormal, nextNormal);
590 Vector2 AAOffset = paintInfo.deriveAAOffset(totalOffset);
591
592 Vector2 innerOffset = totalOffset;
593 paintInfo.scaleOffsetForStrokeWidth(innerOffset);
594 Vector2 outerOffset = innerOffset + AAOffset;
595 innerOffset -= AAOffset;
596
597 AlphaVertex::set(&buffer[currentAAOuterIndex++], current->x + outerOffset.x,
598 current->y + outerOffset.y, 0.0f);
599 AlphaVertex::set(&buffer[currentAAOuterIndex++], current->x + innerOffset.x,
600 current->y + innerOffset.y, paintInfo.maxAlpha);
601
602 AlphaVertex::set(&buffer[currentStrokeIndex++], current->x + innerOffset.x,
603 current->y + innerOffset.y, paintInfo.maxAlpha);
604 AlphaVertex::set(&buffer[currentStrokeIndex++], current->x - innerOffset.x,
605 current->y - innerOffset.y, paintInfo.maxAlpha);
606
607 AlphaVertex::set(&buffer[currentAAInnerIndex--], current->x - innerOffset.x,
608 current->y - innerOffset.y, paintInfo.maxAlpha);
609 AlphaVertex::set(&buffer[currentAAInnerIndex--], current->x - outerOffset.x,
610 current->y - outerOffset.y, 0.0f);
611
612 current = next;
613 lastNormal = nextNormal;
614 }
615
616 // TODO: use normal from bezier traversal for cap, instead of from vertices
617 storeCapAA(paintInfo, vertices, buffer, false, lastNormal, offset);
618
619 DEBUG_DUMP_ALPHA_BUFFER();
620 }
621
getStrokeVerticesFromPerimeterAA(const PaintInfo & paintInfo,const std::vector<Vertex> & perimeter,VertexBuffer & vertexBuffer)622 void getStrokeVerticesFromPerimeterAA(const PaintInfo& paintInfo,
623 const std::vector<Vertex>& perimeter,
624 VertexBuffer& vertexBuffer) {
625 AlphaVertex* buffer = vertexBuffer.alloc<AlphaVertex>(6 * perimeter.size() + 8);
626
627 int offset = 2 * perimeter.size() + 3;
628 int currentAAOuterIndex = 0;
629 int currentStrokeIndex = offset;
630 int currentAAInnerIndex = offset * 2;
631
632 const Vertex* last = &(perimeter[perimeter.size() - 1]);
633 const Vertex* current = &(perimeter[0]);
634 Vector2 lastNormal = {current->y - last->y, last->x - current->x};
635 lastNormal.normalize();
636 for (unsigned int i = 0; i < perimeter.size(); i++) {
637 const Vertex* next = &(perimeter[i + 1 >= perimeter.size() ? 0 : i + 1]);
638 Vector2 nextNormal = {next->y - current->y, current->x - next->x};
639 nextNormal.normalize();
640
641 Vector2 totalOffset = totalOffsetFromNormals(lastNormal, nextNormal);
642 Vector2 AAOffset = paintInfo.deriveAAOffset(totalOffset);
643
644 Vector2 innerOffset = totalOffset;
645 paintInfo.scaleOffsetForStrokeWidth(innerOffset);
646 Vector2 outerOffset = innerOffset + AAOffset;
647 innerOffset -= AAOffset;
648
649 AlphaVertex::set(&buffer[currentAAOuterIndex++], current->x + outerOffset.x,
650 current->y + outerOffset.y, 0.0f);
651 AlphaVertex::set(&buffer[currentAAOuterIndex++], current->x + innerOffset.x,
652 current->y + innerOffset.y, paintInfo.maxAlpha);
653
654 AlphaVertex::set(&buffer[currentStrokeIndex++], current->x + innerOffset.x,
655 current->y + innerOffset.y, paintInfo.maxAlpha);
656 AlphaVertex::set(&buffer[currentStrokeIndex++], current->x - innerOffset.x,
657 current->y - innerOffset.y, paintInfo.maxAlpha);
658
659 AlphaVertex::set(&buffer[currentAAInnerIndex++], current->x - innerOffset.x,
660 current->y - innerOffset.y, paintInfo.maxAlpha);
661 AlphaVertex::set(&buffer[currentAAInnerIndex++], current->x - outerOffset.x,
662 current->y - outerOffset.y, 0.0f);
663
664 current = next;
665 lastNormal = nextNormal;
666 }
667
668 // wrap each strip around to beginning, creating degenerate tris to bridge strips
669 buffer[currentAAOuterIndex++] = buffer[0];
670 buffer[currentAAOuterIndex++] = buffer[1];
671 buffer[currentAAOuterIndex++] = buffer[1];
672
673 buffer[currentStrokeIndex++] = buffer[offset];
674 buffer[currentStrokeIndex++] = buffer[offset + 1];
675 buffer[currentStrokeIndex++] = buffer[offset + 1];
676
677 buffer[currentAAInnerIndex++] = buffer[2 * offset];
678 buffer[currentAAInnerIndex++] = buffer[2 * offset + 1];
679 // don't need to create last degenerate tri
680
681 DEBUG_DUMP_ALPHA_BUFFER();
682 }
683
tessellatePath(const SkPath & path,const SkPaint * paint,const mat4 & transform,VertexBuffer & vertexBuffer)684 void PathTessellator::tessellatePath(const SkPath& path, const SkPaint* paint,
685 const mat4& transform, VertexBuffer& vertexBuffer) {
686 ATRACE_CALL();
687
688 const PaintInfo paintInfo(paint, transform);
689
690 std::vector<Vertex> tempVertices;
691 float threshInvScaleX = paintInfo.inverseScaleX;
692 float threshInvScaleY = paintInfo.inverseScaleY;
693 if (paintInfo.style == SkPaint::kStroke_Style) {
694 // alter the bezier recursion threshold values we calculate in order to compensate for
695 // expansion done after the path vertices are found
696 SkRect bounds = path.getBounds();
697 if (!bounds.isEmpty()) {
698 threshInvScaleX *= bounds.width() / (bounds.width() + paint->getStrokeWidth());
699 threshInvScaleY *= bounds.height() / (bounds.height() + paint->getStrokeWidth());
700 }
701 }
702
703 // force close if we're filling the path, since fill path expects closed perimeter.
704 bool forceClose = paintInfo.style != SkPaint::kStroke_Style;
705 PathApproximationInfo approximationInfo(threshInvScaleX, threshInvScaleY,
706 OUTLINE_REFINE_THRESHOLD);
707 bool wasClosed =
708 approximatePathOutlineVertices(path, forceClose, approximationInfo, tempVertices);
709
710 if (!tempVertices.size()) {
711 // path was empty, return without allocating vertex buffer
712 return;
713 }
714
715 #if VERTEX_DEBUG
716 for (unsigned int i = 0; i < tempVertices.size(); i++) {
717 ALOGD("orig path: point at %f %f", tempVertices[i].x, tempVertices[i].y);
718 }
719 #endif
720
721 if (paintInfo.style == SkPaint::kStroke_Style) {
722 if (!paintInfo.isAA) {
723 if (wasClosed) {
724 getStrokeVerticesFromPerimeter(paintInfo, tempVertices, vertexBuffer);
725 } else {
726 getStrokeVerticesFromUnclosedVertices(paintInfo, tempVertices, vertexBuffer);
727 }
728
729 } else {
730 if (wasClosed) {
731 getStrokeVerticesFromPerimeterAA(paintInfo, tempVertices, vertexBuffer);
732 } else {
733 getStrokeVerticesFromUnclosedVerticesAA(paintInfo, tempVertices, vertexBuffer);
734 }
735 }
736 } else {
737 // For kStrokeAndFill style, the path should be adjusted externally.
738 // It will be treated as a fill here.
739 if (!paintInfo.isAA) {
740 getFillVerticesFromPerimeter(tempVertices, vertexBuffer);
741 } else {
742 getFillVerticesFromPerimeterAA(paintInfo, tempVertices, vertexBuffer);
743 }
744 }
745
746 Rect bounds(path.getBounds());
747 paintInfo.expandBoundsForStroke(&bounds);
748 vertexBuffer.setBounds(bounds);
749 vertexBuffer.setMeshFeatureFlags(paintInfo.isAA ? VertexBuffer::kAlpha : VertexBuffer::kNone);
750 }
751
752 template <class TYPE>
instanceVertices(VertexBuffer & srcBuffer,VertexBuffer & dstBuffer,const float * points,int count,Rect & bounds)753 static void instanceVertices(VertexBuffer& srcBuffer, VertexBuffer& dstBuffer, const float* points,
754 int count, Rect& bounds) {
755 bounds.set(points[0], points[1], points[0], points[1]);
756
757 int numPoints = count / 2;
758 int verticesPerPoint = srcBuffer.getVertexCount();
759 dstBuffer.alloc<TYPE>(numPoints * verticesPerPoint + (numPoints - 1) * 2);
760
761 for (int i = 0; i < count; i += 2) {
762 bounds.expandToCover(points[i + 0], points[i + 1]);
763 dstBuffer.copyInto<TYPE>(srcBuffer, points[i + 0], points[i + 1]);
764 }
765 dstBuffer.createDegenerateSeparators<TYPE>(verticesPerPoint);
766 }
767
tessellatePoints(const float * points,int count,const SkPaint * paint,const mat4 & transform,VertexBuffer & vertexBuffer)768 void PathTessellator::tessellatePoints(const float* points, int count, const SkPaint* paint,
769 const mat4& transform, VertexBuffer& vertexBuffer) {
770 const PaintInfo paintInfo(paint, transform);
771
772 // determine point shape
773 SkPath path;
774 float radius = paintInfo.halfStrokeWidth;
775 if (radius == 0.0f) radius = 0.5f;
776
777 if (paintInfo.cap == SkPaint::kRound_Cap) {
778 path.addCircle(0, 0, radius);
779 } else {
780 path.addRect(-radius, -radius, radius, radius);
781 }
782
783 // calculate outline
784 std::vector<Vertex> outlineVertices;
785 PathApproximationInfo approximationInfo(paintInfo.inverseScaleX, paintInfo.inverseScaleY,
786 OUTLINE_REFINE_THRESHOLD);
787 approximatePathOutlineVertices(path, true, approximationInfo, outlineVertices);
788
789 if (!outlineVertices.size()) return;
790
791 Rect bounds;
792 // tessellate, then duplicate outline across points
793 VertexBuffer tempBuffer;
794 if (!paintInfo.isAA) {
795 getFillVerticesFromPerimeter(outlineVertices, tempBuffer);
796 instanceVertices<Vertex>(tempBuffer, vertexBuffer, points, count, bounds);
797 } else {
798 // note: pass maxAlpha directly, since we want fill to be alpha modulated
799 getFillVerticesFromPerimeterAA(paintInfo, outlineVertices, tempBuffer, paintInfo.maxAlpha);
800 instanceVertices<AlphaVertex>(tempBuffer, vertexBuffer, points, count, bounds);
801 }
802
803 // expand bounds from vertex coords to pixel data
804 paintInfo.expandBoundsForStroke(&bounds);
805 vertexBuffer.setBounds(bounds);
806 vertexBuffer.setMeshFeatureFlags(paintInfo.isAA ? VertexBuffer::kAlpha : VertexBuffer::kNone);
807 }
808
tessellateLines(const float * points,int count,const SkPaint * paint,const mat4 & transform,VertexBuffer & vertexBuffer)809 void PathTessellator::tessellateLines(const float* points, int count, const SkPaint* paint,
810 const mat4& transform, VertexBuffer& vertexBuffer) {
811 ATRACE_CALL();
812 const PaintInfo paintInfo(paint, transform);
813
814 const int extra = paintInfo.capExtraDivisions();
815 int numLines = count / 4;
816 int lineAllocSize;
817 // pre-allocate space for lines in the buffer, and degenerate tris in between
818 if (paintInfo.isAA) {
819 lineAllocSize = 6 * (2) + 2 + 6 * extra;
820 vertexBuffer.alloc<AlphaVertex>(numLines * lineAllocSize + (numLines - 1) * 2);
821 } else {
822 lineAllocSize = 2 * ((2) + extra);
823 vertexBuffer.alloc<Vertex>(numLines * lineAllocSize + (numLines - 1) * 2);
824 }
825
826 std::vector<Vertex> tempVertices(2);
827 Vertex* tempVerticesData = &tempVertices.front();
828 Rect bounds;
829 bounds.set(points[0], points[1], points[0], points[1]);
830 for (int i = 0; i < count; i += 4) {
831 Vertex::set(&(tempVerticesData[0]), points[i + 0], points[i + 1]);
832 Vertex::set(&(tempVerticesData[1]), points[i + 2], points[i + 3]);
833
834 if (paintInfo.isAA) {
835 getStrokeVerticesFromUnclosedVerticesAA(paintInfo, tempVertices, vertexBuffer);
836 } else {
837 getStrokeVerticesFromUnclosedVertices(paintInfo, tempVertices, vertexBuffer);
838 }
839
840 // calculate bounds
841 bounds.expandToCover(tempVerticesData[0].x, tempVerticesData[0].y);
842 bounds.expandToCover(tempVerticesData[1].x, tempVerticesData[1].y);
843 }
844
845 // since multiple objects tessellated into buffer, separate them with degen tris
846 if (paintInfo.isAA) {
847 vertexBuffer.createDegenerateSeparators<AlphaVertex>(lineAllocSize);
848 } else {
849 vertexBuffer.createDegenerateSeparators<Vertex>(lineAllocSize);
850 }
851
852 // expand bounds from vertex coords to pixel data
853 paintInfo.expandBoundsForStroke(&bounds);
854 vertexBuffer.setBounds(bounds);
855 vertexBuffer.setMeshFeatureFlags(paintInfo.isAA ? VertexBuffer::kAlpha : VertexBuffer::kNone);
856 }
857
858 ///////////////////////////////////////////////////////////////////////////////
859 // Simple path line approximation
860 ///////////////////////////////////////////////////////////////////////////////
861
approximatePathOutlineVertices(const SkPath & path,float threshold,std::vector<Vertex> & outputVertices)862 bool PathTessellator::approximatePathOutlineVertices(const SkPath& path, float threshold,
863 std::vector<Vertex>& outputVertices) {
864 PathApproximationInfo approximationInfo(1.0f, 1.0f, threshold);
865 return approximatePathOutlineVertices(path, true, approximationInfo, outputVertices);
866 }
867
868 class ClockwiseEnforcer {
869 public:
addPoint(const SkPoint & point)870 void addPoint(const SkPoint& point) {
871 double x = point.x();
872 double y = point.y();
873
874 if (initialized) {
875 sum += (x + lastX) * (y - lastY);
876 } else {
877 initialized = true;
878 }
879
880 lastX = x;
881 lastY = y;
882 }
reverseVectorIfNotClockwise(std::vector<Vertex> & vertices)883 void reverseVectorIfNotClockwise(std::vector<Vertex>& vertices) {
884 if (sum < 0) {
885 // negative sum implies CounterClockwise
886 const int size = vertices.size();
887 for (int i = 0; i < size / 2; i++) {
888 Vertex tmp = vertices[i];
889 int k = size - 1 - i;
890 vertices[i] = vertices[k];
891 vertices[k] = tmp;
892 }
893 }
894 }
895
896 private:
897 bool initialized = false;
898 double lastX = 0;
899 double lastY = 0;
900 double sum = 0;
901 };
902
approximatePathOutlineVertices(const SkPath & path,bool forceClose,const PathApproximationInfo & approximationInfo,std::vector<Vertex> & outputVertices)903 bool PathTessellator::approximatePathOutlineVertices(const SkPath& path, bool forceClose,
904 const PathApproximationInfo& approximationInfo,
905 std::vector<Vertex>& outputVertices) {
906 ATRACE_CALL();
907
908 // TODO: to support joins other than sharp miter, join vertices should be labelled in the
909 // perimeter, or resolved into more vertices. Reconsider forceClose-ing in that case.
910 SkPath::Iter iter(path, forceClose);
911 SkPoint pts[4];
912 SkPath::Verb v;
913 ClockwiseEnforcer clockwiseEnforcer;
914 while (SkPath::kDone_Verb != (v = iter.next(pts))) {
915 switch (v) {
916 case SkPath::kMove_Verb:
917 outputVertices.push_back(Vertex{pts[0].x(), pts[0].y()});
918 ALOGV("Move to pos %f %f", pts[0].x(), pts[0].y());
919 clockwiseEnforcer.addPoint(pts[0]);
920 break;
921 case SkPath::kClose_Verb:
922 ALOGV("Close at pos %f %f", pts[0].x(), pts[0].y());
923 clockwiseEnforcer.addPoint(pts[0]);
924 break;
925 case SkPath::kLine_Verb:
926 ALOGV("kLine_Verb %f %f -> %f %f", pts[0].x(), pts[0].y(), pts[1].x(), pts[1].y());
927 outputVertices.push_back(Vertex{pts[1].x(), pts[1].y()});
928 clockwiseEnforcer.addPoint(pts[1]);
929 break;
930 case SkPath::kQuad_Verb:
931 ALOGV("kQuad_Verb");
932 recursiveQuadraticBezierVertices(pts[0].x(), pts[0].y(), pts[2].x(), pts[2].y(),
933 pts[1].x(), pts[1].y(), approximationInfo,
934 outputVertices);
935 clockwiseEnforcer.addPoint(pts[1]);
936 clockwiseEnforcer.addPoint(pts[2]);
937 break;
938 case SkPath::kCubic_Verb:
939 ALOGV("kCubic_Verb");
940 recursiveCubicBezierVertices(pts[0].x(), pts[0].y(), pts[1].x(), pts[1].y(),
941 pts[3].x(), pts[3].y(), pts[2].x(), pts[2].y(),
942 approximationInfo, outputVertices);
943 clockwiseEnforcer.addPoint(pts[1]);
944 clockwiseEnforcer.addPoint(pts[2]);
945 clockwiseEnforcer.addPoint(pts[3]);
946 break;
947 case SkPath::kConic_Verb: {
948 ALOGV("kConic_Verb");
949 SkAutoConicToQuads converter;
950 const SkPoint* quads = converter.computeQuads(
951 pts, iter.conicWeight(), approximationInfo.thresholdForConicQuads);
952 for (int i = 0; i < converter.countQuads(); ++i) {
953 const int offset = 2 * i;
954 recursiveQuadraticBezierVertices(quads[offset].x(), quads[offset].y(),
955 quads[offset + 2].x(), quads[offset + 2].y(),
956 quads[offset + 1].x(), quads[offset + 1].y(),
957 approximationInfo, outputVertices);
958 }
959 clockwiseEnforcer.addPoint(pts[1]);
960 clockwiseEnforcer.addPoint(pts[2]);
961 break;
962 }
963 default:
964 static_assert(SkPath::kMove_Verb == 0 && SkPath::kLine_Verb == 1 &&
965 SkPath::kQuad_Verb == 2 && SkPath::kConic_Verb == 3 &&
966 SkPath::kCubic_Verb == 4 && SkPath::kClose_Verb == 5 &&
967 SkPath::kDone_Verb == 6,
968 "Path enum changed, new types may have been added");
969 break;
970 }
971 }
972
973 bool wasClosed = false;
974 int size = outputVertices.size();
975 if (size >= 2 && outputVertices[0].x == outputVertices[size - 1].x &&
976 outputVertices[0].y == outputVertices[size - 1].y) {
977 outputVertices.pop_back();
978 wasClosed = true;
979 }
980
981 // ensure output vector is clockwise
982 clockwiseEnforcer.reverseVectorIfNotClockwise(outputVertices);
983 return wasClosed;
984 }
985
986 ///////////////////////////////////////////////////////////////////////////////
987 // Bezier approximation
988 //
989 // All the inputs and outputs here are in path coordinates.
990 // We convert the error threshold from screen coordinates into path coordinates.
991 ///////////////////////////////////////////////////////////////////////////////
992
993 // Get a threshold in path coordinates, by scaling the thresholdSquared from screen coordinates.
994 // TODO: Document the math behind this algorithm.
getThreshold(const PathApproximationInfo & info,float dx,float dy)995 static inline float getThreshold(const PathApproximationInfo& info, float dx, float dy) {
996 // multiplying by sqrInvScaleY/X equivalent to multiplying in dimensional scale factors
997 float scale = (dx * dx * info.sqrInvScaleY + dy * dy * info.sqrInvScaleX);
998 return info.thresholdSquared * scale;
999 }
1000
recursiveCubicBezierVertices(float p1x,float p1y,float c1x,float c1y,float p2x,float p2y,float c2x,float c2y,const PathApproximationInfo & approximationInfo,std::vector<Vertex> & outputVertices,int depth)1001 void PathTessellator::recursiveCubicBezierVertices(float p1x, float p1y, float c1x, float c1y,
1002 float p2x, float p2y, float c2x, float c2y,
1003 const PathApproximationInfo& approximationInfo,
1004 std::vector<Vertex>& outputVertices, int depth) {
1005 float dx = p2x - p1x;
1006 float dy = p2y - p1y;
1007 float d1 = fabs((c1x - p2x) * dy - (c1y - p2y) * dx);
1008 float d2 = fabs((c2x - p2x) * dy - (c2y - p2y) * dx);
1009 float d = d1 + d2;
1010
1011 if (depth >= MAX_DEPTH || d * d <= getThreshold(approximationInfo, dx, dy)) {
1012 // below thresh, draw line by adding endpoint
1013 outputVertices.push_back(Vertex{p2x, p2y});
1014 } else {
1015 float p1c1x = (p1x + c1x) * 0.5f;
1016 float p1c1y = (p1y + c1y) * 0.5f;
1017 float p2c2x = (p2x + c2x) * 0.5f;
1018 float p2c2y = (p2y + c2y) * 0.5f;
1019
1020 float c1c2x = (c1x + c2x) * 0.5f;
1021 float c1c2y = (c1y + c2y) * 0.5f;
1022
1023 float p1c1c2x = (p1c1x + c1c2x) * 0.5f;
1024 float p1c1c2y = (p1c1y + c1c2y) * 0.5f;
1025
1026 float p2c1c2x = (p2c2x + c1c2x) * 0.5f;
1027 float p2c1c2y = (p2c2y + c1c2y) * 0.5f;
1028
1029 float mx = (p1c1c2x + p2c1c2x) * 0.5f;
1030 float my = (p1c1c2y + p2c1c2y) * 0.5f;
1031
1032 recursiveCubicBezierVertices(p1x, p1y, p1c1x, p1c1y, mx, my, p1c1c2x, p1c1c2y,
1033 approximationInfo, outputVertices, depth + 1);
1034 recursiveCubicBezierVertices(mx, my, p2c1c2x, p2c1c2y, p2x, p2y, p2c2x, p2c2y,
1035 approximationInfo, outputVertices, depth + 1);
1036 }
1037 }
1038
recursiveQuadraticBezierVertices(float ax,float ay,float bx,float by,float cx,float cy,const PathApproximationInfo & approximationInfo,std::vector<Vertex> & outputVertices,int depth)1039 void PathTessellator::recursiveQuadraticBezierVertices(
1040 float ax, float ay, float bx, float by, float cx, float cy,
1041 const PathApproximationInfo& approximationInfo, std::vector<Vertex>& outputVertices,
1042 int depth) {
1043 float dx = bx - ax;
1044 float dy = by - ay;
1045 // d is the cross product of vector (B-A) and (C-B).
1046 float d = (cx - bx) * dy - (cy - by) * dx;
1047
1048 if (depth >= MAX_DEPTH || d * d <= getThreshold(approximationInfo, dx, dy)) {
1049 // below thresh, draw line by adding endpoint
1050 outputVertices.push_back(Vertex{bx, by});
1051 } else {
1052 float acx = (ax + cx) * 0.5f;
1053 float bcx = (bx + cx) * 0.5f;
1054 float acy = (ay + cy) * 0.5f;
1055 float bcy = (by + cy) * 0.5f;
1056
1057 // midpoint
1058 float mx = (acx + bcx) * 0.5f;
1059 float my = (acy + bcy) * 0.5f;
1060
1061 recursiveQuadraticBezierVertices(ax, ay, mx, my, acx, acy, approximationInfo,
1062 outputVertices, depth + 1);
1063 recursiveQuadraticBezierVertices(mx, my, bx, by, bcx, bcy, approximationInfo,
1064 outputVertices, depth + 1);
1065 }
1066 }
1067
1068 }; // namespace uirenderer
1069 }; // namespace android
1070