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