blob: 031dde1e7430d46200f2622ab3ffc0351fdbbab1 [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"
Mike Klein8aa0edf2020-10-16 11:04:18 -050015#include "include/private/SkTPin.h"
Florin Malitac9c4e2e2020-08-13 12:03:37 -040016#include "modules/audioplayer/SkAudioPlayer.h"
Mike Kleinc0bd9f92019-04-23 12:05:21 -050017#include "modules/skottie/include/Skottie.h"
Florin Malitafbddfbb2020-05-06 15:55:18 -040018#include "modules/skottie/utils/SkottieUtils.h"
Brian Osman849f4d62019-11-26 08:58:26 -050019#include "modules/skresources/include/SkResources.h"
Mike Kleinc0bd9f92019-04-23 12:05:21 -050020#include "src/utils/SkOSPath.h"
Hal Canary41248072019-07-11 16:32:53 -040021#include "tools/timer/TimeUtils.h"
Florin Malita094ccde2017-12-30 12:27:00 -050022
Florin Malitaa33447d2018-05-29 13:46:54 -040023#include <cmath>
24
Florin Malitac4f6e022019-12-10 13:38:45 -050025#include "imgui.h"
26
Florin Malitacc013112020-08-13 12:27:19 -040027namespace {
28
29class Track final : public skresources::ExternalTrackAsset {
30public:
31 explicit Track(std::unique_ptr<SkAudioPlayer> player) : fPlayer(std::move(player)) {}
32
33private:
34 void seek(float t) override {
35 if (fPlayer->isStopped() && t >=0) {
36 fPlayer->play();
37 }
38
39 if (fPlayer->isPlaying()) {
40 if (t < 0) {
41 fPlayer->stop();
42 } else {
43 static constexpr float kTolerance = 0.075f;
44 const auto player_pos = fPlayer->time();
45
46 if (std::abs(player_pos - t) > kTolerance) {
47 fPlayer->setTime(t);
48 }
49 }
50 }
51 }
52
53 const std::unique_ptr<SkAudioPlayer> fPlayer;
54};
55
56class AudioProviderProxy final : public skresources::ResourceProviderProxyBase {
57public:
58 explicit AudioProviderProxy(sk_sp<skresources::ResourceProvider> rp)
59 : INHERITED(std::move(rp)) {}
60
61private:
62 sk_sp<skresources::ExternalTrackAsset> loadAudioAsset(const char path[],
63 const char name[],
64 const char[] /*id*/) override {
65 if (auto data = this->load(path, name)) {
66 if (auto player = SkAudioPlayer::Make(std::move(data))) {
67 return sk_make_sp<Track>(std::move(player));
68 }
69 }
70
71 return nullptr;
72 }
73
74 using INHERITED = skresources::ResourceProviderProxyBase;
75};
76
77} // namespace
78
Florin Malita40c37422018-08-22 20:37:04 -040079static void draw_stats_box(SkCanvas* canvas, const skottie::Animation::Builder::Stats& stats) {
Florin Malita6eb85a12018-04-30 10:32:18 -040080 static constexpr SkRect kR = { 10, 10, 280, 120 };
81 static constexpr SkScalar kTextSize = 20;
82
83 SkPaint paint;
84 paint.setAntiAlias(true);
85 paint.setColor(0xffeeeeee);
Hal Canarydf2d27e2019-01-08 09:38:02 -050086
87 SkFont font(nullptr, kTextSize);
Florin Malita6eb85a12018-04-30 10:32:18 -040088
89 canvas->drawRect(kR, paint);
90
91 paint.setColor(SK_ColorBLACK);
92
Adlai Holler684838f2020-05-12 10:41:04 -040093 const auto json_size = SkStringPrintf("Json size: %zu bytes",
Florin Malita6eb85a12018-04-30 10:32:18 -040094 stats.fJsonSize);
Hal Canarydf2d27e2019-01-08 09:38:02 -050095 canvas->drawString(json_size, kR.x() + 10, kR.y() + kTextSize * 1, font, paint);
Adlai Holler684838f2020-05-12 10:41:04 -040096 const auto animator_count = SkStringPrintf("Animator count: %zu",
Florin Malita6eb85a12018-04-30 10:32:18 -040097 stats.fAnimatorCount);
Hal Canarydf2d27e2019-01-08 09:38:02 -050098 canvas->drawString(animator_count, kR.x() + 10, kR.y() + kTextSize * 2, font, paint);
Florin Malita6eb85a12018-04-30 10:32:18 -040099 const auto json_parse_time = SkStringPrintf("Json parse time: %.3f ms",
100 stats.fJsonParseTimeMS);
Hal Canarydf2d27e2019-01-08 09:38:02 -0500101 canvas->drawString(json_parse_time, kR.x() + 10, kR.y() + kTextSize * 3, font, paint);
Florin Malita6eb85a12018-04-30 10:32:18 -0400102 const auto scene_parse_time = SkStringPrintf("Scene build time: %.3f ms",
103 stats.fSceneParseTimeMS);
Hal Canarydf2d27e2019-01-08 09:38:02 -0500104 canvas->drawString(scene_parse_time, kR.x() + 10, kR.y() + kTextSize * 4, font, paint);
Florin Malita6eb85a12018-04-30 10:32:18 -0400105 const auto total_load_time = SkStringPrintf("Total load time: %.3f ms",
106 stats.fTotalLoadTimeMS);
Hal Canarydf2d27e2019-01-08 09:38:02 -0500107 canvas->drawString(total_load_time, kR.x() + 10, kR.y() + kTextSize * 5, font, paint);
Florin Malita6eb85a12018-04-30 10:32:18 -0400108
109 paint.setStyle(SkPaint::kStroke_Style);
110 canvas->drawRect(kR, paint);
111}
112
Florin Malita54f65c42018-01-16 17:04:30 -0500113SkottieSlide::SkottieSlide(const SkString& name, const SkString& path)
Florin Malita094ccde2017-12-30 12:27:00 -0500114 : fPath(path) {
115 fName = name;
116}
117
Florin Malitac378fdc2018-02-09 11:15:32 -0500118void SkottieSlide::load(SkScalar w, SkScalar h) {
Florin Malita57b9d402018-10-02 12:48:00 -0400119 class Logger final : public skottie::Logger {
120 public:
121 struct LogEntry {
122 SkString fMessage,
123 fJSON;
124 };
125
126 void log(skottie::Logger::Level lvl, const char message[], const char json[]) override {
127 auto& log = lvl == skottie::Logger::Level::kError ? fErrors : fWarnings;
128 log.push_back({ SkString(message), json ? SkString(json) : SkString() });
129 }
130
131 void report() const {
132 SkDebugf("Animation loaded with %lu error%s, %lu warning%s.\n",
133 fErrors.size(), fErrors.size() == 1 ? "" : "s",
134 fWarnings.size(), fWarnings.size() == 1 ? "" : "s");
135
136 const auto& show = [](const LogEntry& log, const char prefix[]) {
137 SkDebugf("%s%s", prefix, log.fMessage.c_str());
138 if (!log.fJSON.isEmpty())
139 SkDebugf(" : %s", log.fJSON.c_str());
140 SkDebugf("\n");
141 };
142
143 for (const auto& err : fErrors) show(err, " !! ");
144 for (const auto& wrn : fWarnings) show(wrn, " ?? ");
145 }
146
147 private:
148 std::vector<LogEntry> fErrors,
149 fWarnings;
150 };
151
152 auto logger = sk_make_sp<Logger>();
Florin Malita67ff5412020-05-20 17:04:21 -0400153
154 uint32_t flags = 0;
155 if (fPreferGlyphPaths) {
156 flags |= skottie::Animation::Builder::kPreferEmbeddedFonts;
157 }
158 skottie::Animation::Builder builder(flags);
Florin Malita57b9d402018-10-02 12:48:00 -0400159
Florin Malitafbddfbb2020-05-06 15:55:18 -0400160 auto resource_provider =
Florin Malitacc013112020-08-13 12:27:19 -0400161 sk_make_sp<AudioProviderProxy>(
Florin Malitafbddfbb2020-05-06 15:55:18 -0400162 skresources::DataURIResourceProviderProxy::Make(
163 skresources::FileResourceProvider::Make(SkOSPath::Dirname(fPath.c_str()),
164 /*predecode=*/true),
Florin Malitacc013112020-08-13 12:27:19 -0400165 /*predecode=*/true));
Florin Malitafbddfbb2020-05-06 15:55:18 -0400166
167 static constexpr char kInterceptPrefix[] = "__";
168 auto precomp_interceptor =
169 sk_make_sp<skottie_utils::ExternalAnimationPrecompInterceptor>(resource_provider,
170 kInterceptPrefix);
Florin Malitaa8316552018-11-09 16:19:44 -0500171 fAnimation = builder
172 .setLogger(logger)
Florin Malitafbddfbb2020-05-06 15:55:18 -0400173 .setResourceProvider(std::move(resource_provider))
174 .setPrecompInterceptor(std::move(precomp_interceptor))
Florin Malitaa8316552018-11-09 16:19:44 -0500175 .makeFromFile(fPath.c_str());
Florin Malita40c37422018-08-22 20:37:04 -0400176 fAnimationStats = builder.getStats();
177 fWinSize = SkSize::Make(w, h);
178 fTimeBase = 0; // force a time reset
Florin Malita094ccde2017-12-30 12:27:00 -0500179
180 if (fAnimation) {
Florin Malita87037482019-12-09 11:09:38 -0500181 fAnimation->seek(0);
Florin Malita15ee9702019-12-10 14:23:32 -0500182 fFrameTimes.resize(SkScalarCeilToInt(fAnimation->duration() * fAnimation->fps()));
Florin Malita57b9d402018-10-02 12:48:00 -0400183 SkDebugf("Loaded Bodymovin animation v: %s, size: [%f %f]\n",
Florin Malita094ccde2017-12-30 12:27:00 -0500184 fAnimation->version().c_str(),
185 fAnimation->size().width(),
Florin Malita911ae402018-05-31 16:45:29 -0400186 fAnimation->size().height());
Florin Malita57b9d402018-10-02 12:48:00 -0400187 logger->report();
Florin Malita094ccde2017-12-30 12:27:00 -0500188 } else {
189 SkDebugf("failed to load Bodymovin animation: %s\n", fPath.c_str());
190 }
191}
192
Florin Malita54f65c42018-01-16 17:04:30 -0500193void SkottieSlide::unload() {
Florin Malita094ccde2017-12-30 12:27:00 -0500194 fAnimation.reset();
195}
196
Florin Malitac4f6e022019-12-10 13:38:45 -0500197void SkottieSlide::resize(SkScalar w, SkScalar h) {
198 fWinSize = { w, h };
199}
200
Florin Malita54f65c42018-01-16 17:04:30 -0500201SkISize SkottieSlide::getDimensions() const {
Florin Malitac378fdc2018-02-09 11:15:32 -0500202 // We always scale to fill the window.
203 return fWinSize.toCeil();
Florin Malita094ccde2017-12-30 12:27:00 -0500204}
205
Florin Malita54f65c42018-01-16 17:04:30 -0500206void SkottieSlide::draw(SkCanvas* canvas) {
Florin Malita094ccde2017-12-30 12:27:00 -0500207 if (fAnimation) {
Florin Malitaaa4dc622018-01-02 14:37:37 -0500208 SkAutoCanvasRestore acr(canvas, true);
Florin Malitac378fdc2018-02-09 11:15:32 -0500209 const auto dstR = SkRect::MakeSize(fWinSize);
Florin Malita15ee9702019-12-10 14:23:32 -0500210
211 {
212 const auto t0 = SkTime::GetNSecs();
213 fAnimation->render(canvas, &dstR);
214
215 // TODO: this does not capture GPU flush time!
Florin Malita1bb3a6d2019-12-11 08:55:46 -0500216 const auto frame_index = static_cast<size_t>(fCurrentFrame);
Florin Malita15ee9702019-12-10 14:23:32 -0500217 fFrameTimes[frame_index] = static_cast<float>((SkTime::GetNSecs() - t0) * 1e-6);
218 }
Florin Malita6eb85a12018-04-30 10:32:18 -0400219
220 if (fShowAnimationStats) {
221 draw_stats_box(canvas, fAnimationStats);
222 }
Florin Malita00d4f532019-07-22 12:05:41 -0400223 if (fShowAnimationInval) {
Mike Reed2ac6ce82021-01-15 12:26:22 -0500224 const auto t = SkMatrix::RectToRect(SkRect::MakeSize(fAnimation->size()), dstR,
225 SkMatrix::kCenter_ScaleToFit);
Florin Malita00d4f532019-07-22 12:05:41 -0400226 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