summaryrefslogtreecommitdiff
path: root/libs/hwui/VectorDrawable.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'libs/hwui/VectorDrawable.cpp')
-rw-r--r--libs/hwui/VectorDrawable.cpp503
1 files changed, 279 insertions, 224 deletions
diff --git a/libs/hwui/VectorDrawable.cpp b/libs/hwui/VectorDrawable.cpp
index d35f76485bd1..adfe45c64885 100644
--- a/libs/hwui/VectorDrawable.cpp
+++ b/libs/hwui/VectorDrawable.cpp
@@ -17,6 +17,7 @@
#include "VectorDrawable.h"
#include "PathParser.h"
+#include "SkColorFilter.h"
#include "SkImageInfo.h"
#include "SkShader.h"
#include <utils/Log.h>
@@ -32,41 +33,36 @@ namespace VectorDrawable {
const int Tree::MAX_CACHED_BITMAP_SIZE = 2048;
-void Path::draw(SkCanvas* outCanvas, const SkMatrix& groupStackedMatrix, float scaleX, float scaleY) {
+void Path::draw(SkCanvas* outCanvas, const SkMatrix& groupStackedMatrix, float scaleX, float scaleY,
+ bool useStagingData) {
float matrixScale = getMatrixScale(groupStackedMatrix);
if (matrixScale == 0) {
// When either x or y is scaled to 0, we don't need to draw anything.
return;
}
- const SkPath updatedPath = getUpdatedPath();
SkMatrix pathMatrix(groupStackedMatrix);
pathMatrix.postScale(scaleX, scaleY);
//TODO: try apply the path matrix to the canvas instead of creating a new path.
SkPath renderPath;
renderPath.reset();
- renderPath.addPath(updatedPath, pathMatrix);
+
+ if (useStagingData) {
+ SkPath tmpPath;
+ getStagingPath(&tmpPath);
+ renderPath.addPath(tmpPath, pathMatrix);
+ } else {
+ renderPath.addPath(getUpdatedPath(), pathMatrix);
+ }
float minScale = fmin(scaleX, scaleY);
float strokeScale = minScale * matrixScale;
- drawPath(outCanvas, renderPath, strokeScale, pathMatrix);
-}
-
-void Path::setPathData(const Data& data) {
- if (mData == data) {
- return;
- }
- // Updates the path data. Note that we don't generate a new Skia path right away
- // because there are cases where the animation is changing the path data, but the view
- // that hosts the VD has gone off screen, in which case we won't even draw. So we
- // postpone the Skia path generation to the draw time.
- mData = data;
- mSkPathDirty = true;
+ drawPath(outCanvas, renderPath, strokeScale, pathMatrix, useStagingData);
}
void Path::dump() {
- ALOGD("Path: %s has %zu points", mName.c_str(), mData.points.size());
+ ALOGD("Path: %s has %zu points", mName.c_str(), mProperties.getData().points.size());
}
float Path::getMatrixScale(const SkMatrix& groupStackedMatrix) {
@@ -95,213 +91,215 @@ float Path::getMatrixScale(const SkMatrix& groupStackedMatrix) {
}
return matrixScale;
}
+
+// Called from UI thread during the initial setup/theme change.
Path::Path(const char* pathStr, size_t strLength) {
PathParser::ParseResult result;
- PathParser::getPathDataFromString(&mData, &result, pathStr, strLength);
- if (!result.failureOccurred) {
- VectorDrawableUtils::verbsToPath(&mSkPath, mData);
- }
-}
-
-Path::Path(const Data& data) {
- mData = data;
- // Now we need to construct a path
- VectorDrawableUtils::verbsToPath(&mSkPath, data);
+ Data data;
+ PathParser::getPathDataFromString(&data, &result, pathStr, strLength);
+ mStagingProperties.setData(data);
}
Path::Path(const Path& path) : Node(path) {
- mData = path.mData;
- VectorDrawableUtils::verbsToPath(&mSkPath, mData);
-}
-
-bool Path::canMorph(const Data& morphTo) {
- return VectorDrawableUtils::canMorph(mData, morphTo);
-}
-
-bool Path::canMorph(const Path& path) {
- return canMorph(path.mData);
+ mStagingProperties.syncProperties(path.mStagingProperties);
}
const SkPath& Path::getUpdatedPath() {
if (mSkPathDirty) {
mSkPath.reset();
- VectorDrawableUtils::verbsToPath(&mSkPath, mData);
+ VectorDrawableUtils::verbsToPath(&mSkPath, mProperties.getData());
mSkPathDirty = false;
}
return mSkPath;
}
-void Path::setPath(const char* pathStr, size_t strLength) {
- PathParser::ParseResult result;
- mSkPathDirty = true;
- PathParser::getPathDataFromString(&mData, &result, pathStr, strLength);
+void Path::getStagingPath(SkPath* outPath) {
+ outPath->reset();
+ VectorDrawableUtils::verbsToPath(outPath, mStagingProperties.getData());
+}
+
+void Path::syncProperties() {
+ if (mStagingPropertiesDirty) {
+ mProperties.syncProperties(mStagingProperties);
+ } else {
+ mStagingProperties.syncProperties(mProperties);
+ }
+ mStagingPropertiesDirty = false;
}
FullPath::FullPath(const FullPath& path) : Path(path) {
- mProperties = path.mProperties;
- SkRefCnt_SafeAssign(mStrokeGradient, path.mStrokeGradient);
- SkRefCnt_SafeAssign(mFillGradient, path.mFillGradient);
+ mStagingProperties.syncProperties(path.mStagingProperties);
+}
+
+static void applyTrim(SkPath* outPath, const SkPath& inPath, float trimPathStart, float trimPathEnd,
+ float trimPathOffset) {
+ if (trimPathStart == 0.0f && trimPathEnd == 1.0f) {
+ *outPath = inPath;
+ return;
+ }
+ outPath->reset();
+ if (trimPathStart == trimPathEnd) {
+ // Trimmed path should be empty.
+ return;
+ }
+ SkPathMeasure measure(inPath, false);
+ float len = SkScalarToFloat(measure.getLength());
+ float start = len * fmod((trimPathStart + trimPathOffset), 1.0f);
+ float end = len * fmod((trimPathEnd + trimPathOffset), 1.0f);
+
+ if (start > end) {
+ measure.getSegment(start, len, outPath, true);
+ if (end > 0) {
+ measure.getSegment(0, end, outPath, true);
+ }
+ } else {
+ measure.getSegment(start, end, outPath, true);
+ }
}
const SkPath& FullPath::getUpdatedPath() {
- if (!mSkPathDirty && !mTrimDirty) {
+ if (!mSkPathDirty && !mProperties.mTrimDirty) {
return mTrimmedSkPath;
}
Path::getUpdatedPath();
- if (mProperties.trimPathStart != 0.0f || mProperties.trimPathEnd != 1.0f) {
- applyTrim();
+ if (mProperties.getTrimPathStart() != 0.0f || mProperties.getTrimPathEnd() != 1.0f) {
+ mProperties.mTrimDirty = false;
+ applyTrim(&mTrimmedSkPath, mSkPath, mProperties.getTrimPathStart(),
+ mProperties.getTrimPathEnd(), mProperties.getTrimPathOffset());
return mTrimmedSkPath;
} else {
return mSkPath;
}
}
-void FullPath::updateProperties(float strokeWidth, SkColor strokeColor, float strokeAlpha,
- SkColor fillColor, float fillAlpha, float trimPathStart, float trimPathEnd,
- float trimPathOffset, float strokeMiterLimit, int strokeLineCap, int strokeLineJoin,
- int fillType) {
- mProperties.strokeWidth = strokeWidth;
- mProperties.strokeColor = strokeColor;
- mProperties.strokeAlpha = strokeAlpha;
- mProperties.fillColor = fillColor;
- mProperties.fillAlpha = fillAlpha;
- mProperties.strokeMiterLimit = strokeMiterLimit;
- mProperties.strokeLineCap = strokeLineCap;
- mProperties.strokeLineJoin = strokeLineJoin;
- mProperties.fillType = fillType;
+void FullPath::getStagingPath(SkPath* outPath) {
+ Path::getStagingPath(outPath);
+ SkPath inPath = *outPath;
+ applyTrim(outPath, inPath, mStagingProperties.getTrimPathStart(),
+ mStagingProperties.getTrimPathEnd(), mStagingProperties.getTrimPathOffset());
+}
- // If any trim property changes, mark trim dirty and update the trim path
- setTrimPathStart(trimPathStart);
- setTrimPathEnd(trimPathEnd);
- setTrimPathOffset(trimPathOffset);
+void FullPath::dump() {
+ Path::dump();
+ ALOGD("stroke width, color, alpha: %f, %d, %f, fill color, alpha: %d, %f",
+ mProperties.getStrokeWidth(), mProperties.getStrokeColor(), mProperties.getStrokeAlpha(),
+ mProperties.getFillColor(), mProperties.getFillAlpha());
}
+
inline SkColor applyAlpha(SkColor color, float alpha) {
int alphaBytes = SkColorGetA(color);
return SkColorSetA(color, alphaBytes * alpha);
}
void FullPath::drawPath(SkCanvas* outCanvas, SkPath& renderPath, float strokeScale,
- const SkMatrix& matrix){
+ const SkMatrix& matrix, bool useStagingData){
+ const FullPathProperties& properties = useStagingData ? mStagingProperties : mProperties;
+
// Draw path's fill, if fill color or gradient is valid
bool needsFill = false;
- if (mFillGradient != nullptr) {
- mPaint.setColor(applyAlpha(SK_ColorBLACK, mProperties.fillAlpha));
- SkShader* newShader = mFillGradient->newWithLocalMatrix(matrix);
- mPaint.setShader(newShader);
+ SkPaint paint;
+ if (properties.getFillGradient() != nullptr) {
+ paint.setColor(applyAlpha(SK_ColorBLACK, properties.getFillAlpha()));
+ SkShader* newShader = properties.getFillGradient()->newWithLocalMatrix(matrix);
+ paint.setShader(newShader);
needsFill = true;
- } else if (mProperties.fillColor != SK_ColorTRANSPARENT) {
- mPaint.setColor(applyAlpha(mProperties.fillColor, mProperties.fillAlpha));
+ } else if (properties.getFillColor() != SK_ColorTRANSPARENT) {
+ paint.setColor(applyAlpha(properties.getFillColor(), properties.getFillAlpha()));
needsFill = true;
}
if (needsFill) {
- mPaint.setStyle(SkPaint::Style::kFill_Style);
- mPaint.setAntiAlias(true);
- SkPath::FillType ft = static_cast<SkPath::FillType>(mProperties.fillType);
+ paint.setStyle(SkPaint::Style::kFill_Style);
+ paint.setAntiAlias(true);
+ SkPath::FillType ft = static_cast<SkPath::FillType>(properties.getFillType());
renderPath.setFillType(ft);
- outCanvas->drawPath(renderPath, mPaint);
+ outCanvas->drawPath(renderPath, paint);
}
- // Draw path's stroke, if stroke color or gradient is valid
+ // Draw path's stroke, if stroke color or Gradient is valid
bool needsStroke = false;
- if (mStrokeGradient != nullptr) {
- mPaint.setColor(applyAlpha(SK_ColorBLACK, mProperties.strokeAlpha));
- SkShader* newShader = mStrokeGradient->newWithLocalMatrix(matrix);
- mPaint.setShader(newShader);
+ if (properties.getStrokeGradient() != nullptr) {
+ paint.setColor(applyAlpha(SK_ColorBLACK, properties.getStrokeAlpha()));
+ SkShader* newShader = properties.getStrokeGradient()->newWithLocalMatrix(matrix);
+ paint.setShader(newShader);
needsStroke = true;
- } else if (mProperties.strokeColor != SK_ColorTRANSPARENT) {
- mPaint.setColor(applyAlpha(mProperties.strokeColor, mProperties.strokeAlpha));
+ } else if (properties.getStrokeColor() != SK_ColorTRANSPARENT) {
+ paint.setColor(applyAlpha(properties.getStrokeColor(), properties.getStrokeAlpha()));
needsStroke = true;
}
if (needsStroke) {
- mPaint.setStyle(SkPaint::Style::kStroke_Style);
- mPaint.setAntiAlias(true);
- mPaint.setStrokeJoin(SkPaint::Join(mProperties.strokeLineJoin));
- mPaint.setStrokeCap(SkPaint::Cap(mProperties.strokeLineCap));
- mPaint.setStrokeMiter(mProperties.strokeMiterLimit);
- mPaint.setStrokeWidth(mProperties.strokeWidth * strokeScale);
- outCanvas->drawPath(renderPath, mPaint);
+ paint.setStyle(SkPaint::Style::kStroke_Style);
+ paint.setAntiAlias(true);
+ paint.setStrokeJoin(SkPaint::Join(properties.getStrokeLineJoin()));
+ paint.setStrokeCap(SkPaint::Cap(properties.getStrokeLineCap()));
+ paint.setStrokeMiter(properties.getStrokeMiterLimit());
+ paint.setStrokeWidth(properties.getStrokeWidth() * strokeScale);
+ outCanvas->drawPath(renderPath, paint);
}
}
-/**
- * Applies trimming to the specified path.
- */
-void FullPath::applyTrim() {
- if (mProperties.trimPathStart == 0.0f && mProperties.trimPathEnd == 1.0f) {
- // No trimming necessary.
- return;
- }
- mTrimDirty = false;
- mTrimmedSkPath.reset();
- if (mProperties.trimPathStart == mProperties.trimPathEnd) {
- // Trimmed path should be empty.
- return;
- }
- SkPathMeasure measure(mSkPath, false);
- float len = SkScalarToFloat(measure.getLength());
- float start = len * fmod((mProperties.trimPathStart + mProperties.trimPathOffset), 1.0f);
- float end = len * fmod((mProperties.trimPathEnd + mProperties.trimPathOffset), 1.0f);
+void FullPath::syncProperties() {
+ Path::syncProperties();
- if (start > end) {
- measure.getSegment(start, len, &mTrimmedSkPath, true);
- if (end > 0) {
- measure.getSegment(0, end, &mTrimmedSkPath, true);
- }
+ if (mStagingPropertiesDirty) {
+ mProperties.syncProperties(mStagingProperties);
} else {
- measure.getSegment(start, end, &mTrimmedSkPath, true);
+ // Update staging property with property values from animation.
+ mStagingProperties.syncProperties(mProperties);
}
+ mStagingPropertiesDirty = false;
}
-REQUIRE_COMPATIBLE_LAYOUT(FullPath::Properties);
+REQUIRE_COMPATIBLE_LAYOUT(FullPath::FullPathProperties::PrimitiveFields);
static_assert(sizeof(float) == sizeof(int32_t), "float is not the same size as int32_t");
static_assert(sizeof(SkColor) == sizeof(int32_t), "SkColor is not the same size as int32_t");
-bool FullPath::getProperties(int8_t* outProperties, int length) {
- int propertyDataSize = sizeof(Properties);
+bool FullPath::FullPathProperties::copyProperties(int8_t* outProperties, int length) const {
+ int propertyDataSize = sizeof(FullPathProperties::PrimitiveFields);
if (length != propertyDataSize) {
LOG_ALWAYS_FATAL("Properties needs exactly %d bytes, a byte array of size %d is provided",
propertyDataSize, length);
return false;
}
- Properties* out = reinterpret_cast<Properties*>(outProperties);
- *out = mProperties;
+
+ PrimitiveFields* out = reinterpret_cast<PrimitiveFields*>(outProperties);
+ *out = mPrimitiveFields;
return true;
}
-void FullPath::setColorPropertyValue(int propertyId, int32_t value) {
+void FullPath::FullPathProperties::setColorPropertyValue(int propertyId, int32_t value) {
Property currentProperty = static_cast<Property>(propertyId);
- if (currentProperty == Property::StrokeColor) {
- mProperties.strokeColor = value;
- } else if (currentProperty == Property::FillColor) {
- mProperties.fillColor = value;
+ if (currentProperty == Property::strokeColor) {
+ setStrokeColor(value);
+ } else if (currentProperty == Property::fillColor) {
+ setFillColor(value);
} else {
- LOG_ALWAYS_FATAL("Error setting color property on FullPath: No valid property with id: %d",
- propertyId);
+ LOG_ALWAYS_FATAL("Error setting color property on FullPath: No valid property"
+ " with id: %d", propertyId);
}
}
-void FullPath::setPropertyValue(int propertyId, float value) {
+void FullPath::FullPathProperties::setPropertyValue(int propertyId, float value) {
Property property = static_cast<Property>(propertyId);
switch (property) {
- case Property::StrokeWidth:
+ case Property::strokeWidth:
setStrokeWidth(value);
break;
- case Property::StrokeAlpha:
+ case Property::strokeAlpha:
setStrokeAlpha(value);
break;
- case Property::FillAlpha:
+ case Property::fillAlpha:
setFillAlpha(value);
break;
- case Property::TrimPathStart:
+ case Property::trimPathStart:
setTrimPathStart(value);
break;
- case Property::TrimPathEnd:
+ case Property::trimPathEnd:
setTrimPathEnd(value);
break;
- case Property::TrimPathOffset:
+ case Property::trimPathOffset:
setTrimPathOffset(value);
break;
default:
@@ -311,16 +309,16 @@ void FullPath::setPropertyValue(int propertyId, float value) {
}
void ClipPath::drawPath(SkCanvas* outCanvas, SkPath& renderPath,
- float strokeScale, const SkMatrix& matrix){
+ float strokeScale, const SkMatrix& matrix, bool useStagingData){
outCanvas->clipPath(renderPath, SkRegion::kIntersect_Op);
}
Group::Group(const Group& group) : Node(group) {
- mProperties = group.mProperties;
+ mStagingProperties.syncProperties(group.mStagingProperties);
}
void Group::draw(SkCanvas* outCanvas, const SkMatrix& currentMatrix, float scaleX,
- float scaleY) {
+ float scaleY, bool useStagingData) {
// TODO: Try apply the matrix to the canvas instead of passing it down the tree
// Calculate current group's matrix by preConcat the parent's and
@@ -328,14 +326,15 @@ void Group::draw(SkCanvas* outCanvas, const SkMatrix& currentMatrix, float scale
// Basically the Mfinal = Mviewport * M0 * M1 * M2;
// Mi the local matrix at level i of the group tree.
SkMatrix stackedMatrix;
- getLocalMatrix(&stackedMatrix);
+ const GroupProperties& prop = useStagingData ? mStagingProperties : mProperties;
+ getLocalMatrix(&stackedMatrix, prop);
stackedMatrix.postConcat(currentMatrix);
// Save the current clip information, which is local to this group.
outCanvas->save();
// Draw the group tree in the same order as the XML file.
for (auto& child : mChildren) {
- child->draw(outCanvas, stackedMatrix, scaleX, scaleY);
+ child->draw(outCanvas, stackedMatrix, scaleX, scaleY, useStagingData);
}
// Restore the previous clip information.
outCanvas->restore();
@@ -343,96 +342,106 @@ void Group::draw(SkCanvas* outCanvas, const SkMatrix& currentMatrix, float scale
void Group::dump() {
ALOGD("Group %s has %zu children: ", mName.c_str(), mChildren.size());
+ ALOGD("Group translateX, Y : %f, %f, scaleX, Y: %f, %f", mProperties.getTranslateX(),
+ mProperties.getTranslateY(), mProperties.getScaleX(), mProperties.getScaleY());
for (size_t i = 0; i < mChildren.size(); i++) {
mChildren[i]->dump();
}
}
-void Group::updateLocalMatrix(float rotate, float pivotX, float pivotY,
- float scaleX, float scaleY, float translateX, float translateY) {
- setRotation(rotate);
- setPivotX(pivotX);
- setPivotY(pivotY);
- setScaleX(scaleX);
- setScaleY(scaleY);
- setTranslateX(translateX);
- setTranslateY(translateY);
+void Group::syncProperties() {
+ // Copy over the dirty staging properties
+ if (mStagingPropertiesDirty) {
+ mProperties.syncProperties(mStagingProperties);
+ } else {
+ mStagingProperties.syncProperties(mProperties);
+ }
+ mStagingPropertiesDirty = false;
+ for (auto& child : mChildren) {
+ child->syncProperties();
+ }
}
-void Group::getLocalMatrix(SkMatrix* outMatrix) {
+void Group::getLocalMatrix(SkMatrix* outMatrix, const GroupProperties& properties) {
outMatrix->reset();
// TODO: use rotate(mRotate, mPivotX, mPivotY) and scale with pivot point, instead of
// translating to pivot for rotating and scaling, then translating back.
- outMatrix->postTranslate(-mProperties.pivotX, -mProperties.pivotY);
- outMatrix->postScale(mProperties.scaleX, mProperties.scaleY);
- outMatrix->postRotate(mProperties.rotate, 0, 0);
- outMatrix->postTranslate(mProperties.translateX + mProperties.pivotX,
- mProperties.translateY + mProperties.pivotY);
+ outMatrix->postTranslate(-properties.getPivotX(), -properties.getPivotY());
+ outMatrix->postScale(properties.getScaleX(), properties.getScaleY());
+ outMatrix->postRotate(properties.getRotation(), 0, 0);
+ outMatrix->postTranslate(properties.getTranslateX() + properties.getPivotX(),
+ properties.getTranslateY() + properties.getPivotY());
}
void Group::addChild(Node* child) {
mChildren.emplace_back(child);
+ if (mPropertyChangedListener != nullptr) {
+ child->setPropertyChangedListener(mPropertyChangedListener);
+ }
}
-bool Group::getProperties(float* outProperties, int length) {
- int propertyCount = static_cast<int>(Property::Count);
+bool Group::GroupProperties::copyProperties(float* outProperties, int length) const {
+ int propertyCount = static_cast<int>(Property::count);
if (length != propertyCount) {
LOG_ALWAYS_FATAL("Properties needs exactly %d bytes, a byte array of size %d is provided",
propertyCount, length);
return false;
}
- Properties* out = reinterpret_cast<Properties*>(outProperties);
- *out = mProperties;
+
+ PrimitiveFields* out = reinterpret_cast<PrimitiveFields*>(outProperties);
+ *out = mPrimitiveFields;
return true;
}
// TODO: Consider animating the properties as float pointers
-float Group::getPropertyValue(int propertyId) const {
+// Called on render thread
+float Group::GroupProperties::getPropertyValue(int propertyId) const {
Property currentProperty = static_cast<Property>(propertyId);
switch (currentProperty) {
- case Property::Rotate:
- return mProperties.rotate;
- case Property::PivotX:
- return mProperties.pivotX;
- case Property::PivotY:
- return mProperties.pivotY;
- case Property::ScaleX:
- return mProperties.scaleX;
- case Property::ScaleY:
- return mProperties.scaleY;
- case Property::TranslateX:
- return mProperties.translateX;
- case Property::TranslateY:
- return mProperties.translateY;
+ case Property::rotate:
+ return getRotation();
+ case Property::pivotX:
+ return getPivotX();
+ case Property::pivotY:
+ return getPivotY();
+ case Property::scaleX:
+ return getScaleX();
+ case Property::scaleY:
+ return getScaleY();
+ case Property::translateX:
+ return getTranslateX();
+ case Property::translateY:
+ return getTranslateY();
default:
LOG_ALWAYS_FATAL("Invalid property index: %d", propertyId);
return 0;
}
}
-void Group::setPropertyValue(int propertyId, float value) {
+// Called on render thread
+void Group::GroupProperties::setPropertyValue(int propertyId, float value) {
Property currentProperty = static_cast<Property>(propertyId);
switch (currentProperty) {
- case Property::Rotate:
- mProperties.rotate = value;
+ case Property::rotate:
+ setRotation(value);
break;
- case Property::PivotX:
- mProperties.pivotX = value;
+ case Property::pivotX:
+ setPivotX(value);
break;
- case Property::PivotY:
- mProperties.pivotY = value;
+ case Property::pivotY:
+ setPivotY(value);
break;
- case Property::ScaleX:
- mProperties.scaleX = value;
+ case Property::scaleX:
+ setScaleX(value);
break;
- case Property::ScaleY:
- mProperties.scaleY = value;
+ case Property::scaleY:
+ setScaleY(value);
break;
- case Property::TranslateX:
- mProperties.translateX = value;
+ case Property::translateX:
+ setTranslateX(value);
break;
- case Property::TranslateY:
- mProperties.translateY = value;
+ case Property::translateY:
+ setTranslateY(value);
break;
default:
LOG_ALWAYS_FATAL("Invalid property index: %d", propertyId);
@@ -440,7 +449,11 @@ void Group::setPropertyValue(int propertyId, float value) {
}
bool Group::isValidProperty(int propertyId) {
- return propertyId >= 0 && propertyId < static_cast<int>(Property::Count);
+ return GroupProperties::isValidProperty(propertyId);
+}
+
+bool Group::GroupProperties::isValidProperty(int propertyId) {
+ return propertyId >= 0 && propertyId < static_cast<int>(Property::count);
}
void Tree::draw(Canvas* outCanvas, SkColorFilter* colorFilter,
@@ -449,18 +462,18 @@ void Tree::draw(Canvas* outCanvas, SkColorFilter* colorFilter,
// avoid blurry scaling, we have to draw into a bitmap with exact pixel
// size first. This bitmap size is determined by the bounds and the
// canvas scale.
- outCanvas->getMatrix(&mCanvasMatrix);
- mBounds = bounds;
+ SkMatrix canvasMatrix;
+ outCanvas->getMatrix(&canvasMatrix);
float canvasScaleX = 1.0f;
float canvasScaleY = 1.0f;
- if (mCanvasMatrix.getSkewX() == 0 && mCanvasMatrix.getSkewY() == 0) {
+ if (canvasMatrix.getSkewX() == 0 && canvasMatrix.getSkewY() == 0) {
// Only use the scale value when there's no skew or rotation in the canvas matrix.
// TODO: Add a cts test for drawing VD on a canvas with negative scaling factors.
- canvasScaleX = fabs(mCanvasMatrix.getScaleX());
- canvasScaleY = fabs(mCanvasMatrix.getScaleY());
+ canvasScaleX = fabs(canvasMatrix.getScaleX());
+ canvasScaleY = fabs(canvasMatrix.getScaleY());
}
- int scaledWidth = (int) (mBounds.width() * canvasScaleX);
- int scaledHeight = (int) (mBounds.height() * canvasScaleY);
+ int scaledWidth = (int) (bounds.width() * canvasScaleX);
+ int scaledHeight = (int) (bounds.height() * canvasScaleY);
scaledWidth = std::min(Tree::MAX_CACHED_BITMAP_SIZE, scaledWidth);
scaledHeight = std::min(Tree::MAX_CACHED_BITMAP_SIZE, scaledHeight);
@@ -468,63 +481,105 @@ void Tree::draw(Canvas* outCanvas, SkColorFilter* colorFilter,
return;
}
- mPaint.setColorFilter(colorFilter);
-
+ mStagingProperties.setScaledSize(scaledWidth, scaledHeight);
int saveCount = outCanvas->save(SaveFlags::MatrixClip);
- outCanvas->translate(mBounds.fLeft, mBounds.fTop);
+ outCanvas->translate(bounds.fLeft, bounds.fTop);
// Handle RTL mirroring.
if (needsMirroring) {
- outCanvas->translate(mBounds.width(), 0);
+ outCanvas->translate(bounds.width(), 0);
outCanvas->scale(-1.0f, 1.0f);
}
+ mStagingProperties.setColorFilter(colorFilter);
// At this point, canvas has been translated to the right position.
// And we use this bound for the destination rect for the drawBitmap, so
// we offset to (0, 0);
- mBounds.offsetTo(0, 0);
- createCachedBitmapIfNeeded(scaledWidth, scaledHeight);
-
+ SkRect tmpBounds = bounds;
+ tmpBounds.offsetTo(0, 0);
+ mStagingProperties.setBounds(tmpBounds);
outCanvas->drawVectorDrawable(this);
-
outCanvas->restoreToCount(saveCount);
}
+void Tree::drawStaging(Canvas* outCanvas) {
+ bool redrawNeeded = allocateBitmapIfNeeded(&mStagingCache.bitmap,
+ mStagingProperties.getScaledWidth(), mStagingProperties.getScaledHeight());
+ // draw bitmap cache
+ if (redrawNeeded || mStagingCache.dirty) {
+ updateBitmapCache(&mStagingCache.bitmap, true);
+ mStagingCache.dirty = false;
+ }
+
+ SkPaint tmpPaint;
+ SkPaint* paint = updatePaint(&tmpPaint, &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);
+}
+
SkPaint* Tree::getPaint() {
- SkPaint* paint;
- if (mRootAlpha == 1.0f && mPaint.getColorFilter() == NULL) {
- paint = NULL;
+ 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) {
+ if (prop->getRootAlpha() == 1.0f && prop->getColorFilter() == nullptr) {
+ return nullptr;
} else {
- mPaint.setFilterQuality(kLow_SkFilterQuality);
- mPaint.setAlpha(mRootAlpha * 255);
- paint = &mPaint;
+ outPaint->setColorFilter(mStagingProperties.getColorFilter());
+ outPaint->setFilterQuality(kLow_SkFilterQuality);
+ outPaint->setAlpha(prop->getRootAlpha() * 255);
+ return outPaint;
}
- return paint;
}
const SkBitmap& Tree::getBitmapUpdateIfDirty() {
- mCachedBitmap.eraseColor(SK_ColorTRANSPARENT);
- SkCanvas outCanvas(mCachedBitmap);
- float scaleX = (float) mCachedBitmap.width() / mViewportWidth;
- float scaleY = (float) mCachedBitmap.height() / mViewportHeight;
- mRootNode->draw(&outCanvas, SkMatrix::I(), scaleX, scaleY);
- mCacheDirty = false;
- return mCachedBitmap;
+ bool redrawNeeded = allocateBitmapIfNeeded(&mCache.bitmap, mProperties.getScaledWidth(),
+ mProperties.getScaledHeight());
+ if (redrawNeeded || mCache.dirty) {
+ updateBitmapCache(&mCache.bitmap, false);
+ mCache.dirty = false;
+ }
+ return mCache.bitmap;
+}
+
+void Tree::updateBitmapCache(SkBitmap* outCache, bool useStagingData) {
+ outCache->eraseColor(SK_ColorTRANSPARENT);
+ SkCanvas outCanvas(*outCache);
+ float viewportWidth = useStagingData ?
+ mStagingProperties.getViewportWidth() : mProperties.getViewportWidth();
+ float viewportHeight = useStagingData ?
+ mStagingProperties.getViewportHeight() : mProperties.getViewportHeight();
+ float scaleX = outCache->width() / viewportWidth;
+ float scaleY = outCache->height() / viewportHeight;
+ mRootNode->draw(&outCanvas, SkMatrix::I(), scaleX, scaleY, useStagingData);
}
-void Tree::createCachedBitmapIfNeeded(int width, int height) {
- if (!canReuseBitmap(width, height)) {
+bool Tree::allocateBitmapIfNeeded(SkBitmap* outCache, int width, int height) {
+ if (!canReuseBitmap(*outCache, width, height)) {
SkImageInfo info = SkImageInfo::Make(width, height,
kN32_SkColorType, kPremul_SkAlphaType);
- mCachedBitmap.setInfo(info);
+ outCache->setInfo(info);
// TODO: Count the bitmap cache against app's java heap
- mCachedBitmap.allocPixels(info);
- mCacheDirty = true;
+ outCache->allocPixels(info);
+ return true;
}
+ return false;
}
-bool Tree::canReuseBitmap(int width, int height) {
- return width == mCachedBitmap.width() && height == mCachedBitmap.height();
+bool Tree::canReuseBitmap(const SkBitmap& bitmap, int width, int height) {
+ return width == bitmap.width() && height == bitmap.height();
+}
+
+void Tree::onPropertyChanged(TreeProperties* prop) {
+ if (prop == &mStagingProperties) {
+ mStagingCache.dirty = true;
+ } else {
+ mCache.dirty = true;
+ }
}
}; // namespace VectorDrawable