1 /*
2 * Copyright (c) 2022-2025 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_layout_algorithm.h"
17
18 #include "core/components_ng/pattern/image/image_pattern.h"
19 #include "core/components_ng/property/measure_utils.h"
20 #include "core/pipeline_ng/pipeline_context.h"
21
22 namespace OHOS::Ace::NG {
23 namespace {
24 // returns maximum size of image component
25 // if maxSize is infinite, match screen size and retain aspectRatio
GetMaxSize(const SizeF & maxSize,float aspectRatio)26 SizeF GetMaxSize(const SizeF& maxSize, float aspectRatio)
27 {
28 if (NearZero(aspectRatio)) {
29 return { 0.0, 0.0 };
30 }
31 // infinite size not allowed
32 bool infWidth = GreaterOrEqualToInfinity(maxSize.Width());
33 bool infHeight = GreaterOrEqualToInfinity(maxSize.Height());
34 if (infWidth && infHeight) {
35 auto width = PipelineContext::GetCurrentRootWidth();
36 return { width, width / aspectRatio };
37 }
38 if (infWidth) {
39 return { maxSize.Height() * aspectRatio, maxSize.Height() };
40 }
41 if (infHeight) {
42 return { maxSize.Width(), maxSize.Width() / aspectRatio };
43 }
44 return maxSize;
45 }
46 } // namespace
47
MeasureContent(const LayoutConstraintF & contentConstraint,LayoutWrapper * layoutWrapper)48 std::optional<SizeF> ImageLayoutAlgorithm::MeasureContent(
49 const LayoutConstraintF& contentConstraint, LayoutWrapper* layoutWrapper)
50 {
51 // case 1: image component is set with valid size, return contentConstraint.selfIdealSize as component size
52 if (contentConstraint.selfIdealSize.IsValid()) {
53 return contentConstraint.selfIdealSize.ConvertToSizeT();
54 }
55 // case 2: image component is not set with size, use image source size to determine component size
56 // if image data and altImage are both not ready, can not decide content size,
57 // return std::nullopt and wait for next layout task triggered by [OnImageDataReady]
58 auto host = layoutWrapper->GetHostNode();
59 CHECK_NULL_RETURN(host, std::nullopt);
60 auto pattern = host->GetPattern<ImagePattern>();
61 CHECK_NULL_RETURN(pattern, std::nullopt);
62 auto rawImageSize = pattern->GetImageSizeForMeasure();
63 if (rawImageSize == std::nullopt) {
64 return std::nullopt;
65 }
66 SizeF size(rawImageSize.value());
67 do {
68 auto aspectRatio = static_cast<float>(Size::CalcRatio(rawImageSize.value()));
69 if (NearZero(aspectRatio)) {
70 TAG_LOGW(AceLogTag::ACE_IMAGE, "image aspectRatio is 0");
71 return std::nullopt;
72 }
73 // case 2.1: image component is not set with size, use image source size as image component size
74 // if fitOriginalSize is true, use image source size as image component size
75 // if fitOriginalSize is false, use the parent component LayoutConstraint size as image component size
76 const auto& props = DynamicCast<ImageLayoutProperty>(layoutWrapper->GetLayoutProperty());
77 bool fitOriginalSize = props->GetFitOriginalSize().value_or(false);
78 if (contentConstraint.selfIdealSize.IsNull()) {
79 if (!fitOriginalSize) {
80 size.SetSizeT(GetMaxSize(contentConstraint.maxSize, aspectRatio));
81 }
82 break;
83 }
84 // case 2.2 image component is set with width or height, and
85 // image data is ready, use image source size to determine image component size
86 // keep the principle of making the component aspect ratio and the image source aspect ratio the same.
87 // the fitOriginSize is only useful in case 2.1.
88 auto sizeSet = contentConstraint.selfIdealSize.ConvertToSizeT();
89 size.SetSizeT(sizeSet);
90 uint8_t sizeSetStatus = (Negative(sizeSet.Width()) << 1) | Negative(sizeSet.Height());
91 switch (sizeSetStatus) {
92 case 0b01: // width is positive and height is negative
93 size.SetHeight(sizeSet.Width() / aspectRatio);
94 break;
95 case 0b10: // width is negative and height is positive
96 size.SetWidth(sizeSet.Height() * aspectRatio);
97 break;
98 case 0b11: // both width and height are negative
99 default:
100 break;
101 }
102 } while (false);
103 return contentConstraint.Constrain(size);
104 }
105
106 /**
107 * Measure the layout size of the Image component based on its layout policy.
108 *
109 * This method handles different layout policies:
110 * - For WRAP_CONTENT or FIXED policies, it calculates the image's frame size using its raw size.
111 * - For MATCH_PARENT, it delegates the measurement to the base BoxLayoutAlgorithm and uses the result as the frame
112 * size. If a valid size is determined, it is set on the geometry node.
113 */
Measure(LayoutWrapper * layoutWrapper)114 void ImageLayoutAlgorithm::Measure(LayoutWrapper* layoutWrapper)
115 {
116 CHECK_NULL_VOID(layoutWrapper);
117 const auto& layoutProperty = layoutWrapper->GetLayoutProperty();
118 CHECK_NULL_VOID(layoutProperty);
119 auto layoutPolicy = layoutProperty->GetLayoutPolicyProperty();
120 bool isPolicy = layoutPolicy.has_value();
121 if (isPolicy && (layoutPolicy->IsWrap() || layoutPolicy->IsFix())) {
122 auto host = layoutWrapper->GetHostNode();
123 CHECK_NULL_VOID(host);
124 auto pattern = host->GetPattern<ImagePattern>();
125 CHECK_NULL_VOID(pattern);
126 auto rawImageSize = pattern->GetImageSizeForMeasure();
127 if (rawImageSize == std::nullopt) {
128 return;
129 }
130 OptionalSizeF imageFrameSize;
131 if (layoutPolicy->IsMatch()) {
132 BoxLayoutAlgorithm::Measure(layoutWrapper);
133 imageFrameSize.SetWidth(layoutWrapper->GetGeometryNode()->GetFrameSize().Width());
134 imageFrameSize.SetHeight(layoutWrapper->GetGeometryNode()->GetFrameSize().Height());
135 }
136 UpdateFrameSizeWithLayoutPolicy(layoutWrapper, imageFrameSize, rawImageSize);
137 if (imageFrameSize.IsValid()) {
138 layoutWrapper->GetGeometryNode()->SetFrameSize(imageFrameSize.ConvertToSizeT());
139 }
140 } else {
141 BoxLayoutAlgorithm::Measure(layoutWrapper);
142 }
143 }
144
145 /**
146 * Update the frame size of the image (`imageFrameSize`) based on the layout policy defined in the layout property.
147 *
148 * This method handles different layout policies for width and height:
149 * - WRAP_CONTENT:
150 * - If width or height is set to WRAP, use the smaller of the raw image size and the parent ideal size.
151 * - This ensures the image adapts to its content but remains within parent constraints.
152 *
153 * - FIX_AT_IDEAL_SIZE:
154 * - If width or height is FIXED, directly use the raw image's dimensions.
155 * - This simulates a hardcoded size, unaffected by surrounding layout constraints.
156 *
157 * - MATCH_PARENT:
158 * - MATCH is not handled here; it is assumed to be processed by other logic.
159 * - Typically used when the image should stretch to fill the available space.
160 *
161 * - Default (neither WRAP, FIXED, nor MATCH):
162 * - Use the self ideal size if specified, otherwise fall back to raw image size.
163 */
UpdateFrameSizeWithLayoutPolicy(LayoutWrapper * layoutWrapper,OptionalSizeF & imageFrameSize,const std::optional<SizeF> & rawImageSize)164 void ImageLayoutAlgorithm::UpdateFrameSizeWithLayoutPolicy(
165 LayoutWrapper* layoutWrapper, OptionalSizeF& imageFrameSize, const std::optional<SizeF>& rawImageSize)
166 {
167 // Update frame size based on layout policy
168 auto layoutProperty = AceType::DynamicCast<ImageLayoutProperty>(layoutWrapper->GetLayoutProperty());
169 CHECK_NULL_VOID(layoutProperty);
170 auto layoutPolicy = layoutProperty->GetLayoutPolicyProperty();
171 auto layoutConstraint = layoutProperty->GetLayoutConstraint();
172 CHECK_NULL_VOID(layoutConstraint.has_value());
173 // Calculate the frameWidth size based on the layout policy
174 if (layoutPolicy->IsWidthWrap()) {
175 auto parentIdealSize = layoutConstraint->parentIdealSize;
176 imageFrameSize.SetWidth(
177 std::min(parentIdealSize.Width().value_or(rawImageSize->Width()), rawImageSize->Width()));
178 } else if (layoutPolicy->IsWidthFix()) {
179 // If width is fixed, use the raw image size
180 imageFrameSize.SetWidth(rawImageSize->Width());
181 } else if (!layoutPolicy->IsWidthMatch()) {
182 imageFrameSize.SetWidth(layoutConstraint->selfIdealSize.Width().value_or(
183 layoutConstraint->parentIdealSize.Width().value_or(rawImageSize->Width())));
184 }
185 // Calculate the frameHeight size based on the layout policy
186 if (layoutPolicy->IsHeightWrap()) {
187 auto parentIdealSize = layoutConstraint->parentIdealSize;
188 imageFrameSize.SetHeight(
189 std::min(parentIdealSize.Height().value_or(rawImageSize->Height()), rawImageSize->Height()));
190 } else if (layoutPolicy->IsHeightFix()) {
191 // If height is fixed, use the raw image size
192 imageFrameSize.SetHeight(rawImageSize->Height());
193 } else if (!layoutPolicy->IsHeightMatch()) {
194 imageFrameSize.SetHeight(layoutConstraint->selfIdealSize.Height().value_or(
195 layoutConstraint->parentIdealSize.Height().value_or(rawImageSize->Height())));
196 }
197 }
198
Layout(LayoutWrapper * layoutWrapper)199 void ImageLayoutAlgorithm::Layout(LayoutWrapper* layoutWrapper)
200 {
201 CHECK_NULL_VOID(layoutWrapper);
202 if (IsImageAnimationLayout(layoutWrapper)) {
203 PerformImageAnimationLayout(layoutWrapper);
204 for (auto&& child : layoutWrapper->GetAllChildrenWithBuild()) {
205 child->Layout();
206 }
207 return;
208 }
209 BoxLayoutAlgorithm::Layout(layoutWrapper);
210 auto host = layoutWrapper->GetHostNode();
211 CHECK_NULL_VOID(host);
212 auto pattern = host->GetPattern<ImagePattern>();
213 CHECK_NULL_VOID(pattern);
214 pattern->FinishMeasureForOnComplete();
215 }
216
PerformImageAnimationLayout(LayoutWrapper * layoutWrapper)217 void ImageLayoutAlgorithm::PerformImageAnimationLayout(LayoutWrapper* layoutWrapper)
218 {
219 // update child position.
220 CHECK_NULL_VOID(layoutWrapper);
221 auto size = layoutWrapper->GetGeometryNode()->GetFrameSize();
222 const auto& padding = layoutWrapper->GetLayoutProperty()->CreatePaddingAndBorder();
223 MinusPaddingToSize(padding, size);
224 auto left = padding.left.value_or(0);
225 auto top = padding.top.value_or(0);
226 auto paddingOffset = OffsetF(left, top);
227 auto align = Alignment::CENTER;
228 if (layoutWrapper->GetLayoutProperty()->GetPositionProperty()) {
229 align = layoutWrapper->GetLayoutProperty()->GetPositionProperty()->GetAlignment().value_or(align);
230 }
231 // Update child position.
232 for (const auto& child : layoutWrapper->GetAllChildrenWithBuild()) {
233 if (!child) {
234 continue;
235 }
236 SizeF childSize = child->GetGeometryNode()->GetMarginFrameSize();
237 auto translate = Alignment::GetAlignPosition(size, childSize, align);
238 if (!child->GetHostNode() || child->GetHostNode()->GetTag() != V2::IMAGE_ETS_TAG) {
239 translate += paddingOffset;
240 }
241 child->GetGeometryNode()->SetMarginFrameOffset(translate);
242 }
243 // Update content position.
244 const auto& content = layoutWrapper->GetGeometryNode()->GetContent();
245 if (content) {
246 auto translate = Alignment::GetAlignPosition(size, content->GetRect().GetSize(), align) + paddingOffset;
247 content->SetOffset(translate);
248 }
249 }
250
IsImageAnimationLayout(LayoutWrapper * layoutWrapper)251 bool ImageLayoutAlgorithm::IsImageAnimationLayout(LayoutWrapper* layoutWrapper)
252 {
253 CHECK_NULL_RETURN(layoutWrapper, false);
254 auto frameNode = layoutWrapper->GetHostNode();
255 CHECK_NULL_RETURN(frameNode, false);
256 auto pattern = AceType::DynamicCast<ImagePattern>(frameNode->GetPattern());
257 CHECK_NULL_RETURN(pattern, false);
258 CHECK_EQUAL_RETURN(pattern->GetIsAnimation(), true, true);
259 return false;
260 }
261 } // namespace OHOS::Ace::NG
262