50#if !defined(CPU_ONLY) && !defined(GPU_ONLY)
67#if defined(HAVE_VIDEO_ENCODER)
70 const char*
formats_help =
"Output format (png, skp, mp4, or null)";
75#if defined(SK_BUILD_FOR_MAC) && defined(SK_FONTMGR_CORETEXT_AVAILABLE)
77#elif defined(SK_BUILD_FOR_ANDROID) && defined(SK_FONTMGR_ANDROID_AVAILABLE)
80#elif defined(SK_BUILD_FOR_UNIX) && defined(SK_FONTMGR_FONTCONFIG_AVAILABLE)
87static DEFINE_string2(writePath,
w,
nullptr,
"Output directory. Frames are names [0-9]{6}.png.");
92static DEFINE_double(fps, 0,
"Decode frames per second (default is animation native fps).");
96static DEFINE_int(threads, 0,
"Number of worker threads (0 -> cores count).");
104enum class OutputFormat {
112auto ms_since(std::chrono::steady_clock::time_point
start) {
113 const auto elapsed = std::chrono::steady_clock::now() -
start;
114 return std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count();
117std::unique_ptr<SkFILEWStream> make_file_stream(
size_t frame_index,
const char* extension) {
121 auto stream = std::make_unique<SkFILEWStream>(
path.c_str());
128 virtual ~FrameSink() =
default;
130 static std::unique_ptr<FrameSink>
Make(OutputFormat
fmt,
size_t frame_count);
134 virtual void finalize(
double fps) {}
137 FrameSink() =
default;
140 FrameSink(
const FrameSink&) =
delete;
141 FrameSink& operator=(
const FrameSink&) =
delete;
144class PNGSink final :
public FrameSink {
147 auto stream = make_file_stream(frame_index,
"png");
165class NullSink final :
public FrameSink {
170#if defined(HAVE_VIDEO_ENCODER)
171class MP4Sink final :
public FrameSink {
173 explicit MP4Sink(
size_t frame_count) {
174 fFrames.resize(frame_count);
178 fFrames[frame_index].set_value(std::move(
frame));
181 void finalize(
double fps)
override {
184 fprintf(stderr,
"Invalid video stream configuration.\n");
187 std::vector<double> starved_ms;
188 starved_ms.reserve(fFrames.size());
190 for (
auto& frame_promise : fFrames) {
191 const auto start = std::chrono::steady_clock::now();
192 auto frame = frame_promise.get_future().get();
193 starved_ms.push_back(ms_since(
start));
195 if (!
frame)
continue;
202 auto mp4 =
encoder.endRecording();
205 .
write(mp4->data(), mp4->size());
209 double first = starved_ms[0];
210 std::sort(starved_ms.begin(), starved_ms.end());
211 double sum = std::accumulate(starved_ms.begin(), starved_ms.end(), 0);
212 printf(
"Encoder starved stats: "
213 "min %gms, med %gms, avg %gms, max %gms, sum %gms, first %gms (%s)\n",
214 starved_ms[0], starved_ms[fFrames.size()/2], sum/fFrames.size(), starved_ms.back(),
215 sum, first, first == starved_ms.back() ?
"ok" :
"BAD");
219 std::vector<std::promise<sk_sp<SkImage>>> fFrames;
225 case OutputFormat::kPNG:
226 return std::make_unique<PNGSink>();
227 case OutputFormat::kSKP:
231 return std::make_unique<NullSink>();
232 case OutputFormat::kMP4:
233#if defined(HAVE_VIDEO_ENCODER)
234 return std::make_unique<MP4Sink>(frame_count);
243class FrameGenerator {
245 virtual ~FrameGenerator() =
default;
247 static std::unique_ptr<FrameGenerator>
Make(FrameSink*, OutputFormat,
const SkMatrix&);
252 explicit FrameGenerator(FrameSink* sink) : fSink(sink) {}
257 FrameGenerator(
const FrameGenerator&) =
delete;
258 FrameGenerator& operator=(
const FrameGenerator&) =
delete;
261class CPUGenerator final :
public FrameGenerator {
264 static std::unique_ptr<FrameGenerator>
Make(FrameSink* sink,
const SkMatrix&
matrix) {
268 static std::unique_ptr<FrameGenerator>
Make(FrameSink* sink,
const SkMatrix&
matrix) {
271 SkDebugf(
"Could not allocate a %d x %d surface.\n", FLAGS_width, FLAGS_height);
275 return std::unique_ptr<FrameGenerator>(
new CPUGenerator(sink, std::move(
surface),
matrix));
279 fSurface->getCanvas()->clear(kClearColor);
280 anim->
render(fSurface->getCanvas());
282 fSink->writeFrame(fSurface->makeImageSnapshot(), frame_index);
287 : FrameGenerator(sink)
290 fSurface->getCanvas()->concat(scale_matrix);
297class SKPGenerator final :
public FrameGenerator {
299#if defined(CPU_ONLY) || defined(GPU_ONLY)
300 static std::unique_ptr<FrameGenerator>
Make(FrameSink* sink,
const SkMatrix&
matrix) {
304 static std::unique_ptr<FrameGenerator>
Make(FrameSink* sink,
const SkMatrix& scale_matrix) {
305 return std::unique_ptr<FrameGenerator>(
new SKPGenerator(sink, scale_matrix));
309 auto* canvas = fRecorder.beginRecording(FLAGS_width, FLAGS_height);
310 canvas->concat(fScaleMatrix);
313 auto frame = fRecorder.finishRecordingAsPicture();
314 auto stream = make_file_stream(frame_index,
"skp");
327 SKPGenerator(FrameSink* sink,
const SkMatrix& scale_matrix)
328 : FrameGenerator(sink)
329 , fScaleMatrix(scale_matrix)
337class GPUGenerator final :
public FrameGenerator {
340 static std::unique_ptr<FrameGenerator>
Make(FrameSink* sink,
const SkMatrix&
matrix) {
344 static std::unique_ptr<FrameGenerator>
Make(FrameSink* sink,
const SkMatrix&
matrix) {
345 auto gpu_generator = std::unique_ptr<GPUGenerator>(
new GPUGenerator(sink,
matrix));
347 return gpu_generator->isValid()
348 ? std::unique_ptr<FrameGenerator>(gpu_generator.release())
352 ~GPUGenerator()
override {
358 fSurface->getCanvas()->clear(kClearColor);
359 anim->
render(fSurface->getCanvas());
361 auto rec = std::make_unique<AsyncRec>(fSink, frame_index);
363 {0, 0, FLAGS_width, FLAGS_height},
366 AsyncCallback, rec.release());
373 : FrameGenerator(sink)
383 fSurface->getCanvas()->concat(
matrix);
385 fprintf(stderr,
"Could not initialize GL context.\n");
389 bool isValid()
const {
return !!fSurface; }
395 AsyncRec(FrameSink* sink,
size_t index) : sink(sink), index(index) {}
399 std::unique_ptr<const SkSurface::AsyncReadResult>
result) {
400 std::unique_ptr<const AsyncRec> rec(
reinterpret_cast<const AsyncRec*
>(ctx));
406 std::unique_ptr<const SkSurface::AsyncReadResult>
413 rec->sink->writeFrame(std::move(frame_image), rec->index);
426 if (
fmt == OutputFormat::kSKP) {
447 void report()
const {
448 SkDebugf(
"Animation loaded with %zu error%s, %zu warning%s.\n",
449 fErrors.size(), fErrors.size() == 1 ?
"" :
"s",
450 fWarnings.size(), fWarnings.size() == 1 ?
"" :
"s");
452 const auto&
show = [](
const LogEntry&
log,
const char prefix[]) {
454 if (!
log.fJSON.isEmpty())
459 for (
const auto& err : fErrors)
show(err,
" !! ");
460 for (
const auto& wrn : fWarnings)
show(wrn,
" ?? ");
464 std::vector<LogEntry> fErrors,
477 if (FLAGS_input.isEmpty() || FLAGS_writePath.isEmpty()) {
478 SkDebugf(
"Missing required 'input' and 'writePath' args.\n");
483 if (0 == std::strcmp(FLAGS_format[0],
"png")) {
484 fmt = OutputFormat::kPNG;
485 }
else if (0 == std::strcmp(FLAGS_format[0],
"skp")) {
486 fmt = OutputFormat::kSKP;
487 }
else if (0 == std::strcmp(FLAGS_format[0],
"null")) {
489#if defined(HAVE_VIDEO_ENCODER)
490 }
else if (0 == std::strcmp(FLAGS_format[0],
"mp4")) {
491 fmt = OutputFormat::kMP4;
494 fprintf(stderr,
"Unknown format: %s\n", FLAGS_format[0]);
498 if (
fmt != OutputFormat::kMP4 && !
sk_mkdir(FLAGS_writePath[0])) {
507#if defined(SK_BUILD_FOR_MAC) && defined(SK_FONTMGR_CORETEXT_AVAILABLE)
509#elif defined(SK_BUILD_FOR_ANDROID) && defined(SK_FONTMGR_ANDROID_AVAILABLE)
511#elif defined(SK_BUILD_FOR_UNIX) && defined(SK_FONTMGR_FONTCONFIG_AVAILABLE)
518 auto logger = sk_make_sp<Logger>();
526 auto precomp_interceptor =
527 sk_make_sp<skottie_utils::ExternalAnimationPrecompInterceptor>(rp,
"__");
530 SkDebugf(
"Could not load %s.\n", FLAGS_input[0]);
540 .setResourceProvider(rp)
542 .make(
static_cast<const char*
>(
data->data()),
data->size());
544 SkDebugf(
"Could not parse animation: '%s'.\n", FLAGS_input[0]);
553 const auto t0 =
SkTPin(FLAGS_t0, 0.0, 1.0),
554 t1 =
SkTPin(FLAGS_t1, t0, 1.0),
555 native_fps = anim->
fps(),
556 frame0 = anim->
duration() * t0 * native_fps,
559 double fps = FLAGS_fps > 0 ? FLAGS_fps : native_fps;
561 SkDebugf(
"Invalid fps: %f.\n", fps);
565 auto frame_count =
static_cast<int>(
duration * fps);
566 static constexpr int kMaxFrames = 10000;
567 if (frame_count > kMaxFrames) {
568 frame_count = kMaxFrames;
571 const auto fps_scale = native_fps / fps;
573 printf(
"Rendering %f seconds (%d frames @%f fps).\n",
duration, frame_count, fps);
577 std::vector<double> frames_ms(frame_count);
579 const auto thread_count = FLAGS_gpu ? 0 : FLAGS_threads - 1;
588 std::unique_ptr<FrameGenerator> singleton_generator;
593 tg.
batch(frame_count, [&](
int i) {
596 i = frame_count - 1 -
i;
598 const auto start = std::chrono::steady_clock::now();
599 thread_local static auto* anim =
601 .setResourceProvider(rp)
602 .setPrecompInterceptor(precomp_interceptor)
603 .make(
static_cast<const char*
>(
data->data()),
data->size())
605 thread_local static auto*
gen = singleton_generator
606 ? singleton_generator.get()
616 frames_ms[
i] = ms_since(
start);
624 std::sort(frames_ms.begin(), frames_ms.end());
625 double sum = std::accumulate(frames_ms.begin(), frames_ms.end(), 0);
626 printf(
"Frame time stats: min %gms, med %gms, avg %gms, max %gms, sum %gms\n",
627 frames_ms[0], frames_ms[frame_count/2], sum/frame_count, frames_ms.back(), sum);
SkAssertResult(font.textToGlyphs("Hello", 5, SkTextEncoding::kUTF8, glyphs, std::size(glyphs))==count)
@ kTopLeft_GrSurfaceOrigin
constexpr SkColor SK_ColorWHITE
void SK_SPI SkDebugf(const char format[],...) SK_PRINTF_LIKE(1
#define sk_double_round2int(x)
SK_API sk_sp< SkFontMgr > SkFontMgr_New_Android(const SkFontMgr_Android_CustomFonts *custom)
SK_API sk_sp< SkFontMgr > SkFontMgr_New_Custom_Empty()
SK_API sk_sp< SkFontMgr > SkFontMgr_New_FontConfig(FcConfig *fc)
SK_API sk_sp< SkFontMgr > SkFontMgr_New_CoreText(CTFontCollectionRef)
static SkImage_Base * as_IB(SkImage *image)
bool sk_mkdir(const char *path)
static std::vector< SkPDFIndirectReference > sort(const THashSet< SkPDFIndirectReference > &src)
SK_API SkString SkStringPrintf(const char *format,...) SK_PRINTF_LIKE(1
Creates a new string and writes into it using a printf()-style format.
static constexpr const T & SkTPin(const T &x, const T &lo, const T &hi)
constexpr size_t SkToSizeT(S x)
static void Parse(int argc, const char *const *argv)
static sk_sp< SkData > MakeFromFileName(const char path[])
bool write(const void *buffer, size_t size) override
static SkMatrix RectToRect(const SkRect &src, const SkRect &dst, ScaleToFit mode=kFill_ScaleToFit)
@ kCenter_ScaleToFit
scales and aligns to center
static SkString Join(const char *rootPath, const char *relativePath)
static SkString Dirname(const char *fullPath)
void batch(int N, std::function< void(int)> fn)
void seekFrame(double t, sksg::InvalidationController *ic=nullptr)
const SkSize & size() const
void render(SkCanvas *canvas, const SkRect *dst=nullptr) const
virtual void log(Level, const char message[], const char *json=nullptr)=0
static sk_sp< CachingResourceProvider > Make(sk_sp< ResourceProvider > rp)
static sk_sp< DataURIResourceProviderProxy > Make(sk_sp< ResourceProvider > rp, ImageDecodeStrategy=ImageDecodeStrategy::kLazyDecode, sk_sp< const SkFontMgr > fontMgr=nullptr)
static sk_sp< FileResourceProvider > Make(SkString base_dir, ImageDecodeStrategy=ImageDecodeStrategy::kLazyDecode)
sk_sp< SkFontMgr > fontMgr
static FlMethodResponse * show(FlTextInputPlugin *self)
uint32_t uint32_t * format
void SK_API Register(Decoder d)
SK_API sk_sp< SkImage > RasterFromPixmap(const SkPixmap &pixmap, RasterReleaseProc rasterReleaseProc, ReleaseContext releaseContext)
constexpr SkCodecs::Decoder Decoder()
SK_API sk_sp< SkDocument > Make(SkWStream *dst, const SkSerialProcs *=nullptr, std::function< void(const SkPicture *)> onEndPage=nullptr)
constexpr SkCodecs::Decoder Decoder()
SK_API bool Encode(SkWStream *dst, const SkPixmap &src, const Options &options)
unsigned useCenter Optional< SkMatrix > matrix
std::string printf(const char *fmt,...) SK_PRINTF_LIKE(1
sk_sp< Factory > BestAvailable()
SK_API sk_sp< SkSurface > Raster(const SkImageInfo &imageInfo, size_t rowBytes, const SkSurfaceProps *surfaceProps)
SK_API sk_sp< SkSurface > RenderTarget(GrRecordingContext *context, skgpu::Budgeted budgeted, const SkImageInfo &imageInfo, int sampleCount, GrSurfaceOrigin surfaceOrigin, const SkSurfaceProps *surfaceProps, bool shouldCreateWithMips=false, bool isProtected=false)
constexpr SkCodecs::Decoder Decoder()
DlVertices::Builder Builder
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir path
static SkString fmt(SkColor4f c)
static SkImageInfo MakeN32Premul(int width, int height)
static SkRect MakeIWH(int w, int h)
static constexpr SkRect MakeSize(const SkSize &size)
SkSerialImageProc fImageProc
std::shared_ptr< const fml::Mapping > data