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