blob: d42816a788e1d8dffb31eeeb72156b9bbd09a049 [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 Kleinc0bd9f92019-04-23 12:05:21 -050015#include "modules/skottie/include/Skottie.h"
Florin Malitafbddfbb2020-05-06 15:55:18 -040016#include "modules/skottie/utils/SkottieUtils.h"
Brian Osman849f4d62019-11-26 08:58:26 -050017#include "modules/skresources/include/SkResources.h"
Mike Kleinc0bd9f92019-04-23 12:05:21 -050018#include "src/utils/SkOSPath.h"
Hal Canary41248072019-07-11 16:32:53 -040019#include "tools/timer/TimeUtils.h"
Florin Malita094ccde2017-12-30 12:27:00 -050020
Florin Malitaa33447d2018-05-29 13:46:54 -040021#include <cmath>
22
Florin Malitac4f6e022019-12-10 13:38:45 -050023#include "imgui.h"
24
Florin Malita40c37422018-08-22 20:37:04 -040025static void draw_stats_box(SkCanvas* canvas, const skottie::Animation::Builder::Stats& stats) {
Florin Malita6eb85a12018-04-30 10:32:18 -040026 static constexpr SkRect kR = { 10, 10, 280, 120 };
27 static constexpr SkScalar kTextSize = 20;
28
29 SkPaint paint;
30 paint.setAntiAlias(true);
31 paint.setColor(0xffeeeeee);
Hal Canarydf2d27e2019-01-08 09:38:02 -050032
33 SkFont font(nullptr, kTextSize);
Florin Malita6eb85a12018-04-30 10:32:18 -040034
35 canvas->drawRect(kR, paint);
36
37 paint.setColor(SK_ColorBLACK);
38
Adlai Holler684838f2020-05-12 10:41:04 -040039 const auto json_size = SkStringPrintf("Json size: %zu bytes",
Florin Malita6eb85a12018-04-30 10:32:18 -040040 stats.fJsonSize);
Hal Canarydf2d27e2019-01-08 09:38:02 -050041 canvas->drawString(json_size, kR.x() + 10, kR.y() + kTextSize * 1, font, paint);
Adlai Holler684838f2020-05-12 10:41:04 -040042 const auto animator_count = SkStringPrintf("Animator count: %zu",
Florin Malita6eb85a12018-04-30 10:32:18 -040043 stats.fAnimatorCount);
Hal Canarydf2d27e2019-01-08 09:38:02 -050044 canvas->drawString(animator_count, kR.x() + 10, kR.y() + kTextSize * 2, font, paint);
Florin Malita6eb85a12018-04-30 10:32:18 -040045 const auto json_parse_time = SkStringPrintf("Json parse time: %.3f ms",
46 stats.fJsonParseTimeMS);
Hal Canarydf2d27e2019-01-08 09:38:02 -050047 canvas->drawString(json_parse_time, kR.x() + 10, kR.y() + kTextSize * 3, font, paint);
Florin Malita6eb85a12018-04-30 10:32:18 -040048 const auto scene_parse_time = SkStringPrintf("Scene build time: %.3f ms",
49 stats.fSceneParseTimeMS);
Hal Canarydf2d27e2019-01-08 09:38:02 -050050 canvas->drawString(scene_parse_time, kR.x() + 10, kR.y() + kTextSize * 4, font, paint);
Florin Malita6eb85a12018-04-30 10:32:18 -040051 const auto total_load_time = SkStringPrintf("Total load time: %.3f ms",
52 stats.fTotalLoadTimeMS);
Hal Canarydf2d27e2019-01-08 09:38:02 -050053 canvas->drawString(total_load_time, kR.x() + 10, kR.y() + kTextSize * 5, font, paint);
Florin Malita6eb85a12018-04-30 10:32:18 -040054
55 paint.setStyle(SkPaint::kStroke_Style);
56 canvas->drawRect(kR, paint);
57}
58
Florin Malita54f65c42018-01-16 17:04:30 -050059SkottieSlide::SkottieSlide(const SkString& name, const SkString& path)
Florin Malita094ccde2017-12-30 12:27:00 -050060 : fPath(path) {
61 fName = name;
62}
63
Florin Malitac378fdc2018-02-09 11:15:32 -050064void SkottieSlide::load(SkScalar w, SkScalar h) {
Florin Malita57b9d402018-10-02 12:48:00 -040065 class Logger final : public skottie::Logger {
66 public:
67 struct LogEntry {
68 SkString fMessage,
69 fJSON;
70 };
71
72 void log(skottie::Logger::Level lvl, const char message[], const char json[]) override {
73 auto& log = lvl == skottie::Logger::Level::kError ? fErrors : fWarnings;
74 log.push_back({ SkString(message), json ? SkString(json) : SkString() });
75 }
76
77 void report() const {
78 SkDebugf("Animation loaded with %lu error%s, %lu warning%s.\n",
79 fErrors.size(), fErrors.size() == 1 ? "" : "s",
80 fWarnings.size(), fWarnings.size() == 1 ? "" : "s");
81
82 const auto& show = [](const LogEntry& log, const char prefix[]) {
83 SkDebugf("%s%s", prefix, log.fMessage.c_str());
84 if (!log.fJSON.isEmpty())
85 SkDebugf(" : %s", log.fJSON.c_str());
86 SkDebugf("\n");
87 };
88
89 for (const auto& err : fErrors) show(err, " !! ");
90 for (const auto& wrn : fWarnings) show(wrn, " ?? ");
91 }
92
93 private:
94 std::vector<LogEntry> fErrors,
95 fWarnings;
96 };
97
98 auto logger = sk_make_sp<Logger>();
Florin Malita67ff5412020-05-20 17:04:21 -040099
100 uint32_t flags = 0;
101 if (fPreferGlyphPaths) {
102 flags |= skottie::Animation::Builder::kPreferEmbeddedFonts;
103 }
104 skottie::Animation::Builder builder(flags);
Florin Malita57b9d402018-10-02 12:48:00 -0400105
Florin Malitafbddfbb2020-05-06 15:55:18 -0400106 auto resource_provider =
107 skresources::DataURIResourceProviderProxy::Make(
108 skresources::FileResourceProvider::Make(SkOSPath::Dirname(fPath.c_str()),
109 /*predecode=*/true),
110 /*predecode=*/true);
111
112 static constexpr char kInterceptPrefix[] = "__";
113 auto precomp_interceptor =
114 sk_make_sp<skottie_utils::ExternalAnimationPrecompInterceptor>(resource_provider,
115 kInterceptPrefix);
Florin Malitaa8316552018-11-09 16:19:44 -0500116 fAnimation = builder
117 .setLogger(logger)
Florin Malitafbddfbb2020-05-06 15:55:18 -0400118 .setResourceProvider(std::move(resource_provider))
119 .setPrecompInterceptor(std::move(precomp_interceptor))
Florin Malitaa8316552018-11-09 16:19:44 -0500120 .makeFromFile(fPath.c_str());
Florin Malita40c37422018-08-22 20:37:04 -0400121 fAnimationStats = builder.getStats();
122 fWinSize = SkSize::Make(w, h);
123 fTimeBase = 0; // force a time reset
Florin Malita094ccde2017-12-30 12:27:00 -0500124
125 if (fAnimation) {
Florin Malita87037482019-12-09 11:09:38 -0500126 fAnimation->seek(0);
Florin Malita15ee9702019-12-10 14:23:32 -0500127 fFrameTimes.resize(SkScalarCeilToInt(fAnimation->duration() * fAnimation->fps()));
Florin Malita57b9d402018-10-02 12:48:00 -0400128 SkDebugf("Loaded Bodymovin animation v: %s, size: [%f %f]\n",
Florin Malita094ccde2017-12-30 12:27:00 -0500129 fAnimation->version().c_str(),
130 fAnimation->size().width(),
Florin Malita911ae402018-05-31 16:45:29 -0400131 fAnimation->size().height());
Florin Malita57b9d402018-10-02 12:48:00 -0400132 logger->report();
Florin Malita094ccde2017-12-30 12:27:00 -0500133 } else {
134 SkDebugf("failed to load Bodymovin animation: %s\n", fPath.c_str());
135 }
136}
137
Florin Malita54f65c42018-01-16 17:04:30 -0500138void SkottieSlide::unload() {
Florin Malita094ccde2017-12-30 12:27:00 -0500139 fAnimation.reset();
140}
141
Florin Malitac4f6e022019-12-10 13:38:45 -0500142void SkottieSlide::resize(SkScalar w, SkScalar h) {
143 fWinSize = { w, h };
144}
145
Florin Malita54f65c42018-01-16 17:04:30 -0500146SkISize SkottieSlide::getDimensions() const {
Florin Malitac378fdc2018-02-09 11:15:32 -0500147 // We always scale to fill the window.
148 return fWinSize.toCeil();
Florin Malita094ccde2017-12-30 12:27:00 -0500149}
150
Florin Malita54f65c42018-01-16 17:04:30 -0500151void SkottieSlide::draw(SkCanvas* canvas) {
Florin Malita094ccde2017-12-30 12:27:00 -0500152 if (fAnimation) {
Florin Malitaaa4dc622018-01-02 14:37:37 -0500153 SkAutoCanvasRestore acr(canvas, true);
Florin Malitac378fdc2018-02-09 11:15:32 -0500154 const auto dstR = SkRect::MakeSize(fWinSize);
Florin Malita15ee9702019-12-10 14:23:32 -0500155
156 {
157 const auto t0 = SkTime::GetNSecs();
158 fAnimation->render(canvas, &dstR);
159
160 // TODO: this does not capture GPU flush time!
Florin Malita1bb3a6d2019-12-11 08:55:46 -0500161 const auto frame_index = static_cast<size_t>(fCurrentFrame);
Florin Malita15ee9702019-12-10 14:23:32 -0500162 fFrameTimes[frame_index] = static_cast<float>((SkTime::GetNSecs() - t0) * 1e-6);
163 }
Florin Malita6eb85a12018-04-30 10:32:18 -0400164
165 if (fShowAnimationStats) {
166 draw_stats_box(canvas, fAnimationStats);
167 }
Florin Malita00d4f532019-07-22 12:05:41 -0400168 if (fShowAnimationInval) {
169 const auto t = SkMatrix::MakeRectToRect(SkRect::MakeSize(fAnimation->size()),
170 dstR,
171 SkMatrix::kCenter_ScaleToFit);
172 SkPaint fill, stroke;
173 fill.setAntiAlias(true);
174 fill.setColor(0x40ff0000);
175 stroke.setAntiAlias(true);
176 stroke.setColor(0xffff0000);
177 stroke.setStyle(SkPaint::kStroke_Style);
178
179 for (const auto& r : fInvalController) {
180 SkRect bounds;
181 t.mapRect(&bounds, r);
182 canvas->drawRect(bounds, fill);
183 canvas->drawRect(bounds, stroke);
184 }
185 }
Florin Malitac4f6e022019-12-10 13:38:45 -0500186 if (fShowUI) {
187 this->renderUI();
188 }
189
Florin Malita094ccde2017-12-30 12:27:00 -0500190 }
191}
192
Hal Canary41248072019-07-11 16:32:53 -0400193bool SkottieSlide::animate(double nanos) {
Florin Malita1bb3a6d2019-12-11 08:55:46 -0500194 if (!fTimeBase) {
Florin Malita094ccde2017-12-30 12:27:00 -0500195 // Reset the animation time.
Florin Malita1bb3a6d2019-12-11 08:55:46 -0500196 fTimeBase = nanos;
Florin Malita094ccde2017-12-30 12:27:00 -0500197 }
198
199 if (fAnimation) {
Florin Malita00d4f532019-07-22 12:05:41 -0400200 fInvalController.reset();
Florin Malitac4f6e022019-12-10 13:38:45 -0500201
Florin Malita1bb3a6d2019-12-11 08:55:46 -0500202 const auto frame_count = fAnimation->duration() * fAnimation->fps();
203
Florin Malitac4f6e022019-12-10 13:38:45 -0500204 if (!fDraggingProgress) {
205 // Clock-driven progress: update current frame.
Florin Malita1bb3a6d2019-12-11 08:55:46 -0500206 const double t_sec = (nanos - fTimeBase) * 1e-9;
207 fCurrentFrame = std::fmod(t_sec * fAnimation->fps(), frame_count);
Florin Malitac4f6e022019-12-10 13:38:45 -0500208 } else {
209 // Slider-driven progress: update the time origin.
Florin Malita1bb3a6d2019-12-11 08:55:46 -0500210 fTimeBase = nanos - fCurrentFrame / fAnimation->fps() * 1e9;
211 }
212
213 // Sanitize and rate-lock the current frame.
214 fCurrentFrame = SkTPin<float>(fCurrentFrame, 0.0f, frame_count - 1);
215 if (fFrameRate > 0) {
216 const auto fps_scale = fFrameRate / fAnimation->fps();
217 fCurrentFrame = std::trunc(fCurrentFrame * fps_scale) / fps_scale;
Florin Malitac4f6e022019-12-10 13:38:45 -0500218 }
219
Florin Malita96d6c6f2020-07-29 10:51:28 -0400220 fAnimation->seekFrame(fCurrentFrame, fShowAnimationInval ? &fInvalController
221 : nullptr);
Florin Malita094ccde2017-12-30 12:27:00 -0500222 }
223 return true;
224}
225
Florin Malita54f65c42018-01-16 17:04:30 -0500226bool SkottieSlide::onChar(SkUnichar c) {
Florin Malita094ccde2017-12-30 12:27:00 -0500227 switch (c) {
228 case 'I':
Florin Malita6eb85a12018-04-30 10:32:18 -0400229 fShowAnimationStats = !fShowAnimationStats;
Florin Malita67ff5412020-05-20 17:04:21 -0400230 return true;
231 case 'G':
232 fPreferGlyphPaths = !fPreferGlyphPaths;
233 this->load(fWinSize.width(), fWinSize.height());
234 return true;
Florin Malita094ccde2017-12-30 12:27:00 -0500235 }
236
237 return INHERITED::onChar(c);
238}
Florin Malita60d3bfc2018-02-20 16:49:20 -0500239
Hal Canaryb1f411a2019-08-29 10:39:22 -0400240bool SkottieSlide::onMouse(SkScalar x, SkScalar y, skui::InputState state, skui::ModifierKey) {
Florin Malita60d3bfc2018-02-20 16:49:20 -0500241 switch (state) {
Hal Canaryb1f411a2019-08-29 10:39:22 -0400242 case skui::InputState::kUp:
Florin Malita60d3bfc2018-02-20 16:49:20 -0500243 fShowAnimationInval = !fShowAnimationInval;
Florin Malita6eb85a12018-04-30 10:32:18 -0400244 fShowAnimationStats = !fShowAnimationStats;
Florin Malita60d3bfc2018-02-20 16:49:20 -0500245 break;
246 default:
247 break;
248 }
249
Florin Malitac4f6e022019-12-10 13:38:45 -0500250 fShowUI = this->UIArea().contains(x, y);
251
Florin Malita83286a02018-02-21 13:03:41 -0500252 return false;
Florin Malita60d3bfc2018-02-20 16:49:20 -0500253}
Florin Malita3d856bd2018-05-26 09:49:28 -0400254
Florin Malitac4f6e022019-12-10 13:38:45 -0500255SkRect SkottieSlide::UIArea() const {
Florin Malita1bb3a6d2019-12-11 08:55:46 -0500256 static constexpr float kUIHeight = 120.0f;
Florin Malitac4f6e022019-12-10 13:38:45 -0500257
258 return SkRect::MakeXYWH(0, fWinSize.height() - kUIHeight, fWinSize.width(), kUIHeight);
259}
260
261void SkottieSlide::renderUI() {
Florin Malita15ee9702019-12-10 14:23:32 -0500262 static constexpr auto kUI_opacity = 0.35f,
Florin Malita1bb3a6d2019-12-11 08:55:46 -0500263 kUI_hist_height = 50.0f,
264 kUI_fps_width = 100.0f;
265
266 auto add_frame_rate_option = [this](const char* label, double rate) {
267 const auto is_selected = (fFrameRate == rate);
268 if (ImGui::Selectable(label, is_selected)) {
269 fFrameRate = rate;
270 fFrameRateLabel = label;
271 }
272 if (is_selected) {
273 ImGui::SetItemDefaultFocus();
274 }
275 };
Florin Malitac4f6e022019-12-10 13:38:45 -0500276
277 ImGui::SetNextWindowBgAlpha(kUI_opacity);
278 if (ImGui::Begin("Skottie Controls", nullptr, ImGuiWindowFlags_NoDecoration |
279 ImGuiWindowFlags_NoResize |
280 ImGuiWindowFlags_NoMove |
281 ImGuiWindowFlags_NoSavedSettings |
282 ImGuiWindowFlags_NoFocusOnAppearing |
283 ImGuiWindowFlags_NoNav)) {
284 const auto ui_area = this->UIArea();
285 ImGui::SetWindowPos(ImVec2(ui_area.x(), ui_area.y()));
286 ImGui::SetWindowSize(ImVec2(ui_area.width(), ui_area.height()));
287
288 ImGui::PushItemWidth(-1);
Florin Malita15ee9702019-12-10 14:23:32 -0500289 ImGui::PlotHistogram("", fFrameTimes.data(), fFrameTimes.size(),
Florin Malita1bb3a6d2019-12-11 08:55:46 -0500290 0, nullptr, FLT_MAX, FLT_MAX, ImVec2(0, kUI_hist_height));
Florin Malita15ee9702019-12-10 14:23:32 -0500291 ImGui::SliderFloat("", &fCurrentFrame, 0, fAnimation->duration() * fAnimation->fps() - 1);
Florin Malitac4f6e022019-12-10 13:38:45 -0500292 fDraggingProgress = ImGui::IsItemActive();
Florin Malita1bb3a6d2019-12-11 08:55:46 -0500293 ImGui::PopItemWidth();
Florin Malitac4f6e022019-12-10 13:38:45 -0500294
Florin Malita1bb3a6d2019-12-11 08:55:46 -0500295 ImGui::PushItemWidth(kUI_fps_width);
296 if (ImGui::BeginCombo("FPS", fFrameRateLabel)) {
297 add_frame_rate_option("", 0.0);
298 add_frame_rate_option("Native", fAnimation->fps());
299 add_frame_rate_option( "1", 1.0);
300 add_frame_rate_option("15", 15.0);
301 add_frame_rate_option("24", 24.0);
302 add_frame_rate_option("30", 30.0);
303 add_frame_rate_option("60", 60.0);
304 ImGui::EndCombo();
305 }
Florin Malitac4f6e022019-12-10 13:38:45 -0500306 ImGui::PopItemWidth();
307 }
308 ImGui::End();
309}
310
Florin Malita3d856bd2018-05-26 09:49:28 -0400311#endif // SK_ENABLE_SKOTTIE