Support auto-dark for VectorDrawable

Also fixes a bug where non-animatable properties (colorfilter)
weren't captured at record-time

Test: poked around, quick settings doesn't look awful
Change-Id: I57312dd5eb70f477814a4d898963ee010153c243
This commit is contained in:
John Reck
2018-09-20 16:27:46 -07:00
parent 4603406afe
commit 08ee815625
9 changed files with 162 additions and 46 deletions

View File

@@ -130,7 +130,7 @@ static jboolean setRootAlpha(JNIEnv*, jobject, jlong treePtr, jfloat alpha) {
static jfloat getRootAlpha(JNIEnv*, jobject, jlong treePtr) {
VectorDrawable::Tree* tree = reinterpret_cast<VectorDrawable::Tree*>(treePtr);
return tree->stagingProperties()->getRootAlpha();
return tree->stagingProperties().getRootAlpha();
}
static void updateFullPathPropertiesAndStrokeStyles(JNIEnv*, jobject, jlong fullPathPtr,

View File

@@ -79,7 +79,6 @@ static void applyColorTransform(ColorTransform transform, SkPaint& paint) {
info.fColors = _colorStorage.data();
info.fColorOffsets = _offsetStorage.data();
SkShader::GradientType type = paint.getShader()->asAGradient(&info);
ALOGW_IF(type, "Found gradient of type = %d", type);
if (info.fColorCount <= 10) {
switch (type) {
@@ -108,6 +107,22 @@ static void applyColorTransform(ColorTransform transform, SkPaint& paint) {
}
}
static BitmapPalette paletteForColorHSV(SkColor color) {
float hsv[3];
SkColorToHSV(color, hsv);
return hsv[2] >= .5f ? BitmapPalette::Light : BitmapPalette::Dark;
}
static BitmapPalette filterPalette(const SkPaint* paint, BitmapPalette palette) {
if (palette == BitmapPalette::Unknown || !paint || !paint->getColorFilter()) {
return palette;
}
SkColor color = palette == BitmapPalette::Light ? SK_ColorWHITE : SK_ColorBLACK;
color = paint->getColorFilter()->filterColor(color);
return paletteForColorHSV(color);
}
bool transformPaint(ColorTransform transform, SkPaint* paint) {
// TODO
applyColorTransform(transform, *paint);
@@ -115,6 +130,7 @@ bool transformPaint(ColorTransform transform, SkPaint* paint) {
}
bool transformPaint(ColorTransform transform, SkPaint* paint, BitmapPalette palette) {
palette = filterPalette(paint, palette);
bool shouldInvert = false;
if (palette == BitmapPalette::Light && transform == ColorTransform::Dark) {
shouldInvert = true;

View File

@@ -49,4 +49,5 @@ X(DrawPatch)
X(DrawPoints)
X(DrawVertices)
X(DrawAtlas)
X(DrawShadowRec)
X(DrawShadowRec)
X(DrawVectorDrawable)

View File

@@ -16,6 +16,8 @@
#include "RecordingCanvas.h"
#include "VectorDrawable.h"
#include "SkCanvas.h"
#include "SkData.h"
#include "SkDrawShadowInfo.h"
@@ -498,6 +500,27 @@ struct DrawShadowRec final : Op {
SkDrawShadowRec fRec;
void draw(SkCanvas* c, const SkMatrix&) const { c->private_draw_shadow_rec(fPath, fRec); }
};
struct DrawVectorDrawable final : Op {
static const auto kType = Type::DrawVectorDrawable;
DrawVectorDrawable(VectorDrawableRoot* tree)
: mRoot(tree)
, mBounds(tree->stagingProperties().getBounds())
, palette(tree->computePalette()) {
// Recording, so use staging properties
tree->getPaintFor(&paint, tree->stagingProperties());
}
void draw(SkCanvas* canvas, const SkMatrix&) const {
mRoot->draw(canvas, mBounds, paint);
}
sp<VectorDrawableRoot> mRoot;
SkRect mBounds;
SkPaint paint;
BitmapPalette palette;
};
}
template <typename T, typename... Args>
@@ -698,6 +721,9 @@ void DisplayListData::drawAtlas(const SkImage* atlas, const SkRSXform xforms[],
void DisplayListData::drawShadowRec(const SkPath& path, const SkDrawShadowRec& rec) {
this->push<DrawShadowRec>(0, path, rec);
}
void DisplayListData::drawVectorDrawable(VectorDrawableRoot* tree) {
this->push<DrawVectorDrawable>(0, tree);
}
typedef void (*draw_fn)(const void*, SkCanvas*, const SkMatrix&);
typedef void (*void_fn)(const void*);
@@ -962,5 +988,9 @@ void RecordingCanvas::onDrawShadowRec(const SkPath& path, const SkDrawShadowRec&
fDL->drawShadowRec(path, rec);
}
void RecordingCanvas::drawVectorDrawable(VectorDrawableRoot* tree) {
fDL->drawVectorDrawable(tree);
}
}; // namespace uirenderer
}; // namespace android

View File

@@ -120,6 +120,7 @@ private:
void drawAtlas(const SkImage*, const SkRSXform[], const SkRect[], const SkColor[], int,
SkBlendMode, const SkRect*, const SkPaint*);
void drawShadowRec(const SkPath&, const SkDrawShadowRec&);
void drawVectorDrawable(VectorDrawableRoot* tree);
template <typename T, typename... Args>
void* push(size_t, Args&&...);
@@ -205,6 +206,8 @@ public:
SkBlendMode, const SkRect*, const SkPaint*) override;
void onDrawShadowRec(const SkPath&, const SkDrawShadowRec&) override;
void drawVectorDrawable(VectorDrawableRoot* tree);
private:
typedef SkCanvasVirtualEnforcer<SkNoDrawCanvas> INHERITED;

View File

@@ -458,29 +458,22 @@ void Tree::drawStaging(Canvas* outCanvas) {
mStagingCache.dirty = false;
}
SkPaint tmpPaint;
SkPaint* paint = updatePaint(&tmpPaint, &mStagingProperties);
SkPaint paint;
getPaintFor(&paint, mStagingProperties);
outCanvas->drawBitmap(*mStagingCache.bitmap, 0, 0, mStagingCache.bitmap->width(),
mStagingCache.bitmap->height(), mStagingProperties.getBounds().left(),
mStagingProperties.getBounds().top(),
mStagingProperties.getBounds().right(),
mStagingProperties.getBounds().bottom(), paint);
mStagingProperties.getBounds().bottom(), &paint);
}
SkPaint* Tree::getPaint() {
return updatePaint(&mPaint, &mProperties);
}
// Update the given paint with alpha and color filter. Return nullptr if no color filter is
// specified and root alpha is 1. Otherwise, return updated paint.
SkPaint* Tree::updatePaint(SkPaint* outPaint, TreeProperties* prop) {
void Tree::getPaintFor(SkPaint* outPaint, const TreeProperties &prop) const {
// HWUI always draws VD with bilinear filtering.
outPaint->setFilterQuality(kLow_SkFilterQuality);
if (prop->getRootAlpha() < 1.0f || prop->getColorFilter() != nullptr) {
outPaint->setColorFilter(sk_ref_sp(prop->getColorFilter()));
outPaint->setAlpha(prop->getRootAlpha() * 255);
if (prop.getRootAlpha() < 1.0f || prop.getColorFilter() != nullptr) {
outPaint->setColorFilter(sk_ref_sp(prop.getColorFilter()));
outPaint->setAlpha(prop.getRootAlpha() * 255);
}
return outPaint;
}
Bitmap& Tree::getBitmapUpdateIfDirty() {
@@ -553,11 +546,15 @@ void Tree::Cache::clear() {
mAtlasKey = INVALID_ATLAS_KEY;
}
void Tree::draw(SkCanvas* canvas, const SkRect& bounds) {
void Tree::draw(SkCanvas* canvas, const SkRect& bounds, const SkPaint& inPaint) {
// Update the paint for any animatable properties
SkPaint paint = inPaint;
paint.setAlpha(mProperties.getRootAlpha() * 255);
SkRect src;
sk_sp<SkSurface> vdSurface = mCache.getSurface(&src);
if (vdSurface) {
canvas->drawImageRect(vdSurface->makeImageSnapshot().get(), src, bounds, getPaint(),
canvas->drawImageRect(vdSurface->makeImageSnapshot().get(), src, bounds, &paint,
SkCanvas::kFast_SrcRectConstraint);
} else {
// Handle the case when VectorDrawableAtlas has been destroyed, because of memory pressure.
@@ -570,7 +567,7 @@ void Tree::draw(SkCanvas* canvas, const SkRect& bounds) {
int scaledWidth = SkScalarCeilToInt(mProperties.getScaledWidth());
int scaledHeight = SkScalarCeilToInt(mProperties.getScaledHeight());
canvas->drawBitmapRect(skiaBitmap, SkRect::MakeWH(scaledWidth, scaledHeight), bounds,
getPaint(), SkCanvas::kFast_SrcRectConstraint);
&paint, SkCanvas::kFast_SrcRectConstraint);
mCache.clear();
markDirty();
}
@@ -620,6 +617,80 @@ void Tree::onPropertyChanged(TreeProperties* prop) {
}
}
class MinMaxAverage {
public:
void add(float sample) {
if (mCount == 0) {
mMin = sample;
mMax = sample;
} else {
mMin = std::min(mMin, sample);
mMax = std::max(mMax, sample);
}
mTotal += sample;
mCount++;
}
float average() { return mTotal / mCount; }
float min() { return mMin; }
float max() { return mMax; }
float delta() { return mMax - mMin; }
private:
float mMin = 0.0f;
float mMax = 0.0f;
float mTotal = 0.0f;
int mCount = 0;
};
BitmapPalette Tree::computePalette() {
// TODO Cache this and share the code with Bitmap.cpp
ATRACE_CALL();
// TODO: This calculation of converting to HSV & tracking min/max is probably overkill
// Experiment with something simpler since we just want to figure out if it's "color-ful"
// and then the average perceptual lightness.
MinMaxAverage hue, saturation, value;
int sampledCount = 0;
// Sample a grid of 100 pixels to get an overall estimation of the colors in play
mRootNode->forEachFillColor([&](SkColor color) {
if (SkColorGetA(color) < 75) {
return;
}
sampledCount++;
float hsv[3];
SkColorToHSV(color, hsv);
hue.add(hsv[0]);
saturation.add(hsv[1]);
value.add(hsv[2]);
});
if (sampledCount == 0) {
ALOGV("VectorDrawable is mostly translucent");
return BitmapPalette::Unknown;
}
ALOGV("samples = %d, hue [min = %f, max = %f, avg = %f]; saturation [min = %f, max = %f, avg = "
"%f]; value [min = %f, max = %f, avg = %f]",
sampledCount, hue.min(), hue.max(), hue.average(), saturation.min(), saturation.max(),
saturation.average(), value.min(), value.max(), value.average());
if (hue.delta() <= 20 && saturation.delta() <= .1f) {
if (value.average() >= .5f) {
return BitmapPalette::Light;
} else {
return BitmapPalette::Dark;
}
}
return BitmapPalette::Unknown;
}
}; // namespace VectorDrawable
}; // namespace uirenderer

View File

@@ -120,6 +120,8 @@ public:
virtual void syncProperties() = 0;
virtual void setAntiAlias(bool aa) = 0;
virtual void forEachFillColor(const std::function<void(SkColor)>& func) const { }
protected:
std::string mName;
PropertyChangedListener* mPropertyChangedListener = nullptr;
@@ -349,6 +351,9 @@ public:
}
}
virtual void setAntiAlias(bool aa) { mAntiAlias = aa; }
void forEachFillColor(const std::function<void(SkColor)>& func) const override {
func(mStagingProperties.getFillColor());
}
protected:
const SkPath& getUpdatedPath(bool useStagingData, SkPath* tempStagingPath) override;
@@ -480,6 +485,12 @@ public:
}
}
void forEachFillColor(const std::function<void(SkColor)>& func) const override {
for (auto& child : mChildren) {
child->forEachFillColor(func);
}
}
private:
GroupProperties mProperties = GroupProperties(this);
GroupProperties mStagingProperties = GroupProperties(this);
@@ -495,8 +506,8 @@ public:
// Copy properties from the tree and use the give node as the root node
Tree(const Tree* copy, Group* rootNode) : Tree(rootNode) {
mStagingProperties.syncAnimatableProperties(*copy->stagingProperties());
mStagingProperties.syncNonAnimatableProperties(*copy->stagingProperties());
mStagingProperties.syncAnimatableProperties(copy->stagingProperties());
mStagingProperties.syncNonAnimatableProperties(copy->stagingProperties());
}
// Draws the VD onto a bitmap cache, then the bitmap cache will be rendered onto the input
// canvas. Returns the number of pixels needed for the bitmap cache.
@@ -506,7 +517,6 @@ public:
Bitmap& getBitmapUpdateIfDirty();
void setAllowCaching(bool allowCaching) { mAllowCaching = allowCaching; }
SkPaint* getPaint();
void syncProperties() {
if (mStagingProperties.mNonAnimatablePropertiesDirty) {
mCache.dirty |= (mProperties.mNonAnimatableProperties.viewportWidth !=
@@ -618,7 +628,7 @@ public:
};
void onPropertyChanged(TreeProperties* prop);
TreeProperties* mutateStagingProperties() { return &mStagingProperties; }
const TreeProperties* stagingProperties() const { return &mStagingProperties; }
const TreeProperties& stagingProperties() const { return mStagingProperties; }
// This should only be called from animations on RT
TreeProperties* mutateProperties() { return &mProperties; }
@@ -636,7 +646,10 @@ public:
* Draws VD cache into a canvas. This should always be called from RT and it works with Skia
* pipelines only.
*/
void draw(SkCanvas* canvas, const SkRect& bounds);
void draw(SkCanvas* canvas, const SkRect& bounds, const SkPaint& paint);
void getPaintFor(SkPaint* outPaint, const TreeProperties &props) const;
BitmapPalette computePalette();
/**
* Draws VD into a GPU backed surface.
@@ -680,7 +693,6 @@ private:
skiapipeline::AtlasKey mAtlasKey = INVALID_ATLAS_KEY;
};
SkPaint* updatePaint(SkPaint* outPaint, TreeProperties* prop);
bool allocateBitmapIfNeeded(Cache& cache, int width, int height);
bool canReuseBitmap(Bitmap*, int width, int height);
void updateBitmapCache(Bitmap& outCache, bool useStagingData);
@@ -696,8 +708,6 @@ private:
TreeProperties mProperties = TreeProperties(this);
TreeProperties mStagingProperties = TreeProperties(this);
SkPaint mPaint;
Cache mStagingCache;
Cache mCache;

View File

@@ -34,6 +34,8 @@ enum class PixelStorageType {
Hardware,
};
// TODO: Find a better home for this. It's here because hwui/Bitmap is exported and CanvasTransform
// isn't, but cleanup should be done
enum class BitmapPalette {
Unknown,
Light,

View File

@@ -130,25 +130,8 @@ void SkiaRecordingCanvas::callDrawGLFunction(Functor* functor,
drawDrawable(functorDrawable);
}
class VectorDrawable : public SkDrawable {
public:
VectorDrawable(VectorDrawableRoot* tree)
: mRoot(tree)
, mBounds(tree->stagingProperties()->getBounds()) {}
protected:
virtual SkRect onGetBounds() override { return mBounds; }
virtual void onDraw(SkCanvas* canvas) override {
mRoot->draw(canvas, mBounds);
}
private:
sp<VectorDrawableRoot> mRoot;
SkRect mBounds;
};
void SkiaRecordingCanvas::drawVectorDrawable(VectorDrawableRoot* tree) {
drawDrawable(mDisplayList->allocateDrawable<VectorDrawable>(tree));
mRecorder.drawVectorDrawable(tree);
mDisplayList->mVectorDrawables.push_back(tree);
}