blob: 4abfc602f1042694ab57f35971d5550a29534ca7 [file] [log] [blame]
Florin Malita79725d32018-06-05 16:16:57 -04001/*
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
Mike Kleinc0bd9f92019-04-23 12:05:21 -05008#include "include/core/SkCanvas.h"
9#include "include/core/SkGraphics.h"
10#include "include/core/SkPictureRecorder.h"
11#include "include/core/SkStream.h"
12#include "include/core/SkSurface.h"
Mike Klein8dc7d6c2019-09-18 10:29:40 -050013#include "include/encode/SkPngEncoder.h"
Mike Kleinc0bd9f92019-04-23 12:05:21 -050014#include "modules/skottie/include/Skottie.h"
15#include "modules/skottie/utils/SkottieUtils.h"
16#include "src/core/SkMakeUnique.h"
17#include "src/core/SkOSFile.h"
18#include "src/utils/SkOSPath.h"
19#include "tools/flags/CommandLineFlags.h"
Florin Malita79725d32018-06-05 16:16:57 -040020
Florin Malita57b9d402018-10-02 12:48:00 -040021#include <vector>
Florin Malita79725d32018-06-05 16:16:57 -040022
Mike Klein84836b72019-03-21 11:31:36 -050023static DEFINE_string2(input , i, nullptr, "Input .json file.");
24static DEFINE_string2(writePath, w, nullptr, "Output directory. Frames are names [0-9]{6}.png.");
25static DEFINE_string2(format , f, "png" , "Output format (png or skp)");
Florin Malita79725d32018-06-05 16:16:57 -040026
Mike Klein84836b72019-03-21 11:31:36 -050027static DEFINE_double(t0, 0, "Timeline start [0..1].");
28static DEFINE_double(t1, 1, "Timeline stop [0..1].");
29static DEFINE_double(fps, 30, "Decode frames per second.");
Florin Malita79725d32018-06-05 16:16:57 -040030
Mike Klein5b3f3432019-03-21 11:42:21 -050031static DEFINE_int(width , 800, "Render width.");
32static DEFINE_int(height, 600, "Render height.");
Florin Malita79725d32018-06-05 16:16:57 -040033
Florin Malita0c604ed2018-07-13 15:47:27 -040034namespace {
35
Ben Wagnerd5148e32018-07-16 17:44:06 -040036class Sink {
Florin Malita0c604ed2018-07-13 15:47:27 -040037public:
38 virtual ~Sink() = default;
Ben Wagnerd5148e32018-07-16 17:44:06 -040039 Sink(const Sink&) = delete;
40 Sink& operator=(const Sink&) = delete;
Florin Malita0c604ed2018-07-13 15:47:27 -040041
42 bool handleFrame(const sk_sp<skottie::Animation>& anim, size_t idx) const {
43 const auto frame_file = SkStringPrintf("0%06d.%s", idx, fExtension.c_str());
44 SkFILEWStream stream (SkOSPath::Join(FLAGS_writePath[0], frame_file.c_str()).c_str());
45
46 if (!stream.isValid()) {
47 SkDebugf("Could not open '%s/%s' for writing.\n",
48 FLAGS_writePath[0], frame_file.c_str());
49 return false;
50 }
51
52 return this->saveFrame(anim, &stream);
53 }
54
55protected:
56 Sink(const char* ext) : fExtension(ext) {}
57
58 virtual bool saveFrame(const sk_sp<skottie::Animation>& anim, SkFILEWStream*) const = 0;
59
60private:
61 const SkString fExtension;
Florin Malita0c604ed2018-07-13 15:47:27 -040062};
63
64class PNGSink final : public Sink {
65public:
66 PNGSink()
67 : INHERITED("png")
68 , fSurface(SkSurface::MakeRasterN32Premul(FLAGS_width, FLAGS_height)) {
69 if (!fSurface) {
70 SkDebugf("Could not allocate a %d x %d surface.\n", FLAGS_width, FLAGS_height);
71 }
72 }
73
74 bool saveFrame(const sk_sp<skottie::Animation>& anim, SkFILEWStream* stream) const override {
75 if (!fSurface) return false;
76
77 auto* canvas = fSurface->getCanvas();
78 SkAutoCanvasRestore acr(canvas, true);
79
80 canvas->concat(SkMatrix::MakeRectToRect(SkRect::MakeSize(anim->size()),
81 SkRect::MakeIWH(FLAGS_width, FLAGS_height),
82 SkMatrix::kCenter_ScaleToFit));
83
84 canvas->clear(SK_ColorTRANSPARENT);
85 anim->render(canvas);
86
Florin Malita0c604ed2018-07-13 15:47:27 -040087
Mike Klein8dc7d6c2019-09-18 10:29:40 -050088 // Set encoding options to favor speed over size.
89 SkPngEncoder::Options options;
90 options.fZLibLevel = 1;
91 options.fFilterFlags = SkPngEncoder::FilterFlag::kNone;
92
93 sk_sp<SkImage> img = fSurface->makeImageSnapshot();
94 SkPixmap pixmap;
95 return img->peekPixels(&pixmap)
96 && SkPngEncoder::Encode(stream, pixmap, options);
Florin Malita0c604ed2018-07-13 15:47:27 -040097 }
98
99private:
100 const sk_sp<SkSurface> fSurface;
101
102 using INHERITED = Sink;
103};
104
105class SKPSink final : public Sink {
106public:
107 SKPSink() : INHERITED("skp") {}
108
109 bool saveFrame(const sk_sp<skottie::Animation>& anim, SkFILEWStream* stream) const override {
110 SkPictureRecorder recorder;
111
112 auto canvas = recorder.beginRecording(FLAGS_width, FLAGS_height);
113 canvas->concat(SkMatrix::MakeRectToRect(SkRect::MakeSize(anim->size()),
114 SkRect::MakeIWH(FLAGS_width, FLAGS_height),
115 SkMatrix::kCenter_ScaleToFit));
116 anim->render(canvas);
117 recorder.finishRecordingAsPicture()->serialize(stream);
118
119 return true;
120 }
121
122private:
123 const sk_sp<SkSurface> fSurface;
124
125 using INHERITED = Sink;
126};
127
Florin Malita57b9d402018-10-02 12:48:00 -0400128class Logger final : public skottie::Logger {
129public:
130 struct LogEntry {
131 SkString fMessage,
132 fJSON;
133 };
134
135 void log(skottie::Logger::Level lvl, const char message[], const char json[]) override {
136 auto& log = lvl == skottie::Logger::Level::kError ? fErrors : fWarnings;
137 log.push_back({ SkString(message), json ? SkString(json) : SkString() });
138 }
139
140 void report() const {
141 SkDebugf("Animation loaded with %lu error%s, %lu warning%s.\n",
142 fErrors.size(), fErrors.size() == 1 ? "" : "s",
143 fWarnings.size(), fWarnings.size() == 1 ? "" : "s");
144
145 const auto& show = [](const LogEntry& log, const char prefix[]) {
146 SkDebugf("%s%s", prefix, log.fMessage.c_str());
147 if (!log.fJSON.isEmpty())
148 SkDebugf(" : %s", log.fJSON.c_str());
149 SkDebugf("\n");
150 };
151
152 for (const auto& err : fErrors) show(err, " !! ");
153 for (const auto& wrn : fWarnings) show(wrn, " ?? ");
154 }
155
156private:
157 std::vector<LogEntry> fErrors,
158 fWarnings;
159};
160
Florin Malita0c604ed2018-07-13 15:47:27 -0400161} // namespace
162
Florin Malita79725d32018-06-05 16:16:57 -0400163int main(int argc, char** argv) {
Mike Klein88544fb2019-03-20 10:50:33 -0500164 CommandLineFlags::Parse(argc, argv);
Florin Malita79725d32018-06-05 16:16:57 -0400165 SkAutoGraphics ag;
166
167 if (FLAGS_input.isEmpty() || FLAGS_writePath.isEmpty()) {
168 SkDebugf("Missing required 'input' and 'writePath' args.\n");
169 return 1;
170 }
171
172 if (FLAGS_fps <= 0) {
173 SkDebugf("Invalid fps: %f.\n", FLAGS_fps);
174 return 1;
175 }
176
177 if (!sk_mkdir(FLAGS_writePath[0])) {
178 return 1;
179 }
180
Florin Malita0c604ed2018-07-13 15:47:27 -0400181 std::unique_ptr<Sink> sink;
182 if (0 == strcmp(FLAGS_format[0], "png")) {
183 sink = skstd::make_unique<PNGSink>();
184 } else if (0 == strcmp(FLAGS_format[0], "skp")) {
185 sink = skstd::make_unique<SKPSink>();
186 } else {
187 SkDebugf("Unknown format: %s\n", FLAGS_format[0]);
188 return 1;
189 }
190
Florin Malita57b9d402018-10-02 12:48:00 -0400191 auto logger = sk_make_sp<Logger>();
192
193 auto anim = skottie::Animation::Builder()
194 .setLogger(logger)
Florin Malitaa8316552018-11-09 16:19:44 -0500195 .setResourceProvider(
196 skottie_utils::FileResourceProvider::Make(SkOSPath::Dirname(FLAGS_input[0])))
Florin Malita57b9d402018-10-02 12:48:00 -0400197 .makeFromFile(FLAGS_input[0]);
Florin Malita79725d32018-06-05 16:16:57 -0400198 if (!anim) {
199 SkDebugf("Could not load animation: '%s'.\n", FLAGS_input[0]);
200 return 1;
201 }
202
Florin Malita57b9d402018-10-02 12:48:00 -0400203 logger->report();
204
Florin Malita79725d32018-06-05 16:16:57 -0400205 static constexpr double kMaxFrames = 10000;
206 const auto t0 = SkTPin(FLAGS_t0, 0.0, 1.0),
207 t1 = SkTPin(FLAGS_t1, t0, 1.0),
208 advance = 1 / std::min(anim->duration() * FLAGS_fps, kMaxFrames);
209
210 size_t frame_index = 0;
211 for (auto t = t0; t <= t1; t += advance) {
Florin Malita79725d32018-06-05 16:16:57 -0400212 anim->seek(t);
Florin Malita0c604ed2018-07-13 15:47:27 -0400213 sink->handleFrame(anim, frame_index++);
Florin Malita79725d32018-06-05 16:16:57 -0400214 }
Florin Malita79725d32018-06-05 16:16:57 -0400215
216 return 0;
217}