• 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 "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