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 25static DEFINE_string2(skps, s, "skps", "A path to a directory of skps or a single skp."); 26static DEFINE_string2(out, o, "img-out", "A path to an output directory."); 27static DEFINE_bool(testDecode, false, 28 "Indicates if we want to test that the images decode successfully."); 29static DEFINE_bool(writeImages, true, 30 "Indicates if we want to write out supported/decoded images."); 31static DEFINE_bool(writeFailedImages, false, 32 "Indicates if we want to write out unsupported/failed to decode images."); 33static 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 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 { 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 = 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 126static 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 139int 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