RecordingCanvas.cpp revision e4db79de127cfe961195f52907af8451026eaa20
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 "RecordingCanvas.h" 18 19#include "DeferredLayerUpdater.h" 20#include "RecordedOp.h" 21#include "RenderNode.h" 22 23namespace android { 24namespace uirenderer { 25 26RecordingCanvas::RecordingCanvas(size_t width, size_t height) 27 : mState(*this) 28 , mResourceCache(ResourceCache::getInstance()) { 29 reset(width, height); 30} 31 32RecordingCanvas::~RecordingCanvas() { 33 LOG_ALWAYS_FATAL_IF(mDisplayList, 34 "Destroyed a RecordingCanvas during a record!"); 35} 36 37void RecordingCanvas::reset(int width, int height) { 38 LOG_ALWAYS_FATAL_IF(mDisplayList, 39 "prepareDirty called a second time during a recording!"); 40 mDisplayList = new DisplayList(); 41 42 mState.initializeRecordingSaveStack(width, height); 43 44 mDeferredBarrierType = DeferredBarrierType::InOrder; 45 mState.setDirtyClip(false); 46 mRestoreSaveCount = -1; 47} 48 49DisplayList* RecordingCanvas::finishRecording() { 50 mPaintMap.clear(); 51 mRegionMap.clear(); 52 mPathMap.clear(); 53 DisplayList* displayList = mDisplayList; 54 mDisplayList = nullptr; 55 mSkiaCanvasProxy.reset(nullptr); 56 return displayList; 57} 58 59SkCanvas* RecordingCanvas::asSkCanvas() { 60 LOG_ALWAYS_FATAL_IF(!mDisplayList, 61 "attempting to get an SkCanvas when we are not recording!"); 62 if (!mSkiaCanvasProxy) { 63 mSkiaCanvasProxy.reset(new SkiaCanvasProxy(this)); 64 } 65 66 // SkCanvas instances default to identity transform, but should inherit 67 // the state of this Canvas; if this code was in the SkiaCanvasProxy 68 // constructor, we couldn't cache mSkiaCanvasProxy. 69 SkMatrix parentTransform; 70 getMatrix(&parentTransform); 71 mSkiaCanvasProxy.get()->setMatrix(parentTransform); 72 73 return mSkiaCanvasProxy.get(); 74} 75 76// ---------------------------------------------------------------------------- 77// CanvasStateClient implementation 78// ---------------------------------------------------------------------------- 79 80void RecordingCanvas::onViewportInitialized() { 81} 82 83void RecordingCanvas::onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) { 84 if (removed.flags & Snapshot::kFlagIsFboLayer) { 85 addOp(new (alloc()) EndLayerOp()); 86 } 87} 88 89// ---------------------------------------------------------------------------- 90// android/graphics/Canvas state operations 91// ---------------------------------------------------------------------------- 92// Save (layer) 93int RecordingCanvas::save(SkCanvas::SaveFlags flags) { 94 return mState.save((int) flags); 95} 96 97void RecordingCanvas::RecordingCanvas::restore() { 98 if (mRestoreSaveCount < 0) { 99 restoreToCount(getSaveCount() - 1); 100 return; 101 } 102 103 mRestoreSaveCount--; 104 mState.restore(); 105} 106 107void RecordingCanvas::restoreToCount(int saveCount) { 108 mRestoreSaveCount = saveCount; 109 mState.restoreToCount(saveCount); 110} 111 112int RecordingCanvas::saveLayer(float left, float top, float right, float bottom, const SkPaint* paint, 113 SkCanvas::SaveFlags flags) { 114 if (!(flags & SkCanvas::kClipToLayer_SaveFlag)) { 115 LOG_ALWAYS_FATAL("unclipped layers not supported"); 116 } 117 // force matrix/clip isolation for layer 118 flags |= SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag; 119 120 121 const Snapshot& previous = *mState.currentSnapshot(); 122 123 // initialize the snapshot as though it almost represents an FBO layer so deferred draw 124 // operations will be able to store and restore the current clip and transform info, and 125 // quick rejection will be correct (for display lists) 126 127 const Rect untransformedBounds(left, top, right, bottom); 128 129 // determine clipped bounds relative to previous viewport. 130 Rect visibleBounds = untransformedBounds; 131 previous.transform->mapRect(visibleBounds); 132 133 134 visibleBounds.doIntersect(previous.getRenderTargetClip()); 135 visibleBounds.snapToPixelBoundaries(); 136 137 Rect previousViewport(0, 0, previous.getViewportWidth(), previous.getViewportHeight()); 138 visibleBounds.doIntersect(previousViewport); 139 140 // Map visible bounds back to layer space, and intersect with parameter bounds 141 Rect layerBounds = visibleBounds; 142 Matrix4 inverse; 143 inverse.loadInverse(*previous.transform); 144 inverse.mapRect(layerBounds); 145 layerBounds.doIntersect(untransformedBounds); 146 147 int saveValue = mState.save((int) flags); 148 Snapshot& snapshot = *mState.writableSnapshot(); 149 150 // layerBounds is now original bounds, but with clipped to clip 151 // and viewport to ensure it's minimal size. 152 if (layerBounds.isEmpty() || untransformedBounds.isEmpty()) { 153 // Don't bother recording layer, since it's been rejected 154 snapshot.resetClip(0, 0, 0, 0); 155 return saveValue; 156 } 157 158 auto previousClip = getRecordedClip(); // note: done while snapshot == previous 159 160 snapshot.flags |= Snapshot::kFlagFboTarget | Snapshot::kFlagIsFboLayer; 161 snapshot.initializeViewport(untransformedBounds.getWidth(), untransformedBounds.getHeight()); 162 snapshot.transform->loadTranslate(-untransformedBounds.left, -untransformedBounds.top, 0.0f); 163 164 Rect clip = layerBounds; 165 clip.translate(-untransformedBounds.left, -untransformedBounds.top); 166 snapshot.resetClip(clip.left, clip.top, clip.right, clip.bottom); 167 snapshot.roundRectClipState = nullptr; 168 169 addOp(new (alloc()) BeginLayerOp( 170 Rect(left, top, right, bottom), 171 *previous.transform, // transform to *draw* with 172 previousClip, // clip to *draw* with 173 refPaint(paint))); 174 175 return saveValue; 176} 177 178// Matrix 179void RecordingCanvas::rotate(float degrees) { 180 if (degrees == 0) return; 181 182 mState.rotate(degrees); 183} 184 185void RecordingCanvas::scale(float sx, float sy) { 186 if (sx == 1 && sy == 1) return; 187 188 mState.scale(sx, sy); 189} 190 191void RecordingCanvas::skew(float sx, float sy) { 192 mState.skew(sx, sy); 193} 194 195void RecordingCanvas::translate(float dx, float dy) { 196 if (dx == 0 && dy == 0) return; 197 198 mState.translate(dx, dy, 0); 199} 200 201// Clip 202bool RecordingCanvas::getClipBounds(SkRect* outRect) const { 203 *outRect = mState.getLocalClipBounds().toSkRect(); 204 return !(outRect->isEmpty()); 205} 206bool RecordingCanvas::quickRejectRect(float left, float top, float right, float bottom) const { 207 return mState.quickRejectConservative(left, top, right, bottom); 208} 209bool RecordingCanvas::quickRejectPath(const SkPath& path) const { 210 SkRect bounds = path.getBounds(); 211 return mState.quickRejectConservative(bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom); 212} 213bool RecordingCanvas::clipRect(float left, float top, float right, float bottom, SkRegion::Op op) { 214 return mState.clipRect(left, top, right, bottom, op); 215} 216bool RecordingCanvas::clipPath(const SkPath* path, SkRegion::Op op) { 217 return mState.clipPath(path, op); 218} 219bool RecordingCanvas::clipRegion(const SkRegion* region, SkRegion::Op op) { 220 return mState.clipRegion(region, op); 221} 222 223// ---------------------------------------------------------------------------- 224// android/graphics/Canvas draw operations 225// ---------------------------------------------------------------------------- 226void RecordingCanvas::drawColor(int color, SkXfermode::Mode mode) { 227 SkPaint paint; 228 paint.setColor(color); 229 paint.setXfermodeMode(mode); 230 drawPaint(paint); 231} 232 233void RecordingCanvas::drawPaint(const SkPaint& paint) { 234 addOp(new (alloc()) RectOp( 235 mState.getRenderTargetClipBounds(), // OK, since we've not passed transform 236 Matrix4::identity(), 237 getRecordedClip(), 238 refPaint(&paint))); 239} 240 241static Rect calcBoundsOfPoints(const float* points, int floatCount) { 242 Rect unmappedBounds(points[0], points[1], points[0], points[1]); 243 for (int i = 2; i < floatCount; i += 2) { 244 unmappedBounds.expandToCover(points[i], points[i + 1]); 245 } 246 return unmappedBounds; 247} 248 249// Geometry 250void RecordingCanvas::drawPoints(const float* points, int floatCount, const SkPaint& paint) { 251 if (floatCount < 2) return; 252 floatCount &= ~0x1; // round down to nearest two 253 254 addOp(new (alloc()) PointsOp( 255 calcBoundsOfPoints(points, floatCount), 256 *mState.currentSnapshot()->transform, 257 getRecordedClip(), 258 refPaint(&paint), refBuffer<float>(points, floatCount), floatCount)); 259} 260 261void RecordingCanvas::drawLines(const float* points, int floatCount, const SkPaint& paint) { 262 if (floatCount < 4) return; 263 floatCount &= ~0x3; // round down to nearest four 264 265 addOp(new (alloc()) LinesOp( 266 calcBoundsOfPoints(points, floatCount), 267 *mState.currentSnapshot()->transform, 268 getRecordedClip(), 269 refPaint(&paint), refBuffer<float>(points, floatCount), floatCount)); 270} 271 272void RecordingCanvas::drawRect(float left, float top, float right, float bottom, const SkPaint& paint) { 273 addOp(new (alloc()) RectOp( 274 Rect(left, top, right, bottom), 275 *(mState.currentSnapshot()->transform), 276 getRecordedClip(), 277 refPaint(&paint))); 278} 279 280void RecordingCanvas::drawSimpleRects(const float* rects, int vertexCount, const SkPaint* paint) { 281 if (rects == nullptr) return; 282 283 Vertex* rectData = (Vertex*) mDisplayList->allocator.alloc(vertexCount * sizeof(Vertex)); 284 Vertex* vertex = rectData; 285 286 float left = FLT_MAX; 287 float top = FLT_MAX; 288 float right = FLT_MIN; 289 float bottom = FLT_MIN; 290 for (int index = 0; index < vertexCount; index += 4) { 291 float l = rects[index + 0]; 292 float t = rects[index + 1]; 293 float r = rects[index + 2]; 294 float b = rects[index + 3]; 295 296 Vertex::set(vertex++, l, t); 297 Vertex::set(vertex++, r, t); 298 Vertex::set(vertex++, l, b); 299 Vertex::set(vertex++, r, b); 300 301 left = std::min(left, l); 302 top = std::min(top, t); 303 right = std::max(right, r); 304 bottom = std::max(bottom, b); 305 } 306 addOp(new (alloc()) SimpleRectsOp( 307 Rect(left, top, right, bottom), 308 *(mState.currentSnapshot()->transform), 309 getRecordedClip(), 310 refPaint(paint), rectData, vertexCount)); 311} 312 313void RecordingCanvas::drawRegion(const SkRegion& region, const SkPaint& paint) { 314 if (paint.getStyle() == SkPaint::kFill_Style 315 && (!paint.isAntiAlias() || mState.currentTransform()->isSimple())) { 316 int count = 0; 317 Vector<float> rects; 318 SkRegion::Iterator it(region); 319 while (!it.done()) { 320 const SkIRect& r = it.rect(); 321 rects.push(r.fLeft); 322 rects.push(r.fTop); 323 rects.push(r.fRight); 324 rects.push(r.fBottom); 325 count += 4; 326 it.next(); 327 } 328 drawSimpleRects(rects.array(), count, &paint); 329 } else { 330 SkRegion::Iterator it(region); 331 while (!it.done()) { 332 const SkIRect& r = it.rect(); 333 drawRect(r.fLeft, r.fTop, r.fRight, r.fBottom, paint); 334 it.next(); 335 } 336 } 337} 338void RecordingCanvas::drawRoundRect(float left, float top, float right, float bottom, 339 float rx, float ry, const SkPaint& paint) { 340 addOp(new (alloc()) RoundRectOp( 341 Rect(left, top, right, bottom), 342 *(mState.currentSnapshot()->transform), 343 getRecordedClip(), 344 refPaint(&paint), rx, ry)); 345} 346 347void RecordingCanvas::drawRoundRect( 348 CanvasPropertyPrimitive* left, CanvasPropertyPrimitive* top, 349 CanvasPropertyPrimitive* right, CanvasPropertyPrimitive* bottom, 350 CanvasPropertyPrimitive* rx, CanvasPropertyPrimitive* ry, 351 CanvasPropertyPaint* paint) { 352 mDisplayList->ref(left); 353 mDisplayList->ref(top); 354 mDisplayList->ref(right); 355 mDisplayList->ref(bottom); 356 mDisplayList->ref(rx); 357 mDisplayList->ref(ry); 358 mDisplayList->ref(paint); 359 refBitmapsInShader(paint->value.getShader()); 360 addOp(new (alloc()) RoundRectPropsOp( 361 *(mState.currentSnapshot()->transform), 362 getRecordedClip(), 363 &paint->value, 364 &left->value, &top->value, &right->value, &bottom->value, 365 &rx->value, &ry->value)); 366} 367 368void RecordingCanvas::drawCircle(float x, float y, float radius, const SkPaint& paint) { 369 // TODO: move to Canvas.h 370 if (radius <= 0) return; 371 drawOval(x - radius, y - radius, x + radius, y + radius, paint); 372} 373 374void RecordingCanvas::drawCircle( 375 CanvasPropertyPrimitive* x, CanvasPropertyPrimitive* y, 376 CanvasPropertyPrimitive* radius, CanvasPropertyPaint* paint) { 377 mDisplayList->ref(x); 378 mDisplayList->ref(y); 379 mDisplayList->ref(radius); 380 mDisplayList->ref(paint); 381 refBitmapsInShader(paint->value.getShader()); 382 addOp(new (alloc()) CirclePropsOp( 383 *(mState.currentSnapshot()->transform), 384 getRecordedClip(), 385 &paint->value, 386 &x->value, &y->value, &radius->value)); 387} 388 389 390void RecordingCanvas::drawOval(float left, float top, float right, float bottom, const SkPaint& paint) { 391 addOp(new (alloc()) OvalOp( 392 Rect(left, top, right, bottom), 393 *(mState.currentSnapshot()->transform), 394 getRecordedClip(), 395 refPaint(&paint))); 396} 397 398void RecordingCanvas::drawArc(float left, float top, float right, float bottom, 399 float startAngle, float sweepAngle, bool useCenter, const SkPaint& paint) { 400 addOp(new (alloc()) ArcOp( 401 Rect(left, top, right, bottom), 402 *(mState.currentSnapshot()->transform), 403 getRecordedClip(), 404 refPaint(&paint), 405 startAngle, sweepAngle, useCenter)); 406} 407 408void RecordingCanvas::drawPath(const SkPath& path, const SkPaint& paint) { 409 addOp(new (alloc()) PathOp( 410 Rect(path.getBounds()), 411 *(mState.currentSnapshot()->transform), 412 getRecordedClip(), 413 refPaint(&paint), refPath(&path))); 414} 415 416// Bitmap-based 417void RecordingCanvas::drawBitmap(const SkBitmap& bitmap, float left, float top, const SkPaint* paint) { 418 save(SkCanvas::kMatrix_SaveFlag); 419 translate(left, top); 420 drawBitmap(&bitmap, paint); 421 restore(); 422} 423 424void RecordingCanvas::drawBitmap(const SkBitmap& bitmap, const SkMatrix& matrix, 425 const SkPaint* paint) { 426 if (matrix.isIdentity()) { 427 drawBitmap(&bitmap, paint); 428 } else if (!(matrix.getType() & ~(SkMatrix::kScale_Mask | SkMatrix::kTranslate_Mask)) 429 && MathUtils::isPositive(matrix.getScaleX()) 430 && MathUtils::isPositive(matrix.getScaleY())) { 431 // SkMatrix::isScaleTranslate() not available in L 432 SkRect src; 433 SkRect dst; 434 bitmap.getBounds(&src); 435 matrix.mapRect(&dst, src); 436 drawBitmap(bitmap, src.fLeft, src.fTop, src.fRight, src.fBottom, 437 dst.fLeft, dst.fTop, dst.fRight, dst.fBottom, paint); 438 } else { 439 save(SkCanvas::kMatrix_SaveFlag); 440 concat(matrix); 441 drawBitmap(&bitmap, paint); 442 restore(); 443 } 444} 445 446void RecordingCanvas::drawBitmap(const SkBitmap& bitmap, float srcLeft, float srcTop, 447 float srcRight, float srcBottom, float dstLeft, float dstTop, 448 float dstRight, float dstBottom, const SkPaint* paint) { 449 if (srcLeft == 0 && srcTop == 0 450 && srcRight == bitmap.width() 451 && srcBottom == bitmap.height() 452 && (srcBottom - srcTop == dstBottom - dstTop) 453 && (srcRight - srcLeft == dstRight - dstLeft)) { 454 // transform simple rect to rect drawing case into position bitmap ops, since they merge 455 save(SkCanvas::kMatrix_SaveFlag); 456 translate(dstLeft, dstTop); 457 drawBitmap(&bitmap, paint); 458 restore(); 459 } else { 460 addOp(new (alloc()) BitmapRectOp( 461 Rect(dstLeft, dstTop, dstRight, dstBottom), 462 *(mState.currentSnapshot()->transform), 463 getRecordedClip(), 464 refPaint(paint), refBitmap(bitmap), 465 Rect(srcLeft, srcTop, srcRight, srcBottom))); 466 } 467} 468 469void RecordingCanvas::drawBitmapMesh(const SkBitmap& bitmap, int meshWidth, int meshHeight, 470 const float* vertices, const int* colors, const SkPaint* paint) { 471 int vertexCount = (meshWidth + 1) * (meshHeight + 1); 472 addOp(new (alloc()) BitmapMeshOp( 473 calcBoundsOfPoints(vertices, vertexCount * 2), 474 *(mState.currentSnapshot()->transform), 475 getRecordedClip(), 476 refPaint(paint), refBitmap(bitmap), meshWidth, meshHeight, 477 refBuffer<float>(vertices, vertexCount * 2), // 2 floats per vertex 478 refBuffer<int>(colors, vertexCount))); // 1 color per vertex 479} 480 481void RecordingCanvas::drawNinePatch(const SkBitmap& bitmap, const android::Res_png_9patch& patch, 482 float dstLeft, float dstTop, float dstRight, float dstBottom, 483 const SkPaint* paint) { 484 addOp(new (alloc()) PatchOp( 485 Rect(dstLeft, dstTop, dstRight, dstBottom), 486 *(mState.currentSnapshot()->transform), 487 getRecordedClip(), 488 refPaint(paint), refBitmap(bitmap), refPatch(&patch))); 489} 490 491// Text 492void RecordingCanvas::drawText(const uint16_t* glyphs, const float* positions, int glyphCount, 493 const SkPaint& paint, float x, float y, float boundsLeft, float boundsTop, 494 float boundsRight, float boundsBottom, float totalAdvance) { 495 if (!glyphs || !positions || glyphCount <= 0 || PaintUtils::paintWillNotDrawText(paint)) return; 496 glyphs = refBuffer<glyph_t>(glyphs, glyphCount); 497 positions = refBuffer<float>(positions, glyphCount * 2); 498 499 // TODO: either must account for text shadow in bounds, or record separate ops for text shadows 500 addOp(new (alloc()) TextOp( 501 Rect(boundsLeft, boundsTop, boundsRight, boundsBottom), 502 *(mState.currentSnapshot()->transform), 503 getRecordedClip(), 504 refPaint(&paint), glyphs, positions, glyphCount, x, y)); 505 drawTextDecorations(x, y, totalAdvance, paint); 506} 507 508void RecordingCanvas::drawTextOnPath(const uint16_t* glyphs, int glyphCount, const SkPath& path, 509 float hOffset, float vOffset, const SkPaint& paint) { 510 if (!glyphs || glyphCount <= 0 || PaintUtils::paintWillNotDrawText(paint)) return; 511 glyphs = refBuffer<glyph_t>(glyphs, glyphCount); 512 addOp(new (alloc()) TextOnPathOp( 513 mState.getLocalClipBounds(), // TODO: explicitly define bounds 514 *(mState.currentSnapshot()->transform), 515 getRecordedClip(), 516 refPaint(&paint), glyphs, glyphCount, refPath(&path), hOffset, vOffset)); 517} 518 519void RecordingCanvas::drawBitmap(const SkBitmap* bitmap, const SkPaint* paint) { 520 addOp(new (alloc()) BitmapOp( 521 Rect(bitmap->width(), bitmap->height()), 522 *(mState.currentSnapshot()->transform), 523 getRecordedClip(), 524 refPaint(paint), refBitmap(*bitmap))); 525} 526 527void RecordingCanvas::drawRenderNode(RenderNode* renderNode) { 528 auto&& stagingProps = renderNode->stagingProperties(); 529 RenderNodeOp* op = new (alloc()) RenderNodeOp( 530 Rect(stagingProps.getWidth(), stagingProps.getHeight()), 531 *(mState.currentSnapshot()->transform), 532 getRecordedClip(), 533 renderNode); 534 int opIndex = addOp(op); 535 int childIndex = mDisplayList->addChild(op); 536 537 // update the chunk's child indices 538 DisplayList::Chunk& chunk = mDisplayList->chunks.back(); 539 chunk.endChildIndex = childIndex + 1; 540 541 if (renderNode->stagingProperties().isProjectionReceiver()) { 542 // use staging property, since recording on UI thread 543 mDisplayList->projectionReceiveIndex = opIndex; 544 } 545} 546 547void RecordingCanvas::drawLayer(DeferredLayerUpdater* layerHandle) { 548 // We ref the DeferredLayerUpdater due to its thread-safe ref-counting semantics. 549 mDisplayList->ref(layerHandle); 550 551 Layer* layer = layerHandle->backingLayer(); 552 Matrix4 totalTransform(*(mState.currentSnapshot()->transform)); 553 totalTransform.multiply(layer->getTransform()); 554 555 addOp(new (alloc()) TextureLayerOp( 556 Rect(layer->getWidth(), layer->getHeight()), 557 totalTransform, 558 getRecordedClip(), 559 layer)); 560} 561 562void RecordingCanvas::callDrawGLFunction(Functor* functor) { 563 mDisplayList->functors.push_back(functor); 564 addOp(new (alloc()) FunctorOp( 565 mState.getLocalClipBounds(), // TODO: explicitly define bounds 566 *(mState.currentSnapshot()->transform), 567 getRecordedClip(), 568 functor)); 569} 570 571size_t RecordingCanvas::addOp(RecordedOp* op) { 572 // TODO: validate if "addDrawOp" quickrejection logic is useful before adding 573 int insertIndex = mDisplayList->ops.size(); 574 mDisplayList->ops.push_back(op); 575 if (mDeferredBarrierType != DeferredBarrierType::None) { 576 // op is first in new chunk 577 mDisplayList->chunks.emplace_back(); 578 DisplayList::Chunk& newChunk = mDisplayList->chunks.back(); 579 newChunk.beginOpIndex = insertIndex; 580 newChunk.endOpIndex = insertIndex + 1; 581 newChunk.reorderChildren = (mDeferredBarrierType == DeferredBarrierType::OutOfOrder); 582 583 int nextChildIndex = mDisplayList->children.size(); 584 newChunk.beginChildIndex = newChunk.endChildIndex = nextChildIndex; 585 mDeferredBarrierType = DeferredBarrierType::None; 586 } else { 587 // standard case - append to existing chunk 588 mDisplayList->chunks.back().endOpIndex = insertIndex + 1; 589 } 590 return insertIndex; 591} 592 593void RecordingCanvas::refBitmapsInShader(const SkShader* shader) { 594 if (!shader) return; 595 596 // If this paint has an SkShader that has an SkBitmap add 597 // it to the bitmap pile 598 SkBitmap bitmap; 599 SkShader::TileMode xy[2]; 600 if (shader->isABitmap(&bitmap, nullptr, xy)) { 601 refBitmap(bitmap); 602 return; 603 } 604 SkShader::ComposeRec rec; 605 if (shader->asACompose(&rec)) { 606 refBitmapsInShader(rec.fShaderA); 607 refBitmapsInShader(rec.fShaderB); 608 return; 609 } 610} 611 612}; // namespace uirenderer 613}; // namespace android 614