1 /*
2 * Copyright (c) 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/render/adapter/animated_image.h"
17
18 #ifdef USE_ROSEN_DRAWING
19 #include "drawing/engine_adapter/skia_adapter/skia_bitmap.h"
20 #include "drawing/engine_adapter/skia_adapter/skia_data.h"
21 #include "drawing/engine_adapter/skia_adapter/skia_image_info.h"
22 #endif
23 #include "core/animation/picture_animation.h"
24 #ifndef USE_ROSEN_DRAWING
25 #include "core/components_ng/image_provider/adapter/skia_image_data.h"
26 #else
27 #include "core/components_ng/image_provider/adapter/rosen/drawing_image_data.h"
28 #endif
29 #include "core/components_ng/image_provider/image_utils.h"
30 #include "core/image/sk_image_cache.h"
31 namespace OHOS::Ace::NG {
32 namespace {
33 constexpr int32_t STANDARD_FRAME_DURATION = 100;
34 constexpr int32_t FORM_REPEAT_COUNT = 1;
35 constexpr float RESIZE_THRESHOLD = 0.7f;
36
37 struct RSDataWrapper {
38 std::shared_ptr<RSData> data;
39 };
40
RSDataWrapperReleaseProc(const void *,void * context)41 inline void RSDataWrapperReleaseProc(const void*, void* context)
42 {
43 RSDataWrapper* wrapper = reinterpret_cast<RSDataWrapper*>(context);
44 delete wrapper;
45 }
46 } // namespace
47
48 #ifndef USE_ROSEN_DRAWING
Create(const RefPtr<SkiaImageData> & data,const ResizeParam & size,const std::string & url)49 RefPtr<CanvasImage> AnimatedImage::Create(
50 const RefPtr<SkiaImageData>& data, const ResizeParam& size, const std::string& url)
51 {
52 CHECK_NULL_RETURN(data, nullptr);
53 auto skData = data->GetSkData();
54 CHECK_NULL_RETURN(skData, nullptr);
55 auto codec = SkCodec::MakeFromData(skData);
56 CHECK_NULL_RETURN(codec, nullptr);
57 if (SystemProperties::GetImageFrameworkEnabled()) {
58 auto src = ImageSource::Create(skData->bytes(), skData->size());
59 CHECK_NULL_RETURN(src, nullptr);
60 return MakeRefPtr<AnimatedPixmap>(codec, src, size, url);
61 }
62 return MakeRefPtr<AnimatedSkImage>(std::move(codec), url);
63 }
64 #else
Create(const RefPtr<DrawingImageData> & data,const ResizeParam & size,const std::string & url)65 RefPtr<CanvasImage> AnimatedImage::Create(
66 const RefPtr<DrawingImageData>& data, const ResizeParam& size, const std::string& url)
67 {
68 CHECK_NULL_RETURN(data, nullptr);
69 auto rsData = data->GetRSData();
70 CHECK_NULL_RETURN(rsData, nullptr);
71 RSDataWrapper* wrapper = new RSDataWrapper{rsData};
72 auto skData = SkData::MakeWithProc(rsData->GetData(), rsData->GetSize(), RSDataWrapperReleaseProc, wrapper);
73 auto codec = SkCodec::MakeFromData(skData);
74 CHECK_NULL_RETURN(codec, nullptr);
75 if (SystemProperties::GetImageFrameworkEnabled()) {
76 auto src = ImageSource::Create(static_cast<const uint8_t*>(rsData->GetData()), rsData->GetSize());
77 CHECK_NULL_RETURN(src, nullptr);
78 return MakeRefPtr<AnimatedPixmap>(codec, src, size, url);
79 }
80 return MakeRefPtr<AnimatedRSImage>(std::move(codec), url);
81 }
82 #endif
83
AnimatedImage(const std::unique_ptr<SkCodec> & codec,std::string url)84 AnimatedImage::AnimatedImage(const std::unique_ptr<SkCodec>& codec, std::string url) : cacheKey_(std::move(url))
85 {
86 auto pipelineContext = PipelineBase::GetCurrentContext();
87 CHECK_NULL_VOID(pipelineContext);
88 animator_ = CREATE_ANIMATOR(pipelineContext);
89 animator_->PreventFrameJank();
90
91 // set up animator
92 int32_t totalDuration = 0;
93 auto info = codec->getFrameInfo();
94 for (int32_t i = 0; i < codec->getFrameCount(); ++i) {
95 if (info[i].fDuration <= 0) {
96 info[i].fDuration = STANDARD_FRAME_DURATION;
97 }
98 totalDuration += info[i].fDuration;
99 }
100 animator_->SetDuration(totalDuration);
101 // repetition is 0 => play only once
102 auto iteration = codec->getRepetitionCount() + 1;
103 if (iteration == 0) {
104 iteration = ANIMATION_REPEAT_INFINITE;
105 }
106 animator_->SetIteration(iteration);
107 if (pipelineContext->IsFormRender() && animator_->GetIteration() != 0) {
108 animator_->SetIteration(FORM_REPEAT_COUNT);
109 }
110
111 // initialize PictureAnimation interpolator
112 auto picAnimation = MakeRefPtr<PictureAnimation<uint32_t>>();
113 CHECK_NULL_VOID(picAnimation);
114 for (int32_t i = 0; i < codec->getFrameCount(); ++i) {
115 picAnimation->AddPicture(static_cast<float>(info[i].fDuration) / totalDuration, i);
116 }
117 picAnimation->AddListener([weak = WeakClaim(this)](const uint32_t idx) {
118 auto self = weak.Upgrade();
119 CHECK_NULL_VOID(self);
120 self->RenderFrame(idx);
121 });
122 animator_->AddInterpolator(picAnimation);
123
124 animator_->Play();
125 }
126
~AnimatedImage()127 AnimatedImage::~AnimatedImage()
128 {
129 // animator has to destruct on UI thread
130 ImageUtils::PostToUI([animator = animator_]() mutable { animator.Reset(); }, "ArkUIImageResetAnimated");
131 }
132
ControlAnimation(bool play)133 void AnimatedImage::ControlAnimation(bool play)
134 {
135 (play) ? animator_->Play() : animator_->Pause();
136 }
137
138
GetIsAnimating() const139 bool AnimatedImage::GetIsAnimating() const
140 {
141 return animationState_;
142 }
143
RenderFrame(uint32_t idx)144 void AnimatedImage::RenderFrame(uint32_t idx)
145 {
146 if (GetCachedFrame(idx)) {
147 return;
148 }
149 ImageUtils::PostToBg(
150 [weak = WeakClaim(this), idx] {
151 auto self = weak.Upgrade();
152 CHECK_NULL_VOID(self);
153 self->DecodeFrame(idx);
154 },
155 "ArkUIImageRenderAnimatedFrame");
156 }
157
158 // runs on Background threads
DecodeFrame(uint32_t idx)159 void AnimatedImage::DecodeFrame(uint32_t idx)
160 {
161 // max number of decoding thread = 2
162 if (queueSize_ >= 2) {
163 // skip frame
164 return;
165 }
166 ++queueSize_;
167
168 ACE_SCOPED_TRACE("decode %s frame %d", cacheKey_.c_str(), idx);
169 std::scoped_lock<std::mutex> lock(decodeMtx_);
170 DecodeImpl(idx);
171
172 ImageUtils::PostToUI(
173 [weak = WeakClaim(this)] {
174 auto self = weak.Upgrade();
175 CHECK_NULL_VOID(self && self->redraw_);
176 self->redraw_();
177 },
178 "ArkUIImageDecodeAnimatedFrame");
179
180 CacheFrame(cacheKey_ + std::to_string(idx));
181 --queueSize_;
182 }
183
GetCachedFrame(uint32_t idx)184 bool AnimatedImage::GetCachedFrame(uint32_t idx)
185 {
186 auto image = GetCachedFrameImpl(cacheKey_ + std::to_string(idx));
187 CHECK_NULL_RETURN(image, false);
188
189 if (!decodeMtx_.try_lock()) {
190 // last frame still decoding, skip this frame to avoid blocking UI thread
191 return true;
192 }
193 UseCachedFrame(std::move(image));
194
195 decodeMtx_.unlock();
196
197 if (redraw_) {
198 redraw_();
199 }
200 return true;
201 }
202
203 // ----------------------------------------------------------
204 // AnimatedSkImage implementation
205 // ----------------------------------------------------------
206
207 #ifndef USE_ROSEN_DRAWING
GetImage() const208 sk_sp<SkImage> AnimatedSkImage::GetImage() const
209 #else
210 std::shared_ptr<RSImage> AnimatedRSImage::GetImage() const
211 #endif
212 {
213 std::scoped_lock<std::mutex> lock(frameMtx_);
214 return currentFrame_;
215 }
216
217 #ifndef USE_ROSEN_DRAWING
DecodeImpl(uint32_t idx)218 void AnimatedSkImage::DecodeImpl(uint32_t idx)
219 #else
220 void AnimatedRSImage::DecodeImpl(uint32_t idx)
221 #endif
222 {
223 SkImageInfo imageInfo = codec_->getInfo();
224 #ifndef USE_ROSEN_DRAWING
225 SkBitmap bitmap;
226 #else
227 RSBitmap bitmap;
228 #endif
229
230 SkCodec::Options options;
231 options.fFrameIndex = static_cast<int32_t>(idx);
232
233 SkCodec::FrameInfo info {};
234 codec_->getFrameInfo(idx, &info);
235 if (info.fRequiredFrame != SkCodec::kNoFrame) {
236 // frame requires a previous frame as background layer
237 options.fPriorFrame = info.fRequiredFrame;
238 bitmap = requiredFrame_;
239 } else {
240 // create from empty layer
241 #ifndef USE_ROSEN_DRAWING
242 bitmap.allocPixels(imageInfo);
243 #else
244 auto info = Rosen::Drawing::SkiaImageInfo::ConvertToRSImageInfo(imageInfo);
245 bitmap.Build(info);
246 #endif
247 }
248
249 // decode pixels from codec
250 #ifndef USE_ROSEN_DRAWING
251 auto res = codec_->getPixels(imageInfo, bitmap.getPixels(), bitmap.rowBytes(), &options);
252 #else
253 auto res = codec_->getPixels(imageInfo, bitmap.GetPixels(), bitmap.GetRowBytes(), &options);
254 #endif
255 CHECK_NULL_VOID(res == SkCodec::kSuccess);
256
257 // next frame will be drawn on top of this one
258 if (info.fDisposalMethod != SkCodecAnimation::DisposalMethod::kRestorePrevious) {
259 requiredFrame_ = bitmap;
260 }
261
262 // save current frame, notify redraw
263 {
264 std::scoped_lock<std::mutex> lock(frameMtx_);
265 #ifndef USE_ROSEN_DRAWING
266 currentFrame_ = SkImage::MakeFromBitmap(bitmap);
267 #else
268 currentFrame_ = std::make_shared<RSImage>();
269 currentFrame_->BuildFromBitmap(bitmap);
270 #endif
271 }
272 }
273
274 #ifndef USE_ROSEN_DRAWING
CacheFrame(const std::string & key)275 void AnimatedSkImage::CacheFrame(const std::string& key)
276 #else
277 void AnimatedRSImage::CacheFrame(const std::string& key)
278 #endif
279 {
280 auto ctx = PipelineContext::GetCurrentContext();
281 CHECK_NULL_VOID(ctx);
282 auto cache = ctx->GetImageCache();
283 CHECK_NULL_VOID(cache);
284 std::shared_ptr<CachedImage> cacheNode;
285 {
286 std::scoped_lock<std::mutex> lock(frameMtx_);
287 cacheNode = std::make_shared<CachedImage>(currentFrame_);
288 }
289 cache->CacheImage(key, cacheNode);
290 }
291
292 #ifndef USE_ROSEN_DRAWING
GetCachedFrameImpl(const std::string & key)293 RefPtr<CanvasImage> AnimatedSkImage::GetCachedFrameImpl(const std::string& key)
294 {
295 return SkiaImage::QueryFromCache(key);
296 }
297 #else
GetCachedFrameImpl(const std::string & key)298 RefPtr<CanvasImage> AnimatedRSImage::GetCachedFrameImpl(const std::string& key)
299 {
300 return DrawingImage::QueryFromCache(key);
301 }
302 #endif
303
304 #ifndef USE_ROSEN_DRAWING
UseCachedFrame(RefPtr<CanvasImage> && image)305 void AnimatedSkImage::UseCachedFrame(RefPtr<CanvasImage>&& image)
306 {
307 std::scoped_lock<std::mutex> lock(frameMtx_);
308 currentFrame_ = DynamicCast<SkiaImage>(image)->GetImage();
309 }
310 #else
UseCachedFrame(RefPtr<CanvasImage> && image)311 void AnimatedRSImage::UseCachedFrame(RefPtr<CanvasImage>&& image)
312 {
313 std::scoped_lock<std::mutex> lock(frameMtx_);
314 currentFrame_ = DynamicCast<DrawingImage>(image)->GetImage();
315 }
316 #endif
317
318 // ----------------------------------------------------------
319 // AnimatedPixmap implementation
320 // ----------------------------------------------------------
AnimatedPixmap(const std::unique_ptr<SkCodec> & codec,const RefPtr<ImageSource> & src,const ResizeParam & size,std::string url)321 AnimatedPixmap::AnimatedPixmap(
322 const std::unique_ptr<SkCodec>& codec, const RefPtr<ImageSource>& src, const ResizeParam& size, std::string url)
323 : AnimatedImage(codec, std::move(url)), size_(size), src_(src)
324 {
325 // resizing to a size >= 0.7 [~= sqrt(2) / 2] intrinsic size takes 2x longer to decode while memory usage is 1/2.
326 // 0.7 is the balance point.
327 }
328
GetPixelMap() const329 RefPtr<PixelMap> AnimatedPixmap::GetPixelMap() const
330 {
331 std::scoped_lock<std::mutex> lock(frameMtx_);
332 return currentFrame_;
333 }
334
DecodeImpl(uint32_t idx)335 void AnimatedPixmap::DecodeImpl(uint32_t idx)
336 {
337 if (intrSizeInitial_) {
338 auto intrSize = src_->GetImageSize();
339 if (intrSize.first * RESIZE_THRESHOLD >= size_.width || intrSize.second * RESIZE_THRESHOLD >= size_.height) {
340 size_.forceResize = true;
341 }
342 intrSizeInitial_ = false;
343 }
344 RefPtr<PixelMap> frame;
345 if (SystemProperties::GetDebugEnabled()) {
346 TAG_LOGD(AceLogTag::ACE_IMAGE,
347 "gif decode to pixmap, src=%{public}s, idx = %{public}d, resolutionQuality = %{public}s",
348 GetCacheKey().c_str(), idx, GetResolutionQuality(size_.imageQuality).c_str());
349 }
350 if (size_.forceResize) {
351 frame = src_->CreatePixelMap(idx, { size_.width, size_.height }, size_.imageQuality);
352 } else {
353 // decode to intrinsic size
354 frame = src_->CreatePixelMap(idx, { -1, -1 }, size_.imageQuality);
355 }
356 std::scoped_lock<std::mutex> lock(frameMtx_);
357 currentFrame_ = frame;
358 }
359
CacheFrame(const std::string & key)360 void AnimatedPixmap::CacheFrame(const std::string& key)
361 {
362 auto ctx = PipelineContext::GetCurrentContext();
363 CHECK_NULL_VOID(ctx);
364 auto cache = ctx->GetImageCache();
365 CHECK_NULL_VOID(cache);
366
367 std::scoped_lock<std::mutex> lock(frameMtx_);
368 cache->CacheImageData(key, MakeRefPtr<PixmapData>(currentFrame_));
369 }
370
GetCachedFrameImpl(const std::string & key)371 RefPtr<CanvasImage> AnimatedPixmap::GetCachedFrameImpl(const std::string& key)
372 {
373 return PixelMapImage::QueryFromCache(key);
374 }
375
UseCachedFrame(RefPtr<CanvasImage> && image)376 void AnimatedPixmap::UseCachedFrame(RefPtr<CanvasImage>&& image)
377 {
378 std::scoped_lock<std::mutex> lock(frameMtx_);
379 currentFrame_ = DynamicCast<PixelMapImage>(image)->GetPixelMap();
380 }
381 } // namespace OHOS::Ace::NG
382