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/memory/referenced.h"
23 #include "core/common/container.h"
24 #include "core/common/container_scope.h"
25 #include "core/components_ng/image_provider/adapter/skia_image_data.h"
26 #include "core/components_ng/image_provider/animated_image_object.h"
27 #include "core/components_ng/image_provider/image_loading_context.h"
28 #include "core/components_ng/image_provider/image_object.h"
29 #include "core/components_ng/image_provider/image_utils.h"
30 #include "core/components_ng/image_provider/pixel_map_image_object.h"
31 #include "core/components_ng/image_provider/static_image_object.h"
32 #include "core/components_ng/image_provider/svg_image_object.h"
33 #include "core/components_ng/render/adapter/svg_canvas_image.h"
34 #include "core/image/image_loader.h"
35 #include "core/pipeline_ng/pipeline_context.h"
36
37 namespace OHOS::Ace::NG {
38
39 namespace {
40
CacheImageObject(const RefPtr<ImageObject> & obj)41 void CacheImageObject(const RefPtr<ImageObject>& obj)
42 {
43 auto pipelineCtx = PipelineContext::GetCurrentContext();
44 CHECK_NULL_VOID(pipelineCtx);
45 auto cache = pipelineCtx->GetImageCache();
46 CHECK_NULL_VOID(cache);
47 if (cache && obj->IsSupportCache()) {
48 cache->CacheImgObjNG(obj->GetSourceInfo().GetKey(), obj);
49 }
50 }
51 } // namespace
52
53 std::mutex ImageProvider::taskMtx_;
54 std::unordered_map<std::string, ImageProvider::Task> ImageProvider::tasks_;
55
PrepareImageData(const RefPtr<ImageObject> & imageObj)56 bool ImageProvider::PrepareImageData(const RefPtr<ImageObject>& imageObj)
57 {
58 CHECK_NULL_RETURN(imageObj, false);
59 // data already loaded
60 if (imageObj->GetData()) {
61 return true;
62 }
63 // if image object has no skData, reload data.
64 std::string errorMessage;
65 do {
66 auto imageLoader = ImageLoader::CreateImageLoader(imageObj->GetSourceInfo());
67 if (!imageLoader) {
68 errorMessage = "Fail to create image loader. Image source type is not supported";
69 break;
70 }
71 auto newLoadedData = imageLoader->GetImageData(
72 imageObj->GetSourceInfo(), WeakClaim(RawPtr(NG::PipelineContext::GetCurrentContext())));
73 if (!newLoadedData) {
74 errorMessage = "Fail to load data, please check if data source is invalid";
75 break;
76 }
77 // load data success
78 imageObj->SetData(newLoadedData);
79 return true;
80 } while (false);
81 return false;
82 }
83
QueryImageObjectFromCache(const ImageSourceInfo & src)84 RefPtr<ImageObject> ImageProvider::QueryImageObjectFromCache(const ImageSourceInfo& src)
85 {
86 if (!src.IsSupportCache()) {
87 return nullptr;
88 }
89 auto pipelineCtx = PipelineContext::GetCurrentContext();
90 CHECK_NULL_RETURN(pipelineCtx, nullptr);
91 auto imageCache = pipelineCtx->GetImageCache();
92 if (!imageCache) {
93 LOGD("No image cache %{private}s.", src.ToString().c_str());
94 return nullptr;
95 }
96 RefPtr<ImageObject> imageObj = imageCache->GetCacheImgObjNG(src.GetKey());
97 if (imageObj) {
98 LOGD("imageObj found in cache %{private}s", src.ToString().c_str());
99 }
100 return imageObj;
101 }
102
FailCallback(const std::string & key,const std::string & errorMsg,bool sync)103 void ImageProvider::FailCallback(const std::string& key, const std::string& errorMsg, bool sync)
104 {
105 auto ctxs = EndTask(key);
106 auto notifyLoadFailTask = [ctxs, errorMsg] {
107 for (auto&& it : ctxs) {
108 auto ctx = it.Upgrade();
109 if (!ctx) {
110 continue;
111 }
112 ctx->FailCallback(errorMsg);
113 }
114 };
115 if (sync) {
116 notifyLoadFailTask();
117 } else {
118 ImageUtils::PostToUI(std::move(notifyLoadFailTask));
119 }
120 }
121
SuccessCallback(const RefPtr<CanvasImage> & canvasImage,const std::string & key,bool sync)122 void ImageProvider::SuccessCallback(const RefPtr<CanvasImage>& canvasImage, const std::string& key, bool sync)
123 {
124 auto ctxs = EndTask(key);
125 // when upload success, pass back canvasImage to LoadingContext
126 auto notifyLoadSuccess = [ctxs, canvasImage] {
127 for (auto&& it : ctxs) {
128 auto ctx = it.Upgrade();
129 if (!ctx) {
130 continue;
131 }
132 ctx->SuccessCallback(canvasImage);
133 }
134 };
135 if (sync) {
136 notifyLoadSuccess();
137 } else {
138 ImageUtils::PostToUI(std::move(notifyLoadSuccess));
139 }
140 }
141
CreateImageObjHelper(const ImageSourceInfo & src,bool sync)142 void ImageProvider::CreateImageObjHelper(const ImageSourceInfo& src, bool sync)
143 {
144 ACE_SCOPED_TRACE("CreateImageObj %s", src.ToString().c_str());
145 // load image data
146 auto imageLoader = ImageLoader::CreateImageLoader(src);
147 if (!imageLoader) {
148 std::string errorMessage("Fail to create image loader, Image source type not supported");
149 FailCallback(src.GetKey(), errorMessage, sync);
150 return;
151 }
152 RefPtr<ImageData> data =
153 imageLoader->GetImageData(src, WeakClaim(RawPtr(NG::PipelineContext::GetCurrentContext())));
154
155 // build ImageObject
156 RefPtr<ImageObject> imageObj = ImageProvider::BuildImageObject(src, data);
157 if (!imageObj) {
158 FailCallback(src.GetKey(), "Fail to build image object", sync);
159 return;
160 }
161 CacheImageObject(imageObj);
162
163 auto ctxs = EndTask(src.GetKey());
164 // callback to LoadingContext
165 auto notifyDataReadyTask = [ctxs, imageObj, src] {
166 for (auto&& it : ctxs) {
167 auto ctx = it.Upgrade();
168 if (!ctx) {
169 continue;
170 }
171 ctx->DataReadyCallback(imageObj);
172 }
173 };
174 if (sync) {
175 notifyDataReadyTask();
176 } else {
177 ImageUtils::PostToUI(std::move(notifyDataReadyTask));
178 }
179 }
180
RegisterTask(const std::string & key,const WeakPtr<ImageLoadingContext> & ctx)181 bool ImageProvider::RegisterTask(const std::string& key, const WeakPtr<ImageLoadingContext>& ctx)
182 {
183 std::scoped_lock<std::mutex> lock(taskMtx_);
184 // key exists -> task is running
185 auto it = tasks_.find(key);
186 if (it != tasks_.end()) {
187 it->second.ctxs_.insert(ctx);
188 LOGD("task already exist %{public}s, callbacks size = %u", key.c_str(),
189 static_cast<uint32_t>(it->second.ctxs_.size()));
190 return false;
191 }
192 tasks_[key].ctxs_.insert(ctx);
193 LOGD("task is new %{public}s", key.c_str());
194 return true;
195 }
196
EndTask(const std::string & key)197 std::set<WeakPtr<ImageLoadingContext>> ImageProvider::EndTask(const std::string& key)
198 {
199 std::scoped_lock<std::mutex> lock(taskMtx_);
200 auto it = tasks_.find(key);
201 if (it == tasks_.end()) {
202 LOGW("task not found in map %{private}s", key.c_str());
203 return std::set<WeakPtr<ImageLoadingContext>>();
204 }
205 auto ctxs = it->second.ctxs_;
206 if (ctxs.empty()) {
207 LOGW("registered task has empty context %{public}s", key.c_str());
208 }
209 tasks_.erase(it);
210 LOGD("endTask %s, ctx size = %u", key.c_str(), static_cast<uint32_t>(ctxs.size()));
211 return ctxs;
212 }
213
CancelTask(const std::string & key,const WeakPtr<ImageLoadingContext> & ctx)214 void ImageProvider::CancelTask(const std::string& key, const WeakPtr<ImageLoadingContext>& ctx)
215 {
216 std::scoped_lock<std::mutex> lock(taskMtx_);
217 auto it = tasks_.find(key);
218 CHECK_NULL_VOID(it != tasks_.end());
219 CHECK_NULL_VOID(it->second.ctxs_.find(ctx) != it->second.ctxs_.end());
220 // only one LoadingContext waiting for this task, can just cancel
221 if (it->second.ctxs_.size() == 1) {
222 bool canceled = it->second.bgTask_.Cancel();
223 LOGD("cancel bgTask %s, result: %d", key.c_str(), canceled);
224 if (canceled) {
225 tasks_.erase(it);
226 return;
227 }
228 }
229 // other LoadingContext still waiting for this task, remove ctx from set
230 it->second.ctxs_.erase(ctx);
231 }
232
CreateImageObject(const ImageSourceInfo & src,const WeakPtr<ImageLoadingContext> & ctx,bool sync)233 void ImageProvider::CreateImageObject(const ImageSourceInfo& src, const WeakPtr<ImageLoadingContext>& ctx, bool sync)
234 {
235 if (!RegisterTask(src.GetKey(), ctx)) {
236 // task is already running, only register callbacks
237 return;
238 }
239 if (sync) {
240 CreateImageObjHelper(src, true);
241 } else {
242 std::scoped_lock<std::mutex> lock(taskMtx_);
243 // wrap with [CancelableCallback] and record in [tasks_] map
244 CancelableCallback<void()> task;
245 task.Reset([src] { ImageProvider::CreateImageObjHelper(src); });
246 tasks_[src.GetKey()].bgTask_ = task;
247 ImageUtils::PostToBg(task);
248 }
249 }
250
BuildImageObject(const ImageSourceInfo & src,const RefPtr<ImageData> & data)251 RefPtr<ImageObject> ImageProvider::BuildImageObject(const ImageSourceInfo& src, const RefPtr<ImageData>& data)
252 {
253 if (!data) {
254 LOGW("data is null when try ParseImageObjectType, src: %{public}s", src.ToString().c_str());
255 return nullptr;
256 }
257 if (src.IsSvg()) {
258 // SVG object needs to make SVG dom during creation
259 return SvgImageObject::Create(src, data);
260 }
261 if (src.IsPixmap()) {
262 return PixelMapImageObject::Create(src, data);
263 }
264
265 // standard skia image object
266 auto skiaImageData = DynamicCast<SkiaImageData>(data);
267 CHECK_NULL_RETURN(skiaImageData, nullptr);
268 auto [size, frameCount] = skiaImageData->Parse();
269 CHECK_NULL_RETURN(size.IsPositive() && frameCount > 0, nullptr);
270
271 if (frameCount > 1) {
272 return MakeRefPtr<AnimatedImageObject>(src, size, data);
273 }
274 return MakeRefPtr<StaticImageObject>(src, size, data);
275 }
276 } // namespace OHOS::Ace::NG
277