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