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