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