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