add --writeChecksumBasedFilenames flag to render_pictures

BUG=skia:1455,skia:2230
R=robertphillips@google.com, robertphillips@chromium.org

Author: epoger@google.com

Review URL: https://codereview.chromium.org/202983003

git-svn-id: http://skia.googlecode.com/svn/trunk@13859 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/tools/PictureRenderer.cpp b/tools/PictureRenderer.cpp
index 466e31d..f62a05d 100644
--- a/tools/PictureRenderer.cpp
+++ b/tools/PictureRenderer.cpp
@@ -58,15 +58,19 @@
 const static char kJsonKey_ActualResults_NoComparison[]  = "no-comparison";
 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));
+void ImageResultsSummary::add(const char *testName, uint64_t hash) {
     Json::Value jsonTypeValuePair;
     jsonTypeValuePair.append(Json::Value(kJsonKey_Hashtype_Bitmap_64bitMD5));
     jsonTypeValuePair.append(Json::UInt64(hash));
     fActualResultsNoComparison[testName] = jsonTypeValuePair;
 }
 
+void ImageResultsSummary::add(const char *testName, const SkBitmap& bitmap) {
+    uint64_t hash;
+    SkAssertResult(SkBitmapHasher::ComputeDigest(bitmap, &hash));
+    this->add(testName, hash);
+}
+
 void ImageResultsSummary::writeToFile(const char *filename) {
     Json::Value actualResults;
     actualResults[kJsonKey_ActualResults_NoComparison] = fActualResultsNoComparison;
@@ -77,7 +81,12 @@
     stream.write(jsonStdString.c_str(), jsonStdString.length());
 }
 
