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