diff options
-rw-r--r-- | core/java/android/animation/AnimatorInflater.java | 83 | ||||
-rw-r--r-- | core/java/android/util/PathParser.java | 695 | ||||
-rw-r--r-- | core/jni/android_util_PathParser.cpp | 75 | ||||
-rw-r--r-- | graphics/java/android/graphics/drawable/VectorDrawable.java | 34 | ||||
-rw-r--r-- | libs/hwui/Android.mk | 3 | ||||
-rw-r--r-- | libs/hwui/PathParser.cpp | 2 | ||||
-rw-r--r-- | libs/hwui/PathParser.h | 3 | ||||
-rw-r--r-- | libs/hwui/VectorDrawablePath.cpp | 452 | ||||
-rw-r--r-- | libs/hwui/VectorDrawablePath.h | 9 | ||||
-rw-r--r-- | libs/hwui/microbench/PathParserBench.cpp | 24 | ||||
-rw-r--r-- | libs/hwui/unit_tests/VectorDrawableTests.cpp (renamed from libs/hwui/unit_tests/PathParserTests.cpp) | 61 | ||||
-rw-r--r-- | libs/hwui/utils/VectorDrawableUtils.cpp | 491 | ||||
-rw-r--r-- | libs/hwui/utils/VectorDrawableUtils.h | 40 |
13 files changed, 797 insertions, 1175 deletions
diff --git a/core/java/android/animation/AnimatorInflater.java b/core/java/android/animation/AnimatorInflater.java index d8d27376165c..20d71a62835c 100644 --- a/core/java/android/animation/AnimatorInflater.java +++ b/core/java/android/animation/AnimatorInflater.java @@ -250,50 +250,19 @@ public class AnimatorInflater { /** * PathDataEvaluator is used to interpolate between two paths which are * represented in the same format but different control points' values. - * The path is represented as an array of PathDataNode here, which is - * fundamentally an array of floating point numbers. + * The path is represented as verbs and points for each of the verbs. */ - private static class PathDataEvaluator implements TypeEvaluator<PathParser.PathDataNode[]> { - private PathParser.PathDataNode[] mNodeArray; - - /** - * Create a PathParser.PathDataNode[] that does not reuse the animated value. - * Care must be taken when using this option because on every evaluation - * a new <code>PathParser.PathDataNode[]</code> will be allocated. - */ - private PathDataEvaluator() {} - - /** - * Create a PathDataEvaluator that reuses <code>nodeArray</code> for every evaluate() call. - * Caution must be taken to ensure that the value returned from - * {@link android.animation.ValueAnimator#getAnimatedValue()} is not cached, modified, or - * used across threads. The value will be modified on each <code>evaluate()</code> call. - * - * @param nodeArray The array to modify and return from <code>evaluate</code>. - */ - public PathDataEvaluator(PathParser.PathDataNode[] nodeArray) { - mNodeArray = nodeArray; - } + private static class PathDataEvaluator implements TypeEvaluator<PathParser.PathData> { + private final PathParser.PathData mPathData = new PathParser.PathData(); @Override - public PathParser.PathDataNode[] evaluate(float fraction, - PathParser.PathDataNode[] startPathData, - PathParser.PathDataNode[] endPathData) { - if (!PathParser.canMorph(startPathData, endPathData)) { + public PathParser.PathData evaluate(float fraction, PathParser.PathData startPathData, + PathParser.PathData endPathData) { + if (!PathParser.interpolatePathData(mPathData, startPathData, endPathData, fraction)) { throw new IllegalArgumentException("Can't interpolate between" + " two incompatible pathData"); } - - if (mNodeArray == null || !PathParser.canMorph(mNodeArray, startPathData)) { - mNodeArray = PathParser.deepCopyNodes(startPathData); - } - - for (int i = 0; i < startPathData.length; i++) { - mNodeArray[i].interpolatePathDataNode(startPathData[i], - endPathData[i], fraction); - } - - return mNodeArray; + return mPathData; } } @@ -323,13 +292,14 @@ public class AnimatorInflater { if (valueType == VALUE_TYPE_PATH) { String fromString = styledAttributes.getString(valueFromId); String toString = styledAttributes.getString(valueToId); - PathParser.PathDataNode[] nodesFrom = PathParser.createNodesFromPathData(fromString); - PathParser.PathDataNode[] nodesTo = PathParser.createNodesFromPathData(toString); + PathParser.PathData nodesFrom = fromString == null + ? null : new PathParser.PathData(fromString); + PathParser.PathData nodesTo = toString == null + ? null : new PathParser.PathData(toString); if (nodesFrom != null || nodesTo != null) { if (nodesFrom != null) { - TypeEvaluator evaluator = - new PathDataEvaluator(PathParser.deepCopyNodes(nodesFrom)); + TypeEvaluator evaluator = new PathDataEvaluator(); if (nodesTo != null) { if (!PathParser.canMorph(nodesFrom, nodesTo)) { throw new InflateException(" Can't morph from " + fromString + " to " + @@ -342,8 +312,7 @@ public class AnimatorInflater { (Object) nodesFrom); } } else if (nodesTo != null) { - TypeEvaluator evaluator = - new PathDataEvaluator(PathParser.deepCopyNodes(nodesTo)); + TypeEvaluator evaluator = new PathDataEvaluator(); returnValue = PropertyValuesHolder.ofObject(propertyName, evaluator, (Object) nodesTo); } @@ -484,23 +453,25 @@ public class AnimatorInflater { TypeEvaluator evaluator = null; String fromString = arrayAnimator.getString(R.styleable.Animator_valueFrom); String toString = arrayAnimator.getString(R.styleable.Animator_valueTo); - PathParser.PathDataNode[] nodesFrom = PathParser.createNodesFromPathData(fromString); - PathParser.PathDataNode[] nodesTo = PathParser.createNodesFromPathData(toString); - - if (nodesFrom != null) { - if (nodesTo != null) { - anim.setObjectValues(nodesFrom, nodesTo); - if (!PathParser.canMorph(nodesFrom, nodesTo)) { + PathParser.PathData pathDataFrom = fromString == null + ? null : new PathParser.PathData(fromString); + PathParser.PathData pathDataTo = toString == null + ? null : new PathParser.PathData(toString); + + if (pathDataFrom != null) { + if (pathDataTo != null) { + anim.setObjectValues(pathDataFrom, pathDataTo); + if (!PathParser.canMorph(pathDataFrom, pathDataTo)) { throw new InflateException(arrayAnimator.getPositionDescription() + " Can't morph from " + fromString + " to " + toString); } } else { - anim.setObjectValues((Object)nodesFrom); + anim.setObjectValues((Object)pathDataFrom); } - evaluator = new PathDataEvaluator(PathParser.deepCopyNodes(nodesFrom)); - } else if (nodesTo != null) { - anim.setObjectValues((Object)nodesTo); - evaluator = new PathDataEvaluator(PathParser.deepCopyNodes(nodesTo)); + evaluator = new PathDataEvaluator(); + } else if (pathDataTo != null) { + anim.setObjectValues((Object)pathDataTo); + evaluator = new PathDataEvaluator(); } if (DBG_ANIMATOR_INFLATER && evaluator != null) { diff --git a/core/java/android/util/PathParser.java b/core/java/android/util/PathParser.java index f09947904478..f17a16c066f0 100644 --- a/core/java/android/util/PathParser.java +++ b/core/java/android/util/PathParser.java @@ -15,10 +15,6 @@ package android.util; import android.graphics.Path; -import android.util.Log; - -import java.util.ArrayList; -import java.util.Arrays; /** * @hide @@ -45,663 +41,94 @@ public class PathParser { } /** - * @param pathData The string representing a path, the same as "d" string in svg file. - * @return an array of the PathDataNode. - */ - public static PathDataNode[] createNodesFromPathData(String pathData) { - if (pathData == null) { - return null; - } - int start = 0; - int end = 1; - - ArrayList<PathDataNode> list = new ArrayList<PathDataNode>(); - while (end < pathData.length()) { - end = nextStart(pathData, end); - String s = pathData.substring(start, end).trim(); - if (s.length() > 0) { - float[] val = getFloats(s); - addNode(list, s.charAt(0), val); - } - - start = end; - end++; - } - if ((end - start) == 1 && start < pathData.length()) { - addNode(list, pathData.charAt(start), new float[0]); - } - return list.toArray(new PathDataNode[list.size()]); - } - - /** - * @param source The array of PathDataNode to be duplicated. - * @return a deep copy of the <code>source</code>. - */ - public static PathDataNode[] deepCopyNodes(PathDataNode[] source) { - if (source == null) { - return null; - } - PathDataNode[] copy = new PathParser.PathDataNode[source.length]; - for (int i = 0; i < source.length; i ++) { - copy[i] = new PathDataNode(source[i]); - } - return copy; - } - - /** - * @param nodesFrom The source path represented in an array of PathDataNode - * @param nodesTo The target path represented in an array of PathDataNode - * @return whether the <code>nodesFrom</code> can morph into <code>nodesTo</code> - */ - public static boolean canMorph(PathDataNode[] nodesFrom, PathDataNode[] nodesTo) { - if (nodesFrom == null || nodesTo == null) { - return false; - } - - if (nodesFrom.length != nodesTo.length) { - return false; - } - - for (int i = 0; i < nodesFrom.length; i ++) { - if (nodesFrom[i].mType != nodesTo[i].mType - || nodesFrom[i].mParams.length != nodesTo[i].mParams.length) { - return false; - } - } - return true; - } - - /** - * Update the target's data to match the source. - * Before calling this, make sure canMorph(target, source) is true. + * Interpret PathData as path commands and insert the commands to the given path. * - * @param target The target path represented in an array of PathDataNode - * @param source The source path represented in an array of PathDataNode + * @param data The source PathData to be converted. + * @param outPath The Path object where path commands will be inserted. */ - public static void updateNodes(PathDataNode[] target, PathDataNode[] source) { - for (int i = 0; i < source.length; i ++) { - target[i].mType = source[i].mType; - for (int j = 0; j < source[i].mParams.length; j ++) { - target[i].mParams[j] = source[i].mParams[j]; - } - } - } - - private static int nextStart(String s, int end) { - char c; - - while (end < s.length()) { - c = s.charAt(end); - // 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 end; - } - end++; - } - return end; - } - - private static void addNode(ArrayList<PathDataNode> list, char cmd, float[] val) { - list.add(new PathDataNode(cmd, val)); - } - - private static class ExtractFloatResult { - // We need to return the position of the next separator and whether the - // next float starts with a '-' or a '.'. - int mEndPosition; - boolean mEndWithNegOrDot; + public static void createPathFromPathData(Path outPath, PathData data) { + nCreatePathFromPathData(outPath.mNativePath, data.mNativePathData); } /** - * 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 - */ - private static float[] getFloats(String s) { - if (s.charAt(0) == 'z' || s.charAt(0) == 'Z') { - return new float[0]; - } - try { - float[] results = new float[s.length()]; - int count = 0; - int startPosition = 1; - int endPosition = 0; - - ExtractFloatResult result = new ExtractFloatResult(); - int totalLength = s.length(); - - // The startPosition should always be the first character of the - // current number, and endPosition is the character after the current - // number. - while (startPosition < totalLength) { - extract(s, startPosition, result); - endPosition = result.mEndPosition; - - if (startPosition < endPosition) { - results[count++] = Float.parseFloat( - s.substring(startPosition, endPosition)); - } - - if (result.mEndWithNegOrDot) { - // Keep the '-' or '.' sign with next number. - startPosition = endPosition; - } else { - startPosition = endPosition + 1; - } - } - return Arrays.copyOf(results, count); - } catch (NumberFormatException e) { - throw new RuntimeException("error in parsing \"" + s + "\"", e); - } - } - - /** - * 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 '-'. + * @param pathDataFrom The source path represented in PathData + * @param pathDataTo The target path represented in PathData + * @return whether the <code>nodesFrom</code> can morph into <code>nodesTo</code> */ - private static void extract(String s, int start, ExtractFloatResult result) { - // Now looking for ' ', ',', '.' or '-' from the start. - int currentIndex = start; - boolean foundSeparator = false; - result.mEndWithNegOrDot = false; - boolean secondDot = false; - boolean isExponential = false; - for (; currentIndex < s.length(); currentIndex++) { - boolean isPrevExponential = isExponential; - isExponential = false; - char currentChar = s.charAt(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; - result.mEndWithNegOrDot = true; - } - break; - case '.': - if (!secondDot) { - secondDot = true; - } else { - // This is the second dot, and it is considered as a separator. - foundSeparator = true; - result.mEndWithNegOrDot = true; - } - break; - case 'e': - case 'E': - isExponential = true; - break; - } - if (foundSeparator) { - break; - } - } - // When there is nothing found, then we put the end position to the end - // of the string. - result.mEndPosition = currentIndex; + public static boolean canMorph(PathData pathDataFrom, PathData pathDataTo) { + return nCanMorph(pathDataFrom.mNativePathData, pathDataTo.mNativePathData); } /** - * Each PathDataNode represents one command in the "d" attribute of the svg - * file. - * An array of PathDataNode can represent the whole "d" attribute. + * PathData class is a wrapper around the native PathData object, which contains + * the result of parsing a path string. Specifically, there are verbs and points + * associated with each verb stored in PathData. This data can then be used to + * generate commands to manipulate a Path. */ - public static class PathDataNode { - private char mType; - private float[] mParams; - - private PathDataNode(char type, float[] params) { - mType = type; - mParams = params; + public static class PathData { + long mNativePathData = 0; + public PathData() { + mNativePathData = nCreateEmptyPathData(); } - private PathDataNode(PathDataNode n) { - mType = n.mType; - mParams = Arrays.copyOf(n.mParams, n.mParams.length); + public PathData(PathData data) { + mNativePathData = nCreatePathData(data.mNativePathData); } - /** - * Convert an array of PathDataNode to Path. - * - * @param node The source array of PathDataNode. - * @param path The target Path object. - */ - public static void nodesToPath(PathDataNode[] node, Path path) { - float[] current = new float[6]; - char previousCommand = 'm'; - for (int i = 0; i < node.length; i++) { - addCommand(path, current, previousCommand, node[i].mType, node[i].mParams); - previousCommand = node[i].mType; + public PathData(String pathString) { + mNativePathData = nCreatePathDataFromString(pathString, pathString.length()); + if (mNativePathData == 0) { + throw new IllegalArgumentException("Invalid pathData: " + pathString); } } /** - * The current PathDataNode will be interpolated between the - * <code>nodeFrom</code> and <code>nodeTo</code> according to the - * <code>fraction</code>. + * Update the path data to match the source. + * Before calling this, make sure canMorph(target, source) is true. * - * @param nodeFrom The start value as a PathDataNode. - * @param nodeTo The end value as a PathDataNode - * @param fraction The fraction to interpolate. + * @param source The source path represented in PathData */ - public void interpolatePathDataNode(PathDataNode nodeFrom, - PathDataNode nodeTo, float fraction) { - for (int i = 0; i < nodeFrom.mParams.length; i++) { - mParams[i] = nodeFrom.mParams[i] * (1 - fraction) - + nodeTo.mParams[i] * fraction; - } + public void setPathData(PathData source) { + nSetPathData(mNativePathData, source.mNativePathData); } - private static void addCommand(Path path, float[] current, - char previousCmd, char cmd, float[] val) { - - int incr = 2; - float currentX = current[0]; - float currentY = current[1]; - float ctrlPointX = current[2]; - float ctrlPointY = current[3]; - float currentSegmentStartX = current[4]; - float currentSegmentStartY = current[5]; - float reflectiveCtrlPointX; - float reflectiveCtrlPointY; - - switch (cmd) { - case 'z': - case 'Z': - path.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; - path.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 (int k = 0; k < val.length; k += incr) { - switch (cmd) { - case 'm': // moveto - Start a new sub-path (relative) - currentX += val[k + 0]; - currentY += val[k + 1]; - if (k > 0) { - // According to the spec, if a moveto is followed by multiple - // pairs of coordinates, the subsequent pairs are treated as - // implicit lineto commands. - path.rLineTo(val[k + 0], val[k + 1]); - } else { - path.rMoveTo(val[k + 0], val[k + 1]); - currentSegmentStartX = currentX; - currentSegmentStartY = currentY; - } - break; - case 'M': // moveto - Start a new sub-path - currentX = val[k + 0]; - currentY = val[k + 1]; - if (k > 0) { - // According to the spec, if a moveto is followed by multiple - // pairs of coordinates, the subsequent pairs are treated as - // implicit lineto commands. - path.lineTo(val[k + 0], val[k + 1]); - } else { - path.moveTo(val[k + 0], val[k + 1]); - currentSegmentStartX = currentX; - currentSegmentStartY = currentY; - } - break; - case 'l': // lineto - Draw a line from the current point (relative) - path.rLineTo(val[k + 0], val[k + 1]); - currentX += val[k + 0]; - currentY += val[k + 1]; - break; - case 'L': // lineto - Draw a line from the current point - path.lineTo(val[k + 0], val[k + 1]); - currentX = val[k + 0]; - currentY = val[k + 1]; - break; - case 'h': // horizontal lineto - Draws a horizontal line (relative) - path.rLineTo(val[k + 0], 0); - currentX += val[k + 0]; - break; - case 'H': // horizontal lineto - Draws a horizontal line - path.lineTo(val[k + 0], currentY); - currentX = val[k + 0]; - break; - case 'v': // vertical lineto - Draws a vertical line from the current point (r) - path.rLineTo(0, val[k + 0]); - currentY += val[k + 0]; - break; - case 'V': // vertical lineto - Draws a vertical line from the current point - path.lineTo(currentX, val[k + 0]); - currentY = val[k + 0]; - break; - case 'c': // curveto - Draws a cubic Bézier curve (relative) - path.rCubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3], - val[k + 4], val[k + 5]); - - ctrlPointX = currentX + val[k + 2]; - ctrlPointY = currentY + val[k + 3]; - currentX += val[k + 4]; - currentY += val[k + 5]; - - break; - case 'C': // curveto - Draws a cubic Bézier curve - path.cubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3], - val[k + 4], val[k + 5]); - currentX = val[k + 4]; - currentY = val[k + 5]; - ctrlPointX = val[k + 2]; - ctrlPointY = val[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; - } - path.rCubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY, - val[k + 0], val[k + 1], - val[k + 2], val[k + 3]); - - ctrlPointX = currentX + val[k + 0]; - ctrlPointY = currentY + val[k + 1]; - currentX += val[k + 2]; - currentY += val[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; - } - path.cubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY, - val[k + 0], val[k + 1], val[k + 2], val[k + 3]); - ctrlPointX = val[k + 0]; - ctrlPointY = val[k + 1]; - currentX = val[k + 2]; - currentY = val[k + 3]; - break; - case 'q': // Draws a quadratic Bézier (relative) - path.rQuadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]); - ctrlPointX = currentX + val[k + 0]; - ctrlPointY = currentY + val[k + 1]; - currentX += val[k + 2]; - currentY += val[k + 3]; - break; - case 'Q': // Draws a quadratic Bézier - path.quadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]); - ctrlPointX = val[k + 0]; - ctrlPointY = val[k + 1]; - currentX = val[k + 2]; - currentY = val[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; - } - path.rQuadTo(reflectiveCtrlPointX, reflectiveCtrlPointY, - val[k + 0], val[k + 1]); - ctrlPointX = currentX + reflectiveCtrlPointX; - ctrlPointY = currentY + reflectiveCtrlPointY; - currentX += val[k + 0]; - currentY += val[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; - } - path.quadTo(reflectiveCtrlPointX, reflectiveCtrlPointY, - val[k + 0], val[k + 1]); - ctrlPointX = reflectiveCtrlPointX; - ctrlPointY = reflectiveCtrlPointY; - currentX = val[k + 0]; - currentY = val[k + 1]; - break; - case 'a': // Draws an elliptical arc - // (rx ry x-axis-rotation large-arc-flag sweep-flag x y) - drawArc(path, - currentX, - currentY, - val[k + 5] + currentX, - val[k + 6] + currentY, - val[k + 0], - val[k + 1], - val[k + 2], - val[k + 3] != 0, - val[k + 4] != 0); - currentX += val[k + 5]; - currentY += val[k + 6]; - ctrlPointX = currentX; - ctrlPointY = currentY; - break; - case 'A': // Draws an elliptical arc - drawArc(path, - currentX, - currentY, - val[k + 5], - val[k + 6], - val[k + 0], - val[k + 1], - val[k + 2], - val[k + 3] != 0, - val[k + 4] != 0); - currentX = val[k + 5]; - currentY = val[k + 6]; - ctrlPointX = currentX; - ctrlPointY = currentY; - break; - } - previousCmd = cmd; + @Override + protected void finalize() throws Throwable { + if (mNativePathData != 0) { + nFinalize(mNativePathData); + mNativePathData = 0; } - current[0] = currentX; - current[1] = currentY; - current[2] = ctrlPointX; - current[3] = ctrlPointY; - current[4] = currentSegmentStartX; - current[5] = currentSegmentStartY; + super.finalize(); } + } - private static void drawArc(Path p, - float x0, - float y0, - float x1, - float y1, - float a, - float b, - float theta, - boolean isMoreThanHalf, - boolean isPositiveArc) { - - /* Convert rotation angle from degrees to radians */ - double thetaD = Math.toRadians(theta); - /* Pre-compute rotation matrix entries */ - double cosTheta = Math.cos(thetaD); - double sinTheta = Math.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) { - Log.w(LOGTAG, " Points are coincident"); - return; /* Points are coincident */ - } - double disc = 1.0 / dsq - 1.0 / 4.0; - if (disc < 0.0) { - Log.w(LOGTAG, "Points are too far apart " + dsq); - float adjust = (float) (Math.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 = Math.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 = Math.atan2((y0p - cy), (x0p - cx)); - - double eta1 = Math.atan2((y1p - cy), (x1p - cx)); - - double sweep = (eta1 - eta0); - if (isPositiveArc != (sweep >= 0)) { - if (sweep > 0) { - sweep -= 2 * Math.PI; - } else { - sweep += 2 * Math.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); - } - - /** - * 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 - */ - private static void arcToBezier(Path 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 = (int) Math.ceil(Math.abs(sweep * 4 / Math.PI)); - - double eta1 = start; - double cosTheta = Math.cos(theta); - double sinTheta = Math.sin(theta); - double cosEta1 = Math.cos(eta1); - double sinEta1 = Math.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 = Math.sin(eta2); - double cosEta2 = Math.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 = Math.tan((eta2 - eta1) / 2); - double alpha = - Math.sin(eta2 - eta1) * (Math.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; - } - } + /** + * Interpolate between the <code>fromData</code> and <code>toData</code> according to the + * <code>fraction</code>, and put the resulting path data into <code>outData</code>. + * + * @param outData The resulting PathData of the interpolation + * @param fromData The start value as a PathData. + * @param toData The end value as a PathData + * @param fraction The fraction to interpolate. + */ + public static boolean interpolatePathData(PathData outData, PathData fromData, PathData toData, + float fraction) { + return nInterpolatePathData(outData.mNativePathData, fromData.mNativePathData, + toData.mNativePathData, fraction); } + // Native functions are defined below. private static native boolean nParseStringForPath(long pathPtr, String pathString, int stringLength); + private static native void nCreatePathFromPathData(long outPathPtr, long pathData); + private static native long nCreateEmptyPathData(); + private static native long nCreatePathData(long nativePtr); + private static native long nCreatePathDataFromString(String pathString, int stringLength); + private static native boolean nInterpolatePathData(long outDataPtr, long fromDataPtr, + long toDataPtr, float fraction); + private static native void nFinalize(long nativePtr); + private static native boolean nCanMorph(long fromDataPtr, long toDataPtr); + private static native void nSetPathData(long outDataPtr, long fromDataPtr); } + + diff --git a/core/jni/android_util_PathParser.cpp b/core/jni/android_util_PathParser.cpp index 245aa0f9020e..0927120f1cf5 100644 --- a/core/jni/android_util_PathParser.cpp +++ b/core/jni/android_util_PathParser.cpp @@ -18,19 +18,22 @@ #include <PathParser.h> #include <SkPath.h> +#include <utils/VectorDrawableUtils.h> #include <android/log.h> #include "core_jni_helpers.h" namespace android { +using namespace uirenderer; + static bool parseStringForPath(JNIEnv* env, jobject, jlong skPathHandle, jstring inputPathStr, jint strLength) { const char* pathString = env->GetStringUTFChars(inputPathStr, NULL); SkPath* skPath = reinterpret_cast<SkPath*>(skPathHandle); - android::uirenderer::PathParser::ParseResult result; - android::uirenderer::PathParser::parseStringForSkPath(skPath, &result, pathString, strLength); + PathParser::ParseResult result; + PathParser::parseStringForSkPath(skPath, &result, pathString, strLength); env->ReleaseStringUTFChars(inputPathStr, pathString); if (result.failureOccurred) { ALOGE(result.failureMessage.c_str()); @@ -38,8 +41,74 @@ static bool parseStringForPath(JNIEnv* env, jobject, jlong skPathHandle, jstring return !result.failureOccurred; } +static long createEmptyPathData(JNIEnv*, jobject) { + PathData* pathData = new PathData(); + return reinterpret_cast<jlong>(pathData); +} + +static long createPathData(JNIEnv*, jobject, jlong pathDataPtr) { + PathData* pathData = reinterpret_cast<PathData*>(pathDataPtr); + PathData* newPathData = new PathData(*pathData); + return reinterpret_cast<jlong>(newPathData); +} + +static long createPathDataFromStringPath(JNIEnv* env, jobject, jstring inputStr, jint strLength) { + const char* pathString = env->GetStringUTFChars(inputStr, NULL); + PathData* pathData = new PathData(); + PathParser::ParseResult result; + PathParser::getPathDataFromString(pathData, &result, pathString, strLength); + env->ReleaseStringUTFChars(inputStr, pathString); + if (!result.failureOccurred) { + return reinterpret_cast<jlong>(pathData); + } else { + delete pathData; + ALOGE(result.failureMessage.c_str()); + return NULL; + } +} + +static bool interpolatePathData(JNIEnv*, jobject, jlong outPathDataPtr, jlong fromPathDataPtr, + jlong toPathDataPtr, jfloat fraction) { + PathData* outPathData = reinterpret_cast<PathData*>(outPathDataPtr); + PathData* fromPathData = reinterpret_cast<PathData*>(fromPathDataPtr); + PathData* toPathData = reinterpret_cast<PathData*>(toPathDataPtr); + return VectorDrawableUtils::interpolatePathData(outPathData, *fromPathData, + *toPathData, fraction); +} + +static void deletePathData(JNIEnv*, jobject, jlong pathDataHandle) { + PathData* pathData = reinterpret_cast<PathData*>(pathDataHandle); + delete pathData; +} + +static bool canMorphPathData(JNIEnv*, jobject, jlong fromPathDataPtr, jlong toPathDataPtr) { + PathData* fromPathData = reinterpret_cast<PathData*>(fromPathDataPtr); + PathData* toPathData = reinterpret_cast<PathData*>(toPathDataPtr); + return VectorDrawableUtils::canMorph(*fromPathData, *toPathData); +} + +static void setPathData(JNIEnv*, jobject, jlong outPathDataPtr, jlong fromPathDataPtr) { + PathData* fromPathData = reinterpret_cast<PathData*>(fromPathDataPtr); + PathData* outPathData = reinterpret_cast<PathData*>(outPathDataPtr); + *outPathData = *fromPathData; +} + +static void setSkPathFromPathData(JNIEnv*, jobject, jlong outPathPtr, jlong pathDataPtr) { + PathData* pathData = reinterpret_cast<PathData*>(pathDataPtr); + SkPath* skPath = reinterpret_cast<SkPath*>(outPathPtr); + VectorDrawableUtils::verbsToPath(skPath, *pathData); +} + static const JNINativeMethod gMethods[] = { - {"nParseStringForPath", "(JLjava/lang/String;I)Z", (void*)parseStringForPath} + {"nParseStringForPath", "(JLjava/lang/String;I)Z", (void*)parseStringForPath}, + {"nCreateEmptyPathData", "!()J", (void*)createEmptyPathData}, + {"nCreatePathData", "!(J)J", (void*)createPathData}, + {"nCreatePathDataFromString", "(Ljava/lang/String;I)J", (void*)createPathDataFromStringPath}, + {"nInterpolatePathData", "!(JJJF)Z", (void*)interpolatePathData}, + {"nFinalize", "!(J)V", (void*)deletePathData}, + {"nCanMorph", "!(JJ)Z", (void*)canMorphPathData}, + {"nSetPathData", "!(JJ)V", (void*)setPathData}, + {"nCreatePathFromPathData", "!(JJ)V", (void*)setSkPathFromPathData}, }; int register_android_util_PathParser(JNIEnv* env) { diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java index eee9b24437b7..f961a59f1d02 100644 --- a/graphics/java/android/graphics/drawable/VectorDrawable.java +++ b/graphics/java/android/graphics/drawable/VectorDrawable.java @@ -1321,7 +1321,7 @@ public class VectorDrawable extends Drawable { * Common Path information for clip path and normal path. */ private static abstract class VPath implements VObject { - protected PathParser.PathDataNode[] mNodes = null; + protected PathParser.PathData mPathData = null; String mPathName; int mChangingConfigurations; @@ -1332,7 +1332,7 @@ public class VectorDrawable extends Drawable { public VPath(VPath copy) { mPathName = copy.mPathName; mChangingConfigurations = copy.mChangingConfigurations; - mNodes = PathParser.deepCopyNodes(copy.mNodes); + mPathData = copy.mPathData == null ? null : new PathParser.PathData(copy.mPathData); } public String getPathName() { @@ -1345,18 +1345,14 @@ public class VectorDrawable extends Drawable { /* Setters and Getters, used by animator from AnimatedVectorDrawable. */ @SuppressWarnings("unused") - public PathParser.PathDataNode[] getPathData() { - return mNodes; + public PathParser.PathData getPathData() { + return mPathData; } + // TODO: Move the PathEvaluator and this setter and the getter above into native. @SuppressWarnings("unused") - public void setPathData(PathParser.PathDataNode[] nodes) { - if (!PathParser.canMorph(mNodes, nodes)) { - // This should not happen in the middle of animation. - mNodes = PathParser.deepCopyNodes(nodes); - } else { - PathParser.updateNodes(mNodes, nodes); - } + public void setPathData(PathParser.PathData pathData) { + mPathData.setPathData(pathData); } @Override @@ -1392,8 +1388,8 @@ public class VectorDrawable extends Drawable { * @param outPath the output path */ protected void toPath(TempState temp, Path outPath) { - if (mNodes != null) { - PathParser.PathDataNode.nodesToPath(mNodes, outPath); + if (mPathData != null) { + PathParser.createPathFromPathData(outPath, mPathData); } } @@ -1488,9 +1484,9 @@ public class VectorDrawable extends Drawable { mPathName = pathName; } - final String pathData = a.getString(R.styleable.VectorDrawableClipPath_pathData); - if (pathData != null) { - mNodes = PathParser.createNodesFromPathData(pathData); + final String pathDataString = a.getString(R.styleable.VectorDrawableClipPath_pathData); + if (pathDataString != null) { + mPathData = new PathParser.PathData(pathDataString); } } @@ -1719,9 +1715,9 @@ public class VectorDrawable extends Drawable { mPathName = pathName; } - final String pathData = a.getString(R.styleable.VectorDrawablePath_pathData); - if (pathData != null) { - mNodes = PathParser.createNodesFromPathData(pathData); + final String pathString = a.getString(R.styleable.VectorDrawablePath_pathData); + if (pathString != null) { + mPathData = new PathParser.PathData(pathString); } final ColorStateList fillColors = a.getColorStateList( diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk index 8565372f3b53..92226f5d1b7d 100644 --- a/libs/hwui/Android.mk +++ b/libs/hwui/Android.mk @@ -29,6 +29,7 @@ hwui_src_files := \ utils/NinePatchImpl.cpp \ utils/StringUtils.cpp \ utils/TestWindowContext.cpp \ + utils/VectorDrawableUtils.cpp \ AmbientShadow.cpp \ AnimationContext.cpp \ Animator.cpp \ @@ -218,7 +219,7 @@ LOCAL_SRC_FILES += \ unit_tests/FatVectorTests.cpp \ unit_tests/LayerUpdateQueueTests.cpp \ unit_tests/LinearAllocatorTests.cpp \ - unit_tests/PathParserTests.cpp \ + unit_tests/VectorDrawableTests.cpp \ unit_tests/OffscreenBufferPoolTests.cpp \ unit_tests/StringUtilsTests.cpp diff --git a/libs/hwui/PathParser.cpp b/libs/hwui/PathParser.cpp index 35230fff279e..4e9ac9c7f723 100644 --- a/libs/hwui/PathParser.cpp +++ b/libs/hwui/PathParser.cpp @@ -221,7 +221,7 @@ void PathParser::parseStringForSkPath(SkPath* skPath, ParseResult* result, const result->failureMessage = "No verbs found in the string for pathData"; return; } - VectorDrawablePath::verbsToPath(skPath, &pathData); + VectorDrawableUtils::verbsToPath(skPath, pathData); return; } diff --git a/libs/hwui/PathParser.h b/libs/hwui/PathParser.h index a9c1e60dae2e..4c87b1898ae5 100644 --- a/libs/hwui/PathParser.h +++ b/libs/hwui/PathParser.h @@ -18,6 +18,7 @@ #define ANDROID_HWUI_PATHPARSER_H #include "VectorDrawablePath.h" +#include "utils/VectorDrawableUtils.h" #include <jni.h> #include <android/log.h> @@ -40,7 +41,7 @@ public: */ ANDROID_API static void parseStringForSkPath(SkPath* outPath, ParseResult* result, const char* pathStr, size_t strLength); - static void getPathDataFromString(PathData* outData, ParseResult* result, + ANDROID_API static void getPathDataFromString(PathData* outData, ParseResult* result, const char* pathStr, size_t strLength); static void dump(const PathData& data); }; diff --git a/libs/hwui/VectorDrawablePath.cpp b/libs/hwui/VectorDrawablePath.cpp index 05ea2da88fdb..c9a54ca870fa 100644 --- a/libs/hwui/VectorDrawablePath.cpp +++ b/libs/hwui/VectorDrawablePath.cpp @@ -17,6 +17,7 @@ #include "VectorDrawablePath.h" #include "PathParser.h" +#include "utils/VectorDrawableUtils.h" #include <math.h> #include <utils/Log.h> @@ -24,476 +25,35 @@ 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::ParseResult result; PathParser::getPathDataFromString(&mData, &result, pathStr, strLength); if (!result.failureOccurred) { - verbsToPath(&mSkPath, &mData); + VectorDrawableUtils::verbsToPath(&mSkPath, mData); } } VectorDrawablePath::VectorDrawablePath(const PathData& data) { mData = data; // Now we need to construct a path - verbsToPath(&mSkPath, &data); + VectorDrawableUtils::verbsToPath(&mSkPath, data); } VectorDrawablePath::VectorDrawablePath(const VectorDrawablePath& path) { mData = path.mData; - verbsToPath(&mSkPath, &mData); + VectorDrawableUtils::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 PathData& morphTo) { + return VectorDrawableUtils::canMorph(mData, morphTo); } 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); - 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); -} - -// Use the given verb, and points in the range [start, end) to insert a command into the SkPath. -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 index 40ce986b1a57..2e56349b3aa4 100644 --- a/libs/hwui/VectorDrawablePath.h +++ b/libs/hwui/VectorDrawablePath.h @@ -17,15 +17,14 @@ #ifndef ANDROID_HWUI_VPATH_H #define ANDROID_HWUI_VPATH_H +#include <cutils/compiler.h> #include "SkPath.h" #include <vector> namespace android { namespace uirenderer { - - -struct PathData { +struct ANDROID_API PathData { // TODO: Try using FatVector instead of std::vector and do a micro benchmark on the performance // difference. std::vector<char> verbs; @@ -44,9 +43,7 @@ public: 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; diff --git a/libs/hwui/microbench/PathParserBench.cpp b/libs/hwui/microbench/PathParserBench.cpp index 171078db9ef6..3d9fafac6c93 100644 --- a/libs/hwui/microbench/PathParserBench.cpp +++ b/libs/hwui/microbench/PathParserBench.cpp @@ -17,21 +17,35 @@ #include <benchmark/Benchmark.h> #include "PathParser.h" +#include "VectorDrawablePath.h" #include <SkPath.h> using namespace android; using namespace android::uirenderer; -BENCHMARK_NO_ARG(BM_PathParser_parseStringPath); -void BM_PathParser_parseStringPath::Run(int iter) { - const char* pathString = "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"; +static const char* sPathString = "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"; + +BENCHMARK_NO_ARG(BM_PathParser_parseStringPathForSkPath); +void BM_PathParser_parseStringPathForSkPath::Run(int iter) { SkPath skPath; - size_t length = strlen(pathString); + size_t length = strlen(sPathString); + PathParser::ParseResult result; + StartBenchmarkTiming(); + for (int i = 0; i < iter; i++) { + PathParser::parseStringForSkPath(&skPath, &result, sPathString, length); + } + StopBenchmarkTiming(); +} + +BENCHMARK_NO_ARG(BM_PathParser_parseStringPathForPathData); +void BM_PathParser_parseStringPathForPathData::Run(int iter) { + size_t length = strlen(sPathString); + PathData outData; PathParser::ParseResult result; StartBenchmarkTiming(); for (int i = 0; i < iter; i++) { - PathParser::parseStringForSkPath(&skPath, &result, pathString, length); + PathParser::getPathDataFromString(&outData, &result, sPathString, length); } StopBenchmarkTiming(); } diff --git a/libs/hwui/unit_tests/PathParserTests.cpp b/libs/hwui/unit_tests/VectorDrawableTests.cpp index c99d7b0a0368..77dd73acff10 100644 --- a/libs/hwui/unit_tests/PathParserTests.cpp +++ b/libs/hwui/unit_tests/VectorDrawableTests.cpp @@ -17,7 +17,8 @@ #include <gtest/gtest.h> #include "PathParser.h" -#include "VectorDrawablePath.h" +#include "utils/MathUtils.h" +#include "utils/VectorDrawableUtils.h" #include <functional> @@ -177,6 +178,10 @@ const StringPath sStringPaths[] = { {"1-2e34567", false} }; +static bool hasSameVerbs(const PathData& from, const PathData& to) { + return from.verbs == to.verbs && from.verbSizes == to.verbSizes; +} + TEST(PathParser, parseStringForData) { for (TestData testData: sTestDataSet) { PathParser::ParseResult result; @@ -197,12 +202,12 @@ TEST(PathParser, parseStringForData) { } } -TEST(PathParser, createSkPathFromPathData) { +TEST(VectorDrawableUtils, createSkPathFromPathData) { for (TestData testData: sTestDataSet) { SkPath expectedPath; testData.skPathLamda(&expectedPath); SkPath actualPath; - VectorDrawablePath::verbsToPath(&actualPath, &testData.pathData); + VectorDrawableUtils::verbsToPath(&actualPath, testData.pathData); EXPECT_EQ(expectedPath, actualPath); } } @@ -230,5 +235,55 @@ TEST(PathParser, parseStringForSkPath) { } } +TEST(VectorDrawableUtils, morphPathData) { + for (TestData fromData: sTestDataSet) { + for (TestData toData: sTestDataSet) { + bool canMorph = VectorDrawableUtils::canMorph(fromData.pathData, toData.pathData); + if (fromData.pathData == toData.pathData) { + EXPECT_TRUE(canMorph); + } else { + bool expectedToMorph = hasSameVerbs(fromData.pathData, toData.pathData); + EXPECT_EQ(expectedToMorph, canMorph); + } + } + } +} + +TEST(VectorDrawableUtils, interpolatePathData) { + // Interpolate path data with itself and every other path data + for (TestData fromData: sTestDataSet) { + for (TestData toData: sTestDataSet) { + PathData outData; + bool success = VectorDrawableUtils::interpolatePathData(&outData, fromData.pathData, + toData.pathData, 0.5); + bool expectedToMorph = hasSameVerbs(fromData.pathData, toData.pathData); + EXPECT_EQ(expectedToMorph, success); + } + } + + float fractions[] = {0, 0.00001, 0.28, 0.5, 0.7777, 0.9999999, 1}; + // Now try to interpolate with a slightly modified version of self and expect success + for (TestData fromData : sTestDataSet) { + PathData toPathData = fromData.pathData; + for (size_t i = 0; i < toPathData.points.size(); i++) { + toPathData.points[i]++; + } + const PathData& fromPathData = fromData.pathData; + PathData outData; + // Interpolate the two path data with different fractions + for (float fraction : fractions) { + bool success = VectorDrawableUtils::interpolatePathData( + &outData, fromPathData, toPathData, fraction); + EXPECT_TRUE(success); + for (size_t i = 0; i < outData.points.size(); i++) { + float expectedResult = fromPathData.points[i] * (1.0 - fraction) + + toPathData.points[i] * fraction; + EXPECT_TRUE(MathUtils::areEqual(expectedResult, outData.points[i])); + } + } + } +} + + }; // namespace uirenderer }; // namespace android diff --git a/libs/hwui/utils/VectorDrawableUtils.cpp b/libs/hwui/utils/VectorDrawableUtils.cpp new file mode 100644 index 000000000000..ca75c5945b7f --- /dev/null +++ b/libs/hwui/utils/VectorDrawableUtils.cpp @@ -0,0 +1,491 @@ +/* + * 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 "VectorDrawableUtils.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); +}; + +bool VectorDrawableUtils::canMorph(const PathData& morphFrom, const PathData& morphTo) { + if (morphFrom.verbs.size() != morphTo.verbs.size()) { + return false; + } + + for (unsigned int i = 0; i < morphFrom.verbs.size(); i++) { + if (morphFrom.verbs[i] != morphTo.verbs[i] + || morphFrom.verbSizes[i] != morphTo.verbSizes[i]) { + return false; + } + } + return true; +} + +bool VectorDrawableUtils::interpolatePathData(PathData* outData, const PathData& morphFrom, + const PathData& morphTo, float fraction) { + if (!canMorph(morphFrom, morphTo)) { + return false; + } + interpolatePaths(outData, morphFrom, morphTo, fraction); + return true; +} + + /** + * Convert an array of PathVerb to Path. + */ +void VectorDrawableUtils::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); + 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 VectorDrawableUtils::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); +} + + + +// Use the given verb, and points in the range [start, end) to insert a command into the SkPath. +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; + default: + LOG_ALWAYS_FATAL("Unsupported command: %c", cmd); + break; + } + previousCmd = cmd; + } +} + +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/utils/VectorDrawableUtils.h b/libs/hwui/utils/VectorDrawableUtils.h new file mode 100644 index 000000000000..21c1cdcc37f3 --- /dev/null +++ b/libs/hwui/utils/VectorDrawableUtils.h @@ -0,0 +1,40 @@ +/* + * 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_VECTORDRAWABLE_UTILS_H +#define ANDROID_HWUI_VECTORDRAWABLE_UTILS_H + +#include "VectorDrawablePath.h" + +#include <cutils/compiler.h> +#include "SkPath.h" +#include <vector> + +namespace android { +namespace uirenderer { + +class VectorDrawableUtils { +public: + ANDROID_API static bool canMorph(const PathData& morphFrom, const PathData& morphTo); + ANDROID_API static bool interpolatePathData(PathData* outData, const PathData& morphFrom, + const PathData& morphTo, float fraction); + ANDROID_API static void verbsToPath(SkPath* outPath, const PathData& data); + static void interpolatePaths(PathData* outPathData, const PathData& from, const PathData& to, + float fraction); +}; +} // namespace uirenderer +} // namespace android +#endif /* ANDROID_HWUI_VECTORDRAWABLE_UTILS_H*/ |