/* * Copyright 2018 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "SkCanvas.h" #include "SkCommandLineFlags.h" #include "SkGraphics.h" #include "SkMakeUnique.h" #include "SkOSFile.h" #include "SkOSPath.h" #include "Skottie.h" #include "SkottieUtils.h" #include "SkPictureRecorder.h" #include "SkStream.h" #include "SkSurface.h" #include <vector> DEFINE_string2(input , i, nullptr, "Input .json file."); DEFINE_string2(writePath, w, nullptr, "Output directory. Frames are names [0-9]{6}.png."); DEFINE_string2(format , f, "png" , "Output format (png or skp)"); DEFINE_double(t0, 0, "Timeline start [0..1]."); DEFINE_double(t1, 1, "Timeline stop [0..1]."); DEFINE_double(fps, 30, "Decode frames per second."); DEFINE_int32(width , 800, "Render width."); DEFINE_int32(height, 600, "Render height."); namespace { class Sink { public: virtual ~Sink() = default; Sink(const Sink&) = delete; Sink& operator=(const Sink&) = delete; bool handleFrame(const sk_sp<skottie::Animation>& anim, size_t idx) const { const auto frame_file = SkStringPrintf("0%06d.%s", idx, fExtension.c_str()); SkFILEWStream stream (SkOSPath::Join(FLAGS_writePath[0], frame_file.c_str()).c_str()); if (!stream.isValid()) { SkDebugf("Could not open '%s/%s' for writing.\n", FLAGS_writePath[0], frame_file.c_str()); return false; } return this->saveFrame(anim, &stream); } protected: Sink(const char* ext) : fExtension(ext) {} virtual bool saveFrame(const sk_sp<skottie::Animation>& anim, SkFILEWStream*) const = 0; private: const SkString fExtension; }; class PNGSink final : public Sink { public: PNGSink() : INHERITED("png") , fSurface(SkSurface::MakeRasterN32Premul(FLAGS_width, FLAGS_height)) { if (!fSurface) { SkDebugf("Could not allocate a %d x %d surface.\n", FLAGS_width, FLAGS_height); } } bool saveFrame(const sk_sp<skottie::Animation>& anim, SkFILEWStream* stream) const override { if (!fSurface) return false; auto* canvas = fSurface->getCanvas(); SkAutoCanvasRestore acr(canvas, true); canvas->concat(SkMatrix::MakeRectToRect(SkRect::MakeSize(anim->size()), SkRect::MakeIWH(FLAGS_width, FLAGS_height), SkMatrix::kCenter_ScaleToFit)); canvas->clear(SK_ColorTRANSPARENT); anim->render(canvas); auto png_data = fSurface->makeImageSnapshot()->encodeToData(); if (!png_data) { SkDebugf("Failed to encode frame!\n"); return false; } return stream->write(png_data->data(), png_data->size()); } private: const sk_sp<SkSurface> fSurface; using INHERITED = Sink; }; class SKPSink final : public Sink { public: SKPSink() : INHERITED("skp") {} bool saveFrame(const sk_sp<skottie::Animation>& anim, SkFILEWStream* stream) const override { SkPictureRecorder recorder; auto canvas = recorder.beginRecording(FLAGS_width, FLAGS_height); canvas->concat(SkMatrix::MakeRectToRect(SkRect::MakeSize(anim->size()), SkRect::MakeIWH(FLAGS_width, FLAGS_height), SkMatrix::kCenter_ScaleToFit)); anim->render(canvas); recorder.finishRecordingAsPicture()->serialize(stream); return true; } private: const sk_sp<SkSurface> fSurface; using INHERITED = Sink; }; class Logger final : public skottie::Logger { public: struct LogEntry { SkString fMessage, fJSON; }; void log(skottie::Logger::Level lvl, const char message[], const char json[]) override { auto& log = lvl == skottie::Logger::Level::kError ? fErrors : fWarnings; log.push_back({ SkString(message), json ? SkString(json) : SkString() }); } void report() const { SkDebugf("Animation loaded with %lu error%s, %lu warning%s.\n", fErrors.size(), fErrors.size() == 1 ? "" : "s", fWarnings.size(), fWarnings.size() == 1 ? "" : "s"); const auto& show = [](const LogEntry& log, const char prefix[]) { SkDebugf("%s%s", prefix, log.fMessage.c_str()); if (!log.fJSON.isEmpty()) SkDebugf(" : %s", log.fJSON.c_str()); SkDebugf("\n"); }; for (const auto& err : fErrors) show(err, " !! "); for (const auto& wrn : fWarnings) show(wrn, " ?? "); } private: std::vector<LogEntry> fErrors, fWarnings; }; } // namespace int main(int argc, char** argv) { SkCommandLineFlags::Parse(argc, argv); SkAutoGraphics ag; if (FLAGS_input.isEmpty() || FLAGS_writePath.isEmpty()) { SkDebugf("Missing required 'input' and 'writePath' args.\n"); return 1; } if (FLAGS_fps <= 0) { SkDebugf("Invalid fps: %f.\n", FLAGS_fps); return 1; } if (!sk_mkdir(FLAGS_writePath[0])) { return 1; } std::unique_ptr<Sink> sink; if (0 == strcmp(FLAGS_format[0], "png")) { sink = skstd::make_unique<PNGSink>(); } else if (0 == strcmp(FLAGS_format[0], "skp")) { sink = skstd::make_unique<SKPSink>(); } else { SkDebugf("Unknown format: %s\n", FLAGS_format[0]); return 1; } auto logger = sk_make_sp<Logger>(); auto anim = skottie::Animation::Builder() .setLogger(logger) .setResourceProvider( skottie_utils::FileResourceProvider::Make(SkOSPath::Dirname(FLAGS_input[0]))) .makeFromFile(FLAGS_input[0]); if (!anim) { SkDebugf("Could not load animation: '%s'.\n", FLAGS_input[0]); return 1; } logger->report(); static constexpr double kMaxFrames = 10000; const auto t0 = SkTPin(FLAGS_t0, 0.0, 1.0), t1 = SkTPin(FLAGS_t1, t0, 1.0), advance = 1 / std::min(anim->duration() * FLAGS_fps, kMaxFrames); size_t frame_index = 0; for (auto t = t0; t <= t1; t += advance) { anim->seek(t); sink->handleFrame(anim, frame_index++); } return 0; }