VectorDrawable.cpp revision c2de46fadd4ca9c6aa2d9dd7a65b161b28fc6f3b
1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "VectorDrawable.h"
18
19#include "PathParser.h"
20#include "SkImageInfo.h"
21#include <utils/Log.h>
22#include "utils/Macros.h"
23#include "utils/VectorDrawableUtils.h"
24
25#include <math.h>
26#include <string.h>
27
28namespace android {
29namespace uirenderer {
30namespace VectorDrawable {
31
32const int Tree::MAX_CACHED_BITMAP_SIZE = 2048;
33
34void Path::draw(SkCanvas* outCanvas, const SkMatrix& groupStackedMatrix, float scaleX, float scaleY) {
35    float matrixScale = getMatrixScale(groupStackedMatrix);
36    if (matrixScale == 0) {
37        // When either x or y is scaled to 0, we don't need to draw anything.
38        return;
39    }
40
41    const SkPath updatedPath = getUpdatedPath();
42    SkMatrix pathMatrix(groupStackedMatrix);
43    pathMatrix.postScale(scaleX, scaleY);
44
45    //TODO: try apply the path matrix to the canvas instead of creating a new path.
46    SkPath renderPath;
47    renderPath.reset();
48    renderPath.addPath(updatedPath, pathMatrix);
49
50    float minScale = fmin(scaleX, scaleY);
51    float strokeScale = minScale * matrixScale;
52    drawPath(outCanvas, renderPath, strokeScale);
53}
54
55void Path::setPathData(const Data& data) {
56    if (mData == data) {
57        return;
58    }
59    // Updates the path data. Note that we don't generate a new Skia path right away
60    // because there are cases where the animation is changing the path data, but the view
61    // that hosts the VD has gone off screen, in which case we won't even draw. So we
62    // postpone the Skia path generation to the draw time.
63    mData = data;
64    mSkPathDirty = true;
65}
66
67void Path::dump() {
68    ALOGD("Path: %s has %zu points", mName.c_str(), mData.points.size());
69}
70
71float Path::getMatrixScale(const SkMatrix& groupStackedMatrix) {
72    // Given unit vectors A = (0, 1) and B = (1, 0).
73    // After matrix mapping, we got A' and B'. Let theta = the angel b/t A' and B'.
74    // Therefore, the final scale we want is min(|A'| * sin(theta), |B'| * sin(theta)),
75    // which is (|A'| * |B'| * sin(theta)) / max (|A'|, |B'|);
76    // If  max (|A'|, |B'|) = 0, that means either x or y has a scale of 0.
77    //
78    // For non-skew case, which is most of the cases, matrix scale is computing exactly the
79    // scale on x and y axis, and take the minimal of these two.
80    // For skew case, an unit square will mapped to a parallelogram. And this function will
81    // return the minimal height of the 2 bases.
82    SkVector skVectors[2];
83    skVectors[0].set(0, 1);
84    skVectors[1].set(1, 0);
85    groupStackedMatrix.mapVectors(skVectors, 2);
86    float scaleX = hypotf(skVectors[0].fX, skVectors[0].fY);
87    float scaleY = hypotf(skVectors[1].fX, skVectors[1].fY);
88    float crossProduct = skVectors[0].cross(skVectors[1]);
89    float maxScale = fmax(scaleX, scaleY);
90
91    float matrixScale = 0;
92    if (maxScale > 0) {
93        matrixScale = fabs(crossProduct) / maxScale;
94    }
95    return matrixScale;
96}
97Path::Path(const char* pathStr, size_t strLength) {
98    PathParser::ParseResult result;
99    PathParser::getPathDataFromString(&mData, &result, pathStr, strLength);
100    if (!result.failureOccurred) {
101        VectorDrawableUtils::verbsToPath(&mSkPath, mData);
102    }
103}
104
105Path::Path(const Data& data) {
106    mData = data;
107    // Now we need to construct a path
108    VectorDrawableUtils::verbsToPath(&mSkPath, data);
109}
110
111Path::Path(const Path& path) : Node(path) {
112    mData = path.mData;
113    VectorDrawableUtils::verbsToPath(&mSkPath, mData);
114}
115
116bool Path::canMorph(const Data& morphTo) {
117    return VectorDrawableUtils::canMorph(mData, morphTo);
118}
119
120bool Path::canMorph(const Path& path) {
121    return canMorph(path.mData);
122}
123
124const SkPath& Path::getUpdatedPath() {
125    if (mSkPathDirty) {
126        mSkPath.reset();
127        VectorDrawableUtils::verbsToPath(&mSkPath, mData);
128        mSkPathDirty = false;
129    }
130    return mSkPath;
131}
132
133void Path::setPath(const char* pathStr, size_t strLength) {
134    PathParser::ParseResult result;
135    mSkPathDirty = true;
136    PathParser::getPathDataFromString(&mData, &result, pathStr, strLength);
137}
138
139FullPath::FullPath(const FullPath& path) : Path(path) {
140    mStrokeWidth = path.mStrokeWidth;
141    mStrokeColor = path.mStrokeColor;
142    mStrokeAlpha = path.mStrokeAlpha;
143    mFillColor = path.mFillColor;
144    mFillAlpha = path.mFillAlpha;
145    mTrimPathStart = path.mTrimPathStart;
146    mTrimPathEnd = path.mTrimPathEnd;
147    mTrimPathOffset = path.mTrimPathOffset;
148    mStrokeMiterLimit = path.mStrokeMiterLimit;
149    mStrokeLineCap = path.mStrokeLineCap;
150    mStrokeLineJoin = path.mStrokeLineJoin;
151}
152
153const SkPath& FullPath::getUpdatedPath() {
154    if (!mSkPathDirty && !mTrimDirty) {
155        return mTrimmedSkPath;
156    }
157    Path::getUpdatedPath();
158    if (mTrimPathStart != 0.0f || mTrimPathEnd != 1.0f) {
159        applyTrim();
160        return mTrimmedSkPath;
161    } else {
162        return mSkPath;
163    }
164}
165
166void FullPath::updateProperties(float strokeWidth, SkColor strokeColor, float strokeAlpha,
167        SkColor fillColor, float fillAlpha, float trimPathStart, float trimPathEnd,
168        float trimPathOffset, float strokeMiterLimit, int strokeLineCap, int strokeLineJoin) {
169    mStrokeWidth = strokeWidth;
170    mStrokeColor = strokeColor;
171    mStrokeAlpha = strokeAlpha;
172    mFillColor = fillColor;
173    mFillAlpha = fillAlpha;
174    mStrokeMiterLimit = strokeMiterLimit;
175    mStrokeLineCap = SkPaint::Cap(strokeLineCap);
176    mStrokeLineJoin = SkPaint::Join(strokeLineJoin);
177
178    // If any trim property changes, mark trim dirty and update the trim path
179    setTrimPathStart(trimPathStart);
180    setTrimPathEnd(trimPathEnd);
181    setTrimPathOffset(trimPathOffset);
182}
183
184inline SkColor applyAlpha(SkColor color, float alpha) {
185    int alphaBytes = SkColorGetA(color);
186    return SkColorSetA(color, alphaBytes * alpha);
187}
188
189void FullPath::drawPath(SkCanvas* outCanvas, const SkPath& renderPath, float strokeScale){
190    // Draw path's fill, if fill color isn't transparent.
191    if (mFillColor != SK_ColorTRANSPARENT) {
192        mPaint.setStyle(SkPaint::Style::kFill_Style);
193        mPaint.setAntiAlias(true);
194        mPaint.setColor(applyAlpha(mFillColor, mFillAlpha));
195        outCanvas->drawPath(renderPath, mPaint);
196    }
197    // Draw path's stroke, if stroke color isn't transparent
198    if (mStrokeColor != SK_ColorTRANSPARENT) {
199        mPaint.setStyle(SkPaint::Style::kStroke_Style);
200        mPaint.setAntiAlias(true);
201        mPaint.setStrokeJoin(mStrokeLineJoin);
202        mPaint.setStrokeCap(mStrokeLineCap);
203        mPaint.setStrokeMiter(mStrokeMiterLimit);
204        mPaint.setColor(applyAlpha(mStrokeColor, mStrokeAlpha));
205        mPaint.setStrokeWidth(mStrokeWidth * strokeScale);
206        outCanvas->drawPath(renderPath, mPaint);
207    }
208}
209
210/**
211 * Applies trimming to the specified path.
212 */
213void FullPath::applyTrim() {
214    if (mTrimPathStart == 0.0f && mTrimPathEnd == 1.0f) {
215        // No trimming necessary.
216        return;
217    }
218    SkPathMeasure measure(mSkPath, false);
219    float len = SkScalarToFloat(measure.getLength());
220    float start = len * fmod((mTrimPathStart + mTrimPathOffset), 1.0f);
221    float end = len * fmod((mTrimPathEnd + mTrimPathOffset), 1.0f);
222
223    mTrimmedSkPath.reset();
224    if (start > end) {
225        measure.getSegment(start, len, &mTrimmedSkPath, true);
226        measure.getSegment(0, end, &mTrimmedSkPath, true);
227    } else {
228        measure.getSegment(start, end, &mTrimmedSkPath, true);
229    }
230    mTrimDirty = false;
231}
232
233inline int putData(int8_t* outBytes, int startIndex, float value) {
234    int size = sizeof(float);
235    memcpy(&outBytes[startIndex], &value, size);
236    return size;
237}
238
239inline int putData(int8_t* outBytes, int startIndex, int value) {
240    int size = sizeof(int);
241    memcpy(&outBytes[startIndex], &value, size);
242    return size;
243}
244
245struct FullPathProperties {
246    // TODO: Consider storing full path properties in this struct instead of the fields.
247    float strokeWidth;
248    SkColor strokeColor;
249    float strokeAlpha;
250    SkColor fillColor;
251    float fillAlpha;
252    float trimPathStart;
253    float trimPathEnd;
254    float trimPathOffset;
255    int32_t strokeLineCap;
256    int32_t strokeLineJoin;
257    float strokeMiterLimit;
258};
259
260REQUIRE_COMPATIBLE_LAYOUT(FullPathProperties);
261
262static_assert(sizeof(float) == sizeof(int32_t), "float is not the same size as int32_t");
263static_assert(sizeof(SkColor) == sizeof(int32_t), "SkColor is not the same size as int32_t");
264
265bool FullPath::getProperties(int8_t* outProperties, int length) {
266    int propertyDataSize = sizeof(FullPathProperties);
267    if (length != propertyDataSize) {
268        LOG_ALWAYS_FATAL("Properties needs exactly %d bytes, a byte array of size %d is provided",
269                propertyDataSize, length);
270        return false;
271    }
272    // TODO: consider replacing the property fields with a FullPathProperties struct.
273    FullPathProperties properties;
274    properties.strokeWidth = mStrokeWidth;
275    properties.strokeColor = mStrokeColor;
276    properties.strokeAlpha = mStrokeAlpha;
277    properties.fillColor = mFillColor;
278    properties.fillAlpha = mFillAlpha;
279    properties.trimPathStart = mTrimPathStart;
280    properties.trimPathEnd = mTrimPathEnd;
281    properties.trimPathOffset = mTrimPathOffset;
282    properties.strokeLineCap = mStrokeLineCap;
283    properties.strokeLineJoin = mStrokeLineJoin;
284    properties.strokeMiterLimit = mStrokeMiterLimit;
285
286    memcpy(outProperties, &properties, length);
287    return true;
288}
289
290void ClipPath::drawPath(SkCanvas* outCanvas, const SkPath& renderPath,
291        float strokeScale){
292    outCanvas->clipPath(renderPath, SkRegion::kIntersect_Op);
293}
294
295Group::Group(const Group& group) : Node(group) {
296    mRotate = group.mRotate;
297    mPivotX = group.mPivotX;
298    mPivotY = group.mPivotY;
299    mScaleX = group.mScaleX;
300    mScaleY = group.mScaleY;
301    mTranslateX = group.mTranslateX;
302    mTranslateY = group.mTranslateY;
303}
304
305void Group::draw(SkCanvas* outCanvas, const SkMatrix& currentMatrix, float scaleX,
306        float scaleY) {
307    // TODO: Try apply the matrix to the canvas instead of passing it down the tree
308
309    // Calculate current group's matrix by preConcat the parent's and
310    // and the current one on the top of the stack.
311    // Basically the Mfinal = Mviewport * M0 * M1 * M2;
312    // Mi the local matrix at level i of the group tree.
313    SkMatrix stackedMatrix;
314    getLocalMatrix(&stackedMatrix);
315    stackedMatrix.postConcat(currentMatrix);
316
317    // Save the current clip information, which is local to this group.
318    outCanvas->save();
319    // Draw the group tree in the same order as the XML file.
320    for (Node* child : mChildren) {
321        child->draw(outCanvas, stackedMatrix, scaleX, scaleY);
322    }
323    // Restore the previous clip information.
324    outCanvas->restore();
325}
326
327void Group::dump() {
328    ALOGD("Group %s has %zu children: ", mName.c_str(), mChildren.size());
329    for (size_t i = 0; i < mChildren.size(); i++) {
330        mChildren[i]->dump();
331    }
332}
333
334void Group::updateLocalMatrix(float rotate, float pivotX, float pivotY,
335        float scaleX, float scaleY, float translateX, float translateY) {
336    setRotation(rotate);
337    setPivotX(pivotX);
338    setPivotY(pivotY);
339    setScaleX(scaleX);
340    setScaleY(scaleY);
341    setTranslateX(translateX);
342    setTranslateY(translateY);
343}
344
345void Group::getLocalMatrix(SkMatrix* outMatrix) {
346    outMatrix->reset();
347    // TODO: use rotate(mRotate, mPivotX, mPivotY) and scale with pivot point, instead of
348    // translating to pivot for rotating and scaling, then translating back.
349    outMatrix->postTranslate(-mPivotX, -mPivotY);
350    outMatrix->postScale(mScaleX, mScaleY);
351    outMatrix->postRotate(mRotate, 0, 0);
352    outMatrix->postTranslate(mTranslateX + mPivotX, mTranslateY + mPivotY);
353}
354
355void Group::addChild(Node* child) {
356    mChildren.push_back(child);
357}
358
359bool Group::getProperties(float* outProperties, int length) {
360    int propertyCount = static_cast<int>(Property::Count);
361    if (length != propertyCount) {
362        LOG_ALWAYS_FATAL("Properties needs exactly %d bytes, a byte array of size %d is provided",
363                propertyCount, length);
364        return false;
365    }
366    for (int i = 0; i < propertyCount; i++) {
367        Property currentProperty = static_cast<Property>(i);
368        switch (currentProperty) {
369        case Property::Rotate_Property:
370            outProperties[i] = mRotate;
371            break;
372        case Property::PivotX_Property:
373            outProperties[i] = mPivotX;
374            break;
375        case Property::PivotY_Property:
376            outProperties[i] = mPivotY;
377            break;
378        case Property::ScaleX_Property:
379            outProperties[i] = mScaleX;
380            break;
381        case Property::ScaleY_Property:
382            outProperties[i] = mScaleY;
383            break;
384        case Property::TranslateX_Property:
385            outProperties[i] = mTranslateX;
386            break;
387        case Property::TranslateY_Property:
388            outProperties[i] = mTranslateY;
389            break;
390        default:
391            LOG_ALWAYS_FATAL("Invalid input index: %d", i);
392            return false;
393        }
394    }
395    return true;
396}
397
398void Tree::draw(Canvas* outCanvas, SkColorFilter* colorFilter,
399        const SkRect& bounds, bool needsMirroring, bool canReuseCache) {
400    // The imageView can scale the canvas in different ways, in order to
401    // avoid blurry scaling, we have to draw into a bitmap with exact pixel
402    // size first. This bitmap size is determined by the bounds and the
403    // canvas scale.
404    outCanvas->getMatrix(&mCanvasMatrix);
405    mBounds = bounds;
406    float canvasScaleX = 1.0f;
407    float canvasScaleY = 1.0f;
408    if (mCanvasMatrix.getSkewX() == 0 && mCanvasMatrix.getSkewY() == 0) {
409        // Only use the scale value when there's no skew or rotation in the canvas matrix.
410        // TODO: Add a cts test for drawing VD on a canvas with negative scaling factors.
411        canvasScaleX = fabs(mCanvasMatrix.getScaleX());
412        canvasScaleY = fabs(mCanvasMatrix.getScaleY());
413    }
414    int scaledWidth = (int) (mBounds.width() * canvasScaleX);
415    int scaledHeight = (int) (mBounds.height() * canvasScaleY);
416    scaledWidth = std::min(Tree::MAX_CACHED_BITMAP_SIZE, scaledWidth);
417    scaledHeight = std::min(Tree::MAX_CACHED_BITMAP_SIZE, scaledHeight);
418
419    if (scaledWidth <= 0 || scaledHeight <= 0) {
420        return;
421    }
422
423    int saveCount = outCanvas->save(SkCanvas::SaveFlags::kMatrixClip_SaveFlag);
424    outCanvas->translate(mBounds.fLeft, mBounds.fTop);
425
426    // Handle RTL mirroring.
427    if (needsMirroring) {
428        outCanvas->translate(mBounds.width(), 0);
429        outCanvas->scale(-1.0f, 1.0f);
430    }
431
432    // At this point, canvas has been translated to the right position.
433    // And we use this bound for the destination rect for the drawBitmap, so
434    // we offset to (0, 0);
435    mBounds.offsetTo(0, 0);
436
437    createCachedBitmapIfNeeded(scaledWidth, scaledHeight);
438    if (!mAllowCaching) {
439        updateCachedBitmap(scaledWidth, scaledHeight);
440    } else {
441        if (!canReuseCache || mCacheDirty) {
442            updateCachedBitmap(scaledWidth, scaledHeight);
443        }
444    }
445    drawCachedBitmapWithRootAlpha(outCanvas, colorFilter, mBounds);
446
447    outCanvas->restoreToCount(saveCount);
448}
449
450void Tree::drawCachedBitmapWithRootAlpha(Canvas* outCanvas, SkColorFilter* filter,
451        const SkRect& originalBounds) {
452    SkPaint* paint;
453    if (mRootAlpha == 1.0f && filter == NULL) {
454        paint = NULL;
455    } else {
456        mPaint.setFilterQuality(kLow_SkFilterQuality);
457        mPaint.setAlpha(mRootAlpha * 255);
458        mPaint.setColorFilter(filter);
459        paint = &mPaint;
460    }
461    outCanvas->drawBitmap(mCachedBitmap, 0, 0, mCachedBitmap.width(), mCachedBitmap.height(),
462            originalBounds.fLeft, originalBounds.fTop, originalBounds.fRight,
463            originalBounds.fBottom, paint);
464}
465
466void Tree::updateCachedBitmap(int width, int height) {
467    mCachedBitmap.eraseColor(SK_ColorTRANSPARENT);
468    SkCanvas outCanvas(mCachedBitmap);
469    float scaleX = width / mViewportWidth;
470    float scaleY = height / mViewportHeight;
471    mRootNode->draw(&outCanvas, SkMatrix::I(), scaleX, scaleY);
472    mCacheDirty = false;
473}
474
475void Tree::createCachedBitmapIfNeeded(int width, int height) {
476    if (!canReuseBitmap(width, height)) {
477        SkImageInfo info = SkImageInfo::Make(width, height,
478                kN32_SkColorType, kPremul_SkAlphaType);
479        mCachedBitmap.setInfo(info);
480        // TODO: Count the bitmap cache against app's java heap
481        mCachedBitmap.allocPixels(info);
482        mCacheDirty = true;
483    }
484}
485
486bool Tree::canReuseBitmap(int width, int height) {
487    return width == mCachedBitmap.width() && height == mCachedBitmap.height();
488}
489
490}; // namespace VectorDrawable
491
492}; // namespace uirenderer
493}; // namespace android
494