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 48 Sniffer(std::string name) { 49 skpName = name; 50 } 51 52 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 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 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