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/pattern/image/image_pattern.h"
17
18 #include <array>
19
20 #include "base/utils/utils.h"
21 #include "core/components/theme/icon_theme.h"
22 #include "core/components_ng/base/view_abstract.h"
23 #include "core/components_ng/pattern/image/image_paint_method.h"
24 #include "core/components_v2/inspector/inspector_constants.h"
25 #include "core/pipeline/base/element_register.h"
26 #include "core/pipeline_ng/pipeline_context.h"
27 #include "core/pipeline_ng/ui_task_scheduler.h"
28
29 namespace OHOS::Ace::NG {
30
CreateDataReadyCallback()31 DataReadyNotifyTask ImagePattern::CreateDataReadyCallback()
32 {
33 auto task = [weak = WeakClaim(this)](const ImageSourceInfo& sourceInfo) {
34 auto pattern = weak.Upgrade();
35 CHECK_NULL_VOID(pattern);
36 auto imageLayoutProperty = pattern->GetLayoutProperty<ImageLayoutProperty>();
37 CHECK_NULL_VOID(imageLayoutProperty);
38 auto currentSourceInfo = imageLayoutProperty->GetImageSourceInfo().value_or(ImageSourceInfo(""));
39 if (currentSourceInfo != sourceInfo) {
40 LOGW("sourceInfo does not match, ignore current callback. current: %{public}s vs callback's: %{public}s",
41 currentSourceInfo.ToString().c_str(), sourceInfo.ToString().c_str());
42 return;
43 }
44 LOGD("Image Data Ready %{private}s", sourceInfo.ToString().c_str());
45 pattern->OnImageDataReady();
46 };
47 return task;
48 }
49
CreateLoadSuccessCallback()50 LoadSuccessNotifyTask ImagePattern::CreateLoadSuccessCallback()
51 {
52 auto task = [weak = WeakClaim(this)](const ImageSourceInfo& sourceInfo) {
53 auto pattern = weak.Upgrade();
54 CHECK_NULL_VOID(pattern);
55 auto imageLayoutProperty = pattern->GetLayoutProperty<ImageLayoutProperty>();
56 CHECK_NULL_VOID(imageLayoutProperty);
57 auto currentSourceInfo = imageLayoutProperty->GetImageSourceInfo().value_or(ImageSourceInfo(""));
58 if (currentSourceInfo != sourceInfo) {
59 LOGW("sourceInfo does not match, ignore current callback. current: %{public}s vs callback's: %{public}s",
60 currentSourceInfo.ToString().c_str(), sourceInfo.ToString().c_str());
61 return;
62 }
63 LOGD("Image Load Success %{private}s", sourceInfo.ToString().c_str());
64 pattern->OnImageLoadSuccess();
65 };
66 return task;
67 }
68
CreateLoadFailCallback()69 LoadFailNotifyTask ImagePattern::CreateLoadFailCallback()
70 {
71 auto task = [weak = WeakClaim(this)](const ImageSourceInfo& sourceInfo) {
72 auto pattern = weak.Upgrade();
73 CHECK_NULL_VOID(pattern);
74 auto imageLayoutProperty = pattern->GetLayoutProperty<ImageLayoutProperty>();
75 CHECK_NULL_VOID(imageLayoutProperty);
76 auto currentSourceInfo = imageLayoutProperty->GetImageSourceInfo().value_or(ImageSourceInfo(""));
77 if (currentSourceInfo != sourceInfo) {
78 LOGW("sourceInfo does not match, ignore current callback. current: %{public}s vs callback's: %{public}s",
79 currentSourceInfo.ToString().c_str(), sourceInfo.ToString().c_str());
80 return;
81 }
82 pattern->OnImageLoadFail();
83 };
84 return task;
85 }
86
PrepareAnimation()87 void ImagePattern::PrepareAnimation()
88 {
89 if (image_->IsStatic()) {
90 return;
91 }
92 SetRedrawCallback();
93 RegisterVisibleAreaChange();
94 }
95
SetRedrawCallback()96 void ImagePattern::SetRedrawCallback()
97 {
98 CHECK_NULL_VOID_NOLOG(image_);
99 // set animation flush function for svg / gif
100 image_->SetRedrawCallback([weak = WeakClaim(RawPtr(GetHost()))] {
101 auto imageNode = weak.Upgrade();
102 CHECK_NULL_VOID(imageNode);
103 imageNode->MarkNeedRenderOnly();
104 });
105 }
106
RegisterVisibleAreaChange()107 void ImagePattern::RegisterVisibleAreaChange()
108 {
109 auto pipeline = PipelineContext::GetCurrentContext();
110 // register to onVisibleAreaChange
111 CHECK_NULL_VOID(pipeline);
112 auto callback = [weak = WeakClaim(this)](bool visible, double ratio) {
113 auto self = weak.Upgrade();
114 CHECK_NULL_VOID(self);
115 LOGD("current image visible ratio = %f", ratio);
116 self->OnVisibleChange(visible);
117 };
118 auto host = GetHost();
119 CHECK_NULL_VOID(host);
120 // reset visibleAreaChangeNode
121 pipeline->RemoveVisibleAreaChangeNode(host->GetId());
122 pipeline->AddVisibleAreaChangeNode(host, 0.0f, callback);
123 }
124
OnImageLoadSuccess()125 void ImagePattern::OnImageLoadSuccess()
126 {
127 auto host = GetHost();
128 CHECK_NULL_VOID(host);
129 const auto& geometryNode = host->GetGeometryNode();
130 CHECK_NULL_VOID(geometryNode);
131 auto imageEventHub = GetEventHub<ImageEventHub>();
132 CHECK_NULL_VOID(imageEventHub);
133 LoadImageSuccessEvent loadImageSuccessEvent_(loadingCtx_->GetImageSize().Width(),
134 loadingCtx_->GetImageSize().Height(), geometryNode->GetFrameSize().Width(),
135 geometryNode->GetFrameSize().Height(), 1);
136 imageEventHub->FireCompleteEvent(loadImageSuccessEvent_);
137 // update src data
138 image_ = loadingCtx_->MoveCanvasImage();
139 srcRect_ = loadingCtx_->GetSrcRect();
140 dstRect_ = loadingCtx_->GetDstRect();
141
142 SetImagePaintConfig(image_, srcRect_, dstRect_, loadingCtx_->GetSourceInfo().IsSvg());
143 PrepareAnimation();
144 if (draggable_) {
145 EnableDrag();
146 }
147 // clear alt data
148 altLoadingCtx_ = nullptr;
149 altImage_ = nullptr;
150 altDstRect_.reset();
151 altSrcRect_.reset();
152 // TODO: only do paint task when the pattern is active
153 // figure out why here is always inactive
154 host->MarkNeedRenderOnly();
155 }
156
OnImageDataReady()157 void ImagePattern::OnImageDataReady()
158 {
159 auto host = GetHost();
160 CHECK_NULL_VOID(host);
161 const auto& geometryNode = host->GetGeometryNode();
162 CHECK_NULL_VOID(geometryNode);
163 auto imageEventHub = GetEventHub<ImageEventHub>();
164 CHECK_NULL_VOID(imageEventHub);
165 LoadImageSuccessEvent loadImageSuccessEvent_(loadingCtx_->GetImageSize().Width(),
166 loadingCtx_->GetImageSize().Height(), geometryNode->GetFrameSize().Width(),
167 geometryNode->GetFrameSize().Height(), 0);
168 imageEventHub->FireCompleteEvent(loadImageSuccessEvent_);
169 if (!host->IsActive()) {
170 return;
171 }
172 if (!geometryNode->GetContent() || (geometryNode->GetContent() && altLoadingCtx_)) {
173 host->MarkDirtyNode(PROPERTY_UPDATE_MEASURE);
174 return;
175 }
176 host->MarkDirtyNode(PROPERTY_UPDATE_LAYOUT);
177 }
178
OnImageLoadFail()179 void ImagePattern::OnImageLoadFail()
180 {
181 auto host = GetHost();
182 CHECK_NULL_VOID(host);
183 const auto& geometryNode = host->GetGeometryNode();
184 auto imageEventHub = GetEventHub<ImageEventHub>();
185 CHECK_NULL_VOID(imageEventHub);
186 LoadImageFailEvent loadImageFailEvent_(
187 geometryNode->GetFrameSize().Width(), geometryNode->GetFrameSize().Height(), "");
188 // TODO: remove errorMsg in fail event
189 imageEventHub->FireErrorEvent(std::move(loadImageFailEvent_));
190 }
191
SetImagePaintConfig(const RefPtr<CanvasImage> & canvasImage,const RectF & srcRect,const RectF & dstRect,bool isSvg)192 void ImagePattern::SetImagePaintConfig(
193 const RefPtr<CanvasImage>& canvasImage, const RectF& srcRect, const RectF& dstRect, bool isSvg)
194 {
195 auto layoutProps = GetLayoutProperty<ImageLayoutProperty>();
196 CHECK_NULL_VOID(layoutProps);
197
198 ImagePaintConfig config {
199 .srcRect_ = srcRect,
200 .dstRect_ = dstRect,
201 };
202 config.imageFit_ = layoutProps->GetImageFit().value_or(ImageFit::COVER);
203 config.isSvg_ = isSvg;
204
205 canvasImage->SetPaintConfig(config);
206 }
207
CreateNodePaintMethod()208 RefPtr<NodePaintMethod> ImagePattern::CreateNodePaintMethod()
209 {
210 if (image_) {
211 return MakeRefPtr<ImagePaintMethod>(image_);
212 }
213 if (altImage_ && altDstRect_ && altSrcRect_) {
214 return MakeRefPtr<ImagePaintMethod>(altImage_);
215 }
216 return nullptr;
217 }
218
OnDirtyLayoutWrapperSwap(const RefPtr<LayoutWrapper> & dirty,const DirtySwapConfig & config)219 bool ImagePattern::OnDirtyLayoutWrapperSwap(const RefPtr<LayoutWrapper>& dirty, const DirtySwapConfig& config)
220 {
221 if (config.skipMeasure || dirty->SkipMeasureContent()) {
222 return false;
223 }
224 return image_;
225 }
226
LoadImageDataIfNeed()227 void ImagePattern::LoadImageDataIfNeed()
228 {
229 auto imageLayoutProperty = GetLayoutProperty<ImageLayoutProperty>();
230 CHECK_NULL_VOID(imageLayoutProperty);
231 auto imageRenderProperty = GetPaintProperty<ImageRenderProperty>();
232 CHECK_NULL_VOID(imageRenderProperty);
233 auto src = imageLayoutProperty->GetImageSourceInfo().value_or(ImageSourceInfo(""));
234 UpdateInternalResource(src);
235 std::optional<Color> svgFillColorOpt = std::nullopt;
236 if (src.IsSvg()) {
237 svgFillColorOpt = src.GetFillColor();
238 }
239
240 if (!loadingCtx_ || loadingCtx_->GetSourceInfo() != src ||
241 (src.IsSvg() && loadingCtx_->GetSvgFillColor() != svgFillColorOpt)) {
242 LoadNotifier loadNotifier(CreateDataReadyCallback(), CreateLoadSuccessCallback(), CreateLoadFailCallback());
243
244 bool syncLoad = imageLayoutProperty->GetSyncModeValue(false);
245 loadingCtx_ = AceType::MakeRefPtr<ImageLoadingContext>(src, std::move(loadNotifier), syncLoad);
246 LOGD("start loading image %{public}s", src.ToString().c_str());
247 loadingCtx_->LoadImageData();
248 }
249 if (loadingCtx_->NeedAlt() && imageLayoutProperty->GetAlt()) {
250 auto altImageSourceInfo = imageLayoutProperty->GetAlt().value_or(ImageSourceInfo(""));
251 std::optional<Color> altSvgFillColorOpt = std::nullopt;
252 if (altImageSourceInfo.IsSvg()) {
253 altSvgFillColorOpt = imageRenderProperty->GetSvgFillColor() ? imageRenderProperty->GetSvgFillColor()
254 : altImageSourceInfo.GetFillColor();
255 }
256 LoadNotifier altLoadNotifier(CreateDataReadyCallbackForAlt(), CreateLoadSuccessCallbackForAlt(), nullptr);
257 if (!altLoadingCtx_ || altLoadingCtx_->GetSourceInfo() != altImageSourceInfo ||
258 (altLoadingCtx_ && altImageSourceInfo.IsSvg() && altSvgFillColorOpt.has_value() &&
259 altLoadingCtx_->GetSvgFillColor() != altSvgFillColorOpt)) {
260 altLoadingCtx_ = AceType::MakeRefPtr<ImageLoadingContext>(altImageSourceInfo, std::move(altLoadNotifier));
261 altLoadingCtx_->LoadImageData();
262 }
263 }
264 }
265
OnModifyDone()266 void ImagePattern::OnModifyDone()
267 {
268 LoadImageDataIfNeed();
269 }
270
CreateDataReadyCallbackForAlt()271 DataReadyNotifyTask ImagePattern::CreateDataReadyCallbackForAlt()
272 {
273 auto task = [weak = WeakClaim(this)](const ImageSourceInfo& sourceInfo) {
274 auto pattern = weak.Upgrade();
275 CHECK_NULL_VOID(pattern);
276 auto imageLayoutProperty = pattern->GetLayoutProperty<ImageLayoutProperty>();
277 CHECK_NULL_VOID(imageLayoutProperty);
278 auto currentAltSourceInfo = imageLayoutProperty->GetAlt().value_or(ImageSourceInfo(""));
279 if (currentAltSourceInfo != sourceInfo) {
280 LOGW("alt image sourceInfo does not match, ignore current callback. current: %{public}s vs callback's: "
281 "%{public}s",
282 currentAltSourceInfo.ToString().c_str(), sourceInfo.ToString().c_str());
283 return;
284 }
285 auto host = pattern->GetHost();
286 CHECK_NULL_VOID(host);
287 if (!host->IsActive()) {
288 return;
289 }
290 const auto& geometryNode = host->GetGeometryNode();
291 CHECK_NULL_VOID(geometryNode);
292 if (!geometryNode->GetContent()) {
293 host->MarkDirtyNode(PROPERTY_UPDATE_MEASURE);
294 return;
295 }
296
297 // calculate params for [altLoadingCtx] to do [MakeCanvasImage] if component size is already settled
298 pattern->altLoadingCtx_->MakeCanvasImageIfNeed(
299 geometryNode->GetContentSize(), true, imageLayoutProperty->GetImageFit().value_or(ImageFit::COVER));
300 };
301 return task;
302 }
303
CreateLoadSuccessCallbackForAlt()304 LoadSuccessNotifyTask ImagePattern::CreateLoadSuccessCallbackForAlt()
305 {
306 auto task = [weak = WeakClaim(this)](const ImageSourceInfo& sourceInfo) {
307 auto pattern = weak.Upgrade();
308 CHECK_NULL_VOID(pattern);
309 CHECK_NULL_VOID(pattern->altLoadingCtx_);
310 auto imageLayoutProperty = pattern->GetLayoutProperty<ImageLayoutProperty>();
311 auto currentAltSourceInfo = imageLayoutProperty->GetAlt().value_or(ImageSourceInfo(""));
312 if (currentAltSourceInfo != sourceInfo) {
313 LOGW("alt image sourceInfo does not match, ignore current callback. current: %{public}s vs callback's: "
314 "%{public}s",
315 currentAltSourceInfo.ToString().c_str(), sourceInfo.ToString().c_str());
316 return;
317 }
318 auto host = pattern->GetHost();
319 CHECK_NULL_VOID(host);
320 host->MarkDirtyNode(PROPERTY_UPDATE_RENDER);
321 pattern->altImage_ = pattern->altLoadingCtx_->MoveCanvasImage();
322 pattern->altSrcRect_ = std::make_unique<RectF>(pattern->altLoadingCtx_->GetSrcRect());
323 pattern->altDstRect_ = std::make_unique<RectF>(pattern->altLoadingCtx_->GetDstRect());
324 pattern->SetImagePaintConfig(pattern->altImage_, *pattern->altSrcRect_, *pattern->altDstRect_,
325 pattern->altLoadingCtx_->GetSourceInfo().IsSvg());
326 };
327 return task;
328 }
329
UpdateInternalResource(ImageSourceInfo & sourceInfo)330 void ImagePattern::UpdateInternalResource(ImageSourceInfo& sourceInfo)
331 {
332 if (!sourceInfo.IsInternalResource()) {
333 return;
334 }
335
336 auto pipeline = PipelineBase::GetCurrentContext();
337 CHECK_NULL_VOID(pipeline);
338 auto iconTheme = pipeline->GetTheme<IconTheme>();
339 CHECK_NULL_VOID(iconTheme);
340 auto iconPath = iconTheme->GetIconPath(sourceInfo.GetResourceId());
341 if (!iconPath.empty()) {
342 sourceInfo.SetSrc(iconPath, sourceInfo.GetFillColor());
343 auto imageLayoutProperty = GetLayoutProperty<ImageLayoutProperty>();
344 CHECK_NULL_VOID(imageLayoutProperty);
345 imageLayoutProperty->UpdateImageSourceInfo(sourceInfo);
346 }
347 }
348
OnNotifyMemoryLevel(int32_t level)349 void ImagePattern::OnNotifyMemoryLevel(int32_t level)
350 {
351 LOGI("Receive Memory level notification, level: %{public}d", level);
352 // TODO: do different data cleaning operation according to level
353 // when image component is [onShow], do not clean image data
354 // TODO: use [isActive_] to determine image data management
355 if (isShow_) {
356 return;
357 }
358 // TODO: clean cache data when cache mechanism is ready
359 // Step1: drive stateMachine to reset loading procedure
360 if (altLoadingCtx_) {
361 altLoadingCtx_->ResetLoading();
362 }
363 if (loadingCtx_) {
364 loadingCtx_->ResetLoading();
365 }
366
367 // Step2: clean data and reset params
368 // clear src data
369 image_ = nullptr;
370 srcRect_ = RectF();
371 dstRect_ = RectF();
372 // clear alt data
373 altLoadingCtx_ = nullptr;
374 altImage_ = nullptr;
375 altDstRect_.reset();
376 altSrcRect_.reset();
377
378 // Step3: clean rs node to release the sk_sp<SkImage> held by it
379 // TODO: release PixelMap resource when use PixelMap resource to draw image
380 auto frameNode = GetHost();
381 CHECK_NULL_VOID(frameNode);
382 auto rsRenderContext = frameNode->GetRenderContext();
383 CHECK_NULL_VOID(rsRenderContext);
384 rsRenderContext->ClearDrawCommands();
385 auto pipeline = PipelineContext::GetCurrentContext();
386 CHECK_NULL_VOID(pipeline);
387 pipeline->FlushMessages();
388 }
389
OnWindowHide()390 void ImagePattern::OnWindowHide()
391 {
392 isShow_ = false;
393 }
394
OnWindowShow()395 void ImagePattern::OnWindowShow()
396 {
397 isShow_ = true;
398 LoadImageDataIfNeed();
399 }
400
OnVisibleChange(bool visible)401 void ImagePattern::OnVisibleChange(bool visible)
402 {
403 CHECK_NULL_VOID_NOLOG(image_);
404 // control svg / gif animation
405 image_->ControlAnimation(visible);
406 }
407
OnAttachToFrameNode()408 void ImagePattern::OnAttachToFrameNode()
409 {
410 auto host = GetHost();
411 CHECK_NULL_VOID(host);
412 host->GetRenderContext()->SetClipToBounds(false);
413
414 // register image frame node to pipeline context to receive memory level notification and window state change
415 // notification
416 auto pipeline = PipelineContext::GetCurrentContext();
417 CHECK_NULL_VOID(pipeline);
418 pipeline->AddNodesToNotifyMemoryLevel(host->GetId());
419 pipeline->AddWindowStateChangedCallback(host->GetId());
420 }
421
OnDetachFromFrameNode(FrameNode * frameNode)422 void ImagePattern::OnDetachFromFrameNode(FrameNode* frameNode)
423 {
424 auto id = frameNode->GetId();
425 auto pipeline = AceType::DynamicCast<PipelineContext>(PipelineBase::GetCurrentContext());
426 CHECK_NULL_VOID_NOLOG(pipeline);
427 pipeline->RemoveWindowStateChangedCallback(id);
428 pipeline->RemoveNodesToNotifyMemoryLevel(id);
429 pipeline->RemoveVisibleAreaChangeNode(id);
430 }
431
EnableDrag()432 void ImagePattern::EnableDrag()
433 {
434 auto host = GetHost();
435 CHECK_NULL_VOID(host);
436 auto size = host->GetGeometryNode()->GetContentSize();
437 auto dragStart = [imageWk = WeakClaim(RawPtr(image_)), ctxWk = WeakClaim(RawPtr(loadingCtx_)), size](
438 const RefPtr<OHOS::Ace::DragEvent>& /*event*/,
439 const std::string& /*extraParams*/) -> DragDropInfo {
440 DragDropInfo info;
441 auto image = imageWk.Upgrade();
442 auto ctx = ctxWk.Upgrade();
443 CHECK_NULL_RETURN(image && ctx, info);
444
445 info.extraInfo = "image drag";
446 info.pixelMap = image->GetPixelMap();
447 if (info.pixelMap) {
448 LOGI("using pixmap onDrag");
449 return info;
450 }
451
452 auto node = FrameNode::GetOrCreateFrameNode(V2::IMAGE_ETS_TAG, ElementRegister::GetInstance()->MakeUniqueId(),
453 []() { return AceType::MakeRefPtr<ImagePattern>(); });
454 auto pattern = node->GetPattern<ImagePattern>();
455 pattern->image_ = image;
456 pattern->loadingCtx_ = ctx;
457
458 auto props = node->GetLayoutProperty<ImageLayoutProperty>();
459 props->UpdateImageSourceInfo(ctx->GetSourceInfo());
460 // set dragged image size to match this image
461 props->UpdateUserDefinedIdealSize(CalcSize(CalcLength(size.Width()), CalcLength(size.Height())));
462
463 info.customNode = node;
464 return info;
465 };
466 auto eventHub = GetHost()->GetEventHub<EventHub>();
467 CHECK_NULL_VOID(eventHub);
468 eventHub->SetOnDragStart(std::move(dragStart));
469 }
470
ToJsonValue(std::unique_ptr<JsonValue> & json) const471 void ImagePattern::ToJsonValue(std::unique_ptr<JsonValue>& json) const
472 {
473 json->Put("draggable", draggable_ ? "true" : "false");
474 }
475
476 } // namespace OHOS::Ace::NG
477