aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar scroggo@google.com <scroggo@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>2013-05-08 19:14:23 +0000
committerGravatar scroggo@google.com <scroggo@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>2013-05-08 19:14:23 +0000
commit6843bdb7061364c100990e7e0feb3757fca08bb0 (patch)
treebd6a8529059cdfe4d8e7b26c5dfdd0cd1bcf337c
parent94b366a3e8ed7f03b4417f45999572399e6e591c (diff)
Write/compare against expectations in skimage tool.
skimage: Add two new modes: one to write expectations to a json file, and another to compare results against expectations. Use SkPATH_SEPARATOR instead of '/'. gm_expectations: Split into a static library so it can be used by skimage. Make functions non static and move function definitions into source file. Capitalize static member functions to follow the coding style guidelines. BUG=https://code.google.com/p/skia/issues/detail?id=1241 R=epoger@google.com Review URL: https://codereview.chromium.org/14670021 git-svn-id: http://skia.googlecode.com/svn/trunk@9069 2bbb7eff-a529-9590-31e7-b0007b416f81
-rw-r--r--gm/gm_expectations.cpp29
-rw-r--r--gm/gm_expectations.h36
-rw-r--r--gyp/gm.gyp26
-rw-r--r--gyp/tools.gyp9
-rw-r--r--tools/skimage_main.cpp139
5 files changed, 199 insertions, 40 deletions
diff --git a/gm/gm_expectations.cpp b/gm/gm_expectations.cpp
index 72fb0e6a8d..d9ed7798bb 100644
--- a/gm/gm_expectations.cpp
+++ b/gm/gm_expectations.cpp
@@ -24,6 +24,27 @@ const static char kJsonKey_ExpectedResults_IgnoreFailure[] = "ignore-f
namespace skiagm {
+ void gm_fprintf(FILE *stream, const char format[], ...) {
+ va_list args;
+ va_start(args, format);
+ fprintf(stream, "GM: ");
+ vfprintf(stream, format, args);
+ va_end(args);
+ }
+
+ SkString make_filename(const char path[],
+ const char renderModeDescriptor[],
+ const char *name,
+ const char suffix[]) {
+ SkString filename(path);
+ if (filename.endsWith(SkPATH_SEPARATOR)) {
+ filename.remove(filename.size() - 1, 1);
+ }
+ filename.appendf("%c%s%s.%s", SkPATH_SEPARATOR,
+ name, renderModeDescriptor, suffix);
+ return filename;
+ }
+
// TODO(epoger): This currently assumes that the result SkHashDigest was
// generated as an SkHashDigest of an SkBitmap. We'll need to allow for other
// hash types to cover non-bitmaps.
@@ -160,7 +181,7 @@ namespace skiagm {
// JsonExpectationsSource class...
JsonExpectationsSource::JsonExpectationsSource(const char *jsonPath) {
- parse(jsonPath, &fJsonRoot);
+ Parse(jsonPath, &fJsonRoot);
fJsonExpectedResults = fJsonRoot[kJsonKey_ExpectedResults];
}
@@ -168,7 +189,7 @@ namespace skiagm {
return Expectations(fJsonExpectedResults[testName]);
}
- /*static*/ SkData* JsonExpectationsSource::readIntoSkData(SkStream &stream, size_t maxBytes) {
+ /*static*/ SkData* JsonExpectationsSource::ReadIntoSkData(SkStream &stream, size_t maxBytes) {
if (0 == maxBytes) {
return SkData::NewEmpty();
}
@@ -186,7 +207,7 @@ namespace skiagm {
return SkData::NewFromMalloc(bufStart, maxBytes - bytesRemaining);
}
- /*static*/ bool JsonExpectationsSource::parse(const char *jsonPath, Json::Value *jsonRoot) {
+ /*static*/ bool JsonExpectationsSource::Parse(const char *jsonPath, Json::Value *jsonRoot) {
SkFILEStream inFile(jsonPath);
if (!inFile.isValid()) {
gm_fprintf(stderr, "unable to read JSON file %s\n", jsonPath);
@@ -194,7 +215,7 @@ namespace skiagm {
return false;
}
- SkAutoDataUnref dataRef(readFileIntoSkData(inFile));
+ SkAutoDataUnref dataRef(ReadFileIntoSkData(inFile));
if (NULL == dataRef.get()) {
gm_fprintf(stderr, "error reading JSON file %s\n", jsonPath);
DEBUGFAIL_SEE_STDERR;
diff --git a/gm/gm_expectations.h b/gm/gm_expectations.h
index 2506a98127..0af18c1570 100644
--- a/gm/gm_expectations.h
+++ b/gm/gm_expectations.h
@@ -39,26 +39,12 @@ namespace skiagm {
return jsonValue.asUInt64();
}
- static void gm_fprintf(FILE *stream, const char format[], ...) {
- va_list args;
- va_start(args, format);
- fprintf(stream, "GM: ");
- vfprintf(stream, format, args);
- va_end(args);
- }
+ void gm_fprintf(FILE *stream, const char format[], ...);
- static SkString make_filename(const char path[],
- const char renderModeDescriptor[],
- const char *name,
- const char suffix[]) {
- SkString filename(path);
- if (filename.endsWith(SkPATH_SEPARATOR)) {
- filename.remove(filename.size() - 1, 1);
- }
- filename.appendf("%c%s%s.%s", SkPATH_SEPARATOR,
- name, renderModeDescriptor, suffix);
- return filename;
- }
+ SkString make_filename(const char path[],
+ const char renderModeDescriptor[],
+ const char *name,
+ const char suffix[]);
Json::Value ActualResultAsJsonValue(const SkHashDigest& result);
@@ -208,15 +194,15 @@ namespace skiagm {
*/
// TODO(epoger): Move this, into SkStream.[cpp|h] as attempted in
// https://codereview.appspot.com/7300071 ?
- // And maybe readFileIntoSkData() also?
- static SkData* readIntoSkData(SkStream &stream, size_t maxBytes);
+ // And maybe ReadFileIntoSkData() also?
+ static SkData* ReadIntoSkData(SkStream &stream, size_t maxBytes);
/**
- * Wrapper around readIntoSkData for files: reads the entire file into
+ * Wrapper around ReadIntoSkData for files: reads the entire file into
* an SkData object.
*/
- static SkData* readFileIntoSkData(SkFILEStream &stream) {
- return readIntoSkData(stream, stream.getLength());
+ static SkData* ReadFileIntoSkData(SkFILEStream &stream) {
+ return ReadIntoSkData(stream, stream.getLength());
}
/**
@@ -224,7 +210,7 @@ namespace skiagm {
*
* Returns true if successful.
*/
- static bool parse(const char *jsonPath, Json::Value *jsonRoot);
+ static bool Parse(const char *jsonPath, Json::Value *jsonRoot);
Json::Value fJsonRoot;
Json::Value fJsonExpectedResults;
diff --git a/gyp/gm.gyp b/gyp/gm.gyp
index f0e466ddf4..fa413c37dc 100644
--- a/gyp/gm.gyp
+++ b/gyp/gm.gyp
@@ -5,6 +5,30 @@
],
'targets': [
{
+ 'target_name': 'gm_expectations',
+ 'type': 'static_library',
+ 'include_dirs' : [
+ '../include/core/',
+ '../src/utils/',
+ ],
+ 'sources': [
+ '../gm/gm_expectations.h',
+ '../gm/gm_expectations.cpp',
+ ],
+ 'dependencies': [
+ 'skia_base_libs.gyp:skia_base_libs',
+ 'core.gyp:core',
+ 'images.gyp:images',
+ 'jsoncpp.gyp:jsoncpp',
+ 'utils.gyp:utils',
+ ],
+ 'direct_dependent_settings': {
+ 'include_dirs': [
+ '../gm/',
+ ],
+ },
+ },
+ {
'target_name': 'gm',
'type': 'executable',
'include_dirs' : [
@@ -26,7 +50,6 @@
'../debugger/SkObjectParser.cpp',
'../gm/gm.cpp',
- '../gm/gm_expectations.cpp',
'../gm/gmmain.cpp',
'../gm/system_preferences_default.cpp',
@@ -37,6 +60,7 @@
'skia_base_libs.gyp:skia_base_libs',
'effects.gyp:effects',
'flags.gyp:flags',
+ 'gm.gyp:gm_expectations',
'images.gyp:images',
'jsoncpp.gyp:jsoncpp',
'pdf.gyp:pdf',
diff --git a/gyp/tools.gyp b/gyp/tools.gyp
index 7f79dc4114..ec521cf971 100644
--- a/gyp/tools.gyp
+++ b/gyp/tools.gyp
@@ -78,11 +78,18 @@
'sources': [
'../tools/skimage_main.cpp',
],
+ 'include_dirs': [
+ # For SkBitmapHasher.h
+ '../src/utils/',
+ ],
'dependencies': [
'skia_base_libs.gyp:skia_base_libs',
'effects.gyp:effects',
- 'images.gyp:images',
'flags.gyp:flags',
+ 'gm.gyp:gm_expectations',
+ 'images.gyp:images',
+ 'jsoncpp.gyp:jsoncpp',
+ 'utils.gyp:utils',
],
},
{
diff --git a/tools/skimage_main.cpp b/tools/skimage_main.cpp
index b545af3edb..2041f7bbcc 100644
--- a/tools/skimage_main.cpp
+++ b/tools/skimage_main.cpp
@@ -5,7 +5,9 @@
* found in the LICENSE file.
*/
+#include "gm_expectations.h"
#include "SkBitmap.h"
+#include "SkBitmapHasher.h"
#include "SkColorPriv.h"
#include "SkCommandLineFlags.h"
#include "SkData.h"
@@ -18,7 +20,9 @@
#include "SkTArray.h"
#include "SkTemplates.h"
+DEFINE_string(createExpectationsPath, "", "Path to write JSON expectations.");
DEFINE_string2(readPath, r, "", "Folder(s) and files to decode images. Required.");
+DEFINE_string(readExpectationsPath, "", "Path to read JSON expectations from.");
DEFINE_string2(writePath, w, "", "Write rendered images into this directory.");
DEFINE_bool(reencode, true, "Reencode the images to test encoding.");
DEFINE_bool(testSubsetDecoding, true, "Test decoding subsets of images.");
@@ -97,6 +101,10 @@ static SkTArray<SkString, false> gSuccessfulDecodes;
static SkTArray<SkString, false> gSuccessfulSubsetDecodes;
static SkTArray<SkString, false> gFailedSubsetDecodes;
+// Expections read from a file specified by readExpectationsPath. The expectations must have been
+// previously written using createExpectationsPath.
+SkAutoTUnref<skiagm::JsonExpectationsSource> gJsonExpectations;
+
static bool write_bitmap(const char outName[], SkBitmap* bm) {
SkBitmap bitmap8888;
if (SkBitmap::kARGB_8888_Config != bm->config()) {
@@ -157,6 +165,79 @@ static SkIRect generate_random_rect(SkRandom* rand, int32_t maxX, int32_t maxY)
return rect;
}
+// Stored expectations to be written to a file if createExpectationsPath is specified.
+static Json::Value gExpectationsToWrite;
+
+/**
+ * If expectations are to be recorded, record the expected checksum of bitmap into global
+ * expectations array.
+ */
+static void write_expectations(const SkBitmap& bitmap, const char* filename) {
+ if (!FLAGS_createExpectationsPath.isEmpty()) {
+ // Creates an Expectations object, and add it to the list to write.
+ skiagm::Expectations expectation(bitmap);
+ Json::Value value = expectation.asJsonValue();
+ gExpectationsToWrite[filename] = value;
+ }
+}
+
+/**
+ * Return the name of the file, ignoring the directory structure.
+ * Does not create a new string.
+ * @param fullPath Full path to the file.
+ * @return string The basename of the file - anything beyond the final slash, or the full name
+ * if there is no slash.
+ * TODO: Might this be useful as a utility function in SkOSFile? Would it be more appropriate to
+ * create a new string?
+ */
+static const char* SkBasename(const char* fullPath) {
+ const char* filename = strrchr(fullPath, SkPATH_SEPARATOR);
+ if (NULL == filename || ++filename == '\0') {
+ filename = fullPath;
+ }
+ return filename;
+}
+
+/**
+ * Compare against an expectation for this filename, if there is one.
+ * @param bitmap SkBitmap to compare to the expected value.
+ * @param filename String used to find the expected value.
+ * @return bool True if the bitmap matched the expectation, or if there was no expectation. False
+ * if there was an expecation that the bitmap did not match, or if an expectation could not be
+ * computed from an expectation.
+ */
+static bool compare_to_expectations_if_necessary(const SkBitmap& bitmap, const char* filename,
+ SkTArray<SkString, false>* failureArray) {
+ if (NULL == gJsonExpectations.get()) {
+ return true;
+ }
+
+ skiagm::Expectations jsExpectation = gJsonExpectations->get(filename);
+ if (jsExpectation.empty()) {
+ return true;
+ }
+
+ SkHashDigest checksum;
+ if (!SkBitmapHasher::ComputeDigest(bitmap, &checksum)) {
+ if (failureArray != NULL) {
+ failureArray->push_back().printf("decoded %s, but could not create a checksum.",
+ filename);
+ }
+ return false;
+ }
+
+ if (jsExpectation.match(checksum)) {
+ return true;
+ }
+
+ if (failureArray != NULL) {
+ failureArray->push_back().printf("decoded %s, but the result does not match "
+ "expectations.",
+ filename);
+ }
+ return false;
+}
+
static void decodeFileAndWrite(const char srcPath[], const SkString* writePath) {
SkBitmap bitmap;
SkFILEStream stream(srcPath);
@@ -180,7 +261,15 @@ static void decodeFileAndWrite(const char srcPath[], const SkString* writePath)
return;
}
- gSuccessfulDecodes.push_back().printf("%s [%d %d]", srcPath, bitmap.width(), bitmap.height());
+ // Create a string representing just the filename itself, for use in json expectations.
+ const char* filename = SkBasename(srcPath);
+
+ if (compare_to_expectations_if_necessary(bitmap, filename, &gDecodeFailures)) {
+ gSuccessfulDecodes.push_back().printf("%s [%d %d]", srcPath, bitmap.width(),
+ bitmap.height());
+ }
+
+ write_expectations(bitmap, filename);
if (FLAGS_testSubsetDecoding) {
SkDEBUGCODE(bool couldRewind =) stream.rewind();
@@ -199,8 +288,16 @@ static void decodeFileAndWrite(const char srcPath[], const SkString* writePath)
SkString subsetDim = SkStringPrintf("[%d,%d,%d,%d]", rect.fLeft, rect.fTop,
rect.fRight, rect.fBottom);
if (codec->decodeSubset(&bitmapFromDecodeSubset, rect, SkBitmap::kNo_Config)) {
- gSuccessfulSubsetDecodes.push_back().printf("Decoded subset %s from %s",
- subsetDim.c_str(), srcPath);
+ SkString subsetName = SkStringPrintf("%s_%s", filename, subsetDim.c_str());
+ if (compare_to_expectations_if_necessary(bitmapFromDecodeSubset,
+ subsetName.c_str(),
+ &gFailedSubsetDecodes)) {
+ gSuccessfulSubsetDecodes.push_back().printf("Decoded subset %s from %s",
+ subsetDim.c_str(), srcPath);
+ }
+
+ write_expectations(bitmapFromDecodeSubset, subsetName.c_str());
+
if (writePath != NULL) {
// Write the region to a file whose name includes the dimensions.
SkString suffix = SkStringPrintf("_%s.png", subsetDim.c_str());
@@ -322,6 +419,17 @@ static bool print_strings(const char* title, const SkTArray<SkString, false>& st
return false;
}
+/**
+ * If directory is non null and does not end with a path separator, append one.
+ * @param directory SkString representing the path to a directory. If the last character is not a
+ * path separator (specific to the current OS), append one.
+ */
+static void append_path_separator_if_necessary(SkString* directory) {
+ if (directory != NULL && directory->c_str()[directory->size() - 1] != SkPATH_SEPARATOR) {
+ directory->appendf("%c", SkPATH_SEPARATOR);
+ }
+}
+
int tool_main(int argc, char** argv);
int tool_main(int argc, char** argv) {
SkCommandLineFlags::SetUsage("Decode files, and optionally write the results to files.");
@@ -335,14 +443,17 @@ int tool_main(int argc, char** argv) {
SkAutoGraphics ag;
+ if (!FLAGS_readExpectationsPath.isEmpty()) {
+ gJsonExpectations.reset(SkNEW_ARGS(skiagm::JsonExpectationsSource,
+ (FLAGS_readExpectationsPath[0])));
+ }
+
SkString outDir;
SkString* outDirPtr;
if (FLAGS_writePath.count() == 1) {
outDir.set(FLAGS_writePath[0]);
- if (outDir.c_str()[outDir.size() - 1] != '/') {
- outDir.append("/");
- }
+ append_path_separator_if_necessary(&outDir);
outDirPtr = &outDir;
} else {
outDirPtr = NULL;
@@ -356,9 +467,7 @@ int tool_main(int argc, char** argv) {
SkString filename;
if (iter.next(&filename)) {
SkString directory(FLAGS_readPath[i]);
- if (directory[directory.size() - 1] != '/') {
- directory.append("/");
- }
+ append_path_separator_if_necessary(&directory);
do {
SkString fullname(directory);
fullname.append(filename);
@@ -369,6 +478,18 @@ int tool_main(int argc, char** argv) {
}
}
+ if (!FLAGS_createExpectationsPath.isEmpty()) {
+ // Use an empty value for everything besides expectations, since the reader only cares
+ // about the expectations.
+ Json::Value nullValue;
+ Json::Value root = skiagm::CreateJsonTree(gExpectationsToWrite, nullValue, nullValue,
+ nullValue, nullValue);
+ std::string jsonStdString = root.toStyledString();
+ SkString path = SkStringPrintf("%s%cresults.json", FLAGS_createExpectationsPath[0],
+ SkPATH_SEPARATOR);
+ SkFILEWStream stream(path.c_str());
+ stream.write(jsonStdString.c_str(), jsonStdString.length());
+ }
// Add some space, since codecs may print warnings without newline.
SkDebugf("\n\n");