VectorDrawable native rendering - Step 3 of MANY

- Refactored VPathRenderer & VectorDrawableState
- Moved all the VD rendering into native
- Set up hooks for VD's property changes in JNI for animated VD

TODO: JNI calls can be further reduced when we convert the animation
in AVD to use RenderNodeAnimator, in which case animation will be
driven from native and therefore most of the JNI hooks for changing
VD's properties during animation will no longer be needed.

Change-Id: I52021f4d7bea057b83ace54085d870dd45acae0f
diff --git a/libs/hwui/VectorDrawable.cpp b/libs/hwui/VectorDrawable.cpp
new file mode 100644
index 0000000..e13a2bb
--- /dev/null
+++ b/libs/hwui/VectorDrawable.cpp
@@ -0,0 +1,485 @@
+/*
+ * 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 "VectorDrawable.h"
+
+#include "PathParser.h"
+#include "SkImageInfo.h"
+#include <utils/Log.h>
+#include "utils/Macros.h"
+#include "utils/VectorDrawableUtils.h"
+
+#include <math.h>
+#include <string.h>
+
+namespace android {
+namespace uirenderer {
+namespace VectorDrawable {
+
+const int Tree::MAX_CACHED_BITMAP_SIZE = 2048;
+
+void Path::draw(Canvas* outCanvas, const SkMatrix& groupStackedMatrix, float scaleX, float scaleY) {
+    float matrixScale = getMatrixScale(groupStackedMatrix);
+    if (matrixScale == 0) {
+        // When either x or y is scaled to 0, we don't need to draw anything.
+        return;
+    }
+
+    const SkPath updatedPath = getUpdatedPath();
+    SkMatrix pathMatrix(groupStackedMatrix);
+    pathMatrix.postScale(scaleX, scaleY);
+
+    //TODO: try apply the path matrix to the canvas instead of creating a new path.
+    SkPath renderPath;
+    renderPath.reset();
+    renderPath.addPath(updatedPath, pathMatrix);
+
+    float minScale = fmin(scaleX, scaleY);
+    float strokeScale = minScale * matrixScale;
+    drawPath(outCanvas, renderPath, strokeScale);
+}
+
+void Path::setPathData(const Data& data) {
+    if (mData == data) {
+        return;
+    }
+    // Updates the path data. Note that we don't generate a new Skia path right away
+    // because there are cases where the animation is changing the path data, but the view
+    // that hosts the VD has gone off screen, in which case we won't even draw. So we
+    // postpone the Skia path generation to the draw time.
+    mData = data;
+    mSkPathDirty = true;
+}
+
+void Path::dump() {
+    ALOGD("Path: %s has %zu points", mName.c_str(), mData.points.size());
+}
+
+float Path::getMatrixScale(const SkMatrix& groupStackedMatrix) {
+    // Given unit vectors A = (0, 1) and B = (1, 0).
+    // After matrix mapping, we got A' and B'. Let theta = the angel b/t A' and B'.
+    // Therefore, the final scale we want is min(|A'| * sin(theta), |B'| * sin(theta)),
+    // which is (|A'| * |B'| * sin(theta)) / max (|A'|, |B'|);
+    // If  max (|A'|, |B'|) = 0, that means either x or y has a scale of 0.
+    //
+    // For non-skew case, which is most of the cases, matrix scale is computing exactly the
+    // scale on x and y axis, and take the minimal of these two.
+    // For skew case, an unit square will mapped to a parallelogram. And this function will
+    // return the minimal height of the 2 bases.
+    SkVector skVectors[2];
+    skVectors[0].set(0, 1);
+    skVectors[1].set(1, 0);
+    groupStackedMatrix.mapVectors(skVectors, 2);
+    float scaleX = hypotf(skVectors[0].fX, skVectors[0].fY);
+    float scaleY = hypotf(skVectors[1].fX, skVectors[1].fY);
+    float crossProduct = skVectors[0].cross(skVectors[1]);
+    float maxScale = fmax(scaleX, scaleY);
+
+    float matrixScale = 0;
+    if (maxScale > 0) {
+        matrixScale = fabs(crossProduct) / maxScale;
+    }
+    return matrixScale;
+}
+Path::Path(const char* pathStr, size_t strLength) {
+    PathParser::ParseResult result;
+    PathParser::getPathDataFromString(&mData, &result, pathStr, strLength);
+    if (!result.failureOccurred) {
+        VectorDrawableUtils::verbsToPath(&mSkPath, mData);
+    }
+}
+
+Path::Path(const Data& data) {
+    mData = data;
+    // Now we need to construct a path
+    VectorDrawableUtils::verbsToPath(&mSkPath, data);
+}
+
+Path::Path(const Path& path) : Node(path) {
+    mData = path.mData;
+    VectorDrawableUtils::verbsToPath(&mSkPath, mData);
+}
+
+bool Path::canMorph(const Data& morphTo) {
+    return VectorDrawableUtils::canMorph(mData, morphTo);
+}
+
+bool Path::canMorph(const Path& path) {
+    return canMorph(path.mData);
+}
+
+const SkPath& Path::getUpdatedPath() {
+    if (mSkPathDirty) {
+        mSkPath.reset();
+        VectorDrawableUtils::verbsToPath(&mSkPath, mData);
+        mSkPathDirty = false;
+    }
+    return mSkPath;
+}
+
+void Path::setPath(const char* pathStr, size_t strLength) {
+    PathParser::ParseResult result;
+    mSkPathDirty = true;
+    PathParser::getPathDataFromString(&mData, &result, pathStr, strLength);
+}
+
+FullPath::FullPath(const FullPath& path) : Path(path) {
+    mStrokeWidth = path.mStrokeWidth;
+    mStrokeColor = path.mStrokeColor;
+    mStrokeAlpha = path.mStrokeAlpha;
+    mFillColor = path.mFillColor;
+    mFillAlpha = path.mFillAlpha;
+    mTrimPathStart = path.mTrimPathStart;
+    mTrimPathEnd = path.mTrimPathEnd;
+    mTrimPathOffset = path.mTrimPathOffset;
+    mStrokeMiterLimit = path.mStrokeMiterLimit;
+    mStrokeLineCap = path.mStrokeLineCap;
+    mStrokeLineJoin = path.mStrokeLineJoin;
+}
+
+const SkPath& FullPath::getUpdatedPath() {
+    if (!mSkPathDirty && !mTrimDirty) {
+        return mTrimmedSkPath;
+    }
+    Path::getUpdatedPath();
+    if (mTrimPathStart != 0.0f || mTrimPathEnd != 1.0f) {
+        applyTrim();
+        return mTrimmedSkPath;
+    } else {
+        return mSkPath;
+    }
+}
+
+void FullPath::updateProperties(float strokeWidth, SkColor strokeColor, float strokeAlpha,
+        SkColor fillColor, float fillAlpha, float trimPathStart, float trimPathEnd,
+        float trimPathOffset, float strokeMiterLimit, int strokeLineCap, int strokeLineJoin) {
+    mStrokeWidth = strokeWidth;
+    mStrokeColor = strokeColor;
+    mStrokeAlpha = strokeAlpha;
+    mFillColor = fillColor;
+    mFillAlpha = fillAlpha;
+    mStrokeMiterLimit = strokeMiterLimit;
+    mStrokeLineCap = SkPaint::Cap(strokeLineCap);
+    mStrokeLineJoin = SkPaint::Join(strokeLineJoin);
+
+    // If any trim property changes, mark trim dirty and update the trim path
+    setTrimPathStart(trimPathStart);
+    setTrimPathEnd(trimPathEnd);
+    setTrimPathOffset(trimPathOffset);
+}
+
+inline SkColor applyAlpha(SkColor color, float alpha) {
+    int alphaBytes = SkColorGetA(color);
+    return SkColorSetA(color, alphaBytes * alpha);
+}
+
+void FullPath::drawPath(Canvas* outCanvas, const SkPath& renderPath, float strokeScale){
+    // Draw path's fill, if fill color isn't transparent.
+    if (mFillColor != SK_ColorTRANSPARENT) {
+        mPaint.setStyle(SkPaint::Style::kFill_Style);
+        mPaint.setAntiAlias(true);
+        mPaint.setColor(applyAlpha(mFillColor, mFillAlpha));
+        outCanvas->drawPath(renderPath, mPaint);
+    }
+    // Draw path's stroke, if stroke color isn't transparent
+    if (mStrokeColor != SK_ColorTRANSPARENT) {
+        mPaint.setStyle(SkPaint::Style::kStroke_Style);
+        mPaint.setAntiAlias(true);
+        mPaint.setStrokeJoin(mStrokeLineJoin);
+        mPaint.setStrokeCap(mStrokeLineCap);
+        mPaint.setStrokeMiter(mStrokeMiterLimit);
+        mPaint.setColor(applyAlpha(mStrokeColor, mStrokeAlpha));
+        mPaint.setStrokeWidth(mStrokeWidth * strokeScale);
+        outCanvas->drawPath(renderPath, mPaint);
+    }
+}
+
+/**
+ * Applies trimming to the specified path.
+ */
+void FullPath::applyTrim() {
+    if (mTrimPathStart == 0.0f && mTrimPathEnd == 1.0f) {
+        // No trimming necessary.
+        return;
+    }
+    SkPathMeasure measure(mSkPath, false);
+    float len = SkScalarToFloat(measure.getLength());
+    float start = len * fmod((mTrimPathStart + mTrimPathOffset), 1.0f);
+    float end = len * fmod((mTrimPathEnd + mTrimPathOffset), 1.0f);
+
+    mTrimmedSkPath.reset();
+    if (start > end) {
+        measure.getSegment(start, len, &mTrimmedSkPath, true);
+        measure.getSegment(0, end, &mTrimmedSkPath, true);
+    } else {
+        measure.getSegment(start, end, &mTrimmedSkPath, true);
+    }
+    mTrimDirty = false;
+}
+
+inline int putData(int8_t* outBytes, int startIndex, float value) {
+    int size = sizeof(float);
+    memcpy(&outBytes[startIndex], &value, size);
+    return size;
+}
+
+inline int putData(int8_t* outBytes, int startIndex, int value) {
+    int size = sizeof(int);
+    memcpy(&outBytes[startIndex], &value, size);
+    return size;
+}
+
+struct FullPathProperties {
+    // TODO: Consider storing full path properties in this struct instead of the fields.
+    float strokeWidth;
+    SkColor strokeColor;
+    float strokeAlpha;
+    SkColor fillColor;
+    float fillAlpha;
+    float trimPathStart;
+    float trimPathEnd;
+    float trimPathOffset;
+    int32_t strokeLineCap;
+    int32_t strokeLineJoin;
+    float strokeMiterLimit;
+};
+
+REQUIRE_COMPATIBLE_LAYOUT(FullPathProperties);
+
+static_assert(sizeof(float) == sizeof(int32_t), "float is not the same size as int32_t");
+static_assert(sizeof(SkColor) == sizeof(int32_t), "SkColor is not the same size as int32_t");
+
+bool FullPath::getProperties(int8_t* outProperties, int length) {
+    int propertyDataSize = sizeof(FullPathProperties);
+    if (length != propertyDataSize) {
+        LOG_ALWAYS_FATAL("Properties needs exactly %d bytes, a byte array of size %d is provided",
+                propertyDataSize, length);
+        return false;
+    }
+    // TODO: consider replacing the property fields with a FullPathProperties struct.
+    FullPathProperties properties;
+    properties.strokeWidth = mStrokeWidth;
+    properties.strokeColor = mStrokeColor;
+    properties.strokeAlpha = mStrokeAlpha;
+    properties.fillColor = mFillColor;
+    properties.fillAlpha = mFillAlpha;
+    properties.trimPathStart = mTrimPathStart;
+    properties.trimPathEnd = mTrimPathEnd;
+    properties.trimPathOffset = mTrimPathOffset;
+    properties.strokeLineCap = mStrokeLineCap;
+    properties.strokeLineJoin = mStrokeLineJoin;
+    properties.strokeMiterLimit = mStrokeMiterLimit;
+
+    memcpy(outProperties, &properties, length);
+    return true;
+}
+
+void ClipPath::drawPath(Canvas* outCanvas, const SkPath& renderPath,
+        float strokeScale){
+    outCanvas->clipPath(&renderPath, SkRegion::kIntersect_Op);
+}
+
+Group::Group(const Group& group) : Node(group) {
+    mRotate = group.mRotate;
+    mPivotX = group.mPivotX;
+    mPivotY = group.mPivotY;
+    mScaleX = group.mScaleX;
+    mScaleY = group.mScaleY;
+    mTranslateX = group.mTranslateX;
+    mTranslateY = group.mTranslateY;
+}
+
+void Group::draw(Canvas* outCanvas, const SkMatrix& currentMatrix, float scaleX,
+        float scaleY) {
+    // TODO: Try apply the matrix to the canvas instead of passing it down the tree
+
+    // Calculate current group's matrix by preConcat the parent's and
+    // and the current one on the top of the stack.
+    // Basically the Mfinal = Mviewport * M0 * M1 * M2;
+    // Mi the local matrix at level i of the group tree.
+    SkMatrix stackedMatrix;
+    getLocalMatrix(&stackedMatrix);
+    stackedMatrix.postConcat(currentMatrix);
+
+    // Save the current clip information, which is local to this group.
+    outCanvas->save(SkCanvas::kMatrixClip_SaveFlag);
+    // Draw the group tree in the same order as the XML file.
+    for (Node* child : mChildren) {
+        child->draw(outCanvas, stackedMatrix, scaleX, scaleY);
+    }
+    // Restore the previous clip information.
+    outCanvas->restore();
+}
+
+void Group::dump() {
+    ALOGD("Group %s has %zu children: ", mName.c_str(), mChildren.size());
+    for (size_t i = 0; i < mChildren.size(); i++) {
+        mChildren[i]->dump();
+    }
+}
+
+void Group::updateLocalMatrix(float rotate, float pivotX, float pivotY,
+        float scaleX, float scaleY, float translateX, float translateY) {
+    setRotation(rotate);
+    setPivotX(pivotX);
+    setPivotY(pivotY);
+    setScaleX(scaleX);
+    setScaleY(scaleY);
+    setTranslateX(translateX);
+    setTranslateY(translateY);
+}
+
+void Group::getLocalMatrix(SkMatrix* outMatrix) {
+    outMatrix->reset();
+    // TODO: use rotate(mRotate, mPivotX, mPivotY) and scale with pivot point, instead of
+    // translating to pivot for rotating and scaling, then translating back.
+    outMatrix->postTranslate(-mPivotX, -mPivotY);
+    outMatrix->postScale(mScaleX, mScaleY);
+    outMatrix->postRotate(mRotate, 0, 0);
+    outMatrix->postTranslate(mTranslateX + mPivotX, mTranslateY + mPivotY);
+}
+
+void Group::addChild(Node* child) {
+    mChildren.push_back(child);
+}
+
+bool Group::getProperties(float* outProperties, int length) {
+    int propertyCount = static_cast<int>(Property::Count);
+    if (length != propertyCount) {
+        LOG_ALWAYS_FATAL("Properties needs exactly %d bytes, a byte array of size %d is provided",
+                propertyCount, length);
+        return false;
+    }
+    for (int i = 0; i < propertyCount; i++) {
+        Property currentProperty = static_cast<Property>(i);
+        switch (currentProperty) {
+        case Property::Rotate_Property:
+            outProperties[i] = mRotate;
+            break;
+        case Property::PivotX_Property:
+            outProperties[i] = mPivotX;
+            break;
+        case Property::PivotY_Property:
+            outProperties[i] = mPivotY;
+            break;
+        case Property::ScaleX_Property:
+            outProperties[i] = mScaleX;
+            break;
+        case Property::ScaleY_Property:
+            outProperties[i] = mScaleY;
+            break;
+        case Property::TranslateX_Property:
+            outProperties[i] = mTranslateX;
+            break;
+        case Property::TranslateY_Property:
+            outProperties[i] = mTranslateY;
+            break;
+        default:
+            LOG_ALWAYS_FATAL("Invalid input index: %d", i);
+            return false;
+        }
+    }
+    return true;
+}
+
+void Tree::draw(Canvas* outCanvas, SkColorFilter* colorFilter,
+        const SkRect& bounds, bool needsMirroring, bool canReuseCache) {
+    // The imageView can scale the canvas in different ways, in order to
+    // avoid blurry scaling, we have to draw into a bitmap with exact pixel
+    // size first. This bitmap size is determined by the bounds and the
+    // canvas scale.
+    outCanvas->getMatrix(&mCanvasMatrix);
+    mBounds = bounds;
+    int scaledWidth = (int) (mBounds.width() * mCanvasMatrix.getScaleX());
+    int scaledHeight = (int) (mBounds.height() * mCanvasMatrix.getScaleY());
+    scaledWidth = std::min(Tree::MAX_CACHED_BITMAP_SIZE, scaledWidth);
+    scaledHeight = std::min(Tree::MAX_CACHED_BITMAP_SIZE, scaledHeight);
+
+    if (scaledWidth <= 0 || scaledHeight <= 0) {
+        return;
+    }
+
+    int saveCount = outCanvas->save(SkCanvas::SaveFlags::kMatrixClip_SaveFlag);
+    outCanvas->translate(mBounds.fLeft, mBounds.fTop);
+
+    // Handle RTL mirroring.
+    if (needsMirroring) {
+        outCanvas->translate(mBounds.width(), 0);
+        outCanvas->scale(-1.0f, 1.0f);
+    }
+
+    // At this point, canvas has been translated to the right position.
+    // And we use this bound for the destination rect for the drawBitmap, so
+    // we offset to (0, 0);
+    mBounds.offsetTo(0, 0);
+
+    createCachedBitmapIfNeeded(scaledWidth, scaledHeight);
+    if (!mAllowCaching) {
+        updateCachedBitmap(scaledWidth, scaledHeight);
+    } else {
+        if (!canReuseCache || mCacheDirty) {
+            updateCachedBitmap(scaledWidth, scaledHeight);
+        }
+    }
+    drawCachedBitmapWithRootAlpha(outCanvas, colorFilter, mBounds);
+
+    outCanvas->restoreToCount(saveCount);
+}
+
+void Tree::drawCachedBitmapWithRootAlpha(Canvas* outCanvas, SkColorFilter* filter,
+        const SkRect& originalBounds) {
+    SkPaint* paint;
+    if (mRootAlpha == 1.0f && filter == NULL) {
+        paint = NULL;
+    } else {
+        mPaint.setFilterQuality(kLow_SkFilterQuality);
+        mPaint.setAlpha(mRootAlpha * 255);
+        mPaint.setColorFilter(filter);
+        paint = &mPaint;
+    }
+    outCanvas->drawBitmap(mCachedBitmap, 0, 0, mCachedBitmap.width(), mCachedBitmap.height(),
+            originalBounds.fLeft, originalBounds.fTop, originalBounds.fRight,
+            originalBounds.fBottom, paint);
+}
+
+void Tree::updateCachedBitmap(int width, int height) {
+    mCachedBitmap.eraseColor(SK_ColorTRANSPARENT);
+    Canvas* outCanvas = Canvas::create_canvas(mCachedBitmap);
+    float scaleX = width / mViewportWidth;
+    float scaleY = height / mViewportHeight;
+    mRootNode->draw(outCanvas, SkMatrix::I(), scaleX, scaleY);
+    mCacheDirty = false;
+}
+
+void Tree::createCachedBitmapIfNeeded(int width, int height) {
+    if (!canReuseBitmap(width, height)) {
+        SkImageInfo info = SkImageInfo::Make(width, height,
+                kN32_SkColorType, kPremul_SkAlphaType);
+        mCachedBitmap.setInfo(info);
+        // TODO: Count the bitmap cache against app's java heap
+        mCachedBitmap.allocPixels(info);
+        mCacheDirty = true;
+    }
+}
+
+bool Tree::canReuseBitmap(int width, int height) {
+    return width == mCachedBitmap.width() && height == mCachedBitmap.height();
+}
+
+}; // namespace VectorDrawable
+
+}; // namespace uirenderer
+}; // namespace android