VectorDrawable.cpp revision 17f40b80f6f1dccd72147209aeba3f4efd2d46f2
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 "SkColorFilter.h" 21#include "SkImageInfo.h" 22#include "SkShader.h" 23#include <utils/Log.h> 24#include "utils/Macros.h" 25#include "utils/VectorDrawableUtils.h" 26 27#include <math.h> 28#include <string.h> 29 30namespace android { 31namespace uirenderer { 32namespace VectorDrawable { 33 34const int Tree::MAX_CACHED_BITMAP_SIZE = 2048; 35 36void Path::draw(SkCanvas* outCanvas, const SkMatrix& groupStackedMatrix, float scaleX, float scaleY, 37 bool useStagingData) { 38 float matrixScale = getMatrixScale(groupStackedMatrix); 39 if (matrixScale == 0) { 40 // When either x or y is scaled to 0, we don't need to draw anything. 41 return; 42 } 43 44 SkMatrix pathMatrix(groupStackedMatrix); 45 pathMatrix.postScale(scaleX, scaleY); 46 47 //TODO: try apply the path matrix to the canvas instead of creating a new path. 48 SkPath renderPath; 49 renderPath.reset(); 50 51 if (useStagingData) { 52 SkPath tmpPath; 53 getStagingPath(&tmpPath); 54 renderPath.addPath(tmpPath, pathMatrix); 55 } else { 56 renderPath.addPath(getUpdatedPath(), pathMatrix); 57 } 58 59 float minScale = fmin(scaleX, scaleY); 60 float strokeScale = minScale * matrixScale; 61 drawPath(outCanvas, renderPath, strokeScale, pathMatrix, useStagingData); 62} 63 64void Path::dump() { 65 ALOGD("Path: %s has %zu points", mName.c_str(), mProperties.getData().points.size()); 66} 67 68float Path::getMatrixScale(const SkMatrix& groupStackedMatrix) { 69 // Given unit vectors A = (0, 1) and B = (1, 0). 70 // After matrix mapping, we got A' and B'. Let theta = the angel b/t A' and B'. 71 // Therefore, the final scale we want is min(|A'| * sin(theta), |B'| * sin(theta)), 72 // which is (|A'| * |B'| * sin(theta)) / max (|A'|, |B'|); 73 // If max (|A'|, |B'|) = 0, that means either x or y has a scale of 0. 74 // 75 // For non-skew case, which is most of the cases, matrix scale is computing exactly the 76 // scale on x and y axis, and take the minimal of these two. 77 // For skew case, an unit square will mapped to a parallelogram. And this function will 78 // return the minimal height of the 2 bases. 79 SkVector skVectors[2]; 80 skVectors[0].set(0, 1); 81 skVectors[1].set(1, 0); 82 groupStackedMatrix.mapVectors(skVectors, 2); 83 float scaleX = hypotf(skVectors[0].fX, skVectors[0].fY); 84 float scaleY = hypotf(skVectors[1].fX, skVectors[1].fY); 85 float crossProduct = skVectors[0].cross(skVectors[1]); 86 float maxScale = fmax(scaleX, scaleY); 87 88 float matrixScale = 0; 89 if (maxScale > 0) { 90 matrixScale = fabs(crossProduct) / maxScale; 91 } 92 return matrixScale; 93} 94 95// Called from UI thread during the initial setup/theme change. 96Path::Path(const char* pathStr, size_t strLength) { 97 PathParser::ParseResult result; 98 Data data; 99 PathParser::getPathDataFromAsciiString(&data, &result, pathStr, strLength); 100 mStagingProperties.setData(data); 101} 102 103Path::Path(const Path& path) : Node(path) { 104 mStagingProperties.syncProperties(path.mStagingProperties); 105} 106 107const SkPath& Path::getUpdatedPath() { 108 if (mSkPathDirty) { 109 mSkPath.reset(); 110 VectorDrawableUtils::verbsToPath(&mSkPath, mProperties.getData()); 111 mSkPathDirty = false; 112 } 113 return mSkPath; 114} 115 116void Path::getStagingPath(SkPath* outPath) { 117 outPath->reset(); 118 VectorDrawableUtils::verbsToPath(outPath, mStagingProperties.getData()); 119} 120 121void Path::syncProperties() { 122 if (mStagingPropertiesDirty) { 123 mProperties.syncProperties(mStagingProperties); 124 } else { 125 mStagingProperties.syncProperties(mProperties); 126 } 127 mStagingPropertiesDirty = false; 128} 129 130FullPath::FullPath(const FullPath& path) : Path(path) { 131 mStagingProperties.syncProperties(path.mStagingProperties); 132} 133 134static void applyTrim(SkPath* outPath, const SkPath& inPath, float trimPathStart, float trimPathEnd, 135 float trimPathOffset) { 136 if (trimPathStart == 0.0f && trimPathEnd == 1.0f) { 137 *outPath = inPath; 138 return; 139 } 140 outPath->reset(); 141 if (trimPathStart == trimPathEnd) { 142 // Trimmed path should be empty. 143 return; 144 } 145 SkPathMeasure measure(inPath, false); 146 float len = SkScalarToFloat(measure.getLength()); 147 float start = len * fmod((trimPathStart + trimPathOffset), 1.0f); 148 float end = len * fmod((trimPathEnd + trimPathOffset), 1.0f); 149 150 if (start > end) { 151 measure.getSegment(start, len, outPath, true); 152 if (end > 0) { 153 measure.getSegment(0, end, outPath, true); 154 } 155 } else { 156 measure.getSegment(start, end, outPath, true); 157 } 158} 159 160const SkPath& FullPath::getUpdatedPath() { 161 if (!mSkPathDirty && !mProperties.mTrimDirty) { 162 return mTrimmedSkPath; 163 } 164 Path::getUpdatedPath(); 165 if (mProperties.getTrimPathStart() != 0.0f || mProperties.getTrimPathEnd() != 1.0f) { 166 mProperties.mTrimDirty = false; 167 applyTrim(&mTrimmedSkPath, mSkPath, mProperties.getTrimPathStart(), 168 mProperties.getTrimPathEnd(), mProperties.getTrimPathOffset()); 169 return mTrimmedSkPath; 170 } else { 171 return mSkPath; 172 } 173} 174 175void FullPath::getStagingPath(SkPath* outPath) { 176 Path::getStagingPath(outPath); 177 SkPath inPath = *outPath; 178 applyTrim(outPath, inPath, mStagingProperties.getTrimPathStart(), 179 mStagingProperties.getTrimPathEnd(), mStagingProperties.getTrimPathOffset()); 180} 181 182void FullPath::dump() { 183 Path::dump(); 184 ALOGD("stroke width, color, alpha: %f, %d, %f, fill color, alpha: %d, %f", 185 mProperties.getStrokeWidth(), mProperties.getStrokeColor(), mProperties.getStrokeAlpha(), 186 mProperties.getFillColor(), mProperties.getFillAlpha()); 187} 188 189 190inline SkColor applyAlpha(SkColor color, float alpha) { 191 int alphaBytes = SkColorGetA(color); 192 return SkColorSetA(color, alphaBytes * alpha); 193} 194 195void FullPath::drawPath(SkCanvas* outCanvas, SkPath& renderPath, float strokeScale, 196 const SkMatrix& matrix, bool useStagingData){ 197 const FullPathProperties& properties = useStagingData ? mStagingProperties : mProperties; 198 199 // Draw path's fill, if fill color or gradient is valid 200 bool needsFill = false; 201 SkPaint paint; 202 if (properties.getFillGradient() != nullptr) { 203 paint.setColor(applyAlpha(SK_ColorBLACK, properties.getFillAlpha())); 204 SkShader* newShader = properties.getFillGradient()->newWithLocalMatrix(matrix); 205 // newWithLocalMatrix(...) creates a new SkShader and returns a bare pointer. We need to 206 // remove the extra ref so that the ref count is correctly managed. 207 paint.setShader(newShader)->unref(); 208 needsFill = true; 209 } else if (properties.getFillColor() != SK_ColorTRANSPARENT) { 210 paint.setColor(applyAlpha(properties.getFillColor(), properties.getFillAlpha())); 211 needsFill = true; 212 } 213 214 if (needsFill) { 215 paint.setStyle(SkPaint::Style::kFill_Style); 216 paint.setAntiAlias(true); 217 SkPath::FillType ft = static_cast<SkPath::FillType>(properties.getFillType()); 218 renderPath.setFillType(ft); 219 outCanvas->drawPath(renderPath, paint); 220 } 221 222 // Draw path's stroke, if stroke color or Gradient is valid 223 bool needsStroke = false; 224 if (properties.getStrokeGradient() != nullptr) { 225 paint.setColor(applyAlpha(SK_ColorBLACK, properties.getStrokeAlpha())); 226 SkShader* newShader = properties.getStrokeGradient()->newWithLocalMatrix(matrix); 227 // newWithLocalMatrix(...) creates a new SkShader and returns a bare pointer. We need to 228 // remove the extra ref so that the ref count is correctly managed. 229 paint.setShader(newShader)->unref(); 230 needsStroke = true; 231 } else if (properties.getStrokeColor() != SK_ColorTRANSPARENT) { 232 paint.setColor(applyAlpha(properties.getStrokeColor(), properties.getStrokeAlpha())); 233 needsStroke = true; 234 } 235 if (needsStroke) { 236 paint.setStyle(SkPaint::Style::kStroke_Style); 237 paint.setAntiAlias(true); 238 paint.setStrokeJoin(SkPaint::Join(properties.getStrokeLineJoin())); 239 paint.setStrokeCap(SkPaint::Cap(properties.getStrokeLineCap())); 240 paint.setStrokeMiter(properties.getStrokeMiterLimit()); 241 paint.setStrokeWidth(properties.getStrokeWidth() * strokeScale); 242 outCanvas->drawPath(renderPath, paint); 243 } 244} 245 246void FullPath::syncProperties() { 247 Path::syncProperties(); 248 249 if (mStagingPropertiesDirty) { 250 mProperties.syncProperties(mStagingProperties); 251 } else { 252 // Update staging property with property values from animation. 253 mStagingProperties.syncProperties(mProperties); 254 } 255 mStagingPropertiesDirty = false; 256} 257 258REQUIRE_COMPATIBLE_LAYOUT(FullPath::FullPathProperties::PrimitiveFields); 259 260static_assert(sizeof(float) == sizeof(int32_t), "float is not the same size as int32_t"); 261static_assert(sizeof(SkColor) == sizeof(int32_t), "SkColor is not the same size as int32_t"); 262 263bool FullPath::FullPathProperties::copyProperties(int8_t* outProperties, int length) const { 264 int propertyDataSize = sizeof(FullPathProperties::PrimitiveFields); 265 if (length != propertyDataSize) { 266 LOG_ALWAYS_FATAL("Properties needs exactly %d bytes, a byte array of size %d is provided", 267 propertyDataSize, length); 268 return false; 269 } 270 271 PrimitiveFields* out = reinterpret_cast<PrimitiveFields*>(outProperties); 272 *out = mPrimitiveFields; 273 return true; 274} 275 276void FullPath::FullPathProperties::setColorPropertyValue(int propertyId, int32_t value) { 277 Property currentProperty = static_cast<Property>(propertyId); 278 if (currentProperty == Property::strokeColor) { 279 setStrokeColor(value); 280 } else if (currentProperty == Property::fillColor) { 281 setFillColor(value); 282 } else { 283 LOG_ALWAYS_FATAL("Error setting color property on FullPath: No valid property" 284 " with id: %d", propertyId); 285 } 286} 287 288void FullPath::FullPathProperties::setPropertyValue(int propertyId, float value) { 289 Property property = static_cast<Property>(propertyId); 290 switch (property) { 291 case Property::strokeWidth: 292 setStrokeWidth(value); 293 break; 294 case Property::strokeAlpha: 295 setStrokeAlpha(value); 296 break; 297 case Property::fillAlpha: 298 setFillAlpha(value); 299 break; 300 case Property::trimPathStart: 301 setTrimPathStart(value); 302 break; 303 case Property::trimPathEnd: 304 setTrimPathEnd(value); 305 break; 306 case Property::trimPathOffset: 307 setTrimPathOffset(value); 308 break; 309 default: 310 LOG_ALWAYS_FATAL("Invalid property id: %d for animation", propertyId); 311 break; 312 } 313} 314 315void ClipPath::drawPath(SkCanvas* outCanvas, SkPath& renderPath, 316 float strokeScale, const SkMatrix& matrix, bool useStagingData){ 317 outCanvas->clipPath(renderPath, SkRegion::kIntersect_Op); 318} 319 320Group::Group(const Group& group) : Node(group) { 321 mStagingProperties.syncProperties(group.mStagingProperties); 322} 323 324void Group::draw(SkCanvas* outCanvas, const SkMatrix& currentMatrix, float scaleX, 325 float scaleY, bool useStagingData) { 326 // TODO: Try apply the matrix to the canvas instead of passing it down the tree 327 328 // Calculate current group's matrix by preConcat the parent's and 329 // and the current one on the top of the stack. 330 // Basically the Mfinal = Mviewport * M0 * M1 * M2; 331 // Mi the local matrix at level i of the group tree. 332 SkMatrix stackedMatrix; 333 const GroupProperties& prop = useStagingData ? mStagingProperties : mProperties; 334 getLocalMatrix(&stackedMatrix, prop); 335 stackedMatrix.postConcat(currentMatrix); 336 337 // Save the current clip information, which is local to this group. 338 outCanvas->save(); 339 // Draw the group tree in the same order as the XML file. 340 for (auto& child : mChildren) { 341 child->draw(outCanvas, stackedMatrix, scaleX, scaleY, useStagingData); 342 } 343 // Restore the previous clip information. 344 outCanvas->restore(); 345} 346 347void Group::dump() { 348 ALOGD("Group %s has %zu children: ", mName.c_str(), mChildren.size()); 349 ALOGD("Group translateX, Y : %f, %f, scaleX, Y: %f, %f", mProperties.getTranslateX(), 350 mProperties.getTranslateY(), mProperties.getScaleX(), mProperties.getScaleY()); 351 for (size_t i = 0; i < mChildren.size(); i++) { 352 mChildren[i]->dump(); 353 } 354} 355 356void Group::syncProperties() { 357 // Copy over the dirty staging properties 358 if (mStagingPropertiesDirty) { 359 mProperties.syncProperties(mStagingProperties); 360 } else { 361 mStagingProperties.syncProperties(mProperties); 362 } 363 mStagingPropertiesDirty = false; 364 for (auto& child : mChildren) { 365 child->syncProperties(); 366 } 367} 368 369void Group::getLocalMatrix(SkMatrix* outMatrix, const GroupProperties& properties) { 370 outMatrix->reset(); 371 // TODO: use rotate(mRotate, mPivotX, mPivotY) and scale with pivot point, instead of 372 // translating to pivot for rotating and scaling, then translating back. 373 outMatrix->postTranslate(-properties.getPivotX(), -properties.getPivotY()); 374 outMatrix->postScale(properties.getScaleX(), properties.getScaleY()); 375 outMatrix->postRotate(properties.getRotation(), 0, 0); 376 outMatrix->postTranslate(properties.getTranslateX() + properties.getPivotX(), 377 properties.getTranslateY() + properties.getPivotY()); 378} 379 380void Group::addChild(Node* child) { 381 mChildren.emplace_back(child); 382 if (mPropertyChangedListener != nullptr) { 383 child->setPropertyChangedListener(mPropertyChangedListener); 384 } 385} 386 387bool Group::GroupProperties::copyProperties(float* outProperties, int length) const { 388 int propertyCount = static_cast<int>(Property::count); 389 if (length != propertyCount) { 390 LOG_ALWAYS_FATAL("Properties needs exactly %d bytes, a byte array of size %d is provided", 391 propertyCount, length); 392 return false; 393 } 394 395 PrimitiveFields* out = reinterpret_cast<PrimitiveFields*>(outProperties); 396 *out = mPrimitiveFields; 397 return true; 398} 399 400// TODO: Consider animating the properties as float pointers 401// Called on render thread 402float Group::GroupProperties::getPropertyValue(int propertyId) const { 403 Property currentProperty = static_cast<Property>(propertyId); 404 switch (currentProperty) { 405 case Property::rotate: 406 return getRotation(); 407 case Property::pivotX: 408 return getPivotX(); 409 case Property::pivotY: 410 return getPivotY(); 411 case Property::scaleX: 412 return getScaleX(); 413 case Property::scaleY: 414 return getScaleY(); 415 case Property::translateX: 416 return getTranslateX(); 417 case Property::translateY: 418 return getTranslateY(); 419 default: 420 LOG_ALWAYS_FATAL("Invalid property index: %d", propertyId); 421 return 0; 422 } 423} 424 425// Called on render thread 426void Group::GroupProperties::setPropertyValue(int propertyId, float value) { 427 Property currentProperty = static_cast<Property>(propertyId); 428 switch (currentProperty) { 429 case Property::rotate: 430 setRotation(value); 431 break; 432 case Property::pivotX: 433 setPivotX(value); 434 break; 435 case Property::pivotY: 436 setPivotY(value); 437 break; 438 case Property::scaleX: 439 setScaleX(value); 440 break; 441 case Property::scaleY: 442 setScaleY(value); 443 break; 444 case Property::translateX: 445 setTranslateX(value); 446 break; 447 case Property::translateY: 448 setTranslateY(value); 449 break; 450 default: 451 LOG_ALWAYS_FATAL("Invalid property index: %d", propertyId); 452 } 453} 454 455bool Group::isValidProperty(int propertyId) { 456 return GroupProperties::isValidProperty(propertyId); 457} 458 459bool Group::GroupProperties::isValidProperty(int propertyId) { 460 return propertyId >= 0 && propertyId < static_cast<int>(Property::count); 461} 462 463int Tree::draw(Canvas* outCanvas, SkColorFilter* colorFilter, 464 const SkRect& bounds, bool needsMirroring, bool canReuseCache) { 465 // The imageView can scale the canvas in different ways, in order to 466 // avoid blurry scaling, we have to draw into a bitmap with exact pixel 467 // size first. This bitmap size is determined by the bounds and the 468 // canvas scale. 469 SkMatrix canvasMatrix; 470 outCanvas->getMatrix(&canvasMatrix); 471 float canvasScaleX = 1.0f; 472 float canvasScaleY = 1.0f; 473 if (canvasMatrix.getSkewX() == 0 && canvasMatrix.getSkewY() == 0) { 474 // Only use the scale value when there's no skew or rotation in the canvas matrix. 475 // TODO: Add a cts test for drawing VD on a canvas with negative scaling factors. 476 canvasScaleX = fabs(canvasMatrix.getScaleX()); 477 canvasScaleY = fabs(canvasMatrix.getScaleY()); 478 } 479 int scaledWidth = (int) (bounds.width() * canvasScaleX); 480 int scaledHeight = (int) (bounds.height() * canvasScaleY); 481 scaledWidth = std::min(Tree::MAX_CACHED_BITMAP_SIZE, scaledWidth); 482 scaledHeight = std::min(Tree::MAX_CACHED_BITMAP_SIZE, scaledHeight); 483 484 if (scaledWidth <= 0 || scaledHeight <= 0) { 485 return 0; 486 } 487 488 mStagingProperties.setScaledSize(scaledWidth, scaledHeight); 489 int saveCount = outCanvas->save(SaveFlags::MatrixClip); 490 outCanvas->translate(bounds.fLeft, bounds.fTop); 491 492 // Handle RTL mirroring. 493 if (needsMirroring) { 494 outCanvas->translate(bounds.width(), 0); 495 outCanvas->scale(-1.0f, 1.0f); 496 } 497 mStagingProperties.setColorFilter(colorFilter); 498 499 // At this point, canvas has been translated to the right position. 500 // And we use this bound for the destination rect for the drawBitmap, so 501 // we offset to (0, 0); 502 SkRect tmpBounds = bounds; 503 tmpBounds.offsetTo(0, 0); 504 mStagingProperties.setBounds(tmpBounds); 505 outCanvas->drawVectorDrawable(this); 506 outCanvas->restoreToCount(saveCount); 507 return scaledWidth * scaledHeight; 508} 509 510void Tree::drawStaging(Canvas* outCanvas) { 511 bool redrawNeeded = allocateBitmapIfNeeded(&mStagingCache.bitmap, 512 mStagingProperties.getScaledWidth(), mStagingProperties.getScaledHeight()); 513 // draw bitmap cache 514 if (redrawNeeded || mStagingCache.dirty) { 515 updateBitmapCache(&mStagingCache.bitmap, true); 516 mStagingCache.dirty = false; 517 } 518 519 SkPaint tmpPaint; 520 SkPaint* paint = updatePaint(&tmpPaint, &mStagingProperties); 521 outCanvas->drawBitmap(mStagingCache.bitmap, 0, 0, 522 mStagingCache.bitmap.width(), mStagingCache.bitmap.height(), 523 mStagingProperties.getBounds().left(), mStagingProperties.getBounds().top(), 524 mStagingProperties.getBounds().right(), mStagingProperties.getBounds().bottom(), paint); 525} 526 527SkPaint* Tree::getPaint() { 528 return updatePaint(&mPaint, &mProperties); 529} 530 531// Update the given paint with alpha and color filter. Return nullptr if no color filter is 532// specified and root alpha is 1. Otherwise, return updated paint. 533SkPaint* Tree::updatePaint(SkPaint* outPaint, TreeProperties* prop) { 534 if (prop->getRootAlpha() == 1.0f && prop->getColorFilter() == nullptr) { 535 return nullptr; 536 } else { 537 outPaint->setColorFilter(prop->getColorFilter()); 538 outPaint->setFilterQuality(kLow_SkFilterQuality); 539 outPaint->setAlpha(prop->getRootAlpha() * 255); 540 return outPaint; 541 } 542} 543 544const SkBitmap& Tree::getBitmapUpdateIfDirty() { 545 bool redrawNeeded = allocateBitmapIfNeeded(&mCache.bitmap, mProperties.getScaledWidth(), 546 mProperties.getScaledHeight()); 547 if (redrawNeeded || mCache.dirty) { 548 updateBitmapCache(&mCache.bitmap, false); 549 mCache.dirty = false; 550 } 551 return mCache.bitmap; 552} 553 554void Tree::updateBitmapCache(SkBitmap* outCache, bool useStagingData) { 555 outCache->eraseColor(SK_ColorTRANSPARENT); 556 SkCanvas outCanvas(*outCache); 557 float viewportWidth = useStagingData ? 558 mStagingProperties.getViewportWidth() : mProperties.getViewportWidth(); 559 float viewportHeight = useStagingData ? 560 mStagingProperties.getViewportHeight() : mProperties.getViewportHeight(); 561 float scaleX = outCache->width() / viewportWidth; 562 float scaleY = outCache->height() / viewportHeight; 563 mRootNode->draw(&outCanvas, SkMatrix::I(), scaleX, scaleY, useStagingData); 564} 565 566bool Tree::allocateBitmapIfNeeded(SkBitmap* outCache, int width, int height) { 567 if (!canReuseBitmap(*outCache, width, height)) { 568 SkImageInfo info = SkImageInfo::Make(width, height, 569 kN32_SkColorType, kPremul_SkAlphaType); 570 outCache->setInfo(info); 571 // TODO: Count the bitmap cache against app's java heap 572 outCache->allocPixels(info); 573 return true; 574 } 575 return false; 576} 577 578bool Tree::canReuseBitmap(const SkBitmap& bitmap, int width, int height) { 579 return width <= bitmap.width() && height <= bitmap.height(); 580} 581 582void Tree::onPropertyChanged(TreeProperties* prop) { 583 if (prop == &mStagingProperties) { 584 mStagingCache.dirty = true; 585 } else { 586 mCache.dirty = true; 587 } 588} 589 590}; // namespace VectorDrawable 591 592}; // namespace uirenderer 593}; // namespace android 594