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