diff options
author | commit-bot@chromium.org <commit-bot@chromium.org@2bbb7eff-a529-9590-31e7-b0007b416f81> | 2014-05-15 15:10:48 +0000 |
---|---|---|
committer | commit-bot@chromium.org <commit-bot@chromium.org@2bbb7eff-a529-9590-31e7-b0007b416f81> | 2014-05-15 15:10:48 +0000 |
commit | 3f0451772109959fcb79bacf2c9a03e0eb39ff27 (patch) | |
tree | 19e1b86cc9824172c7f1c422591cc51314203fdb /tools | |
parent | 76ac7f81d39a99616541ead9c40aa6d208b517af (diff) |
render_pictures: add --mismatchPath flag
When set, it will only write out images that don't match expectations.
BUG=skia:1942
R=rmistry@google.com
Author: epoger@google.com
Review URL: https://codereview.chromium.org/283123002
git-svn-id: http://skia.googlecode.com/svn/trunk@14748 2bbb7eff-a529-9590-31e7-b0007b416f81
Diffstat (limited to 'tools')
-rw-r--r-- | tools/CopyTilesRenderer.cpp | 12 | ||||
-rw-r--r-- | tools/CopyTilesRenderer.h | 3 | ||||
-rw-r--r-- | tools/PictureBenchmark.cpp | 2 | ||||
-rw-r--r-- | tools/PictureRenderer.cpp | 87 | ||||
-rw-r--r-- | tools/PictureRenderer.h | 25 | ||||
-rw-r--r-- | tools/bbh_shootout.cpp | 2 | ||||
-rw-r--r-- | tools/image_expectations.cpp | 25 | ||||
-rw-r--r-- | tools/image_expectations.h | 13 | ||||
-rw-r--r-- | tools/picture_utils.cpp | 27 | ||||
-rw-r--r-- | tools/picture_utils.h | 16 | ||||
-rw-r--r-- | tools/render_pictures_main.cpp | 76 | ||||
-rwxr-xr-x | tools/tests/base_unittest.py | 16 | ||||
-rwxr-xr-x | tools/tests/render_pictures_test.py | 106 |
13 files changed, 285 insertions, 125 deletions
diff --git a/tools/CopyTilesRenderer.cpp b/tools/CopyTilesRenderer.cpp index 9e919e0a4f..8022092c9a 100644 --- a/tools/CopyTilesRenderer.cpp +++ b/tools/CopyTilesRenderer.cpp @@ -20,15 +20,17 @@ namespace sk_tools { : fXTilesPerLargeTile(x) , fYTilesPerLargeTile(y) { } - void CopyTilesRenderer::init(SkPicture* pict, const SkString* outputDir, - const SkString* inputFilename, bool useChecksumBasedFilenames) { + void CopyTilesRenderer::init(SkPicture* pict, const SkString* writePath, + const SkString* mismatchPath, const SkString* inputFilename, + bool useChecksumBasedFilenames) { // Do not call INHERITED::init(), which would create a (potentially large) canvas which is // not used by bench_pictures. SkASSERT(pict != NULL); // Only work with absolute widths (as opposed to percentages). SkASSERT(this->getTileWidth() != 0 && this->getTileHeight() != 0); fPicture.reset(pict)->ref(); - this->CopyString(&fOutputDir, outputDir); + this->CopyString(&fWritePath, writePath); + this->CopyString(&fMismatchPath, mismatchPath); this->CopyString(&fInputFilename, inputFilename); fUseChecksumBasedFilenames = useChecksumBasedFilenames; this->buildBBoxHierarchy(); @@ -64,13 +66,13 @@ namespace sk_tools { SkDEBUGCODE(bool extracted =) baseBitmap.extractSubset(&dst, subset); SkASSERT(extracted); - if (!fOutputDir.isEmpty()) { + if (!fWritePath.isEmpty()) { // Similar to write() in PictureRenderer.cpp, but just encodes // a bitmap directly. // TODO: Share more common code with write() to do this, to properly // write out the JSON summary, etc. SkString pathWithNumber; - make_filepath(&pathWithNumber, fOutputDir, fInputFilename); + make_filepath(&pathWithNumber, fWritePath, fInputFilename); pathWithNumber.remove(pathWithNumber.size() - 4, 4); pathWithNumber.appendf("%i.png", i++); SkBitmap copy; diff --git a/tools/CopyTilesRenderer.h b/tools/CopyTilesRenderer.h index ef1ccb09b4..3bf969b15f 100644 --- a/tools/CopyTilesRenderer.h +++ b/tools/CopyTilesRenderer.h @@ -23,7 +23,8 @@ namespace sk_tools { public: CopyTilesRenderer(int x, int y); - virtual void init(SkPicture* pict, const SkString* outputDir, const SkString* inputFilename, + virtual void init(SkPicture* pict, const SkString* writePath, const SkString* mismatchPath, + const SkString* inputFilename, bool useChecksumBasedFilenames) SK_OVERRIDE; /** diff --git a/tools/PictureBenchmark.cpp b/tools/PictureBenchmark.cpp index 2950e7d7a2..6c0325e2ee 100644 --- a/tools/PictureBenchmark.cpp +++ b/tools/PictureBenchmark.cpp @@ -74,7 +74,7 @@ void PictureBenchmark::run(SkPicture* pict) { return; } - fRenderer->init(pict, NULL, NULL, false); + fRenderer->init(pict, NULL, NULL, NULL, false); // We throw this away to remove first time effects (such as paging in this program) fRenderer->setup(); diff --git a/tools/PictureRenderer.cpp b/tools/PictureRenderer.cpp index 74567001e6..12f0afa879 100644 --- a/tools/PictureRenderer.cpp +++ b/tools/PictureRenderer.cpp @@ -48,9 +48,10 @@ enum { kDefaultTileHeight = 256 }; -void PictureRenderer::init(SkPicture* pict, const SkString* outputDir, +void PictureRenderer::init(SkPicture* pict, const SkString* writePath, const SkString* mismatchPath, const SkString* inputFilename, bool useChecksumBasedFilenames) { - this->CopyString(&fOutputDir, outputDir); + this->CopyString(&fWritePath, writePath); + this->CopyString(&fMismatchPath, mismatchPath); this->CopyString(&fInputFilename, inputFilename); fUseChecksumBasedFilenames = useChecksumBasedFilenames; @@ -265,8 +266,9 @@ uint32_t PictureRenderer::recordFlags() { * Write the canvas to an image file and/or JSON summary. * * @param canvas Must be non-null. Canvas to be written to a file. - * @param outputDir If nonempty, write the binary image to a file within this directory; - * if empty, don't write out the image at all. + * @param writePath If nonempty, write the binary image to a file within this directory. + * @param mismatchPath If nonempty, write the binary image to a file within this directory, + * but only if the image does not match expectations. * @param inputFilename If we are writing out a binary image, use this to build its filename. * @param jsonSummaryPtr If not null, add image results (checksum) to this summary. * @param useChecksumBasedFilenames If true, use checksum-based filenames when writing to disk. @@ -274,9 +276,9 @@ uint32_t PictureRenderer::recordFlags() { * * @return bool True if the operation completed successfully. */ -static bool write(SkCanvas* canvas, const SkString& outputDir, const SkString& inputFilename, - ImageResultsAndExpectations *jsonSummaryPtr, bool useChecksumBasedFilenames, - const int* tileNumberPtr=NULL) { +static bool write(SkCanvas* canvas, const SkString& writePath, const SkString& mismatchPath, + const SkString& inputFilename, ImageResultsAndExpectations *jsonSummaryPtr, + bool useChecksumBasedFilenames, const int* tileNumberPtr=NULL) { SkASSERT(canvas != NULL); if (NULL == canvas) { return false; @@ -325,21 +327,20 @@ static bool write(SkCanvas* canvas, const SkString& outputDir, const SkString& i jsonSummaryPtr->add(inputFilename.c_str(), outputRelativePath.c_str(), *imageDigestPtr, tileNumberPtr); + if (!mismatchPath.isEmpty() && + !jsonSummaryPtr->matchesExpectation(inputFilename.c_str(), *imageDigestPtr, + tileNumberPtr)) { + if (!write_bitmap_to_disk(bitmap, mismatchPath, outputSubdirPtr, outputFilename)) { + return false; + } + } } - if (outputDir.isEmpty()) { + if (writePath.isEmpty()) { return true; - } - - SkString dirPath; - if (outputSubdirPtr) { - dirPath = SkOSPath::SkPathJoin(outputDir.c_str(), outputSubdirPtr); - sk_mkdir(dirPath.c_str()); } else { - dirPath.set(outputDir); + return write_bitmap_to_disk(bitmap, writePath, outputSubdirPtr, outputFilename); } - SkString fullPath = SkOSPath::SkPathJoin(dirPath.c_str(), outputFilename.c_str()); - return SkImageEncoder::EncodeFile(fullPath.c_str(), bitmap, SkImageEncoder::kPNG_Type, 100); } /////////////////////////////////////////////////////////////////////////////////////////////// @@ -363,9 +364,9 @@ bool RecordPictureRenderer::render(SkBitmap** out) { this->scaleToScaleFactor(canvas); fPicture->draw(canvas); SkAutoTUnref<SkPicture> picture(recorder.endRecording()); - if (!fOutputDir.isEmpty()) { + if (!fWritePath.isEmpty()) { // Record the new picture as a new SKP with PNG encoded bitmaps. - SkString skpPath = SkOSPath::SkPathJoin(fOutputDir.c_str(), fInputFilename.c_str()); + SkString skpPath = SkOSPath::SkPathJoin(fWritePath.c_str(), fInputFilename.c_str()); SkFILEWStream stream(skpPath.c_str()); picture->serialize(&stream, &encode_bitmap_to_data); return true; @@ -397,7 +398,7 @@ bool PipePictureRenderer::render(SkBitmap** out) { setup_bitmap(*out, fPicture->width(), fPicture->height()); fCanvas->readPixels(*out, 0, 0); } - return write(fCanvas, fOutputDir, fInputFilename, fJsonSummaryPtr, + return write(fCanvas, fWritePath, fMismatchPath, fInputFilename, fJsonSummaryPtr, fUseChecksumBasedFilenames); } @@ -407,9 +408,10 @@ SkString PipePictureRenderer::getConfigNameInternal() { /////////////////////////////////////////////////////////////////////////////////////////////// -void SimplePictureRenderer::init(SkPicture* picture, const SkString* outputDir, - const SkString* inputFilename, bool useChecksumBasedFilenames) { - INHERITED::init(picture, outputDir, inputFilename, useChecksumBasedFilenames); +void SimplePictureRenderer::init(SkPicture* picture, const SkString* writePath, + const SkString* mismatchPath, const SkString* inputFilename, + bool useChecksumBasedFilenames) { + INHERITED::init(picture, writePath, mismatchPath, inputFilename, useChecksumBasedFilenames); this->buildBBoxHierarchy(); } @@ -427,7 +429,7 @@ bool SimplePictureRenderer::render(SkBitmap** out) { setup_bitmap(*out, fPicture->width(), fPicture->height()); fCanvas->readPixels(*out, 0, 0); } - return write(fCanvas, fOutputDir, fInputFilename, fJsonSummaryPtr, + return write(fCanvas, fWritePath, fMismatchPath, fInputFilename, fJsonSummaryPtr, fUseChecksumBasedFilenames); } @@ -447,8 +449,9 @@ TiledPictureRenderer::TiledPictureRenderer() , fTilesX(0) , fTilesY(0) { } -void TiledPictureRenderer::init(SkPicture* pict, const SkString* outputDir, - const SkString* inputFilename, bool useChecksumBasedFilenames) { +void TiledPictureRenderer::init(SkPicture* pict, const SkString* writePath, + const SkString* mismatchPath, const SkString* inputFilename, + bool useChecksumBasedFilenames) { SkASSERT(NULL != pict); SkASSERT(0 == fTileRects.count()); if (NULL == pict || fTileRects.count() != 0) { @@ -458,7 +461,8 @@ void TiledPictureRenderer::init(SkPicture* pict, const SkString* outputDir, // Do not call INHERITED::init(), which would create a (potentially large) canvas which is not // used by bench_pictures. fPicture.reset(pict)->ref(); - this->CopyString(&fOutputDir, outputDir); + this->CopyString(&fWritePath, writePath); + this->CopyString(&fMismatchPath, mismatchPath); this->CopyString(&fInputFilename, inputFilename); fUseChecksumBasedFilenames = useChecksumBasedFilenames; this->buildBBoxHierarchy(); @@ -636,7 +640,7 @@ bool TiledPictureRenderer::render(SkBitmap** out) { bool success = true; for (int i = 0; i < fTileRects.count(); ++i) { draw_tile_to_canvas(fCanvas, fTileRects[i], fPicture); - success &= write(fCanvas, fOutputDir, fInputFilename, fJsonSummaryPtr, + success &= write(fCanvas, fWritePath, fMismatchPath, fInputFilename, fJsonSummaryPtr, fUseChecksumBasedFilenames, &i); if (NULL != out) { if (fCanvas->readPixels(&bitmap, 0, 0)) { @@ -720,7 +724,7 @@ public: for (int i = fStart; i < fEnd; i++) { draw_tile_to_canvas(fCanvas, fRects[i], fClone); - if (!write(fCanvas, fOutputDir, fInputFilename, fJsonSummaryPtr, + if (!write(fCanvas, fWritePath, fMismatchPath, fInputFilename, fJsonSummaryPtr, fUseChecksumBasedFilenames, &i) && fSuccess != NULL) { *fSuccess = false; @@ -742,9 +746,10 @@ public: fDone->run(); } - void setPathsAndSuccess(const SkString& outputDir, const SkString& inputFilename, - bool* success) { - fOutputDir.set(outputDir); + void setPathsAndSuccess(const SkString& writePath, const SkString& mismatchPath, + const SkString& inputFilename, bool* success) { + fWritePath.set(writePath); + fMismatchPath.set(mismatchPath); fInputFilename.set(inputFilename); fSuccess = success; } @@ -758,7 +763,8 @@ private: 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. - SkString fOutputDir; // If not empty, write results into this directory. + SkString fWritePath; // If not empty, write all results into this directory. + SkString fMismatchPath; // If not empty, write all unexpected results into this dir. 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. @@ -781,10 +787,11 @@ MultiCorePictureRenderer::MultiCorePictureRenderer(int threadCount) fCloneData = SkNEW_ARRAY(CloneData*, fNumThreads); } -void MultiCorePictureRenderer::init(SkPicture *pict, const SkString* outputDir, - const SkString* inputFilename, bool useChecksumBasedFilenames) { +void MultiCorePictureRenderer::init(SkPicture *pict, const SkString* writePath, + const SkString* mismatchPath, const SkString* inputFilename, + bool useChecksumBasedFilenames) { // Set fPicture and the tiles. - this->INHERITED::init(pict, outputDir, inputFilename, useChecksumBasedFilenames); + this->INHERITED::init(pict, writePath, mismatchPath, inputFilename, useChecksumBasedFilenames); for (int i = 0; i < fNumThreads; ++i) { *fCanvasPool.append() = this->setupCanvas(this->getTileWidth(), this->getTileHeight()); } @@ -812,9 +819,9 @@ void MultiCorePictureRenderer::init(SkPicture *pict, const SkString* outputDir, bool MultiCorePictureRenderer::render(SkBitmap** out) { bool success = true; - if (!fOutputDir.isEmpty()) { + if (!fWritePath.isEmpty() || !fMismatchPath.isEmpty()) { for (int i = 0; i < fNumThreads-1; i++) { - fCloneData[i]->setPathsAndSuccess(fOutputDir, fInputFilename, &success); + fCloneData[i]->setPathsAndSuccess(fWritePath, fMismatchPath, fInputFilename, &success); } } @@ -912,7 +919,7 @@ public: SkData* data = SkPictureUtils::GatherPixelRefs(fPicture, bounds); SkSafeUnref(data); - return (fOutputDir.isEmpty()); // we don't have anything to write + return (fWritePath.isEmpty()); // we don't have anything to write } private: @@ -935,7 +942,7 @@ public: SkSafeUnref(clone); } - return (fOutputDir.isEmpty()); // we don't have anything to write + return (fWritePath.isEmpty()); // we don't have anything to write } private: diff --git a/tools/PictureRenderer.h b/tools/PictureRenderer.h index 342df7872a..468c567b13 100644 --- a/tools/PictureRenderer.h +++ b/tools/PictureRenderer.h @@ -84,13 +84,15 @@ public: * Called with each new SkPicture to render. * * @param pict The SkPicture to render. - * @param outputDir The output directory within which this renderer should write files, - * or NULL if this renderer should not write files at all. + * @param writePath The output directory within which this renderer should write all images, + * or NULL if this renderer should not write all images. + * @param mismatchPath The output directory within which this renderer should write any images + * which do not match expectations, or NULL if this renderer should not write mismatches. * @param inputFilename The name of the input file we are rendering. * @param useChecksumBasedFilenames Whether to use checksum-based filenames when writing * bitmap images to disk. */ - virtual void init(SkPicture* pict, const SkString* outputDir, + virtual void init(SkPicture* pict, const SkString* writePath, const SkString* mismatchPath, const SkString* inputFilename, bool useChecksumBasedFilenames); /** @@ -116,11 +118,13 @@ public: * Typically "the work" is rendering an SkPicture into a bitmap, but in some subclasses * it is recording the source SkPicture into another SkPicture. * - * If fOutputDir has been specified, the result of the work will be written to that dir. + * If fWritePath has been specified, the result of the work will be written to that dir. + * If fMismatchPath has been specified, and the actual image result differs from its + * expectation, the result of the work will be written to that dir. * * @param out If non-null, the implementing subclass MAY allocate an SkBitmap, copy the * output image into it, and return it here. (Some subclasses ignore this parameter) - * @return bool True if rendering succeeded and, if fOutputDir had been specified, the output + * @return bool True if rendering succeeded and, if fWritePath had been specified, the output * was successfully written to a file. */ virtual bool render(SkBitmap** out = NULL) = 0; @@ -370,7 +374,8 @@ protected: BBoxHierarchyType fBBoxHierarchyType; DrawFilterFlags fDrawFilters[SkDrawFilter::kTypeCount]; SkString fDrawFiltersConfig; - SkString fOutputDir; + SkString fWritePath; + SkString fMismatchPath; SkString fInputFilename; SkTileGridFactory::TileGridInfo fGridInfo; // used when fBBoxHierarchyType is TileGrid @@ -447,7 +452,7 @@ private: class SimplePictureRenderer : public PictureRenderer { public: - virtual void init(SkPicture* pict, const SkString* outputDir, + virtual void init(SkPicture* pict, const SkString* writePath, const SkString* mismatchPath, const SkString* inputFilename, bool useChecksumBasedFilenames) SK_OVERRIDE; virtual bool render(SkBitmap** out = NULL) SK_OVERRIDE; @@ -462,12 +467,12 @@ class TiledPictureRenderer : public PictureRenderer { public: TiledPictureRenderer(); - virtual void init(SkPicture* pict, const SkString* outputDir, + virtual void init(SkPicture* pict, const SkString* writePath, const SkString* mismatchPath, const SkString* inputFilename, bool useChecksumBasedFilenames) SK_OVERRIDE; /** * Renders to tiles, rather than a single canvas. - * If fOutputDir was provided, a separate file is + * If fWritePath was provided, a separate file is * created for each tile, named "path0.png", "path1.png", etc. * Multithreaded mode currently does not support writing to a file. */ @@ -587,7 +592,7 @@ public: ~MultiCorePictureRenderer(); - virtual void init(SkPicture* pict, const SkString* outputDir, + virtual void init(SkPicture* pict, const SkString* writePath, const SkString* mismatchPath, const SkString* inputFilename, bool useChecksumBasedFilenames) SK_OVERRIDE; /** diff --git a/tools/bbh_shootout.cpp b/tools/bbh_shootout.cpp index 65d37826d1..f3758cb903 100644 --- a/tools/bbh_shootout.cpp +++ b/tools/bbh_shootout.cpp @@ -67,7 +67,7 @@ static void do_benchmark_work(sk_tools::PictureRenderer* renderer, BenchTimer* timer) { renderer->setBBoxHierarchyType(bBoxType); renderer->setGridSize(FLAGS_tilesize, FLAGS_tilesize); - renderer->init(pic, NULL, NULL, false); + renderer->init(pic, NULL, NULL, NULL, false); SkDebugf("%s %d times...\n", renderer->getConfigName().c_str(), numRepeats); for (int i = 0; i < numRepeats; ++i) { diff --git a/tools/image_expectations.cpp b/tools/image_expectations.cpp index 9b180da5fe..24c9175541 100644 --- a/tools/image_expectations.cpp +++ b/tools/image_expectations.cpp @@ -104,7 +104,7 @@ namespace sk_tools { } void ImageResultsAndExpectations::add(const char *sourceName, const char *fileName, - const ImageDigest &digest, const int *tileNumber) { + const ImageDigest &digest, const int *tileNumber) { // Get expectation, if any. Json::Value expectedImage; if (!fExpectedResults.isNull()) { @@ -146,6 +146,29 @@ namespace sk_tools { } } + bool ImageResultsAndExpectations::matchesExpectation(const char *sourceName, + const ImageDigest &digest, + const int *tileNumber) { + if (fExpectedResults.isNull()) { + return false; + } + + Json::Value expectedImage; + if (NULL == tileNumber) { + expectedImage = fExpectedResults[sourceName][kJsonKey_Source_WholeImage]; + } else { + expectedImage = fExpectedResults[sourceName][kJsonKey_Source_TiledImages][*tileNumber]; + } + if (expectedImage.isNull()) { + return false; + } + + Json::Value actualChecksumAlgorithm = digest.getHashType().c_str(); + Json::Value actualChecksumValue = Json::UInt64(digest.getHashValue()); + return ((actualChecksumAlgorithm == expectedImage[kJsonKey_Image_ChecksumAlgorithm]) && + (actualChecksumValue == expectedImage[kJsonKey_Image_ChecksumValue])); + } + void ImageResultsAndExpectations::writeToFile(const char *filename) const { Json::Value header; header[kJsonKey_Header_Type] = kJsonValue_Header_Type; diff --git a/tools/image_expectations.h b/tools/image_expectations.h index 09a945a1b0..b7b135d912 100644 --- a/tools/image_expectations.h +++ b/tools/image_expectations.h @@ -90,12 +90,23 @@ namespace sk_tools { * @param sourceName name of the source file that generated this result * @param fileName relative path to the image output file on local disk * @param digest description of the image's contents - * @param tileNumber if not NULL, ptr to tile number + * @param tileNumber if not NULL, pointer to tile number */ void add(const char *sourceName, const char *fileName, const ImageDigest &digest, const int *tileNumber=NULL); /** + * Returns true if this test result matches its expectations. + * If there are no expectations for this test result, this will return false. + * + * @param sourceName name of the source file that generated this result + * @param digest description of the image's contents + * @param tileNumber if not NULL, pointer to tile number + */ + bool matchesExpectation(const char *sourceName, const ImageDigest &digest, + const int *tileNumber=NULL); + + /** * Writes the summary (as constructed so far) to a file. * * @param filename path to write the summary to diff --git a/tools/picture_utils.cpp b/tools/picture_utils.cpp index 4bcdf8a964..c698a69835 100644 --- a/tools/picture_utils.cpp +++ b/tools/picture_utils.cpp @@ -6,11 +6,13 @@ */ #include "picture_utils.h" -#include "SkColorPriv.h" #include "SkBitmap.h" +#include "SkColorPriv.h" +#include "SkImageEncoder.h" +#include "SkOSFile.h" #include "SkPicture.h" -#include "SkString.h" #include "SkStream.h" +#include "SkString.h" static bool is_path_seperator(const char chr) { #if defined(SK_BUILD_FOR_WIN) @@ -99,4 +101,23 @@ namespace sk_tools { bitmap->allocPixels(); bitmap->eraseColor(SK_ColorTRANSPARENT); } -} + + bool write_bitmap_to_disk(const SkBitmap& bm, const SkString& dirPath, + const char *subdirOrNull, const SkString& baseName) { + SkString partialPath; + if (subdirOrNull) { + partialPath = SkOSPath::SkPathJoin(dirPath.c_str(), subdirOrNull); + sk_mkdir(partialPath.c_str()); + } else { + partialPath.set(dirPath); + } + SkString fullPath = SkOSPath::SkPathJoin(partialPath.c_str(), baseName.c_str()); + if (SkImageEncoder::EncodeFile(fullPath.c_str(), bm, SkImageEncoder::kPNG_Type, 100)) { + return true; + } else { + SkDebugf("Failed to write the bitmap to %s.\n", fullPath.c_str()); + return false; + } + } + +} // namespace sk_tools diff --git a/tools/picture_utils.h b/tools/picture_utils.h index 02eee285d0..c0e0d2c8dd 100644 --- a/tools/picture_utils.h +++ b/tools/picture_utils.h @@ -53,6 +53,20 @@ namespace sk_tools { // Specifically, it configures the bitmap, allocates pixels and then // erases the pixels to transparent black. void setup_bitmap(SkBitmap* bitmap, int width, int height); -} + + /** + * Write a bitmap file to disk. + * + * @param bm the bitmap to record + * @param dirPath directory within which to write the image file + * @param subdirOrNull subdirectory within dirPath, or NULL to just write into dirPath + * @param baseName last part of the filename + * + * @return true if written out successfully + */ + bool write_bitmap_to_disk(const SkBitmap& bm, const SkString& dirPath, + const char *subdirOrNull, const SkString& baseName); + +} // namespace sk_tools #endif // picture_utils_DEFINED diff --git a/tools/render_pictures_main.cpp b/tools/render_pictures_main.cpp index 9f28bff4b9..cbbca7ab22 100644 --- a/tools/render_pictures_main.cpp +++ b/tools/render_pictures_main.cpp @@ -31,6 +31,8 @@ DECLARE_bool(deferImageDecoding); DEFINE_int32(maxComponentDiff, 256, "Maximum diff on a component, 0 - 256. Components that differ " "by more than this amount are considered errors, though all diffs are reported. " "Requires --validate."); +DEFINE_string(mismatchPath, "", "Write images for tests that failed due to " + "pixel mismatches into this directory."); DEFINE_string(readJsonSummaryPath, "", "JSON file to read image expectations from."); DECLARE_string(readPath); DEFINE_bool(writeChecksumBasedFilenames, false, @@ -137,14 +139,19 @@ static bool write_image_to_file(const void* buffer, size_t size, SkBitmap* bitma /** * Called only by render_picture(). */ -static bool render_picture_internal(const SkString& inputPath, const SkString* outputDir, +static bool render_picture_internal(const SkString& inputPath, const SkString* writePath, + const SkString* mismatchPath, sk_tools::PictureRenderer& renderer, SkBitmap** out) { SkString inputFilename; sk_tools::get_basename(&inputFilename, inputPath); - SkString outputDirString; - if (NULL != outputDir && outputDir->size() > 0 && !FLAGS_writeEncodedImages) { - outputDirString.set(*outputDir); + SkString writePathString; + if (NULL != writePath && writePath->size() > 0 && !FLAGS_writeEncodedImages) { + writePathString.set(*writePath); + } + SkString mismatchPathString; + if (NULL != mismatchPath && mismatchPath->size() > 0) { + mismatchPathString.set(*mismatchPath); } SkFILEStream inputStream; @@ -189,7 +196,8 @@ static bool render_picture_internal(const SkString& inputPath, const SkString* o SkDebugf("drawing... [%i %i] %s\n", picture->width(), picture->height(), inputPath.c_str()); - renderer.init(picture, &outputDirString, &inputFilename, FLAGS_writeChecksumBasedFilenames); + renderer.init(picture, &writePathString, &mismatchPathString, &inputFilename, + FLAGS_writeChecksumBasedFilenames); if (FLAGS_preprocess) { if (NULL != renderer.getCanvas()) { @@ -246,21 +254,23 @@ private: }; /** - * Render the SKP file(s) within inputPath, writing their bitmap images into outputDir. + * Render the SKP file(s) within inputPath. * * @param inputPath path to an individual SKP file, or a directory of SKP files - * @param outputDir if not NULL, write the image(s) generated into this directory + * @param writePath if not NULL, write all image(s) generated into this directory + * @param mismatchPath if not NULL, write any image(s) not matching expectations into this directory * @param renderer PictureRenderer to use to render the SKPs * @param jsonSummaryPtr if not NULL, add the image(s) generated to this summary */ -static bool render_picture(const SkString& inputPath, const SkString* outputDir, - sk_tools::PictureRenderer& renderer, +static bool render_picture(const SkString& inputPath, const SkString* writePath, + const SkString* mismatchPath, sk_tools::PictureRenderer& renderer, sk_tools::ImageResultsAndExpectations *jsonSummaryPtr) { int diffs[256] = {0}; SkBitmap* bitmap = NULL; renderer.setJsonSummaryPtr(jsonSummaryPtr); bool success = render_picture_internal(inputPath, - FLAGS_writeWholeImage ? NULL : outputDir, + FLAGS_writeWholeImage ? NULL : writePath, + FLAGS_writeWholeImage ? NULL : mismatchPath, renderer, FLAGS_validate || FLAGS_writeWholeImage ? &bitmap : NULL); @@ -286,7 +296,7 @@ static bool render_picture(const SkString& inputPath, const SkString* outputDir, } SkAutoTUnref<sk_tools::PictureRenderer> aurReferenceRenderer(referenceRenderer); - success = render_picture_internal(inputPath, NULL, *referenceRenderer, + success = render_picture_internal(inputPath, NULL, NULL, *referenceRenderer, &referenceBitmap); if (!success || NULL == referenceBitmap || NULL == referenceBitmap->getPixels()) { @@ -342,25 +352,24 @@ static bool render_picture(const SkString& inputPath, const SkString* outputDir, if (FLAGS_writeWholeImage) { sk_tools::force_all_opaque(*bitmap); - SkString inputFilename, outputPath; + SkString inputFilename; sk_tools::get_basename(&inputFilename, inputPath); - sk_tools::make_filepath(&outputPath, *outputDir, inputFilename); - sk_tools::replace_char(&outputPath, '.', '_'); - outputPath.append(".png"); + SkString outputFilename(inputFilename); + sk_tools::replace_char(&outputFilename, '.', '_'); + outputFilename.append(".png"); if (NULL != jsonSummaryPtr) { sk_tools::ImageDigest imageDigest(*bitmap); - SkString outputFileBasename; - sk_tools::get_basename(&outputFileBasename, outputPath); - jsonSummaryPtr->add(inputFilename.c_str(), outputFileBasename.c_str(), imageDigest); + jsonSummaryPtr->add(inputFilename.c_str(), outputFilename.c_str(), imageDigest); + if ((NULL != mismatchPath) && !mismatchPath->isEmpty() && + !jsonSummaryPtr->matchesExpectation(inputFilename.c_str(), imageDigest)) { + success &= sk_tools::write_bitmap_to_disk(*bitmap, *mismatchPath, NULL, + outputFilename); + } } - if (NULL != outputDir) { - if (!SkImageEncoder::EncodeFile(outputPath.c_str(), *bitmap, - SkImageEncoder::kPNG_Type, 100)) { - SkDebugf("Failed to draw the picture.\n"); - success = false; - } + if ((NULL != writePath) && !writePath->isEmpty()) { + success &= sk_tools::write_bitmap_to_disk(*bitmap, *writePath, NULL, outputFilename); } } SkDELETE(bitmap); @@ -369,8 +378,8 @@ static bool render_picture(const SkString& inputPath, const SkString* outputDir, } -static int process_input(const char* input, const SkString* outputDir, - sk_tools::PictureRenderer& renderer, +static int process_input(const char* input, const SkString* writePath, + const SkString* mismatchPath, sk_tools::PictureRenderer& renderer, sk_tools::ImageResultsAndExpectations *jsonSummaryPtr) { SkOSFile::Iter iter(input, "skp"); SkString inputFilename; @@ -381,13 +390,13 @@ static int process_input(const char* input, const SkString* outputDir, SkString inputPath; SkString inputAsSkString(input); sk_tools::make_filepath(&inputPath, inputAsSkString, inputFilename); - if (!render_picture(inputPath, outputDir, renderer, jsonSummaryPtr)) { + if (!render_picture(inputPath, writePath, mismatchPath, renderer, jsonSummaryPtr)) { ++failures; } } while(iter.next(&inputFilename)); } else if (SkStrEndsWith(input, ".skp")) { SkString inputPath(input); - if (!render_picture(inputPath, outputDir, renderer, jsonSummaryPtr)) { + if (!render_picture(inputPath, writePath, mismatchPath, renderer, jsonSummaryPtr)) { ++failures; } } else { @@ -447,9 +456,13 @@ int tool_main(int argc, char** argv) { SkAutoGraphics ag; - SkString outputDir; + SkString writePath; if (FLAGS_writePath.count() == 1) { - outputDir.set(FLAGS_writePath[0]); + writePath.set(FLAGS_writePath[0]); + } + SkString mismatchPath; + if (FLAGS_mismatchPath.count() == 1) { + mismatchPath.set(FLAGS_mismatchPath[0]); } sk_tools::ImageResultsAndExpectations jsonSummary; sk_tools::ImageResultsAndExpectations* jsonSummaryPtr = NULL; @@ -462,7 +475,8 @@ int tool_main(int argc, char** argv) { int failures = 0; for (int i = 0; i < FLAGS_readPath.count(); i ++) { - failures += process_input(FLAGS_readPath[i], &outputDir, *renderer.get(), jsonSummaryPtr); + failures += process_input(FLAGS_readPath[i], &writePath, &mismatchPath, *renderer.get(), + jsonSummaryPtr); } if (failures != 0) { SkDebugf("Failed to render %i pictures.\n", failures); diff --git a/tools/tests/base_unittest.py b/tools/tests/base_unittest.py index 2adaed0b70..f7ee570a24 100755 --- a/tools/tests/base_unittest.py +++ b/tools/tests/base_unittest.py @@ -10,7 +10,9 @@ A wrapper around the standard Python unittest library, adding features we need for various unittests within this directory. """ +import errno import os +import shutil import sys import unittest @@ -26,6 +28,20 @@ class TestCase(unittest.TestCase): """Tell unittest framework to not print docstrings for test cases.""" return None + def create_empty_dir(self, path): + """Creates an empty directory at path and returns path. + + Args: + path: path on local disk + """ + shutil.rmtree(path=path, ignore_errors=True) + try: + os.makedirs(path) + except OSError as exc: + if exc.errno != errno.EEXIST: + raise + return path + def run_command(self, args): """Runs a program from the command line and returns stdout. diff --git a/tools/tests/render_pictures_test.py b/tools/tests/render_pictures_test.py index 4b11e56ae9..5ab9d673bb 100755 --- a/tools/tests/render_pictures_test.py +++ b/tools/tests/render_pictures_test.py @@ -156,12 +156,13 @@ class RenderPicturesTest(base_unittest.TestCase): self.maxDiff = MAX_DIFF_LENGTH self._expectations_dir = tempfile.mkdtemp() self._input_skp_dir = tempfile.mkdtemp() - self._temp_dir = tempfile.mkdtemp() + # All output of render_pictures binary will go into this directory. + self._output_dir = tempfile.mkdtemp() def tearDown(self): shutil.rmtree(self._expectations_dir) shutil.rmtree(self._input_skp_dir) - shutil.rmtree(self._temp_dir) + shutil.rmtree(self._output_dir) def test_tiled_whole_image(self): """Run render_pictures with tiles and --writeWholeImage flag. @@ -169,6 +170,7 @@ class RenderPicturesTest(base_unittest.TestCase): TODO(epoger): This test generates undesired results! The JSON summary includes both whole-image and tiled-images (as it should), but only whole-images are written out to disk. See http://skbug.com/2463 + Once I fix that, I should add a similar test that exercises mismatchPath. TODO(epoger): I noticed that when this is run without --writePath being specified, this test writes red_skp.png and green_skp.png to the current @@ -176,7 +178,9 @@ class RenderPicturesTest(base_unittest.TestCase): probably shouldn't write out red_skp.png and green_skp.png at all! See http://skbug.com/2464 """ - output_json_path = os.path.join(self._temp_dir, 'actuals.json') + output_json_path = os.path.join(self._output_dir, 'actuals.json') + write_path_dir = self.create_empty_dir( + path=os.path.join(self._output_dir, 'writePath')) self._generate_skps() expectations_path = self._create_expectations() self._run_render_pictures([ @@ -185,7 +189,7 @@ class RenderPicturesTest(base_unittest.TestCase): '--mode', 'tile', '256', '256', '--readJsonSummaryPath', expectations_path, '--writeJsonSummaryPath', output_json_path, - '--writePath', self._temp_dir, + '--writePath', write_path_dir, '--writeWholeImage']) expected_summary_dict = { "header" : EXPECTED_HEADER_CONTENTS, @@ -202,12 +206,14 @@ class RenderPicturesTest(base_unittest.TestCase): } self._assert_json_contents(output_json_path, expected_summary_dict) self._assert_directory_contents( - self._temp_dir, ['red_skp.png', 'green_skp.png', 'actuals.json']) + write_path_dir, ['red_skp.png', 'green_skp.png']) def test_missing_tile_and_whole_image(self): """test_tiled_whole_image, but missing expectations for some images. """ - output_json_path = os.path.join(self._temp_dir, 'actuals.json') + output_json_path = os.path.join(self._output_dir, 'actuals.json') + write_path_dir = self.create_empty_dir( + path=os.path.join(self._output_dir, 'writePath')) self._generate_skps() expectations_path = self._create_expectations(missing_some_images=True) self._run_render_pictures([ @@ -216,7 +222,7 @@ class RenderPicturesTest(base_unittest.TestCase): '--mode', 'tile', '256', '256', '--readJsonSummaryPath', expectations_path, '--writeJsonSummaryPath', output_json_path, - '--writePath', self._temp_dir, + '--writePath', write_path_dir, '--writeWholeImage']) modified_red_tiles = copy.deepcopy(RED_TILES) modified_red_tiles[5]['comparisonResult'] = 'no-comparison' @@ -238,13 +244,15 @@ class RenderPicturesTest(base_unittest.TestCase): def test_untiled(self): """Run without tiles.""" - output_json_path = os.path.join(self._temp_dir, 'actuals.json') + output_json_path = os.path.join(self._output_dir, 'actuals.json') + write_path_dir = self.create_empty_dir( + path=os.path.join(self._output_dir, 'writePath')) self._generate_skps() expectations_path = self._create_expectations() self._run_render_pictures([ '-r', self._input_skp_dir, '--readJsonSummaryPath', expectations_path, - '--writePath', self._temp_dir, + '--writePath', write_path_dir, '--writeJsonSummaryPath', output_json_path]) expected_summary_dict = { "header" : EXPECTED_HEADER_CONTENTS, @@ -259,15 +267,17 @@ class RenderPicturesTest(base_unittest.TestCase): } self._assert_json_contents(output_json_path, expected_summary_dict) self._assert_directory_contents( - self._temp_dir, ['red_skp.png', 'green_skp.png', 'actuals.json']) + write_path_dir, ['red_skp.png', 'green_skp.png']) def test_untiled_writeChecksumBasedFilenames(self): """Same as test_untiled, but with --writeChecksumBasedFilenames.""" - output_json_path = os.path.join(self._temp_dir, 'actuals.json') + output_json_path = os.path.join(self._output_dir, 'actuals.json') + write_path_dir = self.create_empty_dir( + path=os.path.join(self._output_dir, 'writePath')) self._generate_skps() self._run_render_pictures(['-r', self._input_skp_dir, '--writeChecksumBasedFilenames', - '--writePath', self._temp_dir, + '--writePath', write_path_dir, '--writeJsonSummaryPath', output_json_path]) expected_summary_dict = { "header" : EXPECTED_HEADER_CONTENTS, @@ -293,25 +303,26 @@ class RenderPicturesTest(base_unittest.TestCase): } } self._assert_json_contents(output_json_path, expected_summary_dict) - self._assert_directory_contents(self._temp_dir, [ - 'red_skp', 'green_skp', 'actuals.json']) + self._assert_directory_contents(write_path_dir, ['red_skp', 'green_skp']) self._assert_directory_contents( - os.path.join(self._temp_dir, 'red_skp'), + os.path.join(write_path_dir, 'red_skp'), ['bitmap-64bitMD5_11092453015575919668.png']) self._assert_directory_contents( - os.path.join(self._temp_dir, 'green_skp'), + os.path.join(write_path_dir, 'green_skp'), ['bitmap-64bitMD5_8891695120562235492.png']) def test_untiled_validate(self): """Same as test_untiled, but with --validate.""" - output_json_path = os.path.join(self._temp_dir, 'actuals.json') + output_json_path = os.path.join(self._output_dir, 'actuals.json') + write_path_dir = self.create_empty_dir( + path=os.path.join(self._output_dir, 'writePath')) self._generate_skps() expectations_path = self._create_expectations() self._run_render_pictures([ '-r', self._input_skp_dir, '--readJsonSummaryPath', expectations_path, '--validate', - '--writePath', self._temp_dir, + '--writePath', write_path_dir, '--writeJsonSummaryPath', output_json_path]) expected_summary_dict = { "header" : EXPECTED_HEADER_CONTENTS, @@ -326,11 +337,11 @@ class RenderPicturesTest(base_unittest.TestCase): } self._assert_json_contents(output_json_path, expected_summary_dict) self._assert_directory_contents( - self._temp_dir, ['red_skp.png', 'green_skp.png', 'actuals.json']) + write_path_dir, ['red_skp.png', 'green_skp.png']) def test_untiled_without_writePath(self): """Same as test_untiled, but without --writePath.""" - output_json_path = os.path.join(self._temp_dir, 'actuals.json') + output_json_path = os.path.join(self._output_dir, 'actuals.json') self._generate_skps() expectations_path = self._create_expectations() self._run_render_pictures([ @@ -352,7 +363,9 @@ class RenderPicturesTest(base_unittest.TestCase): def test_tiled(self): """Generate individual tiles.""" - output_json_path = os.path.join(self._temp_dir, 'actuals.json') + output_json_path = os.path.join(self._output_dir, 'actuals.json') + write_path_dir = self.create_empty_dir( + path=os.path.join(self._output_dir, 'writePath')) self._generate_skps() expectations_path = self._create_expectations() self._run_render_pictures([ @@ -360,7 +373,7 @@ class RenderPicturesTest(base_unittest.TestCase): '--bbh', 'grid', '256', '256', '--mode', 'tile', '256', '256', '--readJsonSummaryPath', expectations_path, - '--writePath', self._temp_dir, + '--writePath', write_path_dir, '--writeJsonSummaryPath', output_json_path]) expected_summary_dict = { "header" : EXPECTED_HEADER_CONTENTS, @@ -375,22 +388,56 @@ class RenderPicturesTest(base_unittest.TestCase): } self._assert_json_contents(output_json_path, expected_summary_dict) self._assert_directory_contents( - self._temp_dir, + write_path_dir, ['red_skp-tile0.png', 'red_skp-tile1.png', 'red_skp-tile2.png', 'red_skp-tile3.png', 'red_skp-tile4.png', 'red_skp-tile5.png', 'green_skp-tile0.png', 'green_skp-tile1.png', 'green_skp-tile2.png', 'green_skp-tile3.png', 'green_skp-tile4.png', 'green_skp-tile5.png', - 'actuals.json']) + ]) + + def test_tiled_mismatches(self): + """Same as test_tiled, but only write out mismatching images.""" + output_json_path = os.path.join(self._output_dir, 'actuals.json') + mismatch_path_dir = self.create_empty_dir( + path=os.path.join(self._output_dir, 'mismatchPath')) + self._generate_skps() + expectations_path = self._create_expectations() + self._run_render_pictures([ + '-r', self._input_skp_dir, + '--bbh', 'grid', '256', '256', + '--mode', 'tile', '256', '256', + '--readJsonSummaryPath', expectations_path, + '--mismatchPath', mismatch_path_dir, + '--writeJsonSummaryPath', output_json_path]) + expected_summary_dict = { + "header" : EXPECTED_HEADER_CONTENTS, + "actual-results" : { + "red.skp": { + "tiled-images": RED_TILES, + }, + "green.skp": { + "tiled-images": GREEN_TILES, + } + } + } + self._assert_json_contents(output_json_path, expected_summary_dict) + self._assert_directory_contents( + mismatch_path_dir, + ['red_skp-tile0.png', 'red_skp-tile1.png', 'red_skp-tile2.png', + 'red_skp-tile3.png', 'red_skp-tile4.png', 'red_skp-tile5.png', + ]) def test_tiled_writeChecksumBasedFilenames(self): """Same as test_tiled, but with --writeChecksumBasedFilenames.""" - output_json_path = os.path.join(self._temp_dir, 'actuals.json') + output_json_path = os.path.join(self._output_dir, 'actuals.json') + write_path_dir = self.create_empty_dir( + path=os.path.join(self._output_dir, 'writePath')) self._generate_skps() self._run_render_pictures(['-r', self._input_skp_dir, '--bbh', 'grid', '256', '256', '--mode', 'tile', '256', '256', '--writeChecksumBasedFilenames', - '--writePath', self._temp_dir, + '--writePath', write_path_dir, '--writeJsonSummaryPath', output_json_path]) expected_summary_dict = { "header" : EXPECTED_HEADER_CONTENTS, @@ -470,10 +517,9 @@ class RenderPicturesTest(base_unittest.TestCase): } } self._assert_json_contents(output_json_path, expected_summary_dict) - self._assert_directory_contents(self._temp_dir, [ - 'red_skp', 'green_skp', 'actuals.json']) + self._assert_directory_contents(write_path_dir, ['red_skp', 'green_skp']) self._assert_directory_contents( - os.path.join(self._temp_dir, 'red_skp'), + os.path.join(write_path_dir, 'red_skp'), ['bitmap-64bitMD5_5815827069051002745.png', 'bitmap-64bitMD5_9323613075234140270.png', 'bitmap-64bitMD5_16670399404877552232.png', @@ -481,7 +527,7 @@ class RenderPicturesTest(base_unittest.TestCase): 'bitmap-64bitMD5_7325267995523877959.png', 'bitmap-64bitMD5_2181381724594493116.png']) self._assert_directory_contents( - os.path.join(self._temp_dir, 'green_skp'), + os.path.join(write_path_dir, 'green_skp'), ['bitmap-64bitMD5_12587324416545178013.png', 'bitmap-64bitMD5_7624374914829746293.png', 'bitmap-64bitMD5_5686489729535631913.png', |