1 /*
2 * Copyright (c) 2022-2023 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "core/components_ng/pattern/image_animator/image_animator_pattern.h"
17 #include <algorithm>
18 #include <string>
19
20 #include "base/log/ace_trace.h"
21 #include "core/components_ng/pattern/image/image_event_hub.h"
22 #include "core/components_ng/pattern/image/image_layout_property.h"
23 #include "core/components_ng/pattern/image/image_pattern.h"
24 #include "core/components_ng/property/calc_length.h"
25 #include "core/components_ng/property/property.h"
26 #include "core/pipeline_ng/pipeline_context.h"
27
28 namespace OHOS::Ace::NG {
29
30 namespace {
31
32 constexpr uint32_t DEFAULT_DURATION = 1000; // ms
33 constexpr uint32_t CRITICAL_TIME = 50; // ms. If show time of image is less than this, use more cacheImages.
34
35 } // namespace
36
ImageAnimatorPattern()37 ImageAnimatorPattern::ImageAnimatorPattern()
38 {
39 animator_ = CREATE_ANIMATOR(PipelineContext::GetCurrentContext());
40 animator_->SetFillMode(FillMode::FORWARDS);
41 animator_->SetDuration(DEFAULT_DURATION);
42 }
43
CreatePictureAnimation(int32_t size)44 RefPtr<PictureAnimation<int32_t>> ImageAnimatorPattern::CreatePictureAnimation(int32_t size)
45 {
46 auto pictureAnimation = MakeRefPtr<PictureAnimation<int32_t>>();
47
48 if (durationTotal_ > 0) {
49 for (int32_t index = 0; index < size; ++index) {
50 pictureAnimation->AddPicture(images_[index].duration / static_cast<float>(durationTotal_), index);
51 }
52 animator_->SetDuration(durationTotal_);
53 } else {
54 for (int32_t index = 0; index < size; ++index) {
55 pictureAnimation->AddPicture(NORMALIZED_DURATION_MAX / static_cast<float>(size), index);
56 }
57 }
58
59 pictureAnimation->AddListener([weak = WeakClaim(this)](int32_t index) {
60 auto imageAnimator = weak.Upgrade();
61 CHECK_NULL_VOID(imageAnimator);
62 imageAnimator->SetShowingIndex(index);
63 });
64 return pictureAnimation;
65 }
66
SetShowingIndex(int32_t index)67 void ImageAnimatorPattern::SetShowingIndex(int32_t index)
68 {
69 auto host = GetHost();
70 CHECK_NULL_VOID(host);
71 auto imageFrameNode = AceType::DynamicCast<FrameNode>(host->GetChildren().front());
72 CHECK_NULL_VOID(imageFrameNode);
73 auto imageLayoutProperty = imageFrameNode->GetLayoutProperty<ImageLayoutProperty>();
74 CHECK_NULL_VOID(imageLayoutProperty);
75 if (index >= static_cast<int32_t>(images_.size())) {
76 LOGW("ImageAnimator update index error, index: %{public}d, size: %{public}zu", index, images_.size());
77 return;
78 }
79 nowImageIndex_ = index;
80 auto cacheImageIter = FindCacheImageNode(images_[index].src);
81 if (IsShowingSrc(imageFrameNode, images_[index].src)) {
82 ACE_SCOPED_TRACE("ImageAnimator same src %s, index %d", images_[index].src.c_str(), index);
83 UpdateShowingImageInfo(imageFrameNode, index);
84 } else if (cacheImageIter == cacheImages_.end()) {
85 ACE_SCOPED_TRACE("ImageAnimator no cache found, src %s, index %d", images_[index].src.c_str(), index);
86 UpdateShowingImageInfo(imageFrameNode, index);
87 } else if (cacheImageIter->isLoaded) {
88 ACE_SCOPED_TRACE("ImageAnimator useCache src %s, index %d", images_[index].src.c_str(), index);
89 auto cacheImageNode = cacheImageIter->imageNode;
90 host->RemoveChild(imageFrameNode);
91 host->AddChild(cacheImageNode, DEFAULT_NODE_SLOT, true);
92 host->RebuildRenderContextTree();
93 cacheImages_.erase(cacheImageIter);
94 cacheImages_.emplace_back(CacheImageStruct(imageFrameNode));
95 UpdateShowingImageInfo(cacheImageNode, index);
96 } else {
97 // wait for cache image loading
98 ACE_SCOPED_TRACE("ImageAnimator waitForCache src %s, index %d", images_[index].src.c_str(), index);
99 return;
100 }
101 // update cache images
102 CHECK_NULL_VOID_NOLOG(cacheImages_.size());
103 int32_t nextIndex = GetNextIndex(index);
104 for (auto& cacheImage : cacheImages_) {
105 UpdateCacheImageInfo(cacheImage, nextIndex);
106 nextIndex = GetNextIndex(nextIndex);
107 }
108 }
109
UpdateShowingImageInfo(const RefPtr<FrameNode> & imageFrameNode,int32_t index)110 void ImageAnimatorPattern::UpdateShowingImageInfo(const RefPtr<FrameNode>& imageFrameNode, int32_t index)
111 {
112 auto imageLayoutProperty = imageFrameNode->GetLayoutProperty<ImageLayoutProperty>();
113 CHECK_NULL_VOID(imageLayoutProperty);
114 imageLayoutProperty->UpdateImageSourceInfo(ImageSourceInfo(images_[index].src));
115 MarginProperty margin;
116 if (!fixedSize_) {
117 margin.left = CalcLength(images_[index].left);
118 margin.top = CalcLength(images_[index].top);
119 imageLayoutProperty->UpdateMargin(margin);
120 CalcSize realSize = { CalcLength(images_[index].width), CalcLength(images_[index].height) };
121 imageLayoutProperty->UpdateUserDefinedIdealSize(realSize);
122 imageLayoutProperty->UpdateMeasureType(MeasureType::MATCH_CONTENT);
123 imageFrameNode->MarkModifyDone();
124 imageFrameNode->MarkDirtyNode(PROPERTY_UPDATE_MEASURE_SELF);
125 return;
126 }
127 margin.SetEdges(CalcLength(0.0));
128 imageLayoutProperty->UpdateMargin(margin);
129 imageLayoutProperty->ClearUserDefinedIdealSize(true, true);
130 imageLayoutProperty->UpdateMeasureType(MeasureType::MATCH_PARENT);
131 imageFrameNode->MarkModifyDone();
132 imageFrameNode->MarkDirtyNode(PROPERTY_UPDATE_MEASURE_SELF);
133 }
134
UpdateCacheImageInfo(CacheImageStruct & cacheImage,int32_t index)135 void ImageAnimatorPattern::UpdateCacheImageInfo(CacheImageStruct& cacheImage, int32_t index)
136 {
137 if (index >= static_cast<int32_t>(images_.size())) {
138 LOGW("PrepareImageInfo index error, index: %{public}d, size: %{public}zu", index, images_.size());
139 return;
140 }
141 auto imageLayoutProperty = cacheImage.imageNode->GetLayoutProperty<ImageLayoutProperty>();
142 auto preSrc =
143 imageLayoutProperty->HasImageSourceInfo() ? imageLayoutProperty->GetImageSourceInfoValue().GetSrc() : "";
144 if (preSrc != images_[index].src) {
145 // need to cache newImage
146 imageLayoutProperty->UpdateImageSourceInfo(ImageSourceInfo(images_[index].src));
147 cacheImage.index = index;
148 cacheImage.isLoaded = false;
149 }
150 if (!fixedSize_) {
151 CalcSize realSize = { CalcLength(images_[index].width), CalcLength(images_[index].height) };
152 imageLayoutProperty->UpdateUserDefinedIdealSize(realSize);
153 cacheImage.imageNode->MarkModifyDone();
154 return;
155 }
156 auto host = GetHost();
157 CHECK_NULL_VOID(host);
158 auto hostSize = host->GetGeometryNode()->GetPaddingSize();
159 if (!hostSize.IsPositive()) {
160 // if imageNode size is nonPositive, no pixelMap will be generated. Wait for size.
161 return;
162 }
163 imageLayoutProperty->UpdateUserDefinedIdealSize(
164 CalcSize(CalcLength(hostSize.Width()), CalcLength(hostSize.Height())));
165 cacheImage.imageNode->MarkModifyDone();
166 }
167
FindCacheImageNode(const std::string & src)168 std::list<ImageAnimatorPattern::CacheImageStruct>::iterator ImageAnimatorPattern::FindCacheImageNode(
169 const std::string& src)
170 {
171 for (auto iter = cacheImages_.begin(); iter != cacheImages_.end(); ++iter) {
172 if (IsShowingSrc(iter->imageNode, src)) {
173 return iter;
174 }
175 }
176 return cacheImages_.end();
177 }
178
GenerateCachedImages()179 void ImageAnimatorPattern::GenerateCachedImages()
180 {
181 CHECK_NULL_VOID(images_.size());
182 auto averageShowTime = animator_->GetDuration() / images_.size();
183 size_t cacheImageNum = averageShowTime >= CRITICAL_TIME ? 1 : 2;
184 cacheImageNum = std::min(images_.size() - 1, cacheImageNum);
185 if (cacheImages_.size() > cacheImageNum) {
186 cacheImages_.resize(cacheImageNum);
187 return;
188 }
189 while (cacheImages_.size() < cacheImageNum) {
190 auto imageNode = FrameNode::CreateFrameNode(V2::IMAGE_ETS_TAG, -1, AceType::MakeRefPtr<ImagePattern>());
191 auto imageLayoutProperty = imageNode->GetLayoutProperty();
192 imageLayoutProperty->UpdateMeasureType(MeasureType::MATCH_PARENT);
193 imageLayoutProperty->UpdateAlignment(Alignment::TOP_LEFT);
194 AddImageLoadSuccessEvent(imageNode);
195 cacheImages_.emplace_back(CacheImageStruct(imageNode));
196 }
197 }
198
OnDirtyLayoutWrapperSwap(const RefPtr<LayoutWrapper> & wrapper,const DirtySwapConfig & config)199 bool ImageAnimatorPattern::OnDirtyLayoutWrapperSwap(const RefPtr<LayoutWrapper>& wrapper, const DirtySwapConfig& config)
200 {
201 if (!isLayouted_) {
202 isLayouted_ = true;
203 if (fixedSize_ && images_.size()) {
204 int32_t nextIndex = GetNextIndex(nowImageIndex_);
205 for (auto& cacheImage : cacheImages_) {
206 UpdateCacheImageInfo(cacheImage, nextIndex);
207 nextIndex = GetNextIndex(nextIndex);
208 }
209 }
210 }
211 return false;
212 }
213
OnModifyDone()214 void ImageAnimatorPattern::OnModifyDone()
215 {
216 auto host = GetHost();
217 CHECK_NULL_VOID(host);
218 Pattern::OnModifyDone();
219 auto size = static_cast<int32_t>(images_.size());
220 if (size <= 0) {
221 LOGE("image size is less than 0.");
222 return;
223 }
224 GenerateCachedImages();
225 auto index = nowImageIndex_;
226 if ((status_ == Animator::Status::IDLE || status_ == Animator::Status::STOPPED) && firstUpdateEvent_) {
227 index = isReverse_ ? (size - 1) : 0;
228 }
229 SetShowingIndex(index);
230
231 if (imagesChangedFlag_) {
232 animator_->ClearInterpolators();
233 animator_->AddInterpolator(CreatePictureAnimation(size));
234 AdaptSelfSize();
235 imagesChangedFlag_ = false;
236 }
237 if (firstUpdateEvent_) {
238 UpdateEventCallback();
239 firstUpdateEvent_ = false;
240 auto imageFrameNode = AceType::DynamicCast<FrameNode>(host->GetChildren().front());
241 AddImageLoadSuccessEvent(imageFrameNode);
242 }
243
244 switch (status_) {
245 case Animator::Status::IDLE:
246 animator_->Cancel();
247 break;
248 case Animator::Status::PAUSED:
249 animator_->Pause();
250 break;
251 case Animator::Status::STOPPED:
252 animator_->Finish();
253 break;
254 default:
255 isReverse_ ? animator_->Backward() : animator_->Forward();
256 }
257 }
258
OnAttachToFrameNode()259 void ImageAnimatorPattern::OnAttachToFrameNode()
260 {
261 auto host = GetHost();
262 CHECK_NULL_VOID(host);
263 auto context = host->GetRenderContext();
264 CHECK_NULL_VOID(context);
265 context->SetClipToFrame(true);
266 }
267
UpdateEventCallback()268 void ImageAnimatorPattern::UpdateEventCallback()
269 {
270 auto eventHub = GetEventHub<ImageAnimatorEventHub>();
271 CHECK_NULL_VOID(eventHub);
272
273 animator_->ClearAllListeners();
274 auto startEvent = eventHub->GetStartEvent();
275 if (startEvent != nullptr) {
276 animator_->AddStartListener([startEvent] { startEvent(); });
277 }
278
279 auto stopEvent = eventHub->GetStopEvent();
280 if (stopEvent != nullptr) {
281 animator_->AddStopListener([stopEvent] { stopEvent(); });
282 }
283
284 auto pauseEvent = eventHub->GetPauseEvent();
285 if (pauseEvent != nullptr) {
286 animator_->AddPauseListener([pauseEvent] { pauseEvent(); });
287 }
288
289 auto repeatEvent = eventHub->GetRepeatEvent();
290 if (repeatEvent != nullptr) {
291 animator_->AddRepeatListener([repeatEvent] { repeatEvent(); });
292 }
293
294 auto cancelEvent = eventHub->GetCancelEvent();
295 if (cancelEvent != nullptr) {
296 animator_->AddIdleListener([cancelEvent] { cancelEvent(); });
297 }
298 }
299
ToJsonValue(std::unique_ptr<JsonValue> & json) const300 void ImageAnimatorPattern::ToJsonValue(std::unique_ptr<JsonValue>& json) const
301 {
302 Pattern::ToJsonValue(json);
303 static const char* STATUS_MODE[] = { "AnimationStatus.Initial", "AnimationStatus.Running", "AnimationStatus.Paused",
304 "AnimationStatus.Stopped" };
305 json->Put("state", STATUS_MODE[static_cast<int32_t>(status_)]);
306 json->Put("duration", std::to_string(animator_->GetDuration()).c_str());
307 json->Put("reverse", isReverse_ ? "true" : "false");
308 json->Put("fixedSize", fixedSize_ ? "true" : "false");
309 static const char* FILL_MODE[] = { "FillMode.None", "FillMode.Forwards", "FillMode.Backwards", "FillMode.Both" };
310 json->Put("fillMode", FILL_MODE[static_cast<int32_t>(animator_->GetFillMode())]);
311 json->Put("iterations", std::to_string(animator_->GetIteration()).c_str());
312 json->Put("images", ImagesToString().c_str());
313 }
314
ImagesToString() const315 std::string ImageAnimatorPattern::ImagesToString() const
316 {
317 auto imageArray = JsonUtil::CreateArray(true);
318 for (const auto& image : images_) {
319 auto item = JsonUtil::Create(true);
320 item->Put("src", image.src.c_str());
321 item->Put("left", image.left.ToString().c_str());
322 item->Put("top", image.top.ToString().c_str());
323 item->Put("width", image.width.ToString().c_str());
324 item->Put("height", image.height.ToString().c_str());
325 item->Put("duration", std::to_string(image.duration).c_str());
326 imageArray->Put(item);
327 }
328 return imageArray->ToString();
329 }
330
AdaptSelfSize()331 void ImageAnimatorPattern::AdaptSelfSize()
332 {
333 auto host = GetHost();
334 CHECK_NULL_VOID(host);
335 const auto& layoutProperty = host->GetLayoutProperty();
336 CHECK_NULL_VOID(layoutProperty);
337 if (layoutProperty->GetCalcLayoutConstraint() && layoutProperty->GetCalcLayoutConstraint()->selfIdealSize &&
338 layoutProperty->GetCalcLayoutConstraint()->selfIdealSize->IsValid()) {
339 return;
340 }
341 Dimension maxWidth;
342 Dimension maxHeight;
343 double maxWidthPx = 0.0;
344 double maxHeightPx = 0.0;
345 for (const auto& image : images_) {
346 if (image.width.Unit() != DimensionUnit::PERCENT) {
347 auto widthPx = image.width.ConvertToPx();
348 if (widthPx > maxWidthPx) {
349 maxWidthPx = widthPx;
350 maxWidth = image.width;
351 }
352 }
353 if (image.height.Unit() != DimensionUnit::PERCENT) {
354 auto heightPx = image.height.ConvertToPx();
355 if (heightPx > maxHeightPx) {
356 maxHeightPx = heightPx;
357 maxHeight = image.height;
358 }
359 }
360 }
361 if (!maxWidth.IsValid() || !maxHeight.IsValid()) {
362 return;
363 }
364 const auto& layoutConstraint = layoutProperty->GetCalcLayoutConstraint();
365 if (!layoutConstraint || !layoutConstraint->selfIdealSize) {
366 layoutProperty->UpdateUserDefinedIdealSize(CalcSize(CalcLength(maxWidth), CalcLength(maxHeight)));
367 return;
368 }
369 if (!layoutConstraint->selfIdealSize->Width()) {
370 layoutProperty->UpdateUserDefinedIdealSize(CalcSize(CalcLength(maxWidth), std::nullopt));
371 return;
372 }
373 layoutProperty->UpdateUserDefinedIdealSize(CalcSize(std::nullopt, CalcLength(maxHeight)));
374 }
375
GetNextIndex(int32_t preIndex)376 int32_t ImageAnimatorPattern::GetNextIndex(int32_t preIndex)
377 {
378 if (isReverse_) {
379 return preIndex == 0 ? (static_cast<int32_t>(images_.size()) - 1) : (preIndex - 1);
380 }
381 return (preIndex + 1) % static_cast<int32_t>(images_.size());
382 }
383
AddImageLoadSuccessEvent(const RefPtr<FrameNode> & imageFrameNode)384 void ImageAnimatorPattern::AddImageLoadSuccessEvent(const RefPtr<FrameNode>& imageFrameNode)
385 {
386 CHECK_NULL_VOID(imageFrameNode);
387 auto eventHub = imageFrameNode->GetEventHub<ImageEventHub>();
388 eventHub->SetOnComplete(
389 [weakImage = WeakPtr<FrameNode>(imageFrameNode), weak = WeakClaim(this)](const LoadImageSuccessEvent& info) {
390 if (info.GetLoadingStatus() != 1) {
391 // status 1 means load success. Only need loadSuccess event.
392 return;
393 }
394 auto pattern = weak.Upgrade();
395 CHECK_NULL_VOID(pattern);
396 auto cacheImageNode = weakImage.Upgrade();
397 CHECK_NULL_VOID(cacheImageNode);
398 auto imageAnimator = pattern->GetHost();
399 CHECK_NULL_VOID(imageAnimator);
400 auto cacheLayoutProperty = cacheImageNode->GetLayoutProperty<ImageLayoutProperty>();
401 auto cacheSrc = cacheLayoutProperty->GetImageSourceInfoValue(ImageSourceInfo()).GetSrc();
402 ACE_SCOPED_TRACE("ImageAnimator cache succeed. src %s", cacheSrc.c_str());
403 auto iter = std::find_if(pattern->cacheImages_.begin(), pattern->cacheImages_.end(),
404 [&cacheImageNode](const CacheImageStruct& other) { return other.imageNode == cacheImageNode; });
405 if (iter == pattern->cacheImages_.end()) {
406 return;
407 }
408 iter->isLoaded = true;
409 if (pattern->nowImageIndex_ >= static_cast<int32_t>(pattern->images_.size())) {
410 LOGW("ImageAnimator showImage index is invalid");
411 return;
412 }
413 if (pattern->nowImageIndex_ == iter->index &&
414 IsShowingSrc(cacheImageNode, pattern->images_[pattern->nowImageIndex_].src)) {
415 pattern->SetShowingIndex(pattern->nowImageIndex_);
416 }
417 });
418 }
419
IsShowingSrc(const RefPtr<FrameNode> & imageFrameNode,const std::string & src)420 bool ImageAnimatorPattern::IsShowingSrc(const RefPtr<FrameNode>& imageFrameNode, const std::string& src)
421 {
422 auto imageLayoutProperty = imageFrameNode->GetLayoutProperty<ImageLayoutProperty>();
423 return imageLayoutProperty->HasImageSourceInfo() && imageLayoutProperty->GetImageSourceInfoValue().GetSrc() == src;
424 }
425
426 } // namespace OHOS::Ace::NG
427