• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2018 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 "SkCanvas.h"
9 #include "SkCommandLineFlags.h"
10 #include "SkGraphics.h"
11 #include "SkMakeUnique.h"
12 #include "SkOSFile.h"
13 #include "SkOSPath.h"
14 #include "Skottie.h"
15 #include "SkottieUtils.h"
16 #include "SkPictureRecorder.h"
17 #include "SkStream.h"
18 #include "SkSurface.h"
19 
20 #include <vector>
21 
22 DEFINE_string2(input    , i, nullptr, "Input .json file.");
23 DEFINE_string2(writePath, w, nullptr, "Output directory.  Frames are names [0-9]{6}.png.");
24 DEFINE_string2(format   , f, "png"  , "Output format (png or skp)");
25 
26 DEFINE_double(t0,   0, "Timeline start [0..1].");
27 DEFINE_double(t1,   1, "Timeline stop [0..1].");
28 DEFINE_double(fps, 30, "Decode frames per second.");
29 
30 DEFINE_int32(width , 800, "Render width.");
31 DEFINE_int32(height, 600, "Render height.");
32 
33 namespace {
34 
35 class Sink {
36 public:
37     virtual ~Sink() = default;
38     Sink(const Sink&) = delete;
39     Sink& operator=(const Sink&) = delete;
40 
handleFrame(const sk_sp<skottie::Animation> & anim,size_t idx) const41     bool handleFrame(const sk_sp<skottie::Animation>& anim, size_t idx) const {
42         const auto frame_file = SkStringPrintf("0%06d.%s", idx, fExtension.c_str());
43         SkFILEWStream stream (SkOSPath::Join(FLAGS_writePath[0], frame_file.c_str()).c_str());
44 
45         if (!stream.isValid()) {
46             SkDebugf("Could not open '%s/%s' for writing.\n",
47                      FLAGS_writePath[0], frame_file.c_str());
48             return false;
49         }
50 
51         return this->saveFrame(anim, &stream);
52     }
53 
54 protected:
Sink(const char * ext)55     Sink(const char* ext) : fExtension(ext) {}
56 
57     virtual bool saveFrame(const sk_sp<skottie::Animation>& anim, SkFILEWStream*) const = 0;
58 
59 private:
60     const SkString fExtension;
61 };
62 
63 class PNGSink final : public Sink {
64 public:
PNGSink()65     PNGSink()
66         : INHERITED("png")
67         , fSurface(SkSurface::MakeRasterN32Premul(FLAGS_width, FLAGS_height)) {
68         if (!fSurface) {
69             SkDebugf("Could not allocate a %d x %d surface.\n", FLAGS_width, FLAGS_height);
70         }
71     }
72 
saveFrame(const sk_sp<skottie::Animation> & anim,SkFILEWStream * stream) const73     bool saveFrame(const sk_sp<skottie::Animation>& anim, SkFILEWStream* stream) const override {
74         if (!fSurface) return false;
75 
76         auto* canvas = fSurface->getCanvas();
77         SkAutoCanvasRestore acr(canvas, true);
78 
79         canvas->concat(SkMatrix::MakeRectToRect(SkRect::MakeSize(anim->size()),
80                                                 SkRect::MakeIWH(FLAGS_width, FLAGS_height),
81                                                 SkMatrix::kCenter_ScaleToFit));
82 
83         canvas->clear(SK_ColorTRANSPARENT);
84         anim->render(canvas);
85 
86         auto png_data = fSurface->makeImageSnapshot()->encodeToData();
87         if (!png_data) {
88             SkDebugf("Failed to encode frame!\n");
89             return false;
90         }
91 
92         return stream->write(png_data->data(), png_data->size());
93     }
94 
95 private:
96     const sk_sp<SkSurface> fSurface;
97 
98     using INHERITED = Sink;
99 };
100 
101 class SKPSink final : public Sink {
102 public:
SKPSink()103     SKPSink() : INHERITED("skp") {}
104 
saveFrame(const sk_sp<skottie::Animation> & anim,SkFILEWStream * stream) const105     bool saveFrame(const sk_sp<skottie::Animation>& anim, SkFILEWStream* stream) const override {
106         SkPictureRecorder recorder;
107 
108         auto canvas = recorder.beginRecording(FLAGS_width, FLAGS_height);
109         canvas->concat(SkMatrix::MakeRectToRect(SkRect::MakeSize(anim->size()),
110                                                 SkRect::MakeIWH(FLAGS_width, FLAGS_height),
111                                                 SkMatrix::kCenter_ScaleToFit));
112         anim->render(canvas);
113         recorder.finishRecordingAsPicture()->serialize(stream);
114 
115         return true;
116     }
117 
118 private:
119     const sk_sp<SkSurface> fSurface;
120 
121     using INHERITED = Sink;
122 };
123 
124 class Logger final : public skottie::Logger {
125 public:
126     struct LogEntry {
127         SkString fMessage,
128                  fJSON;
129     };
130 
log(skottie::Logger::Level lvl,const char message[],const char json[])131     void log(skottie::Logger::Level lvl, const char message[], const char json[]) override {
132         auto& log = lvl == skottie::Logger::Level::kError ? fErrors : fWarnings;
133         log.push_back({ SkString(message), json ? SkString(json) : SkString() });
134     }
135 
report() const136     void report() const {
137         SkDebugf("Animation loaded with %lu error%s, %lu warning%s.\n",
138                  fErrors.size(), fErrors.size() == 1 ? "" : "s",
139                  fWarnings.size(), fWarnings.size() == 1 ? "" : "s");
140 
141         const auto& show = [](const LogEntry& log, const char prefix[]) {
142             SkDebugf("%s%s", prefix, log.fMessage.c_str());
143             if (!log.fJSON.isEmpty())
144                 SkDebugf(" : %s", log.fJSON.c_str());
145             SkDebugf("\n");
146         };
147 
148         for (const auto& err : fErrors)   show(err, "  !! ");
149         for (const auto& wrn : fWarnings) show(wrn, "  ?? ");
150     }
151 
152 private:
153     std::vector<LogEntry> fErrors,
154                           fWarnings;
155 };
156 
157 } // namespace
158 
main(int argc,char ** argv)159 int main(int argc, char** argv) {
160     SkCommandLineFlags::Parse(argc, argv);
161     SkAutoGraphics ag;
162 
163     if (FLAGS_input.isEmpty() || FLAGS_writePath.isEmpty()) {
164         SkDebugf("Missing required 'input' and 'writePath' args.\n");
165         return 1;
166     }
167 
168     if (FLAGS_fps <= 0) {
169         SkDebugf("Invalid fps: %f.\n", FLAGS_fps);
170         return 1;
171     }
172 
173     if (!sk_mkdir(FLAGS_writePath[0])) {
174         return 1;
175     }
176 
177     std::unique_ptr<Sink> sink;
178     if (0 == strcmp(FLAGS_format[0], "png")) {
179         sink = skstd::make_unique<PNGSink>();
180     } else if (0 == strcmp(FLAGS_format[0], "skp")) {
181         sink = skstd::make_unique<SKPSink>();
182     } else {
183         SkDebugf("Unknown format: %s\n", FLAGS_format[0]);
184         return 1;
185     }
186 
187     auto logger = sk_make_sp<Logger>();
188 
189     auto anim = skottie::Animation::Builder()
190             .setLogger(logger)
191             .setResourceProvider(
192                 skottie_utils::FileResourceProvider::Make(SkOSPath::Dirname(FLAGS_input[0])))
193             .makeFromFile(FLAGS_input[0]);
194     if (!anim) {
195         SkDebugf("Could not load animation: '%s'.\n", FLAGS_input[0]);
196         return 1;
197     }
198 
199     logger->report();
200 
201     static constexpr double kMaxFrames = 10000;
202     const auto t0 = SkTPin(FLAGS_t0, 0.0, 1.0),
203                t1 = SkTPin(FLAGS_t1,  t0, 1.0),
204                advance = 1 / std::min(anim->duration() * FLAGS_fps, kMaxFrames);
205 
206     size_t frame_index = 0;
207     for (auto t = t0; t <= t1; t += advance) {
208         anim->seek(t);
209         sink->handleFrame(anim, frame_index++);
210     }
211 
212     return 0;
213 }
214