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