Index: dm/DMWriteTask.cpp |
diff --git a/dm/DMWriteTask.cpp b/dm/DMWriteTask.cpp |
index 5a07e4669d472dd05eb3d113f156fc9946a41982..2a129a57b16351380ac4e77644c44c0ae011119e 100644 |
--- a/dm/DMWriteTask.cpp |
+++ b/dm/DMWriteTask.cpp |
@@ -4,13 +4,12 @@ |
#include "SkColorPriv.h" |
#include "SkCommonFlags.h" |
#include "SkImageEncoder.h" |
+#include "SkMD5.h" |
#include "SkMallocPixelRef.h" |
+#include "SkOSFile.h" |
#include "SkStream.h" |
#include "SkString.h" |
-DEFINE_bool(writePngOnly, false, "If true, don't encode raw bitmap after .png data. " |
- "This means -r won't work, but skdiff will still work fine."); |
- |
namespace DM { |
// Splits off the last N suffixes of name (splitting on _) and appends them to out. |
@@ -27,23 +26,33 @@ static int split_suffixes(int N, const char* name, SkTArray<SkString>* out) { |
return consumed; |
} |
-inline static SkString find_gm_name(const Task& parent, SkTArray<SkString>* suffixList) { |
+inline static SkString find_base_name(const Task& parent, SkTArray<SkString>* suffixList) { |
const int suffixes = parent.depth() + 1; |
const SkString& name = parent.name(); |
const int totalSuffixLength = split_suffixes(suffixes, name.c_str(), suffixList); |
return SkString(name.c_str(), name.size() - totalSuffixLength); |
} |
+struct JsonData { |
+ SkString name; |
+ SkMD5::Digest md5; |
+}; |
+SkTArray<JsonData> gJsonData; |
+SK_DECLARE_STATIC_MUTEX(gJsonDataLock); |
+ |
WriteTask::WriteTask(const Task& parent, SkBitmap bitmap) |
: CpuTask(parent) |
- , fGmName(find_gm_name(parent, &fSuffixes)) |
+ , fFullName(parent.name()) |
+ , fBaseName(find_base_name(parent, &fSuffixes)) |
, fBitmap(bitmap) |
, fData(NULL) |
- , fExtension(".png") {} |
+ , fExtension(".png") { |
+} |
WriteTask::WriteTask(const Task& parent, SkStreamAsset *data, const char* ext) |
: CpuTask(parent) |
- , fGmName(find_gm_name(parent, &fSuffixes)) |
+ , fFullName(parent.name()) |
+ , fBaseName(find_base_name(parent, &fSuffixes)) |
, fData(data) |
, fExtension(ext) { |
SkASSERT(fData.get()); |
@@ -56,80 +65,27 @@ void WriteTask::makeDirOrFail(SkString dir) { |
} |
} |
-namespace { |
- |
-// One file that first contains a .png of an SkBitmap, then its raw pixels. |
-// We use this custom format to avoid premultiplied/unpremultiplied pixel conversions. |
-struct PngAndRaw { |
- static bool Encode(SkBitmap bitmap, const char* path) { |
- SkFILEWStream stream(path); |
- if (!stream.isValid()) { |
- SkDebugf("Can't write %s.\n", path); |
- return false; |
- } |
- |
- // Write a PNG first for humans and other tools to look at. |
- if (!SkImageEncoder::EncodeStream(&stream, bitmap, SkImageEncoder::kPNG_Type, 100)) { |
- SkDebugf("Can't encode a PNG.\n"); |
- return false; |
- } |
- if (FLAGS_writePngOnly) { |
- return true; |
- } |
- |
- // Pad out so the raw pixels start 4-byte aligned. |
- const uint32_t maxPadding = 0; |
- const size_t pos = stream.bytesWritten(); |
- stream.write(&maxPadding, SkAlign4(pos) - pos); |
- |
- // Then write our secret raw pixels that only DM reads. |
- SkAutoLockPixels lock(bitmap); |
- return stream.write(bitmap.getPixels(), bitmap.getSize()); |
- } |
- |
- // This assumes bitmap already has allocated pixels of the correct size. |
- static bool Decode(const char* path, SkImageInfo info, SkBitmap* bitmap) { |
- SkAutoTUnref<SkData> data(SkData::NewFromFileName(path)); |
- if (!data) { |
- SkDebugf("Can't read %s.\n", path); |
- return false; |
- } |
- |
- // The raw pixels are at the end of the file. We'll skip the encoded PNG at the front. |
- const size_t rowBytes = info.minRowBytes(); // Assume densely packed. |
- const size_t bitmapBytes = info.getSafeSize(rowBytes); |
- if (data->size() < bitmapBytes) { |
- SkDebugf("%s is too small to contain the bitmap we're looking for.\n", path); |
- return false; |
- } |
- |
- const size_t offset = data->size() - bitmapBytes; |
- SkAutoTUnref<SkData> subset( |
- SkData::NewSubset(data, offset, bitmapBytes)); |
- SkAutoTUnref<SkPixelRef> pixels( |
- SkMallocPixelRef::NewWithData( |
- info, rowBytes, NULL/*ctable*/, subset)); |
- SkASSERT(pixels); |
- |
- bitmap->setInfo(info, rowBytes); |
- bitmap->setPixelRef(pixels); |
- return true; |
+static bool save_bitmap_to_file(SkBitmap bitmap, const char* path) { |
+ SkFILEWStream stream(path); |
+ if (!stream.isValid() || |
+ !SkImageEncoder::EncodeStream(&stream, bitmap, SkImageEncoder::kPNG_Type, 100)) { |
+ SkDebugf("Can't write a PNG to %s.\n", path); |
+ return false; |
} |
-}; |
+ return true; |
+} |
// Does not take ownership of data. |
-bool save_data_to_file(SkStreamAsset* data, const char* path) { |
+static bool save_data_to_file(SkStreamAsset* data, const char* path) { |
data->rewind(); |
SkFILEWStream stream(path); |
if (!stream.isValid() || !stream.writeStream(data, data->getLength())) { |
- SkDebugf("Can't write %s.\n", path); |
+ SkDebugf("Can't write data to %s.\n", path); |
return false; |
} |
return true; |
} |
-} // namespace |
- |
void WriteTask::draw() { |
SkString dir(FLAGS_writePath[0]); |
#if SK_BUILD_FOR_IOS |
@@ -143,11 +99,29 @@ void WriteTask::draw() { |
this->makeDirOrFail(dir); |
} |
- SkString path = SkOSPath::Join(dir.c_str(), fGmName.c_str()); |
+ // FIXME: MD5 is really slow. Let's use a different hash. |
+ SkMD5 hasher; |
+ if (fData.get()) { |
+ hasher.write(fData->getMemoryBase(), fData->getLength()); |
+ } else { |
+ SkAutoLockPixels lock(fBitmap); |
+ hasher.write(fBitmap.getPixels(), fBitmap.getSize()); |
+ } |
+ |
+ JsonData entry; |
+ entry.name = fFullName; |
+ hasher.finish(entry.md5); |
+ |
+ { |
+ SkAutoMutexAcquire lock(&gJsonDataLock); |
+ gJsonData.push_back(entry); |
+ } |
+ |
+ SkString path = SkOSPath::Join(dir.c_str(), fBaseName.c_str()); |
path.append(fExtension); |
const bool ok = fData.get() ? save_data_to_file(fData.get(), path.c_str()) |
- : PngAndRaw::Encode(fBitmap, path.c_str()); |
+ : save_bitmap_to_file(fBitmap, path.c_str()); |
if (!ok) { |
this->fail(); |
} |
@@ -158,7 +132,7 @@ SkString WriteTask::name() const { |
for (int i = 0; i < fSuffixes.count(); i++) { |
name.appendf("%s/", fSuffixes[i].c_str()); |
} |
- name.append(fGmName.c_str()); |
+ name.append(fBaseName.c_str()); |
return name; |
} |
@@ -166,38 +140,82 @@ bool WriteTask::shouldSkip() const { |
return FLAGS_writePath.isEmpty(); |
} |
-static SkString path_to_expected_image(const char* root, const Task& task) { |
- SkString filename = task.name(); |
- |
- // We know that all names passed in here belong to top-level Tasks, which have a single suffix |
- // (8888, 565, gpu, etc.) indicating what subdirectory to look in. |
- SkTArray<SkString> suffixes; |
- const int suffixLength = split_suffixes(1, filename.c_str(), &suffixes); |
- SkASSERT(1 == suffixes.count()); |
+WriteTask::Expectations* WriteTask::Expectations::Create(const char* path) { |
+ if (!FLAGS_writePath.isEmpty() && 0 == strcmp(FLAGS_writePath[0], path)) { |
+ SkDebugf("We seem to be reading and writing %s concurrently. This won't work.\n", path); |
+ return NULL; |
+ } |
- // We'll look in root/suffix for images. |
- const SkString dir = SkOSPath::Join(root, suffixes[0].c_str()); |
+ SkString jsonPath; |
+ if (sk_isdir(path)) { |
+ jsonPath = SkOSPath::Join(path, "dm.json"); |
+ } else { |
+ jsonPath.set(path); |
+ } |
- // Remove the suffix and tack on a .png. |
- filename.remove(filename.size() - suffixLength, suffixLength); |
- filename.append(".png"); |
+ SkAutoDataUnref json(SkData::NewFromFileName(jsonPath.c_str())); |
+ if (NULL == json.get()) { |
+ SkDebugf("Can't read %s!\n", jsonPath.c_str()); |
+ return NULL; |
+ } |
- return SkOSPath::Join(dir.c_str(), filename.c_str()); |
+ SkAutoTDelete<Expectations> expectations(SkNEW(Expectations)); |
+ Json::Reader reader; |
+ const char* begin = (const char*)json->bytes(); |
+ const char* end = begin + json->size(); |
+ if (!reader.parse(begin, end, expectations->fJson)) { |
+ SkDebugf("Can't read %s as JSON!\n", jsonPath.c_str()); |
+ return NULL; |
+ } |
+ return expectations.detach(); |
} |
bool WriteTask::Expectations::check(const Task& task, SkBitmap bitmap) const { |
- if (!FLAGS_writePath.isEmpty() && 0 == strcmp(FLAGS_writePath[0], fRoot)) { |
- SkDebugf("We seem to be reading and writing %s concurrently. This won't work.\n", fRoot); |
- return false; |
+ const SkString name = task.name(); |
+ if (fJson[name.c_str()].isNull()) { |
+ return true; // No expectations. |
} |
- const SkString path = path_to_expected_image(fRoot, task); |
- SkBitmap expected; |
- if (!PngAndRaw::Decode(path.c_str(), bitmap.info(), &expected)) { |
- return false; |
+ const char* md5Ascii = fJson[name.c_str()].asCString(); |
+ uint8_t md5[16]; |
+ |
+ for (int j = 0; j < 16; j++) { |
+ sscanf(md5Ascii + (j*2), "%02hhx", md5 + j); |
+ } |
+ |
+ SkMD5 hasher; |
+ { |
+ SkAutoLockPixels lock(bitmap); |
+ hasher.write(bitmap.getPixels(), bitmap.getSize()); |
+ } |
+ SkMD5::Digest digest; |
+ hasher.finish(digest); |
+ |
+ return 0 == memcmp(md5, digest.data, 16); |
+} |
+ |
+void WriteTask::DumpJson() { |
+ if (FLAGS_writePath.isEmpty()) { |
+ return; |
+ } |
+ |
+ // FIXME: This JSON format is a complete MVP strawman. |
+ Json::Value root; |
+ { |
+ SkAutoMutexAcquire lock(&gJsonDataLock); |
+ for (int i = 0; i < gJsonData.count(); i++) { |
+ char md5Ascii[32]; |
+ for (int j = 0; j < 16; j++) { |
+ sprintf(md5Ascii + (j*2), "%02x", gJsonData[i].md5.data[j]); |
+ } |
+ root[gJsonData[i].name.c_str()] = md5Ascii; |
+ } |
} |
- return BitmapsEqual(expected, bitmap); |
+ SkString path = SkOSPath::Join(FLAGS_writePath[0], "dm.json"); |
+ SkFILEWStream stream(path.c_str()); |
+ stream.writeText(Json::StyledWriter().write(root).c_str()); |
+ stream.flush(); |
} |
} // namespace DM |