From 4b203ad7ac024b959fb6baa2d0cc3a724fb8fa85 Mon Sep 17 00:00:00 2001 From: Mike Reed Date: Mon, 17 Jun 2019 17:45:01 -0400 Subject: [PATCH] add motion blur params to skottie2movie --motion_angle ... [default is 180] --motion_samples ... [default is 1, for no motion blur] Change-Id: Iec0f31655b3369f51e0b398efb2d5b156dcbaf2e Reviewed-on: https://skia-review.googlesource.com/c/skia/+/221416 Reviewed-by: Mike Reed Commit-Queue: Mike Reed Auto-Submit: Mike Reed --- include/core/SkSurface.h | 9 +++ modules/skottie/include/Skottie.h | 5 ++ modules/skottie/src/Skottie.cpp | 6 ++ src/image/SkSurface.cpp | 9 +++ tools/skottie2movie.cpp | 124 ++++++++++++++++++++++-------- 5 files changed, 121 insertions(+), 32 deletions(-) diff --git a/include/core/SkSurface.h b/include/core/SkSurface.h index aff5e1fcde..181ddd9548 100644 --- a/include/core/SkSurface.h +++ b/include/core/SkSurface.h @@ -432,6 +432,10 @@ public: */ int height() const { return fHeight; } + /** Returns an ImageInfo describing the surface. + */ + SkImageInfo imageInfo(); + /** Returns unique value identifying the content of SkSurface. Returned value changes each time the content changes. Content is changed by drawing, or by calling notifyContentWillChange(). @@ -542,6 +546,11 @@ public: */ sk_sp makeSurface(const SkImageInfo& imageInfo); + /** Calls makeSurface(ImageInfo) with the same ImageInfo as this surface, but with the + * specified width and height. + */ + sk_sp makeSurface(int width, int height); + /** Returns SkImage capturing SkSurface contents. Subsequent drawing to SkSurface contents are not captured. SkImage allocation is accounted for if SkSurface was created with SkBudgeted::kYes. diff --git a/modules/skottie/include/Skottie.h b/modules/skottie/include/Skottie.h index cc29dc930b..538e5f3d8a 100644 --- a/modules/skottie/include/Skottie.h +++ b/modules/skottie/include/Skottie.h @@ -216,6 +216,11 @@ public: */ void seek(SkScalar t); + /** Update the animation state to match t, specifed in frame time + * i.e. relative to duration(). + */ + void seekFrameTime(double t); + /** * Returns the animation duration in seconds. */ diff --git a/modules/skottie/src/Skottie.cpp b/modules/skottie/src/Skottie.cpp index 5ac0402033..3f958e8809 100644 --- a/modules/skottie/src/Skottie.cpp +++ b/modules/skottie/src/Skottie.cpp @@ -604,6 +604,12 @@ void Animation::seek(SkScalar t) { fScene->animate(SkTPin(fInPoint + t * (fOutPoint - fInPoint), fInPoint, kLastValidFrame)); } +void Animation::seekFrameTime(double t) { + if (double dur = this->duration()) { + this->seek((SkScalar)(t / dur)); + } +} + sk_sp Animation::Make(const char* data, size_t length) { return Builder().make(data, length); } diff --git a/src/image/SkSurface.cpp b/src/image/SkSurface.cpp index debcda7d71..1266e17c5e 100644 --- a/src/image/SkSurface.cpp +++ b/src/image/SkSurface.cpp @@ -274,6 +274,11 @@ SkSurface::SkSurface(const SkImageInfo& info, const SkSurfaceProps* props) fGenerationID = 0; } +SkImageInfo SkSurface::imageInfo() { + // TODO: do we need to go through canvas for this? + return this->getCanvas()->imageInfo(); +} + uint32_t SkSurface::generationID() { if (0 == fGenerationID) { fGenerationID = asSB(this)->newGenerationID(); @@ -311,6 +316,10 @@ sk_sp SkSurface::makeSurface(const SkImageInfo& info) { return asSB(this)->onNewSurface(info); } +sk_sp SkSurface::makeSurface(int width, int height) { + return this->makeSurface(this->imageInfo().makeWH(width, height)); +} + void SkSurface::draw(SkCanvas* canvas, SkScalar x, SkScalar y, const SkPaint* paint) { return asSB(this)->onDraw(canvas, x, y, paint); diff --git a/tools/skottie2movie.cpp b/tools/skottie2movie.cpp index a6567a7b7a..35c1352eae 100644 --- a/tools/skottie2movie.cpp +++ b/tools/skottie2movie.cpp @@ -21,6 +21,37 @@ static DEFINE_string2(assetPath, a, "", "path to assets needed for json file"); static DEFINE_int_2(fps, f, 25, "fps"); static DEFINE_bool2(verbose, v, false, "verbose mode"); static DEFINE_bool2(loop, l, false, "loop mode for profiling"); +static DEFINE_double(motion_angle, 180, "motion blur angle"); +static DEFINE_double(motion_slope, 0, "motion blur slope"); +static DEFINE_int(motion_samples, 1, "motion blur samples"); + +static void produce_frame(SkSurface* surf, skottie::Animation* anim, double frame_time) { + anim->seekFrameTime(frame_time); + surf->getCanvas()->clear(SK_ColorWHITE); + anim->render(surf->getCanvas()); +} + +static void produce_frame(SkSurface* surf, SkSurface* tmp, skottie::Animation* anim, + double frame_time, double frame_duration, double motion_radius, + int motion_samples) { + double samples_duration = frame_duration * motion_radius * 2; + double dt = samples_duration / (motion_samples - 1); + double t = frame_time - samples_duration / 2; + + SkPaint paint; + paint.setAlphaf(1.0f / motion_samples); + paint.setBlendMode(SkBlendMode::kPlus); + surf->getCanvas()->clear(0); + + for (int i = 0; i < motion_samples; ++i) { + if (FLAGS_verbose) { + SkDebugf("time %g sample_time %g\n", frame_time, t); + } + produce_frame(tmp, anim, t); + t += dt; + tmp->draw(surf->getCanvas(), 0, 0, &paint); + } +} int main(int argc, char** argv) { CommandLineFlags::SetUsage("Converts skottie to a mp4"); @@ -31,6 +62,25 @@ int main(int argc, char** argv) { return -1; } + if (FLAGS_motion_angle < 0 || FLAGS_motion_angle > 360) { + SkDebugf("--motion_angle must be [0...360]\n"); + return -1; + } + if (FLAGS_motion_slope < -1 || FLAGS_motion_slope > 1) { + SkDebugf("--motion_slope must be [-1...1]\n"); + return -1; + } + if (FLAGS_motion_samples < 1) { + SkDebugf("--motion_samples must be >= 1\n"); + return -1; + } + + // map angle=180 to radius=1/4 (of a frame duration) + double motion_radius = FLAGS_motion_angle * 0.25 / 180.0; + if (FLAGS_motion_samples == 1) { + motion_radius = 0; // no blur if we're only 1 sample + } + SkString assetPath; if (FLAGS_assetPath.count() > 0) { assetPath.set(FLAGS_assetPath[0]); @@ -56,53 +106,63 @@ int main(int argc, char** argv) { fps = 240; } + const int frames = SkScalarRoundToInt(duration * fps); + const double frame_duration = 1.0 / fps; + if (FLAGS_verbose) { - SkDebugf("size %dx%d duration %g\n", dim.width(), dim.height(), duration, fps); + SkDebugf("size %dx%d duration %g, fps %d, frame_duration %g\n", + dim.width(), dim.height(), duration, fps, frame_duration); } SkVideoEncoder encoder; - const int frames = SkScalarRoundToInt(duration * fps); - while (FLAGS_loop) { - double start = SkTime::GetSecs(); + sk_sp surf, tmp_surf; + sk_sp data; + + do { + double loop_start = SkTime::GetSecs(); + encoder.beginRecording(dim, fps); + // lazily allocate the surfaces + if (!surf) { + surf = SkSurface::MakeRaster(encoder.preferredInfo()); + tmp_surf = surf->makeSurface(surf->width(), surf->height()); + } + for (int i = 0; i <= frames; ++i) { - animation->seek(i * 1.0 / frames); // normalized time - animation->render(encoder.beginFrame()); - encoder.endFrame(); + double ts = i * 1.0 / fps; + if (FLAGS_verbose) { + SkDebugf("rendering frame %d ts %g\n", i, ts); + } + + double normal_time = i * 1.0 / frames; + double frame_time = normal_time * duration; + + if (motion_radius > 0) { + produce_frame(surf.get(), tmp_surf.get(), animation.get(), frame_time, frame_duration, + motion_radius, FLAGS_motion_samples); + } else { + produce_frame(surf.get(), animation.get(), frame_time); + } + + SkPixmap pm; + SkAssertResult(surf->peekPixels(&pm)); + encoder.addFrame(pm); } - (void)encoder.endRecording(); - if (FLAGS_verbose) { - double dur = SkTime::GetSecs() - start; - SkDebugf("%d frames, %g secs, %d fps\n", - frames, dur, (int)floor(frames / dur + 0.5)); + data = encoder.endRecording(); + + if (FLAGS_loop) { + double loop_dur = SkTime::GetSecs() - loop_start; + SkDebugf("recording secs %g, frames %d, recording fps %d\n", + loop_dur, frames, (int)(frames / loop_dur)); } - } - - encoder.beginRecording(dim, fps); - - auto surf = SkSurface::MakeRaster(encoder.preferredInfo()); - - for (int i = 0; i <= frames; ++i) { - double ts = i * 1.0 / fps; - if (FLAGS_verbose) { - SkDebugf("rendering frame %d ts %g\n", i, ts); - } - animation->seek(i * 1.0 / frames); // normalized time - surf->getCanvas()->clear(SK_ColorWHITE); - animation->render(surf->getCanvas()); - - SkPixmap pm; - SkAssertResult(surf->peekPixels(&pm)); - encoder.addFrame(pm); - } + } while (FLAGS_loop); if (FLAGS_output.count() == 0) { SkDebugf("missing -o output_file.mp4 argument\n"); return 0; } - auto data = encoder.endRecording(); SkFILEWStream ostream(FLAGS_output[0]); if (!ostream.isValid()) { SkDebugf("Can't create output file %s\n", FLAGS_output[0]);