diff options
author | 2015-11-04 14:56:24 -0800 | |
---|---|---|
committer | 2015-11-10 15:46:06 -0800 | |
commit | 30bcf69df9cfae40b621335958656cb0e4afd7d5 (patch) | |
tree | d4cc05d731a54d19775a0fd5245d0764a5156bcf /libs | |
parent | 429c5b93ff66e82fa3fd65475489fde133c66002 (diff) |
VectorDrawable native rendering - Step 1 of MANY
Implement path parsing from string to skia path in native. The parsing
contains two main stages:
1) Parse string into a list of nodes that contains one operation (such
as move) and a vector of floats as params for that operation.
2) Interpret the operations defined in the nodes into SkPath operations,
and create a skia path
Also provided unit test for parsing a string path into a list of nodes,
and then to a skia path.
Change-Id: I0ce13df5e3bb90987dcdc80fe8b039af175ad2e2
Diffstat (limited to 'libs')
-rw-r--r-- | libs/hwui/Android.mk | 3 | ||||
-rw-r--r-- | libs/hwui/PathParser.cpp | 193 | ||||
-rw-r--r-- | libs/hwui/PathParser.h | 37 | ||||
-rw-r--r-- | libs/hwui/VectorDrawablePath.cpp | 496 | ||||
-rw-r--r-- | libs/hwui/VectorDrawablePath.h | 58 | ||||
-rw-r--r-- | libs/hwui/unit_tests/PathParserTests.cpp | 172 | ||||
-rw-r--r-- | libs/hwui/utils/FatVector.h | 2 |
7 files changed, 961 insertions, 0 deletions
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk index 9d3d4ae5c7ca..95e4e77371a8 100644 --- a/libs/hwui/Android.mk +++ b/libs/hwui/Android.mk @@ -64,6 +64,7 @@ hwui_src_files := \ PatchCache.cpp \ PathCache.cpp \ PathTessellator.cpp \ + PathParser.cpp \ PixelBuffer.cpp \ Program.cpp \ ProgramCache.cpp \ @@ -82,6 +83,7 @@ hwui_src_files := \ TextDropShadowCache.cpp \ Texture.cpp \ TextureCache.cpp \ + VectorDrawablePath.cpp \ protos/hwui.proto hwui_cflags := \ @@ -213,6 +215,7 @@ LOCAL_SRC_FILES += \ unit_tests/FatVectorTests.cpp \ unit_tests/LayerUpdateQueueTests.cpp \ unit_tests/LinearAllocatorTests.cpp \ + unit_tests/PathParserTests.cpp \ unit_tests/StringUtilsTests.cpp ifeq (true, $(HWUI_NEW_OPS)) diff --git a/libs/hwui/PathParser.cpp b/libs/hwui/PathParser.cpp new file mode 100644 index 000000000000..e8ed8a14100a --- /dev/null +++ b/libs/hwui/PathParser.cpp @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "PathParser.h" + +#include "jni.h" + +#include <utils/Log.h> +#include <sstream> +#include <stdlib.h> +#include <string> +#include <vector> + +namespace android { +namespace uirenderer { + +static size_t nextStart(const char* s, size_t length, size_t startIndex) { + size_t index = startIndex; + while (index < length) { + char c = s[index]; + // Note that 'e' or 'E' are not valid path commands, but could be + // used for floating point numbers' scientific notation. + // Therefore, when searching for next command, we should ignore 'e' + // and 'E'. + if ((((c - 'A') * (c - 'Z') <= 0) || ((c - 'a') * (c - 'z') <= 0)) + && c != 'e' && c != 'E') { + return index; + } + index++; + } + return index; +} + +/** + * Calculate the position of the next comma or space or negative sign + * @param s the string to search + * @param start the position to start searching + * @param result the result of the extraction, including the position of the + * the starting position of next number, whether it is ending with a '-'. + */ +static void extract(int* outEndPosition, bool* outEndWithNegOrDot, const char* s, int start, int end) { + // Now looking for ' ', ',', '.' or '-' from the start. + int currentIndex = start; + bool foundSeparator = false; + *outEndWithNegOrDot = false; + bool secondDot = false; + bool isExponential = false; + for (; currentIndex < end; currentIndex++) { + bool isPrevExponential = isExponential; + isExponential = false; + char currentChar = s[currentIndex]; + switch (currentChar) { + case ' ': + case ',': + foundSeparator = true; + break; + case '-': + // The negative sign following a 'e' or 'E' is not a separator. + if (currentIndex != start && !isPrevExponential) { + foundSeparator = true; + *outEndWithNegOrDot = true; + } + break; + case '.': + if (!secondDot) { + secondDot = true; + } else { + // This is the second dot, and it is considered as a separator. + foundSeparator = true; + *outEndWithNegOrDot = true; + } + break; + case 'e': + case 'E': + isExponential = true; + break; + } + if (foundSeparator) { + break; + } + } + // In the case where nothing is found, we put the end position to the end of + // our extract range. Otherwise, end position will be where separator is found. + *outEndPosition = currentIndex; +} + +/** +* Parse the floats in the string. +* This is an optimized version of parseFloat(s.split(",|\\s")); +* +* @param s the string containing a command and list of floats +* @return array of floats +*/ +static void getFloats(std::vector<float>* outPoints, const char* pathStr, int start, int end) { + + if (pathStr[start] == 'z' || pathStr[start] == 'Z') { + return; + } + int startPosition = start + 1; + int endPosition = start; + + // The startPosition should always be the first character of the + // current number, and endPosition is the character after the current + // number. + while (startPosition < end) { + bool endWithNegOrDot; + extract(&endPosition, &endWithNegOrDot, pathStr, startPosition, end); + + if (startPosition < endPosition) { + outPoints->push_back(strtof(&pathStr[startPosition], NULL)); + } + + if (endWithNegOrDot) { + // Keep the '-' or '.' sign with next number. + startPosition = endPosition; + } else { + startPosition = endPosition + 1; + } + } +} + +void PathParser::getPathDataFromString(PathData* data, const char* pathStr, size_t strLen) { + if (pathStr == NULL) { + return; + } + + size_t start = 0; + size_t end = 1; + + while (end < strLen) { + end = nextStart(pathStr, strLen, end); + std::vector<float> points; + getFloats(&points, pathStr, start, end); + data->verbs.push_back(pathStr[start]); + data->verbSizes.push_back(points.size()); + data->points.insert(data->points.end(), points.begin(), points.end()); + start = end; + end++; + } + + if ((end - start) == 1 && pathStr[start] != '\0') { + data->verbs.push_back(pathStr[start]); + data->verbSizes.push_back(0); + } + + int i = 0; + while(pathStr[i] != '\0') { + i++; + } + +} + +void PathParser::dump(const PathData& data) { + // Print out the path data. + size_t start = 0; + for (size_t i = 0; i < data.verbs.size(); i++) { + std::ostringstream os; + os << data.verbs[i]; + for (size_t j = 0; j < data.verbSizes[i]; j++) { + os << " " << data.points[start + j]; + } + start += data.verbSizes[i]; + ALOGD("%s", os.str().c_str()); + } + + std::ostringstream os; + for (size_t i = 0; i < data.points.size(); i++) { + os << data.points[i] << ", "; + } + ALOGD("points are : %s", os.str().c_str()); +} + +void PathParser::parseStringForSkPath(SkPath* skPath, const char* pathStr, size_t strLen) { + PathData pathData; + getPathDataFromString(&pathData, pathStr, strLen); + VectorDrawablePath::verbsToPath(skPath, &pathData); +} + +}; // namespace uirenderer +}; //namespace android diff --git a/libs/hwui/PathParser.h b/libs/hwui/PathParser.h new file mode 100644 index 000000000000..6dc7ee193197 --- /dev/null +++ b/libs/hwui/PathParser.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_HWUI_PATHPARSER_H +#define ANDROID_HWUI_PATHPARSER_H + +#include "VectorDrawablePath.h" + +#include <jni.h> +#include <android/log.h> + +namespace android { +namespace uirenderer { + +class PathParser { +public: + static void parseStringForSkPath(SkPath* outPath, const char* pathStr, size_t strLength); + static void getPathDataFromString(PathData* outData, const char* pathStr, size_t strLength); + static void dump(const PathData& data); +}; + +}; // namespace uirenderer +}; // namespace android +#endif //ANDROID_HWUI_PATHPARSER_H diff --git a/libs/hwui/VectorDrawablePath.cpp b/libs/hwui/VectorDrawablePath.cpp new file mode 100644 index 000000000000..115435c14641 --- /dev/null +++ b/libs/hwui/VectorDrawablePath.cpp @@ -0,0 +1,496 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "VectorDrawablePath.h" + +#include "PathParser.h" + +#include <math.h> +#include <utils/Log.h> + +namespace android { +namespace uirenderer { + +class PathResolver { +public: + float currentX = 0; + float currentY = 0; + float ctrlPointX = 0; + float ctrlPointY = 0; + float currentSegmentStartX = 0; + float currentSegmentStartY = 0; + void addCommand(SkPath* outPath, char previousCmd, + char cmd, const std::vector<float>* points, size_t start, size_t end); +}; + +VectorDrawablePath::VectorDrawablePath(const char* pathStr, size_t strLength) { + PathParser::getPathDataFromString(&mData, pathStr, strLength); + verbsToPath(&mSkPath, &mData); +} + +VectorDrawablePath::VectorDrawablePath(const PathData& data) { + mData = data; + // Now we need to construct a path + verbsToPath(&mSkPath, &data); +} + +VectorDrawablePath::VectorDrawablePath(const VectorDrawablePath& path) { + mData = path.mData; + verbsToPath(&mSkPath, &mData); +} + +bool VectorDrawablePath::canMorph(const PathData& morphTo) { + if (mData.verbs.size() != morphTo.verbs.size()) { + return false; + } + + for (unsigned int i = 0; i < mData.verbs.size(); i++) { + if (mData.verbs[i] != morphTo.verbs[i] + || mData.verbSizes[i] != morphTo.verbSizes[i]) { + return false; + } + } + return true; +} + +bool VectorDrawablePath::canMorph(const VectorDrawablePath& path) { + return canMorph(path.mData); +} + /** + * Convert an array of PathVerb to Path. + */ +void VectorDrawablePath::verbsToPath(SkPath* outPath, const PathData* data) { + PathResolver resolver; + char previousCommand = 'm'; + size_t start = 0; + outPath->reset(); + for (unsigned int i = 0; i < data->verbs.size(); i++) { + size_t verbSize = data->verbSizes[i]; + resolver.addCommand(outPath, previousCommand, data->verbs[i], &data->points, start, + start + verbSize - 1u); + previousCommand = data->verbs[i]; + start += verbSize; + } +} + +/** + * The current PathVerb will be interpolated between the + * <code>nodeFrom</code> and <code>nodeTo</code> according to the + * <code>fraction</code>. + * + * @param nodeFrom The start value as a PathVerb. + * @param nodeTo The end value as a PathVerb + * @param fraction The fraction to interpolate. + */ +void VectorDrawablePath::interpolatePaths(PathData* outData, + const PathData* from, const PathData* to, float fraction) { + outData->points.resize(from->points.size()); + outData->verbSizes = from->verbSizes; + outData->verbs = from->verbs; + + for (size_t i = 0; i < from->points.size(); i++) { + outData->points[i] = from->points[i] * (1 - fraction) + to->points[i] * fraction; + } +} + +/** + * Converts an arc to cubic Bezier segments and records them in p. + * + * @param p The target for the cubic Bezier segments + * @param cx The x coordinate center of the ellipse + * @param cy The y coordinate center of the ellipse + * @param a The radius of the ellipse in the horizontal direction + * @param b The radius of the ellipse in the vertical direction + * @param e1x E(eta1) x coordinate of the starting point of the arc + * @param e1y E(eta2) y coordinate of the starting point of the arc + * @param theta The angle that the ellipse bounding rectangle makes with horizontal plane + * @param start The start angle of the arc on the ellipse + * @param sweep The angle (positive or negative) of the sweep of the arc on the ellipse + */ +static void arcToBezier(SkPath* p, + double cx, + double cy, + double a, + double b, + double e1x, + double e1y, + double theta, + double start, + double sweep) { + // Taken from equations at: http://spaceroots.org/documents/ellipse/node8.html + // and http://www.spaceroots.org/documents/ellipse/node22.html + + // Maximum of 45 degrees per cubic Bezier segment + int numSegments = ceil(fabs(sweep * 4 / M_PI)); + + double eta1 = start; + double cosTheta = cos(theta); + double sinTheta = sin(theta); + double cosEta1 = cos(eta1); + double sinEta1 = sin(eta1); + double ep1x = (-a * cosTheta * sinEta1) - (b * sinTheta * cosEta1); + double ep1y = (-a * sinTheta * sinEta1) + (b * cosTheta * cosEta1); + + double anglePerSegment = sweep / numSegments; + for (int i = 0; i < numSegments; i++) { + double eta2 = eta1 + anglePerSegment; + double sinEta2 = sin(eta2); + double cosEta2 = cos(eta2); + double e2x = cx + (a * cosTheta * cosEta2) - (b * sinTheta * sinEta2); + double e2y = cy + (a * sinTheta * cosEta2) + (b * cosTheta * sinEta2); + double ep2x = -a * cosTheta * sinEta2 - b * sinTheta * cosEta2; + double ep2y = -a * sinTheta * sinEta2 + b * cosTheta * cosEta2; + double tanDiff2 = tan((eta2 - eta1) / 2); + double alpha = + sin(eta2 - eta1) * (sqrt(4 + (3 * tanDiff2 * tanDiff2)) - 1) / 3; + double q1x = e1x + alpha * ep1x; + double q1y = e1y + alpha * ep1y; + double q2x = e2x - alpha * ep2x; + double q2y = e2y - alpha * ep2y; + + p->cubicTo((float) q1x, + (float) q1y, + (float) q2x, + (float) q2y, + (float) e2x, + (float) e2y); + eta1 = eta2; + e1x = e2x; + e1y = e2y; + ep1x = ep2x; + ep1y = ep2y; + } +} + +inline double toRadians(float theta) { return theta * M_PI / 180;} + +static void drawArc(SkPath* p, + float x0, + float y0, + float x1, + float y1, + float a, + float b, + float theta, + bool isMoreThanHalf, + bool isPositiveArc) { + + /* Convert rotation angle from degrees to radians */ + double thetaD = toRadians(theta); + /* Pre-compute rotation matrix entries */ + double cosTheta = cos(thetaD); + double sinTheta = sin(thetaD); + /* Transform (x0, y0) and (x1, y1) into unit space */ + /* using (inverse) rotation, followed by (inverse) scale */ + double x0p = (x0 * cosTheta + y0 * sinTheta) / a; + double y0p = (-x0 * sinTheta + y0 * cosTheta) / b; + double x1p = (x1 * cosTheta + y1 * sinTheta) / a; + double y1p = (-x1 * sinTheta + y1 * cosTheta) / b; + + /* Compute differences and averages */ + double dx = x0p - x1p; + double dy = y0p - y1p; + double xm = (x0p + x1p) / 2; + double ym = (y0p + y1p) / 2; + /* Solve for intersecting unit circles */ + double dsq = dx * dx + dy * dy; + if (dsq == 0.0) { + ALOGW("Points are coincident"); + return; /* Points are coincident */ + } + double disc = 1.0 / dsq - 1.0 / 4.0; + if (disc < 0.0) { + ALOGW("Points are too far apart %f", dsq); + float adjust = (float) (sqrt(dsq) / 1.99999); + drawArc(p, x0, y0, x1, y1, a * adjust, + b * adjust, theta, isMoreThanHalf, isPositiveArc); + return; /* Points are too far apart */ + } + double s = sqrt(disc); + double sdx = s * dx; + double sdy = s * dy; + double cx; + double cy; + if (isMoreThanHalf == isPositiveArc) { + cx = xm - sdy; + cy = ym + sdx; + } else { + cx = xm + sdy; + cy = ym - sdx; + } + + double eta0 = atan2((y0p - cy), (x0p - cx)); + + double eta1 = atan2((y1p - cy), (x1p - cx)); + + double sweep = (eta1 - eta0); + if (isPositiveArc != (sweep >= 0)) { + if (sweep > 0) { + sweep -= 2 * M_PI; + } else { + sweep += 2 * M_PI; + } + } + + cx *= a; + cy *= b; + double tcx = cx; + cx = cx * cosTheta - cy * sinTheta; + cy = tcx * sinTheta + cy * cosTheta; + + arcToBezier(p, cx, cy, a, b, x0, y0, thetaD, eta0, sweep); +} + + +void PathResolver::addCommand(SkPath* outPath, char previousCmd, + char cmd, const std::vector<float>* points, size_t start, size_t end) { + + int incr = 2; + float reflectiveCtrlPointX; + float reflectiveCtrlPointY; + + switch (cmd) { + case 'z': + case 'Z': + outPath->close(); + // Path is closed here, but we need to move the pen to the + // closed position. So we cache the segment's starting position, + // and restore it here. + currentX = currentSegmentStartX; + currentY = currentSegmentStartY; + ctrlPointX = currentSegmentStartX; + ctrlPointY = currentSegmentStartY; + outPath->moveTo(currentX, currentY); + break; + case 'm': + case 'M': + case 'l': + case 'L': + case 't': + case 'T': + incr = 2; + break; + case 'h': + case 'H': + case 'v': + case 'V': + incr = 1; + break; + case 'c': + case 'C': + incr = 6; + break; + case 's': + case 'S': + case 'q': + case 'Q': + incr = 4; + break; + case 'a': + case 'A': + incr = 7; + break; + } + + for (unsigned int k = start; k <= end; k += incr) { + switch (cmd) { + case 'm': // moveto - Start a new sub-path (relative) + currentX += points->at(k + 0); + currentY += points->at(k + 1); + if (k > start) { + // According to the spec, if a moveto is followed by multiple + // pairs of coordinates, the subsequent pairs are treated as + // implicit lineto commands. + outPath->rLineTo(points->at(k + 0), points->at(k + 1)); + } else { + outPath->rMoveTo(points->at(k + 0), points->at(k + 1)); + currentSegmentStartX = currentX; + currentSegmentStartY = currentY; + } + break; + case 'M': // moveto - Start a new sub-path + currentX = points->at(k + 0); + currentY = points->at(k + 1); + if (k > start) { + // According to the spec, if a moveto is followed by multiple + // pairs of coordinates, the subsequent pairs are treated as + // implicit lineto commands. + outPath->lineTo(points->at(k + 0), points->at(k + 1)); + } else { + outPath->moveTo(points->at(k + 0), points->at(k + 1)); + currentSegmentStartX = currentX; + currentSegmentStartY = currentY; + } + break; + case 'l': // lineto - Draw a line from the current point (relative) + outPath->rLineTo(points->at(k + 0), points->at(k + 1)); + currentX += points->at(k + 0); + currentY += points->at(k + 1); + break; + case 'L': // lineto - Draw a line from the current point + outPath->lineTo(points->at(k + 0), points->at(k + 1)); + currentX = points->at(k + 0); + currentY = points->at(k + 1); + break; + case 'h': // horizontal lineto - Draws a horizontal line (relative) + outPath->rLineTo(points->at(k + 0), 0); + currentX += points->at(k + 0); + break; + case 'H': // horizontal lineto - Draws a horizontal line + outPath->lineTo(points->at(k + 0), currentY); + currentX = points->at(k + 0); + break; + case 'v': // vertical lineto - Draws a vertical line from the current point (r) + outPath->rLineTo(0, points->at(k + 0)); + currentY += points->at(k + 0); + break; + case 'V': // vertical lineto - Draws a vertical line from the current point + outPath->lineTo(currentX, points->at(k + 0)); + currentY = points->at(k + 0); + break; + case 'c': // curveto - Draws a cubic Bézier curve (relative) + outPath->rCubicTo(points->at(k + 0), points->at(k + 1), points->at(k + 2), points->at(k + 3), + points->at(k + 4), points->at(k + 5)); + + ctrlPointX = currentX + points->at(k + 2); + ctrlPointY = currentY + points->at(k + 3); + currentX += points->at(k + 4); + currentY += points->at(k + 5); + + break; + case 'C': // curveto - Draws a cubic Bézier curve + outPath->cubicTo(points->at(k + 0), points->at(k + 1), points->at(k + 2), points->at(k + 3), + points->at(k + 4), points->at(k + 5)); + currentX = points->at(k + 4); + currentY = points->at(k + 5); + ctrlPointX = points->at(k + 2); + ctrlPointY = points->at(k + 3); + break; + case 's': // smooth curveto - Draws a cubic Bézier curve (reflective cp) + reflectiveCtrlPointX = 0; + reflectiveCtrlPointY = 0; + if (previousCmd == 'c' || previousCmd == 's' + || previousCmd == 'C' || previousCmd == 'S') { + reflectiveCtrlPointX = currentX - ctrlPointX; + reflectiveCtrlPointY = currentY - ctrlPointY; + } + outPath->rCubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY, + points->at(k + 0), points->at(k + 1), + points->at(k + 2), points->at(k + 3)); + ctrlPointX = currentX + points->at(k + 0); + ctrlPointY = currentY + points->at(k + 1); + currentX += points->at(k + 2); + currentY += points->at(k + 3); + break; + case 'S': // shorthand/smooth curveto Draws a cubic Bézier curve(reflective cp) + reflectiveCtrlPointX = currentX; + reflectiveCtrlPointY = currentY; + if (previousCmd == 'c' || previousCmd == 's' + || previousCmd == 'C' || previousCmd == 'S') { + reflectiveCtrlPointX = 2 * currentX - ctrlPointX; + reflectiveCtrlPointY = 2 * currentY - ctrlPointY; + } + outPath->cubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY, + points->at(k + 0), points->at(k + 1), points->at(k + 2), points->at(k + 3)); + ctrlPointX = points->at(k + 0); + ctrlPointY = points->at(k + 1); + currentX = points->at(k + 2); + currentY = points->at(k + 3); + break; + case 'q': // Draws a quadratic Bézier (relative) + outPath->rQuadTo(points->at(k + 0), points->at(k + 1), points->at(k + 2), points->at(k + 3)); + ctrlPointX = currentX + points->at(k + 0); + ctrlPointY = currentY + points->at(k + 1); + currentX += points->at(k + 2); + currentY += points->at(k + 3); + break; + case 'Q': // Draws a quadratic Bézier + outPath->quadTo(points->at(k + 0), points->at(k + 1), points->at(k + 2), points->at(k + 3)); + ctrlPointX = points->at(k + 0); + ctrlPointY = points->at(k + 1); + currentX = points->at(k + 2); + currentY = points->at(k + 3); + break; + case 't': // Draws a quadratic Bézier curve(reflective control point)(relative) + reflectiveCtrlPointX = 0; + reflectiveCtrlPointY = 0; + if (previousCmd == 'q' || previousCmd == 't' + || previousCmd == 'Q' || previousCmd == 'T') { + reflectiveCtrlPointX = currentX - ctrlPointX; + reflectiveCtrlPointY = currentY - ctrlPointY; + } + outPath->rQuadTo(reflectiveCtrlPointX, reflectiveCtrlPointY, + points->at(k + 0), points->at(k + 1)); + ctrlPointX = currentX + reflectiveCtrlPointX; + ctrlPointY = currentY + reflectiveCtrlPointY; + currentX += points->at(k + 0); + currentY += points->at(k + 1); + break; + case 'T': // Draws a quadratic Bézier curve (reflective control point) + reflectiveCtrlPointX = currentX; + reflectiveCtrlPointY = currentY; + if (previousCmd == 'q' || previousCmd == 't' + || previousCmd == 'Q' || previousCmd == 'T') { + reflectiveCtrlPointX = 2 * currentX - ctrlPointX; + reflectiveCtrlPointY = 2 * currentY - ctrlPointY; + } + outPath->quadTo(reflectiveCtrlPointX, reflectiveCtrlPointY, + points->at(k + 0), points->at(k + 1)); + ctrlPointX = reflectiveCtrlPointX; + ctrlPointY = reflectiveCtrlPointY; + currentX = points->at(k + 0); + currentY = points->at(k + 1); + break; + case 'a': // Draws an elliptical arc + // (rx ry x-axis-rotation large-arc-flag sweep-flag x y) + drawArc(outPath, + currentX, + currentY, + points->at(k + 5) + currentX, + points->at(k + 6) + currentY, + points->at(k + 0), + points->at(k + 1), + points->at(k + 2), + points->at(k + 3) != 0, + points->at(k + 4) != 0); + currentX += points->at(k + 5); + currentY += points->at(k + 6); + ctrlPointX = currentX; + ctrlPointY = currentY; + break; + case 'A': // Draws an elliptical arc + drawArc(outPath, + currentX, + currentY, + points->at(k + 5), + points->at(k + 6), + points->at(k + 0), + points->at(k + 1), + points->at(k + 2), + points->at(k + 3) != 0, + points->at(k + 4) != 0); + currentX = points->at(k + 5); + currentY = points->at(k + 6); + ctrlPointX = currentX; + ctrlPointY = currentY; + break; + } + previousCmd = cmd; + } +} + +}; // namespace uirenderer +}; // namespace android diff --git a/libs/hwui/VectorDrawablePath.h b/libs/hwui/VectorDrawablePath.h new file mode 100644 index 000000000000..40ce986b1a57 --- /dev/null +++ b/libs/hwui/VectorDrawablePath.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_HWUI_VPATH_H +#define ANDROID_HWUI_VPATH_H + +#include "SkPath.h" +#include <vector> + +namespace android { +namespace uirenderer { + + + +struct PathData { + // TODO: Try using FatVector instead of std::vector and do a micro benchmark on the performance + // difference. + std::vector<char> verbs; + std::vector<size_t> verbSizes; + std::vector<float> points; + bool operator== (const PathData& data) const { + return verbs == data.verbs && verbSizes == data.verbSizes && points == data.points; + } + +}; + +class VectorDrawablePath { +public: + VectorDrawablePath(const PathData& nodes); + VectorDrawablePath(const VectorDrawablePath& path); + VectorDrawablePath(const char* path, size_t strLength); + bool canMorph(const PathData& path); + bool canMorph(const VectorDrawablePath& path); + static void verbsToPath(SkPath* outPath, const PathData* data); + static void interpolatePaths(PathData* outPathData, const PathData* from, const PathData* to, + float fraction); +private: + PathData mData; + SkPath mSkPath; +}; + +} // namespace uirenderer +} // namespace android + +#endif // ANDROID_HWUI_VPATH_H diff --git a/libs/hwui/unit_tests/PathParserTests.cpp b/libs/hwui/unit_tests/PathParserTests.cpp new file mode 100644 index 000000000000..244bd6c7a4bd --- /dev/null +++ b/libs/hwui/unit_tests/PathParserTests.cpp @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <gtest/gtest.h> + +#include "PathParser.h" +#include "VectorDrawablePath.h" + +#include <functional> + +namespace android { +namespace uirenderer { + +struct TestData { + const char* pathString; + const PathData pathData; + const std::function<void(SkPath*)> skPathLamda; +}; + +static TestData testData1 { + // Path + "M2.000000,22.000000l20.000000,0.000000 1e0-2e3z", + { + // Verbs + {'M', 'l', 'z'}, + // Verb sizes + {2, 4, 0}, + // Points + {2, 22, 20, 0, 1, -2000}, + }, + [](SkPath* outPath) { + outPath->moveTo(2, 22); + outPath->rLineTo(20, 0); + outPath->rLineTo(1, -2000); + outPath->close(); + outPath->moveTo(2, 22); + } +}; + +static TestData testData2 { + // Path + "M 1 1 m 2 2, l 3 3 L 3 3 H 4 h4 V5 v5, Q6 6 6 6 q 6 6 6 6t 7 7 T 7 7 C 8 8 8 8 8 8 c 8 8 8 8 8 8 S 9 9 9 9 s 9 9 9 9 A 10 10 0 1 1 10 10 a 10 10 0 1 1 10 10", + { + // Verbs + {'M', 'm', 'l', 'L', 'H', 'h', 'V', 'v', 'Q', 'q', 't', 'T', 'C', 'c', 'S', 's', 'A', 'a'}, + // VerbSizes + {2, 2, 2, 2, 1, 1, 1, 1, 4, 4, 2, 2, 6, 6, 4, 4, 7, 7}, + // Points + {1.0, 1.0, 2.0, 2.0, 3.0, 3.0, 3.0, 3.0, 4.0, 4.0, 5.0, 5.0, 6.0, 6.0, 6.0, 6.0, 6.0, 6.0, 6.0, 6.0, 7.0, 7.0, 7.0, 7.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 9.0, 9.0, 9.0, 9.0, 9.0, 9.0, 9.0, 9.0, 10.0, 10.0, 0.0, 1.0, 1.0, 10.0, 10.0, 10.0, 10.0, 0.0, 1.0, 1.0, 10.0, 10.0, } + }, + [](SkPath* outPath) { + outPath->moveTo(1.0, 1.0); + outPath->rMoveTo(2.0, 2.0); + outPath->rLineTo(3.0, 3.0); + outPath->lineTo(3.0, 3.0); + outPath->lineTo(4.0, 3.0); + outPath->rLineTo(4.0, 0); + outPath->lineTo(8.0, 5.0); + outPath->rLineTo(0, 5.0); + outPath->quadTo(6.0, 6.0, 6.0, 6.0); + outPath->rQuadTo(6.0, 6.0, 6.0, 6.0); + outPath->rQuadTo(0.0, 0.0, 7.0, 7.0); + outPath->quadTo(26.0, 26.0, 7.0, 7.0); + outPath->cubicTo(8.0, 8.0, 8.0, 8.0, 8.0, 8.0); + outPath->rCubicTo(8.0, 8.0, 8.0, 8.0, 8.0, 8.0); + outPath->cubicTo(16.0, 16.0, 9.0, 9.0, 9.0, 9.0); + outPath->rCubicTo(0.0, 0.0, 9.0, 9.0, 9.0, 9.0); + outPath->cubicTo(18.447775037328352, 20.404243860300607, 17.998389141249767, 22.8911717921705, 16.737515350332117, 24.986664170401575); + outPath->cubicTo(15.476641559414468, 27.08215654863265, 13.489843598291483, 28.644011882390082, 11.155893964798905, 29.37447073281729); + outPath->cubicTo(8.821944331306327, 30.1049295832445, 6.299226382436471, 29.954422532383525, 4.0686829203897235, 28.951642951534332); + outPath->cubicTo(1.838139458342976, 27.94886337068514, 0.05113662931485696, 26.161860541657013, -0.9516429515343354, 23.931317079610267); + outPath->cubicTo(-1.9544225323835278, 21.70077361756352, -2.1049295832444987, 19.178055668693663, -1.37447073281729, 16.844106035201087); + outPath->cubicTo(-0.6440118823900814, 14.51015640170851, 0.9178434513673546, 12.523358440585524, 3.0133358295984305, 11.262484649667876); + outPath->cubicTo(5.108828207829506, 10.001610858750228, 7.5957561396993984, 9.552224962671648, 10.000000000000005, 10.0); + outPath->cubicTo(10.0, 7.348852265086975, 11.054287646850167, 4.803576729418881, 12.928932188134523, 2.9289321881345254); + outPath->cubicTo(14.803576729418879, 1.0542876468501696, 17.348852265086972, 4.870079381441987E-16, 19.999999999999996, 0.0); + outPath->cubicTo(22.65114773491302, -4.870079381441987E-16, 25.19642327058112, 1.0542876468501678, 27.071067811865476, 2.9289321881345227); + outPath->cubicTo(28.94571235314983, 4.803576729418878, 30.0, 7.348852265086974, 30.0, 9.999999999999998); + outPath->cubicTo(30.0, 12.651147734913023, 28.94571235314983, 15.19642327058112, 27.071067811865476, 17.071067811865476); + outPath->cubicTo(25.19642327058112, 18.94571235314983, 22.651147734913028, 20.0, 20.000000000000004, 20.0); + } +}; + +static TestData testData3 { + // Path + "M5.3,13.2c-0.1,0.0 -0.3,0.0 -0.4,-0.1c-0.3,-0.2 -0.4,-0.7 -0.2,-1.0c1.3,-1.9 2.9,-3.4 4.9,-4.5c4.1,-2.2 9.3,-2.2 13.4,0.0c1.9,1.1 3.6,2.5 4.9,4.4c0.2,0.3 0.1,0.8 -0.2,1.0c-0.3,0.2 -0.8,0.1 -1.0,-0.2c-1.2,-1.7 -2.6,-3.0 -4.3,-4.0c-3.7,-2.0 -8.3,-2.0 -12.0,0.0c-1.7,0.9 -3.2,2.3 -4.3,4.0C5.7,13.1 5.5,13.2 5.3,13.2z", + { + // Verbs + {'M', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'C', 'z'}, + // Verb sizes + {2, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0}, + // Points + {5.3, 13.2, -0.1, 0, -0.3, 0, -0.4, -0.1, -0.3, -0.2, -0.4, -0.7, -0.2, -1, 1.3, -1.9, 2.9, -3.4, 4.9, -4.5, 4.1, -2.2, 9.3, -2.2, 13.4, 0, 1.9, 1.1, 3.6, 2.5, 4.9, 4.4, 0.2, 0.3, 0.1, 0.8, -0.2, 1, -0.3, 0.2, -0.8, 0.1, -1, -0.2, -1.2, -1.7, -2.6, -3, -4.3, -4, -3.7, -2, -8.3, -2, -12, 0, -1.7, 0.9, -3.2, 2.3, -4.3, 4, 5.7, 13.1, 5.5, 13.2, 5.3, 13.2}, + }, + [](SkPath* outPath) { + outPath->moveTo(5.3, 13.2); + outPath->rCubicTo(-0.1, 0.0, -0.3, 0.0, -0.4, -0.1); + outPath->rCubicTo(-0.3, -0.2, -0.4, -0.7, -0.2, -1.0); + outPath->rCubicTo(1.3, -1.9, 2.9, -3.4, 4.9, -4.5); + outPath->rCubicTo(4.1, -2.2, 9.3, -2.2, 13.4, 0.0); + outPath->rCubicTo(1.9, 1.1, 3.6, 2.5, 4.9, 4.4); + outPath->rCubicTo(0.2, 0.3, 0.1, 0.8, -0.2, 1.0); + outPath->rCubicTo(-0.3, 0.2, -0.8, 0.1, -1.0, -0.2); + outPath->rCubicTo(-1.2, -1.7, -2.6, -3.0, -4.3, -4.0); + outPath->rCubicTo(-3.7, -2.0, -8.3, -2.0, -12.0, 0.0); + outPath->rCubicTo(-1.7, 0.9, -3.2, 2.3, -4.3, 4.0); + outPath->cubicTo(5.7, 13.1, 5.5, 13.2, 5.3, 13.2); + outPath->close(); + outPath->moveTo(5.3, 13.2); + } +}; + +static TestData testData4 { + // Path + "l0.0.0.5.0.0.5-0.5.0.0-.5z", + { + // Verbs + {'l', 'z'}, + // Verb sizes + {10, 0}, + // Points + {0, 0, 0.5, 0, 0, 0.5, -0.5, 0, 0, -0.5}, + }, + [](SkPath* outPath) { + outPath->rLineTo(0.0, 0.0); + outPath->rLineTo(0.5, 0.0); + outPath->rLineTo(0.0, 0.5); + outPath->rLineTo(-0.5, 0.0); + outPath->rLineTo(0.0, -0.5); + outPath->close(); + outPath->moveTo(0.0, 0.0); + } +}; + +const static TestData testDataSet[] = {testData1, testData2, testData3, testData4}; + +TEST(PathPaser, parseString) { + + for (int i = 0; i < 4; i++) { + // Test generated path data against the given data. + PathData pathData; + TestData testData = testDataSet[i]; + size_t length = strlen(testData.pathString); + PathParser::getPathDataFromString(&pathData, testData.pathString, length); + PathParser::dump(pathData); + EXPECT_EQ(testData.pathData, pathData); + + // Test SkPath generated + SkPath expectedPath; + testData.skPathLamda(&expectedPath); + SkPath actualPath; + VectorDrawablePath::verbsToPath(&actualPath, &pathData); + EXPECT_EQ(expectedPath, actualPath); + } + +} + +}; // namespace uirenderer +}; // namespace android diff --git a/libs/hwui/utils/FatVector.h b/libs/hwui/utils/FatVector.h index 315c24978a1f..93d37c28f8a4 100644 --- a/libs/hwui/utils/FatVector.h +++ b/libs/hwui/utils/FatVector.h @@ -29,6 +29,7 @@ #include "utils/Macros.h" #include <stddef.h> +#include <stdlib.h> #include <type_traits> #include <utils/Log.h> @@ -95,6 +96,7 @@ public: FatVector(size_t capacity) : FatVector() { this->resize(capacity); } + private: typename InlineStdAllocator<T, SIZE>::Allocation mAllocation; }; |