| 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/Effects.h" | 
|---|
| 9 |  | 
|---|
| 10 | #include "include/core/SkCanvas.h" | 
|---|
| 11 | #include "include/core/SkPictureRecorder.h" | 
|---|
| 12 | #include "include/core/SkShader.h" | 
|---|
| 13 | #include "include/effects/SkGradientShader.h" | 
|---|
| 14 | #include "modules/skottie/src/Adapter.h" | 
|---|
| 15 | #include "modules/skottie/src/SkottieValue.h" | 
|---|
| 16 | #include "modules/sksg/include/SkSGRenderNode.h" | 
|---|
| 17 | #include "src/utils/SkJSON.h" | 
|---|
| 18 |  | 
|---|
| 19 | #include <cmath> | 
|---|
| 20 |  | 
|---|
| 21 | namespace skottie { | 
|---|
| 22 | namespace internal { | 
|---|
| 23 |  | 
|---|
| 24 | namespace  { | 
|---|
| 25 |  | 
|---|
| 26 | // AE motion tile effect semantics | 
|---|
| 27 | // (https://helpx.adobe.com/after-effects/using/stylize-effects.html#motion_tile_effect): | 
|---|
| 28 | // | 
|---|
| 29 | //   - the full content of the layer is mapped to a tile: tile_center, tile_width, tile_height | 
|---|
| 30 | // | 
|---|
| 31 | //   - tiles are repeated in both dimensions to fill the output area: output_width, output_height | 
|---|
| 32 | // | 
|---|
| 33 | //   - tiling mode is either kRepeat (default) or kMirror (when mirror_edges == true) | 
|---|
| 34 | // | 
|---|
| 35 | //   - for a non-zero phase, alternating vertical columns (every other column) are offset by | 
|---|
| 36 | //     the specified amount | 
|---|
| 37 | // | 
|---|
| 38 | //   - when horizontal_phase is true, the phase is applied to horizontal rows instead of columns | 
|---|
| 39 | // | 
|---|
| 40 | class TileRenderNode final : public sksg::CustomRenderNode { | 
|---|
| 41 | public: | 
|---|
| 42 | TileRenderNode(const SkSize& size, sk_sp<sksg::RenderNode> layer) | 
|---|
| 43 | : INHERITED({std::move(layer)}) | 
|---|
| 44 | , fLayerSize(size) {} | 
|---|
| 45 |  | 
|---|
| 46 | SG_ATTRIBUTE(TileCenter     , SkPoint , fTileCenter     ) | 
|---|
| 47 | SG_ATTRIBUTE(TileWidth      , SkScalar, fTileW          ) | 
|---|
| 48 | SG_ATTRIBUTE(TileHeight     , SkScalar, fTileH          ) | 
|---|
| 49 | SG_ATTRIBUTE(OutputWidth    , SkScalar, fOutputW        ) | 
|---|
| 50 | SG_ATTRIBUTE(OutputHeight   , SkScalar, fOutputH        ) | 
|---|
| 51 | SG_ATTRIBUTE(Phase          , SkScalar, fPhase          ) | 
|---|
| 52 | SG_ATTRIBUTE(MirrorEdges    , bool    , fMirrorEdges    ) | 
|---|
| 53 | SG_ATTRIBUTE(HorizontalPhase, bool    , fHorizontalPhase) | 
|---|
| 54 |  | 
|---|
| 55 | protected: | 
|---|
| 56 | const RenderNode* onNodeAt(const SkPoint&) const override { return nullptr; } // no hit-testing | 
|---|
| 57 |  | 
|---|
| 58 | SkRect onRevalidate(sksg::InvalidationController* ic, const SkMatrix& ctm) override { | 
|---|
| 59 | // Re-record the layer picture if needed. | 
|---|
| 60 | if (!fLayerPicture || this->hasChildrenInval()) { | 
|---|
| 61 | SkASSERT(this->children().size() == 1ul); | 
|---|
| 62 | const auto& layer = this->children()[0]; | 
|---|
| 63 |  | 
|---|
| 64 | layer->revalidate(ic, ctm); | 
|---|
| 65 |  | 
|---|
| 66 | SkPictureRecorder recorder; | 
|---|
| 67 | layer->render(recorder.beginRecording(fLayerSize.width(), fLayerSize.height())); | 
|---|
| 68 | fLayerPicture = recorder.finishRecordingAsPicture(); | 
|---|
| 69 | } | 
|---|
| 70 |  | 
|---|
| 71 | // tileW and tileH use layer size percentage units. | 
|---|
| 72 | const auto tileW = SkTPin(fTileW, 0.0f, 100.0f) * 0.01f * fLayerSize.width(), | 
|---|
| 73 | tileH = SkTPin(fTileH, 0.0f, 100.0f) * 0.01f * fLayerSize.height(); | 
|---|
| 74 | const auto tile_size = SkSize::Make(std::max(tileW, 1.0f), | 
|---|
| 75 | std::max(tileH, 1.0f)); | 
|---|
| 76 | const auto tile  = SkRect::MakeXYWH(fTileCenter.fX - 0.5f * tile_size.width(), | 
|---|
| 77 | fTileCenter.fY - 0.5f * tile_size.height(), | 
|---|
| 78 | tile_size.width(), | 
|---|
| 79 | tile_size.height()); | 
|---|
| 80 |  | 
|---|
| 81 | const auto layerShaderMatrix = SkMatrix::MakeRectToRect( | 
|---|
| 82 | SkRect::MakeWH(fLayerSize.width(), fLayerSize.height()), | 
|---|
| 83 | tile, SkMatrix::kFill_ScaleToFit); | 
|---|
| 84 |  | 
|---|
| 85 | const auto tm = fMirrorEdges ? SkTileMode::kMirror : SkTileMode::kRepeat; | 
|---|
| 86 | auto layer_shader = fLayerPicture->makeShader(tm, tm, &layerShaderMatrix); | 
|---|
| 87 |  | 
|---|
| 88 | if (fPhase) { | 
|---|
| 89 | // To implement AE phase semantics, we construct a mask shader for the pass-through | 
|---|
| 90 | // rows/columns.  We then draw the layer content through this mask, and then again | 
|---|
| 91 | // through the inverse mask with a phase shift. | 
|---|
| 92 | const auto phase_vec = fHorizontalPhase | 
|---|
| 93 | ? SkVector::Make(tile.width(), 0) | 
|---|
| 94 | : SkVector::Make(0, tile.height()); | 
|---|
| 95 | const auto phase_shift = SkVector::Make(phase_vec.fX / layerShaderMatrix.getScaleX(), | 
|---|
| 96 | phase_vec.fY / layerShaderMatrix.getScaleY()) | 
|---|
| 97 | * std::fmod(fPhase * (1/360.0f), 1); | 
|---|
| 98 | const auto phase_shader_matrix = SkMatrix::MakeTrans(phase_shift.x(), phase_shift.y()); | 
|---|
| 99 |  | 
|---|
| 100 | // The mask is generated using a step gradient shader, spanning 2 x tile width/height, | 
|---|
| 101 | // and perpendicular to the phase vector. | 
|---|
| 102 | static constexpr SkColor colors[] = { 0xffffffff, 0x00000000 }; | 
|---|
| 103 | static constexpr SkScalar   pos[] = {       0.5f,       0.5f }; | 
|---|
| 104 |  | 
|---|
| 105 | const SkPoint pts[] = {{ tile.x(), tile.y() }, | 
|---|
| 106 | { tile.x() + 2 * (tile.width()  - phase_vec.fX), | 
|---|
| 107 | tile.y() + 2 * (tile.height() - phase_vec.fY) }}; | 
|---|
| 108 |  | 
|---|
| 109 | auto mask_shader = SkGradientShader::MakeLinear(pts, colors, pos, | 
|---|
| 110 | SK_ARRAY_COUNT(colors), | 
|---|
| 111 | SkTileMode::kRepeat); | 
|---|
| 112 |  | 
|---|
| 113 | // First drawing pass: in-place masked layer content. | 
|---|
| 114 | fMainPassShader  = SkShaders::Blend(SkBlendMode::kSrcIn , mask_shader, layer_shader); | 
|---|
| 115 | // Second pass: phased-shifted layer content, with an inverse mask. | 
|---|
| 116 | fPhasePassShader = SkShaders::Blend(SkBlendMode::kSrcOut, mask_shader, layer_shader) | 
|---|
| 117 | ->makeWithLocalMatrix(phase_shader_matrix); | 
|---|
| 118 | } else { | 
|---|
| 119 | fMainPassShader  = std::move(layer_shader); | 
|---|
| 120 | fPhasePassShader = nullptr; | 
|---|
| 121 | } | 
|---|
| 122 |  | 
|---|
| 123 | // outputW and outputH also use layer size percentage units. | 
|---|
| 124 | const auto outputW = fOutputW * 0.01f * fLayerSize.width(), | 
|---|
| 125 | outputH = fOutputH * 0.01f * fLayerSize.height(); | 
|---|
| 126 |  | 
|---|
| 127 | return SkRect::MakeXYWH((fLayerSize.width()  - outputW) * 0.5f, | 
|---|
| 128 | (fLayerSize.height() - outputH) * 0.5f, | 
|---|
| 129 | outputW, outputH); | 
|---|
| 130 | } | 
|---|
| 131 |  | 
|---|
| 132 | void onRender(SkCanvas* canvas, const RenderContext* ctx) const override { | 
|---|
| 133 | // AE allow one of the tile dimensions to collapse, but not both. | 
|---|
| 134 | if (this->bounds().isEmpty() || (fTileW <= 0 && fTileH <= 0)) { | 
|---|
| 135 | return; | 
|---|
| 136 | } | 
|---|
| 137 |  | 
|---|
| 138 | SkPaint paint; | 
|---|
| 139 | paint.setAntiAlias(true); | 
|---|
| 140 |  | 
|---|
| 141 | paint.setShader(fMainPassShader); | 
|---|
| 142 | canvas->drawRect(this->bounds(), paint); | 
|---|
| 143 |  | 
|---|
| 144 | if (fPhasePassShader) { | 
|---|
| 145 | paint.setShader(fPhasePassShader); | 
|---|
| 146 | canvas->drawRect(this->bounds(), paint); | 
|---|
| 147 | } | 
|---|
| 148 | } | 
|---|
| 149 |  | 
|---|
| 150 | private: | 
|---|
| 151 | const SkSize fLayerSize; | 
|---|
| 152 |  | 
|---|
| 153 | SkPoint  fTileCenter      = { 0, 0 }; | 
|---|
| 154 | SkScalar fTileW           = 1, | 
|---|
| 155 | fTileH           = 1, | 
|---|
| 156 | fOutputW         = 1, | 
|---|
| 157 | fOutputH         = 1, | 
|---|
| 158 | fPhase           = 0; | 
|---|
| 159 | bool     fMirrorEdges     = false; | 
|---|
| 160 | bool     fHorizontalPhase = false; | 
|---|
| 161 |  | 
|---|
| 162 | // These are computed/cached on revalidation. | 
|---|
| 163 | sk_sp<SkPicture> fLayerPicture;      // cached picture for layer content | 
|---|
| 164 | sk_sp<SkShader>  fMainPassShader,    // shader for the main tile(s) | 
|---|
| 165 | fPhasePassShader;   // shader for the phased tile(s) | 
|---|
| 166 |  | 
|---|
| 167 | using INHERITED = sksg::CustomRenderNode; | 
|---|
| 168 | }; | 
|---|
| 169 |  | 
|---|
| 170 | class MotionTileAdapter final : public DiscardableAdapterBase<MotionTileAdapter, TileRenderNode> { | 
|---|
| 171 | public: | 
|---|
| 172 | MotionTileAdapter(const skjson::ArrayValue& jprops, | 
|---|
| 173 | sk_sp<sksg::RenderNode> layer, | 
|---|
| 174 | const AnimationBuilder& abuilder, | 
|---|
| 175 | const SkSize& layer_size) | 
|---|
| 176 | : INHERITED(sk_make_sp<TileRenderNode>(layer_size, std::move(layer))) { | 
|---|
| 177 |  | 
|---|
| 178 | enum : size_t { | 
|---|
| 179 | kTileCenter_Index = 0, | 
|---|
| 180 | kTileWidth_Index = 1, | 
|---|
| 181 | kTileHeight_Index = 2, | 
|---|
| 182 | kOutputWidth_Index = 3, | 
|---|
| 183 | kOutputHeight_Index = 4, | 
|---|
| 184 | kMirrorEdges_Index = 5, | 
|---|
| 185 | kPhase_Index = 6, | 
|---|
| 186 | kHorizontalPhaseShift_Index = 7, | 
|---|
| 187 | }; | 
|---|
| 188 |  | 
|---|
| 189 | EffectBinder(jprops, abuilder, this) | 
|---|
| 190 | .bind(          kTileCenter_Index, fTileCenter     ) | 
|---|
| 191 | .bind(           kTileWidth_Index, fTileW          ) | 
|---|
| 192 | .bind(          kTileHeight_Index, fTileH          ) | 
|---|
| 193 | .bind(         kOutputWidth_Index, fOutputW        ) | 
|---|
| 194 | .bind(        kOutputHeight_Index, fOutputH        ) | 
|---|
| 195 | .bind(         kMirrorEdges_Index, fMirrorEdges    ) | 
|---|
| 196 | .bind(               kPhase_Index, fPhase          ) | 
|---|
| 197 | .bind(kHorizontalPhaseShift_Index, fHorizontalPhase); | 
|---|
| 198 | } | 
|---|
| 199 |  | 
|---|
| 200 | private: | 
|---|
| 201 | void onSync() override { | 
|---|
| 202 | const auto& tiler = this->node(); | 
|---|
| 203 |  | 
|---|
| 204 | tiler->setTileCenter({fTileCenter.x, fTileCenter.y}); | 
|---|
| 205 | tiler->setTileWidth (fTileW); | 
|---|
| 206 | tiler->setTileHeight(fTileH); | 
|---|
| 207 | tiler->setOutputWidth (fOutputW); | 
|---|
| 208 | tiler->setOutputHeight(fOutputH); | 
|---|
| 209 | tiler->setPhase(fPhase); | 
|---|
| 210 | tiler->setMirrorEdges(SkToBool(fMirrorEdges)); | 
|---|
| 211 | tiler->setHorizontalPhase(SkToBool(fHorizontalPhase)); | 
|---|
| 212 | } | 
|---|
| 213 |  | 
|---|
| 214 | Vec2Value   fTileCenter      = {0,0}; | 
|---|
| 215 | ScalarValue fTileW           = 1, | 
|---|
| 216 | fTileH           = 1, | 
|---|
| 217 | fOutputW         = 1, | 
|---|
| 218 | fOutputH         = 1, | 
|---|
| 219 | fMirrorEdges     = 0, | 
|---|
| 220 | fPhase           = 0, | 
|---|
| 221 | fHorizontalPhase = 0; | 
|---|
| 222 |  | 
|---|
| 223 | using INHERITED = DiscardableAdapterBase<MotionTileAdapter, TileRenderNode>; | 
|---|
| 224 | }; | 
|---|
| 225 |  | 
|---|
| 226 | } // anonymous ns | 
|---|
| 227 |  | 
|---|
| 228 | sk_sp<sksg::RenderNode> EffectBuilder::attachMotionTileEffect(const skjson::ArrayValue& jprops, | 
|---|
| 229 | sk_sp<sksg::RenderNode> layer) const { | 
|---|
| 230 | return fBuilder->attachDiscardableAdapter<MotionTileAdapter>(jprops, | 
|---|
| 231 | std::move(layer), | 
|---|
| 232 | *fBuilder, | 
|---|
| 233 | fLayerSize); | 
|---|
| 234 | } | 
|---|
| 235 |  | 
|---|
| 236 | } // namespace internal | 
|---|
| 237 | } // namespace skottie | 
|---|
| 238 |  | 
|---|