blob: 115435c146412198b5eda31abec4a5e4c334a8da [file] [log] [blame]
Doris Liu30bcf692015-11-04 14:56:24 -08001/*
2 * Copyright (C) 2015 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#include "VectorDrawablePath.h"
18
19#include "PathParser.h"
20
21#include <math.h>
22#include <utils/Log.h>
23
24namespace android {
25namespace uirenderer {
26
27class PathResolver {
28public:
29 float currentX = 0;
30 float currentY = 0;
31 float ctrlPointX = 0;
32 float ctrlPointY = 0;
33 float currentSegmentStartX = 0;
34 float currentSegmentStartY = 0;
35 void addCommand(SkPath* outPath, char previousCmd,
36 char cmd, const std::vector<float>* points, size_t start, size_t end);
37};
38
39VectorDrawablePath::VectorDrawablePath(const char* pathStr, size_t strLength) {
40 PathParser::getPathDataFromString(&mData, pathStr, strLength);
41 verbsToPath(&mSkPath, &mData);
42}
43
44VectorDrawablePath::VectorDrawablePath(const PathData& data) {
45 mData = data;
46 // Now we need to construct a path
47 verbsToPath(&mSkPath, &data);
48}
49
50VectorDrawablePath::VectorDrawablePath(const VectorDrawablePath& path) {
51 mData = path.mData;
52 verbsToPath(&mSkPath, &mData);
53}
54
55bool VectorDrawablePath::canMorph(const PathData& morphTo) {
56 if (mData.verbs.size() != morphTo.verbs.size()) {
57 return false;
58 }
59
60 for (unsigned int i = 0; i < mData.verbs.size(); i++) {
61 if (mData.verbs[i] != morphTo.verbs[i]
62 || mData.verbSizes[i] != morphTo.verbSizes[i]) {
63 return false;
64 }
65 }
66 return true;
67}
68
69bool VectorDrawablePath::canMorph(const VectorDrawablePath& path) {
70 return canMorph(path.mData);
71}
72 /**
73 * Convert an array of PathVerb to Path.
74 */
75void VectorDrawablePath::verbsToPath(SkPath* outPath, const PathData* data) {
76 PathResolver resolver;
77 char previousCommand = 'm';
78 size_t start = 0;
79 outPath->reset();
80 for (unsigned int i = 0; i < data->verbs.size(); i++) {
81 size_t verbSize = data->verbSizes[i];
82 resolver.addCommand(outPath, previousCommand, data->verbs[i], &data->points, start,
83 start + verbSize - 1u);
84 previousCommand = data->verbs[i];
85 start += verbSize;
86 }
87}
88
89/**
90 * The current PathVerb will be interpolated between the
91 * <code>nodeFrom</code> and <code>nodeTo</code> according to the
92 * <code>fraction</code>.
93 *
94 * @param nodeFrom The start value as a PathVerb.
95 * @param nodeTo The end value as a PathVerb
96 * @param fraction The fraction to interpolate.
97 */
98void VectorDrawablePath::interpolatePaths(PathData* outData,
99 const PathData* from, const PathData* to, float fraction) {
100 outData->points.resize(from->points.size());
101 outData->verbSizes = from->verbSizes;
102 outData->verbs = from->verbs;
103
104 for (size_t i = 0; i < from->points.size(); i++) {
105 outData->points[i] = from->points[i] * (1 - fraction) + to->points[i] * fraction;
106 }
107}
108
109/**
110 * Converts an arc to cubic Bezier segments and records them in p.
111 *
112 * @param p The target for the cubic Bezier segments
113 * @param cx The x coordinate center of the ellipse
114 * @param cy The y coordinate center of the ellipse
115 * @param a The radius of the ellipse in the horizontal direction
116 * @param b The radius of the ellipse in the vertical direction
117 * @param e1x E(eta1) x coordinate of the starting point of the arc
118 * @param e1y E(eta2) y coordinate of the starting point of the arc
119 * @param theta The angle that the ellipse bounding rectangle makes with horizontal plane
120 * @param start The start angle of the arc on the ellipse
121 * @param sweep The angle (positive or negative) of the sweep of the arc on the ellipse
122 */
123static void arcToBezier(SkPath* p,
124 double cx,
125 double cy,
126 double a,
127 double b,
128 double e1x,
129 double e1y,
130 double theta,
131 double start,
132 double sweep) {
133 // Taken from equations at: http://spaceroots.org/documents/ellipse/node8.html
134 // and http://www.spaceroots.org/documents/ellipse/node22.html
135
136 // Maximum of 45 degrees per cubic Bezier segment
137 int numSegments = ceil(fabs(sweep * 4 / M_PI));
138
139 double eta1 = start;
140 double cosTheta = cos(theta);
141 double sinTheta = sin(theta);
142 double cosEta1 = cos(eta1);
143 double sinEta1 = sin(eta1);
144 double ep1x = (-a * cosTheta * sinEta1) - (b * sinTheta * cosEta1);
145 double ep1y = (-a * sinTheta * sinEta1) + (b * cosTheta * cosEta1);
146
147 double anglePerSegment = sweep / numSegments;
148 for (int i = 0; i < numSegments; i++) {
149 double eta2 = eta1 + anglePerSegment;
150 double sinEta2 = sin(eta2);
151 double cosEta2 = cos(eta2);
152 double e2x = cx + (a * cosTheta * cosEta2) - (b * sinTheta * sinEta2);
153 double e2y = cy + (a * sinTheta * cosEta2) + (b * cosTheta * sinEta2);
154 double ep2x = -a * cosTheta * sinEta2 - b * sinTheta * cosEta2;
155 double ep2y = -a * sinTheta * sinEta2 + b * cosTheta * cosEta2;
156 double tanDiff2 = tan((eta2 - eta1) / 2);
157 double alpha =
158 sin(eta2 - eta1) * (sqrt(4 + (3 * tanDiff2 * tanDiff2)) - 1) / 3;
159 double q1x = e1x + alpha * ep1x;
160 double q1y = e1y + alpha * ep1y;
161 double q2x = e2x - alpha * ep2x;
162 double q2y = e2y - alpha * ep2y;
163
164 p->cubicTo((float) q1x,
165 (float) q1y,
166 (float) q2x,
167 (float) q2y,
168 (float) e2x,
169 (float) e2y);
170 eta1 = eta2;
171 e1x = e2x;
172 e1y = e2y;
173 ep1x = ep2x;
174 ep1y = ep2y;
175 }
176}
177
178inline double toRadians(float theta) { return theta * M_PI / 180;}
179
180static void drawArc(SkPath* p,
181 float x0,
182 float y0,
183 float x1,
184 float y1,
185 float a,
186 float b,
187 float theta,
188 bool isMoreThanHalf,
189 bool isPositiveArc) {
190
191 /* Convert rotation angle from degrees to radians */
192 double thetaD = toRadians(theta);
193 /* Pre-compute rotation matrix entries */
194 double cosTheta = cos(thetaD);
195 double sinTheta = sin(thetaD);
196 /* Transform (x0, y0) and (x1, y1) into unit space */
197 /* using (inverse) rotation, followed by (inverse) scale */
198 double x0p = (x0 * cosTheta + y0 * sinTheta) / a;
199 double y0p = (-x0 * sinTheta + y0 * cosTheta) / b;
200 double x1p = (x1 * cosTheta + y1 * sinTheta) / a;
201 double y1p = (-x1 * sinTheta + y1 * cosTheta) / b;
202
203 /* Compute differences and averages */
204 double dx = x0p - x1p;
205 double dy = y0p - y1p;
206 double xm = (x0p + x1p) / 2;
207 double ym = (y0p + y1p) / 2;
208 /* Solve for intersecting unit circles */
209 double dsq = dx * dx + dy * dy;
210 if (dsq == 0.0) {
211 ALOGW("Points are coincident");
212 return; /* Points are coincident */
213 }
214 double disc = 1.0 / dsq - 1.0 / 4.0;
215 if (disc < 0.0) {
216 ALOGW("Points are too far apart %f", dsq);
217 float adjust = (float) (sqrt(dsq) / 1.99999);
218 drawArc(p, x0, y0, x1, y1, a * adjust,
219 b * adjust, theta, isMoreThanHalf, isPositiveArc);
220 return; /* Points are too far apart */
221 }
222 double s = sqrt(disc);
223 double sdx = s * dx;
224 double sdy = s * dy;
225 double cx;
226 double cy;
227 if (isMoreThanHalf == isPositiveArc) {
228 cx = xm - sdy;
229 cy = ym + sdx;
230 } else {
231 cx = xm + sdy;
232 cy = ym - sdx;
233 }
234
235 double eta0 = atan2((y0p - cy), (x0p - cx));
236
237 double eta1 = atan2((y1p - cy), (x1p - cx));
238
239 double sweep = (eta1 - eta0);
240 if (isPositiveArc != (sweep >= 0)) {
241 if (sweep > 0) {
242 sweep -= 2 * M_PI;
243 } else {
244 sweep += 2 * M_PI;
245 }
246 }
247
248 cx *= a;
249 cy *= b;
250 double tcx = cx;
251 cx = cx * cosTheta - cy * sinTheta;
252 cy = tcx * sinTheta + cy * cosTheta;
253
254 arcToBezier(p, cx, cy, a, b, x0, y0, thetaD, eta0, sweep);
255}
256
257
258void PathResolver::addCommand(SkPath* outPath, char previousCmd,
259 char cmd, const std::vector<float>* points, size_t start, size_t end) {
260
261 int incr = 2;
262 float reflectiveCtrlPointX;
263 float reflectiveCtrlPointY;
264
265 switch (cmd) {
266 case 'z':
267 case 'Z':
268 outPath->close();
269 // Path is closed here, but we need to move the pen to the
270 // closed position. So we cache the segment's starting position,
271 // and restore it here.
272 currentX = currentSegmentStartX;
273 currentY = currentSegmentStartY;
274 ctrlPointX = currentSegmentStartX;
275 ctrlPointY = currentSegmentStartY;
276 outPath->moveTo(currentX, currentY);
277 break;
278 case 'm':
279 case 'M':
280 case 'l':
281 case 'L':
282 case 't':
283 case 'T':
284 incr = 2;
285 break;
286 case 'h':
287 case 'H':
288 case 'v':
289 case 'V':
290 incr = 1;
291 break;
292 case 'c':
293 case 'C':
294 incr = 6;
295 break;
296 case 's':
297 case 'S':
298 case 'q':
299 case 'Q':
300 incr = 4;
301 break;
302 case 'a':
303 case 'A':
304 incr = 7;
305 break;
306 }
307
308 for (unsigned int k = start; k <= end; k += incr) {
309 switch (cmd) {
310 case 'm': // moveto - Start a new sub-path (relative)
311 currentX += points->at(k + 0);
312 currentY += points->at(k + 1);
313 if (k > start) {
314 // According to the spec, if a moveto is followed by multiple
315 // pairs of coordinates, the subsequent pairs are treated as
316 // implicit lineto commands.
317 outPath->rLineTo(points->at(k + 0), points->at(k + 1));
318 } else {
319 outPath->rMoveTo(points->at(k + 0), points->at(k + 1));
320 currentSegmentStartX = currentX;
321 currentSegmentStartY = currentY;
322 }
323 break;
324 case 'M': // moveto - Start a new sub-path
325 currentX = points->at(k + 0);
326 currentY = points->at(k + 1);
327 if (k > start) {
328 // According to the spec, if a moveto is followed by multiple
329 // pairs of coordinates, the subsequent pairs are treated as
330 // implicit lineto commands.
331 outPath->lineTo(points->at(k + 0), points->at(k + 1));
332 } else {
333 outPath->moveTo(points->at(k + 0), points->at(k + 1));
334 currentSegmentStartX = currentX;
335 currentSegmentStartY = currentY;
336 }
337 break;
338 case 'l': // lineto - Draw a line from the current point (relative)
339 outPath->rLineTo(points->at(k + 0), points->at(k + 1));
340 currentX += points->at(k + 0);
341 currentY += points->at(k + 1);
342 break;
343 case 'L': // lineto - Draw a line from the current point
344 outPath->lineTo(points->at(k + 0), points->at(k + 1));
345 currentX = points->at(k + 0);
346 currentY = points->at(k + 1);
347 break;
348 case 'h': // horizontal lineto - Draws a horizontal line (relative)
349 outPath->rLineTo(points->at(k + 0), 0);
350 currentX += points->at(k + 0);
351 break;
352 case 'H': // horizontal lineto - Draws a horizontal line
353 outPath->lineTo(points->at(k + 0), currentY);
354 currentX = points->at(k + 0);
355 break;
356 case 'v': // vertical lineto - Draws a vertical line from the current point (r)
357 outPath->rLineTo(0, points->at(k + 0));
358 currentY += points->at(k + 0);
359 break;
360 case 'V': // vertical lineto - Draws a vertical line from the current point
361 outPath->lineTo(currentX, points->at(k + 0));
362 currentY = points->at(k + 0);
363 break;
364 case 'c': // curveto - Draws a cubic Bézier curve (relative)
365 outPath->rCubicTo(points->at(k + 0), points->at(k + 1), points->at(k + 2), points->at(k + 3),
366 points->at(k + 4), points->at(k + 5));
367
368 ctrlPointX = currentX + points->at(k + 2);
369 ctrlPointY = currentY + points->at(k + 3);
370 currentX += points->at(k + 4);
371 currentY += points->at(k + 5);
372
373 break;
374 case 'C': // curveto - Draws a cubic Bézier curve
375 outPath->cubicTo(points->at(k + 0), points->at(k + 1), points->at(k + 2), points->at(k + 3),
376 points->at(k + 4), points->at(k + 5));
377 currentX = points->at(k + 4);
378 currentY = points->at(k + 5);
379 ctrlPointX = points->at(k + 2);
380 ctrlPointY = points->at(k + 3);
381 break;
382 case 's': // smooth curveto - Draws a cubic Bézier curve (reflective cp)
383 reflectiveCtrlPointX = 0;
384 reflectiveCtrlPointY = 0;
385 if (previousCmd == 'c' || previousCmd == 's'
386 || previousCmd == 'C' || previousCmd == 'S') {
387 reflectiveCtrlPointX = currentX - ctrlPointX;
388 reflectiveCtrlPointY = currentY - ctrlPointY;
389 }
390 outPath->rCubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
391 points->at(k + 0), points->at(k + 1),
392 points->at(k + 2), points->at(k + 3));
393 ctrlPointX = currentX + points->at(k + 0);
394 ctrlPointY = currentY + points->at(k + 1);
395 currentX += points->at(k + 2);
396 currentY += points->at(k + 3);
397 break;
398 case 'S': // shorthand/smooth curveto Draws a cubic Bézier curve(reflective cp)
399 reflectiveCtrlPointX = currentX;
400 reflectiveCtrlPointY = currentY;
401 if (previousCmd == 'c' || previousCmd == 's'
402 || previousCmd == 'C' || previousCmd == 'S') {
403 reflectiveCtrlPointX = 2 * currentX - ctrlPointX;
404 reflectiveCtrlPointY = 2 * currentY - ctrlPointY;
405 }
406 outPath->cubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
407 points->at(k + 0), points->at(k + 1), points->at(k + 2), points->at(k + 3));
408 ctrlPointX = points->at(k + 0);
409 ctrlPointY = points->at(k + 1);
410 currentX = points->at(k + 2);
411 currentY = points->at(k + 3);
412 break;
413 case 'q': // Draws a quadratic Bézier (relative)
414 outPath->rQuadTo(points->at(k + 0), points->at(k + 1), points->at(k + 2), points->at(k + 3));
415 ctrlPointX = currentX + points->at(k + 0);
416 ctrlPointY = currentY + points->at(k + 1);
417 currentX += points->at(k + 2);
418 currentY += points->at(k + 3);
419 break;
420 case 'Q': // Draws a quadratic Bézier
421 outPath->quadTo(points->at(k + 0), points->at(k + 1), points->at(k + 2), points->at(k + 3));
422 ctrlPointX = points->at(k + 0);
423 ctrlPointY = points->at(k + 1);
424 currentX = points->at(k + 2);
425 currentY = points->at(k + 3);
426 break;
427 case 't': // Draws a quadratic Bézier curve(reflective control point)(relative)
428 reflectiveCtrlPointX = 0;
429 reflectiveCtrlPointY = 0;
430 if (previousCmd == 'q' || previousCmd == 't'
431 || previousCmd == 'Q' || previousCmd == 'T') {
432 reflectiveCtrlPointX = currentX - ctrlPointX;
433 reflectiveCtrlPointY = currentY - ctrlPointY;
434 }
435 outPath->rQuadTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
436 points->at(k + 0), points->at(k + 1));
437 ctrlPointX = currentX + reflectiveCtrlPointX;
438 ctrlPointY = currentY + reflectiveCtrlPointY;
439 currentX += points->at(k + 0);
440 currentY += points->at(k + 1);
441 break;
442 case 'T': // Draws a quadratic Bézier curve (reflective control point)
443 reflectiveCtrlPointX = currentX;
444 reflectiveCtrlPointY = currentY;
445 if (previousCmd == 'q' || previousCmd == 't'
446 || previousCmd == 'Q' || previousCmd == 'T') {
447 reflectiveCtrlPointX = 2 * currentX - ctrlPointX;
448 reflectiveCtrlPointY = 2 * currentY - ctrlPointY;
449 }
450 outPath->quadTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
451 points->at(k + 0), points->at(k + 1));
452 ctrlPointX = reflectiveCtrlPointX;
453 ctrlPointY = reflectiveCtrlPointY;
454 currentX = points->at(k + 0);
455 currentY = points->at(k + 1);
456 break;
457 case 'a': // Draws an elliptical arc
458 // (rx ry x-axis-rotation large-arc-flag sweep-flag x y)
459 drawArc(outPath,
460 currentX,
461 currentY,
462 points->at(k + 5) + currentX,
463 points->at(k + 6) + currentY,
464 points->at(k + 0),
465 points->at(k + 1),
466 points->at(k + 2),
467 points->at(k + 3) != 0,
468 points->at(k + 4) != 0);
469 currentX += points->at(k + 5);
470 currentY += points->at(k + 6);
471 ctrlPointX = currentX;
472 ctrlPointY = currentY;
473 break;
474 case 'A': // Draws an elliptical arc
475 drawArc(outPath,
476 currentX,
477 currentY,
478 points->at(k + 5),
479 points->at(k + 6),
480 points->at(k + 0),
481 points->at(k + 1),
482 points->at(k + 2),
483 points->at(k + 3) != 0,
484 points->at(k + 4) != 0);
485 currentX = points->at(k + 5);
486 currentY = points->at(k + 6);
487 ctrlPointX = currentX;
488 ctrlPointY = currentY;
489 break;
490 }
491 previousCmd = cmd;
492 }
493}
494
495}; // namespace uirenderer
496}; // namespace android