• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/SkottiePriv.h"
9 
10 #include "include/core/SkImage.h"
11 #include "modules/skottie/src/SkottieJson.h"
12 #include "modules/sksg/include/SkSGImage.h"
13 #include "modules/sksg/include/SkSGTransform.h"
14 
15 namespace skottie {
16 namespace internal {
17 
18 namespace  {
19 
image_matrix(const ImageAsset::FrameData & frame_data,const SkISize & dest_size)20 SkMatrix image_matrix(const ImageAsset::FrameData& frame_data, const SkISize& dest_size) {
21     if (!frame_data.image) {
22         return SkMatrix::I();
23     }
24 
25     return frame_data.matrix * SkMatrix::RectToRect(SkRect::Make(frame_data.image->bounds()),
26                                                     SkRect::Make(dest_size),
27                                                     SkMatrix::kCenter_ScaleToFit);
28 }
29 
30 class FootageAnimator final : public Animator {
31 public:
FootageAnimator(sk_sp<ImageAsset> asset,sk_sp<sksg::Image> image_node,sk_sp<sksg::Matrix<SkMatrix>> image_transform_node,const SkISize & asset_size,float time_bias,float time_scale)32     FootageAnimator(sk_sp<ImageAsset> asset,
33                     sk_sp<sksg::Image> image_node,
34                     sk_sp<sksg::Matrix<SkMatrix>> image_transform_node,
35                     const SkISize& asset_size,
36                     float time_bias, float time_scale)
37         : fAsset(std::move(asset))
38         , fImageNode(std::move(image_node))
39         , fImageTransformNode(std::move(image_transform_node))
40         , fAssetSize(asset_size)
41         , fTimeBias(time_bias)
42         , fTimeScale(time_scale)
43         , fIsMultiframe(fAsset->isMultiFrame()) {}
44 
onSeek(float t)45     StateChanged onSeek(float t) override {
46         if (!fIsMultiframe && fImageNode->getImage()) {
47             // Single frame already resolved.
48             return false;
49         }
50 
51         auto frame_data = fAsset->getFrameData((t + fTimeBias) * fTimeScale);
52         const auto m = image_matrix(frame_data, fAssetSize);
53         if (frame_data.image    != fImageNode->getImage() ||
54             frame_data.sampling != fImageNode->getSamplingOptions() ||
55             m                   != fImageTransformNode->getMatrix()) {
56 
57             fImageNode->setImage(std::move(frame_data.image));
58             fImageNode->setSamplingOptions(frame_data.sampling);
59             fImageTransformNode->setMatrix(m);
60             return true;
61         }
62 
63         return false;
64     }
65 
66 private:
67     const sk_sp<ImageAsset>             fAsset;
68     const sk_sp<sksg::Image>            fImageNode;
69     const sk_sp<sksg::Matrix<SkMatrix>> fImageTransformNode;
70     const SkISize                       fAssetSize;
71     const float                         fTimeBias,
72                                         fTimeScale;
73     const bool                          fIsMultiframe;
74 };
75 
76 } // namespace
77 
78 const AnimationBuilder::FootageAssetInfo*
loadFootageAsset(const skjson::ObjectValue & jimage) const79 AnimationBuilder::loadFootageAsset(const skjson::ObjectValue& jimage) const {
80     const skjson::StringValue* name = jimage["p"];
81     const skjson::StringValue* path = jimage["u"];
82     const skjson::StringValue* id   = jimage["id"];
83     if (!name || !path || !id) {
84         return nullptr;
85     }
86 
87     const SkString res_id(id->begin());
88     if (auto* cached_info = fImageAssetCache.find(res_id)) {
89         return cached_info;
90     }
91 
92     auto asset = fResourceProvider->loadImageAsset(path->begin(), name->begin(), id->begin());
93     if (!asset) {
94         this->log(Logger::Level::kError, nullptr, "Could not load image asset: %s/%s (id: '%s').",
95                   path->begin(), name->begin(), id->begin());
96         return nullptr;
97     }
98 
99     const auto size = SkISize::Make(ParseDefault<int>(jimage["w"], 0),
100                                     ParseDefault<int>(jimage["h"], 0));
101     return fImageAssetCache.set(res_id, { std::move(asset), size });
102 }
103 
attachFootageAsset(const skjson::ObjectValue & jimage,LayerInfo * layer_info) const104 sk_sp<sksg::RenderNode> AnimationBuilder::attachFootageAsset(const skjson::ObjectValue& jimage,
105                                                              LayerInfo* layer_info) const {
106     const auto* asset_info = this->loadFootageAsset(jimage);
107     if (!asset_info) {
108         return nullptr;
109     }
110     SkASSERT(asset_info->fAsset);
111 
112     auto image_node = sksg::Image::Make(nullptr);
113 
114     // Optional image transform (mapping the intrinsic image size to declared asset size).
115     sk_sp<sksg::Matrix<SkMatrix>> image_transform;
116 
117     const auto requires_animator = (fFlags & Animation::Builder::kDeferImageLoading)
118                                     || asset_info->fAsset->isMultiFrame();
119     if (requires_animator) {
120         // We don't know the intrinsic image size yet (plus, in the general case,
121         // the size may change from frame to frame) -> we always prepare a scaling transform.
122         image_transform = sksg::Matrix<SkMatrix>::Make(SkMatrix::I());
123         fCurrentAnimatorScope->push_back(sk_make_sp<FootageAnimator>(asset_info->fAsset,
124                                                                      image_node,
125                                                                      image_transform,
126                                                                      asset_info->fSize,
127                                                                      -layer_info->fInPoint,
128                                                                      1 / fFrameRate));
129     } else {
130         // No animator needed, resolve the (only) frame upfront.
131         auto frame_data = asset_info->fAsset->getFrameData(0);
132         if (!frame_data.image) {
133             this->log(Logger::Level::kError, nullptr, "Could not load single-frame image asset.");
134             return nullptr;
135         }
136 
137         const auto m = image_matrix(frame_data, asset_info->fSize);
138         if (!m.isIdentity()) {
139             image_transform = sksg::Matrix<SkMatrix>::Make(m);
140         }
141 
142         image_node->setImage(std::move(frame_data.image));
143         image_node->setSamplingOptions(frame_data.sampling);
144     }
145 
146     // Image layers are sized explicitly.
147     layer_info->fSize = SkSize::Make(asset_info->fSize);
148 
149     if (!image_transform) {
150         // No resize needed.
151         return std::move(image_node);
152     }
153 
154     return sksg::TransformEffect::Make(std::move(image_node), std::move(image_transform));
155 }
156 
attachFootageLayer(const skjson::ObjectValue & jlayer,LayerInfo * layer_info) const157 sk_sp<sksg::RenderNode> AnimationBuilder::attachFootageLayer(const skjson::ObjectValue& jlayer,
158                                                              LayerInfo* layer_info) const {
159     const ScopedAssetRef footage_asset(this, jlayer);
160 
161     return footage_asset
162         ? this->attachFootageAsset(*footage_asset, layer_info)
163         : nullptr;
164 }
165 
166 } // namespace internal
167 } // namespace skottie
168