• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 <utils/Log.h>
20 #include "PathParser.h"
21 #include "SkColorFilter.h"
22 #include "SkImageInfo.h"
23 #include "SkShader.h"
24 #include "utils/Macros.h"
25 #include "utils/TraceUtils.h"
26 #include "utils/VectorDrawableUtils.h"
27 
28 #include <math.h>
29 #include <string.h>
30 
31 namespace android {
32 namespace uirenderer {
33 namespace VectorDrawable {
34 
35 const int Tree::MAX_CACHED_BITMAP_SIZE = 2048;
36 
dump()37 void Path::dump() {
38     ALOGD("Path: %s has %zu points", mName.c_str(), mProperties.getData().points.size());
39 }
40 
41 // Called from UI thread during the initial setup/theme change.
Path(const char * pathStr,size_t strLength)42 Path::Path(const char* pathStr, size_t strLength) {
43     PathParser::ParseResult result;
44     Data data;
45     PathParser::getPathDataFromAsciiString(&data, &result, pathStr, strLength);
46     mStagingProperties.setData(data);
47 }
48 
Path(const Path & path)49 Path::Path(const Path& path) : Node(path) {
50     mStagingProperties.syncProperties(path.mStagingProperties);
51 }
52 
getUpdatedPath(bool useStagingData,SkPath * tempStagingPath)53 const SkPath& Path::getUpdatedPath(bool useStagingData, SkPath* tempStagingPath) {
54     if (useStagingData) {
55         tempStagingPath->reset();
56         VectorDrawableUtils::verbsToPath(tempStagingPath, mStagingProperties.getData());
57         return *tempStagingPath;
58     } else {
59         if (mSkPathDirty) {
60             mSkPath.reset();
61             VectorDrawableUtils::verbsToPath(&mSkPath, mProperties.getData());
62             mSkPathDirty = false;
63         }
64         return mSkPath;
65     }
66 }
67 
syncProperties()68 void Path::syncProperties() {
69     if (mStagingPropertiesDirty) {
70         mProperties.syncProperties(mStagingProperties);
71     } else {
72         mStagingProperties.syncProperties(mProperties);
73     }
74     mStagingPropertiesDirty = false;
75 }
76 
FullPath(const FullPath & path)77 FullPath::FullPath(const FullPath& path) : Path(path) {
78     mStagingProperties.syncProperties(path.mStagingProperties);
79 }
80 
applyTrim(SkPath * outPath,const SkPath & inPath,float trimPathStart,float trimPathEnd,float trimPathOffset)81 static void applyTrim(SkPath* outPath, const SkPath& inPath, float trimPathStart, float trimPathEnd,
82                       float trimPathOffset) {
83     if (trimPathStart == 0.0f && trimPathEnd == 1.0f) {
84         *outPath = inPath;
85         return;
86     }
87     outPath->reset();
88     if (trimPathStart == trimPathEnd) {
89         // Trimmed path should be empty.
90         return;
91     }
92     SkPathMeasure measure(inPath, false);
93     float len = SkScalarToFloat(measure.getLength());
94     float start = len * fmod((trimPathStart + trimPathOffset), 1.0f);
95     float end = len * fmod((trimPathEnd + trimPathOffset), 1.0f);
96 
97     if (start > end) {
98         measure.getSegment(start, len, outPath, true);
99         if (end > 0) {
100             measure.getSegment(0, end, outPath, true);
101         }
102     } else {
103         measure.getSegment(start, end, outPath, true);
104     }
105 }
106 
getUpdatedPath(bool useStagingData,SkPath * tempStagingPath)107 const SkPath& FullPath::getUpdatedPath(bool useStagingData, SkPath* tempStagingPath) {
108     if (!useStagingData && !mSkPathDirty && !mProperties.mTrimDirty) {
109         return mTrimmedSkPath;
110     }
111     Path::getUpdatedPath(useStagingData, tempStagingPath);
112     SkPath* outPath;
113     if (useStagingData) {
114         SkPath inPath = *tempStagingPath;
115         applyTrim(tempStagingPath, inPath, mStagingProperties.getTrimPathStart(),
116                   mStagingProperties.getTrimPathEnd(), mStagingProperties.getTrimPathOffset());
117         outPath = tempStagingPath;
118     } else {
119         if (mProperties.getTrimPathStart() != 0.0f || mProperties.getTrimPathEnd() != 1.0f) {
120             mProperties.mTrimDirty = false;
121             applyTrim(&mTrimmedSkPath, mSkPath, mProperties.getTrimPathStart(),
122                       mProperties.getTrimPathEnd(), mProperties.getTrimPathOffset());
123             outPath = &mTrimmedSkPath;
124         } else {
125             outPath = &mSkPath;
126         }
127     }
128     const FullPathProperties& properties = useStagingData ? mStagingProperties : mProperties;
129     bool setFillPath = properties.getFillGradient() != nullptr ||
130                        properties.getFillColor() != SK_ColorTRANSPARENT;
131     if (setFillPath) {
132         SkPath::FillType ft = static_cast<SkPath::FillType>(properties.getFillType());
133         outPath->setFillType(ft);
134     }
135     return *outPath;
136 }
137 
dump()138 void FullPath::dump() {
139     Path::dump();
140     ALOGD("stroke width, color, alpha: %f, %d, %f, fill color, alpha: %d, %f",
141           mProperties.getStrokeWidth(), mProperties.getStrokeColor(), mProperties.getStrokeAlpha(),
142           mProperties.getFillColor(), mProperties.getFillAlpha());
143 }
144 
applyAlpha(SkColor color,float alpha)145 inline SkColor applyAlpha(SkColor color, float alpha) {
146     int alphaBytes = SkColorGetA(color);
147     return SkColorSetA(color, alphaBytes * alpha);
148 }
149 
draw(SkCanvas * outCanvas,bool useStagingData)150 void 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(mAntiAlias);
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(mAntiAlias);
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 
syncProperties()194 void 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 
206 REQUIRE_COMPATIBLE_LAYOUT(FullPath::FullPathProperties::PrimitiveFields);
207 
208 static_assert(sizeof(float) == sizeof(int32_t), "float is not the same size as int32_t");
209 static_assert(sizeof(SkColor) == sizeof(int32_t), "SkColor is not the same size as int32_t");
210 
copyProperties(int8_t * outProperties,int length) const211 bool 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 
setColorPropertyValue(int propertyId,int32_t value)224 void 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(
232                 "Error setting color property on FullPath: No valid property"
233                 " with id: %d",
234                 propertyId);
235     }
236 }
237 
setPropertyValue(int propertyId,float value)238 void FullPath::FullPathProperties::setPropertyValue(int propertyId, float value) {
239     Property property = static_cast<Property>(propertyId);
240     switch (property) {
241         case Property::strokeWidth:
242             setStrokeWidth(value);
243             break;
244         case Property::strokeAlpha:
245             setStrokeAlpha(value);
246             break;
247         case Property::fillAlpha:
248             setFillAlpha(value);
249             break;
250         case Property::trimPathStart:
251             setTrimPathStart(value);
252             break;
253         case Property::trimPathEnd:
254             setTrimPathEnd(value);
255             break;
256         case Property::trimPathOffset:
257             setTrimPathOffset(value);
258             break;
259         default:
260             LOG_ALWAYS_FATAL("Invalid property id: %d for animation", propertyId);
261             break;
262     }
263 }
264 
draw(SkCanvas * outCanvas,bool useStagingData)265 void ClipPath::draw(SkCanvas* outCanvas, bool useStagingData) {
266     SkPath tempStagingPath;
267     outCanvas->clipPath(getUpdatedPath(useStagingData, &tempStagingPath));
268 }
269 
Group(const Group & group)270 Group::Group(const Group& group) : Node(group) {
271     mStagingProperties.syncProperties(group.mStagingProperties);
272 }
273 
draw(SkCanvas * outCanvas,bool useStagingData)274 void Group::draw(SkCanvas* outCanvas, bool useStagingData) {
275     // Save the current clip and matrix information, which is local to this group.
276     SkAutoCanvasRestore saver(outCanvas, true);
277     // apply the current group's matrix to the canvas
278     SkMatrix stackedMatrix;
279     const GroupProperties& prop = useStagingData ? mStagingProperties : mProperties;
280     getLocalMatrix(&stackedMatrix, prop);
281     outCanvas->concat(stackedMatrix);
282     // Draw the group tree in the same order as the XML file.
283     for (auto& child : mChildren) {
284         child->draw(outCanvas, useStagingData);
285     }
286     // Restore the previous clip and matrix information.
287 }
288 
dump()289 void Group::dump() {
290     ALOGD("Group %s has %zu children: ", mName.c_str(), mChildren.size());
291     ALOGD("Group translateX, Y : %f, %f, scaleX, Y: %f, %f", mProperties.getTranslateX(),
292           mProperties.getTranslateY(), mProperties.getScaleX(), mProperties.getScaleY());
293     for (size_t i = 0; i < mChildren.size(); i++) {
294         mChildren[i]->dump();
295     }
296 }
297 
syncProperties()298 void Group::syncProperties() {
299     // Copy over the dirty staging properties
300     if (mStagingPropertiesDirty) {
301         mProperties.syncProperties(mStagingProperties);
302     } else {
303         mStagingProperties.syncProperties(mProperties);
304     }
305     mStagingPropertiesDirty = false;
306     for (auto& child : mChildren) {
307         child->syncProperties();
308     }
309 }
310 
getLocalMatrix(SkMatrix * outMatrix,const GroupProperties & properties)311 void Group::getLocalMatrix(SkMatrix* outMatrix, const GroupProperties& properties) {
312     outMatrix->reset();
313     // TODO: use rotate(mRotate, mPivotX, mPivotY) and scale with pivot point, instead of
314     // translating to pivot for rotating and scaling, then translating back.
315     outMatrix->postTranslate(-properties.getPivotX(), -properties.getPivotY());
316     outMatrix->postScale(properties.getScaleX(), properties.getScaleY());
317     outMatrix->postRotate(properties.getRotation(), 0, 0);
318     outMatrix->postTranslate(properties.getTranslateX() + properties.getPivotX(),
319                              properties.getTranslateY() + properties.getPivotY());
320 }
321 
addChild(Node * child)322 void Group::addChild(Node* child) {
323     mChildren.emplace_back(child);
324     if (mPropertyChangedListener != nullptr) {
325         child->setPropertyChangedListener(mPropertyChangedListener);
326     }
327 }
328 
copyProperties(float * outProperties,int length) const329 bool Group::GroupProperties::copyProperties(float* outProperties, int length) const {
330     int propertyCount = static_cast<int>(Property::count);
331     if (length != propertyCount) {
332         LOG_ALWAYS_FATAL("Properties needs exactly %d bytes, a byte array of size %d is provided",
333                          propertyCount, length);
334         return false;
335     }
336 
337     PrimitiveFields* out = reinterpret_cast<PrimitiveFields*>(outProperties);
338     *out = mPrimitiveFields;
339     return true;
340 }
341 
342 // TODO: Consider animating the properties as float pointers
343 // Called on render thread
getPropertyValue(int propertyId) const344 float Group::GroupProperties::getPropertyValue(int propertyId) const {
345     Property currentProperty = static_cast<Property>(propertyId);
346     switch (currentProperty) {
347         case Property::rotate:
348             return getRotation();
349         case Property::pivotX:
350             return getPivotX();
351         case Property::pivotY:
352             return getPivotY();
353         case Property::scaleX:
354             return getScaleX();
355         case Property::scaleY:
356             return getScaleY();
357         case Property::translateX:
358             return getTranslateX();
359         case Property::translateY:
360             return getTranslateY();
361         default:
362             LOG_ALWAYS_FATAL("Invalid property index: %d", propertyId);
363             return 0;
364     }
365 }
366 
367 // Called on render thread
setPropertyValue(int propertyId,float value)368 void Group::GroupProperties::setPropertyValue(int propertyId, float value) {
369     Property currentProperty = static_cast<Property>(propertyId);
370     switch (currentProperty) {
371         case Property::rotate:
372             setRotation(value);
373             break;
374         case Property::pivotX:
375             setPivotX(value);
376             break;
377         case Property::pivotY:
378             setPivotY(value);
379             break;
380         case Property::scaleX:
381             setScaleX(value);
382             break;
383         case Property::scaleY:
384             setScaleY(value);
385             break;
386         case Property::translateX:
387             setTranslateX(value);
388             break;
389         case Property::translateY:
390             setTranslateY(value);
391             break;
392         default:
393             LOG_ALWAYS_FATAL("Invalid property index: %d", propertyId);
394     }
395 }
396 
isValidProperty(int propertyId)397 bool Group::isValidProperty(int propertyId) {
398     return GroupProperties::isValidProperty(propertyId);
399 }
400 
isValidProperty(int propertyId)401 bool Group::GroupProperties::isValidProperty(int propertyId) {
402     return propertyId >= 0 && propertyId < static_cast<int>(Property::count);
403 }
404 
draw(Canvas * outCanvas,SkColorFilter * colorFilter,const SkRect & bounds,bool needsMirroring,bool canReuseCache)405 int Tree::draw(Canvas* outCanvas, SkColorFilter* colorFilter, const SkRect& bounds,
406                bool needsMirroring, bool canReuseCache) {
407     // The imageView can scale the canvas in different ways, in order to
408     // avoid blurry scaling, we have to draw into a bitmap with exact pixel
409     // size first. This bitmap size is determined by the bounds and the
410     // canvas scale.
411     SkMatrix canvasMatrix;
412     outCanvas->getMatrix(&canvasMatrix);
413     float canvasScaleX = 1.0f;
414     float canvasScaleY = 1.0f;
415     if (canvasMatrix.getSkewX() == 0 && canvasMatrix.getSkewY() == 0) {
416         // Only use the scale value when there's no skew or rotation in the canvas matrix.
417         // TODO: Add a cts test for drawing VD on a canvas with negative scaling factors.
418         canvasScaleX = fabs(canvasMatrix.getScaleX());
419         canvasScaleY = fabs(canvasMatrix.getScaleY());
420     }
421     int scaledWidth = (int)(bounds.width() * canvasScaleX);
422     int scaledHeight = (int)(bounds.height() * canvasScaleY);
423     scaledWidth = std::min(Tree::MAX_CACHED_BITMAP_SIZE, scaledWidth);
424     scaledHeight = std::min(Tree::MAX_CACHED_BITMAP_SIZE, scaledHeight);
425 
426     if (scaledWidth <= 0 || scaledHeight <= 0) {
427         return 0;
428     }
429 
430     mStagingProperties.setScaledSize(scaledWidth, scaledHeight);
431     int saveCount = outCanvas->save(SaveFlags::MatrixClip);
432     outCanvas->translate(bounds.fLeft, bounds.fTop);
433 
434     // Handle RTL mirroring.
435     if (needsMirroring) {
436         outCanvas->translate(bounds.width(), 0);
437         outCanvas->scale(-1.0f, 1.0f);
438     }
439     mStagingProperties.setColorFilter(colorFilter);
440 
441     // At this point, canvas has been translated to the right position.
442     // And we use this bound for the destination rect for the drawBitmap, so
443     // we offset to (0, 0);
444     SkRect tmpBounds = bounds;
445     tmpBounds.offsetTo(0, 0);
446     mStagingProperties.setBounds(tmpBounds);
447     outCanvas->drawVectorDrawable(this);
448     outCanvas->restoreToCount(saveCount);
449     return scaledWidth * scaledHeight;
450 }
451 
drawStaging(Canvas * outCanvas)452 void Tree::drawStaging(Canvas* outCanvas) {
453     bool redrawNeeded = allocateBitmapIfNeeded(mStagingCache, mStagingProperties.getScaledWidth(),
454                                                mStagingProperties.getScaledHeight());
455     // draw bitmap cache
456     if (redrawNeeded || mStagingCache.dirty) {
457         updateBitmapCache(*mStagingCache.bitmap, true);
458         mStagingCache.dirty = false;
459     }
460 
461     SkPaint paint;
462     getPaintFor(&paint, mStagingProperties);
463     outCanvas->drawBitmap(*mStagingCache.bitmap, 0, 0, mStagingCache.bitmap->width(),
464                           mStagingCache.bitmap->height(), mStagingProperties.getBounds().left(),
465                           mStagingProperties.getBounds().top(),
466                           mStagingProperties.getBounds().right(),
467                           mStagingProperties.getBounds().bottom(), &paint);
468 }
469 
getPaintFor(SkPaint * outPaint,const TreeProperties & prop) const470 void Tree::getPaintFor(SkPaint* outPaint, const TreeProperties &prop) const {
471     // HWUI always draws VD with bilinear filtering.
472     outPaint->setFilterQuality(kLow_SkFilterQuality);
473     if (prop.getColorFilter() != nullptr) {
474         outPaint->setColorFilter(sk_ref_sp(prop.getColorFilter()));
475     }
476     outPaint->setAlpha(prop.getRootAlpha() * 255);
477 }
478 
getBitmapUpdateIfDirty()479 Bitmap& Tree::getBitmapUpdateIfDirty() {
480     bool redrawNeeded = allocateBitmapIfNeeded(mCache, mProperties.getScaledWidth(),
481                                                mProperties.getScaledHeight());
482     if (redrawNeeded || mCache.dirty) {
483         updateBitmapCache(*mCache.bitmap, false);
484         mCache.dirty = false;
485     }
486     return *mCache.bitmap;
487 }
488 
updateCache(sp<skiapipeline::VectorDrawableAtlas> & atlas,GrContext * context)489 void Tree::updateCache(sp<skiapipeline::VectorDrawableAtlas>& atlas, GrContext* context) {
490     SkRect dst;
491     sk_sp<SkSurface> surface = mCache.getSurface(&dst);
492     bool canReuseSurface = surface && dst.width() >= mProperties.getScaledWidth() &&
493                            dst.height() >= mProperties.getScaledHeight();
494     if (!canReuseSurface) {
495         int scaledWidth = SkScalarCeilToInt(mProperties.getScaledWidth());
496         int scaledHeight = SkScalarCeilToInt(mProperties.getScaledHeight());
497         auto atlasEntry = atlas->requestNewEntry(scaledWidth, scaledHeight, context);
498         if (INVALID_ATLAS_KEY != atlasEntry.key) {
499             dst = atlasEntry.rect;
500             surface = atlasEntry.surface;
501             mCache.setAtlas(atlas, atlasEntry.key);
502         } else {
503             // don't draw, if we failed to allocate an offscreen buffer
504             mCache.clear();
505             surface.reset();
506         }
507     }
508     if (!canReuseSurface || mCache.dirty) {
509         if (surface) {
510             Bitmap& bitmap = getBitmapUpdateIfDirty();
511             SkBitmap skiaBitmap;
512             bitmap.getSkBitmap(&skiaBitmap);
513             surface->writePixels(skiaBitmap, dst.fLeft, dst.fTop);
514         }
515         mCache.dirty = false;
516     }
517 }
518 
setAtlas(sp<skiapipeline::VectorDrawableAtlas> newAtlas,skiapipeline::AtlasKey newAtlasKey)519 void Tree::Cache::setAtlas(sp<skiapipeline::VectorDrawableAtlas> newAtlas,
520                            skiapipeline::AtlasKey newAtlasKey) {
521     LOG_ALWAYS_FATAL_IF(newAtlasKey == INVALID_ATLAS_KEY);
522     clear();
523     mAtlas = newAtlas;
524     mAtlasKey = newAtlasKey;
525 }
526 
getSurface(SkRect * bounds)527 sk_sp<SkSurface> Tree::Cache::getSurface(SkRect* bounds) {
528     sk_sp<SkSurface> surface;
529     sp<skiapipeline::VectorDrawableAtlas> atlas = mAtlas.promote();
530     if (atlas.get() && mAtlasKey != INVALID_ATLAS_KEY) {
531         auto atlasEntry = atlas->getEntry(mAtlasKey);
532         *bounds = atlasEntry.rect;
533         surface = atlasEntry.surface;
534         mAtlasKey = atlasEntry.key;
535     }
536 
537     return surface;
538 }
539 
clear()540 void Tree::Cache::clear() {
541     sp<skiapipeline::VectorDrawableAtlas> lockAtlas = mAtlas.promote();
542     if (lockAtlas.get()) {
543         lockAtlas->releaseEntry(mAtlasKey);
544     }
545     mAtlas = nullptr;
546     mAtlasKey = INVALID_ATLAS_KEY;
547 }
548 
draw(SkCanvas * canvas,const SkRect & bounds,const SkPaint & inPaint)549 void Tree::draw(SkCanvas* canvas, const SkRect& bounds, const SkPaint& inPaint) {
550     if (canvas->quickReject(bounds)) {
551         // The RenderNode is on screen, but the AVD is not.
552         return;
553     }
554 
555     // Update the paint for any animatable properties
556     SkPaint paint = inPaint;
557     paint.setAlpha(mProperties.getRootAlpha() * 255);
558 
559     if (canvas->getGrContext() == nullptr) {
560         // Recording to picture, don't use the SkSurface which won't work off of renderthread.
561         Bitmap& bitmap = getBitmapUpdateIfDirty();
562         SkBitmap skiaBitmap;
563         bitmap.getSkBitmap(&skiaBitmap);
564 
565         int scaledWidth = SkScalarCeilToInt(mProperties.getScaledWidth());
566         int scaledHeight = SkScalarCeilToInt(mProperties.getScaledHeight());
567         canvas->drawBitmapRect(skiaBitmap, SkRect::MakeWH(scaledWidth, scaledHeight), bounds,
568                                &paint, SkCanvas::kFast_SrcRectConstraint);
569         return;
570     }
571 
572     SkRect src;
573     sk_sp<SkSurface> vdSurface = mCache.getSurface(&src);
574     if (vdSurface) {
575         canvas->drawImageRect(vdSurface->makeImageSnapshot().get(), src, bounds, &paint,
576                               SkCanvas::kFast_SrcRectConstraint);
577     } else {
578         // Handle the case when VectorDrawableAtlas has been destroyed, because of memory pressure.
579         // We render the VD into a temporary standalone buffer and mark the frame as dirty. Next
580         // frame will be cached into the atlas.
581         Bitmap& bitmap = getBitmapUpdateIfDirty();
582         SkBitmap skiaBitmap;
583         bitmap.getSkBitmap(&skiaBitmap);
584 
585         int scaledWidth = SkScalarCeilToInt(mProperties.getScaledWidth());
586         int scaledHeight = SkScalarCeilToInt(mProperties.getScaledHeight());
587         canvas->drawBitmapRect(skiaBitmap, SkRect::MakeWH(scaledWidth, scaledHeight), bounds,
588                                &paint, SkCanvas::kFast_SrcRectConstraint);
589         mCache.clear();
590         markDirty();
591     }
592 }
593 
updateBitmapCache(Bitmap & bitmap,bool useStagingData)594 void Tree::updateBitmapCache(Bitmap& bitmap, bool useStagingData) {
595     SkBitmap outCache;
596     bitmap.getSkBitmap(&outCache);
597     int cacheWidth = outCache.width();
598     int cacheHeight = outCache.height();
599     ATRACE_FORMAT("VectorDrawable repaint %dx%d", cacheWidth, cacheHeight);
600     outCache.eraseColor(SK_ColorTRANSPARENT);
601     SkCanvas outCanvas(outCache);
602     float viewportWidth =
603             useStagingData ? mStagingProperties.getViewportWidth() : mProperties.getViewportWidth();
604     float viewportHeight = useStagingData ? mStagingProperties.getViewportHeight()
605                                           : mProperties.getViewportHeight();
606     float scaleX = cacheWidth / viewportWidth;
607     float scaleY = cacheHeight / viewportHeight;
608     outCanvas.scale(scaleX, scaleY);
609     mRootNode->draw(&outCanvas, useStagingData);
610 }
611 
allocateBitmapIfNeeded(Cache & cache,int width,int height)612 bool Tree::allocateBitmapIfNeeded(Cache& cache, int width, int height) {
613     if (!canReuseBitmap(cache.bitmap.get(), width, height)) {
614         SkImageInfo info = SkImageInfo::MakeN32(width, height, kPremul_SkAlphaType);
615         cache.bitmap = Bitmap::allocateHeapBitmap(info);
616         return true;
617     }
618     return false;
619 }
620 
canReuseBitmap(Bitmap * bitmap,int width,int height)621 bool Tree::canReuseBitmap(Bitmap* bitmap, int width, int height) {
622     return bitmap && width <= bitmap->width() && height <= bitmap->height();
623 }
624 
onPropertyChanged(TreeProperties * prop)625 void Tree::onPropertyChanged(TreeProperties* prop) {
626     if (prop == &mStagingProperties) {
627         mStagingCache.dirty = true;
628     } else {
629         mCache.dirty = true;
630     }
631 }
632 
633 class MinMaxAverage {
634 public:
add(float sample)635     void add(float sample) {
636         if (mCount == 0) {
637             mMin = sample;
638             mMax = sample;
639         } else {
640             mMin = std::min(mMin, sample);
641             mMax = std::max(mMax, sample);
642         }
643         mTotal += sample;
644         mCount++;
645     }
646 
average()647     float average() { return mTotal / mCount; }
648 
min()649     float min() { return mMin; }
650 
max()651     float max() { return mMax; }
652 
delta()653     float delta() { return mMax - mMin; }
654 
655 private:
656     float mMin = 0.0f;
657     float mMax = 0.0f;
658     float mTotal = 0.0f;
659     int mCount = 0;
660 };
661 
computePalette()662 BitmapPalette Tree::computePalette() {
663     // TODO Cache this and share the code with Bitmap.cpp
664 
665     ATRACE_CALL();
666 
667     // TODO: This calculation of converting to HSV & tracking min/max is probably overkill
668     // Experiment with something simpler since we just want to figure out if it's "color-ful"
669     // and then the average perceptual lightness.
670 
671     MinMaxAverage hue, saturation, value;
672     int sampledCount = 0;
673 
674     // Sample a grid of 100 pixels to get an overall estimation of the colors in play
675     mRootNode->forEachFillColor([&](SkColor color) {
676         if (SkColorGetA(color) < 75) {
677             return;
678         }
679         sampledCount++;
680         float hsv[3];
681         SkColorToHSV(color, hsv);
682         hue.add(hsv[0]);
683         saturation.add(hsv[1]);
684         value.add(hsv[2]);
685     });
686 
687     if (sampledCount == 0) {
688         ALOGV("VectorDrawable is mostly translucent");
689         return BitmapPalette::Unknown;
690     }
691 
692     ALOGV("samples = %d, hue [min = %f, max = %f, avg = %f]; saturation [min = %f, max = %f, avg = "
693           "%f]; value [min = %f, max = %f, avg = %f]",
694           sampledCount, hue.min(), hue.max(), hue.average(), saturation.min(), saturation.max(),
695           saturation.average(), value.min(), value.max(), value.average());
696 
697     if (hue.delta() <= 20 && saturation.delta() <= .1f) {
698         if (value.average() >= .5f) {
699             return BitmapPalette::Light;
700         } else {
701             return BitmapPalette::Dark;
702         }
703     }
704     return BitmapPalette::Unknown;
705 }
706 
707 }  // namespace VectorDrawable
708 
709 }  // namespace uirenderer
710 }  // namespace android
711