blob: 5b55c2b945891d18c17d617ba70edc7a96c81c32 [file] [log] [blame]
Chris Craik710f46d2012-09-17 17:25:49 -07001/*
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 "PathRenderer"
18#define LOG_NDEBUG 1
19#define ATRACE_TAG ATRACE_TAG_GRAPHICS
20
21#define VERTEX_DEBUG 0
22
23#include <SkPath.h>
Chris Craikcb4d6002012-09-25 12:00:29 -070024#include <SkPaint.h>
Chris Craik710f46d2012-09-17 17:25:49 -070025
26#include <stdlib.h>
27#include <stdint.h>
28#include <sys/types.h>
29
30#include <utils/Log.h>
31#include <utils/Trace.h>
32
33#include "PathRenderer.h"
34#include "Matrix.h"
35#include "Vector.h"
36#include "Vertex.h"
37
38namespace android {
39namespace uirenderer {
40
41#define THRESHOLD 0.5f
42
Chris Craikcb4d6002012-09-25 12:00:29 -070043SkRect PathRenderer::computePathBounds(const SkPath& path, const SkPaint* paint) {
44 SkRect bounds = path.getBounds();
45 if (paint->getStyle() != SkPaint::kFill_Style) {
46 float outset = paint->getStrokeWidth() * 0.5f;
47 bounds.outset(outset, outset);
48 }
49 return bounds;
50}
51
52void computeInverseScales(const mat4 *transform, float &inverseScaleX, float& inverseScaleY) {
Chris Craik710f46d2012-09-17 17:25:49 -070053 if (CC_UNLIKELY(!transform->isPureTranslate())) {
54 float m00 = transform->data[Matrix4::kScaleX];
55 float m01 = transform->data[Matrix4::kSkewY];
56 float m10 = transform->data[Matrix4::kSkewX];
57 float m11 = transform->data[Matrix4::kScaleY];
58 float scaleX = sqrt(m00 * m00 + m01 * m01);
59 float scaleY = sqrt(m10 * m10 + m11 * m11);
Chris Craik16b897c2012-09-27 13:10:56 -070060 inverseScaleX = (scaleX != 0) ? (1.0f / scaleX) : 1.0f;
61 inverseScaleY = (scaleY != 0) ? (1.0f / scaleY) : 1.0f;
Chris Craikcb4d6002012-09-25 12:00:29 -070062 } else {
63 inverseScaleX = 1.0f;
64 inverseScaleY = 1.0f;
Chris Craik710f46d2012-09-17 17:25:49 -070065 }
66}
67
Chris Craik16b897c2012-09-27 13:10:56 -070068inline void copyVertex(Vertex* destPtr, const Vertex* srcPtr) {
Chris Craikcb4d6002012-09-25 12:00:29 -070069 Vertex::set(destPtr, srcPtr->position[0], srcPtr->position[1]);
70}
Chris Craik710f46d2012-09-17 17:25:49 -070071
Chris Craik16b897c2012-09-27 13:10:56 -070072inline void copyAlphaVertex(AlphaVertex* destPtr, const AlphaVertex* srcPtr) {
Chris Craikcb4d6002012-09-25 12:00:29 -070073 AlphaVertex::set(destPtr, srcPtr->position[0], srcPtr->position[1], srcPtr->alpha);
74}
Chris Craik710f46d2012-09-17 17:25:49 -070075
Chris Craik16b897c2012-09-27 13:10:56 -070076/**
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 */
84inline vec2 totalOffsetFromNormals(const vec2& normalA, const vec2& normalB) {
85 return (normalA + normalB) / (1 + fabs(normalA.dot(normalB)));
86}
87
Chris Craikcb4d6002012-09-25 12:00:29 -070088void getFillVerticesFromPerimeter(const Vector<Vertex>& perimeter, VertexBuffer& vertexBuffer) {
89 Vertex* buffer = vertexBuffer.alloc<Vertex>(perimeter.size());
90
Chris Craik710f46d2012-09-17 17:25:49 -070091 int currentIndex = 0;
Chris Craikcb4d6002012-09-25 12:00:29 -070092 // zig zag between all previous points on the inside of the hull to create a
93 // triangle strip that fills the hull
94 int srcAindex = 0;
95 int srcBindex = perimeter.size() - 1;
96 while (srcAindex <= srcBindex) {
97 copyVertex(&buffer[currentIndex++], &perimeter[srcAindex]);
98 if (srcAindex == srcBindex) break;
99 copyVertex(&buffer[currentIndex++], &perimeter[srcBindex]);
100 srcAindex++;
101 srcBindex--;
Chris Craik710f46d2012-09-17 17:25:49 -0700102 }
Chris Craikcb4d6002012-09-25 12:00:29 -0700103}
104
105void getStrokeVerticesFromPerimeter(const Vector<Vertex>& perimeter, float halfStrokeWidth,
106 VertexBuffer& vertexBuffer, float inverseScaleX, float inverseScaleY) {
107 Vertex* buffer = vertexBuffer.alloc<Vertex>(perimeter.size() * 2 + 2);
108
109 int currentIndex = 0;
110 const Vertex* last = &(perimeter[perimeter.size() - 1]);
111 const Vertex* current = &(perimeter[0]);
112 vec2 lastNormal(current->position[1] - last->position[1],
113 last->position[0] - current->position[0]);
114 lastNormal.normalize();
115 for (unsigned int i = 0; i < perimeter.size(); i++) {
116 const Vertex* next = &(perimeter[i + 1 >= perimeter.size() ? 0 : i + 1]);
117 vec2 nextNormal(next->position[1] - current->position[1],
118 current->position[0] - next->position[0]);
119 nextNormal.normalize();
120
Chris Craik16b897c2012-09-27 13:10:56 -0700121 vec2 totalOffset = totalOffsetFromNormals(lastNormal, nextNormal);
Chris Craikcb4d6002012-09-25 12:00:29 -0700122 if (halfStrokeWidth == 0.0f) {
123 // hairline - compensate for scale
124 totalOffset.x *= 0.5f * inverseScaleX;
125 totalOffset.y *= 0.5f * inverseScaleY;
126 } else {
127 totalOffset *= halfStrokeWidth;
128 }
129
130 Vertex::set(&buffer[currentIndex++],
131 current->position[0] + totalOffset.x,
132 current->position[1] + totalOffset.y);
133
134 Vertex::set(&buffer[currentIndex++],
135 current->position[0] - totalOffset.x,
136 current->position[1] - totalOffset.y);
137
138 last = current;
139 current = next;
140 lastNormal = nextNormal;
141 }
142
143 // wrap around to beginning
144 copyVertex(&buffer[currentIndex++], &buffer[0]);
145 copyVertex(&buffer[currentIndex++], &buffer[1]);
146}
147
148void getFillVerticesFromPerimeterAA(const Vector<Vertex>& perimeter, VertexBuffer& vertexBuffer,
149 float inverseScaleX, float inverseScaleY) {
150 AlphaVertex* buffer = vertexBuffer.alloc<AlphaVertex>(perimeter.size() * 3 + 2);
Chris Craik710f46d2012-09-17 17:25:49 -0700151
152 // generate alpha points - fill Alpha vertex gaps in between each point with
153 // alpha 0 vertex, offset by a scaled normal.
Chris Craikcb4d6002012-09-25 12:00:29 -0700154 int currentIndex = 0;
155 const Vertex* last = &(perimeter[perimeter.size() - 1]);
156 const Vertex* current = &(perimeter[0]);
157 vec2 lastNormal(current->position[1] - last->position[1],
158 last->position[0] - current->position[0]);
159 lastNormal.normalize();
160 for (unsigned int i = 0; i < perimeter.size(); i++) {
161 const Vertex* next = &(perimeter[i + 1 >= perimeter.size() ? 0 : i + 1]);
Chris Craik710f46d2012-09-17 17:25:49 -0700162 vec2 nextNormal(next->position[1] - current->position[1],
Chris Craikcb4d6002012-09-25 12:00:29 -0700163 current->position[0] - next->position[0]);
Chris Craik710f46d2012-09-17 17:25:49 -0700164 nextNormal.normalize();
165
Chris Craik16b897c2012-09-27 13:10:56 -0700166 // AA point offset from original point is that point's normal, such that each side is offset
167 // by .5 pixels
168 vec2 totalOffset = totalOffsetFromNormals(lastNormal, nextNormal);
169 totalOffset.x *= 0.5f * inverseScaleX;
170 totalOffset.y *= 0.5f * inverseScaleY;
Chris Craik710f46d2012-09-17 17:25:49 -0700171
172 AlphaVertex::set(&buffer[currentIndex++],
Chris Craikcb4d6002012-09-25 12:00:29 -0700173 current->position[0] + totalOffset.x,
174 current->position[1] + totalOffset.y,
175 0.0f);
Chris Craik710f46d2012-09-17 17:25:49 -0700176 AlphaVertex::set(&buffer[currentIndex++],
Chris Craikcb4d6002012-09-25 12:00:29 -0700177 current->position[0] - totalOffset.x,
178 current->position[1] - totalOffset.y,
179 1.0f);
180
Chris Craik710f46d2012-09-17 17:25:49 -0700181 last = current;
Chris Craikcb4d6002012-09-25 12:00:29 -0700182 current = next;
183 lastNormal = nextNormal;
Chris Craik710f46d2012-09-17 17:25:49 -0700184 }
185
186 // wrap around to beginning
Chris Craikcb4d6002012-09-25 12:00:29 -0700187 copyAlphaVertex(&buffer[currentIndex++], &buffer[0]);
188 copyAlphaVertex(&buffer[currentIndex++], &buffer[1]);
Chris Craik710f46d2012-09-17 17:25:49 -0700189
190 // zig zag between all previous points on the inside of the hull to create a
191 // triangle strip that fills the hull, repeating the first inner point to
192 // create degenerate tris to start inside path
193 int srcAindex = 0;
Chris Craikcb4d6002012-09-25 12:00:29 -0700194 int srcBindex = perimeter.size() - 1;
Chris Craik710f46d2012-09-17 17:25:49 -0700195 while (srcAindex <= srcBindex) {
Chris Craikcb4d6002012-09-25 12:00:29 -0700196 copyAlphaVertex(&buffer[currentIndex++], &buffer[srcAindex * 2 + 1]);
Chris Craik710f46d2012-09-17 17:25:49 -0700197 if (srcAindex == srcBindex) break;
Chris Craikcb4d6002012-09-25 12:00:29 -0700198 copyAlphaVertex(&buffer[currentIndex++], &buffer[srcBindex * 2 + 1]);
Chris Craik710f46d2012-09-17 17:25:49 -0700199 srcAindex++;
200 srcBindex--;
201 }
202
203#if VERTEX_DEBUG
Chris Craikcb4d6002012-09-25 12:00:29 -0700204 for (unsigned int i = 0; i < vertexBuffer.getSize(); i++) {
205 ALOGD("point at %f %f", buffer[i].position[0], buffer[i].position[1]);
Chris Craik710f46d2012-09-17 17:25:49 -0700206 }
207#endif
208}
209
Chris Craikcb4d6002012-09-25 12:00:29 -0700210void getStrokeVerticesFromPerimeterAA(const Vector<Vertex>& perimeter, float halfStrokeWidth,
211 VertexBuffer& vertexBuffer, float inverseScaleX, float inverseScaleY) {
212 AlphaVertex* buffer = vertexBuffer.alloc<AlphaVertex>(6 * perimeter.size() + 8);
Chris Craik710f46d2012-09-17 17:25:49 -0700213
Chris Craik16b897c2012-09-27 13:10:56 -0700214 // avoid lines smaller than hairline since they break triangle based sampling. instead reducing
215 // alpha value (TODO: support different X/Y scale)
216 float maxAlpha = 1.0f;
217 if (halfStrokeWidth != 0 && inverseScaleX == inverseScaleY &&
218 halfStrokeWidth * inverseScaleX < 1.0f) {
219 maxAlpha *= (2 * halfStrokeWidth) / inverseScaleX;
220 halfStrokeWidth = 0.0f;
221 }
222
Chris Craikcb4d6002012-09-25 12:00:29 -0700223 int offset = 2 * perimeter.size() + 3;
224 int currentAAOuterIndex = 0;
225 int currentStrokeIndex = offset;
226 int currentAAInnerIndex = offset * 2;
227
228 const Vertex* last = &(perimeter[perimeter.size() - 1]);
229 const Vertex* current = &(perimeter[0]);
230 vec2 lastNormal(current->position[1] - last->position[1],
231 last->position[0] - current->position[0]);
232 lastNormal.normalize();
233 for (unsigned int i = 0; i < perimeter.size(); i++) {
234 const Vertex* next = &(perimeter[i + 1 >= perimeter.size() ? 0 : i + 1]);
235 vec2 nextNormal(next->position[1] - current->position[1],
236 current->position[0] - next->position[0]);
237 nextNormal.normalize();
238
Chris Craik16b897c2012-09-27 13:10:56 -0700239 vec2 totalOffset = totalOffsetFromNormals(lastNormal, nextNormal);
240 vec2 AAOffset = totalOffset;
241 AAOffset.x *= 0.5f * inverseScaleX;
242 AAOffset.y *= 0.5f * inverseScaleY;
Chris Craikcb4d6002012-09-25 12:00:29 -0700243
Chris Craik16b897c2012-09-27 13:10:56 -0700244 vec2 innerOffset = totalOffset;
Chris Craikcb4d6002012-09-25 12:00:29 -0700245 if (halfStrokeWidth == 0.0f) {
246 // hairline! - compensate for scale
247 innerOffset.x *= 0.5f * inverseScaleX;
248 innerOffset.y *= 0.5f * inverseScaleY;
249 } else {
250 innerOffset *= halfStrokeWidth;
251 }
252 vec2 outerOffset = innerOffset + AAOffset;
253 innerOffset -= AAOffset;
254
255 AlphaVertex::set(&buffer[currentAAOuterIndex++],
256 current->position[0] + outerOffset.x,
257 current->position[1] + outerOffset.y,
258 0.0f);
259 AlphaVertex::set(&buffer[currentAAOuterIndex++],
260 current->position[0] + innerOffset.x,
261 current->position[1] + innerOffset.y,
Chris Craik16b897c2012-09-27 13:10:56 -0700262 maxAlpha);
Chris Craikcb4d6002012-09-25 12:00:29 -0700263
264 AlphaVertex::set(&buffer[currentStrokeIndex++],
265 current->position[0] + innerOffset.x,
266 current->position[1] + innerOffset.y,
Chris Craik16b897c2012-09-27 13:10:56 -0700267 maxAlpha);
Chris Craikcb4d6002012-09-25 12:00:29 -0700268 AlphaVertex::set(&buffer[currentStrokeIndex++],
269 current->position[0] - innerOffset.x,
270 current->position[1] - innerOffset.y,
Chris Craik16b897c2012-09-27 13:10:56 -0700271 maxAlpha);
Chris Craikcb4d6002012-09-25 12:00:29 -0700272
273 AlphaVertex::set(&buffer[currentAAInnerIndex++],
274 current->position[0] - innerOffset.x,
275 current->position[1] - innerOffset.y,
Chris Craik16b897c2012-09-27 13:10:56 -0700276 maxAlpha);
Chris Craikcb4d6002012-09-25 12:00:29 -0700277 AlphaVertex::set(&buffer[currentAAInnerIndex++],
278 current->position[0] - outerOffset.x,
279 current->position[1] - outerOffset.y,
280 0.0f);
281
Chris Craikcb4d6002012-09-25 12:00:29 -0700282 last = current;
283 current = next;
284 lastNormal = nextNormal;
285 }
286
287 // wrap each strip around to beginning, creating degenerate tris to bridge strips
288 copyAlphaVertex(&buffer[currentAAOuterIndex++], &buffer[0]);
289 copyAlphaVertex(&buffer[currentAAOuterIndex++], &buffer[1]);
290 copyAlphaVertex(&buffer[currentAAOuterIndex++], &buffer[1]);
291
292 copyAlphaVertex(&buffer[currentStrokeIndex++], &buffer[offset]);
293 copyAlphaVertex(&buffer[currentStrokeIndex++], &buffer[offset + 1]);
294 copyAlphaVertex(&buffer[currentStrokeIndex++], &buffer[offset + 1]);
295
296 copyAlphaVertex(&buffer[currentAAInnerIndex++], &buffer[2 * offset]);
297 copyAlphaVertex(&buffer[currentAAInnerIndex++], &buffer[2 * offset + 1]);
298 // don't need to create last degenerate tri
299}
300
301void PathRenderer::convexPathVertices(const SkPath &path, const SkPaint* paint,
302 const mat4 *transform, VertexBuffer& vertexBuffer) {
303 ATRACE_CALL();
304
305 SkPaint::Style style = paint->getStyle();
306 bool isAA = paint->isAntiAlias();
307
308 float inverseScaleX, inverseScaleY;
309 computeInverseScales(transform, inverseScaleX, inverseScaleY);
310
311 Vector<Vertex> tempVertices;
Chris Craik16b897c2012-09-27 13:10:56 -0700312 float threshInvScaleX = inverseScaleX;
313 float threshInvScaleY = inverseScaleY;
314 if (style == SkPaint::kStroke_Style) {
315 // alter the bezier recursion threshold values we calculate in order to compensate for
316 // expansion done after the path vertices are found
317 SkRect bounds = path.getBounds();
318 if (!bounds.isEmpty()) {
319 threshInvScaleX *= bounds.width() / (bounds.width() + paint->getStrokeWidth());
320 threshInvScaleY *= bounds.height() / (bounds.height() + paint->getStrokeWidth());
321 }
322 }
323 convexPathPerimeterVertices(path, threshInvScaleX * threshInvScaleX,
324 threshInvScaleY * threshInvScaleY, tempVertices);
Chris Craikcb4d6002012-09-25 12:00:29 -0700325
Chris Craikbf09ffb2012-10-01 13:50:37 -0700326 if (!tempVertices.size()) {
327 // path was empty, return without allocating vertex buffer
328 return;
329 }
330
Chris Craikcb4d6002012-09-25 12:00:29 -0700331#if VERTEX_DEBUG
332 for (unsigned int i = 0; i < tempVertices.size(); i++) {
333 ALOGD("orig path: point at %f %f", tempVertices[i].position[0], tempVertices[i].position[1]);
334 }
335#endif
336
337 if (style == SkPaint::kStroke_Style) {
338 float halfStrokeWidth = paint->getStrokeWidth() * 0.5f;
339 if (!isAA) {
340 getStrokeVerticesFromPerimeter(tempVertices, halfStrokeWidth, vertexBuffer,
341 inverseScaleX, inverseScaleY);
342 } else {
343 getStrokeVerticesFromPerimeterAA(tempVertices, halfStrokeWidth, vertexBuffer,
344 inverseScaleX, inverseScaleY);
345 }
346 } else {
347 // For kStrokeAndFill style, the path should be adjusted externally, as it will be treated as a fill here.
348 if (!isAA) {
349 getFillVerticesFromPerimeter(tempVertices, vertexBuffer);
350 } else {
351 getFillVerticesFromPerimeterAA(tempVertices, vertexBuffer, inverseScaleX, inverseScaleY);
352 }
353 }
354}
355
356
357void PathRenderer::convexPathPerimeterVertices(const SkPath& path,
358 float sqrInvScaleX, float sqrInvScaleY, Vector<Vertex>& outputVertices) {
Chris Craik710f46d2012-09-17 17:25:49 -0700359 ATRACE_CALL();
360
361 SkPath::Iter iter(path, true);
362 SkPoint pos;
363 SkPoint pts[4];
364 SkPath::Verb v;
365 Vertex* newVertex = 0;
366 while (SkPath::kDone_Verb != (v = iter.next(pts))) {
367 switch (v) {
368 case SkPath::kMove_Verb:
369 pos = pts[0];
370 ALOGV("Move to pos %f %f", pts[0].x(), pts[0].y());
371 break;
372 case SkPath::kClose_Verb:
373 ALOGV("Close at pos %f %f", pts[0].x(), pts[0].y());
374 break;
375 case SkPath::kLine_Verb:
376 ALOGV("kLine_Verb %f %f -> %f %f",
Chris Craikcb4d6002012-09-25 12:00:29 -0700377 pts[0].x(), pts[0].y(),
378 pts[1].x(), pts[1].y());
Chris Craik710f46d2012-09-17 17:25:49 -0700379
380 // TODO: make this not yuck
381 outputVertices.push();
Chris Craikcb4d6002012-09-25 12:00:29 -0700382 newVertex = &(outputVertices.editArray()[outputVertices.size() - 1]);
Chris Craik710f46d2012-09-17 17:25:49 -0700383 Vertex::set(newVertex, pts[1].x(), pts[1].y());
384 break;
385 case SkPath::kQuad_Verb:
386 ALOGV("kQuad_Verb");
387 recursiveQuadraticBezierVertices(
Chris Craikcb4d6002012-09-25 12:00:29 -0700388 pts[0].x(), pts[0].y(),
389 pts[2].x(), pts[2].y(),
390 pts[1].x(), pts[1].y(),
391 sqrInvScaleX, sqrInvScaleY, outputVertices);
Chris Craik710f46d2012-09-17 17:25:49 -0700392 break;
393 case SkPath::kCubic_Verb:
394 ALOGV("kCubic_Verb");
395 recursiveCubicBezierVertices(
Chris Craikcb4d6002012-09-25 12:00:29 -0700396 pts[0].x(), pts[0].y(),
397 pts[1].x(), pts[1].y(),
398 pts[3].x(), pts[3].y(),
399 pts[2].x(), pts[2].y(),
400 sqrInvScaleX, sqrInvScaleY, outputVertices);
Chris Craik710f46d2012-09-17 17:25:49 -0700401 break;
402 default:
403 break;
404 }
405 }
406}
407
408void PathRenderer::recursiveCubicBezierVertices(
409 float p1x, float p1y, float c1x, float c1y,
410 float p2x, float p2y, float c2x, float c2y,
Chris Craikcb4d6002012-09-25 12:00:29 -0700411 float sqrInvScaleX, float sqrInvScaleY, Vector<Vertex>& outputVertices) {
Chris Craik710f46d2012-09-17 17:25:49 -0700412 float dx = p2x - p1x;
413 float dy = p2y - p1y;
414 float d1 = fabs((c1x - p2x) * dy - (c1y - p2y) * dx);
415 float d2 = fabs((c2x - p2x) * dy - (c2y - p2y) * dx);
416 float d = d1 + d2;
417
Chris Craikcb4d6002012-09-25 12:00:29 -0700418 // multiplying by sqrInvScaleY/X equivalent to multiplying in dimensional scale factors
419
420 if (d * d < THRESHOLD * THRESHOLD * (dx * dx * sqrInvScaleY + dy * dy * sqrInvScaleX)) {
Chris Craik710f46d2012-09-17 17:25:49 -0700421 // below thresh, draw line by adding endpoint
422 // TODO: make this not yuck
423 outputVertices.push();
Chris Craikcb4d6002012-09-25 12:00:29 -0700424 Vertex* newVertex = &(outputVertices.editArray()[outputVertices.size() - 1]);
Chris Craik710f46d2012-09-17 17:25:49 -0700425 Vertex::set(newVertex, p2x, p2y);
426 } else {
427 float p1c1x = (p1x + c1x) * 0.5f;
428 float p1c1y = (p1y + c1y) * 0.5f;
429 float p2c2x = (p2x + c2x) * 0.5f;
430 float p2c2y = (p2y + c2y) * 0.5f;
431
432 float c1c2x = (c1x + c2x) * 0.5f;
433 float c1c2y = (c1y + c2y) * 0.5f;
434
435 float p1c1c2x = (p1c1x + c1c2x) * 0.5f;
436 float p1c1c2y = (p1c1y + c1c2y) * 0.5f;
437
438 float p2c1c2x = (p2c2x + c1c2x) * 0.5f;
439 float p2c1c2y = (p2c2y + c1c2y) * 0.5f;
440
441 float mx = (p1c1c2x + p2c1c2x) * 0.5f;
442 float my = (p1c1c2y + p2c1c2y) * 0.5f;
443
444 recursiveCubicBezierVertices(
445 p1x, p1y, p1c1x, p1c1y,
446 mx, my, p1c1c2x, p1c1c2y,
Chris Craikcb4d6002012-09-25 12:00:29 -0700447 sqrInvScaleX, sqrInvScaleY, outputVertices);
Chris Craik710f46d2012-09-17 17:25:49 -0700448 recursiveCubicBezierVertices(
449 mx, my, p2c1c2x, p2c1c2y,
450 p2x, p2y, p2c2x, p2c2y,
Chris Craikcb4d6002012-09-25 12:00:29 -0700451 sqrInvScaleX, sqrInvScaleY, outputVertices);
Chris Craik710f46d2012-09-17 17:25:49 -0700452 }
453}
454
455void PathRenderer::recursiveQuadraticBezierVertices(
456 float ax, float ay,
457 float bx, float by,
458 float cx, float cy,
Chris Craikcb4d6002012-09-25 12:00:29 -0700459 float sqrInvScaleX, float sqrInvScaleY, Vector<Vertex>& outputVertices) {
Chris Craik710f46d2012-09-17 17:25:49 -0700460 float dx = bx - ax;
461 float dy = by - ay;
462 float d = (cx - bx) * dy - (cy - by) * dx;
463
Chris Craikcb4d6002012-09-25 12:00:29 -0700464 if (d * d < THRESHOLD * THRESHOLD * (dx * dx * sqrInvScaleY + dy * dy * sqrInvScaleX)) {
Chris Craik710f46d2012-09-17 17:25:49 -0700465 // below thresh, draw line by adding endpoint
466 // TODO: make this not yuck
467 outputVertices.push();
Chris Craikcb4d6002012-09-25 12:00:29 -0700468 Vertex* newVertex = &(outputVertices.editArray()[outputVertices.size() - 1]);
Chris Craik710f46d2012-09-17 17:25:49 -0700469 Vertex::set(newVertex, bx, by);
470 } else {
471 float acx = (ax + cx) * 0.5f;
472 float bcx = (bx + cx) * 0.5f;
473 float acy = (ay + cy) * 0.5f;
474 float bcy = (by + cy) * 0.5f;
475
476 // midpoint
477 float mx = (acx + bcx) * 0.5f;
478 float my = (acy + bcy) * 0.5f;
479
480 recursiveQuadraticBezierVertices(ax, ay, mx, my, acx, acy,
Chris Craikcb4d6002012-09-25 12:00:29 -0700481 sqrInvScaleX, sqrInvScaleY, outputVertices);
Chris Craik710f46d2012-09-17 17:25:49 -0700482 recursiveQuadraticBezierVertices(mx, my, bx, by, bcx, bcy,
Chris Craikcb4d6002012-09-25 12:00:29 -0700483 sqrInvScaleX, sqrInvScaleY, outputVertices);
Chris Craik710f46d2012-09-17 17:25:49 -0700484 }
485}
486
487}; // namespace uirenderer
488}; // namespace android