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
14 DEFINE_bool(nameByHash, false, "If true, write .../hash.png instead of .../mode/config/name.png");
15
16 namespace 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.
split_suffixes(int N,const char * name,SkTArray<SkString> * out)20 static 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
find_base_name(const Task & parent,SkTArray<SkString> * suffixList)32 inline 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
WriteTask(const Task & parent,const char * sourceType,SkBitmap bitmap)39 WriteTask::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
WriteTask(const Task & parent,const char * sourceType,SkStreamAsset * data,const char * ext)48 WriteTask::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
makeDirOrFail(SkString dir)61 void WriteTask::makeDirOrFail(SkString dir) {
62 if (!sk_mkdir(dir.c_str())) {
63 this->fail();
64 }
65 }
66
get_md5(const void * ptr,size_t len)67 static 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
80 struct 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 };
87 SkTArray<JsonData> gJsonData;
88 SK_DECLARE_STATIC_MUTEX(gJsonDataLock);
89
draw()90 void 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
name() const154 SkString 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
shouldSkip() const163 bool WriteTask::shouldSkip() const {
164 return FLAGS_writePath.isEmpty();
165 }
166
DumpJson()167 void 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