1 /*
2 * Copyright 2019 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 "modules/skottie/src/effects/MotionBlurEffect.h"
9
10 #include "include/core/SkCanvas.h"
11 #include "modules/sksg/include/SkSGInvalidationController.h"
12
13 namespace skottie {
14 namespace internal {
15
Make(sk_sp<sksg::Animator> animator,sk_sp<sksg::RenderNode> child,size_t samples_per_frame,float shutter_angle,float shutter_phase)16 sk_sp<MotionBlurEffect> MotionBlurEffect::Make(sk_sp<sksg::Animator> animator,
17 sk_sp<sksg::RenderNode> child,
18 size_t samples_per_frame,
19 float shutter_angle, float shutter_phase) {
20 if (!samples_per_frame || shutter_angle <= 0) {
21 return nullptr;
22 }
23
24 // shutter_angle is [ 0 .. 720], mapped to [ 0 .. 2] (frame space)
25 // shutter_phase is [-360 .. 360], mapped to [-1 .. 1] (frame space)
26 const auto samples_duration = shutter_angle / 360,
27 phase = shutter_phase / 360,
28 dt = samples_duration / (samples_per_frame - 1);
29
30 return sk_sp<MotionBlurEffect>(new MotionBlurEffect(std::move(animator),
31 std::move(child),
32 samples_per_frame,
33 phase, dt));
34 }
35
MotionBlurEffect(sk_sp<sksg::Animator> animator,sk_sp<sksg::RenderNode> child,size_t samples,float phase,float dt)36 MotionBlurEffect::MotionBlurEffect(sk_sp<sksg::Animator> animator,
37 sk_sp<sksg::RenderNode> child,
38 size_t samples, float phase, float dt)
39 : INHERITED({std::move(child)})
40 , fAnimator(std::move(animator))
41 , fSampleCount(samples)
42 , fPhase(phase)
43 , fDT(dt) {}
44
onNodeAt(const SkPoint &) const45 const sksg::RenderNode* MotionBlurEffect::onNodeAt(const SkPoint&) const {
46 return nullptr;
47 }
48
onRevalidate(sksg::InvalidationController *,const SkMatrix & ctm)49 SkRect MotionBlurEffect::onRevalidate(sksg::InvalidationController*, const SkMatrix& ctm) {
50 SkASSERT(this->children().size() == 1ul);
51 const auto& child = this->children()[0];
52
53 auto bounds = SkRect::MakeEmpty();
54
55 // Use a local inval controller to suppress descendent invals during sampling
56 // (superseded by our local inval bounds).
57 sksg::InvalidationController ic;
58
59 auto t = fT + fPhase;
60
61 for (size_t i = 0; i < fSampleCount; ++i) {
62 fAnimator->tick(t);
63 t += fDT;
64
65 if (!child->isVisible()) {
66 continue;
67 }
68
69 bounds.join(child->revalidate(&ic, ctm));
70 }
71
72 return bounds;
73 }
74
onRender(SkCanvas * canvas,const RenderContext * ctx) const75 void MotionBlurEffect::onRender(SkCanvas* canvas, const RenderContext* ctx) const {
76 SkASSERT(this->children().size() == 1ul);
77 const auto& child = this->children()[0];
78
79 SkAutoCanvasRestore acr(canvas, false);
80
81 // Accumulate in F16 for more precision.
82 canvas->saveLayer(SkCanvas::SaveLayerRec(&this->bounds(), nullptr, SkCanvas::kF16ColorType));
83
84 const auto frame_alpha = 1.0f / fSampleCount;
85
86 // Depending on whether we can defer frame blending,
87 // use a local (deferred) RenderContext or an explicit layer for frame/content rendering.
88 ScopedRenderContext frame_ctx(canvas, ctx);
89 SkPaint frame_paint;
90
91 const auto isolate_frames = ctx->fBlendMode != SkBlendMode::kSrcOver;
92 if (isolate_frames) {
93 frame_paint.setAlphaf(frame_alpha);
94 frame_paint.setBlendMode(SkBlendMode::kPlus);
95 } else {
96 frame_ctx = frame_ctx.modulateOpacity(frame_alpha)
97 .modulateBlendMode(SkBlendMode::kPlus);
98 }
99
100 sksg::InvalidationController ic;
101
102 auto t = fT + fPhase;
103
104 for (size_t i = 0; i < fSampleCount; ++i) {
105 fAnimator->tick(t);
106 t += fDT;
107
108 if (!child->isVisible()) {
109 continue;
110 }
111
112 child->revalidate(&ic, canvas->getTotalMatrix());
113
114 SkAutoCanvasRestore acr(canvas, false);
115 if (isolate_frames) {
116 canvas->saveLayer(nullptr, &frame_paint);
117 }
118
119 child->render(canvas, frame_ctx);
120 }
121 }
122
123 } // namespace internal
124 } // namespace skottie
125