VectorDrawable.cpp revision ef062ebd20032efe697741d6c3dfd1faec54f590
1/* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17#include "VectorDrawable.h" 18 19#include "PathParser.h" 20#include "SkImageInfo.h" 21#include "SkShader.h" 22#include <utils/Log.h> 23#include "utils/Macros.h" 24#include "utils/VectorDrawableUtils.h" 25 26#include <math.h> 27#include <string.h> 28 29namespace android { 30namespace uirenderer { 31namespace VectorDrawable { 32 33const int Tree::MAX_CACHED_BITMAP_SIZE = 2048; 34 35void Path::draw(SkCanvas* outCanvas, const SkMatrix& groupStackedMatrix, float scaleX, float scaleY) { 36 float matrixScale = getMatrixScale(groupStackedMatrix); 37 if (matrixScale == 0) { 38 // When either x or y is scaled to 0, we don't need to draw anything. 39 return; 40 } 41 42 const SkPath updatedPath = getUpdatedPath(); 43 SkMatrix pathMatrix(groupStackedMatrix); 44 pathMatrix.postScale(scaleX, scaleY); 45 46 //TODO: try apply the path matrix to the canvas instead of creating a new path. 47 SkPath renderPath; 48 renderPath.reset(); 49 renderPath.addPath(updatedPath, pathMatrix); 50 51 float minScale = fmin(scaleX, scaleY); 52 float strokeScale = minScale * matrixScale; 53 drawPath(outCanvas, renderPath, strokeScale, pathMatrix); 54} 55 56void Path::setPathData(const Data& data) { 57 if (mData == data) { 58 return; 59 } 60 // Updates the path data. Note that we don't generate a new Skia path right away 61 // because there are cases where the animation is changing the path data, but the view 62 // that hosts the VD has gone off screen, in which case we won't even draw. So we 63 // postpone the Skia path generation to the draw time. 64 mData = data; 65 mSkPathDirty = true; 66} 67 68void Path::dump() { 69 ALOGD("Path: %s has %zu points", mName.c_str(), mData.points.size()); 70} 71 72float Path::getMatrixScale(const SkMatrix& groupStackedMatrix) { 73 // Given unit vectors A = (0, 1) and B = (1, 0). 74 // After matrix mapping, we got A' and B'. Let theta = the angel b/t A' and B'. 75 // Therefore, the final scale we want is min(|A'| * sin(theta), |B'| * sin(theta)), 76 // which is (|A'| * |B'| * sin(theta)) / max (|A'|, |B'|); 77 // If max (|A'|, |B'|) = 0, that means either x or y has a scale of 0. 78 // 79 // For non-skew case, which is most of the cases, matrix scale is computing exactly the 80 // scale on x and y axis, and take the minimal of these two. 81 // For skew case, an unit square will mapped to a parallelogram. And this function will 82 // return the minimal height of the 2 bases. 83 SkVector skVectors[2]; 84 skVectors[0].set(0, 1); 85 skVectors[1].set(1, 0); 86 groupStackedMatrix.mapVectors(skVectors, 2); 87 float scaleX = hypotf(skVectors[0].fX, skVectors[0].fY); 88 float scaleY = hypotf(skVectors[1].fX, skVectors[1].fY); 89 float crossProduct = skVectors[0].cross(skVectors[1]); 90 float maxScale = fmax(scaleX, scaleY); 91 92 float matrixScale = 0; 93 if (maxScale > 0) { 94 matrixScale = fabs(crossProduct) / maxScale; 95 } 96 return matrixScale; 97} 98Path::Path(const char* pathStr, size_t strLength) { 99 PathParser::ParseResult result; 100 PathParser::getPathDataFromString(&mData, &result, pathStr, strLength); 101 if (!result.failureOccurred) { 102 VectorDrawableUtils::verbsToPath(&mSkPath, mData); 103 } 104} 105 106Path::Path(const Data& data) { 107 mData = data; 108 // Now we need to construct a path 109 VectorDrawableUtils::verbsToPath(&mSkPath, data); 110} 111 112Path::Path(const Path& path) : Node(path) { 113 mData = path.mData; 114 VectorDrawableUtils::verbsToPath(&mSkPath, mData); 115} 116 117bool Path::canMorph(const Data& morphTo) { 118 return VectorDrawableUtils::canMorph(mData, morphTo); 119} 120 121bool Path::canMorph(const Path& path) { 122 return canMorph(path.mData); 123} 124 125const SkPath& Path::getUpdatedPath() { 126 if (mSkPathDirty) { 127 mSkPath.reset(); 128 VectorDrawableUtils::verbsToPath(&mSkPath, mData); 129 mSkPathDirty = false; 130 } 131 return mSkPath; 132} 133 134void Path::setPath(const char* pathStr, size_t strLength) { 135 PathParser::ParseResult result; 136 mSkPathDirty = true; 137 PathParser::getPathDataFromString(&mData, &result, pathStr, strLength); 138} 139 140FullPath::FullPath(const FullPath& path) : Path(path) { 141 mProperties = path.mProperties; 142 SkRefCnt_SafeAssign(mStrokeGradient, path.mStrokeGradient); 143 SkRefCnt_SafeAssign(mFillGradient, path.mFillGradient); 144} 145 146const SkPath& FullPath::getUpdatedPath() { 147 if (!mSkPathDirty && !mTrimDirty) { 148 return mTrimmedSkPath; 149 } 150 Path::getUpdatedPath(); 151 if (mProperties.trimPathStart != 0.0f || mProperties.trimPathEnd != 1.0f) { 152 applyTrim(); 153 return mTrimmedSkPath; 154 } else { 155 return mSkPath; 156 } 157} 158 159void FullPath::updateProperties(float strokeWidth, SkColor strokeColor, float strokeAlpha, 160 SkColor fillColor, float fillAlpha, float trimPathStart, float trimPathEnd, 161 float trimPathOffset, float strokeMiterLimit, int strokeLineCap, int strokeLineJoin) { 162 mProperties.strokeWidth = strokeWidth; 163 mProperties.strokeColor = strokeColor; 164 mProperties.strokeAlpha = strokeAlpha; 165 mProperties.fillColor = fillColor; 166 mProperties.fillAlpha = fillAlpha; 167 mProperties.strokeMiterLimit = strokeMiterLimit; 168 mProperties.strokeLineCap = strokeLineCap; 169 mProperties.strokeLineJoin = strokeLineJoin; 170 171 // If any trim property changes, mark trim dirty and update the trim path 172 setTrimPathStart(trimPathStart); 173 setTrimPathEnd(trimPathEnd); 174 setTrimPathOffset(trimPathOffset); 175} 176 177inline SkColor applyAlpha(SkColor color, float alpha) { 178 int alphaBytes = SkColorGetA(color); 179 return SkColorSetA(color, alphaBytes * alpha); 180} 181 182void FullPath::drawPath(SkCanvas* outCanvas, const SkPath& renderPath, float strokeScale, 183 const SkMatrix& matrix){ 184 // Draw path's fill, if fill color or gradient is valid 185 bool needsFill = false; 186 if (mFillGradient != nullptr) { 187 mPaint.setColor(applyAlpha(SK_ColorBLACK, mProperties.fillAlpha)); 188 SkShader* newShader = mFillGradient->newWithLocalMatrix(matrix); 189 mPaint.setShader(newShader); 190 needsFill = true; 191 } else if (mProperties.fillColor != SK_ColorTRANSPARENT) { 192 mPaint.setColor(applyAlpha(mProperties.fillColor, mProperties.fillAlpha)); 193 needsFill = true; 194 } 195 196 if (needsFill) { 197 mPaint.setStyle(SkPaint::Style::kFill_Style); 198 mPaint.setAntiAlias(true); 199 outCanvas->drawPath(renderPath, mPaint); 200 } 201 202 // Draw path's stroke, if stroke color or gradient is valid 203 bool needsStroke = false; 204 if (mStrokeGradient != nullptr) { 205 mPaint.setColor(applyAlpha(SK_ColorBLACK, mProperties.strokeAlpha)); 206 SkShader* newShader = mStrokeGradient->newWithLocalMatrix(matrix); 207 mPaint.setShader(newShader); 208 needsStroke = true; 209 } else if (mProperties.strokeColor != SK_ColorTRANSPARENT) { 210 mPaint.setColor(applyAlpha(mProperties.strokeColor, mProperties.strokeAlpha)); 211 needsStroke = true; 212 } 213 if (needsStroke) { 214 mPaint.setStyle(SkPaint::Style::kStroke_Style); 215 mPaint.setAntiAlias(true); 216 mPaint.setStrokeJoin(SkPaint::Join(mProperties.strokeLineJoin)); 217 mPaint.setStrokeCap(SkPaint::Cap(mProperties.strokeLineCap)); 218 mPaint.setStrokeMiter(mProperties.strokeMiterLimit); 219 mPaint.setStrokeWidth(mProperties.strokeWidth * strokeScale); 220 outCanvas->drawPath(renderPath, mPaint); 221 } 222} 223 224/** 225 * Applies trimming to the specified path. 226 */ 227void FullPath::applyTrim() { 228 if (mProperties.trimPathStart == 0.0f && mProperties.trimPathEnd == 1.0f) { 229 // No trimming necessary. 230 return; 231 } 232 SkPathMeasure measure(mSkPath, false); 233 float len = SkScalarToFloat(measure.getLength()); 234 float start = len * fmod((mProperties.trimPathStart + mProperties.trimPathOffset), 1.0f); 235 float end = len * fmod((mProperties.trimPathEnd + mProperties.trimPathOffset), 1.0f); 236 237 mTrimmedSkPath.reset(); 238 if (start > end) { 239 measure.getSegment(start, len, &mTrimmedSkPath, true); 240 measure.getSegment(0, end, &mTrimmedSkPath, true); 241 } else { 242 measure.getSegment(start, end, &mTrimmedSkPath, true); 243 } 244 mTrimDirty = false; 245} 246 247REQUIRE_COMPATIBLE_LAYOUT(FullPath::Properties); 248 249static_assert(sizeof(float) == sizeof(int32_t), "float is not the same size as int32_t"); 250static_assert(sizeof(SkColor) == sizeof(int32_t), "SkColor is not the same size as int32_t"); 251 252bool FullPath::getProperties(int8_t* outProperties, int length) { 253 int propertyDataSize = sizeof(Properties); 254 if (length != propertyDataSize) { 255 LOG_ALWAYS_FATAL("Properties needs exactly %d bytes, a byte array of size %d is provided", 256 propertyDataSize, length); 257 return false; 258 } 259 Properties* out = reinterpret_cast<Properties*>(outProperties); 260 *out = mProperties; 261 return true; 262} 263 264void FullPath::setColorPropertyValue(int propertyId, int32_t value) { 265 Property currentProperty = static_cast<Property>(propertyId); 266 if (currentProperty == Property::StrokeColor) { 267 mProperties.strokeColor = value; 268 } else if (currentProperty == Property::FillColor) { 269 mProperties.fillColor = value; 270 } else { 271 LOG_ALWAYS_FATAL("Error setting color property on FullPath: No valid property with id: %d", 272 propertyId); 273 } 274} 275 276void FullPath::setPropertyValue(int propertyId, float value) { 277 Property property = static_cast<Property>(propertyId); 278 switch (property) { 279 case Property::StrokeWidth: 280 setStrokeWidth(value); 281 break; 282 case Property::StrokeAlpha: 283 setStrokeAlpha(value); 284 break; 285 case Property::FillAlpha: 286 setFillAlpha(value); 287 break; 288 case Property::TrimPathStart: 289 setTrimPathStart(value); 290 break; 291 case Property::TrimPathEnd: 292 setTrimPathEnd(value); 293 break; 294 case Property::TrimPathOffset: 295 setTrimPathOffset(value); 296 break; 297 default: 298 LOG_ALWAYS_FATAL("Invalid property id: %d for animation", propertyId); 299 break; 300 } 301} 302 303void ClipPath::drawPath(SkCanvas* outCanvas, const SkPath& renderPath, 304 float strokeScale, const SkMatrix& matrix){ 305 outCanvas->clipPath(renderPath, SkRegion::kIntersect_Op); 306} 307 308Group::Group(const Group& group) : Node(group) { 309 mProperties = group.mProperties; 310} 311 312void Group::draw(SkCanvas* outCanvas, const SkMatrix& currentMatrix, float scaleX, 313 float scaleY) { 314 // TODO: Try apply the matrix to the canvas instead of passing it down the tree 315 316 // Calculate current group's matrix by preConcat the parent's and 317 // and the current one on the top of the stack. 318 // Basically the Mfinal = Mviewport * M0 * M1 * M2; 319 // Mi the local matrix at level i of the group tree. 320 SkMatrix stackedMatrix; 321 getLocalMatrix(&stackedMatrix); 322 stackedMatrix.postConcat(currentMatrix); 323 324 // Save the current clip information, which is local to this group. 325 outCanvas->save(); 326 // Draw the group tree in the same order as the XML file. 327 for (auto& child : mChildren) { 328 child->draw(outCanvas, stackedMatrix, scaleX, scaleY); 329 } 330 // Restore the previous clip information. 331 outCanvas->restore(); 332} 333 334void Group::dump() { 335 ALOGD("Group %s has %zu children: ", mName.c_str(), mChildren.size()); 336 for (size_t i = 0; i < mChildren.size(); i++) { 337 mChildren[i]->dump(); 338 } 339} 340 341void Group::updateLocalMatrix(float rotate, float pivotX, float pivotY, 342 float scaleX, float scaleY, float translateX, float translateY) { 343 setRotation(rotate); 344 setPivotX(pivotX); 345 setPivotY(pivotY); 346 setScaleX(scaleX); 347 setScaleY(scaleY); 348 setTranslateX(translateX); 349 setTranslateY(translateY); 350} 351 352void Group::getLocalMatrix(SkMatrix* outMatrix) { 353 outMatrix->reset(); 354 // TODO: use rotate(mRotate, mPivotX, mPivotY) and scale with pivot point, instead of 355 // translating to pivot for rotating and scaling, then translating back. 356 outMatrix->postTranslate(-mProperties.pivotX, -mProperties.pivotY); 357 outMatrix->postScale(mProperties.scaleX, mProperties.scaleY); 358 outMatrix->postRotate(mProperties.rotate, 0, 0); 359 outMatrix->postTranslate(mProperties.translateX + mProperties.pivotX, 360 mProperties.translateY + mProperties.pivotY); 361} 362 363void Group::addChild(Node* child) { 364 mChildren.emplace_back(child); 365} 366 367bool Group::getProperties(float* outProperties, int length) { 368 int propertyCount = static_cast<int>(Property::Count); 369 if (length != propertyCount) { 370 LOG_ALWAYS_FATAL("Properties needs exactly %d bytes, a byte array of size %d is provided", 371 propertyCount, length); 372 return false; 373 } 374 Properties* out = reinterpret_cast<Properties*>(outProperties); 375 *out = mProperties; 376 return true; 377} 378 379// TODO: Consider animating the properties as float pointers 380float Group::getPropertyValue(int propertyId) const { 381 Property currentProperty = static_cast<Property>(propertyId); 382 switch (currentProperty) { 383 case Property::Rotate: 384 return mProperties.rotate; 385 case Property::PivotX: 386 return mProperties.pivotX; 387 case Property::PivotY: 388 return mProperties.pivotY; 389 case Property::ScaleX: 390 return mProperties.scaleX; 391 case Property::ScaleY: 392 return mProperties.scaleY; 393 case Property::TranslateX: 394 return mProperties.translateX; 395 case Property::TranslateY: 396 return mProperties.translateY; 397 default: 398 LOG_ALWAYS_FATAL("Invalid property index: %d", propertyId); 399 return 0; 400 } 401} 402 403void Group::setPropertyValue(int propertyId, float value) { 404 Property currentProperty = static_cast<Property>(propertyId); 405 switch (currentProperty) { 406 case Property::Rotate: 407 mProperties.rotate = value; 408 break; 409 case Property::PivotX: 410 mProperties.pivotX = value; 411 break; 412 case Property::PivotY: 413 mProperties.pivotY = value; 414 break; 415 case Property::ScaleX: 416 mProperties.scaleX = value; 417 break; 418 case Property::ScaleY: 419 mProperties.scaleY = value; 420 break; 421 case Property::TranslateX: 422 mProperties.translateX = value; 423 break; 424 case Property::TranslateY: 425 mProperties.translateY = value; 426 break; 427 default: 428 LOG_ALWAYS_FATAL("Invalid property index: %d", propertyId); 429 } 430} 431 432bool Group::isValidProperty(int propertyId) { 433 return propertyId >= 0 && propertyId < static_cast<int>(Property::Count); 434} 435 436void Tree::draw(Canvas* outCanvas, SkColorFilter* colorFilter, 437 const SkRect& bounds, bool needsMirroring, bool canReuseCache) { 438 // The imageView can scale the canvas in different ways, in order to 439 // avoid blurry scaling, we have to draw into a bitmap with exact pixel 440 // size first. This bitmap size is determined by the bounds and the 441 // canvas scale. 442 outCanvas->getMatrix(&mCanvasMatrix); 443 mBounds = bounds; 444 float canvasScaleX = 1.0f; 445 float canvasScaleY = 1.0f; 446 if (mCanvasMatrix.getSkewX() == 0 && mCanvasMatrix.getSkewY() == 0) { 447 // Only use the scale value when there's no skew or rotation in the canvas matrix. 448 // TODO: Add a cts test for drawing VD on a canvas with negative scaling factors. 449 canvasScaleX = fabs(mCanvasMatrix.getScaleX()); 450 canvasScaleY = fabs(mCanvasMatrix.getScaleY()); 451 } 452 int scaledWidth = (int) (mBounds.width() * canvasScaleX); 453 int scaledHeight = (int) (mBounds.height() * canvasScaleY); 454 scaledWidth = std::min(Tree::MAX_CACHED_BITMAP_SIZE, scaledWidth); 455 scaledHeight = std::min(Tree::MAX_CACHED_BITMAP_SIZE, scaledHeight); 456 457 if (scaledWidth <= 0 || scaledHeight <= 0) { 458 return; 459 } 460 461 mPaint.setColorFilter(colorFilter); 462 463 int saveCount = outCanvas->save(SaveFlags::MatrixClip); 464 outCanvas->translate(mBounds.fLeft, mBounds.fTop); 465 466 // Handle RTL mirroring. 467 if (needsMirroring) { 468 outCanvas->translate(mBounds.width(), 0); 469 outCanvas->scale(-1.0f, 1.0f); 470 } 471 472 // At this point, canvas has been translated to the right position. 473 // And we use this bound for the destination rect for the drawBitmap, so 474 // we offset to (0, 0); 475 mBounds.offsetTo(0, 0); 476 createCachedBitmapIfNeeded(scaledWidth, scaledHeight); 477 478 outCanvas->drawVectorDrawable(this); 479 480 outCanvas->restoreToCount(saveCount); 481} 482 483SkPaint* Tree::getPaint() { 484 SkPaint* paint; 485 if (mRootAlpha == 1.0f && mPaint.getColorFilter() == NULL) { 486 paint = NULL; 487 } else { 488 mPaint.setFilterQuality(kLow_SkFilterQuality); 489 mPaint.setAlpha(mRootAlpha * 255); 490 paint = &mPaint; 491 } 492 return paint; 493} 494 495const SkBitmap& Tree::getBitmapUpdateIfDirty() { 496 mCachedBitmap.eraseColor(SK_ColorTRANSPARENT); 497 SkCanvas outCanvas(mCachedBitmap); 498 float scaleX = (float) mCachedBitmap.width() / mViewportWidth; 499 float scaleY = (float) mCachedBitmap.height() / mViewportHeight; 500 mRootNode->draw(&outCanvas, SkMatrix::I(), scaleX, scaleY); 501 mCacheDirty = false; 502 return mCachedBitmap; 503} 504 505void Tree::createCachedBitmapIfNeeded(int width, int height) { 506 if (!canReuseBitmap(width, height)) { 507 SkImageInfo info = SkImageInfo::Make(width, height, 508 kN32_SkColorType, kPremul_SkAlphaType); 509 mCachedBitmap.setInfo(info); 510 // TODO: Count the bitmap cache against app's java heap 511 mCachedBitmap.allocPixels(info); 512 mCacheDirty = true; 513 } 514} 515 516bool Tree::canReuseBitmap(int width, int height) { 517 return width == mCachedBitmap.width() && height == mCachedBitmap.height(); 518} 519 520}; // namespace VectorDrawable 521 522}; // namespace uirenderer 523}; // namespace android 524