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