blob: 7284183ce7ed6e631a78c58cb778b26ab9465060 [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
39 const auto json_size = SkStringPrintf("Json size: %lu bytes",
40 stats.fJsonSize);
Hal Canarydf2d27e2019-01-08 09:38:02 -050041 canvas->drawString(json_size, kR.x() + 10, kR.y() + kTextSize * 1, font, paint);
Florin Malita6eb85a12018-04-30 10:32:18 -040042 const auto animator_count = SkStringPrintf("Animator count: %lu",
43 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 Malita40c37422018-08-22 20:37:04 -040099 skottie::Animation::Builder builder;
Florin Malita57b9d402018-10-02 12:48:00 -0400100
Florin Malitafbddfbb2020-05-06 15:55:18 -0400101 auto resource_provider =
102 skresources::DataURIResourceProviderProxy::Make(
103 skresources::FileResourceProvider::Make(SkOSPath::Dirname(fPath.c_str()),
104 /*predecode=*/true),
105 /*predecode=*/true);
106
107 static constexpr char kInterceptPrefix[] = "__";
108 auto precomp_interceptor =
109 sk_make_sp<skottie_utils::ExternalAnimationPrecompInterceptor>(resource_provider,
110 kInterceptPrefix);
Florin Malitaa8316552018-11-09 16:19:44 -0500111 fAnimation = builder
112 .setLogger(logger)
Florin Malitafbddfbb2020-05-06 15:55:18 -0400113 .setResourceProvider(std::move(resource_provider))
114 .setPrecompInterceptor(std::move(precomp_interceptor))
Florin Malitaa8316552018-11-09 16:19:44 -0500115 .makeFromFile(fPath.c_str());
Florin Malita40c37422018-08-22 20:37:04 -0400116 fAnimationStats = builder.getStats();
117 fWinSize = SkSize::Make(w, h);
118 fTimeBase = 0; // force a time reset
Florin Malita094ccde2017-12-30 12:27:00 -0500119
120 if (fAnimation) {
Florin Malita87037482019-12-09 11:09:38 -0500121 fAnimation->seek(0);
Florin Malita15ee9702019-12-10 14:23:32 -0500122 fFrameTimes.resize(SkScalarCeilToInt(fAnimation->duration() * fAnimation->fps()));
Florin Malita57b9d402018-10-02 12:48:00 -0400123 SkDebugf("Loaded Bodymovin animation v: %s, size: [%f %f]\n",
Florin Malita094ccde2017-12-30 12:27:00 -0500124 fAnimation->version().c_str(),
125 fAnimation->size().width(),
Florin Malita911ae402018-05-31 16:45:29 -0400126 fAnimation->size().height());
Florin Malita57b9d402018-10-02 12:48:00 -0400127 logger->report();
Florin Malita094ccde2017-12-30 12:27:00 -0500128 } else {
129 SkDebugf("failed to load Bodymovin animation: %s\n", fPath.c_str());
130 }
131}
132
Florin Malita54f65c42018-01-16 17:04:30 -0500133void SkottieSlide::unload() {
Florin Malita094ccde2017-12-30 12:27:00 -0500134 fAnimation.reset();
135}
136
Florin Malitac4f6e022019-12-10 13:38:45 -0500137void SkottieSlide::resize(SkScalar w, SkScalar h) {
138 fWinSize = { w, h };
139}
140
Florin Malita54f65c42018-01-16 17:04:30 -0500141SkISize SkottieSlide::getDimensions() const {
Florin Malitac378fdc2018-02-09 11:15:32 -0500142 // We always scale to fill the window.
143 return fWinSize.toCeil();
Florin Malita094ccde2017-12-30 12:27:00 -0500144}
145
Florin Malita54f65c42018-01-16 17:04:30 -0500146void SkottieSlide::draw(SkCanvas* canvas) {
Florin Malita094ccde2017-12-30 12:27:00 -0500147 if (fAnimation) {
Florin Malitaaa4dc622018-01-02 14:37:37 -0500148 SkAutoCanvasRestore acr(canvas, true);
Florin Malitac378fdc2018-02-09 11:15:32 -0500149 const auto dstR = SkRect::MakeSize(fWinSize);
Florin Malita15ee9702019-12-10 14:23:32 -0500150
151 {
152 const auto t0 = SkTime::GetNSecs();
153 fAnimation->render(canvas, &dstR);
154
155 // TODO: this does not capture GPU flush time!
Florin Malita1bb3a6d2019-12-11 08:55:46 -0500156 const auto frame_index = static_cast<size_t>(fCurrentFrame);
Florin Malita15ee9702019-12-10 14:23:32 -0500157 fFrameTimes[frame_index] = static_cast<float>((SkTime::GetNSecs() - t0) * 1e-6);
158 }
Florin Malita6eb85a12018-04-30 10:32:18 -0400159
160 if (fShowAnimationStats) {
161 draw_stats_box(canvas, fAnimationStats);
162 }
Florin Malita00d4f532019-07-22 12:05:41 -0400163 if (fShowAnimationInval) {
164 const auto t = SkMatrix::MakeRectToRect(SkRect::MakeSize(fAnimation->size()),
165 dstR,
166 SkMatrix::kCenter_ScaleToFit);
167 SkPaint fill, stroke;
168 fill.setAntiAlias(true);
169 fill.setColor(0x40ff0000);
170 stroke.setAntiAlias(true);
171 stroke.setColor(0xffff0000);
172 stroke.setStyle(SkPaint::kStroke_Style);
173
174 for (const auto& r : fInvalController) {
175 SkRect bounds;
176 t.mapRect(&bounds, r);
177 canvas->drawRect(bounds, fill);
178 canvas->drawRect(bounds, stroke);
179 }
180 }
Florin Malitac4f6e022019-12-10 13:38:45 -0500181 if (fShowUI) {
182 this->renderUI();
183 }
184
Florin Malita094ccde2017-12-30 12:27:00 -0500185 }
186}
187
Hal Canary41248072019-07-11 16:32:53 -0400188bool SkottieSlide::animate(double nanos) {
Florin Malita1bb3a6d2019-12-11 08:55:46 -0500189 if (!fTimeBase) {
Florin Malita094ccde2017-12-30 12:27:00 -0500190 // Reset the animation time.
Florin Malita1bb3a6d2019-12-11 08:55:46 -0500191 fTimeBase = nanos;
Florin Malita094ccde2017-12-30 12:27:00 -0500192 }
193
194 if (fAnimation) {
Florin Malita00d4f532019-07-22 12:05:41 -0400195 fInvalController.reset();
Florin Malitac4f6e022019-12-10 13:38:45 -0500196
Florin Malita1bb3a6d2019-12-11 08:55:46 -0500197 const auto frame_count = fAnimation->duration() * fAnimation->fps();
198
Florin Malitac4f6e022019-12-10 13:38:45 -0500199 if (!fDraggingProgress) {
200 // Clock-driven progress: update current frame.
Florin Malita1bb3a6d2019-12-11 08:55:46 -0500201 const double t_sec = (nanos - fTimeBase) * 1e-9;
202 fCurrentFrame = std::fmod(t_sec * fAnimation->fps(), frame_count);
Florin Malitac4f6e022019-12-10 13:38:45 -0500203 } else {
204 // Slider-driven progress: update the time origin.
Florin Malita1bb3a6d2019-12-11 08:55:46 -0500205 fTimeBase = nanos - fCurrentFrame / fAnimation->fps() * 1e9;
206 }
207
208 // Sanitize and rate-lock the current frame.
209 fCurrentFrame = SkTPin<float>(fCurrentFrame, 0.0f, frame_count - 1);
210 if (fFrameRate > 0) {
211 const auto fps_scale = fFrameRate / fAnimation->fps();
212 fCurrentFrame = std::trunc(fCurrentFrame * fps_scale) / fps_scale;
Florin Malitac4f6e022019-12-10 13:38:45 -0500213 }
214
215 fAnimation->seekFrame(fCurrentFrame);
Florin Malita094ccde2017-12-30 12:27:00 -0500216 }
217 return true;
218}
219
Florin Malita54f65c42018-01-16 17:04:30 -0500220bool SkottieSlide::onChar(SkUnichar c) {
Florin Malita094ccde2017-12-30 12:27:00 -0500221 switch (c) {
222 case 'I':
Florin Malita6eb85a12018-04-30 10:32:18 -0400223 fShowAnimationStats = !fShowAnimationStats;
Florin Malita094ccde2017-12-30 12:27:00 -0500224 break;
225 default:
226 break;
227 }
228
229 return INHERITED::onChar(c);
230}
Florin Malita60d3bfc2018-02-20 16:49:20 -0500231
Hal Canaryb1f411a2019-08-29 10:39:22 -0400232bool SkottieSlide::onMouse(SkScalar x, SkScalar y, skui::InputState state, skui::ModifierKey) {
Florin Malita60d3bfc2018-02-20 16:49:20 -0500233 switch (state) {
Hal Canaryb1f411a2019-08-29 10:39:22 -0400234 case skui::InputState::kUp:
Florin Malita60d3bfc2018-02-20 16:49:20 -0500235 fShowAnimationInval = !fShowAnimationInval;
Florin Malita6eb85a12018-04-30 10:32:18 -0400236 fShowAnimationStats = !fShowAnimationStats;
Florin Malita60d3bfc2018-02-20 16:49:20 -0500237 break;
238 default:
239 break;
240 }
241
Florin Malitac4f6e022019-12-10 13:38:45 -0500242 fShowUI = this->UIArea().contains(x, y);
243
Florin Malita83286a02018-02-21 13:03:41 -0500244 return false;
Florin Malita60d3bfc2018-02-20 16:49:20 -0500245}
Florin Malita3d856bd2018-05-26 09:49:28 -0400246
Florin Malitac4f6e022019-12-10 13:38:45 -0500247SkRect SkottieSlide::UIArea() const {
Florin Malita1bb3a6d2019-12-11 08:55:46 -0500248 static constexpr float kUIHeight = 120.0f;
Florin Malitac4f6e022019-12-10 13:38:45 -0500249
250 return SkRect::MakeXYWH(0, fWinSize.height() - kUIHeight, fWinSize.width(), kUIHeight);
251}
252
253void SkottieSlide::renderUI() {
Florin Malita15ee9702019-12-10 14:23:32 -0500254 static constexpr auto kUI_opacity = 0.35f,
Florin Malita1bb3a6d2019-12-11 08:55:46 -0500255 kUI_hist_height = 50.0f,
256 kUI_fps_width = 100.0f;
257
258 auto add_frame_rate_option = [this](const char* label, double rate) {
259 const auto is_selected = (fFrameRate == rate);
260 if (ImGui::Selectable(label, is_selected)) {
261 fFrameRate = rate;
262 fFrameRateLabel = label;
263 }
264 if (is_selected) {
265 ImGui::SetItemDefaultFocus();
266 }
267 };
Florin Malitac4f6e022019-12-10 13:38:45 -0500268
269 ImGui::SetNextWindowBgAlpha(kUI_opacity);
270 if (ImGui::Begin("Skottie Controls", nullptr, ImGuiWindowFlags_NoDecoration |
271 ImGuiWindowFlags_NoResize |
272 ImGuiWindowFlags_NoMove |
273 ImGuiWindowFlags_NoSavedSettings |
274 ImGuiWindowFlags_NoFocusOnAppearing |
275 ImGuiWindowFlags_NoNav)) {
276 const auto ui_area = this->UIArea();
277 ImGui::SetWindowPos(ImVec2(ui_area.x(), ui_area.y()));
278 ImGui::SetWindowSize(ImVec2(ui_area.width(), ui_area.height()));
279
280 ImGui::PushItemWidth(-1);
Florin Malita15ee9702019-12-10 14:23:32 -0500281 ImGui::PlotHistogram("", fFrameTimes.data(), fFrameTimes.size(),
Florin Malita1bb3a6d2019-12-11 08:55:46 -0500282 0, nullptr, FLT_MAX, FLT_MAX, ImVec2(0, kUI_hist_height));
Florin Malita15ee9702019-12-10 14:23:32 -0500283 ImGui::SliderFloat("", &fCurrentFrame, 0, fAnimation->duration() * fAnimation->fps() - 1);
Florin Malitac4f6e022019-12-10 13:38:45 -0500284 fDraggingProgress = ImGui::IsItemActive();
Florin Malita1bb3a6d2019-12-11 08:55:46 -0500285 ImGui::PopItemWidth();
Florin Malitac4f6e022019-12-10 13:38:45 -0500286
Florin Malita1bb3a6d2019-12-11 08:55:46 -0500287 ImGui::PushItemWidth(kUI_fps_width);
288 if (ImGui::BeginCombo("FPS", fFrameRateLabel)) {
289 add_frame_rate_option("", 0.0);
290 add_frame_rate_option("Native", fAnimation->fps());
291 add_frame_rate_option( "1", 1.0);
292 add_frame_rate_option("15", 15.0);
293 add_frame_rate_option("24", 24.0);
294 add_frame_rate_option("30", 30.0);
295 add_frame_rate_option("60", 60.0);
296 ImGui::EndCombo();
297 }
Florin Malitac4f6e022019-12-10 13:38:45 -0500298 ImGui::PopItemWidth();
299 }
300 ImGui::End();
301}
302
Florin Malita3d856bd2018-05-26 09:49:28 -0400303#endif // SK_ENABLE_SKOTTIE