diff options
Diffstat (limited to 'libs/hwui/PathTessellator.cpp')
| -rw-r--r-- | libs/hwui/PathTessellator.cpp | 122 |
1 files changed, 90 insertions, 32 deletions
diff --git a/libs/hwui/PathTessellator.cpp b/libs/hwui/PathTessellator.cpp index c1f61d6..38f214a 100644 --- a/libs/hwui/PathTessellator.cpp +++ b/libs/hwui/PathTessellator.cpp @@ -37,6 +37,7 @@ #include <SkPath.h> #include <SkPaint.h> +#include <SkPoint.h> #include <SkGeometry.h> // WARNING: Internal Skia Header #include <stdlib.h> @@ -55,7 +56,7 @@ namespace android { namespace uirenderer { -#define OUTLINE_REFINE_THRESHOLD_SQUARED (0.5f * 0.5f) +#define OUTLINE_REFINE_THRESHOLD 0.5f #define ROUND_CAP_THRESH 0.25f #define PI 3.1415926535897932f #define MAX_DEPTH 15 @@ -151,13 +152,11 @@ public: */ inline int capExtraDivisions() const { if (cap == SkPaint::kRound_Cap) { + // always use 2 points for hairline if (halfStrokeWidth == 0.0f) return 2; - // ROUND_CAP_THRESH is the maximum error for polygonal approximation of the round cap - const float errConst = (-ROUND_CAP_THRESH / halfStrokeWidth + 1); - const float targetCosVal = 2 * errConst * errConst - 1; - int neededDivisions = (int)(ceilf(PI / acos(targetCosVal)/2)) * 2; - return neededDivisions; + float threshold = MathUtils::min(inverseScaleX, inverseScaleY) * ROUND_CAP_THRESH; + return MathUtils::divisionsNeededToApproximateArc(halfStrokeWidth, PI, threshold); } return 0; } @@ -740,9 +739,10 @@ void PathTessellator::tessellatePath(const SkPath &path, const SkPaint* paint, // force close if we're filling the path, since fill path expects closed perimeter. bool forceClose = paintInfo.style != SkPaint::kStroke_Style; + PathApproximationInfo approximationInfo(threshInvScaleX, threshInvScaleY, + OUTLINE_REFINE_THRESHOLD); bool wasClosed = approximatePathOutlineVertices(path, forceClose, - threshInvScaleX * threshInvScaleX, threshInvScaleY * threshInvScaleY, - OUTLINE_REFINE_THRESHOLD_SQUARED, tempVertices); + approximationInfo, tempVertices); if (!tempVertices.size()) { // path was empty, return without allocating vertex buffer @@ -820,10 +820,9 @@ void PathTessellator::tessellatePoints(const float* points, int count, const SkP // calculate outline Vector<Vertex> outlineVertices; - approximatePathOutlineVertices(path, true, - paintInfo.inverseScaleX * paintInfo.inverseScaleX, - paintInfo.inverseScaleY * paintInfo.inverseScaleY, - OUTLINE_REFINE_THRESHOLD_SQUARED, outlineVertices); + PathApproximationInfo approximationInfo(paintInfo.inverseScaleX, paintInfo.inverseScaleY, + OUTLINE_REFINE_THRESHOLD); + approximatePathOutlineVertices(path, true, approximationInfo, outlineVertices); if (!outlineVertices.size()) return; @@ -900,9 +899,10 @@ void PathTessellator::tessellateLines(const float* points, int count, const SkPa // Simple path line approximation /////////////////////////////////////////////////////////////////////////////// -bool PathTessellator::approximatePathOutlineVertices(const SkPath& path, float thresholdSquared, +bool PathTessellator::approximatePathOutlineVertices(const SkPath& path, float threshold, Vector<Vertex>& outputVertices) { - return approximatePathOutlineVertices(path, true, 1.0f, 1.0f, thresholdSquared, outputVertices); + PathApproximationInfo approximationInfo(1.0f, 1.0f, threshold); + return approximatePathOutlineVertices(path, true, approximationInfo, outputVertices); } void pushToVector(Vector<Vertex>& vertices, float x, float y) { @@ -912,9 +912,42 @@ void pushToVector(Vector<Vertex>& vertices, float x, float y) { Vertex::set(newVertex, x, y); } +class ClockwiseEnforcer { +public: + void addPoint(const SkPoint& point) { + double x = point.x(); + double y = point.y(); + + if (initialized) { + sum += (x + lastX) * (y - lastY); + } else { + initialized = true; + } + + lastX = x; + lastY = y; + } + void reverseVectorIfNotClockwise(Vector<Vertex>& vertices) { + if (sum < 0) { + // negative sum implies CounterClockwise + const int size = vertices.size(); + for (int i = 0; i < size / 2; i++) { + Vertex tmp = vertices[i]; + int k = size - 1 - i; + vertices.replaceAt(vertices[k], i); + vertices.replaceAt(tmp, k); + } + } + } +private: + bool initialized = false; + double lastX = 0; + double lastY = 0; + double sum = 0; +}; + bool PathTessellator::approximatePathOutlineVertices(const SkPath& path, bool forceClose, - float sqrInvScaleX, float sqrInvScaleY, float thresholdSquared, - Vector<Vertex>& outputVertices) { + const PathApproximationInfo& approximationInfo, Vector<Vertex>& outputVertices) { ATRACE_CALL(); // TODO: to support joins other than sharp miter, join vertices should be labelled in the @@ -922,18 +955,22 @@ bool PathTessellator::approximatePathOutlineVertices(const SkPath& path, bool fo SkPath::Iter iter(path, forceClose); SkPoint pts[4]; SkPath::Verb v; + ClockwiseEnforcer clockwiseEnforcer; while (SkPath::kDone_Verb != (v = iter.next(pts))) { switch (v) { case SkPath::kMove_Verb: pushToVector(outputVertices, pts[0].x(), pts[0].y()); ALOGV("Move to pos %f %f", pts[0].x(), pts[0].y()); + clockwiseEnforcer.addPoint(pts[0]); break; case SkPath::kClose_Verb: ALOGV("Close at pos %f %f", pts[0].x(), pts[0].y()); + clockwiseEnforcer.addPoint(pts[0]); break; case SkPath::kLine_Verb: ALOGV("kLine_Verb %f %f -> %f %f", pts[0].x(), pts[0].y(), pts[1].x(), pts[1].y()); pushToVector(outputVertices, pts[1].x(), pts[1].y()); + clockwiseEnforcer.addPoint(pts[1]); break; case SkPath::kQuad_Verb: ALOGV("kQuad_Verb"); @@ -941,7 +978,9 @@ bool PathTessellator::approximatePathOutlineVertices(const SkPath& path, bool fo pts[0].x(), pts[0].y(), pts[2].x(), pts[2].y(), pts[1].x(), pts[1].y(), - sqrInvScaleX, sqrInvScaleY, thresholdSquared, outputVertices); + approximationInfo, outputVertices); + clockwiseEnforcer.addPoint(pts[1]); + clockwiseEnforcer.addPoint(pts[2]); break; case SkPath::kCubic_Verb: ALOGV("kCubic_Verb"); @@ -950,21 +989,26 @@ bool PathTessellator::approximatePathOutlineVertices(const SkPath& path, bool fo pts[1].x(), pts[1].y(), pts[3].x(), pts[3].y(), pts[2].x(), pts[2].y(), - sqrInvScaleX, sqrInvScaleY, thresholdSquared, outputVertices); + approximationInfo, outputVertices); + clockwiseEnforcer.addPoint(pts[1]); + clockwiseEnforcer.addPoint(pts[2]); + clockwiseEnforcer.addPoint(pts[3]); break; case SkPath::kConic_Verb: { ALOGV("kConic_Verb"); SkAutoConicToQuads converter; const SkPoint* quads = converter.computeQuads(pts, iter.conicWeight(), - thresholdSquared); + approximationInfo.thresholdForConicQuads); for (int i = 0; i < converter.countQuads(); ++i) { const int offset = 2 * i; recursiveQuadraticBezierVertices( quads[offset].x(), quads[offset].y(), quads[offset+2].x(), quads[offset+2].y(), quads[offset+1].x(), quads[offset+1].y(), - sqrInvScaleX, sqrInvScaleY, thresholdSquared, outputVertices); + approximationInfo, outputVertices); } + clockwiseEnforcer.addPoint(pts[1]); + clockwiseEnforcer.addPoint(pts[2]); break; } default: @@ -972,23 +1016,38 @@ bool PathTessellator::approximatePathOutlineVertices(const SkPath& path, bool fo } } + bool wasClosed = false; int size = outputVertices.size(); if (size >= 2 && outputVertices[0].x == outputVertices[size - 1].x && outputVertices[0].y == outputVertices[size - 1].y) { outputVertices.pop(); - return true; + wasClosed = true; } - return false; + + // ensure output vector is clockwise + clockwiseEnforcer.reverseVectorIfNotClockwise(outputVertices); + return wasClosed; } /////////////////////////////////////////////////////////////////////////////// // Bezier approximation +// +// All the inputs and outputs here are in path coordinates. +// We convert the error threshold from screen coordinates into path coordinates. /////////////////////////////////////////////////////////////////////////////// +// Get a threshold in path coordinates, by scaling the thresholdSquared from screen coordinates. +// TODO: Document the math behind this algorithm. +static inline float getThreshold(const PathApproximationInfo& info, float dx, float dy) { + // multiplying by sqrInvScaleY/X equivalent to multiplying in dimensional scale factors + float scale = (dx * dx * info.sqrInvScaleY + dy * dy * info.sqrInvScaleX); + return info.thresholdSquared * scale; +} + void PathTessellator::recursiveCubicBezierVertices( float p1x, float p1y, float c1x, float c1y, float p2x, float p2y, float c2x, float c2y, - float sqrInvScaleX, float sqrInvScaleY, float thresholdSquared, + const PathApproximationInfo& approximationInfo, Vector<Vertex>& outputVertices, int depth) { float dx = p2x - p1x; float dy = p2y - p1y; @@ -996,9 +1055,8 @@ void PathTessellator::recursiveCubicBezierVertices( float d2 = fabs((c2x - p2x) * dy - (c2y - p2y) * dx); float d = d1 + d2; - // multiplying by sqrInvScaleY/X equivalent to multiplying in dimensional scale factors if (depth >= MAX_DEPTH - || d * d <= thresholdSquared * (dx * dx * sqrInvScaleY + dy * dy * sqrInvScaleX)) { + || d * d <= getThreshold(approximationInfo, dx, dy)) { // below thresh, draw line by adding endpoint pushToVector(outputVertices, p2x, p2y); } else { @@ -1022,11 +1080,11 @@ void PathTessellator::recursiveCubicBezierVertices( recursiveCubicBezierVertices( p1x, p1y, p1c1x, p1c1y, mx, my, p1c1c2x, p1c1c2y, - sqrInvScaleX, sqrInvScaleY, thresholdSquared, outputVertices, depth + 1); + approximationInfo, outputVertices, depth + 1); recursiveCubicBezierVertices( mx, my, p2c1c2x, p2c1c2y, p2x, p2y, p2c2x, p2c2y, - sqrInvScaleX, sqrInvScaleY, thresholdSquared, outputVertices, depth + 1); + approximationInfo, outputVertices, depth + 1); } } @@ -1034,15 +1092,15 @@ void PathTessellator::recursiveQuadraticBezierVertices( float ax, float ay, float bx, float by, float cx, float cy, - float sqrInvScaleX, float sqrInvScaleY, float thresholdSquared, + const PathApproximationInfo& approximationInfo, Vector<Vertex>& outputVertices, int depth) { float dx = bx - ax; float dy = by - ay; + // d is the cross product of vector (B-A) and (C-B). float d = (cx - bx) * dy - (cy - by) * dx; - // multiplying by sqrInvScaleY/X equivalent to multiplying in dimensional scale factors if (depth >= MAX_DEPTH - || d * d <= thresholdSquared * (dx * dx * sqrInvScaleY + dy * dy * sqrInvScaleX)) { + || d * d <= getThreshold(approximationInfo, dx, dy)) { // below thresh, draw line by adding endpoint pushToVector(outputVertices, bx, by); } else { @@ -1056,9 +1114,9 @@ void PathTessellator::recursiveQuadraticBezierVertices( float my = (acy + bcy) * 0.5f; recursiveQuadraticBezierVertices(ax, ay, mx, my, acx, acy, - sqrInvScaleX, sqrInvScaleY, thresholdSquared, outputVertices, depth + 1); + approximationInfo, outputVertices, depth + 1); recursiveQuadraticBezierVertices(mx, my, bx, by, bcx, bcy, - sqrInvScaleX, sqrInvScaleY, thresholdSquared, outputVertices, depth + 1); + approximationInfo, outputVertices, depth + 1); } } |
