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