2 * Copyright 2012 Google Inc.
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
8 #include "PictureRenderer.h"
9 #include "picture_utils.h"
10 #include "SamplePipeControllers.h"
11 #include "SkBitmapHasher.h"
15 #include "SkDiscardableMemoryPool.h"
18 #include "gl/GrGLDefines.h"
19 #include "SkGpuDevice.h"
21 #include "SkGraphics.h"
22 #include "SkImageEncoder.h"
23 #include "SkMaskFilter.h"
26 #include "SkPicture.h"
27 #include "SkPictureRecorder.h"
28 #include "SkPictureUtils.h"
29 #include "SkPixelRef.h"
33 #include "SkTemplates.h"
34 #include "SkTDArray.h"
35 #include "SkThreadUtils.h"
38 static inline SkScalar scalar_log2(SkScalar x) {
39 static const SkScalar log2_conversion_factor = SkScalarDiv(1, SkScalarLog(2));
41 return SkScalarLog(x) * log2_conversion_factor;
47 kDefaultTileWidth = 256,
48 kDefaultTileHeight = 256
51 void PictureRenderer::init(const SkPicture* pict,
52 const SkString* writePath,
53 const SkString* mismatchPath,
54 const SkString* inputFilename,
55 bool useChecksumBasedFilenames) {
56 this->CopyString(&fWritePath, writePath);
57 this->CopyString(&fMismatchPath, mismatchPath);
58 this->CopyString(&fInputFilename, inputFilename);
59 fUseChecksumBasedFilenames = useChecksumBasedFilenames;
61 SkASSERT(NULL == fPicture);
62 SkASSERT(NULL == fCanvas.get());
63 if (fPicture || fCanvas.get()) {
67 SkASSERT(pict != NULL);
72 fPicture.reset(pict)->ref();
73 fCanvas.reset(this->setupCanvas());
76 void PictureRenderer::CopyString(SkString* dest, const SkString* src) {
84 class FlagsDrawFilter : public SkDrawFilter {
86 FlagsDrawFilter(PictureRenderer::DrawFilterFlags* flags) :
89 virtual bool filter(SkPaint* paint, Type t) {
90 paint->setFlags(paint->getFlags() & ~fFlags[t] & SkPaint::kAllFlags);
91 if (PictureRenderer::kMaskFilter_DrawFilterFlag & fFlags[t]) {
92 SkMaskFilter* maskFilter = paint->getMaskFilter();
94 paint->setMaskFilter(NULL);
97 if (PictureRenderer::kHinting_DrawFilterFlag & fFlags[t]) {
98 paint->setHinting(SkPaint::kNo_Hinting);
99 } else if (PictureRenderer::kSlightHinting_DrawFilterFlag & fFlags[t]) {
100 paint->setHinting(SkPaint::kSlight_Hinting);
106 PictureRenderer::DrawFilterFlags* fFlags;
109 static void setUpFilter(SkCanvas* canvas, PictureRenderer::DrawFilterFlags* drawFilters) {
110 if (drawFilters && !canvas->getDrawFilter()) {
111 canvas->setDrawFilter(SkNEW_ARGS(FlagsDrawFilter, (drawFilters)))->unref();
112 if (drawFilters[0] & PictureRenderer::kAAClip_DrawFilterFlag) {
113 canvas->setAllowSoftClip(false);
118 SkCanvas* PictureRenderer::setupCanvas() {
119 const int width = this->getViewWidth();
120 const int height = this->getViewHeight();
121 return this->setupCanvas(width, height);
124 SkCanvas* PictureRenderer::setupCanvas(int width, int height) {
126 switch(fDeviceType) {
127 case kBitmap_DeviceType: {
129 sk_tools::setup_bitmap(&bitmap, width, height);
130 canvas = SkNEW_ARGS(SkCanvas, (bitmap));
135 case kAngle_DeviceType:
139 case kMesa_DeviceType:
142 case kGPU_DeviceType:
143 case kNVPR_DeviceType: {
144 SkAutoTUnref<GrSurface> target;
146 // create a render target to back the device
148 desc.fConfig = kSkia8888_GrPixelConfig;
149 desc.fFlags = kRenderTarget_GrTextureFlagBit;
151 desc.fHeight = height;
152 desc.fSampleCnt = fSampleCount;
153 target.reset(fGrContext->createUncachedTexture(desc, NULL, 0));
155 if (NULL == target.get()) {
160 SkAutoTUnref<SkGpuDevice> device(SkGpuDevice::Create(target,
161 SkSurfaceProps(SkSurfaceProps::kLegacyFontHost_InitType)));
162 canvas = SkNEW_ARGS(SkCanvas, (device.get()));
170 setUpFilter(canvas, fDrawFilters);
171 this->scaleToScaleFactor(canvas);
173 // Pictures often lie about their extent (i.e., claim to be 100x100 but
174 // only ever draw to 90x100). Clear here so the undrawn portion will have
175 // a consistent color
176 canvas->clear(SK_ColorTRANSPARENT);
180 void PictureRenderer::scaleToScaleFactor(SkCanvas* canvas) {
181 SkASSERT(canvas != NULL);
182 if (fScaleFactor != SK_Scalar1) {
183 canvas->scale(fScaleFactor, fScaleFactor);
187 void PictureRenderer::end() {
188 this->resetState(true);
189 fPicture.reset(NULL);
193 int PictureRenderer::getViewWidth() {
194 SkASSERT(fPicture != NULL);
195 int width = SkScalarCeilToInt(fPicture->cullRect().width() * fScaleFactor);
196 if (fViewport.width() > 0) {
197 width = SkMin32(width, fViewport.width());
202 int PictureRenderer::getViewHeight() {
203 SkASSERT(fPicture != NULL);
204 int height = SkScalarCeilToInt(fPicture->cullRect().height() * fScaleFactor);
205 if (fViewport.height() > 0) {
206 height = SkMin32(height, fViewport.height());
211 /** Converts fPicture to a picture that uses a BBoxHierarchy.
212 * PictureRenderer subclasses that are used to test picture playback
213 * should call this method during init.
215 void PictureRenderer::buildBBoxHierarchy() {
217 if (kNone_BBoxHierarchyType != fBBoxHierarchyType && fPicture) {
218 SkAutoTDelete<SkBBHFactory> factory(this->getFactory());
219 SkPictureRecorder recorder;
220 SkCanvas* canvas = recorder.beginRecording(fPicture->cullRect().width(),
221 fPicture->cullRect().height(),
223 this->recordFlags());
224 fPicture->playback(canvas);
225 fPicture.reset(recorder.endRecording());
229 void PictureRenderer::resetState(bool callFinish) {
231 SkGLContextHelper* glContext = this->getGLContext();
232 if (NULL == glContext) {
233 SkASSERT(kBitmap_DeviceType == fDeviceType);
238 glContext->swapBuffers();
240 SK_GL(*glContext, Finish());
245 void PictureRenderer::purgeTextures() {
246 SkDiscardableMemoryPool* pool = SkGetGlobalDiscardableMemoryPool();
251 SkGLContextHelper* glContext = this->getGLContext();
252 if (NULL == glContext) {
253 SkASSERT(kBitmap_DeviceType == fDeviceType);
257 // resetState should've already done this
260 fGrContext->purgeAllUnlockedResources();
265 * Write the canvas to an image file and/or JSON summary.
267 * @param canvas Must be non-null. Canvas to be written to a file.
268 * @param writePath If nonempty, write the binary image to a file within this directory.
269 * @param mismatchPath If nonempty, write the binary image to a file within this directory,
270 * but only if the image does not match expectations.
271 * @param inputFilename If we are writing out a binary image, use this to build its filename.
272 * @param jsonSummaryPtr If not null, add image results (checksum) to this summary.
273 * @param useChecksumBasedFilenames If true, use checksum-based filenames when writing to disk.
274 * @param tileNumberPtr If not null, which tile number this image contains.
276 * @return bool True if the operation completed successfully.
278 static bool write(SkCanvas* canvas, const SkString& writePath, const SkString& mismatchPath,
279 const SkString& inputFilename, ImageResultsAndExpectations *jsonSummaryPtr,
280 bool useChecksumBasedFilenames, const int* tileNumberPtr=NULL) {
281 SkASSERT(canvas != NULL);
282 if (NULL == canvas) {
287 SkISize size = canvas->getDeviceSize();
288 setup_bitmap(&bitmap, size.width(), size.height());
290 canvas->readPixels(&bitmap, 0, 0);
291 force_all_opaque(bitmap);
292 BitmapAndDigest bitmapAndDigest(bitmap);
294 SkString escapedInputFilename(inputFilename);
295 replace_char(&escapedInputFilename, '.', '_');
297 // TODO(epoger): what about including the config type within outputFilename? That way,
298 // we could combine results of different config types without conflicting filenames.
299 SkString outputFilename;
300 const char *outputSubdirPtr = NULL;
301 if (useChecksumBasedFilenames) {
302 ImageDigest *imageDigestPtr = bitmapAndDigest.getImageDigestPtr();
303 outputSubdirPtr = escapedInputFilename.c_str();
304 outputFilename.set(imageDigestPtr->getHashType());
305 outputFilename.append("_");
306 outputFilename.appendU64(imageDigestPtr->getHashValue());
308 outputFilename.set(escapedInputFilename);
310 outputFilename.append("-tile");
311 outputFilename.appendS32(*tileNumberPtr);
314 outputFilename.append(".png");
316 if (jsonSummaryPtr) {
317 ImageDigest *imageDigestPtr = bitmapAndDigest.getImageDigestPtr();
318 SkString outputRelativePath;
319 if (outputSubdirPtr) {
320 outputRelativePath.set(outputSubdirPtr);
321 outputRelativePath.append("/"); // always use "/", even on Windows
322 outputRelativePath.append(outputFilename);
324 outputRelativePath.set(outputFilename);
327 jsonSummaryPtr->add(inputFilename.c_str(), outputRelativePath.c_str(),
328 *imageDigestPtr, tileNumberPtr);
329 if (!mismatchPath.isEmpty() &&
330 !jsonSummaryPtr->getExpectation(inputFilename.c_str(),
331 tileNumberPtr).matches(*imageDigestPtr)) {
332 if (!write_bitmap_to_disk(bitmap, mismatchPath, outputSubdirPtr, outputFilename)) {
338 if (writePath.isEmpty()) {
341 return write_bitmap_to_disk(bitmap, writePath, outputSubdirPtr, outputFilename);
345 ///////////////////////////////////////////////////////////////////////////////////////////////
347 SkCanvas* RecordPictureRenderer::setupCanvas(int width, int height) {
348 // defer the canvas setup until the render step
352 // the size_t* parameter is deprecated, so we ignore it
353 static SkData* encode_bitmap_to_data(size_t*, const SkBitmap& bm) {
354 return SkImageEncoder::EncodeData(bm, SkImageEncoder::kPNG_Type, 100);
357 bool RecordPictureRenderer::render(SkBitmap** out) {
358 SkAutoTDelete<SkBBHFactory> factory(this->getFactory());
359 SkPictureRecorder recorder;
360 SkCanvas* canvas = recorder.beginRecording(SkIntToScalar(this->getViewWidth()),
361 SkIntToScalar(this->getViewHeight()),
363 this->recordFlags());
364 this->scaleToScaleFactor(canvas);
365 fPicture->playback(canvas);
366 SkAutoTUnref<SkPicture> picture(recorder.endRecording());
367 if (!fWritePath.isEmpty()) {
368 // Record the new picture as a new SKP with PNG encoded bitmaps.
369 SkString skpPath = SkOSPath::Join(fWritePath.c_str(), fInputFilename.c_str());
370 SkFILEWStream stream(skpPath.c_str());
371 picture->serialize(&stream, &encode_bitmap_to_data);
377 SkString RecordPictureRenderer::getConfigNameInternal() {
378 return SkString("record");
381 ///////////////////////////////////////////////////////////////////////////////////////////////
383 bool PipePictureRenderer::render(SkBitmap** out) {
384 SkASSERT(fCanvas.get() != NULL);
385 SkASSERT(fPicture != NULL);
386 if (NULL == fCanvas.get() || NULL == fPicture) {
390 PipeController pipeController(fCanvas.get());
391 SkGPipeWriter writer;
392 SkCanvas* pipeCanvas = writer.startRecording(&pipeController);
393 pipeCanvas->drawPicture(fPicture);
394 writer.endRecording();
397 *out = SkNEW(SkBitmap);
398 setup_bitmap(*out, SkScalarCeilToInt(fPicture->cullRect().width()),
399 SkScalarCeilToInt(fPicture->cullRect().height()));
400 fCanvas->readPixels(*out, 0, 0);
403 return write(fCanvas, fWritePath, fMismatchPath, fInputFilename, fJsonSummaryPtr,
404 fUseChecksumBasedFilenames);
410 SkString PipePictureRenderer::getConfigNameInternal() {
411 return SkString("pipe");
414 ///////////////////////////////////////////////////////////////////////////////////////////////
416 void SimplePictureRenderer::init(const SkPicture* picture, const SkString* writePath,
417 const SkString* mismatchPath, const SkString* inputFilename,
418 bool useChecksumBasedFilenames) {
419 INHERITED::init(picture, writePath, mismatchPath, inputFilename, useChecksumBasedFilenames);
420 this->buildBBoxHierarchy();
423 bool SimplePictureRenderer::render(SkBitmap** out) {
424 SkASSERT(fCanvas.get() != NULL);
426 if (NULL == fCanvas.get() || NULL == fPicture) {
430 fCanvas->drawPicture(fPicture);
433 *out = SkNEW(SkBitmap);
434 setup_bitmap(*out, SkScalarCeilToInt(fPicture->cullRect().width()),
435 SkScalarCeilToInt(fPicture->cullRect().height()));
436 fCanvas->readPixels(*out, 0, 0);
439 return write(fCanvas, fWritePath, fMismatchPath, fInputFilename, fJsonSummaryPtr,
440 fUseChecksumBasedFilenames);
446 SkString SimplePictureRenderer::getConfigNameInternal() {
447 return SkString("simple");
450 ///////////////////////////////////////////////////////////////////////////////////////////////
453 TiledPictureRenderer::TiledPictureRenderer(const GrContext::Options& opts)
455 , fTileWidth(kDefaultTileWidth)
457 TiledPictureRenderer::TiledPictureRenderer()
458 : fTileWidth(kDefaultTileWidth)
460 , fTileHeight(kDefaultTileHeight)
461 , fTileWidthPercentage(0.0)
462 , fTileHeightPercentage(0.0)
463 , fTileMinPowerOf2Width(0)
464 , fCurrentTileOffset(-1)
468 void TiledPictureRenderer::init(const SkPicture* pict, const SkString* writePath,
469 const SkString* mismatchPath, const SkString* inputFilename,
470 bool useChecksumBasedFilenames) {
472 SkASSERT(0 == fTileRects.count());
473 if (NULL == pict || fTileRects.count() != 0) {
477 // Do not call INHERITED::init(), which would create a (potentially large) canvas which is not
478 // used by bench_pictures.
479 fPicture.reset(pict)->ref();
480 this->CopyString(&fWritePath, writePath);
481 this->CopyString(&fMismatchPath, mismatchPath);
482 this->CopyString(&fInputFilename, inputFilename);
483 fUseChecksumBasedFilenames = useChecksumBasedFilenames;
484 this->buildBBoxHierarchy();
486 if (fTileWidthPercentage > 0) {
487 fTileWidth = SkScalarCeilToInt(float(fTileWidthPercentage * fPicture->cullRect().width() / 100));
489 if (fTileHeightPercentage > 0) {
490 fTileHeight = SkScalarCeilToInt(float(fTileHeightPercentage * fPicture->cullRect().height() / 100));
493 if (fTileMinPowerOf2Width > 0) {
494 this->setupPowerOf2Tiles();
498 fCanvas.reset(this->setupCanvas(fTileWidth, fTileHeight));
499 // Initialize to -1 so that the first call to nextTile will set this up to draw tile 0 on the
500 // first call to drawCurrentTile.
501 fCurrentTileOffset = -1;
504 void TiledPictureRenderer::end() {
506 this->INHERITED::end();
509 void TiledPictureRenderer::setupTiles() {
510 // Only use enough tiles to cover the viewport
511 const int width = this->getViewWidth();
512 const int height = this->getViewHeight();
514 fTilesX = fTilesY = 0;
515 for (int tile_y_start = 0; tile_y_start < height; tile_y_start += fTileHeight) {
517 for (int tile_x_start = 0; tile_x_start < width; tile_x_start += fTileWidth) {
518 if (0 == tile_y_start) {
519 // Only count tiles in the X direction on the first pass.
522 *fTileRects.append() = SkRect::MakeXYWH(SkIntToScalar(tile_x_start),
523 SkIntToScalar(tile_y_start),
524 SkIntToScalar(fTileWidth),
525 SkIntToScalar(fTileHeight));
530 bool TiledPictureRenderer::tileDimensions(int &x, int &y) {
531 if (fTileRects.count() == 0 || NULL == fPicture) {
539 // The goal of the powers of two tiles is to minimize the amount of wasted tile
540 // space in the width-wise direction and then minimize the number of tiles. The
541 // constraints are that every tile must have a pixel width that is a power of
542 // two and also be of some minimal width (that is also a power of two).
544 // This is solved by first taking our picture size and rounding it up to the
545 // multiple of the minimal width. The binary representation of this rounded
546 // value gives us the tiles we need: a bit of value one means we need a tile of
548 void TiledPictureRenderer::setupPowerOf2Tiles() {
549 // Only use enough tiles to cover the viewport
550 const int width = this->getViewWidth();
551 const int height = this->getViewHeight();
553 int rounded_value = width;
554 if (width % fTileMinPowerOf2Width != 0) {
555 rounded_value = width - (width % fTileMinPowerOf2Width) + fTileMinPowerOf2Width;
558 int num_bits = SkScalarCeilToInt(scalar_log2(SkIntToScalar(width)));
559 int largest_possible_tile_size = 1 << num_bits;
561 fTilesX = fTilesY = 0;
562 // The tile height is constant for a particular picture.
563 for (int tile_y_start = 0; tile_y_start < height; tile_y_start += fTileHeight) {
565 int tile_x_start = 0;
566 int current_width = largest_possible_tile_size;
567 // Set fTileWidth to be the width of the widest tile, so that each canvas is large enough
568 // to draw each tile.
569 fTileWidth = current_width;
571 while (current_width >= fTileMinPowerOf2Width) {
572 // It is very important this is a bitwise AND.
573 if (current_width & rounded_value) {
574 if (0 == tile_y_start) {
575 // Only count tiles in the X direction on the first pass.
578 *fTileRects.append() = SkRect::MakeXYWH(SkIntToScalar(tile_x_start),
579 SkIntToScalar(tile_y_start),
580 SkIntToScalar(current_width),
581 SkIntToScalar(fTileHeight));
582 tile_x_start += current_width;
591 * Draw the specified picture to the canvas translated to rectangle provided, so that this mini
592 * canvas represents the rectangle's portion of the overall picture.
593 * Saves and restores so that the initial clip and matrix return to their state before this function
596 static void draw_tile_to_canvas(SkCanvas* canvas,
597 const SkRect& tileRect,
598 const SkPicture* picture) {
599 int saveCount = canvas->save();
600 // Translate so that we draw the correct portion of the picture.
601 // Perform a postTranslate so that the scaleFactor does not interfere with the positioning.
602 SkMatrix mat(canvas->getTotalMatrix());
603 mat.postTranslate(-tileRect.fLeft, -tileRect.fTop);
604 canvas->setMatrix(mat);
605 canvas->drawPicture(picture);
606 canvas->restoreToCount(saveCount);
610 ///////////////////////////////////////////////////////////////////////////////////////////////
613 * Copies the entirety of the src bitmap (typically a tile) into a portion of the dst bitmap.
614 * If the src bitmap is too large to fit within the dst bitmap after the x and y
615 * offsets have been applied, any excess will be ignored (so only the top-left portion of the
616 * src bitmap will be copied).
618 * @param src source bitmap
619 * @param dst destination bitmap
620 * @param xOffset x-offset within destination bitmap
621 * @param yOffset y-offset within destination bitmap
623 static void bitmapCopyAtOffset(const SkBitmap& src, SkBitmap* dst,
624 int xOffset, int yOffset) {
625 for (int y = 0; y <src.height() && y + yOffset < dst->height() ; y++) {
626 for (int x = 0; x < src.width() && x + xOffset < dst->width() ; x++) {
627 *dst->getAddr32(xOffset + x, yOffset + y) = *src.getAddr32(x, y);
632 bool TiledPictureRenderer::nextTile(int &i, int &j) {
633 if (++fCurrentTileOffset < fTileRects.count()) {
634 i = fCurrentTileOffset % fTilesX;
635 j = fCurrentTileOffset / fTilesX;
641 void TiledPictureRenderer::drawCurrentTile() {
642 SkASSERT(fCurrentTileOffset >= 0 && fCurrentTileOffset < fTileRects.count());
643 draw_tile_to_canvas(fCanvas, fTileRects[fCurrentTileOffset], fPicture);
646 bool TiledPictureRenderer::render(SkBitmap** out) {
647 SkASSERT(fPicture != NULL);
648 if (NULL == fPicture) {
654 *out = SkNEW(SkBitmap);
655 setup_bitmap(*out, SkScalarCeilToInt(fPicture->cullRect().width()),
656 SkScalarCeilToInt(fPicture->cullRect().height()));
657 setup_bitmap(&bitmap, fTileWidth, fTileHeight);
660 for (int i = 0; i < fTileRects.count(); ++i) {
661 draw_tile_to_canvas(fCanvas, fTileRects[i], fPicture);
663 success &= write(fCanvas, fWritePath, fMismatchPath, fInputFilename, fJsonSummaryPtr,
664 fUseChecksumBasedFilenames, &i);
667 if (fCanvas->readPixels(&bitmap, 0, 0)) {
668 // Add this tile to the entire bitmap.
669 bitmapCopyAtOffset(bitmap, *out, SkScalarFloorToInt(fTileRects[i].left()),
670 SkScalarFloorToInt(fTileRects[i].top()));
679 SkCanvas* TiledPictureRenderer::setupCanvas(int width, int height) {
680 SkCanvas* canvas = this->INHERITED::setupCanvas(width, height);
682 // Clip the tile to an area that is completely inside both the SkPicture and the viewport. This
683 // is mostly important for tiles on the right and bottom edges as they may go over this area and
684 // the picture may have some commands that draw outside of this area and so should not actually
686 // Uses a clipRegion so that it will be unaffected by the scale factor, which may have been set
687 // by INHERITED::setupCanvas.
689 clipRegion.setRect(0, 0, this->getViewWidth(), this->getViewHeight());
690 canvas->clipRegion(clipRegion);
694 SkString TiledPictureRenderer::getConfigNameInternal() {
696 if (fTileMinPowerOf2Width > 0) {
697 name.append("pow2tile_");
698 name.appendf("%i", fTileMinPowerOf2Width);
700 name.append("tile_");
701 if (fTileWidthPercentage > 0) {
702 name.appendf("%.f%%", fTileWidthPercentage);
704 name.appendf("%i", fTileWidth);
708 if (fTileHeightPercentage > 0) {
709 name.appendf("%.f%%", fTileHeightPercentage);
711 name.appendf("%i", fTileHeight);
716 ///////////////////////////////////////////////////////////////////////////////////////////////
718 void PlaybackCreationRenderer::setup() {
719 SkAutoTDelete<SkBBHFactory> factory(this->getFactory());
720 fRecorder.reset(SkNEW(SkPictureRecorder));
721 SkCanvas* canvas = fRecorder->beginRecording(SkIntToScalar(this->getViewWidth()),
722 SkIntToScalar(this->getViewHeight()),
724 this->recordFlags());
725 this->scaleToScaleFactor(canvas);
726 canvas->drawPicture(fPicture);
729 bool PlaybackCreationRenderer::render(SkBitmap** out) {
730 fPicture.reset(fRecorder->endRecording());
731 // Since this class does not actually render, return false.
735 SkString PlaybackCreationRenderer::getConfigNameInternal() {
736 return SkString("playback_creation");
739 ///////////////////////////////////////////////////////////////////////////////////////////////
740 // SkPicture variants for each BBoxHierarchy type
742 SkBBHFactory* PictureRenderer::getFactory() {
743 switch (fBBoxHierarchyType) {
744 case kNone_BBoxHierarchyType:
746 case kRTree_BBoxHierarchyType:
747 return SkNEW(SkRTreeFactory);
748 case kTileGrid_BBoxHierarchyType:
749 return SkNEW_ARGS(SkTileGridFactory, (fGridInfo));
751 SkASSERT(0); // invalid bbhType
755 ///////////////////////////////////////////////////////////////////////////////
757 class GatherRenderer : public PictureRenderer {
760 GatherRenderer(const GrContext::Options& opts) : INHERITED(opts) { }
763 virtual bool render(SkBitmap** out = NULL) SK_OVERRIDE {
764 SkRect bounds = SkRect::MakeWH(SkIntToScalar(fPicture->cullRect().width()),
765 SkIntToScalar(fPicture->cullRect().height()));
766 SkData* data = SkPictureUtils::GatherPixelRefs(fPicture, bounds);
769 return (fWritePath.isEmpty()); // we don't have anything to write
773 virtual SkString getConfigNameInternal() SK_OVERRIDE {
774 return SkString("gather_pixelrefs");
777 typedef PictureRenderer INHERITED;
781 PictureRenderer* CreateGatherPixelRefsRenderer(const GrContext::Options& opts) {
782 return SkNEW_ARGS(GatherRenderer, (opts));
785 PictureRenderer* CreateGatherPixelRefsRenderer() {
786 return SkNEW(GatherRenderer);
790 } // namespace sk_tools