Chromium Code Reviews| Index: tools/PictureRenderer.cpp |
| diff --git a/tools/PictureRenderer.cpp b/tools/PictureRenderer.cpp |
| index c53bd08265a6e477215c7a9658c0c277d561dbd5..ce5b80a61e695f712d060f11d3bebd12e253cd78 100644 |
| --- a/tools/PictureRenderer.cpp |
| +++ b/tools/PictureRenderer.cpp |
| @@ -8,6 +8,7 @@ |
| #include "PictureRenderer.h" |
| #include "picture_utils.h" |
| #include "SamplePipeControllers.h" |
| +#include "SkBitmapHasher.h" |
| #include "SkCanvas.h" |
| #include "SkData.h" |
| #include "SkDevice.h" |
| @@ -40,6 +41,39 @@ enum { |
| kDefaultTileHeight = 256 |
| }; |
| +/* TODO(epoger): These constants are already maintained in 2 other places: |
| + * gm/gm_json.py and gm/gm_expectations.cpp. We shouldn't add yet a third place. |
| + * Figure out a way to share the definitions instead. |
| + */ |
| +const static char kJsonKey_ActualResults[] = "actual-results"; |
| +const static char kJsonKey_ActualResults_Failed[] = "failed"; |
| +const static char kJsonKey_ActualResults_FailureIgnored[]= "failure-ignored"; |
| +const static char kJsonKey_ActualResults_NoComparison[] = "no-comparison"; |
| +const static char kJsonKey_ActualResults_Succeeded[] = "succeeded"; |
| +const static char kJsonKey_ExpectedResults[] = "expected-results"; |
| +const static char kJsonKey_ExpectedResults_AllowedDigests[] = "allowed-digests"; |
| +const static char kJsonKey_ExpectedResults_IgnoreFailure[] = "ignore-failure"; |
| +const static char kJsonKey_Hashtype_Bitmap_64bitMD5[] = "bitmap-64bitMD5"; |
| + |
| +void ImageResultsSummary::add(const char *testName, const SkBitmap& bitmap) { |
| + uint64_t hash; |
| + SkAssertResult(SkBitmapHasher::ComputeDigest(bitmap, &hash)); |
| + Json::Value jsonTypeValuePair; |
| + jsonTypeValuePair.append(Json::Value(kJsonKey_Hashtype_Bitmap_64bitMD5)); |
| + jsonTypeValuePair.append(Json::UInt64(hash)); |
| + fActualResultsNoComparison[testName] = jsonTypeValuePair; |
| +} |
| + |
| +void ImageResultsSummary::writeToFile(const char *filename) { |
| + Json::Value actualResults; |
| + actualResults[kJsonKey_ActualResults_NoComparison] = fActualResultsNoComparison; |
| + Json::Value root; |
| + root[kJsonKey_ActualResults] = actualResults; |
| + std::string jsonStdString = root.toStyledString(); |
| + SkFILEWStream stream(filename); |
| + stream.write(jsonStdString.c_str(), jsonStdString.length()); |
| +} |
| + |
| void PictureRenderer::init(SkPicture* pict) { |
| SkASSERT(NULL == fPicture); |
| SkASSERT(NULL == fCanvas.get()); |
| @@ -217,14 +251,28 @@ uint32_t PictureRenderer::recordFlags() { |
| * @param canvas Must be non-null. Canvas to be written to a file. |
| * @param path Path for the file to be written. Should have no extension; write() will append |
| * an appropriate one. Passed in by value so it can be modified. |
| + * @param jsonSummaryPtr If not null, add image results to this summary. |
| * @return bool True if the Canvas is written to a file. |
| + * |
| + * TODO(epoger): Right now, all canvases must pass through this function in order to be appended |
| + * to the ImageResultsSummary. We need some way to add bitmaps to the ImageResultsSummary |
| + * even if --writePath has not been specified (and thus this function is not called). |
| + * |
| + * One fix would be to pass in these path elements separately, and allow this function to be |
| + * called even if --writePath was not specified... |
| + * const char *outputDir // NULL if we don't want to write image files to disk |
| + * const char *filename // name we use within JSON summary, and as the filename within outputDir |
| */ |
| -static bool write(SkCanvas* canvas, SkString path) { |
| +static bool write(SkCanvas* canvas, const SkString* path, ImageResultsSummary *jsonSummaryPtr) { |
| SkASSERT(canvas != NULL); |
| if (NULL == canvas) { |
| return false; |
| } |
| + SkASSERT(path != NULL); // TODO(epoger): we want to remove this constraint, as noted above |
| + SkString fullPathname(*path); |
| + fullPathname.append(".png"); |
| + |
| SkBitmap bitmap; |
| SkISize size = canvas->getDeviceSize(); |
| sk_tools::setup_bitmap(&bitmap, size.width(), size.height()); |
| @@ -232,22 +280,33 @@ static bool write(SkCanvas* canvas, SkString path) { |
| canvas->readPixels(&bitmap, 0, 0); |
| sk_tools::force_all_opaque(bitmap); |
| - // Since path is passed in by value, it is okay to modify it. |
| - path.append(".png"); |
| - return SkImageEncoder::EncodeFile(path.c_str(), bitmap, SkImageEncoder::kPNG_Type, 100); |
| + if (NULL != jsonSummaryPtr) { |
| + // EPOGER: This is a hacky way of constructing the filename associated with the |
| + // image checksum; we assume that outputDir is not NULL, and we remove outputDir |
| + // from fullPathname. |
| + // |
| + // EPOGER: what about including the config type within hashFilename? That way, |
| + // we could combine results of different config types without conflicting filenames. |
| + SkString hashFilename; |
| + sk_tools::get_basename(&hashFilename, fullPathname); |
| + jsonSummaryPtr->add(hashFilename.c_str(), bitmap); |
| + } |
| + |
| + return SkImageEncoder::EncodeFile(fullPathname.c_str(), bitmap, SkImageEncoder::kPNG_Type, 100); |
| } |
| /** |
| - * If path is non NULL, append number to it, and call write(SkCanvas*, SkString) to write the |
| + * If path is non NULL, append number to it, and call write() to write the |
| * provided canvas to a file. Returns true if path is NULL or if write() succeeds. |
| */ |
| -static bool writeAppendNumber(SkCanvas* canvas, const SkString* path, int number) { |
| +static bool writeAppendNumber(SkCanvas* canvas, const SkString* path, int number, |
| + ImageResultsSummary *jsonSummaryPtr) { |
| if (NULL == path) { |
| return true; |
| } |
| SkString pathWithNumber(*path); |
| pathWithNumber.appendf("%i", number); |
| - return write(canvas, pathWithNumber); |
| + return write(canvas, &pathWithNumber, jsonSummaryPtr); |
| } |
| /////////////////////////////////////////////////////////////////////////////////////////////// |
| @@ -309,7 +368,7 @@ bool PipePictureRenderer::render(const SkString* path, SkBitmap** out) { |
| writer.endRecording(); |
| fCanvas->flush(); |
| if (NULL != path) { |
| - return write(fCanvas, *path); |
| + return write(fCanvas, path, fJsonSummaryPtr); |
| } |
| if (NULL != out) { |
| *out = SkNEW(SkBitmap); |
| @@ -340,7 +399,7 @@ bool SimplePictureRenderer::render(const SkString* path, SkBitmap** out) { |
| fCanvas->drawPicture(*fPicture); |
| fCanvas->flush(); |
| if (NULL != path) { |
| - return write(fCanvas, *path); |
| + return write(fCanvas, path, fJsonSummaryPtr); |
| } |
| if (NULL != out) { |
| @@ -506,6 +565,17 @@ static void DrawTileToCanvas(SkCanvas* canvas, const SkRect& tileRect, T* playba |
| /////////////////////////////////////////////////////////////////////////////////////////////// |
| +/** |
| + * Copies the entirety of the src bitmap (typically a tile) into a portion of the dst bitmap. |
|
epoger
2013/12/13 18:33:25
Added this documentation because I thought the fun
rmistry
2013/12/13 19:04:11
looks fine to me
|
| + * If the src bitmap is too large to fit within the dst bitmap after the x and y |
| + * offsets have been applied, any excess will be ignored (so only the top-left portion of the |
| + * src bitmap will be copied). |
| + * |
| + * @param src source bitmap |
| + * @param dst destination bitmap |
| + * @param xDst x-offset within destination bitmap |
| + * @param yDst y-offset within destination bitmap |
| + */ |
| static void bitmapCopySubset(const SkBitmap& src, SkBitmap* dst, int xDst, |
| int yDst) { |
| for (int y = 0; y <src.height() && y + yDst < dst->height() ; y++) { |
| @@ -545,10 +615,11 @@ bool TiledPictureRenderer::render(const SkString* path, SkBitmap** out) { |
| for (int i = 0; i < fTileRects.count(); ++i) { |
| DrawTileToCanvas(fCanvas, fTileRects[i], fPicture); |
| if (NULL != path) { |
| - success &= writeAppendNumber(fCanvas, path, i); |
| + success &= writeAppendNumber(fCanvas, path, i, fJsonSummaryPtr); |
| } |
| if (NULL != out) { |
| if (fCanvas->readPixels(&bitmap, 0, 0)) { |
| + // Add this tile to the entire bitmap. |
| bitmapCopySubset(bitmap, *out, SkScalarFloorToInt(fTileRects[i].left()), |
| SkScalarFloorToInt(fTileRects[i].top())); |
| } else { |
| @@ -603,7 +674,7 @@ class CloneData : public SkRunnable { |
| public: |
| CloneData(SkPicture* clone, SkCanvas* canvas, SkTDArray<SkRect>& rects, int start, int end, |
| - SkRunnable* done) |
| + SkRunnable* done, ImageResultsSummary* jsonSummaryPtr) |
| : fClone(clone) |
| , fCanvas(canvas) |
| , fPath(NULL) |
| @@ -611,7 +682,8 @@ public: |
| , fStart(start) |
| , fEnd(end) |
| , fSuccess(NULL) |
| - , fDone(done) { |
| + , fDone(done) |
| + , fJsonSummaryPtr(jsonSummaryPtr) { |
| SkASSERT(fDone != NULL); |
| } |
| @@ -626,7 +698,7 @@ public: |
| for (int i = fStart; i < fEnd; i++) { |
| DrawTileToCanvas(fCanvas, fRects[i], fClone); |
| - if (fPath != NULL && !writeAppendNumber(fCanvas, fPath, i) |
| + if ((fPath != NULL) && !writeAppendNumber(fCanvas, fPath, i, fJsonSummaryPtr) |
| && fSuccess != NULL) { |
| *fSuccess = false; |
| // If one tile fails to write to a file, do not continue drawing the rest. |
| @@ -669,6 +741,7 @@ private: |
| // and only set to false upon failure to write to a PNG. |
| SkRunnable* fDone; |
| SkBitmap* fBitmap; |
| + ImageResultsSummary* fJsonSummaryPtr; |
| }; |
| MultiCorePictureRenderer::MultiCorePictureRenderer(int threadCount) |
| @@ -704,7 +777,8 @@ void MultiCorePictureRenderer::init(SkPicture *pict) { |
| const int start = i * chunkSize; |
| const int end = SkMin32(start + chunkSize, fTileRects.count()); |
| fCloneData[i] = SkNEW_ARGS(CloneData, |
| - (pic, fCanvasPool[i], fTileRects, start, end, &fCountdown)); |
| + (pic, fCanvasPool[i], fTileRects, start, end, &fCountdown, |
| + fJsonSummaryPtr)); |
| } |
| } |