text: /*
   * Copyright 2019 Google Inc.
   *
   * Use of this source code is governed by a BSD-style license that can be
   * found in the LICENSE file.
   */
  
  #include "modules/skottie/src/effects/MotionBlurEffect.h"
  
  #include "include/core/SkCanvas.h"
  #include "modules/sksg/include/SkSGInvalidationController.h"
  
  namespace skottie {
  namespace internal {
  
  sk_sp<MotionBlurEffect> MotionBlurEffect::Make(sk_sp<sksg::Animator> animator,
                                                 sk_sp<sksg::RenderNode> child,
                                                 size_t samples_per_frame,
                                                 float shutter_angle, float shutter_phase) {
      if (!samples_per_frame || shutter_angle <= 0) {
          return nullptr;
      }
  
      // shutter_angle is [   0 .. 720], mapped to [ 0 .. 2] (frame space)
      // shutter_phase is [-360 .. 360], mapped to [-1 .. 1] (frame space)
      const auto samples_duration = shutter_angle / 360,
                            phase = shutter_phase / 360,
                               dt = samples_duration / (samples_per_frame - 1);
  
      return sk_sp<MotionBlurEffect>(new MotionBlurEffect(std::move(animator),
                                                          std::move(child),
                                                          samples_per_frame,
                                                          phase, dt));
  }
  
  MotionBlurEffect::MotionBlurEffect(sk_sp<sksg::Animator> animator,
                                     sk_sp<sksg::RenderNode> child,
                                     size_t samples, float phase, float dt)
      : INHERITED({std::move(child)})
      , fAnimator(std::move(animator))
      , fSampleCount(samples)
      , fPhase(phase)
      , fDT(dt) {}
  
  const sksg::RenderNode* MotionBlurEffect::onNodeAt(const SkPoint&) const {
      return nullptr;
  }
  
  SkRect MotionBlurEffect::onRevalidate(sksg::InvalidationController*, const SkMatrix& ctm) {
      SkASSERT(this->children().size() == 1ul);
      const auto& child = this->children()[0];
  
      auto bounds = SkRect::MakeEmpty();
  
      // Use a local inval controller to suppress descendent invals during sampling
      // (superseded by our local inval bounds).
      sksg::InvalidationController ic;
  
      auto t = fT + fPhase;
  
      for (size_t i = 0; i < fSampleCount; ++i) {
          fAnimator->tick(t);
          t += fDT;
  
          if (!child->isVisible()) {
              continue;
          }
  
          bounds.join(child->revalidate(&ic, ctm));
      }
  
      return bounds;
  }
  
  void MotionBlurEffect::onRender(SkCanvas* canvas, const RenderContext* ctx) const {
      SkASSERT(this->children().size() == 1ul);
      const auto& child = this->children()[0];
  
      SkAutoCanvasRestore acr(canvas, false);
  
      // Accumulate in F16 for more precision.
      canvas->saveLayer(SkCanvas::SaveLayerRec(&this->bounds(), nullptr, SkCanvas::kF16ColorType));
  
      const auto frame_alpha = 1.0f / fSampleCount;
  
      // Depending on whether we can defer frame blending,
      // use a local (deferred) RenderContext or an explicit layer for frame/content rendering.
      ScopedRenderContext frame_ctx(canvas, ctx);
      SkPaint             frame_paint;
  
      const auto isolate_frames = ctx->fBlendMode != SkBlendMode::kSrcOver;
      if (isolate_frames) {
          frame_paint.setAlphaf(frame_alpha);
          frame_paint.setBlendMode(SkBlendMode::kPlus);
      } else {
          frame_ctx = frame_ctx.modulateOpacity(frame_alpha)
                               .modulateBlendMode(SkBlendMode::kPlus);
      }
  
      sksg::InvalidationController ic;
  
      auto t = fT + fPhase;
  
      for (size_t i = 0; i < fSampleCount; ++i) {
          fAnimator->tick(t);
          t += fDT;
  
          if (!child->isVisible()) {
              continue;
          }
  
          child->revalidate(&ic, canvas->getTotalMatrix());
  
          SkAutoCanvasRestore acr(canvas, false);
          if (isolate_frames) {
              canvas->saveLayer(nullptr, &frame_paint);
          }
  
          child->render(canvas, frame_ctx);
      }
  }
  
  } // namespace internal
  } // namespace skottie
  