• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
8 #include "tools/viewer/SkottieSlide.h"
9 
10 #if defined(SK_ENABLE_SKOTTIE)
11 
12 #include "include/core/SkCanvas.h"
13 #include "include/core/SkFont.h"
14 #include "include/core/SkTime.h"
15 #include "modules/skottie/include/Skottie.h"
16 #include "modules/skresources/include/SkResources.h"
17 #include "src/utils/SkOSPath.h"
18 #include "tools/timer/TimeUtils.h"
19 
20 #include <cmath>
21 
22 #include "imgui.h"
23 
draw_stats_box(SkCanvas * canvas,const skottie::Animation::Builder::Stats & stats)24 static void draw_stats_box(SkCanvas* canvas, const skottie::Animation::Builder::Stats& stats) {
25     static constexpr SkRect kR = { 10, 10, 280, 120 };
26     static constexpr SkScalar kTextSize = 20;
27 
28     SkPaint paint;
29     paint.setAntiAlias(true);
30     paint.setColor(0xffeeeeee);
31 
32     SkFont font(nullptr, kTextSize);
33 
34     canvas->drawRect(kR, paint);
35 
36     paint.setColor(SK_ColorBLACK);
37 
38     const auto json_size = SkStringPrintf("Json size: %lu bytes",
39                                           stats.fJsonSize);
40     canvas->drawString(json_size, kR.x() + 10, kR.y() + kTextSize * 1, font, paint);
41     const auto animator_count = SkStringPrintf("Animator count: %lu",
42                                                stats.fAnimatorCount);
43     canvas->drawString(animator_count, kR.x() + 10, kR.y() + kTextSize * 2, font, paint);
44     const auto json_parse_time = SkStringPrintf("Json parse time: %.3f ms",
45                                                 stats.fJsonParseTimeMS);
46     canvas->drawString(json_parse_time, kR.x() + 10, kR.y() + kTextSize * 3, font, paint);
47     const auto scene_parse_time = SkStringPrintf("Scene build time: %.3f ms",
48                                                  stats.fSceneParseTimeMS);
49     canvas->drawString(scene_parse_time, kR.x() + 10, kR.y() + kTextSize * 4, font, paint);
50     const auto total_load_time = SkStringPrintf("Total load time: %.3f ms",
51                                                 stats.fTotalLoadTimeMS);
52     canvas->drawString(total_load_time, kR.x() + 10, kR.y() + kTextSize * 5, font, paint);
53 
54     paint.setStyle(SkPaint::kStroke_Style);
55     canvas->drawRect(kR, paint);
56 }
57 
SkottieSlide(const SkString & name,const SkString & path)58 SkottieSlide::SkottieSlide(const SkString& name, const SkString& path)
59     : fPath(path) {
60     fName = name;
61 }
62 
load(SkScalar w,SkScalar h)63 void SkottieSlide::load(SkScalar w, SkScalar h) {
64     class Logger final : public skottie::Logger {
65     public:
66         struct LogEntry {
67             SkString fMessage,
68                      fJSON;
69         };
70 
71         void log(skottie::Logger::Level lvl, const char message[], const char json[]) override {
72             auto& log = lvl == skottie::Logger::Level::kError ? fErrors : fWarnings;
73             log.push_back({ SkString(message), json ? SkString(json) : SkString() });
74         }
75 
76         void report() const {
77             SkDebugf("Animation loaded with %lu error%s, %lu warning%s.\n",
78                      fErrors.size(), fErrors.size() == 1 ? "" : "s",
79                      fWarnings.size(), fWarnings.size() == 1 ? "" : "s");
80 
81             const auto& show = [](const LogEntry& log, const char prefix[]) {
82                 SkDebugf("%s%s", prefix, log.fMessage.c_str());
83                 if (!log.fJSON.isEmpty())
84                     SkDebugf(" : %s", log.fJSON.c_str());
85                 SkDebugf("\n");
86             };
87 
88             for (const auto& err : fErrors)   show(err, "  !! ");
89             for (const auto& wrn : fWarnings) show(wrn, "  ?? ");
90         }
91 
92     private:
93         std::vector<LogEntry> fErrors,
94                               fWarnings;
95     };
96 
97     auto logger = sk_make_sp<Logger>();
98     skottie::Animation::Builder builder;
99 
100     fAnimation      = builder
101             .setLogger(logger)
102             .setResourceProvider(
103                 skresources::DataURIResourceProviderProxy::Make(
104                     skresources::FileResourceProvider::Make(SkOSPath::Dirname(fPath.c_str()),
105                                                               /*predecode=*/true),
106                     /*predecode=*/true))
107             .makeFromFile(fPath.c_str());
108     fAnimationStats = builder.getStats();
109     fWinSize        = SkSize::Make(w, h);
110     fTimeBase       = 0; // force a time reset
111 
112     if (fAnimation) {
113         fAnimation->seek(0);
114         fFrameTimes.resize(SkScalarCeilToInt(fAnimation->duration() * fAnimation->fps()));
115         SkDebugf("Loaded Bodymovin animation v: %s, size: [%f %f]\n",
116                  fAnimation->version().c_str(),
117                  fAnimation->size().width(),
118                  fAnimation->size().height());
119         logger->report();
120     } else {
121         SkDebugf("failed to load Bodymovin animation: %s\n", fPath.c_str());
122     }
123 }
124 
unload()125 void SkottieSlide::unload() {
126     fAnimation.reset();
127 }
128 
resize(SkScalar w,SkScalar h)129 void SkottieSlide::resize(SkScalar w, SkScalar h) {
130     fWinSize = { w, h };
131 }
132 
getDimensions() const133 SkISize SkottieSlide::getDimensions() const {
134     // We always scale to fill the window.
135     return fWinSize.toCeil();
136 }
137 
draw(SkCanvas * canvas)138 void SkottieSlide::draw(SkCanvas* canvas) {
139     if (fAnimation) {
140         SkAutoCanvasRestore acr(canvas, true);
141         const auto dstR = SkRect::MakeSize(fWinSize);
142 
143         {
144             const auto t0 = SkTime::GetNSecs();
145             fAnimation->render(canvas, &dstR);
146 
147             // TODO: this does not capture GPU flush time!
148             const auto  frame_index  = static_cast<size_t>(fCurrentFrame);
149             fFrameTimes[frame_index] = static_cast<float>((SkTime::GetNSecs() - t0) * 1e-6);
150         }
151 
152         if (fShowAnimationStats) {
153             draw_stats_box(canvas, fAnimationStats);
154         }
155         if (fShowAnimationInval) {
156             const auto t = SkMatrix::MakeRectToRect(SkRect::MakeSize(fAnimation->size()),
157                                                     dstR,
158                                                     SkMatrix::kCenter_ScaleToFit);
159             SkPaint fill, stroke;
160             fill.setAntiAlias(true);
161             fill.setColor(0x40ff0000);
162             stroke.setAntiAlias(true);
163             stroke.setColor(0xffff0000);
164             stroke.setStyle(SkPaint::kStroke_Style);
165 
166             for (const auto& r : fInvalController) {
167                 SkRect bounds;
168                 t.mapRect(&bounds, r);
169                 canvas->drawRect(bounds, fill);
170                 canvas->drawRect(bounds, stroke);
171             }
172         }
173         if (fShowUI) {
174             this->renderUI();
175         }
176 
177     }
178 }
179 
animate(double nanos)180 bool SkottieSlide::animate(double nanos) {
181     if (!fTimeBase) {
182         // Reset the animation time.
183         fTimeBase = nanos;
184     }
185 
186     if (fAnimation) {
187         fInvalController.reset();
188 
189         const auto frame_count = fAnimation->duration() * fAnimation->fps();
190 
191         if (!fDraggingProgress) {
192             // Clock-driven progress: update current frame.
193             const double t_sec = (nanos - fTimeBase) * 1e-9;
194             fCurrentFrame = std::fmod(t_sec * fAnimation->fps(), frame_count);
195         } else {
196             // Slider-driven progress: update the time origin.
197             fTimeBase = nanos - fCurrentFrame / fAnimation->fps() * 1e9;
198         }
199 
200         // Sanitize and rate-lock the current frame.
201         fCurrentFrame = SkTPin<float>(fCurrentFrame, 0.0f, frame_count - 1);
202         if (fFrameRate > 0) {
203             const auto fps_scale = fFrameRate / fAnimation->fps();
204             fCurrentFrame = std::trunc(fCurrentFrame * fps_scale) / fps_scale;
205         }
206 
207         fAnimation->seekFrame(fCurrentFrame);
208     }
209     return true;
210 }
211 
onChar(SkUnichar c)212 bool SkottieSlide::onChar(SkUnichar c) {
213     switch (c) {
214     case 'I':
215         fShowAnimationStats = !fShowAnimationStats;
216         break;
217     default:
218         break;
219     }
220 
221     return INHERITED::onChar(c);
222 }
223 
onMouse(SkScalar x,SkScalar y,skui::InputState state,skui::ModifierKey)224 bool SkottieSlide::onMouse(SkScalar x, SkScalar y, skui::InputState state, skui::ModifierKey) {
225     switch (state) {
226     case skui::InputState::kUp:
227         fShowAnimationInval = !fShowAnimationInval;
228         fShowAnimationStats = !fShowAnimationStats;
229         break;
230     default:
231         break;
232     }
233 
234     fShowUI = this->UIArea().contains(x, y);
235 
236     return false;
237 }
238 
UIArea() const239 SkRect SkottieSlide::UIArea() const {
240     static constexpr float kUIHeight = 120.0f;
241 
242     return SkRect::MakeXYWH(0, fWinSize.height() - kUIHeight, fWinSize.width(), kUIHeight);
243 }
244 
renderUI()245 void SkottieSlide::renderUI() {
246     static constexpr auto kUI_opacity     = 0.35f,
247                           kUI_hist_height = 50.0f,
248                           kUI_fps_width   = 100.0f;
249 
250     auto add_frame_rate_option = [this](const char* label, double rate) {
251         const auto is_selected = (fFrameRate == rate);
252         if (ImGui::Selectable(label, is_selected)) {
253             fFrameRate      = rate;
254             fFrameRateLabel = label;
255         }
256         if (is_selected) {
257             ImGui::SetItemDefaultFocus();
258         }
259     };
260 
261     ImGui::SetNextWindowBgAlpha(kUI_opacity);
262     if (ImGui::Begin("Skottie Controls", nullptr, ImGuiWindowFlags_NoDecoration |
263                                                   ImGuiWindowFlags_NoResize |
264                                                   ImGuiWindowFlags_NoMove |
265                                                   ImGuiWindowFlags_NoSavedSettings |
266                                                   ImGuiWindowFlags_NoFocusOnAppearing |
267                                                   ImGuiWindowFlags_NoNav)) {
268         const auto ui_area = this->UIArea();
269         ImGui::SetWindowPos(ImVec2(ui_area.x(), ui_area.y()));
270         ImGui::SetWindowSize(ImVec2(ui_area.width(), ui_area.height()));
271 
272         ImGui::PushItemWidth(-1);
273         ImGui::PlotHistogram("", fFrameTimes.data(), fFrameTimes.size(),
274                                  0, nullptr, FLT_MAX, FLT_MAX, ImVec2(0, kUI_hist_height));
275         ImGui::SliderFloat("", &fCurrentFrame, 0, fAnimation->duration() * fAnimation->fps() - 1);
276         fDraggingProgress = ImGui::IsItemActive();
277         ImGui::PopItemWidth();
278 
279         ImGui::PushItemWidth(kUI_fps_width);
280         if (ImGui::BeginCombo("FPS", fFrameRateLabel)) {
281             add_frame_rate_option("", 0.0);
282             add_frame_rate_option("Native", fAnimation->fps());
283             add_frame_rate_option( "1",  1.0);
284             add_frame_rate_option("15", 15.0);
285             add_frame_rate_option("24", 24.0);
286             add_frame_rate_option("30", 30.0);
287             add_frame_rate_option("60", 60.0);
288             ImGui::EndCombo();
289         }
290         ImGui::PopItemWidth();
291     }
292     ImGui::End();
293 }
294 
295 #endif // SK_ENABLE_SKOTTIE
296