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 "SkJSONCPP.h" 14 #include "SkMD5.h" 15 #include "SkOSFile.h" 16 #include "SkOSPath.h" 17 #include "SkPicture.h" 18 #include "SkPixelSerializer.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."); 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_bool(testICCSupport, false, 34 "Indicates if we want to test that the images with ICC profiles are supported"); 35 DEFINE_string2(failuresJsonPath, j, "", 36 "Dump SKP and count of unknown images to the specified JSON file. Will not be " 37 "written anywhere if empty."); 38 39 static int gKnown; 40 static const char* gOutputDir; 41 static std::map<std::string, unsigned int> gSkpToUnknownCount = {}; 42 static std::map<std::string, unsigned int> gSkpToUnsupportedCount; 43 44 static SkTHashSet<SkMD5::Digest> gSeen; 45 46 struct Sniffer : public SkPixelSerializer { 47 48 std::string skpName; 49 50 Sniffer(std::string name) { 51 skpName = name; 52 } 53 54 void sniff(const void* ptr, size_t len) { 55 SkMD5 md5; 56 md5.write(ptr, len); 57 SkMD5::Digest digest; 58 md5.finish(digest); 59 60 if (gSeen.contains(digest)) { 61 return; 62 } 63 gSeen.add(digest); 64 65 sk_sp<SkData> data(SkData::MakeWithoutCopy(ptr, len)); 66 std::unique_ptr<SkCodec> codec(SkCodec::NewFromData(data)); 67 if (!codec) { 68 // FIXME: This code is currently unreachable because we create an empty generator when 69 // we fail to create a codec. 70 SkDebugf("Codec could not be created for %s\n", skpName.c_str()); 71 gSkpToUnknownCount[skpName]++; 72 return; 73 } 74 SkString ext; 75 switch (codec->getEncodedFormat()) { 76 case SkEncodedImageFormat::kBMP: ext = "bmp"; break; 77 case SkEncodedImageFormat::kGIF: ext = "gif"; break; 78 case SkEncodedImageFormat::kICO: ext = "ico"; break; 79 case SkEncodedImageFormat::kJPEG: ext = "jpg"; break; 80 case SkEncodedImageFormat::kPNG: ext = "png"; break; 81 case SkEncodedImageFormat::kDNG: ext = "dng"; break; 82 case SkEncodedImageFormat::kWBMP: ext = "wbmp"; break; 83 case SkEncodedImageFormat::kWEBP: ext = "webp"; break; 84 default: 85 // This should be unreachable because we cannot create a codec if we do not know 86 // the image type. 87 SkASSERT(false); 88 } 89 90 auto writeImage = [&] { 91 SkString path; 92 path.appendf("%s/%d.%s", gOutputDir, gKnown, ext.c_str()); 93 94 SkFILEWStream file(path.c_str()); 95 file.write(ptr, len); 96 97 SkDebugf("%s\n", path.c_str()); 98 }; 99 100 101 if (FLAGS_testDecode) { 102 SkBitmap bitmap; 103 SkImageInfo info = codec->getInfo().makeColorType(kN32_SkColorType); 104 bitmap.allocPixels(info); 105 const SkCodec::Result result = codec->getPixels( 106 info, bitmap.getPixels(), bitmap.rowBytes()); 107 if (SkCodec::kIncompleteInput != result && SkCodec::kSuccess != result) { 108 SkDebugf("Decoding failed for %s\n", skpName.c_str()); 109 gSkpToUnknownCount[skpName]++; 110 if (FLAGS_writeFailedImages) { 111 writeImage(); 112 } 113 return; 114 } 115 } 116 117 #ifdef SK_DEBUG 118 if (FLAGS_testICCSupport) { 119 if (codec->fUnsupportedICC) { 120 SkDebugf("Color correction failed for %s\n", skpName.c_str()); 121 gSkpToUnsupportedCount[skpName]++; 122 if (FLAGS_writeFailedImages) { 123 writeImage(); 124 } 125 return; 126 } 127 } 128 #endif 129 130 if (FLAGS_writeImages) { 131 writeImage(); 132 } 133 134 135 gKnown++; 136 } 137 138 bool onUseEncodedData(const void* ptr, size_t len) override { 139 this->sniff(ptr, len); 140 return true; 141 } 142 SkData* onEncode(const SkPixmap&) override { return nullptr; } 143 }; 144 145 146 int main(int argc, char** argv) { 147 SkCommandLineFlags::SetUsage( 148 "Usage: get_images_from_skps -s <dir of skps> -o <dir for output images> --testDecode " 149 "-j <output JSON path> --testICCSupport --writeImages, --writeFailedImages\n"); 150 151 SkCommandLineFlags::Parse(argc, argv); 152 const char* inputs = FLAGS_skps[0]; 153 gOutputDir = FLAGS_out[0]; 154 155 if (!sk_isdir(inputs) || !sk_isdir(gOutputDir)) { 156 SkCommandLineFlags::PrintUsage(); 157 return 1; 158 } 159 #ifndef SK_DEBUG 160 if (FLAGS_testICCSupport) { 161 std::cerr << "--testICCSupport unavailable outside of SK_DEBUG builds" << std::endl; 162 return 1; 163 } 164 #endif 165 166 SkOSFile::Iter iter(inputs, "skp"); 167 for (SkString file; iter.next(&file); ) { 168 std::unique_ptr<SkStream> stream = 169 SkStream::MakeFromFile(SkOSPath::Join(inputs, file.c_str()).c_str()); 170 sk_sp<SkPicture> picture(SkPicture::MakeFromStream(stream.get())); 171 172 SkDynamicMemoryWStream scratch; 173 Sniffer sniff(file.c_str()); 174 picture->serialize(&scratch, &sniff); 175 } 176 /** 177 JSON results are written out in the following format: 178 { 179 "failures": { 180 "skp1": 12, 181 "skp4": 2, 182 ... 183 }, 184 "unsupported": { 185 "skp9": 13, 186 "skp17": 3, 187 ... 188 } 189 "totalFailures": 32, 190 "totalUnsupported": 9, 191 "totalSuccesses": 21, 192 } 193 */ 194 Json::Value fRoot; 195 int totalFailures = 0; 196 for(auto it = gSkpToUnknownCount.cbegin(); it != gSkpToUnknownCount.cend(); ++it) 197 { 198 SkDebugf("%s %d\n", it->first.c_str(), it->second); 199 totalFailures += it->second; 200 fRoot["failures"][it->first.c_str()] = it->second; 201 } 202 fRoot["totalFailures"] = totalFailures; 203 int totalUnsupported = 0; 204 #ifdef SK_DEBUG 205 for (const auto& unsupported : gSkpToUnsupportedCount) { 206 SkDebugf("%s %d\n", unsupported.first.c_str(), unsupported.second); 207 totalUnsupported += unsupported.second; 208 fRoot["unsupported"][unsupported.first] = unsupported.second; 209 } 210 fRoot["totalUnsupported"] = totalUnsupported; 211 #endif 212 fRoot["totalSuccesses"] = gKnown; 213 SkDebugf("%d known, %d failures, %d unsupported\n", gKnown, totalFailures, totalUnsupported); 214 if (totalFailures > 0 || totalUnsupported > 0) { 215 if (!FLAGS_failuresJsonPath.isEmpty()) { 216 SkDebugf("Writing failures to %s\n", FLAGS_failuresJsonPath[0]); 217 SkFILEWStream stream(FLAGS_failuresJsonPath[0]); 218 stream.writeText(Json::StyledWriter().write(fRoot).c_str()); 219 stream.flush(); 220 } 221 return -1; 222 } 223 return 0; 224 } 225