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/image_provider/image_provider.h"
17
18 #include <cstdint>
19 #include <mutex>
20
21 #include "base/log/ace_trace.h"
22 #include "base/log/log_wrapper.h"
23 #include "base/memory/referenced.h"
24 #include "base/subwindow/subwindow_manager.h"
25 #include "base/utils/utils.h"
26 #include "core/components_ng/image_provider/adapter/image_decoder.h"
27 #include "core/components_ng/image_provider/adapter/rosen/drawing_image_data.h"
28 #include "core/components_ng/image_provider/animated_image_object.h"
29 #include "core/components_ng/image_provider/image_loading_context.h"
30 #include "core/components_ng/image_provider/image_object.h"
31 #include "core/components_ng/image_provider/image_utils.h"
32 #include "core/components_ng/image_provider/pixel_map_image_object.h"
33 #include "core/components_ng/image_provider/static_image_object.h"
34 #include "core/components_ng/image_provider/svg_image_object.h"
35 #include "core/components_ng/render/adapter/rosen/drawing_image.h"
36 #include "core/image/image_loader.h"
37 #include "core/image/sk_image_cache.h"
38 #include "core/pipeline_ng/pipeline_context.h"
39
40 namespace OHOS::Ace::NG {
41
CacheImageObject(const RefPtr<ImageObject> & obj)42 void ImageProvider::CacheImageObject(const RefPtr<ImageObject>& obj)
43 {
44 CHECK_NULL_VOID(obj);
45 auto pipelineCtx = PipelineContext::GetCurrentContext();
46 CHECK_NULL_VOID(pipelineCtx);
47 auto cache = pipelineCtx->GetImageCache();
48 CHECK_NULL_VOID(cache);
49 if (cache && obj->IsSupportCache()) {
50 cache->CacheImgObjNG(obj->GetSourceInfo().GetKey(), obj);
51 }
52 }
53
54 std::mutex ImageProvider::taskMtx_;
55 std::unordered_map<std::string, ImageProvider::Task> ImageProvider::tasks_;
56
PrepareImageData(const RefPtr<ImageObject> & imageObj)57 bool ImageProvider::PrepareImageData(const RefPtr<ImageObject>& imageObj)
58 {
59 CHECK_NULL_RETURN(imageObj, false);
60 // Attempt to acquire a timed lock (maximum wait time: 1000ms)
61 auto lock = imageObj->GetPrepareImageDataLock();
62 if (!lock.owns_lock()) {
63 TAG_LOGW(AceLogTag::ACE_IMAGE, "Failed to acquire lock within timeout.");
64 return false;
65 }
66 // data already loaded
67 if (imageObj->GetData()) {
68 return true;
69 }
70 // if image object has no skData, reload data.
71 auto imageLoader = ImageLoader::CreateImageLoader(imageObj->GetSourceInfo());
72 CHECK_NULL_RETURN(imageLoader, false);
73
74 auto container = Container::Current();
75 if (container && container->IsSubContainer()) {
76 TAG_LOGI(AceLogTag::ACE_IMAGE, "subContainer's pipeline's dataProviderManager is null, cannot load image "
77 "source, need to switch pipeline in parentContainer.");
78 auto currentId = SubwindowManager::GetInstance()->GetParentContainerId(Container::CurrentId());
79 container = Container::GetContainer(currentId);
80 }
81 CHECK_NULL_RETURN(container, false);
82 auto pipeline = container->GetPipelineContext();
83 CHECK_NULL_RETURN(pipeline, false);
84 auto newLoadedData = imageLoader->GetImageData(imageObj->GetSourceInfo(), WeakClaim(RawPtr(pipeline)));
85 CHECK_NULL_RETURN(newLoadedData, false);
86 // load data success
87 imageObj->SetData(newLoadedData);
88 return true;
89 }
90
QueryThumbnailCache(const ImageSourceInfo & src)91 RefPtr<ImageObject> ImageProvider::QueryThumbnailCache(const ImageSourceInfo& src)
92 {
93 // query thumbnail from cache
94 auto pipeline = PipelineContext::GetCurrentContext();
95 CHECK_NULL_RETURN(pipeline, nullptr);
96 auto cache = pipeline->GetImageCache();
97 CHECK_NULL_RETURN(cache, nullptr);
98 auto data = DynamicCast<PixmapData>(cache->GetCacheImageData(src.GetKey()));
99 if (data) {
100 return PixelMapImageObject::Create(src, data);
101 }
102 return nullptr;
103 }
104
QueryImageObjectFromCache(const ImageSourceInfo & src)105 RefPtr<ImageObject> ImageProvider::QueryImageObjectFromCache(const ImageSourceInfo& src)
106 {
107 if (src.GetSrcType() == SrcType::DATA_ABILITY_DECODED) {
108 return QueryThumbnailCache(src);
109 }
110 if (!src.SupportObjCache()) {
111 return nullptr;
112 }
113 auto pipelineCtx = PipelineContext::GetCurrentContext();
114 CHECK_NULL_RETURN(pipelineCtx, nullptr);
115 auto imageCache = pipelineCtx->GetImageCache();
116 CHECK_NULL_RETURN(imageCache, nullptr);
117 RefPtr<ImageObject> imageObj = imageCache->GetCacheImgObjNG(src.GetKey());
118 return imageObj;
119 }
120
FailCallback(const std::string & key,const std::string & errorMsg,bool sync)121 void ImageProvider::FailCallback(const std::string& key, const std::string& errorMsg, bool sync)
122 {
123 auto ctxs = EndTask(key);
124 for (auto&& it : ctxs) {
125 auto ctx = it.Upgrade();
126 if (!ctx) {
127 continue;
128 }
129
130 if (sync) {
131 ctx->FailCallback(errorMsg);
132 } else {
133 // NOTE: contexts may belong to different arkui pipelines
134 auto notifyLoadFailTask = [ctx, errorMsg] { ctx->FailCallback(errorMsg); };
135 ImageUtils::PostToUI(std::move(notifyLoadFailTask), "ArkUIImageProviderFail", ctx->GetContainerId());
136 }
137 }
138 }
139
SuccessCallback(const RefPtr<CanvasImage> & canvasImage,const std::string & key,bool sync,bool loadInVipChannel)140 void ImageProvider::SuccessCallback(
141 const RefPtr<CanvasImage>& canvasImage, const std::string& key, bool sync, bool loadInVipChannel)
142 {
143 canvasImage->Cache(key);
144 auto ctxs = EndTask(key);
145 // when upload success, pass back canvasImage to LoadingContext
146 for (auto&& it : ctxs) {
147 auto ctx = it.Upgrade();
148 if (!ctx) {
149 continue;
150 }
151 if (sync) {
152 ctx->SuccessCallback(canvasImage->Clone());
153 } else {
154 // NOTE: contexts may belong to different arkui pipelines
155 auto notifyLoadSuccess = [ctx, canvasImage] { ctx->SuccessCallback(canvasImage->Clone()); };
156 ImageUtils::PostToUI(std::move(notifyLoadSuccess), "ArkUIImageProviderSuccess", ctx->GetContainerId());
157 }
158 }
159 }
160
CreateImageObjHelper(const ImageSourceInfo & src,bool sync)161 void ImageProvider::CreateImageObjHelper(const ImageSourceInfo& src, bool sync)
162 {
163 ACE_SCOPED_TRACE("CreateImageObj %s", src.ToString().c_str());
164 // load image data
165 auto imageLoader = ImageLoader::CreateImageLoader(src);
166 if (!imageLoader) {
167 std::string errorMessage("Failed to create image loader, Image source type not supported");
168 FailCallback(src.GetKey(), src.ToString() + errorMessage, sync);
169 return;
170 }
171 auto pipeline = PipelineContext::GetCurrentContext();
172 RefPtr<ImageData> data = imageLoader->GetImageData(src, WeakClaim(RawPtr(pipeline)));
173 if (!data) {
174 FailCallback(src.GetKey(), "Failed to load image data", sync);
175 return;
176 }
177
178 // build ImageObject
179 RefPtr<ImageObject> imageObj = ImageProvider::BuildImageObject(src, data);
180 if (!imageObj) {
181 FailCallback(src.GetKey(), "Failed to build image object", sync);
182 return;
183 }
184
185 auto cloneImageObj = imageObj->Clone();
186
187 // ImageObject cache is only for saving image size info, clear data to save memory
188 cloneImageObj->ClearData();
189
190 CacheImageObject(cloneImageObj);
191
192 auto ctxs = EndTask(src.GetKey());
193 // callback to LoadingContext
194 for (auto&& it : ctxs) {
195 auto ctx = it.Upgrade();
196 if (!ctx) {
197 continue;
198 }
199 if (sync) {
200 ctx->DataReadyCallback(imageObj);
201 } else {
202 // NOTE: contexts may belong to different arkui pipelines
203 auto notifyDataReadyTask = [ctx, imageObj, src] { ctx->DataReadyCallback(imageObj); };
204 ImageUtils::PostToUI(std::move(notifyDataReadyTask), "ArkUIImageProviderDataReady", ctx->GetContainerId());
205 }
206 }
207 }
208
RegisterTask(const std::string & key,const WeakPtr<ImageLoadingContext> & ctx)209 bool ImageProvider::RegisterTask(const std::string& key, const WeakPtr<ImageLoadingContext>& ctx)
210 {
211 std::scoped_lock<std::mutex> lock(taskMtx_);
212 // key exists -> task is running
213 auto it = tasks_.find(key);
214 if (it != tasks_.end()) {
215 it->second.ctxs_.insert(ctx);
216 return false;
217 }
218 tasks_[key].ctxs_.insert(ctx);
219 return true;
220 }
221
EndTask(const std::string & key)222 std::set<WeakPtr<ImageLoadingContext>> ImageProvider::EndTask(const std::string& key)
223 {
224 std::scoped_lock<std::mutex> lock(taskMtx_);
225 auto it = tasks_.find(key);
226 if (it == tasks_.end()) {
227 TAG_LOGW(AceLogTag::ACE_IMAGE, "task not found in map %{private}s", key.c_str());
228 return {};
229 }
230 auto ctxs = it->second.ctxs_;
231 if (ctxs.empty()) {
232 TAG_LOGW(AceLogTag::ACE_IMAGE, "registered task has empty context %{public}s", key.c_str());
233 }
234 tasks_.erase(it);
235 return ctxs;
236 }
237
CancelTask(const std::string & key,const WeakPtr<ImageLoadingContext> & ctx)238 void ImageProvider::CancelTask(const std::string& key, const WeakPtr<ImageLoadingContext>& ctx)
239 {
240 std::scoped_lock<std::mutex> lock(taskMtx_);
241 auto it = tasks_.find(key);
242 CHECK_NULL_VOID(it != tasks_.end());
243 CHECK_NULL_VOID(it->second.ctxs_.find(ctx) != it->second.ctxs_.end());
244 // only one LoadingContext waiting for this task, can just cancel
245 if (it->second.ctxs_.size() == 1) {
246 // task should be deleted regardless of whether the cancellation is successful or not
247 it->second.bgTask_.Cancel();
248 tasks_.erase(it);
249 return;
250 }
251 // other LoadingContext still waiting for this task, remove ctx from set
252 it->second.ctxs_.erase(ctx);
253 }
254
CreateImageObject(const ImageSourceInfo & src,const WeakPtr<ImageLoadingContext> & ctxWp,bool sync)255 void ImageProvider::CreateImageObject(const ImageSourceInfo& src, const WeakPtr<ImageLoadingContext>& ctxWp, bool sync)
256 {
257 if (!RegisterTask(src.GetKey(), ctxWp)) {
258 // task is already running, only register callbacks
259 return;
260 }
261 if (sync) {
262 CreateImageObjHelper(src, true);
263 } else {
264 std::scoped_lock<std::mutex> lock(taskMtx_);
265 // wrap with [CancelableCallback] and record in [tasks_] map
266 CancelableCallback<void()> task;
267 task.Reset([src, ctxWp] { ImageProvider::CreateImageObjHelper(src); });
268 tasks_[src.GetKey()].bgTask_ = task;
269 auto ctx = ctxWp.Upgrade();
270 CHECK_NULL_VOID(ctx);
271 ImageUtils::PostToBg(task, "ArkUIImageProviderCreateImageObject", ctx->GetContainerId());
272 }
273 }
274
BuildImageObject(const ImageSourceInfo & src,const RefPtr<ImageData> & data)275 RefPtr<ImageObject> ImageProvider::BuildImageObject(const ImageSourceInfo& src, const RefPtr<ImageData>& data)
276 {
277 if (!data) {
278 TAG_LOGW(AceLogTag::ACE_IMAGE, "data is null when try ParseImageObjectType, src: %{private}s",
279 src.ToString().c_str());
280 return nullptr;
281 }
282 if (src.IsSvg()) {
283 // SVG object needs to make SVG dom during creation
284 return SvgImageObject::Create(src, data);
285 }
286 if (src.IsPixmap()) {
287 return PixelMapImageObject::Create(src, data);
288 }
289
290 auto rosenImageData = DynamicCast<DrawingImageData>(data);
291 CHECK_NULL_RETURN(rosenImageData, nullptr);
292 auto codec = rosenImageData->Parse();
293 if (!codec.imageSize.IsPositive()) {
294 TAG_LOGW(AceLogTag::ACE_IMAGE,
295 "Image of src: %{private}s, imageData's size = %{public}d is invalid, and the parsed size is invalid "
296 "%{public}s, "
297 "frameCount is %{public}d",
298 src.ToString().c_str(), static_cast<int32_t>(data->GetSize()), codec.imageSize.ToString().c_str(),
299 codec.frameCount);
300 return nullptr;
301 }
302 RefPtr<ImageObject> imageObject;
303 if (codec.frameCount > 1) {
304 auto imageObject = MakeRefPtr<AnimatedImageObject>(src, codec.imageSize, data);
305 imageObject->SetFrameCount(codec.frameCount);
306 return imageObject;
307 }
308 imageObject = MakeRefPtr<StaticImageObject>(src, codec.imageSize, data);
309 imageObject->SetOrientation(codec.orientation);
310 return imageObject;
311 }
312
MakeCanvasImage(const RefPtr<ImageObject> & obj,const WeakPtr<ImageLoadingContext> & ctxWp,const SizeF & size,const ImageDecoderOptions & imageDecoderOptions)313 void ImageProvider::MakeCanvasImage(const RefPtr<ImageObject>& obj, const WeakPtr<ImageLoadingContext>& ctxWp,
314 const SizeF& size, const ImageDecoderOptions& imageDecoderOptions)
315 {
316 auto key = ImageUtils::GenerateImageKey(obj->GetSourceInfo(), size);
317 // check if same task is already executing
318 if (!RegisterTask(key, ctxWp)) {
319 return;
320 }
321 if (imageDecoderOptions.sync) {
322 MakeCanvasImageHelper(obj, size, key, imageDecoderOptions);
323 } else {
324 std::scoped_lock<std::mutex> lock(taskMtx_);
325 // wrap with [CancelableCallback] and record in [tasks_] map
326 CancelableCallback<void()> task;
327 task.Reset(
328 [key, obj, size, imageDecoderOptions] { MakeCanvasImageHelper(obj, size, key, imageDecoderOptions); });
329 tasks_[key].bgTask_ = task;
330 auto ctx = ctxWp.Upgrade();
331 CHECK_NULL_VOID(ctx);
332 ImageUtils::PostToBg(task, "ArkUIImageProviderMakeCanvasImage", ctx->GetContainerId());
333 }
334 }
335
MakeCanvasImageHelper(const RefPtr<ImageObject> & obj,const SizeF & size,const std::string & key,const ImageDecoderOptions & imageDecoderOptions)336 void ImageProvider::MakeCanvasImageHelper(const RefPtr<ImageObject>& obj, const SizeF& size, const std::string& key,
337 const ImageDecoderOptions& imageDecoderOptions)
338 {
339 ImageDecoder decoder(obj, size, imageDecoderOptions.forceResize);
340 RefPtr<CanvasImage> image;
341 // preview and ohos platform
342 if (SystemProperties::GetImageFrameworkEnabled()) {
343 image = decoder.MakePixmapImage(imageDecoderOptions.imageQuality, imageDecoderOptions.isHdrDecoderNeed);
344 } else {
345 image = decoder.MakeDrawingImage();
346 }
347
348 if (image) {
349 SuccessCallback(image, key, imageDecoderOptions.sync, imageDecoderOptions.loadInVipChannel);
350 } else {
351 FailCallback(key, "Failed to decode image");
352 }
353 }
354 } // namespace OHOS::Ace::NG
355