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 26DEFINE_string2(skps, s, "skps", "A path to a directory of skps or a single skp."); 27DEFINE_string2(out, o, "img-out", "A path to an output directory."); 28DEFINE_bool(testDecode, false, "Indicates if we want to test that the images decode successfully."); 29DEFINE_bool(writeImages, true, 30 "Indicates if we want to write out supported/decoded images."); 31DEFINE_bool(writeFailedImages, false, 32 "Indicates if we want to write out unsupported/failed to decode images."); 33DEFINE_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 37static int gKnown; 38static const char* gOutputDir; 39static std::map<std::string, unsigned int> gSkpToUnknownCount = {}; 40static std::map<std::string, unsigned int> gSkpToUnsupportedCount; 41 42static SkTHashSet<SkMD5::Digest> gSeen; 43 44struct Sniffer : public SkPixelSerializer { 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::NewFromData(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 bool onUseEncodedData(const void* ptr, size_t len) override { 127 this->sniff(ptr, len); 128 return true; 129 } 130 SkData* onEncode(const SkPixmap&) override { return nullptr; } 131}; 132 133static bool get_images_from_file(const SkString& file) { 134 auto stream = SkStream::MakeFromFile(file.c_str()); 135 sk_sp<SkPicture> picture(SkPicture::MakeFromStream(stream.get())); 136 if (!picture) { 137 return false; 138 } 139 140 SkDynamicMemoryWStream scratch; 141 Sniffer sniff(file.c_str()); 142 picture->serialize(&scratch, &sniff); 143 return true; 144} 145 146int 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> --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(gOutputDir)) { 156 SkCommandLineFlags::PrintUsage(); 157 return 1; 158 } 159 160 if (sk_isdir(inputs)) { 161 SkOSFile::Iter iter(inputs, "skp"); 162 for (SkString file; iter.next(&file); ) { 163 if (!get_images_from_file(SkOSPath::Join(inputs, file.c_str()))) { 164 return 2; 165 } 166 } 167 } else { 168 if (!get_images_from_file(SkString(inputs))) { 169 return 2; 170 } 171 } 172 /** 173 JSON results are written out in the following format: 174 { 175 "failures": { 176 "skp1": 12, 177 "skp4": 2, 178 ... 179 }, 180 "unsupported": { 181 "skp9": 13, 182 "skp17": 3, 183 ... 184 } 185 "totalFailures": 32, 186 "totalUnsupported": 9, 187 "totalSuccesses": 21, 188 } 189 */ 190 Json::Value fRoot; 191 int totalFailures = 0; 192 for(auto it = gSkpToUnknownCount.cbegin(); it != gSkpToUnknownCount.cend(); ++it) 193 { 194 SkDebugf("%s %d\n", it->first.c_str(), it->second); 195 totalFailures += it->second; 196 fRoot["failures"][it->first.c_str()] = it->second; 197 } 198 fRoot["totalFailures"] = totalFailures; 199 int totalUnsupported = 0; 200#ifdef SK_DEBUG 201 for (const auto& unsupported : gSkpToUnsupportedCount) { 202 SkDebugf("%s %d\n", unsupported.first.c_str(), unsupported.second); 203 totalUnsupported += unsupported.second; 204 fRoot["unsupported"][unsupported.first] = unsupported.second; 205 } 206 fRoot["totalUnsupported"] = totalUnsupported; 207#endif 208 fRoot["totalSuccesses"] = gKnown; 209 SkDebugf("%d known, %d failures, %d unsupported\n", gKnown, totalFailures, totalUnsupported); 210 if (totalFailures > 0 || totalUnsupported > 0) { 211 if (!FLAGS_failuresJsonPath.isEmpty()) { 212 SkDebugf("Writing failures to %s\n", FLAGS_failuresJsonPath[0]); 213 SkFILEWStream stream(FLAGS_failuresJsonPath[0]); 214 stream.writeText(Json::StyledWriter().write(fRoot).c_str()); 215 stream.flush(); 216 } 217 return -1; 218 } 219 return 0; 220} 221