aboutsummaryrefslogtreecommitdiffhomepage
path: root/tools
diff options
context:
space:
mode:
authorGravatar commit-bot@chromium.org <commit-bot@chromium.org@2bbb7eff-a529-9590-31e7-b0007b416f81>2014-05-15 15:10:48 +0000
committerGravatar commit-bot@chromium.org <commit-bot@chromium.org@2bbb7eff-a529-9590-31e7-b0007b416f81>2014-05-15 15:10:48 +0000
commit3f0451772109959fcb79bacf2c9a03e0eb39ff27 (patch)
tree19e1b86cc9824172c7f1c422591cc51314203fdb /tools
parent76ac7f81d39a99616541ead9c40aa6d208b517af (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.cpp12
-rw-r--r--tools/CopyTilesRenderer.h3
-rw-r--r--tools/PictureBenchmark.cpp2
-rw-r--r--tools/PictureRenderer.cpp87
-rw-r--r--tools/PictureRenderer.h25
-rw-r--r--tools/bbh_shootout.cpp2
-rw-r--r--tools/image_expectations.cpp25
-rw-r--r--tools/image_expectations.h13
-rw-r--r--tools/picture_utils.cpp27
-rw-r--r--tools/picture_utils.h16
-rw-r--r--tools/render_pictures_main.cpp76
-rwxr-xr-xtools/tests/base_unittest.py16
-rwxr-xr-xtools/tests/render_pictures_test.py106
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',