1 /*
2 * Copyright 2016 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8 #include "include/codec/SkCodec.h"
9 #include "include/core/SkBitmap.h"
10 #include "include/core/SkColorSpace.h"
11 #include "include/core/SkData.h"
12 #include "include/core/SkPicture.h"
13 #include "include/core/SkSerialProcs.h"
14 #include "include/core/SkStream.h"
15 #include "include/private/SkTHash.h"
16 #include "src/core/SkMD5.h"
17 #include "src/core/SkOSFile.h"
18 #include "src/utils/SkJSONWriter.h"
19 #include "src/utils/SkOSPath.h"
20 #include "tools/flags/CommandLineFlags.h"
21
22 #include <iostream>
23 #include <map>
24
25 static DEFINE_string2(skps, s, "skps", "A path to a directory of skps or a single skp.");
26 static DEFINE_string2(out, o, "img-out", "A path to an output directory.");
27 static DEFINE_bool(testDecode, false,
28 "Indicates if we want to test that the images decode successfully.");
29 static DEFINE_bool(writeImages, true,
30 "Indicates if we want to write out supported/decoded images.");
31 static DEFINE_bool(writeFailedImages, false,
32 "Indicates if we want to write out unsupported/failed to decode images.");
33 static DEFINE_string2(failuresJsonPath, j, "",
34 "Dump SKP and count of unknown images to the specified JSON file. Will not be "
35 "written anywhere if empty.");
36
37 static int gKnown;
38 static const char* gOutputDir;
39 static std::map<std::string, unsigned int> gSkpToUnknownCount = {};
40 static std::map<std::string, unsigned int> gSkpToUnsupportedCount;
41
42 static SkTHashSet<SkMD5::Digest> gSeen;
43
44 struct Sniffer {
45
46 std::string skpName;
47
SnifferSniffer48 Sniffer(std::string name) {
49 skpName = name;
50 }
51
sniffSniffer52 void sniff(const void* ptr, size_t len) {
53 SkMD5 md5;
54 md5.write(ptr, len);
55 SkMD5::Digest digest = md5.finish();
56
57 if (gSeen.contains(digest)) {
58 return;
59 }
60 gSeen.add(digest);
61
62 sk_sp<SkData> data(SkData::MakeWithoutCopy(ptr, len));
63 std::unique_ptr<SkCodec> codec = SkCodec::MakeFromData(data);
64 if (!codec) {
65 // FIXME: This code is currently unreachable because we create an empty generator when
66 // we fail to create a codec.
67 SkDebugf("Codec could not be created for %s\n", skpName.c_str());
68 gSkpToUnknownCount[skpName]++;
69 return;
70 }
71 SkString ext;
72 switch (codec->getEncodedFormat()) {
73 case SkEncodedImageFormat::kBMP: ext = "bmp"; break;
74 case SkEncodedImageFormat::kGIF: ext = "gif"; break;
75 case SkEncodedImageFormat::kICO: ext = "ico"; break;
76 case SkEncodedImageFormat::kJPEG: ext = "jpg"; break;
77 case SkEncodedImageFormat::kPNG: ext = "png"; break;
78 case SkEncodedImageFormat::kDNG: ext = "dng"; break;
79 case SkEncodedImageFormat::kWBMP: ext = "wbmp"; break;
80 case SkEncodedImageFormat::kWEBP: ext = "webp"; break;
81 default:
82 // This should be unreachable because we cannot create a codec if we do not know
83 // the image type.
84 SkASSERT(false);
85 }
86
87 auto writeImage = [&] (const char* name, int num) {
88 SkString path;
89 path.appendf("%s/%s%d.%s", gOutputDir, name, num, ext.c_str());
90
91 SkFILEWStream file(path.c_str());
92 file.write(ptr, len);
93
94 SkDebugf("%s\n", path.c_str());
95 };
96
97 if (FLAGS_testDecode) {
98 SkBitmap bitmap;
99 SkImageInfo info = codec->getInfo().makeColorType(kN32_SkColorType);
100 bitmap.allocPixels(info);
101 const SkCodec::Result result = codec->getPixels(
102 info, bitmap.getPixels(), bitmap.rowBytes());
103 switch (result) {
104 case SkCodec::kSuccess:
105 case SkCodec::kIncompleteInput:
106 case SkCodec::kErrorInInput:
107 break;
108 default:
109 SkDebugf("Decoding failed for %s\n", skpName.c_str());
110 if (FLAGS_writeFailedImages) {
111 writeImage("unknown", gSkpToUnknownCount[skpName]);
112 }
113 gSkpToUnknownCount[skpName]++;
114 return;
115 }
116 }
117
118 if (FLAGS_writeImages) {
119 writeImage("", gKnown);
120 }
121
122 gKnown++;
123 }
124 };
125
get_images_from_file(const SkString & file)126 static bool get_images_from_file(const SkString& file) {
127 Sniffer sniff(file.c_str());
128 auto stream = SkStream::MakeFromFile(file.c_str());
129
130 SkDeserialProcs procs;
131 procs.fImageProc = [](const void* data, size_t size, void* ctx) -> sk_sp<SkImage> {
132 ((Sniffer*)ctx)->sniff(data, size);
133 return nullptr;
134 };
135 procs.fImageCtx = &sniff;
136 return SkPicture::MakeFromStream(stream.get(), &procs) != nullptr;
137 }
138
main(int argc,char ** argv)139 int main(int argc, char** argv) {
140 CommandLineFlags::SetUsage(
141 "Usage: get_images_from_skps -s <dir of skps> -o <dir for output images> --testDecode "
142 "-j <output JSON path> --writeImages, --writeFailedImages\n");
143
144 CommandLineFlags::Parse(argc, argv);
145 const char* inputs = FLAGS_skps[0];
146 gOutputDir = FLAGS_out[0];
147
148 if (!sk_isdir(gOutputDir)) {
149 CommandLineFlags::PrintUsage();
150 return 1;
151 }
152
153 if (sk_isdir(inputs)) {
154 SkOSFile::Iter iter(inputs, "skp");
155 for (SkString file; iter.next(&file); ) {
156 if (!get_images_from_file(SkOSPath::Join(inputs, file.c_str()))) {
157 return 2;
158 }
159 }
160 } else {
161 if (!get_images_from_file(SkString(inputs))) {
162 return 2;
163 }
164 }
165 /**
166 JSON results are written out in the following format:
167 {
168 "failures": {
169 "skp1": 12,
170 "skp4": 2,
171 ...
172 },
173 "unsupported": {
174 "skp9": 13,
175 "skp17": 3,
176 ...
177 }
178 "totalFailures": 32,
179 "totalUnsupported": 9,
180 "totalSuccesses": 21,
181 }
182 */
183
184 unsigned int totalFailures = 0,
185 totalUnsupported = 0;
186 SkDynamicMemoryWStream memStream;
187 SkJSONWriter writer(&memStream, SkJSONWriter::Mode::kPretty);
188 writer.beginObject();
189 {
190 writer.beginObject("failures");
191 {
192 for(const auto& failure : gSkpToUnknownCount) {
193 SkDebugf("%s %d\n", failure.first.c_str(), failure.second);
194 totalFailures += failure.second;
195 writer.appendU32(failure.first.c_str(), failure.second);
196 }
197 }
198 writer.endObject();
199 writer.appendU32("totalFailures", totalFailures);
200
201 #ifdef SK_DEBUG
202 writer.beginObject("unsupported");
203 {
204 for (const auto& unsupported : gSkpToUnsupportedCount) {
205 SkDebugf("%s %d\n", unsupported.first.c_str(), unsupported.second);
206 totalUnsupported += unsupported.second;
207 writer.appendHexU32(unsupported.first.c_str(), unsupported.second);
208 }
209
210 }
211 writer.endObject();
212 writer.appendU32("totalUnsupported", totalUnsupported);
213 #endif
214
215 writer.appendS32("totalSuccesses", gKnown);
216 SkDebugf("%d known, %d failures, %d unsupported\n",
217 gKnown, totalFailures, totalUnsupported);
218 }
219 writer.endObject();
220 writer.flush();
221
222 if (totalFailures > 0 || totalUnsupported > 0) {
223 if (!FLAGS_failuresJsonPath.isEmpty()) {
224 SkDebugf("Writing failures to %s\n", FLAGS_failuresJsonPath[0]);
225 SkFILEWStream stream(FLAGS_failuresJsonPath[0]);
226 auto jsonStream = memStream.detachAsStream();
227 stream.writeStream(jsonStream.get(), jsonStream->getLength());
228 }
229 }
230
231 return 0;
232 }
233