SkottieTool.cpp 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. /*
  2. * Copyright 2018 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. #include "include/core/SkCanvas.h"
  8. #include "include/core/SkGraphics.h"
  9. #include "include/core/SkPictureRecorder.h"
  10. #include "include/core/SkStream.h"
  11. #include "include/core/SkSurface.h"
  12. #include "modules/skottie/include/Skottie.h"
  13. #include "modules/skottie/utils/SkottieUtils.h"
  14. #include "src/core/SkMakeUnique.h"
  15. #include "src/core/SkOSFile.h"
  16. #include "src/utils/SkOSPath.h"
  17. #include "tools/flags/CommandLineFlags.h"
  18. #include <vector>
  19. static DEFINE_string2(input , i, nullptr, "Input .json file.");
  20. static DEFINE_string2(writePath, w, nullptr, "Output directory. Frames are names [0-9]{6}.png.");
  21. static DEFINE_string2(format , f, "png" , "Output format (png or skp)");
  22. static DEFINE_double(t0, 0, "Timeline start [0..1].");
  23. static DEFINE_double(t1, 1, "Timeline stop [0..1].");
  24. static DEFINE_double(fps, 30, "Decode frames per second.");
  25. static DEFINE_int(width , 800, "Render width.");
  26. static DEFINE_int(height, 600, "Render height.");
  27. namespace {
  28. class Sink {
  29. public:
  30. virtual ~Sink() = default;
  31. Sink(const Sink&) = delete;
  32. Sink& operator=(const Sink&) = delete;
  33. bool handleFrame(const sk_sp<skottie::Animation>& anim, size_t idx) const {
  34. const auto frame_file = SkStringPrintf("0%06d.%s", idx, fExtension.c_str());
  35. SkFILEWStream stream (SkOSPath::Join(FLAGS_writePath[0], frame_file.c_str()).c_str());
  36. if (!stream.isValid()) {
  37. SkDebugf("Could not open '%s/%s' for writing.\n",
  38. FLAGS_writePath[0], frame_file.c_str());
  39. return false;
  40. }
  41. return this->saveFrame(anim, &stream);
  42. }
  43. protected:
  44. Sink(const char* ext) : fExtension(ext) {}
  45. virtual bool saveFrame(const sk_sp<skottie::Animation>& anim, SkFILEWStream*) const = 0;
  46. private:
  47. const SkString fExtension;
  48. };
  49. class PNGSink final : public Sink {
  50. public:
  51. PNGSink()
  52. : INHERITED("png")
  53. , fSurface(SkSurface::MakeRasterN32Premul(FLAGS_width, FLAGS_height)) {
  54. if (!fSurface) {
  55. SkDebugf("Could not allocate a %d x %d surface.\n", FLAGS_width, FLAGS_height);
  56. }
  57. }
  58. bool saveFrame(const sk_sp<skottie::Animation>& anim, SkFILEWStream* stream) const override {
  59. if (!fSurface) return false;
  60. auto* canvas = fSurface->getCanvas();
  61. SkAutoCanvasRestore acr(canvas, true);
  62. canvas->concat(SkMatrix::MakeRectToRect(SkRect::MakeSize(anim->size()),
  63. SkRect::MakeIWH(FLAGS_width, FLAGS_height),
  64. SkMatrix::kCenter_ScaleToFit));
  65. canvas->clear(SK_ColorTRANSPARENT);
  66. anim->render(canvas);
  67. auto png_data = fSurface->makeImageSnapshot()->encodeToData();
  68. if (!png_data) {
  69. SkDebugf("Failed to encode frame!\n");
  70. return false;
  71. }
  72. return stream->write(png_data->data(), png_data->size());
  73. }
  74. private:
  75. const sk_sp<SkSurface> fSurface;
  76. using INHERITED = Sink;
  77. };
  78. class SKPSink final : public Sink {
  79. public:
  80. SKPSink() : INHERITED("skp") {}
  81. bool saveFrame(const sk_sp<skottie::Animation>& anim, SkFILEWStream* stream) const override {
  82. SkPictureRecorder recorder;
  83. auto canvas = recorder.beginRecording(FLAGS_width, FLAGS_height);
  84. canvas->concat(SkMatrix::MakeRectToRect(SkRect::MakeSize(anim->size()),
  85. SkRect::MakeIWH(FLAGS_width, FLAGS_height),
  86. SkMatrix::kCenter_ScaleToFit));
  87. anim->render(canvas);
  88. recorder.finishRecordingAsPicture()->serialize(stream);
  89. return true;
  90. }
  91. private:
  92. const sk_sp<SkSurface> fSurface;
  93. using INHERITED = Sink;
  94. };
  95. class Logger final : public skottie::Logger {
  96. public:
  97. struct LogEntry {
  98. SkString fMessage,
  99. fJSON;
  100. };
  101. void log(skottie::Logger::Level lvl, const char message[], const char json[]) override {
  102. auto& log = lvl == skottie::Logger::Level::kError ? fErrors : fWarnings;
  103. log.push_back({ SkString(message), json ? SkString(json) : SkString() });
  104. }
  105. void report() const {
  106. SkDebugf("Animation loaded with %lu error%s, %lu warning%s.\n",
  107. fErrors.size(), fErrors.size() == 1 ? "" : "s",
  108. fWarnings.size(), fWarnings.size() == 1 ? "" : "s");
  109. const auto& show = [](const LogEntry& log, const char prefix[]) {
  110. SkDebugf("%s%s", prefix, log.fMessage.c_str());
  111. if (!log.fJSON.isEmpty())
  112. SkDebugf(" : %s", log.fJSON.c_str());
  113. SkDebugf("\n");
  114. };
  115. for (const auto& err : fErrors) show(err, " !! ");
  116. for (const auto& wrn : fWarnings) show(wrn, " ?? ");
  117. }
  118. private:
  119. std::vector<LogEntry> fErrors,
  120. fWarnings;
  121. };
  122. } // namespace
  123. int main(int argc, char** argv) {
  124. CommandLineFlags::Parse(argc, argv);
  125. SkAutoGraphics ag;
  126. if (FLAGS_input.isEmpty() || FLAGS_writePath.isEmpty()) {
  127. SkDebugf("Missing required 'input' and 'writePath' args.\n");
  128. return 1;
  129. }
  130. if (FLAGS_fps <= 0) {
  131. SkDebugf("Invalid fps: %f.\n", FLAGS_fps);
  132. return 1;
  133. }
  134. if (!sk_mkdir(FLAGS_writePath[0])) {
  135. return 1;
  136. }
  137. std::unique_ptr<Sink> sink;
  138. if (0 == strcmp(FLAGS_format[0], "png")) {
  139. sink = skstd::make_unique<PNGSink>();
  140. } else if (0 == strcmp(FLAGS_format[0], "skp")) {
  141. sink = skstd::make_unique<SKPSink>();
  142. } else {
  143. SkDebugf("Unknown format: %s\n", FLAGS_format[0]);
  144. return 1;
  145. }
  146. auto logger = sk_make_sp<Logger>();
  147. auto anim = skottie::Animation::Builder()
  148. .setLogger(logger)
  149. .setResourceProvider(
  150. skottie_utils::FileResourceProvider::Make(SkOSPath::Dirname(FLAGS_input[0])))
  151. .makeFromFile(FLAGS_input[0]);
  152. if (!anim) {
  153. SkDebugf("Could not load animation: '%s'.\n", FLAGS_input[0]);
  154. return 1;
  155. }
  156. logger->report();
  157. static constexpr double kMaxFrames = 10000;
  158. const auto t0 = SkTPin(FLAGS_t0, 0.0, 1.0),
  159. t1 = SkTPin(FLAGS_t1, t0, 1.0),
  160. advance = 1 / std::min(anim->duration() * FLAGS_fps, kMaxFrames);
  161. size_t frame_index = 0;
  162. for (auto t = t0; t <= t1; t += advance) {
  163. anim->seek(t);
  164. sink->handleFrame(anim, frame_index++);
  165. }
  166. return 0;
  167. }