1#include "DMWriteTask.h" 2 3#include "DMUtil.h" 4#include "SkColorPriv.h" 5#include "SkCommonFlags.h" 6#include "SkData.h" 7#include "SkImageEncoder.h" 8#include "SkMD5.h" 9#include "SkMallocPixelRef.h" 10#include "SkOSFile.h" 11#include "SkStream.h" 12#include "SkString.h" 13 14DEFINE_bool(nameByHash, false, "If true, write .../hash.png instead of .../mode/config/name.png"); 15 16namespace DM { 17 18// Splits off the last N suffixes of name (splitting on _) and appends them to out. 19// Returns the total number of characters consumed. 20static int split_suffixes(int N, const char* name, SkTArray<SkString>* out) { 21 SkTArray<SkString> split; 22 SkStrSplit(name, "_", &split); 23 int consumed = 0; 24 for (int i = 0; i < N; i++) { 25 // We're splitting off suffixes from the back to front. 26 out->push_back(split[split.count()-i-1]); 27 consumed += out->back().size() + 1; // Add one for the _. 28 } 29 return consumed; 30} 31 32inline static SkString find_base_name(const Task& parent, SkTArray<SkString>* suffixList) { 33 const int suffixes = parent.depth() + 1; 34 const SkString& name = parent.name(); 35 const int totalSuffixLength = split_suffixes(suffixes, name.c_str(), suffixList); 36 return SkString(name.c_str(), name.size() - totalSuffixLength); 37} 38 39WriteTask::WriteTask(const Task& parent, const char* sourceType, SkBitmap bitmap) 40 : CpuTask(parent) 41 , fBaseName(find_base_name(parent, &fSuffixes)) 42 , fSourceType(sourceType) 43 , fBitmap(bitmap) 44 , fData(NULL) 45 , fExtension(".png") { 46} 47 48WriteTask::WriteTask(const Task& parent, 49 const char* sourceType, 50 SkStreamAsset *data, 51 const char* ext) 52 : CpuTask(parent) 53 , fBaseName(find_base_name(parent, &fSuffixes)) 54 , fSourceType(sourceType) 55 , fData(data) 56 , fExtension(ext) { 57 SkASSERT(fData.get()); 58 SkASSERT(fData->unique()); 59} 60 61void WriteTask::makeDirOrFail(SkString dir) { 62 if (!sk_mkdir(dir.c_str())) { 63 this->fail(); 64 } 65} 66 67static SkString get_md5(const void* ptr, size_t len) { 68 SkMD5 hasher; 69 hasher.write(ptr, len); 70 SkMD5::Digest digest; 71 hasher.finish(digest); 72 73 SkString md5; 74 for (int i = 0; i < 16; i++) { 75 md5.appendf("%02x", digest.data[i]); 76 } 77 return md5; 78} 79 80struct JsonData { 81 SkString name; // E.g. "ninepatch-stretch", "desk-gws_skp" 82 SkString config; // "gpu", "8888" 83 SkString mode; // "direct", "default-tilegrid", "pipe" 84 SkString sourceType; // "GM", "SKP" 85 SkString md5; // In ASCII, so 32 bytes long. 86}; 87SkTArray<JsonData> gJsonData; 88SK_DECLARE_STATIC_MUTEX(gJsonDataLock); 89 90void WriteTask::draw() { 91 SkString md5; 92 { 93 SkAutoLockPixels lock(fBitmap); 94 md5 = fData ? get_md5(fData->getMemoryBase(), fData->getLength()) 95 : get_md5(fBitmap.getPixels(), fBitmap.getSize()); 96 } 97 98 SkASSERT(fSuffixes.count() > 0); 99 SkString config = fSuffixes.back(); 100 SkString mode("direct"); 101 if (fSuffixes.count() > 1) { 102 mode = fSuffixes.fromBack(1); 103 } 104 105 JsonData entry = { fBaseName, config, mode, fSourceType, md5 }; 106 { 107 SkAutoMutexAcquire lock(&gJsonDataLock); 108 gJsonData.push_back(entry); 109 } 110 111 SkString dir(FLAGS_writePath[0]); 112#if SK_BUILD_FOR_IOS 113 if (dir.equals("@")) { 114 dir.set(FLAGS_resourcePath[0]); 115 } 116#endif 117 this->makeDirOrFail(dir); 118 119 SkString path; 120 if (FLAGS_nameByHash) { 121 // Flat directory of hash-named files. 122 path = SkOSPath::Join(dir.c_str(), md5.c_str()); 123 path.append(fExtension); 124 // We're content-addressed, so it's possible two threads race to write 125 // this file. We let the first one win. This also means we won't 126 // overwrite identical files from previous runs. 127 if (sk_exists(path.c_str())) { 128 return; 129 } 130 } else { 131 // Nested by mode, config, etc. 132 for (int i = 0; i < fSuffixes.count(); i++) { 133 dir = SkOSPath::Join(dir.c_str(), fSuffixes[i].c_str()); 134 this->makeDirOrFail(dir); 135 } 136 path = SkOSPath::Join(dir.c_str(), fBaseName.c_str()); 137 path.append(fExtension); 138 // The path is unique, so two threads can't both write to the same file. 139 // If already present we overwrite here, since the content may have changed. 140 } 141 142 SkFILEWStream file(path.c_str()); 143 if (!file.isValid()) { 144 return this->fail("Can't open file."); 145 } 146 147 bool ok = fData ? file.writeStream(fData, fData->getLength()) 148 : SkImageEncoder::EncodeStream(&file, fBitmap, SkImageEncoder::kPNG_Type, 100); 149 if (!ok) { 150 return this->fail("Can't write to file."); 151 } 152} 153 154SkString WriteTask::name() const { 155 SkString name("writing "); 156 for (int i = 0; i < fSuffixes.count(); i++) { 157 name.appendf("%s/", fSuffixes[i].c_str()); 158 } 159 name.append(fBaseName.c_str()); 160 return name; 161} 162 163bool WriteTask::shouldSkip() const { 164 return FLAGS_writePath.isEmpty(); 165} 166 167void WriteTask::DumpJson() { 168 if (FLAGS_writePath.isEmpty()) { 169 return; 170 } 171 172 Json::Value root; 173 174 for (int i = 1; i < FLAGS_properties.count(); i += 2) { 175 root[FLAGS_properties[i-1]] = FLAGS_properties[i]; 176 } 177 for (int i = 1; i < FLAGS_key.count(); i += 2) { 178 root["key"][FLAGS_key[i-1]] = FLAGS_key[i]; 179 } 180 181 { 182 SkAutoMutexAcquire lock(&gJsonDataLock); 183 for (int i = 0; i < gJsonData.count(); i++) { 184 Json::Value result; 185 result["key"]["name"] = gJsonData[i].name.c_str(); 186 result["key"]["config"] = gJsonData[i].config.c_str(); 187 result["key"]["mode"] = gJsonData[i].mode.c_str(); 188 result["options"]["source_type"] = gJsonData[i].sourceType.c_str(); 189 result["md5"] = gJsonData[i].md5.c_str(); 190 191 root["results"].append(result); 192 } 193 } 194 195 SkString path = SkOSPath::Join(FLAGS_writePath[0], "dm.json"); 196 SkFILEWStream stream(path.c_str()); 197 stream.writeText(Json::StyledWriter().write(root).c_str()); 198 stream.flush(); 199} 200 201} // namespace DM 202