blob: 8b2993623383a840396a718ab7f9d7084084071a [file] [log] [blame]
Florin Malita094ccde2017-12-30 12:27:00 -05001/*
2 * Copyright 2017 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 "tools/viewer/SkottieSlide.h"
Florin Malita094ccde2017-12-30 12:27:00 -05009
Florin Malita3d856bd2018-05-26 09:49:28 -040010#if defined(SK_ENABLE_SKOTTIE)
11
Mike Kleinc0bd9f92019-04-23 12:05:21 -050012#include "include/core/SkCanvas.h"
13#include "include/core/SkFont.h"
Florin Malita15ee9702019-12-10 14:23:32 -050014#include "include/core/SkTime.h"
Florin Malitac9c4e2e2020-08-13 12:03:37 -040015#include "modules/audioplayer/SkAudioPlayer.h"
Mike Kleinc0bd9f92019-04-23 12:05:21 -050016#include "modules/skottie/include/Skottie.h"
Florin Malitafbddfbb2020-05-06 15:55:18 -040017#include "modules/skottie/utils/SkottieUtils.h"
Brian Osman849f4d62019-11-26 08:58:26 -050018#include "modules/skresources/include/SkResources.h"
Mike Kleinc0bd9f92019-04-23 12:05:21 -050019#include "src/utils/SkOSPath.h"
Hal Canary41248072019-07-11 16:32:53 -040020#include "tools/timer/TimeUtils.h"
Florin Malita094ccde2017-12-30 12:27:00 -050021
Florin Malitaa33447d2018-05-29 13:46:54 -040022#include <cmath>
23
Florin Malitac4f6e022019-12-10 13:38:45 -050024#include "imgui.h"
25
Florin Malitacc013112020-08-13 12:27:19 -040026namespace {
27
28class Track final : public skresources::ExternalTrackAsset {
29public:
30 explicit Track(std::unique_ptr<SkAudioPlayer> player) : fPlayer(std::move(player)) {}
31
32private:
33 void seek(float t) override {
34 if (fPlayer->isStopped() && t >=0) {
35 fPlayer->play();
36 }
37
38 if (fPlayer->isPlaying()) {
39 if (t < 0) {
40 fPlayer->stop();
41 } else {
42 static constexpr float kTolerance = 0.075f;
43 const auto player_pos = fPlayer->time();
44
45 if (std::abs(player_pos - t) > kTolerance) {
46 fPlayer->setTime(t);
47 }
48 }
49 }
50 }
51
52 const std::unique_ptr<SkAudioPlayer> fPlayer;
53};
54
55class AudioProviderProxy final : public skresources::ResourceProviderProxyBase {
56public:
57 explicit AudioProviderProxy(sk_sp<skresources::ResourceProvider> rp)
58 : INHERITED(std::move(rp)) {}
59
60private:
61 sk_sp<skresources::ExternalTrackAsset> loadAudioAsset(const char path[],
62 const char name[],
63 const char[] /*id*/) override {
64 if (auto data = this->load(path, name)) {
65 if (auto player = SkAudioPlayer::Make(std::move(data))) {
66 return sk_make_sp<Track>(std::move(player));
67 }
68 }
69
70 return nullptr;
71 }
72
73 using INHERITED = skresources::ResourceProviderProxyBase;
74};
75
76} // namespace
77
Florin Malita40c37422018-08-22 20:37:04 -040078static void draw_stats_box(SkCanvas* canvas, const skottie::Animation::Builder::Stats& stats) {
Florin Malita6eb85a12018-04-30 10:32:18 -040079 static constexpr SkRect kR = { 10, 10, 280, 120 };
80 static constexpr SkScalar kTextSize = 20;
81
82 SkPaint paint;
83 paint.setAntiAlias(true);
84 paint.setColor(0xffeeeeee);
Hal Canarydf2d27e2019-01-08 09:38:02 -050085
86 SkFont font(nullptr, kTextSize);
Florin Malita6eb85a12018-04-30 10:32:18 -040087
88 canvas->drawRect(kR, paint);
89
90 paint.setColor(SK_ColorBLACK);
91
Adlai Holler684838f2020-05-12 10:41:04 -040092 const auto json_size = SkStringPrintf("Json size: %zu bytes",
Florin Malita6eb85a12018-04-30 10:32:18 -040093 stats.fJsonSize);
Hal Canarydf2d27e2019-01-08 09:38:02 -050094 canvas->drawString(json_size, kR.x() + 10, kR.y() + kTextSize * 1, font, paint);
Adlai Holler684838f2020-05-12 10:41:04 -040095 const auto animator_count = SkStringPrintf("Animator count: %zu",
Florin Malita6eb85a12018-04-30 10:32:18 -040096 stats.fAnimatorCount);
Hal Canarydf2d27e2019-01-08 09:38:02 -050097 canvas->drawString(animator_count, kR.x() + 10, kR.y() + kTextSize * 2, font, paint);
Florin Malita6eb85a12018-04-30 10:32:18 -040098 const auto json_parse_time = SkStringPrintf("Json parse time: %.3f ms",
99 stats.fJsonParseTimeMS);
Hal Canarydf2d27e2019-01-08 09:38:02 -0500100 canvas->drawString(json_parse_time, kR.x() + 10, kR.y() + kTextSize * 3, font, paint);
Florin Malita6eb85a12018-04-30 10:32:18 -0400101 const auto scene_parse_time = SkStringPrintf("Scene build time: %.3f ms",
102 stats.fSceneParseTimeMS);
Hal Canarydf2d27e2019-01-08 09:38:02 -0500103 canvas->drawString(scene_parse_time, kR.x() + 10, kR.y() + kTextSize * 4, font, paint);
Florin Malita6eb85a12018-04-30 10:32:18 -0400104 const auto total_load_time = SkStringPrintf("Total load time: %.3f ms",
105 stats.fTotalLoadTimeMS);
Hal Canarydf2d27e2019-01-08 09:38:02 -0500106 canvas->drawString(total_load_time, kR.x() + 10, kR.y() + kTextSize * 5, font, paint);
Florin Malita6eb85a12018-04-30 10:32:18 -0400107
108 paint.setStyle(SkPaint::kStroke_Style);
109 canvas->drawRect(kR, paint);
110}
111
Florin Malita54f65c42018-01-16 17:04:30 -0500112SkottieSlide::SkottieSlide(const SkString& name, const SkString& path)
Florin Malita094ccde2017-12-30 12:27:00 -0500113 : fPath(path) {
114 fName = name;
115}
116
Florin Malitac378fdc2018-02-09 11:15:32 -0500117void SkottieSlide::load(SkScalar w, SkScalar h) {
Florin Malita57b9d402018-10-02 12:48:00 -0400118 class Logger final : public skottie::Logger {
119 public:
120 struct LogEntry {
121 SkString fMessage,
122 fJSON;
123 };
124
125 void log(skottie::Logger::Level lvl, const char message[], const char json[]) override {
126 auto& log = lvl == skottie::Logger::Level::kError ? fErrors : fWarnings;
127 log.push_back({ SkString(message), json ? SkString(json) : SkString() });
128 }
129
130 void report() const {
131 SkDebugf("Animation loaded with %lu error%s, %lu warning%s.\n",
132 fErrors.size(), fErrors.size() == 1 ? "" : "s",
133 fWarnings.size(), fWarnings.size() == 1 ? "" : "s");
134
135 const auto& show = [](const LogEntry& log, const char prefix[]) {
136 SkDebugf("%s%s", prefix, log.fMessage.c_str());
137 if (!log.fJSON.isEmpty())
138 SkDebugf(" : %s", log.fJSON.c_str());
139 SkDebugf("\n");
140 };
141
142 for (const auto& err : fErrors) show(err, " !! ");
143 for (const auto& wrn : fWarnings) show(wrn, " ?? ");
144 }
145
146 private:
147 std::vector<LogEntry> fErrors,
148 fWarnings;
149 };
150
151 auto logger = sk_make_sp<Logger>();
Florin Malita67ff5412020-05-20 17:04:21 -0400152
153 uint32_t flags = 0;
154 if (fPreferGlyphPaths) {
155 flags |= skottie::Animation::Builder::kPreferEmbeddedFonts;
156 }
157 skottie::Animation::Builder builder(flags);
Florin Malita57b9d402018-10-02 12:48:00 -0400158
Florin Malitafbddfbb2020-05-06 15:55:18 -0400159 auto resource_provider =
Florin Malitacc013112020-08-13 12:27:19 -0400160 sk_make_sp<AudioProviderProxy>(
Florin Malitafbddfbb2020-05-06 15:55:18 -0400161 skresources::DataURIResourceProviderProxy::Make(
162 skresources::FileResourceProvider::Make(SkOSPath::Dirname(fPath.c_str()),
163 /*predecode=*/true),
Florin Malitacc013112020-08-13 12:27:19 -0400164 /*predecode=*/true));
Florin Malitafbddfbb2020-05-06 15:55:18 -0400165
166 static constexpr char kInterceptPrefix[] = "__";
167 auto precomp_interceptor =
168 sk_make_sp<skottie_utils::ExternalAnimationPrecompInterceptor>(resource_provider,
169 kInterceptPrefix);
Florin Malitaa8316552018-11-09 16:19:44 -0500170 fAnimation = builder
171 .setLogger(logger)
Florin Malitafbddfbb2020-05-06 15:55:18 -0400172 .setResourceProvider(std::move(resource_provider))
173 .setPrecompInterceptor(std::move(precomp_interceptor))
Florin Malitaa8316552018-11-09 16:19:44 -0500174 .makeFromFile(fPath.c_str());
Florin Malita40c37422018-08-22 20:37:04 -0400175 fAnimationStats = builder.getStats();
176 fWinSize = SkSize::Make(w, h);
177 fTimeBase = 0; // force a time reset
Florin Malita094ccde2017-12-30 12:27:00 -0500178
179 if (fAnimation) {
Florin Malita87037482019-12-09 11:09:38 -0500180 fAnimation->seek(0);
Florin Malita15ee9702019-12-10 14:23:32 -0500181 fFrameTimes.resize(SkScalarCeilToInt(fAnimation->duration() * fAnimation->fps()));
Florin Malita57b9d402018-10-02 12:48:00 -0400182 SkDebugf("Loaded Bodymovin animation v: %s, size: [%f %f]\n",
Florin Malita094ccde2017-12-30 12:27:00 -0500183 fAnimation->version().c_str(),
184 fAnimation->size().width(),
Florin Malita911ae402018-05-31 16:45:29 -0400185 fAnimation->size().height());
Florin Malita57b9d402018-10-02 12:48:00 -0400186 logger->report();
Florin Malita094ccde2017-12-30 12:27:00 -0500187 } else {
188 SkDebugf("failed to load Bodymovin animation: %s\n", fPath.c_str());
189 }
190}
191
Florin Malita54f65c42018-01-16 17:04:30 -0500192void SkottieSlide::unload() {
Florin Malita094ccde2017-12-30 12:27:00 -0500193 fAnimation.reset();
194}
195
Florin Malitac4f6e022019-12-10 13:38:45 -0500196void SkottieSlide::resize(SkScalar w, SkScalar h) {
197 fWinSize = { w, h };
198}
199
Florin Malita54f65c42018-01-16 17:04:30 -0500200SkISize SkottieSlide::getDimensions() const {
Florin Malitac378fdc2018-02-09 11:15:32 -0500201 // We always scale to fill the window.
202 return fWinSize.toCeil();
Florin Malita094ccde2017-12-30 12:27:00 -0500203}
204
Florin Malita54f65c42018-01-16 17:04:30 -0500205void SkottieSlide::draw(SkCanvas* canvas) {
Florin Malita094ccde2017-12-30 12:27:00 -0500206 if (fAnimation) {
Florin Malitaaa4dc622018-01-02 14:37:37 -0500207 SkAutoCanvasRestore acr(canvas, true);
Florin Malitac378fdc2018-02-09 11:15:32 -0500208 const auto dstR = SkRect::MakeSize(fWinSize);
Florin Malita15ee9702019-12-10 14:23:32 -0500209
210 {
211 const auto t0 = SkTime::GetNSecs();
212 fAnimation->render(canvas, &dstR);
213
214 // TODO: this does not capture GPU flush time!
Florin Malita1bb3a6d2019-12-11 08:55:46 -0500215 const auto frame_index = static_cast<size_t>(fCurrentFrame);
Florin Malita15ee9702019-12-10 14:23:32 -0500216 fFrameTimes[frame_index] = static_cast<float>((SkTime::GetNSecs() - t0) * 1e-6);
217 }
Florin Malita6eb85a12018-04-30 10:32:18 -0400218
219 if (fShowAnimationStats) {
220 draw_stats_box(canvas, fAnimationStats);
221 }
Florin Malita00d4f532019-07-22 12:05:41 -0400222 if (fShowAnimationInval) {
223 const auto t = SkMatrix::MakeRectToRect(SkRect::MakeSize(fAnimation->size()),
224 dstR,
225 SkMatrix::kCenter_ScaleToFit);
226 SkPaint fill, stroke;
227 fill.setAntiAlias(true);
228 fill.setColor(0x40ff0000);
229 stroke.setAntiAlias(true);
230 stroke.setColor(0xffff0000);
231 stroke.setStyle(SkPaint::kStroke_Style);
232
233 for (const auto& r : fInvalController) {
234 SkRect bounds;
235 t.mapRect(&bounds, r);
236 canvas->drawRect(bounds, fill);
237 canvas->drawRect(bounds, stroke);
238 }
239 }
Florin Malitac4f6e022019-12-10 13:38:45 -0500240 if (fShowUI) {
241 this->renderUI();
242 }
243
Florin Malita094ccde2017-12-30 12:27:00 -0500244 }
245}
246
Hal Canary41248072019-07-11 16:32:53 -0400247bool SkottieSlide::animate(double nanos) {
Florin Malita1bb3a6d2019-12-11 08:55:46 -0500248 if (!fTimeBase) {
Florin Malita094ccde2017-12-30 12:27:00 -0500249 // Reset the animation time.
Florin Malita1bb3a6d2019-12-11 08:55:46 -0500250 fTimeBase = nanos;
Florin Malita094ccde2017-12-30 12:27:00 -0500251 }
252
253 if (fAnimation) {
Florin Malita00d4f532019-07-22 12:05:41 -0400254 fInvalController.reset();
Florin Malitac4f6e022019-12-10 13:38:45 -0500255
Florin Malita1bb3a6d2019-12-11 08:55:46 -0500256 const auto frame_count = fAnimation->duration() * fAnimation->fps();
257
Florin Malitac4f6e022019-12-10 13:38:45 -0500258 if (!fDraggingProgress) {
259 // Clock-driven progress: update current frame.
Florin Malita1bb3a6d2019-12-11 08:55:46 -0500260 const double t_sec = (nanos - fTimeBase) * 1e-9;
261 fCurrentFrame = std::fmod(t_sec * fAnimation->fps(), frame_count);
Florin Malitac4f6e022019-12-10 13:38:45 -0500262 } else {
263 // Slider-driven progress: update the time origin.
Florin Malita1bb3a6d2019-12-11 08:55:46 -0500264 fTimeBase = nanos - fCurrentFrame / fAnimation->fps() * 1e9;
265 }
266
267 // Sanitize and rate-lock the current frame.
268 fCurrentFrame = SkTPin<float>(fCurrentFrame, 0.0f, frame_count - 1);
269 if (fFrameRate > 0) {
270 const auto fps_scale = fFrameRate / fAnimation->fps();
271 fCurrentFrame = std::trunc(fCurrentFrame * fps_scale) / fps_scale;
Florin Malitac4f6e022019-12-10 13:38:45 -0500272 }
273
Florin Malita96d6c6f2020-07-29 10:51:28 -0400274 fAnimation->seekFrame(fCurrentFrame, fShowAnimationInval ? &fInvalController
275 : nullptr);
Florin Malita094ccde2017-12-30 12:27:00 -0500276 }
277 return true;
278}
279
Florin Malita54f65c42018-01-16 17:04:30 -0500280bool SkottieSlide::onChar(SkUnichar c) {
Florin Malita094ccde2017-12-30 12:27:00 -0500281 switch (c) {
282 case 'I':
Florin Malita6eb85a12018-04-30 10:32:18 -0400283 fShowAnimationStats = !fShowAnimationStats;
Florin Malita67ff5412020-05-20 17:04:21 -0400284 return true;
285 case 'G':
286 fPreferGlyphPaths = !fPreferGlyphPaths;
287 this->load(fWinSize.width(), fWinSize.height());
288 return true;
Florin Malita094ccde2017-12-30 12:27:00 -0500289 }
290
291 return INHERITED::onChar(c);
292}
Florin Malita60d3bfc2018-02-20 16:49:20 -0500293
Hal Canaryb1f411a2019-08-29 10:39:22 -0400294bool SkottieSlide::onMouse(SkScalar x, SkScalar y, skui::InputState state, skui::ModifierKey) {
Florin Malita60d3bfc2018-02-20 16:49:20 -0500295 switch (state) {
Hal Canaryb1f411a2019-08-29 10:39:22 -0400296 case skui::InputState::kUp:
Florin Malita60d3bfc2018-02-20 16:49:20 -0500297 fShowAnimationInval = !fShowAnimationInval;
Florin Malita6eb85a12018-04-30 10:32:18 -0400298 fShowAnimationStats = !fShowAnimationStats;
Florin Malita60d3bfc2018-02-20 16:49:20 -0500299 break;
300 default:
301 break;
302 }
303
Florin Malitac4f6e022019-12-10 13:38:45 -0500304 fShowUI = this->UIArea().contains(x, y);
305
Florin Malita83286a02018-02-21 13:03:41 -0500306 return false;
Florin Malita60d3bfc2018-02-20 16:49:20 -0500307}
Florin Malita3d856bd2018-05-26 09:49:28 -0400308
Florin Malitac4f6e022019-12-10 13:38:45 -0500309SkRect SkottieSlide::UIArea() const {
Florin Malita1bb3a6d2019-12-11 08:55:46 -0500310 static constexpr float kUIHeight = 120.0f;
Florin Malitac4f6e022019-12-10 13:38:45 -0500311
312 return SkRect::MakeXYWH(0, fWinSize.height() - kUIHeight, fWinSize.width(), kUIHeight);
313}
314
315void SkottieSlide::renderUI() {
Florin Malita15ee9702019-12-10 14:23:32 -0500316 static constexpr auto kUI_opacity = 0.35f,
Florin Malita1bb3a6d2019-12-11 08:55:46 -0500317 kUI_hist_height = 50.0f,
318 kUI_fps_width = 100.0f;
319
320 auto add_frame_rate_option = [this](const char* label, double rate) {
321 const auto is_selected = (fFrameRate == rate);
322 if (ImGui::Selectable(label, is_selected)) {
323 fFrameRate = rate;
324 fFrameRateLabel = label;
325 }
326 if (is_selected) {
327 ImGui::SetItemDefaultFocus();
328 }
329 };
Florin Malitac4f6e022019-12-10 13:38:45 -0500330
331 ImGui::SetNextWindowBgAlpha(kUI_opacity);
332 if (ImGui::Begin("Skottie Controls", nullptr, ImGuiWindowFlags_NoDecoration |
333 ImGuiWindowFlags_NoResize |
334 ImGuiWindowFlags_NoMove |
335 ImGuiWindowFlags_NoSavedSettings |
336 ImGuiWindowFlags_NoFocusOnAppearing |
337 ImGuiWindowFlags_NoNav)) {
338 const auto ui_area = this->UIArea();
339 ImGui::SetWindowPos(ImVec2(ui_area.x(), ui_area.y()));
340 ImGui::SetWindowSize(ImVec2(ui_area.width(), ui_area.height()));
341
342 ImGui::PushItemWidth(-1);
Florin Malita15ee9702019-12-10 14:23:32 -0500343 ImGui::PlotHistogram("", fFrameTimes.data(), fFrameTimes.size(),
Florin Malita1bb3a6d2019-12-11 08:55:46 -0500344 0, nullptr, FLT_MAX, FLT_MAX, ImVec2(0, kUI_hist_height));
Florin Malita15ee9702019-12-10 14:23:32 -0500345 ImGui::SliderFloat("", &fCurrentFrame, 0, fAnimation->duration() * fAnimation->fps() - 1);
Florin Malitac4f6e022019-12-10 13:38:45 -0500346 fDraggingProgress = ImGui::IsItemActive();
Florin Malita1bb3a6d2019-12-11 08:55:46 -0500347 ImGui::PopItemWidth();
Florin Malitac4f6e022019-12-10 13:38:45 -0500348
Florin Malita1bb3a6d2019-12-11 08:55:46 -0500349 ImGui::PushItemWidth(kUI_fps_width);
350 if (ImGui::BeginCombo("FPS", fFrameRateLabel)) {
351 add_frame_rate_option("", 0.0);
352 add_frame_rate_option("Native", fAnimation->fps());
353 add_frame_rate_option( "1", 1.0);
354 add_frame_rate_option("15", 15.0);
355 add_frame_rate_option("24", 24.0);
356 add_frame_rate_option("30", 30.0);
357 add_frame_rate_option("60", 60.0);
358 ImGui::EndCombo();
359 }
Florin Malitac4f6e022019-12-10 13:38:45 -0500360 ImGui::PopItemWidth();
361 }
362 ImGui::End();
363}
364
Florin Malita3d856bd2018-05-26 09:49:28 -0400365#endif // SK_ENABLE_SKOTTIE