diff options
Diffstat (limited to 'libs/hwui/PathRenderer.cpp')
-rw-r--r-- | libs/hwui/PathRenderer.cpp | 372 |
1 files changed, 263 insertions, 109 deletions
diff --git a/libs/hwui/PathRenderer.cpp b/libs/hwui/PathRenderer.cpp index d222009..6893f9d 100644 --- a/libs/hwui/PathRenderer.cpp +++ b/libs/hwui/PathRenderer.cpp @@ -21,6 +21,7 @@ #define VERTEX_DEBUG 0 #include <SkPath.h> +#include <SkPaint.h> #include <stdlib.h> #include <stdint.h> @@ -39,10 +40,16 @@ namespace uirenderer { #define THRESHOLD 0.5f -void PathRenderer::computeInverseScales(const mat4 *transform, - float &inverseScaleX, float& inverseScaleY) { - inverseScaleX = 1.0f; - inverseScaleY = 1.0f; +SkRect PathRenderer::computePathBounds(const SkPath& path, const SkPaint* paint) { + SkRect bounds = path.getBounds(); + if (paint->getStyle() != SkPaint::kFill_Style) { + float outset = paint->getStrokeWidth() * 0.5f; + bounds.outset(outset, outset); + } + return bounds; +} + +void computeInverseScales(const mat4 *transform, float &inverseScaleX, float& inverseScaleY) { if (CC_UNLIKELY(!transform->isPureTranslate())) { float m00 = transform->data[Matrix4::kScaleX]; float m01 = transform->data[Matrix4::kSkewY]; @@ -50,127 +57,275 @@ void PathRenderer::computeInverseScales(const mat4 *transform, float m11 = transform->data[Matrix4::kScaleY]; float scaleX = sqrt(m00 * m00 + m01 * m01); float scaleY = sqrt(m10 * m10 + m11 * m11); - inverseScaleX = (scaleX != 0) ? (inverseScaleX / scaleX) : 0; - inverseScaleY = (scaleY != 0) ? (inverseScaleY / scaleY) : 0; + inverseScaleX = (scaleX != 0) ? (1.0f / scaleX) : 0; + inverseScaleY = (scaleY != 0) ? (1.0f / scaleY) : 0; + } else { + inverseScaleX = 1.0f; + inverseScaleY = 1.0f; } } -void PathRenderer::convexPathFillVertices(const SkPath &path, const mat4 *transform, - VertexBuffer &vertexBuffer, bool isAA) { - ATRACE_CALL(); - float inverseScaleX; - float inverseScaleY; - computeInverseScales(transform, inverseScaleX, inverseScaleY); +inline void copyVertex(Vertex* destPtr, const Vertex* srcPtr) +{ + Vertex::set(destPtr, srcPtr->position[0], srcPtr->position[1]); +} - Vector<Vertex> tempVertices; - float thresholdx = THRESHOLD * inverseScaleX; - float thresholdy = THRESHOLD * inverseScaleY; - convexPathVertices(path, - thresholdx * thresholdx, - thresholdy * thresholdy, - tempVertices); +inline void copyAlphaVertex(AlphaVertex* destPtr, const AlphaVertex* srcPtr) +{ + AlphaVertex::set(destPtr, srcPtr->position[0], srcPtr->position[1], srcPtr->alpha); +} -#if VERTEX_DEBUG - for (unsigned int i = 0; i < tempVertices.size(); i++) { - ALOGD("orig path: point at %f %f", - tempVertices[i].position[0], - tempVertices[i].position[1]); +void getFillVerticesFromPerimeter(const Vector<Vertex>& perimeter, VertexBuffer& vertexBuffer) { + Vertex* buffer = vertexBuffer.alloc<Vertex>(perimeter.size()); + + int currentIndex = 0; + // zig zag between all previous points on the inside of the hull to create a + // triangle strip that fills the hull + int srcAindex = 0; + int srcBindex = perimeter.size() - 1; + while (srcAindex <= srcBindex) { + copyVertex(&buffer[currentIndex++], &perimeter[srcAindex]); + if (srcAindex == srcBindex) break; + copyVertex(&buffer[currentIndex++], &perimeter[srcBindex]); + srcAindex++; + srcBindex--; } -#endif +} + +void getStrokeVerticesFromPerimeter(const Vector<Vertex>& perimeter, float halfStrokeWidth, + VertexBuffer& vertexBuffer, float inverseScaleX, float inverseScaleY) { + Vertex* buffer = vertexBuffer.alloc<Vertex>(perimeter.size() * 2 + 2); + int currentIndex = 0; - if (!isAA) { - Vertex* buffer = vertexBuffer.alloc<Vertex>(tempVertices.size()); - - // zig zag between all previous points on the inside of the hull to create a - // triangle strip that fills the hull - int srcAindex = 0; - int srcBindex = tempVertices.size() - 1; - while (srcAindex <= srcBindex) { - Vertex::set(&buffer[currentIndex++], - tempVertices.editArray()[srcAindex].position[0], - tempVertices.editArray()[srcAindex].position[1]); - if (srcAindex == srcBindex) break; - Vertex::set(&buffer[currentIndex++], - tempVertices.editArray()[srcBindex].position[0], - tempVertices.editArray()[srcBindex].position[1]); - srcAindex++; - srcBindex--; + const Vertex* last = &(perimeter[perimeter.size() - 1]); + const Vertex* current = &(perimeter[0]); + vec2 lastNormal(current->position[1] - last->position[1], + last->position[0] - current->position[0]); + lastNormal.normalize(); + for (unsigned int i = 0; i < perimeter.size(); i++) { + const Vertex* next = &(perimeter[i + 1 >= perimeter.size() ? 0 : i + 1]); + vec2 nextNormal(next->position[1] - current->position[1], + current->position[0] - next->position[0]); + nextNormal.normalize(); + + // offset each point by its normal, out and in, by appropriate stroke offset + vec2 totalOffset = (lastNormal + nextNormal); + totalOffset.normalize(); + if (halfStrokeWidth == 0.0f) { + // hairline - compensate for scale + totalOffset.x *= 0.5f * inverseScaleX; + totalOffset.y *= 0.5f * inverseScaleY; + } else { + totalOffset *= halfStrokeWidth; } - return; + + Vertex::set(&buffer[currentIndex++], + current->position[0] + totalOffset.x, + current->position[1] + totalOffset.y); + + Vertex::set(&buffer[currentIndex++], + current->position[0] - totalOffset.x, + current->position[1] - totalOffset.y); + + last = current; + current = next; + lastNormal = nextNormal; } - AlphaVertex* buffer = vertexBuffer.alloc<AlphaVertex>(tempVertices.size() * 3 + 2); - // generate alpha points - fill Alpha vertex gaps in between each point with - // alpha 0 vertex, offset by a scaled normal. - Vertex* last = &(tempVertices.editArray()[tempVertices.size()-1]); + // wrap around to beginning + copyVertex(&buffer[currentIndex++], &buffer[0]); + copyVertex(&buffer[currentIndex++], &buffer[1]); +} - for (unsigned int i = 0; i<tempVertices.size(); i++) { - Vertex* current = &(tempVertices.editArray()[i]); - Vertex* next = &(tempVertices.editArray()[i + 1 >= tempVertices.size() ? 0 : i + 1]); +void getFillVerticesFromPerimeterAA(const Vector<Vertex>& perimeter, VertexBuffer& vertexBuffer, + float inverseScaleX, float inverseScaleY) { + AlphaVertex* buffer = vertexBuffer.alloc<AlphaVertex>(perimeter.size() * 3 + 2); - vec2 lastNormal(current->position[1] - last->position[1], - last->position[0] - current->position[0]); - lastNormal.normalize(); + // generate alpha points - fill Alpha vertex gaps in between each point with + // alpha 0 vertex, offset by a scaled normal. + int currentIndex = 0; + const Vertex* last = &(perimeter[perimeter.size() - 1]); + const Vertex* current = &(perimeter[0]); + vec2 lastNormal(current->position[1] - last->position[1], + last->position[0] - current->position[0]); + lastNormal.normalize(); + for (unsigned int i = 0; i < perimeter.size(); i++) { + const Vertex* next = &(perimeter[i + 1 >= perimeter.size() ? 0 : i + 1]); vec2 nextNormal(next->position[1] - current->position[1], - current->position[0] - next->position[0]); + current->position[0] - next->position[0]); nextNormal.normalize(); // AA point offset from original point is that point's normal, such that // each side is offset by .5 pixels - vec2 totalOffset = (lastNormal + nextNormal) / (2 * (1 + lastNormal.dot(nextNormal))); - totalOffset.x *= inverseScaleX; - totalOffset.y *= inverseScaleY; + vec2 totalOffset = (lastNormal + nextNormal); + totalOffset.normalize(); + totalOffset.x *= inverseScaleX * 0.5f; + totalOffset.y *= inverseScaleY * 0.5f; AlphaVertex::set(&buffer[currentIndex++], - current->position[0] + totalOffset.x, - current->position[1] + totalOffset.y, - 0.0f); + current->position[0] + totalOffset.x, + current->position[1] + totalOffset.y, + 0.0f); AlphaVertex::set(&buffer[currentIndex++], - current->position[0] - totalOffset.x, - current->position[1] - totalOffset.y, - 1.0f); + current->position[0] - totalOffset.x, + current->position[1] - totalOffset.y, + 1.0f); + last = current; + current = next; + lastNormal = nextNormal; } // wrap around to beginning - AlphaVertex::set(&buffer[currentIndex++], - buffer[0].position[0], - buffer[0].position[1], 0.0f); - AlphaVertex::set(&buffer[currentIndex++], - buffer[1].position[0], - buffer[1].position[1], 1.0f); + copyAlphaVertex(&buffer[currentIndex++], &buffer[0]); + copyAlphaVertex(&buffer[currentIndex++], &buffer[1]); // zig zag between all previous points on the inside of the hull to create a // triangle strip that fills the hull, repeating the first inner point to // create degenerate tris to start inside path int srcAindex = 0; - int srcBindex = tempVertices.size() - 1; + int srcBindex = perimeter.size() - 1; while (srcAindex <= srcBindex) { - AlphaVertex::set(&buffer[currentIndex++], - buffer[srcAindex * 2 + 1].position[0], - buffer[srcAindex * 2 + 1].position[1], - 1.0f); + copyAlphaVertex(&buffer[currentIndex++], &buffer[srcAindex * 2 + 1]); if (srcAindex == srcBindex) break; - AlphaVertex::set(&buffer[currentIndex++], - buffer[srcBindex * 2 + 1].position[0], - buffer[srcBindex * 2 + 1].position[1], - 1.0f); + copyAlphaVertex(&buffer[currentIndex++], &buffer[srcBindex * 2 + 1]); srcAindex++; srcBindex--; } #if VERTEX_DEBUG - for (unsigned int i = 0; i < vertexBuffer.mSize; i++) { - ALOGD("point at %f %f", - buffer[i].position[0], - buffer[i].position[1]); + for (unsigned int i = 0; i < vertexBuffer.getSize(); i++) { + ALOGD("point at %f %f", buffer[i].position[0], buffer[i].position[1]); } #endif } +void getStrokeVerticesFromPerimeterAA(const Vector<Vertex>& perimeter, float halfStrokeWidth, + VertexBuffer& vertexBuffer, float inverseScaleX, float inverseScaleY) { + AlphaVertex* buffer = vertexBuffer.alloc<AlphaVertex>(6 * perimeter.size() + 8); + + int offset = 2 * perimeter.size() + 3; + int currentAAOuterIndex = 0; + int currentStrokeIndex = offset; + int currentAAInnerIndex = offset * 2; + + const Vertex* last = &(perimeter[perimeter.size() - 1]); + const Vertex* current = &(perimeter[0]); + vec2 lastNormal(current->position[1] - last->position[1], + last->position[0] - current->position[0]); + lastNormal.normalize(); + for (unsigned int i = 0; i < perimeter.size(); i++) { + const Vertex* next = &(perimeter[i + 1 >= perimeter.size() ? 0 : i + 1]); + vec2 nextNormal(next->position[1] - current->position[1], + current->position[0] - next->position[0]); + nextNormal.normalize(); + + vec2 pointNormal = (lastNormal + nextNormal); + pointNormal.normalize(); + vec2 AAOffset = pointNormal * 0.5f; + AAOffset.x *= inverseScaleX; + AAOffset.y *= inverseScaleY; + + vec2 innerOffset = pointNormal; + if (halfStrokeWidth == 0.0f) { + // hairline! - compensate for scale + innerOffset.x *= 0.5f * inverseScaleX; + innerOffset.y *= 0.5f * inverseScaleY; + } else { + innerOffset *= halfStrokeWidth; + } + vec2 outerOffset = innerOffset + AAOffset; + innerOffset -= AAOffset; + + AlphaVertex::set(&buffer[currentAAOuterIndex++], + current->position[0] + outerOffset.x, + current->position[1] + outerOffset.y, + 0.0f); + AlphaVertex::set(&buffer[currentAAOuterIndex++], + current->position[0] + innerOffset.x, + current->position[1] + innerOffset.y, + 1.0f); + + AlphaVertex::set(&buffer[currentStrokeIndex++], + current->position[0] + innerOffset.x, + current->position[1] + innerOffset.y, + 1.0f); + AlphaVertex::set(&buffer[currentStrokeIndex++], + current->position[0] - innerOffset.x, + current->position[1] - innerOffset.y, + 1.0f); + + AlphaVertex::set(&buffer[currentAAInnerIndex++], + current->position[0] - innerOffset.x, + current->position[1] - innerOffset.y, + 1.0f); + AlphaVertex::set(&buffer[currentAAInnerIndex++], + current->position[0] - outerOffset.x, + current->position[1] - outerOffset.y, + 0.0f); + + // TODO: current = next, copy last normal instead of recalculate + last = current; + current = next; + lastNormal = nextNormal; + } + + // wrap each strip around to beginning, creating degenerate tris to bridge strips + copyAlphaVertex(&buffer[currentAAOuterIndex++], &buffer[0]); + copyAlphaVertex(&buffer[currentAAOuterIndex++], &buffer[1]); + copyAlphaVertex(&buffer[currentAAOuterIndex++], &buffer[1]); -void PathRenderer::convexPathVertices(const SkPath &path, float thresholdx, float thresholdy, - Vector<Vertex> &outputVertices) { + copyAlphaVertex(&buffer[currentStrokeIndex++], &buffer[offset]); + copyAlphaVertex(&buffer[currentStrokeIndex++], &buffer[offset + 1]); + copyAlphaVertex(&buffer[currentStrokeIndex++], &buffer[offset + 1]); + + copyAlphaVertex(&buffer[currentAAInnerIndex++], &buffer[2 * offset]); + copyAlphaVertex(&buffer[currentAAInnerIndex++], &buffer[2 * offset + 1]); + // don't need to create last degenerate tri +} + +void PathRenderer::convexPathVertices(const SkPath &path, const SkPaint* paint, + const mat4 *transform, VertexBuffer& vertexBuffer) { + ATRACE_CALL(); + + SkPaint::Style style = paint->getStyle(); + bool isAA = paint->isAntiAlias(); + + float inverseScaleX, inverseScaleY; + computeInverseScales(transform, inverseScaleX, inverseScaleY); + + Vector<Vertex> tempVertices; + convexPathPerimeterVertices(path, inverseScaleX * inverseScaleX, inverseScaleY * inverseScaleY, + tempVertices); + +#if VERTEX_DEBUG + for (unsigned int i = 0; i < tempVertices.size(); i++) { + ALOGD("orig path: point at %f %f", tempVertices[i].position[0], tempVertices[i].position[1]); + } +#endif + + if (style == SkPaint::kStroke_Style) { + float halfStrokeWidth = paint->getStrokeWidth() * 0.5f; + if (!isAA) { + getStrokeVerticesFromPerimeter(tempVertices, halfStrokeWidth, vertexBuffer, + inverseScaleX, inverseScaleY); + } else { + getStrokeVerticesFromPerimeterAA(tempVertices, halfStrokeWidth, vertexBuffer, + inverseScaleX, inverseScaleY); + } + } else { + // For kStrokeAndFill style, the path should be adjusted externally, as it will be treated as a fill here. + if (!isAA) { + getFillVerticesFromPerimeter(tempVertices, vertexBuffer); + } else { + getFillVerticesFromPerimeterAA(tempVertices, vertexBuffer, inverseScaleX, inverseScaleY); + } + } +} + + +void PathRenderer::convexPathPerimeterVertices(const SkPath& path, + float sqrInvScaleX, float sqrInvScaleY, Vector<Vertex>& outputVertices) { ATRACE_CALL(); SkPath::Iter iter(path, true); @@ -189,31 +344,30 @@ void PathRenderer::convexPathVertices(const SkPath &path, float thresholdx, floa break; case SkPath::kLine_Verb: ALOGV("kLine_Verb %f %f -> %f %f", - pts[0].x(), pts[0].y(), - pts[1].x(), pts[1].y()); + pts[0].x(), pts[0].y(), + pts[1].x(), pts[1].y()); // TODO: make this not yuck outputVertices.push(); - newVertex = &(outputVertices.editArray()[outputVertices.size()-1]); + newVertex = &(outputVertices.editArray()[outputVertices.size() - 1]); Vertex::set(newVertex, pts[1].x(), pts[1].y()); break; case SkPath::kQuad_Verb: ALOGV("kQuad_Verb"); recursiveQuadraticBezierVertices( - pts[0].x(), pts[0].y(), - pts[2].x(), pts[2].y(), - pts[1].x(), pts[1].y(), - thresholdx, thresholdy, - outputVertices); + pts[0].x(), pts[0].y(), + pts[2].x(), pts[2].y(), + pts[1].x(), pts[1].y(), + sqrInvScaleX, sqrInvScaleY, outputVertices); break; case SkPath::kCubic_Verb: ALOGV("kCubic_Verb"); recursiveCubicBezierVertices( - pts[0].x(), pts[0].y(), - pts[1].x(), pts[1].y(), - pts[3].x(), pts[3].y(), - pts[2].x(), pts[2].y(), - thresholdx, thresholdy, outputVertices); + pts[0].x(), pts[0].y(), + pts[1].x(), pts[1].y(), + pts[3].x(), pts[3].y(), + pts[2].x(), pts[2].y(), + sqrInvScaleX, sqrInvScaleY, outputVertices); break; default: break; @@ -224,18 +378,20 @@ void PathRenderer::convexPathVertices(const SkPath &path, float thresholdx, floa void PathRenderer::recursiveCubicBezierVertices( float p1x, float p1y, float c1x, float c1y, float p2x, float p2y, float c2x, float c2y, - float thresholdx, float thresholdy, Vector<Vertex> &outputVertices) { + float sqrInvScaleX, float sqrInvScaleY, Vector<Vertex>& outputVertices) { float dx = p2x - p1x; float dy = p2y - p1y; float d1 = fabs((c1x - p2x) * dy - (c1y - p2y) * dx); float d2 = fabs((c2x - p2x) * dy - (c2y - p2y) * dx); float d = d1 + d2; - if (d * d < (thresholdx * (dx * dx) + thresholdy * (dy * dy))) { + // multiplying by sqrInvScaleY/X equivalent to multiplying in dimensional scale factors + + if (d * d < THRESHOLD * THRESHOLD * (dx * dx * sqrInvScaleY + dy * dy * sqrInvScaleX)) { // below thresh, draw line by adding endpoint // TODO: make this not yuck outputVertices.push(); - Vertex* newVertex = &(outputVertices.editArray()[outputVertices.size()-1]); + Vertex* newVertex = &(outputVertices.editArray()[outputVertices.size() - 1]); Vertex::set(newVertex, p2x, p2y); } else { float p1c1x = (p1x + c1x) * 0.5f; @@ -258,13 +414,11 @@ void PathRenderer::recursiveCubicBezierVertices( recursiveCubicBezierVertices( p1x, p1y, p1c1x, p1c1y, mx, my, p1c1c2x, p1c1c2y, - thresholdx, thresholdy, - outputVertices); + sqrInvScaleX, sqrInvScaleY, outputVertices); recursiveCubicBezierVertices( mx, my, p2c1c2x, p2c1c2y, p2x, p2y, p2c2x, p2c2y, - thresholdx, thresholdy, - outputVertices); + sqrInvScaleX, sqrInvScaleY, outputVertices); } } @@ -272,16 +426,16 @@ void PathRenderer::recursiveQuadraticBezierVertices( float ax, float ay, float bx, float by, float cx, float cy, - float thresholdx, float thresholdy, Vector<Vertex> &outputVertices) { + float sqrInvScaleX, float sqrInvScaleY, Vector<Vertex>& outputVertices) { float dx = bx - ax; float dy = by - ay; float d = (cx - bx) * dy - (cy - by) * dx; - if (d * d < (thresholdx * (dx * dx) + thresholdy * (dy * dy))) { + if (d * d < THRESHOLD * THRESHOLD * (dx * dx * sqrInvScaleY + dy * dy * sqrInvScaleX)) { // below thresh, draw line by adding endpoint // TODO: make this not yuck outputVertices.push(); - Vertex* newVertex = &(outputVertices.editArray()[outputVertices.size()-1]); + Vertex* newVertex = &(outputVertices.editArray()[outputVertices.size() - 1]); Vertex::set(newVertex, bx, by); } else { float acx = (ax + cx) * 0.5f; @@ -294,9 +448,9 @@ void PathRenderer::recursiveQuadraticBezierVertices( float my = (acy + bcy) * 0.5f; recursiveQuadraticBezierVertices(ax, ay, mx, my, acx, acy, - thresholdx, thresholdy, outputVertices); + sqrInvScaleX, sqrInvScaleY, outputVertices); recursiveQuadraticBezierVertices(mx, my, bx, by, bcx, bcy, - thresholdx, thresholdy, outputVertices); + sqrInvScaleX, sqrInvScaleY, outputVertices); } } |