| OLD | NEW |
| 1 /* | 1 /* |
| 2 * Copyright 2012 Google Inc. | 2 * Copyright 2012 Google Inc. |
| 3 * | 3 * |
| 4 * Use of this source code is governed by a BSD-style license that can be | 4 * Use of this source code is governed by a BSD-style license that can be |
| 5 * found in the LICENSE file. | 5 * found in the LICENSE file. |
| 6 */ | 6 */ |
| 7 | 7 |
| 8 #include "PictureRenderer.h" | 8 #include "PictureRenderer.h" |
| 9 #include "picture_utils.h" | 9 #include "picture_utils.h" |
| 10 #include "SamplePipeControllers.h" | 10 #include "SamplePipeControllers.h" |
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 51 }; | 51 }; |
| 52 | 52 |
| 53 /* TODO(epoger): These constants are already maintained in 2 other places: | 53 /* TODO(epoger): These constants are already maintained in 2 other places: |
| 54 * gm/gm_json.py and gm/gm_expectations.cpp. We shouldn't add yet a third place
. | 54 * gm/gm_json.py and gm/gm_expectations.cpp. We shouldn't add yet a third place
. |
| 55 * Figure out a way to share the definitions instead. | 55 * Figure out a way to share the definitions instead. |
| 56 */ | 56 */ |
| 57 const static char kJsonKey_ActualResults[] = "actual-results"; | 57 const static char kJsonKey_ActualResults[] = "actual-results"; |
| 58 const static char kJsonKey_ActualResults_NoComparison[] = "no-comparison"; | 58 const static char kJsonKey_ActualResults_NoComparison[] = "no-comparison"; |
| 59 const static char kJsonKey_Hashtype_Bitmap_64bitMD5[] = "bitmap-64bitMD5"; | 59 const static char kJsonKey_Hashtype_Bitmap_64bitMD5[] = "bitmap-64bitMD5"; |
| 60 | 60 |
| 61 void ImageResultsSummary::add(const char *testName, const SkBitmap& bitmap) { | 61 void ImageResultsSummary::add(const char *testName, uint64_t hash) { |
| 62 uint64_t hash; | |
| 63 SkAssertResult(SkBitmapHasher::ComputeDigest(bitmap, &hash)); | |
| 64 Json::Value jsonTypeValuePair; | 62 Json::Value jsonTypeValuePair; |
| 65 jsonTypeValuePair.append(Json::Value(kJsonKey_Hashtype_Bitmap_64bitMD5)); | 63 jsonTypeValuePair.append(Json::Value(kJsonKey_Hashtype_Bitmap_64bitMD5)); |
| 66 jsonTypeValuePair.append(Json::UInt64(hash)); | 64 jsonTypeValuePair.append(Json::UInt64(hash)); |
| 67 fActualResultsNoComparison[testName] = jsonTypeValuePair; | 65 fActualResultsNoComparison[testName] = jsonTypeValuePair; |
| 68 } | 66 } |
| 69 | 67 |
| 68 void ImageResultsSummary::add(const char *testName, const SkBitmap& bitmap) { |
| 69 uint64_t hash; |
| 70 SkAssertResult(SkBitmapHasher::ComputeDigest(bitmap, &hash)); |
| 71 this->add(testName, hash); |
| 72 } |
| 73 |
| 70 void ImageResultsSummary::writeToFile(const char *filename) { | 74 void ImageResultsSummary::writeToFile(const char *filename) { |
| 71 Json::Value actualResults; | 75 Json::Value actualResults; |
| 72 actualResults[kJsonKey_ActualResults_NoComparison] = fActualResultsNoCompari
son; | 76 actualResults[kJsonKey_ActualResults_NoComparison] = fActualResultsNoCompari
son; |
| 73 Json::Value root; | 77 Json::Value root; |
| 74 root[kJsonKey_ActualResults] = actualResults; | 78 root[kJsonKey_ActualResults] = actualResults; |
| 75 std::string jsonStdString = root.toStyledString(); | 79 std::string jsonStdString = root.toStyledString(); |
| 76 SkFILEWStream stream(filename); | 80 SkFILEWStream stream(filename); |
| 77 stream.write(jsonStdString.c_str(), jsonStdString.length()); | 81 stream.write(jsonStdString.c_str(), jsonStdString.length()); |
| 78 } | 82 } |
| 79 | 83 |
| 80 void PictureRenderer::init(SkPicture* pict) { | 84 void PictureRenderer::init(SkPicture* pict, const SkString* outputDir, |
| 85 const SkString* inputFilename, bool useChecksumBasedF
ilenames) { |
| 86 this->CopyString(&fOutputDir, outputDir); |
| 87 this->CopyString(&fInputFilename, inputFilename); |
| 88 fUseChecksumBasedFilenames = useChecksumBasedFilenames; |
| 89 |
| 81 SkASSERT(NULL == fPicture); | 90 SkASSERT(NULL == fPicture); |
| 82 SkASSERT(NULL == fCanvas.get()); | 91 SkASSERT(NULL == fCanvas.get()); |
| 83 if (fPicture != NULL || NULL != fCanvas.get()) { | 92 if (fPicture != NULL || NULL != fCanvas.get()) { |
| 84 return; | 93 return; |
| 85 } | 94 } |
| 86 | 95 |
| 87 SkASSERT(pict != NULL); | 96 SkASSERT(pict != NULL); |
| 88 if (NULL == pict) { | 97 if (NULL == pict) { |
| 89 return; | 98 return; |
| 90 } | 99 } |
| 91 | 100 |
| 92 fPicture = pict; | 101 fPicture = pict; |
| 93 fPicture->ref(); | 102 fPicture->ref(); |
| 94 fCanvas.reset(this->setupCanvas()); | 103 fCanvas.reset(this->setupCanvas()); |
| 95 } | 104 } |
| 96 | 105 |
| 106 void PictureRenderer::CopyString(SkString* dest, const SkString* src) { |
| 107 if (NULL != src) { |
| 108 dest->set(*src); |
| 109 } else { |
| 110 dest->reset(); |
| 111 } |
| 112 } |
| 113 |
| 97 class FlagsDrawFilter : public SkDrawFilter { | 114 class FlagsDrawFilter : public SkDrawFilter { |
| 98 public: | 115 public: |
| 99 FlagsDrawFilter(PictureRenderer::DrawFilterFlags* flags) : | 116 FlagsDrawFilter(PictureRenderer::DrawFilterFlags* flags) : |
| 100 fFlags(flags) {} | 117 fFlags(flags) {} |
| 101 | 118 |
| 102 virtual bool filter(SkPaint* paint, Type t) { | 119 virtual bool filter(SkPaint* paint, Type t) { |
| 103 paint->setFlags(paint->getFlags() & ~fFlags[t] & SkPaint::kAllFlags); | 120 paint->setFlags(paint->getFlags() & ~fFlags[t] & SkPaint::kAllFlags); |
| 104 if (PictureRenderer::kMaskFilter_DrawFilterFlag & fFlags[t]) { | 121 if (PictureRenderer::kMaskFilter_DrawFilterFlag & fFlags[t]) { |
| 105 SkMaskFilter* maskFilter = paint->getMaskFilter(); | 122 SkMaskFilter* maskFilter = paint->getMaskFilter(); |
| 106 if (NULL != maskFilter) { | 123 if (NULL != maskFilter) { |
| (...skipping 167 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 274 } | 291 } |
| 275 | 292 |
| 276 uint32_t PictureRenderer::recordFlags() { | 293 uint32_t PictureRenderer::recordFlags() { |
| 277 return ((kNone_BBoxHierarchyType == fBBoxHierarchyType) ? 0 : | 294 return ((kNone_BBoxHierarchyType == fBBoxHierarchyType) ? 0 : |
| 278 SkPicture::kOptimizeForClippedPlayback_RecordingFlag) | | 295 SkPicture::kOptimizeForClippedPlayback_RecordingFlag) | |
| 279 SkPicture::kUsePathBoundsForClip_RecordingFlag; | 296 SkPicture::kUsePathBoundsForClip_RecordingFlag; |
| 280 } | 297 } |
| 281 | 298 |
| 282 /** | 299 /** |
| 283 * Write the canvas to the specified path. | 300 * Write the canvas to the specified path. |
| 301 * |
| 284 * @param canvas Must be non-null. Canvas to be written to a file. | 302 * @param canvas Must be non-null. Canvas to be written to a file. |
| 285 * @param path Path for the file to be written. Should have no extension; write(
) will append | 303 * @param outputDir If nonempty, write the binary image to a file within this di
rectory. |
| 286 * an appropriate one. Passed in by value so it can be modified. | 304 * @param inputFilename If we are writing out a binary image, use this to build
its filename. |
| 287 * @param jsonSummaryPtr If not null, add image results to this summary. | 305 * @param jsonSummaryPtr If not null, add image results to this summary. |
| 306 * @param useChecksumBasedFilenames If true, use checksum-based filenames when w
riting to disk. |
| 307 * @param numberToAppend If not null, append this number to the filename. |
| 288 * @return bool True if the Canvas is written to a file. | 308 * @return bool True if the Canvas is written to a file. |
| 289 * | 309 * |
| 290 * TODO(epoger): Right now, all canvases must pass through this function in orde
r to be appended | 310 * TODO(epoger): Right now, all canvases must pass through this function in orde
r to be appended |
| 291 * to the ImageResultsSummary. We need some way to add bitmaps to the ImageResu
ltsSummary | 311 * to the ImageResultsSummary. We need some way to add bitmaps to the ImageResu
ltsSummary |
| 292 * even if --writePath has not been specified (and thus this function is not cal
led). | 312 * even if --writePath has not been specified (and thus this function is not cal
led). |
| 293 * | 313 * |
| 294 * One fix would be to pass in these path elements separately, and allow this fu
nction to be | 314 * One fix would be to pass in these path elements separately, and allow this fu
nction to be |
| 295 * called even if --writePath was not specified... | 315 * called even if --writePath was not specified... |
| 296 * const char *outputDir // NULL if we don't want to write image files to dis
k | 316 * const char *outputDir // NULL if we don't want to write image files to dis
k |
| 297 * const char *filename // name we use within JSON summary, and as the filen
ame within outputDir | 317 * const char *filename // name we use within JSON summary, and as the filen
ame within outputDir |
| 318 * |
| 319 * UPDATE: Now that outputDir and inputFilename are passed separately, we should
be able to do that. |
| 298 */ | 320 */ |
| 299 static bool write(SkCanvas* canvas, const SkString* path, ImageResultsSummary *j
sonSummaryPtr) { | 321 static bool write(SkCanvas* canvas, const SkString& outputDir, const SkString& i
nputFilename, |
| 322 ImageResultsSummary *jsonSummaryPtr, bool useChecksumBasedFile
names, |
| 323 const int* numberToAppend=NULL) { |
| 300 SkASSERT(canvas != NULL); | 324 SkASSERT(canvas != NULL); |
| 301 if (NULL == canvas) { | 325 if (NULL == canvas) { |
| 302 return false; | 326 return false; |
| 303 } | 327 } |
| 304 | 328 |
| 305 SkASSERT(path != NULL); // TODO(epoger): we want to remove this constraint,
as noted above | |
| 306 SkString fullPathname(*path); | |
| 307 fullPathname.append(".png"); | |
| 308 | |
| 309 SkBitmap bitmap; | 329 SkBitmap bitmap; |
| 310 SkISize size = canvas->getDeviceSize(); | 330 SkISize size = canvas->getDeviceSize(); |
| 311 sk_tools::setup_bitmap(&bitmap, size.width(), size.height()); | 331 sk_tools::setup_bitmap(&bitmap, size.width(), size.height()); |
| 312 | 332 |
| 333 // Make sure we only compute the bitmap hash once (at most). |
| 334 uint64_t hash; |
| 335 bool generatedHash = false; |
| 336 |
| 313 canvas->readPixels(&bitmap, 0, 0); | 337 canvas->readPixels(&bitmap, 0, 0); |
| 314 sk_tools::force_all_opaque(bitmap); | 338 sk_tools::force_all_opaque(bitmap); |
| 315 | 339 |
| 340 SkString outputFilename(inputFilename); |
| 341 outputFilename.remove(outputFilename.size() - 4, 4); |
| 342 if (NULL != numberToAppend) { |
| 343 outputFilename.appendf("%i", *numberToAppend); |
| 344 } |
| 345 outputFilename.append(".png"); |
| 346 // TODO(epoger): what about including the config type within outputFilename?
That way, |
| 347 // we could combine results of different config types without conflicting fi
lenames. |
| 348 |
| 316 if (NULL != jsonSummaryPtr) { | 349 if (NULL != jsonSummaryPtr) { |
| 317 // EPOGER: This is a hacky way of constructing the filename associated w
ith the | 350 if (!generatedHash) { |
| 318 // image checksum; we assume that outputDir is not NULL, and we remove o
utputDir | 351 SkAssertResult(SkBitmapHasher::ComputeDigest(bitmap, &hash)); |
| 319 // from fullPathname. | 352 generatedHash = true; |
| 320 // | 353 } |
| 321 // EPOGER: what about including the config type within hashFilename? Th
at way, | 354 jsonSummaryPtr->add(outputFilename.c_str(), hash); |
| 322 // we could combine results of different config types without conflictin
g filenames. | |
| 323 SkString hashFilename; | |
| 324 sk_tools::get_basename(&hashFilename, fullPathname); | |
| 325 jsonSummaryPtr->add(hashFilename.c_str(), bitmap); | |
| 326 } | 355 } |
| 327 | 356 |
| 357 // Update outputFilename AFTER adding to JSON summary, but BEFORE writing ou
t the image file. |
| 358 if (useChecksumBasedFilenames) { |
| 359 if (!generatedHash) { |
| 360 SkAssertResult(SkBitmapHasher::ComputeDigest(bitmap, &hash)); |
| 361 generatedHash = true; |
| 362 } |
| 363 outputFilename.set(kJsonKey_Hashtype_Bitmap_64bitMD5); |
| 364 outputFilename.append("_"); |
| 365 outputFilename.appendU64(hash); |
| 366 outputFilename.append(".png"); |
| 367 } |
| 368 |
| 369 SkASSERT(!outputDir.isEmpty()); // TODO(epoger): we want to remove this cons
traint, |
| 370 // as noted above |
| 371 SkString fullPathname; |
| 372 make_filepath(&fullPathname, outputDir, outputFilename); |
| 328 return SkImageEncoder::EncodeFile(fullPathname.c_str(), bitmap, SkImageEncod
er::kPNG_Type, 100); | 373 return SkImageEncoder::EncodeFile(fullPathname.c_str(), bitmap, SkImageEncod
er::kPNG_Type, 100); |
| 329 } | 374 } |
| 330 | 375 |
| 331 /** | |
| 332 * If path is non NULL, append number to it, and call write() to write the | |
| 333 * provided canvas to a file. Returns true if path is NULL or if write() succeed
s. | |
| 334 */ | |
| 335 static bool writeAppendNumber(SkCanvas* canvas, const SkString* path, int number
, | |
| 336 ImageResultsSummary *jsonSummaryPtr) { | |
| 337 if (NULL == path) { | |
| 338 return true; | |
| 339 } | |
| 340 SkString pathWithNumber(*path); | |
| 341 pathWithNumber.appendf("%i", number); | |
| 342 return write(canvas, &pathWithNumber, jsonSummaryPtr); | |
| 343 } | |
| 344 | |
| 345 ////////////////////////////////////////////////////////////////////////////////
/////////////// | 376 ////////////////////////////////////////////////////////////////////////////////
/////////////// |
| 346 | 377 |
| 347 SkCanvas* RecordPictureRenderer::setupCanvas(int width, int height) { | 378 SkCanvas* RecordPictureRenderer::setupCanvas(int width, int height) { |
| 348 // defer the canvas setup until the render step | 379 // defer the canvas setup until the render step |
| 349 return NULL; | 380 return NULL; |
| 350 } | 381 } |
| 351 | 382 |
| 352 // the size_t* parameter is deprecated, so we ignore it | 383 // the size_t* parameter is deprecated, so we ignore it |
| 353 static SkData* encode_bitmap_to_data(size_t*, const SkBitmap& bm) { | 384 static SkData* encode_bitmap_to_data(size_t*, const SkBitmap& bm) { |
| 354 return SkImageEncoder::EncodeData(bm, SkImageEncoder::kPNG_Type, 100); | 385 return SkImageEncoder::EncodeData(bm, SkImageEncoder::kPNG_Type, 100); |
| 355 } | 386 } |
| 356 | 387 |
| 357 bool RecordPictureRenderer::render(const SkString* path, SkBitmap** out) { | 388 bool RecordPictureRenderer::render(SkBitmap** out) { |
| 358 SkAutoTUnref<SkPicture> replayer(this->createPicture()); | 389 SkAutoTUnref<SkPicture> replayer(this->createPicture()); |
| 359 SkCanvas* recorder = replayer->beginRecording(this->getViewWidth(), this->ge
tViewHeight(), | 390 SkCanvas* recorder = replayer->beginRecording(this->getViewWidth(), this->ge
tViewHeight(), |
| 360 this->recordFlags()); | 391 this->recordFlags()); |
| 361 this->scaleToScaleFactor(recorder); | 392 this->scaleToScaleFactor(recorder); |
| 362 fPicture->draw(recorder); | 393 fPicture->draw(recorder); |
| 363 replayer->endRecording(); | 394 replayer->endRecording(); |
| 364 if (path != NULL) { | 395 if (!fOutputDir.isEmpty()) { |
| 365 // Record the new picture as a new SKP with PNG encoded bitmaps. | 396 // Record the new picture as a new SKP with PNG encoded bitmaps. |
| 366 SkString skpPath(*path); | 397 SkString skpPath; |
| 367 // ".skp" was removed from 'path' before being passed in here. | 398 make_filepath(&skpPath, fOutputDir, fInputFilename); |
| 368 skpPath.append(".skp"); | |
| 369 SkFILEWStream stream(skpPath.c_str()); | 399 SkFILEWStream stream(skpPath.c_str()); |
| 370 replayer->serialize(&stream, &encode_bitmap_to_data); | 400 replayer->serialize(&stream, &encode_bitmap_to_data); |
| 371 return true; | 401 return true; |
| 372 } | 402 } |
| 373 return false; | 403 return false; |
| 374 } | 404 } |
| 375 | 405 |
| 376 SkString RecordPictureRenderer::getConfigNameInternal() { | 406 SkString RecordPictureRenderer::getConfigNameInternal() { |
| 377 return SkString("record"); | 407 return SkString("record"); |
| 378 } | 408 } |
| 379 | 409 |
| 380 ////////////////////////////////////////////////////////////////////////////////
/////////////// | 410 ////////////////////////////////////////////////////////////////////////////////
/////////////// |
| 381 | 411 |
| 382 bool PipePictureRenderer::render(const SkString* path, SkBitmap** out) { | 412 bool PipePictureRenderer::render(SkBitmap** out) { |
| 383 SkASSERT(fCanvas.get() != NULL); | 413 SkASSERT(fCanvas.get() != NULL); |
| 384 SkASSERT(fPicture != NULL); | 414 SkASSERT(fPicture != NULL); |
| 385 if (NULL == fCanvas.get() || NULL == fPicture) { | 415 if (NULL == fCanvas.get() || NULL == fPicture) { |
| 386 return false; | 416 return false; |
| 387 } | 417 } |
| 388 | 418 |
| 389 PipeController pipeController(fCanvas.get()); | 419 PipeController pipeController(fCanvas.get()); |
| 390 SkGPipeWriter writer; | 420 SkGPipeWriter writer; |
| 391 SkCanvas* pipeCanvas = writer.startRecording(&pipeController); | 421 SkCanvas* pipeCanvas = writer.startRecording(&pipeController); |
| 392 pipeCanvas->drawPicture(*fPicture); | 422 pipeCanvas->drawPicture(*fPicture); |
| 393 writer.endRecording(); | 423 writer.endRecording(); |
| 394 fCanvas->flush(); | 424 fCanvas->flush(); |
| 395 if (NULL != path) { | 425 if (!fOutputDir.isEmpty()) { |
| 396 return write(fCanvas, path, fJsonSummaryPtr); | 426 return write(fCanvas, fOutputDir, fInputFilename, fJsonSummaryPtr, |
| 427 fUseChecksumBasedFilenames); |
| 397 } | 428 } |
| 398 if (NULL != out) { | 429 if (NULL != out) { |
| 399 *out = SkNEW(SkBitmap); | 430 *out = SkNEW(SkBitmap); |
| 400 setup_bitmap(*out, fPicture->width(), fPicture->height()); | 431 setup_bitmap(*out, fPicture->width(), fPicture->height()); |
| 401 fCanvas->readPixels(*out, 0, 0); | 432 fCanvas->readPixels(*out, 0, 0); |
| 402 } | 433 } |
| 403 return true; | 434 return true; |
| 404 } | 435 } |
| 405 | 436 |
| 406 SkString PipePictureRenderer::getConfigNameInternal() { | 437 SkString PipePictureRenderer::getConfigNameInternal() { |
| 407 return SkString("pipe"); | 438 return SkString("pipe"); |
| 408 } | 439 } |
| 409 | 440 |
| 410 ////////////////////////////////////////////////////////////////////////////////
/////////////// | 441 ////////////////////////////////////////////////////////////////////////////////
/////////////// |
| 411 | 442 |
| 412 void SimplePictureRenderer::init(SkPicture* picture) { | 443 void SimplePictureRenderer::init(SkPicture* picture, const SkString* outputDir, |
| 413 INHERITED::init(picture); | 444 const SkString* inputFilename, bool useChecksum
BasedFilenames) { |
| 445 INHERITED::init(picture, outputDir, inputFilename, useChecksumBasedFilenames
); |
| 414 this->buildBBoxHierarchy(); | 446 this->buildBBoxHierarchy(); |
| 415 } | 447 } |
| 416 | 448 |
| 417 bool SimplePictureRenderer::render(const SkString* path, SkBitmap** out) { | 449 bool SimplePictureRenderer::render(SkBitmap** out) { |
| 418 SkASSERT(fCanvas.get() != NULL); | 450 SkASSERT(fCanvas.get() != NULL); |
| 419 SkASSERT(fPicture != NULL); | 451 SkASSERT(fPicture != NULL); |
| 420 if (NULL == fCanvas.get() || NULL == fPicture) { | 452 if (NULL == fCanvas.get() || NULL == fPicture) { |
| 421 return false; | 453 return false; |
| 422 } | 454 } |
| 423 | 455 |
| 424 fCanvas->drawPicture(*fPicture); | 456 fCanvas->drawPicture(*fPicture); |
| 425 fCanvas->flush(); | 457 fCanvas->flush(); |
| 426 if (NULL != path) { | 458 if (!fOutputDir.isEmpty()) { |
| 427 return write(fCanvas, path, fJsonSummaryPtr); | 459 return write(fCanvas, fOutputDir, fInputFilename, fJsonSummaryPtr, |
| 460 fUseChecksumBasedFilenames); |
| 428 } | 461 } |
| 429 | 462 |
| 430 if (NULL != out) { | 463 if (NULL != out) { |
| 431 *out = SkNEW(SkBitmap); | 464 *out = SkNEW(SkBitmap); |
| 432 setup_bitmap(*out, fPicture->width(), fPicture->height()); | 465 setup_bitmap(*out, fPicture->width(), fPicture->height()); |
| 433 fCanvas->readPixels(*out, 0, 0); | 466 fCanvas->readPixels(*out, 0, 0); |
| 434 } | 467 } |
| 435 | 468 |
| 436 return true; | 469 return true; |
| 437 } | 470 } |
| 438 | 471 |
| 439 SkString SimplePictureRenderer::getConfigNameInternal() { | 472 SkString SimplePictureRenderer::getConfigNameInternal() { |
| 440 return SkString("simple"); | 473 return SkString("simple"); |
| 441 } | 474 } |
| 442 | 475 |
| 443 ////////////////////////////////////////////////////////////////////////////////
/////////////// | 476 ////////////////////////////////////////////////////////////////////////////////
/////////////// |
| 444 | 477 |
| 445 TiledPictureRenderer::TiledPictureRenderer() | 478 TiledPictureRenderer::TiledPictureRenderer() |
| 446 : fTileWidth(kDefaultTileWidth) | 479 : fTileWidth(kDefaultTileWidth) |
| 447 , fTileHeight(kDefaultTileHeight) | 480 , fTileHeight(kDefaultTileHeight) |
| 448 , fTileWidthPercentage(0.0) | 481 , fTileWidthPercentage(0.0) |
| 449 , fTileHeightPercentage(0.0) | 482 , fTileHeightPercentage(0.0) |
| 450 , fTileMinPowerOf2Width(0) | 483 , fTileMinPowerOf2Width(0) |
| 451 , fCurrentTileOffset(-1) | 484 , fCurrentTileOffset(-1) |
| 452 , fTilesX(0) | 485 , fTilesX(0) |
| 453 , fTilesY(0) { } | 486 , fTilesY(0) { } |
| 454 | 487 |
| 455 void TiledPictureRenderer::init(SkPicture* pict) { | 488 void TiledPictureRenderer::init(SkPicture* pict, const SkString* outputDir, |
| 489 const SkString* inputFilename, bool useChecksumB
asedFilenames) { |
| 456 SkASSERT(pict != NULL); | 490 SkASSERT(pict != NULL); |
| 457 SkASSERT(0 == fTileRects.count()); | 491 SkASSERT(0 == fTileRects.count()); |
| 458 if (NULL == pict || fTileRects.count() != 0) { | 492 if (NULL == pict || fTileRects.count() != 0) { |
| 459 return; | 493 return; |
| 460 } | 494 } |
| 461 | 495 |
| 462 // Do not call INHERITED::init(), which would create a (potentially large) c
anvas which is not | 496 // Do not call INHERITED::init(), which would create a (potentially large) c
anvas which is not |
| 463 // used by bench_pictures. | 497 // used by bench_pictures. |
| 464 fPicture = pict; | 498 fPicture = pict; |
| 499 this->CopyString(&fOutputDir, outputDir); |
| 500 this->CopyString(&fInputFilename, inputFilename); |
| 501 fUseChecksumBasedFilenames = useChecksumBasedFilenames; |
| 465 fPicture->ref(); | 502 fPicture->ref(); |
| 466 this->buildBBoxHierarchy(); | 503 this->buildBBoxHierarchy(); |
| 467 | 504 |
| 468 if (fTileWidthPercentage > 0) { | 505 if (fTileWidthPercentage > 0) { |
| 469 fTileWidth = sk_float_ceil2int(float(fTileWidthPercentage * fPicture->wi
dth() / 100)); | 506 fTileWidth = sk_float_ceil2int(float(fTileWidthPercentage * fPicture->wi
dth() / 100)); |
| 470 } | 507 } |
| 471 if (fTileHeightPercentage > 0) { | 508 if (fTileHeightPercentage > 0) { |
| 472 fTileHeight = sk_float_ceil2int(float(fTileHeightPercentage * fPicture->
height() / 100)); | 509 fTileHeight = sk_float_ceil2int(float(fTileHeightPercentage * fPicture->
height() / 100)); |
| 473 } | 510 } |
| 474 | 511 |
| (...skipping 141 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 616 return true; | 653 return true; |
| 617 } | 654 } |
| 618 return false; | 655 return false; |
| 619 } | 656 } |
| 620 | 657 |
| 621 void TiledPictureRenderer::drawCurrentTile() { | 658 void TiledPictureRenderer::drawCurrentTile() { |
| 622 SkASSERT(fCurrentTileOffset >= 0 && fCurrentTileOffset < fTileRects.count())
; | 659 SkASSERT(fCurrentTileOffset >= 0 && fCurrentTileOffset < fTileRects.count())
; |
| 623 draw_tile_to_canvas(fCanvas, fTileRects[fCurrentTileOffset], fPicture); | 660 draw_tile_to_canvas(fCanvas, fTileRects[fCurrentTileOffset], fPicture); |
| 624 } | 661 } |
| 625 | 662 |
| 626 bool TiledPictureRenderer::render(const SkString* path, SkBitmap** out) { | 663 bool TiledPictureRenderer::render(SkBitmap** out) { |
| 627 SkASSERT(fPicture != NULL); | 664 SkASSERT(fPicture != NULL); |
| 628 if (NULL == fPicture) { | 665 if (NULL == fPicture) { |
| 629 return false; | 666 return false; |
| 630 } | 667 } |
| 631 | 668 |
| 632 SkBitmap bitmap; | 669 SkBitmap bitmap; |
| 633 if (out){ | 670 if (out){ |
| 634 *out = SkNEW(SkBitmap); | 671 *out = SkNEW(SkBitmap); |
| 635 setup_bitmap(*out, fPicture->width(), fPicture->height()); | 672 setup_bitmap(*out, fPicture->width(), fPicture->height()); |
| 636 setup_bitmap(&bitmap, fTileWidth, fTileHeight); | 673 setup_bitmap(&bitmap, fTileWidth, fTileHeight); |
| 637 } | 674 } |
| 638 bool success = true; | 675 bool success = true; |
| 639 for (int i = 0; i < fTileRects.count(); ++i) { | 676 for (int i = 0; i < fTileRects.count(); ++i) { |
| 640 draw_tile_to_canvas(fCanvas, fTileRects[i], fPicture); | 677 draw_tile_to_canvas(fCanvas, fTileRects[i], fPicture); |
| 641 if (NULL != path) { | 678 if (!fOutputDir.isEmpty()) { |
| 642 success &= writeAppendNumber(fCanvas, path, i, fJsonSummaryPtr); | 679 success &= write(fCanvas, fOutputDir, fInputFilename, fJsonSummaryPt
r, |
| 680 fUseChecksumBasedFilenames, &i); |
| 643 } | 681 } |
| 644 if (NULL != out) { | 682 if (NULL != out) { |
| 645 if (fCanvas->readPixels(&bitmap, 0, 0)) { | 683 if (fCanvas->readPixels(&bitmap, 0, 0)) { |
| 646 // Add this tile to the entire bitmap. | 684 // Add this tile to the entire bitmap. |
| 647 bitmapCopyAtOffset(bitmap, *out, SkScalarFloorToInt(fTileRects[i
].left()), | 685 bitmapCopyAtOffset(bitmap, *out, SkScalarFloorToInt(fTileRects[i
].left()), |
| 648 SkScalarFloorToInt(fTileRects[i].top())); | 686 SkScalarFloorToInt(fTileRects[i].top())); |
| 649 } else { | 687 } else { |
| 650 success = false; | 688 success = false; |
| 651 } | 689 } |
| 652 } | 690 } |
| (...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 691 return name; | 729 return name; |
| 692 } | 730 } |
| 693 | 731 |
| 694 ////////////////////////////////////////////////////////////////////////////////
/////////////// | 732 ////////////////////////////////////////////////////////////////////////////////
/////////////// |
| 695 | 733 |
| 696 // Holds all of the information needed to draw a set of tiles. | 734 // Holds all of the information needed to draw a set of tiles. |
| 697 class CloneData : public SkRunnable { | 735 class CloneData : public SkRunnable { |
| 698 | 736 |
| 699 public: | 737 public: |
| 700 CloneData(SkPicture* clone, SkCanvas* canvas, SkTDArray<SkRect>& rects, int
start, int end, | 738 CloneData(SkPicture* clone, SkCanvas* canvas, SkTDArray<SkRect>& rects, int
start, int end, |
| 701 SkRunnable* done, ImageResultsSummary* jsonSummaryPtr) | 739 SkRunnable* done, ImageResultsSummary* jsonSummaryPtr, bool useChe
cksumBasedFilenames) |
| 702 : fClone(clone) | 740 : fClone(clone) |
| 703 , fCanvas(canvas) | 741 , fCanvas(canvas) |
| 704 , fPath(NULL) | |
| 705 , fRects(rects) | 742 , fRects(rects) |
| 706 , fStart(start) | 743 , fStart(start) |
| 707 , fEnd(end) | 744 , fEnd(end) |
| 708 , fSuccess(NULL) | 745 , fSuccess(NULL) |
| 709 , fDone(done) | 746 , fDone(done) |
| 710 , fJsonSummaryPtr(jsonSummaryPtr) { | 747 , fJsonSummaryPtr(jsonSummaryPtr) |
| 748 , fUseChecksumBasedFilenames(useChecksumBasedFilenames) { |
| 711 SkASSERT(fDone != NULL); | 749 SkASSERT(fDone != NULL); |
| 712 } | 750 } |
| 713 | 751 |
| 714 virtual void run() SK_OVERRIDE { | 752 virtual void run() SK_OVERRIDE { |
| 715 SkGraphics::SetTLSFontCacheLimit(1024 * 1024); | 753 SkGraphics::SetTLSFontCacheLimit(1024 * 1024); |
| 716 | 754 |
| 717 SkBitmap bitmap; | 755 SkBitmap bitmap; |
| 718 if (fBitmap != NULL) { | 756 if (fBitmap != NULL) { |
| 719 // All tiles are the same size. | 757 // All tiles are the same size. |
| 720 setup_bitmap(&bitmap, SkScalarFloorToInt(fRects[0].width()), SkScala
rFloorToInt(fRects[0].height())); | 758 setup_bitmap(&bitmap, SkScalarFloorToInt(fRects[0].width()), SkScala
rFloorToInt(fRects[0].height())); |
| 721 } | 759 } |
| 722 | 760 |
| 723 for (int i = fStart; i < fEnd; i++) { | 761 for (int i = fStart; i < fEnd; i++) { |
| 724 draw_tile_to_canvas(fCanvas, fRects[i], fClone); | 762 draw_tile_to_canvas(fCanvas, fRects[i], fClone); |
| 725 if ((fPath != NULL) && !writeAppendNumber(fCanvas, fPath, i, fJsonSu
mmaryPtr) | 763 if ((!fOutputDir.isEmpty()) |
| 764 && !write(fCanvas, fOutputDir, fInputFilename, fJsonSummaryPtr, |
| 765 fUseChecksumBasedFilenames, &i) |
| 726 && fSuccess != NULL) { | 766 && fSuccess != NULL) { |
| 727 *fSuccess = false; | 767 *fSuccess = false; |
| 728 // If one tile fails to write to a file, do not continue drawing
the rest. | 768 // If one tile fails to write to a file, do not continue drawing
the rest. |
| 729 break; | 769 break; |
| 730 } | 770 } |
| 731 if (fBitmap != NULL) { | 771 if (fBitmap != NULL) { |
| 732 if (fCanvas->readPixels(&bitmap, 0, 0)) { | 772 if (fCanvas->readPixels(&bitmap, 0, 0)) { |
| 733 SkAutoLockPixels alp(*fBitmap); | 773 SkAutoLockPixels alp(*fBitmap); |
| 734 bitmapCopyAtOffset(bitmap, fBitmap, SkScalarFloorToInt(fRect
s[i].left()), | 774 bitmapCopyAtOffset(bitmap, fBitmap, SkScalarFloorToInt(fRect
s[i].left()), |
| 735 SkScalarFloorToInt(fRects[i].top())); | 775 SkScalarFloorToInt(fRects[i].top())); |
| 736 } else { | 776 } else { |
| 737 *fSuccess = false; | 777 *fSuccess = false; |
| 738 // If one tile fails to read pixels, do not continue drawing
the rest. | 778 // If one tile fails to read pixels, do not continue drawing
the rest. |
| 739 break; | 779 break; |
| 740 } | 780 } |
| 741 } | 781 } |
| 742 } | 782 } |
| 743 fDone->run(); | 783 fDone->run(); |
| 744 } | 784 } |
| 745 | 785 |
| 746 void setPathAndSuccess(const SkString* path, bool* success) { | 786 void setPathsAndSuccess(const SkString& outputDir, const SkString& inputFile
name, |
| 747 fPath = path; | 787 bool* success) { |
| 788 fOutputDir.set(outputDir); |
| 789 fInputFilename.set(inputFilename); |
| 748 fSuccess = success; | 790 fSuccess = success; |
| 749 } | 791 } |
| 750 | 792 |
| 751 void setBitmap(SkBitmap* bitmap) { | 793 void setBitmap(SkBitmap* bitmap) { |
| 752 fBitmap = bitmap; | 794 fBitmap = bitmap; |
| 753 } | 795 } |
| 754 | 796 |
| 755 private: | 797 private: |
| 756 // All pointers unowned. | 798 // All pointers unowned. |
| 757 SkPicture* fClone; // Picture to draw from. Each CloneData has
a unique one which | 799 SkPicture* fClone; // Picture to draw from. Each CloneData has
a unique one which |
| 758 // is threadsafe. | 800 // is threadsafe. |
| 759 SkCanvas* fCanvas; // Canvas to draw to. Reused for each tile. | 801 SkCanvas* fCanvas; // Canvas to draw to. Reused for each tile. |
| 760 const SkString* fPath; // If non-null, path to write the result to
as a PNG. | 802 SkString fOutputDir; // If not empty, write results into this dir
ectory. |
| 803 SkString fInputFilename; // Filename of input SkPicture file. |
| 761 SkTDArray<SkRect>& fRects; // All tiles of the picture. | 804 SkTDArray<SkRect>& fRects; // All tiles of the picture. |
| 762 const int fStart; // Range of tiles drawn by this thread. | 805 const int fStart; // Range of tiles drawn by this thread. |
| 763 const int fEnd; | 806 const int fEnd; |
| 764 bool* fSuccess; // Only meaningful if path is non-null. Shar
ed by all threads, | 807 bool* fSuccess; // Only meaningful if path is non-null. Shar
ed by all threads, |
| 765 // and only set to false upon failure to wri
te to a PNG. | 808 // and only set to false upon failure to wri
te to a PNG. |
| 766 SkRunnable* fDone; | 809 SkRunnable* fDone; |
| 767 SkBitmap* fBitmap; | 810 SkBitmap* fBitmap; |
| 768 ImageResultsSummary* fJsonSummaryPtr; | 811 ImageResultsSummary* fJsonSummaryPtr; |
| 812 bool fUseChecksumBasedFilenames; |
| 769 }; | 813 }; |
| 770 | 814 |
| 771 MultiCorePictureRenderer::MultiCorePictureRenderer(int threadCount) | 815 MultiCorePictureRenderer::MultiCorePictureRenderer(int threadCount) |
| 772 : fNumThreads(threadCount) | 816 : fNumThreads(threadCount) |
| 773 , fThreadPool(threadCount) | 817 , fThreadPool(threadCount) |
| 774 , fCountdown(threadCount) { | 818 , fCountdown(threadCount) { |
| 775 // Only need to create fNumThreads - 1 clones, since one thread will use the
base | 819 // Only need to create fNumThreads - 1 clones, since one thread will use the
base |
| 776 // picture. | 820 // picture. |
| 777 fPictureClones = SkNEW_ARRAY(SkPicture, fNumThreads - 1); | 821 fPictureClones = SkNEW_ARRAY(SkPicture, fNumThreads - 1); |
| 778 fCloneData = SkNEW_ARRAY(CloneData*, fNumThreads); | 822 fCloneData = SkNEW_ARRAY(CloneData*, fNumThreads); |
| 779 } | 823 } |
| 780 | 824 |
| 781 void MultiCorePictureRenderer::init(SkPicture *pict) { | 825 void MultiCorePictureRenderer::init(SkPicture *pict, const SkString* outputDir, |
| 826 const SkString* inputFilename, bool useCheck
sumBasedFilenames) { |
| 782 // Set fPicture and the tiles. | 827 // Set fPicture and the tiles. |
| 783 this->INHERITED::init(pict); | 828 this->INHERITED::init(pict, outputDir, inputFilename, useChecksumBasedFilena
mes); |
| 784 for (int i = 0; i < fNumThreads; ++i) { | 829 for (int i = 0; i < fNumThreads; ++i) { |
| 785 *fCanvasPool.append() = this->setupCanvas(this->getTileWidth(), this->ge
tTileHeight()); | 830 *fCanvasPool.append() = this->setupCanvas(this->getTileWidth(), this->ge
tTileHeight()); |
| 786 } | 831 } |
| 787 // Only need to create fNumThreads - 1 clones, since one thread will use the
base picture. | 832 // Only need to create fNumThreads - 1 clones, since one thread will use the
base picture. |
| 788 fPicture->clone(fPictureClones, fNumThreads - 1); | 833 fPicture->clone(fPictureClones, fNumThreads - 1); |
| 789 // Populate each thread with the appropriate data. | 834 // Populate each thread with the appropriate data. |
| 790 // Group the tiles into nearly equal size chunks, rounding up so we're sure
to cover them all. | 835 // Group the tiles into nearly equal size chunks, rounding up so we're sure
to cover them all. |
| 791 const int chunkSize = (fTileRects.count() + fNumThreads - 1) / fNumThreads; | 836 const int chunkSize = (fTileRects.count() + fNumThreads - 1) / fNumThreads; |
| 792 | 837 |
| 793 for (int i = 0; i < fNumThreads; i++) { | 838 for (int i = 0; i < fNumThreads; i++) { |
| 794 SkPicture* pic; | 839 SkPicture* pic; |
| 795 if (i == fNumThreads-1) { | 840 if (i == fNumThreads-1) { |
| 796 // The last set will use the original SkPicture. | 841 // The last set will use the original SkPicture. |
| 797 pic = fPicture; | 842 pic = fPicture; |
| 798 } else { | 843 } else { |
| 799 pic = &fPictureClones[i]; | 844 pic = &fPictureClones[i]; |
| 800 } | 845 } |
| 801 const int start = i * chunkSize; | 846 const int start = i * chunkSize; |
| 802 const int end = SkMin32(start + chunkSize, fTileRects.count()); | 847 const int end = SkMin32(start + chunkSize, fTileRects.count()); |
| 803 fCloneData[i] = SkNEW_ARGS(CloneData, | 848 fCloneData[i] = SkNEW_ARGS(CloneData, |
| 804 (pic, fCanvasPool[i], fTileRects, start, end,
&fCountdown, | 849 (pic, fCanvasPool[i], fTileRects, start, end,
&fCountdown, |
| 805 fJsonSummaryPtr)); | 850 fJsonSummaryPtr, useChecksumBasedFilenames))
; |
| 806 } | 851 } |
| 807 } | 852 } |
| 808 | 853 |
| 809 bool MultiCorePictureRenderer::render(const SkString *path, SkBitmap** out) { | 854 bool MultiCorePictureRenderer::render(SkBitmap** out) { |
| 810 bool success = true; | 855 bool success = true; |
| 811 if (path != NULL) { | 856 if (!fOutputDir.isEmpty()) { |
| 812 for (int i = 0; i < fNumThreads-1; i++) { | 857 for (int i = 0; i < fNumThreads-1; i++) { |
| 813 fCloneData[i]->setPathAndSuccess(path, &success); | 858 fCloneData[i]->setPathsAndSuccess(fOutputDir, fInputFilename, &succe
ss); |
| 814 } | 859 } |
| 815 } | 860 } |
| 816 | 861 |
| 817 if (NULL != out) { | 862 if (NULL != out) { |
| 818 *out = SkNEW(SkBitmap); | 863 *out = SkNEW(SkBitmap); |
| 819 setup_bitmap(*out, fPicture->width(), fPicture->height()); | 864 setup_bitmap(*out, fPicture->width(), fPicture->height()); |
| 820 for (int i = 0; i < fNumThreads; i++) { | 865 for (int i = 0; i < fNumThreads; i++) { |
| 821 fCloneData[i]->setBitmap(*out); | 866 fCloneData[i]->setBitmap(*out); |
| 822 } | 867 } |
| 823 } else { | 868 } else { |
| (...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 861 ////////////////////////////////////////////////////////////////////////////////
/////////////// | 906 ////////////////////////////////////////////////////////////////////////////////
/////////////// |
| 862 | 907 |
| 863 void PlaybackCreationRenderer::setup() { | 908 void PlaybackCreationRenderer::setup() { |
| 864 fReplayer.reset(this->createPicture()); | 909 fReplayer.reset(this->createPicture()); |
| 865 SkCanvas* recorder = fReplayer->beginRecording(this->getViewWidth(), this->g
etViewHeight(), | 910 SkCanvas* recorder = fReplayer->beginRecording(this->getViewWidth(), this->g
etViewHeight(), |
| 866 this->recordFlags()); | 911 this->recordFlags()); |
| 867 this->scaleToScaleFactor(recorder); | 912 this->scaleToScaleFactor(recorder); |
| 868 fPicture->draw(recorder); | 913 fPicture->draw(recorder); |
| 869 } | 914 } |
| 870 | 915 |
| 871 bool PlaybackCreationRenderer::render(const SkString*, SkBitmap** out) { | 916 bool PlaybackCreationRenderer::render(SkBitmap** out) { |
| 872 fReplayer->endRecording(); | 917 fReplayer->endRecording(); |
| 873 // Since this class does not actually render, return false. | 918 // Since this class does not actually render, return false. |
| 874 return false; | 919 return false; |
| 875 } | 920 } |
| 876 | 921 |
| 877 SkString PlaybackCreationRenderer::getConfigNameInternal() { | 922 SkString PlaybackCreationRenderer::getConfigNameInternal() { |
| 878 return SkString("playback_creation"); | 923 return SkString("playback_creation"); |
| 879 } | 924 } |
| 880 | 925 |
| 881 ////////////////////////////////////////////////////////////////////////////////
/////////////// | 926 ////////////////////////////////////////////////////////////////////////////////
/////////////// |
| (...skipping 26 matching lines...) Expand all Loading... |
| 908 fPicture->height(), fGridInfo)); | 953 fPicture->height(), fGridInfo)); |
| 909 } | 954 } |
| 910 SkASSERT(0); // invalid bbhType | 955 SkASSERT(0); // invalid bbhType |
| 911 return NULL; | 956 return NULL; |
| 912 } | 957 } |
| 913 | 958 |
| 914 /////////////////////////////////////////////////////////////////////////////// | 959 /////////////////////////////////////////////////////////////////////////////// |
| 915 | 960 |
| 916 class GatherRenderer : public PictureRenderer { | 961 class GatherRenderer : public PictureRenderer { |
| 917 public: | 962 public: |
| 918 virtual bool render(const SkString* path, SkBitmap** out = NULL) | 963 virtual bool render(SkBitmap** out = NULL) |
| 919 SK_OVERRIDE { | 964 SK_OVERRIDE { |
| 920 SkRect bounds = SkRect::MakeWH(SkIntToScalar(fPicture->width()), | 965 SkRect bounds = SkRect::MakeWH(SkIntToScalar(fPicture->width()), |
| 921 SkIntToScalar(fPicture->height())); | 966 SkIntToScalar(fPicture->height())); |
| 922 SkData* data = SkPictureUtils::GatherPixelRefs(fPicture, bounds); | 967 SkData* data = SkPictureUtils::GatherPixelRefs(fPicture, bounds); |
| 923 SkSafeUnref(data); | 968 SkSafeUnref(data); |
| 924 | 969 |
| 925 return NULL == path; // we don't have anything to write | 970 return (fOutputDir.isEmpty()); // we don't have anything to write |
| 926 } | 971 } |
| 927 | 972 |
| 928 private: | 973 private: |
| 929 virtual SkString getConfigNameInternal() SK_OVERRIDE { | 974 virtual SkString getConfigNameInternal() SK_OVERRIDE { |
| 930 return SkString("gather_pixelrefs"); | 975 return SkString("gather_pixelrefs"); |
| 931 } | 976 } |
| 932 }; | 977 }; |
| 933 | 978 |
| 934 PictureRenderer* CreateGatherPixelRefsRenderer() { | 979 PictureRenderer* CreateGatherPixelRefsRenderer() { |
| 935 return SkNEW(GatherRenderer); | 980 return SkNEW(GatherRenderer); |
| 936 } | 981 } |
| 937 | 982 |
| 938 /////////////////////////////////////////////////////////////////////////////// | 983 /////////////////////////////////////////////////////////////////////////////// |
| 939 | 984 |
| 940 class PictureCloneRenderer : public PictureRenderer { | 985 class PictureCloneRenderer : public PictureRenderer { |
| 941 public: | 986 public: |
| 942 virtual bool render(const SkString* path, SkBitmap** out = NULL) | 987 virtual bool render(SkBitmap** out = NULL) |
| 943 SK_OVERRIDE { | 988 SK_OVERRIDE { |
| 944 for (int i = 0; i < 100; ++i) { | 989 for (int i = 0; i < 100; ++i) { |
| 945 SkPicture* clone = fPicture->clone(); | 990 SkPicture* clone = fPicture->clone(); |
| 946 SkSafeUnref(clone); | 991 SkSafeUnref(clone); |
| 947 } | 992 } |
| 948 | 993 |
| 949 return NULL == path; // we don't have anything to write | 994 return (fOutputDir.isEmpty()); // we don't have anything to write |
| 950 } | 995 } |
| 951 | 996 |
| 952 private: | 997 private: |
| 953 virtual SkString getConfigNameInternal() SK_OVERRIDE { | 998 virtual SkString getConfigNameInternal() SK_OVERRIDE { |
| 954 return SkString("picture_clone"); | 999 return SkString("picture_clone"); |
| 955 } | 1000 } |
| 956 }; | 1001 }; |
| 957 | 1002 |
| 958 PictureRenderer* CreatePictureCloneRenderer() { | 1003 PictureRenderer* CreatePictureCloneRenderer() { |
| 959 return SkNEW(PictureCloneRenderer); | 1004 return SkNEW(PictureCloneRenderer); |
| 960 } | 1005 } |
| 961 | 1006 |
| 962 } // namespace sk_tools | 1007 } // namespace sk_tools |
| OLD | NEW |