-void PictureRenderer::init(SkPicture* pict) {
+void PictureRenderer::init(SkPicture* pict, const SkString* outputDir,
+                           const SkString* inputFilename, bool useChecksumBasedFilenames) {
+    this->CopyString(&fOutputDir, outputDir);
+    this->CopyString(&fInputFilename, inputFilename);
+    fUseChecksumBasedFilenames = useChecksumBasedFilenames;
+
     SkASSERT(NULL == fPicture);
     SkASSERT(NULL == fCanvas.get());
     if (fPicture != NULL || NULL != fCanvas.get()) {
@@ -94,6 +103,14 @@
     fCanvas.reset(this->setupCanvas());
 }
 
+void PictureRenderer::CopyString(SkString* dest, const SkString* src) {
+    if (NULL != src) {
+        dest->set(*src);
+    } else {
+        dest->reset();
+    }
+}
+
 class FlagsDrawFilter : public SkDrawFilter {
 public:
     FlagsDrawFilter(PictureRenderer::DrawFilterFlags* flags) :
@@ -281,10 +298,13 @@
 
 /**
  * Write the canvas to the specified path.
+ *
  * @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 outputDir If nonempty, write the binary image to a file within this directory.
+ * @param inputFilename If we are writing out a binary image, use this to build its filename.
  * @param jsonSummaryPtr If not null, add image results to this summary.
+ * @param useChecksumBasedFilenames If true, use checksum-based filenames when writing to disk.
+ * @param numberToAppend If not null, append this number to the filename.
  * @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
@@ -295,53 +315,64 @@
  * 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
+ *
+ * UPDATE: Now that outputDir and inputFilename are passed separately, we should be able to do that.
  */
-static bool write(SkCanvas* canvas, const SkString* path, ImageResultsSummary *jsonSummaryPtr) {
+static bool write(SkCanvas* canvas, const SkString& outputDir, const SkString& inputFilename,
+                  ImageResultsSummary *jsonSummaryPtr, bool useChecksumBasedFilenames,
+                  const int* numberToAppend=NULL) {
     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());
 
+    // Make sure we only compute the bitmap hash once (at most).
+    uint64_t hash;
+    bool generatedHash = false;
+
     canvas->readPixels(&bitmap, 0, 0);
     sk_tools::force_all_opaque(bitmap);
 
+    SkString outputFilename(inputFilename);
+    outputFilename.remove(outputFilename.size() - 4, 4);
+    if (NULL != numberToAppend) {
+        outputFilename.appendf("%i", *numberToAppend);
+    }
+    outputFilename.append(".png");
+    // TODO(epoger): what about including the config type within outputFilename?  That way,
+    // we could combine results of different config types without conflicting filenames.
+
     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);
+        if (!generatedHash) {
+            SkAssertResult(SkBitmapHasher::ComputeDigest(bitmap, &hash));
+            generatedHash = true;
+        }
+        jsonSummaryPtr->add(outputFilename.c_str(), hash);
     }
 
+    // Update outputFilename AFTER adding to JSON summary, but BEFORE writing out the image file.
+    if (useChecksumBasedFilenames) {
+        if (!generatedHash) {
+            SkAssertResult(SkBitmapHasher::ComputeDigest(bitmap, &hash));
+            generatedHash = true;
+        }
+        outputFilename.set(kJsonKey_Hashtype_Bitmap_64bitMD5);
+        outputFilename.append("_");
+        outputFilename.appendU64(hash);
+        outputFilename.append(".png");
+    }
+
+    SkASSERT(!outputDir.isEmpty()); // TODO(epoger): we want to remove this constraint,
+                                    // as noted above
+    SkString fullPathname;
+    make_filepath(&fullPathname, outputDir, outputFilename);
     return SkImageEncoder::EncodeFile(fullPathname.c_str(), bitmap, SkImageEncoder::kPNG_Type, 100);
 }
 
-/**
- * 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,
-                              ImageResultsSummary *jsonSummaryPtr) {
-    if (NULL == path) {
-        return true;
-    }
-    SkString pathWithNumber(*path);
-    pathWithNumber.appendf("%i", number);
-    return write(canvas, &pathWithNumber, jsonSummaryPtr);
-}
-
 ///////////////////////////////////////////////////////////////////////////////////////////////
 
 SkCanvas* RecordPictureRenderer::setupCanvas(int width, int height) {
@@ -354,18 +385,17 @@
     return SkImageEncoder::EncodeData(bm, SkImageEncoder::kPNG_Type, 100);
 }
 
-bool RecordPictureRenderer::render(const SkString* path, SkBitmap** out) {
+bool RecordPictureRenderer::render(SkBitmap** out) {
     SkAutoTUnref<SkPicture> replayer(this->createPicture());
     SkCanvas* recorder = replayer->beginRecording(this->getViewWidth(), this->getViewHeight(),
                                                   this->recordFlags());
     this->scaleToScaleFactor(recorder);
     fPicture->draw(recorder);
     replayer->endRecording();
-    if (path != NULL) {
+    if (!fOutputDir.isEmpty()) {
         // Record the new picture as a new SKP with PNG encoded bitmaps.
-        SkString skpPath(*path);
-        // ".skp" was removed from 'path' before being passed in here.
-        skpPath.append(".skp");
+        SkString skpPath;
+        make_filepath(&skpPath, fOutputDir, fInputFilename);
         SkFILEWStream stream(skpPath.c_str());
         replayer->serialize(&stream, &encode_bitmap_to_data);
         return true;
@@ -379,7 +409,7 @@
 
 ///////////////////////////////////////////////////////////////////////////////////////////////
 
-bool PipePictureRenderer::render(const SkString* path, SkBitmap** out) {
+bool PipePictureRenderer::render(SkBitmap** out) {
     SkASSERT(fCanvas.get() != NULL);
     SkASSERT(fPicture != NULL);
     if (NULL == fCanvas.get() || NULL == fPicture) {
@@ -392,8 +422,9 @@
     pipeCanvas->drawPicture(*fPicture);
     writer.endRecording();
     fCanvas->flush();
-    if (NULL != path) {
-        return write(fCanvas, path, fJsonSummaryPtr);
+    if (!fOutputDir.isEmpty()) {
+        return write(fCanvas, fOutputDir, fInputFilename, fJsonSummaryPtr,
+                     fUseChecksumBasedFilenames);
     }
     if (NULL != out) {
         *out = SkNEW(SkBitmap);
@@ -409,12 +440,13 @@
 
 ///////////////////////////////////////////////////////////////////////////////////////////////
 
-void SimplePictureRenderer::init(SkPicture* picture) {
-    INHERITED::init(picture);
+void SimplePictureRenderer::init(SkPicture* picture, const SkString* outputDir,
+                                 const SkString* inputFilename, bool useChecksumBasedFilenames) {
+    INHERITED::init(picture, outputDir, inputFilename, useChecksumBasedFilenames);
     this->buildBBoxHierarchy();
 }
 
-bool SimplePictureRenderer::render(const SkString* path, SkBitmap** out) {
+bool SimplePictureRenderer::render(SkBitmap** out) {
     SkASSERT(fCanvas.get() != NULL);
     SkASSERT(fPicture != NULL);
     if (NULL == fCanvas.get() || NULL == fPicture) {
@@ -423,8 +455,9 @@
 
     fCanvas->drawPicture(*fPicture);
     fCanvas->flush();
-    if (NULL != path) {
-        return write(fCanvas, path, fJsonSummaryPtr);
+    if (!fOutputDir.isEmpty()) {
+        return write(fCanvas, fOutputDir, fInputFilename, fJsonSummaryPtr,
+                     fUseChecksumBasedFilenames);
     }
 
     if (NULL != out) {
@@ -452,7 +485,8 @@
     , fTilesX(0)
     , fTilesY(0) { }
 
-void TiledPictureRenderer::init(SkPicture* pict) {
+void TiledPictureRenderer::init(SkPicture* pict, const SkString* outputDir,
+                                const SkString* inputFilename, bool useChecksumBasedFilenames) {
     SkASSERT(pict != NULL);
     SkASSERT(0 == fTileRects.count());
     if (NULL == pict || fTileRects.count() != 0) {
@@ -462,6 +496,9 @@
     // Do not call INHERITED::init(), which would create a (potentially large) canvas which is not
     // used by bench_pictures.
     fPicture = pict;
+    this->CopyString(&fOutputDir, outputDir);
+    this->CopyString(&fInputFilename, inputFilename);
+    fUseChecksumBasedFilenames = useChecksumBasedFilenames;
     fPicture->ref();
     this->buildBBoxHierarchy();
 
@@ -623,7 +660,7 @@
     draw_tile_to_canvas(fCanvas, fTileRects[fCurrentTileOffset], fPicture);
 }
 
-bool TiledPictureRenderer::render(const SkString* path, SkBitmap** out) {
+bool TiledPictureRenderer::render(SkBitmap** out) {
     SkASSERT(fPicture != NULL);
     if (NULL == fPicture) {
         return false;
@@ -638,8 +675,9 @@
     bool success = true;
     for (int i = 0; i < fTileRects.count(); ++i) {
         draw_tile_to_canvas(fCanvas, fTileRects[i], fPicture);
-        if (NULL != path) {
-            success &= writeAppendNumber(fCanvas, path, i, fJsonSummaryPtr);
+        if (!fOutputDir.isEmpty()) {
+            success &= write(fCanvas, fOutputDir, fInputFilename, fJsonSummaryPtr,
+                             fUseChecksumBasedFilenames, &i);
         }
         if (NULL != out) {
             if (fCanvas->readPixels(&bitmap, 0, 0)) {
@@ -698,16 +736,16 @@
 
 public:
     CloneData(SkPicture* clone, SkCanvas* canvas, SkTDArray<SkRect>& rects, int start, int end,
-              SkRunnable* done, ImageResultsSummary* jsonSummaryPtr)
+              SkRunnable* done, ImageResultsSummary* jsonSummaryPtr, bool useChecksumBasedFilenames)
         : fClone(clone)
         , fCanvas(canvas)
-        , fPath(NULL)
         , fRects(rects)
         , fStart(start)
         , fEnd(end)
         , fSuccess(NULL)
         , fDone(done)
-        , fJsonSummaryPtr(jsonSummaryPtr) {
+        , fJsonSummaryPtr(jsonSummaryPtr)
+        , fUseChecksumBasedFilenames(useChecksumBasedFilenames) {
         SkASSERT(fDone != NULL);
     }
 
@@ -722,7 +760,9 @@
 
         for (int i = fStart; i < fEnd; i++) {
             draw_tile_to_canvas(fCanvas, fRects[i], fClone);
-            if ((fPath != NULL) && !writeAppendNumber(fCanvas, fPath, i, fJsonSummaryPtr)
+            if ((!fOutputDir.isEmpty())
+                && !write(fCanvas, fOutputDir, fInputFilename, fJsonSummaryPtr,
+                          fUseChecksumBasedFilenames, &i)
                 && fSuccess != NULL) {
                 *fSuccess = false;
                 // If one tile fails to write to a file, do not continue drawing the rest.
@@ -743,8 +783,10 @@
         fDone->run();
     }
 
-    void setPathAndSuccess(const SkString* path, bool* success) {
-        fPath = path;
+    void setPathsAndSuccess(const SkString& outputDir, const SkString& inputFilename,
+                            bool* success) {
+        fOutputDir.set(outputDir);
+        fInputFilename.set(inputFilename);
         fSuccess = success;
     }
 
@@ -757,7 +799,8 @@
     SkPicture*         fClone;      // Picture to draw from. Each CloneData has a unique one which
                                     // is threadsafe.
     SkCanvas*          fCanvas;     // Canvas to draw to. Reused for each tile.
-    const SkString*    fPath;       // If non-null, path to write the result to as a PNG.
+    SkString           fOutputDir;  // If not empty, write results into this directory.
+    SkString           fInputFilename; // Filename of input SkPicture file.
     SkTDArray<SkRect>& fRects;      // All tiles of the picture.
     const int          fStart;      // Range of tiles drawn by this thread.
     const int          fEnd;
@@ -766,6 +809,7 @@
     SkRunnable*        fDone;
     SkBitmap*          fBitmap;
     ImageResultsSummary* fJsonSummaryPtr;
+    bool               fUseChecksumBasedFilenames;
 };
 
 MultiCorePictureRenderer::MultiCorePictureRenderer(int threadCount)
@@ -778,9 +822,10 @@
     fCloneData = SkNEW_ARRAY(CloneData*, fNumThreads);
 }
 
-void MultiCorePictureRenderer::init(SkPicture *pict) {
+void MultiCorePictureRenderer::init(SkPicture *pict, const SkString* outputDir,
+                                    const SkString* inputFilename, bool useChecksumBasedFilenames) {
     // Set fPicture and the tiles.
-    this->INHERITED::init(pict);
+    this->INHERITED::init(pict, outputDir, inputFilename, useChecksumBasedFilenames);
     for (int i = 0; i < fNumThreads; ++i) {
         *fCanvasPool.append() = this->setupCanvas(this->getTileWidth(), this->getTileHeight());
     }
@@ -802,15 +847,15 @@
         const int end = SkMin32(start + chunkSize, fTileRects.count());
         fCloneData[i] = SkNEW_ARGS(CloneData,
                                    (pic, fCanvasPool[i], fTileRects, start, end, &fCountdown,
-                                    fJsonSummaryPtr));
+                                    fJsonSummaryPtr, useChecksumBasedFilenames));
     }
 }
 
-bool MultiCorePictureRenderer::render(const SkString *path, SkBitmap** out) {
+bool MultiCorePictureRenderer::render(SkBitmap** out) {
     bool success = true;
-    if (path != NULL) {
+    if (!fOutputDir.isEmpty()) {
         for (int i = 0; i < fNumThreads-1; i++) {
-            fCloneData[i]->setPathAndSuccess(path, &success);
+            fCloneData[i]->setPathsAndSuccess(fOutputDir, fInputFilename, &success);
         }
     }
 
@@ -868,7 +913,7 @@
     fPicture->draw(recorder);
 }
 
-bool PlaybackCreationRenderer::render(const SkString*, SkBitmap** out) {
+bool PlaybackCreationRenderer::render(SkBitmap** out) {
     fReplayer->endRecording();
     // Since this class does not actually render, return false.
     return false;
@@ -915,14 +960,14 @@
 
 class GatherRenderer : public PictureRenderer {
 public:
-    virtual bool render(const SkString* path, SkBitmap** out = NULL)
+    virtual bool render(SkBitmap** out = NULL)
             SK_OVERRIDE {
         SkRect bounds = SkRect::MakeWH(SkIntToScalar(fPicture->width()),
                                        SkIntToScalar(fPicture->height()));
         SkData* data = SkPictureUtils::GatherPixelRefs(fPicture, bounds);
         SkSafeUnref(data);
 
-        return NULL == path;    // we don't have anything to write
+        return (fOutputDir.isEmpty());    // we don't have anything to write
     }
 
 private:
@@ -939,14 +984,14 @@
 
 class PictureCloneRenderer : public PictureRenderer {
 public:
-    virtual bool render(const SkString* path, SkBitmap** out = NULL)
+    virtual bool render(SkBitmap** out = NULL)
             SK_OVERRIDE {
         for (int i = 0; i < 100; ++i) {
             SkPicture* clone = fPicture->clone();
             SkSafeUnref(clone);
         }
 
-        return NULL == path;    // we don't have anything to write
+        return (fOutputDir.isEmpty());    // we don't have anything to write
     }
 
 private: