VectorDrawable.cpp revision cc29a5dde1ef0a3cf0fcec10eb9d37d9e8fa3afb
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 "SkColorFilter.h"
21#include "SkImageInfo.h"
22#include "SkShader.h"
23#include <utils/Log.h>
24#include "utils/Macros.h"
25#include "utils/VectorDrawableUtils.h"
26
27#include <math.h>
28#include <string.h>
29
30namespace android {
31namespace uirenderer {
32namespace VectorDrawable {
33
34const int Tree::MAX_CACHED_BITMAP_SIZE = 2048;
35
36void Path::dump() {
37    ALOGD("Path: %s has %zu points", mName.c_str(), mProperties.getData().points.size());
38}
39
40// Called from UI thread during the initial setup/theme change.
41Path::Path(const char* pathStr, size_t strLength) {
42    PathParser::ParseResult result;
43    Data data;
44    PathParser::getPathDataFromAsciiString(&data, &result, pathStr, strLength);
45    mStagingProperties.setData(data);
46}
47
48Path::Path(const Path& path) : Node(path) {
49    mStagingProperties.syncProperties(path.mStagingProperties);
50}
51
52const SkPath& Path::getUpdatedPath(bool useStagingData, SkPath* tempStagingPath) {
53    if (useStagingData) {
54        tempStagingPath->reset();
55        VectorDrawableUtils::verbsToPath(tempStagingPath, mStagingProperties.getData());
56        return *tempStagingPath;
57    } else {
58        if (mSkPathDirty) {
59            mSkPath.reset();
60            VectorDrawableUtils::verbsToPath(&mSkPath, mProperties.getData());
61            mSkPathDirty = false;
62        }
63        return mSkPath;
64    }
65}
66
67void Path::syncProperties() {
68    if (mStagingPropertiesDirty) {
69        mProperties.syncProperties(mStagingProperties);
70    } else {
71        mStagingProperties.syncProperties(mProperties);
72    }
73    mStagingPropertiesDirty = false;
74}
75
76FullPath::FullPath(const FullPath& path) : Path(path) {
77    mStagingProperties.syncProperties(path.mStagingProperties);
78}
79
80static void applyTrim(SkPath* outPath, const SkPath& inPath, float trimPathStart, float trimPathEnd,
81        float trimPathOffset) {
82    if (trimPathStart == 0.0f && trimPathEnd == 1.0f) {
83        *outPath = inPath;
84        return;
85    }
86    outPath->reset();
87    if (trimPathStart == trimPathEnd) {
88        // Trimmed path should be empty.
89        return;
90    }
91    SkPathMeasure measure(inPath, false);
92    float len = SkScalarToFloat(measure.getLength());
93    float start = len * fmod((trimPathStart + trimPathOffset), 1.0f);
94    float end = len * fmod((trimPathEnd + trimPathOffset), 1.0f);
95
96    if (start > end) {
97        measure.getSegment(start, len, outPath, true);
98        if (end > 0) {
99            measure.getSegment(0, end, outPath, true);
100        }
101    } else {
102        measure.getSegment(start, end, outPath, true);
103    }
104}
105
106const SkPath& FullPath::getUpdatedPath(bool useStagingData, SkPath* tempStagingPath) {
107    if (!useStagingData && !mSkPathDirty && !mProperties.mTrimDirty) {
108        return mTrimmedSkPath;
109    }
110    Path::getUpdatedPath(useStagingData, tempStagingPath);
111    SkPath *outPath;
112    if (useStagingData) {
113        SkPath inPath = *tempStagingPath;
114        applyTrim(tempStagingPath, inPath, mStagingProperties.getTrimPathStart(),
115                mStagingProperties.getTrimPathEnd(), mStagingProperties.getTrimPathOffset());
116        outPath = tempStagingPath;
117    } else {
118        if (mProperties.getTrimPathStart() != 0.0f || mProperties.getTrimPathEnd() != 1.0f) {
119            mProperties.mTrimDirty = false;
120            applyTrim(&mTrimmedSkPath, mSkPath, mProperties.getTrimPathStart(),
121                    mProperties.getTrimPathEnd(), mProperties.getTrimPathOffset());
122            outPath = &mTrimmedSkPath;
123        } else {
124            outPath = &mSkPath;
125        }
126    }
127    const FullPathProperties& properties = useStagingData ? mStagingProperties : mProperties;
128    bool setFillPath = properties.getFillGradient() != nullptr
129            || properties.getFillColor() != SK_ColorTRANSPARENT;
130    if (setFillPath) {
131        SkPath::FillType ft = static_cast<SkPath::FillType>(properties.getFillType());
132        outPath->setFillType(ft);
133    }
134    return *outPath;
135}
136
137void FullPath::dump() {
138    Path::dump();
139    ALOGD("stroke width, color, alpha: %f, %d, %f, fill color, alpha: %d, %f",
140            mProperties.getStrokeWidth(), mProperties.getStrokeColor(), mProperties.getStrokeAlpha(),
141            mProperties.getFillColor(), mProperties.getFillAlpha());
142}
143
144
145inline SkColor applyAlpha(SkColor color, float alpha) {
146    int alphaBytes = SkColorGetA(color);
147    return SkColorSetA(color, alphaBytes * alpha);
148}
149
150void FullPath::draw(SkCanvas* outCanvas, bool useStagingData) {
151    const FullPathProperties& properties = useStagingData ? mStagingProperties : mProperties;
152    SkPath tempStagingPath;
153    const SkPath& renderPath = getUpdatedPath(useStagingData, &tempStagingPath);
154
155    // Draw path's fill, if fill color or gradient is valid
156    bool needsFill = false;
157    SkPaint paint;
158    if (properties.getFillGradient() != nullptr) {
159        paint.setColor(applyAlpha(SK_ColorBLACK, properties.getFillAlpha()));
160        paint.setShader(sk_sp<SkShader>(SkSafeRef(properties.getFillGradient())));
161        needsFill = true;
162    } else if (properties.getFillColor() != SK_ColorTRANSPARENT) {
163        paint.setColor(applyAlpha(properties.getFillColor(), properties.getFillAlpha()));
164        needsFill = true;
165    }
166
167    if (needsFill) {
168        paint.setStyle(SkPaint::Style::kFill_Style);
169        paint.setAntiAlias(true);
170        outCanvas->drawPath(renderPath, paint);
171    }
172
173    // Draw path's stroke, if stroke color or Gradient is valid
174    bool needsStroke = false;
175    if (properties.getStrokeGradient() != nullptr) {
176        paint.setColor(applyAlpha(SK_ColorBLACK, properties.getStrokeAlpha()));
177        paint.setShader(sk_sp<SkShader>(SkSafeRef(properties.getStrokeGradient())));
178        needsStroke = true;
179    } else if (properties.getStrokeColor() != SK_ColorTRANSPARENT) {
180        paint.setColor(applyAlpha(properties.getStrokeColor(), properties.getStrokeAlpha()));
181        needsStroke = true;
182    }
183    if (needsStroke) {
184        paint.setStyle(SkPaint::Style::kStroke_Style);
185        paint.setAntiAlias(true);
186        paint.setStrokeJoin(SkPaint::Join(properties.getStrokeLineJoin()));
187        paint.setStrokeCap(SkPaint::Cap(properties.getStrokeLineCap()));
188        paint.setStrokeMiter(properties.getStrokeMiterLimit());
189        paint.setStrokeWidth(properties.getStrokeWidth());
190        outCanvas->drawPath(renderPath, paint);
191    }
192}
193
194void FullPath::syncProperties() {
195    Path::syncProperties();
196
197    if (mStagingPropertiesDirty) {
198        mProperties.syncProperties(mStagingProperties);
199    } else {
200        // Update staging property with property values from animation.
201        mStagingProperties.syncProperties(mProperties);
202    }
203    mStagingPropertiesDirty = false;
204}
205
206REQUIRE_COMPATIBLE_LAYOUT(FullPath::FullPathProperties::PrimitiveFields);
207
208static_assert(sizeof(float) == sizeof(int32_t), "float is not the same size as int32_t");
209static_assert(sizeof(SkColor) == sizeof(int32_t), "SkColor is not the same size as int32_t");
210
211bool FullPath::FullPathProperties::copyProperties(int8_t* outProperties, int length) const {
212    int propertyDataSize = sizeof(FullPathProperties::PrimitiveFields);
213    if (length != propertyDataSize) {
214        LOG_ALWAYS_FATAL("Properties needs exactly %d bytes, a byte array of size %d is provided",
215                propertyDataSize, length);
216        return false;
217    }
218
219    PrimitiveFields* out = reinterpret_cast<PrimitiveFields*>(outProperties);
220    *out = mPrimitiveFields;
221    return true;
222}
223
224void FullPath::FullPathProperties::setColorPropertyValue(int propertyId, int32_t value) {
225    Property currentProperty = static_cast<Property>(propertyId);
226    if (currentProperty == Property::strokeColor) {
227        setStrokeColor(value);
228    } else if (currentProperty == Property::fillColor) {
229        setFillColor(value);
230    } else {
231        LOG_ALWAYS_FATAL("Error setting color property on FullPath: No valid property"
232                " with id: %d", propertyId);
233    }
234}
235
236void FullPath::FullPathProperties::setPropertyValue(int propertyId, float value) {
237    Property property = static_cast<Property>(propertyId);
238    switch (property) {
239    case Property::strokeWidth:
240        setStrokeWidth(value);
241        break;
242    case Property::strokeAlpha:
243        setStrokeAlpha(value);
244        break;
245    case Property::fillAlpha:
246        setFillAlpha(value);
247        break;
248    case Property::trimPathStart:
249        setTrimPathStart(value);
250        break;
251    case Property::trimPathEnd:
252        setTrimPathEnd(value);
253        break;
254    case Property::trimPathOffset:
255        setTrimPathOffset(value);
256        break;
257    default:
258        LOG_ALWAYS_FATAL("Invalid property id: %d for animation", propertyId);
259        break;
260    }
261}
262
263void ClipPath::draw(SkCanvas* outCanvas, bool useStagingData) {
264    SkPath tempStagingPath;
265    outCanvas->clipPath(getUpdatedPath(useStagingData, &tempStagingPath));
266}
267
268Group::Group(const Group& group) : Node(group) {
269    mStagingProperties.syncProperties(group.mStagingProperties);
270}
271
272void Group::draw(SkCanvas* outCanvas, bool useStagingData) {
273    // Save the current clip and matrix information, which is local to this group.
274    SkAutoCanvasRestore saver(outCanvas, true);
275    // apply the current group's matrix to the canvas
276    SkMatrix stackedMatrix;
277    const GroupProperties& prop = useStagingData ? mStagingProperties : mProperties;
278    getLocalMatrix(&stackedMatrix, prop);
279    outCanvas->concat(stackedMatrix);
280    // Draw the group tree in the same order as the XML file.
281    for (auto& child : mChildren) {
282        child->draw(outCanvas, useStagingData);
283    }
284    // Restore the previous clip and matrix information.
285}
286
287void Group::dump() {
288    ALOGD("Group %s has %zu children: ", mName.c_str(), mChildren.size());
289    ALOGD("Group translateX, Y : %f, %f, scaleX, Y: %f, %f", mProperties.getTranslateX(),
290            mProperties.getTranslateY(), mProperties.getScaleX(), mProperties.getScaleY());
291    for (size_t i = 0; i < mChildren.size(); i++) {
292        mChildren[i]->dump();
293    }
294}
295
296void Group::syncProperties() {
297    // Copy over the dirty staging properties
298    if (mStagingPropertiesDirty) {
299        mProperties.syncProperties(mStagingProperties);
300    } else {
301        mStagingProperties.syncProperties(mProperties);
302    }
303    mStagingPropertiesDirty = false;
304    for (auto& child : mChildren) {
305        child->syncProperties();
306    }
307}
308
309void Group::getLocalMatrix(SkMatrix* outMatrix, const GroupProperties& properties) {
310    outMatrix->reset();
311    // TODO: use rotate(mRotate, mPivotX, mPivotY) and scale with pivot point, instead of
312    // translating to pivot for rotating and scaling, then translating back.
313    outMatrix->postTranslate(-properties.getPivotX(), -properties.getPivotY());
314    outMatrix->postScale(properties.getScaleX(), properties.getScaleY());
315    outMatrix->postRotate(properties.getRotation(), 0, 0);
316    outMatrix->postTranslate(properties.getTranslateX() + properties.getPivotX(),
317            properties.getTranslateY() + properties.getPivotY());
318}
319
320void Group::addChild(Node* child) {
321    mChildren.emplace_back(child);
322    if (mPropertyChangedListener != nullptr) {
323        child->setPropertyChangedListener(mPropertyChangedListener);
324    }
325}
326
327bool Group::GroupProperties::copyProperties(float* outProperties, int length) const {
328    int propertyCount = static_cast<int>(Property::count);
329    if (length != propertyCount) {
330        LOG_ALWAYS_FATAL("Properties needs exactly %d bytes, a byte array of size %d is provided",
331                propertyCount, length);
332        return false;
333    }
334
335    PrimitiveFields* out = reinterpret_cast<PrimitiveFields*>(outProperties);
336    *out = mPrimitiveFields;
337    return true;
338}
339
340// TODO: Consider animating the properties as float pointers
341// Called on render thread
342float Group::GroupProperties::getPropertyValue(int propertyId) const {
343    Property currentProperty = static_cast<Property>(propertyId);
344    switch (currentProperty) {
345    case Property::rotate:
346        return getRotation();
347    case Property::pivotX:
348        return getPivotX();
349    case Property::pivotY:
350        return getPivotY();
351    case Property::scaleX:
352        return getScaleX();
353    case Property::scaleY:
354        return getScaleY();
355    case Property::translateX:
356        return getTranslateX();
357    case Property::translateY:
358        return getTranslateY();
359    default:
360        LOG_ALWAYS_FATAL("Invalid property index: %d", propertyId);
361        return 0;
362    }
363}
364
365// Called on render thread
366void Group::GroupProperties::setPropertyValue(int propertyId, float value) {
367    Property currentProperty = static_cast<Property>(propertyId);
368    switch (currentProperty) {
369    case Property::rotate:
370        setRotation(value);
371        break;
372    case Property::pivotX:
373        setPivotX(value);
374        break;
375    case Property::pivotY:
376        setPivotY(value);
377        break;
378    case Property::scaleX:
379        setScaleX(value);
380        break;
381    case Property::scaleY:
382        setScaleY(value);
383        break;
384    case Property::translateX:
385        setTranslateX(value);
386        break;
387    case Property::translateY:
388        setTranslateY(value);
389        break;
390    default:
391        LOG_ALWAYS_FATAL("Invalid property index: %d", propertyId);
392    }
393}
394
395bool Group::isValidProperty(int propertyId) {
396    return GroupProperties::isValidProperty(propertyId);
397}
398
399bool Group::GroupProperties::isValidProperty(int propertyId) {
400    return propertyId >= 0 && propertyId < static_cast<int>(Property::count);
401}
402
403int Tree::draw(Canvas* outCanvas, SkColorFilter* colorFilter,
404        const SkRect& bounds, bool needsMirroring, bool canReuseCache) {
405    // The imageView can scale the canvas in different ways, in order to
406    // avoid blurry scaling, we have to draw into a bitmap with exact pixel
407    // size first. This bitmap size is determined by the bounds and the
408    // canvas scale.
409    SkMatrix canvasMatrix;
410    outCanvas->getMatrix(&canvasMatrix);
411    float canvasScaleX = 1.0f;
412    float canvasScaleY = 1.0f;
413    if (canvasMatrix.getSkewX() == 0 && canvasMatrix.getSkewY() == 0) {
414        // Only use the scale value when there's no skew or rotation in the canvas matrix.
415        // TODO: Add a cts test for drawing VD on a canvas with negative scaling factors.
416        canvasScaleX = fabs(canvasMatrix.getScaleX());
417        canvasScaleY = fabs(canvasMatrix.getScaleY());
418    }
419    int scaledWidth = (int) (bounds.width() * canvasScaleX);
420    int scaledHeight = (int) (bounds.height() * canvasScaleY);
421    scaledWidth = std::min(Tree::MAX_CACHED_BITMAP_SIZE, scaledWidth);
422    scaledHeight = std::min(Tree::MAX_CACHED_BITMAP_SIZE, scaledHeight);
423
424    if (scaledWidth <= 0 || scaledHeight <= 0) {
425        return 0;
426    }
427
428    mStagingProperties.setScaledSize(scaledWidth, scaledHeight);
429    int saveCount = outCanvas->save(SaveFlags::MatrixClip);
430    outCanvas->translate(bounds.fLeft, bounds.fTop);
431
432    // Handle RTL mirroring.
433    if (needsMirroring) {
434        outCanvas->translate(bounds.width(), 0);
435        outCanvas->scale(-1.0f, 1.0f);
436    }
437    mStagingProperties.setColorFilter(colorFilter);
438
439    // At this point, canvas has been translated to the right position.
440    // And we use this bound for the destination rect for the drawBitmap, so
441    // we offset to (0, 0);
442    SkRect tmpBounds = bounds;
443    tmpBounds.offsetTo(0, 0);
444    mStagingProperties.setBounds(tmpBounds);
445    outCanvas->drawVectorDrawable(this);
446    outCanvas->restoreToCount(saveCount);
447    return scaledWidth * scaledHeight;
448}
449
450void Tree::drawStaging(Canvas* outCanvas) {
451    bool redrawNeeded = allocateBitmapIfNeeded(mStagingCache,
452            mStagingProperties.getScaledWidth(), mStagingProperties.getScaledHeight());
453    // draw bitmap cache
454    if (redrawNeeded || mStagingCache.dirty) {
455        updateBitmapCache(*mStagingCache.bitmap, true);
456        mStagingCache.dirty = false;
457    }
458
459    SkPaint tmpPaint;
460    SkPaint* paint = updatePaint(&tmpPaint, &mStagingProperties);
461    outCanvas->drawBitmap(*mStagingCache.bitmap, 0, 0,
462            mStagingCache.bitmap->width(), mStagingCache.bitmap->height(),
463            mStagingProperties.getBounds().left(), mStagingProperties.getBounds().top(),
464            mStagingProperties.getBounds().right(), mStagingProperties.getBounds().bottom(), paint);
465}
466
467SkPaint* Tree::getPaint() {
468    return updatePaint(&mPaint, &mProperties);
469}
470
471// Update the given paint with alpha and color filter. Return nullptr if no color filter is
472// specified and root alpha is 1. Otherwise, return updated paint.
473SkPaint* Tree::updatePaint(SkPaint* outPaint, TreeProperties* prop) {
474    if (prop->getRootAlpha() == 1.0f && prop->getColorFilter() == nullptr) {
475        return nullptr;
476    } else {
477        outPaint->setColorFilter(sk_ref_sp(prop->getColorFilter()));
478        outPaint->setFilterQuality(kLow_SkFilterQuality);
479        outPaint->setAlpha(prop->getRootAlpha() * 255);
480        return outPaint;
481    }
482}
483
484Bitmap& Tree::getBitmapUpdateIfDirty() {
485    bool redrawNeeded = allocateBitmapIfNeeded(mCache, mProperties.getScaledWidth(),
486            mProperties.getScaledHeight());
487    if (redrawNeeded || mCache.dirty) {
488        updateBitmapCache(*mCache.bitmap, false);
489        mCache.dirty = false;
490    }
491    return *mCache.bitmap;
492}
493
494void Tree::updateBitmapCache(Bitmap& bitmap, bool useStagingData) {
495    SkBitmap outCache;
496    bitmap.getSkBitmap(&outCache);
497    outCache.eraseColor(SK_ColorTRANSPARENT);
498    SkCanvas outCanvas(outCache);
499    float viewportWidth = useStagingData ?
500            mStagingProperties.getViewportWidth() : mProperties.getViewportWidth();
501    float viewportHeight = useStagingData ?
502            mStagingProperties.getViewportHeight() : mProperties.getViewportHeight();
503    float scaleX = outCache.width() / viewportWidth;
504    float scaleY = outCache.height() / viewportHeight;
505    outCanvas.scale(scaleX, scaleY);
506    mRootNode->draw(&outCanvas, useStagingData);
507}
508
509bool Tree::allocateBitmapIfNeeded(Cache& cache, int width, int height) {
510    if (!canReuseBitmap(cache.bitmap.get(), width, height)) {
511#ifndef ANDROID_ENABLE_LINEAR_BLENDING
512        sk_sp<SkColorSpace> colorSpace = nullptr;
513#else
514        sk_sp<SkColorSpace> colorSpace = SkColorSpace::MakeSRGB();
515#endif
516        SkImageInfo info = SkImageInfo::MakeN32(width, height, kPremul_SkAlphaType, colorSpace);
517        cache.bitmap = Bitmap::allocateHeapBitmap(info);
518        return true;
519    }
520    return false;
521}
522
523bool Tree::canReuseBitmap(Bitmap* bitmap, int width, int height) {
524    return bitmap && width <= bitmap->width() && height <= bitmap->height();
525}
526
527void Tree::onPropertyChanged(TreeProperties* prop) {
528    if (prop == &mStagingProperties) {
529        mStagingCache.dirty = true;
530    } else {
531        mCache.dirty = true;
532    }
533}
534
535}; // namespace VectorDrawable
536
537}; // namespace uirenderer
538}; // namespace android
539