1#![allow(non_snake_case)]
4#![allow(clippy::too_many_arguments)] use crate::composable;
7use crate::layout::core::{Alignment, Measurable};
8use crate::modifier::{Modifier, Rect, Size};
9use crate::widgets::Layout;
10use cranpose_core::NodeId;
11use cranpose_ui_graphics::{ColorFilter, DrawScope, ImageBitmap};
12use cranpose_ui_layout::{Constraints, MeasurePolicy, MeasureResult};
13
14pub const DEFAULT_ALPHA: f32 = 1.0;
15
16#[derive(Clone, Copy, Debug, PartialEq)]
17pub enum ContentScale {
18 Fit,
19 Crop,
20 FillBounds,
21 FillWidth,
22 FillHeight,
23 Inside,
24 None,
25}
26
27impl ContentScale {
28 pub fn scaled_size(self, src_size: Size, dst_size: Size) -> Size {
29 if src_size.width <= 0.0
30 || src_size.height <= 0.0
31 || dst_size.width <= 0.0
32 || dst_size.height <= 0.0
33 {
34 return Size::ZERO;
35 }
36
37 let scale_x = dst_size.width / src_size.width;
38 let scale_y = dst_size.height / src_size.height;
39
40 let (factor_x, factor_y) = match self {
41 Self::Fit => {
42 let factor = scale_x.min(scale_y);
43 (factor, factor)
44 }
45 Self::Crop => {
46 let factor = scale_x.max(scale_y);
47 (factor, factor)
48 }
49 Self::FillBounds => (scale_x, scale_y),
50 Self::FillWidth => (scale_x, scale_x),
51 Self::FillHeight => (scale_y, scale_y),
52 Self::Inside => {
53 if src_size.width <= dst_size.width && src_size.height <= dst_size.height {
54 (1.0, 1.0)
55 } else {
56 let factor = scale_x.min(scale_y);
57 (factor, factor)
58 }
59 }
60 Self::None => (1.0, 1.0),
61 };
62
63 Size {
64 width: src_size.width * factor_x,
65 height: src_size.height * factor_y,
66 }
67 }
68}
69
70#[derive(Clone, Debug, PartialEq, Eq, Hash)]
71pub struct Painter {
72 bitmap: ImageBitmap,
73}
74
75impl Painter {
76 pub fn from_bitmap(bitmap: ImageBitmap) -> Self {
77 Self { bitmap }
78 }
79
80 pub fn intrinsic_size(&self) -> Size {
81 self.bitmap.intrinsic_size()
82 }
83
84 pub fn bitmap(&self) -> &ImageBitmap {
85 &self.bitmap
86 }
87}
88
89impl From<ImageBitmap> for Painter {
90 fn from(value: ImageBitmap) -> Self {
91 Self::from_bitmap(value)
92 }
93}
94
95pub fn BitmapPainter(bitmap: ImageBitmap) -> Painter {
96 Painter::from_bitmap(bitmap)
97}
98
99#[derive(Clone, Debug, PartialEq)]
106struct ImageMeasurePolicy {
107 intrinsic_size: Size,
108}
109
110impl MeasurePolicy for ImageMeasurePolicy {
111 fn measure(
112 &self,
113 _measurables: &[Box<dyn Measurable>],
114 constraints: Constraints,
115 ) -> MeasureResult {
116 let iw = self.intrinsic_size.width;
117 let ih = self.intrinsic_size.height;
118
119 if iw <= 0.0 || ih <= 0.0 {
120 let (w, h) = constraints.constrain(0.0, 0.0);
121 return MeasureResult::new(
122 Size {
123 width: w,
124 height: h,
125 },
126 vec![],
127 );
128 }
129
130 let cw = iw.clamp(constraints.min_width, constraints.max_width);
132 let ch = ih.clamp(constraints.min_height, constraints.max_height);
133
134 let scale_x = cw / iw;
137 let scale_y = ch / ih;
138
139 let (width, height) = if scale_x < 1.0 || scale_y < 1.0 {
140 let factor = scale_x.min(scale_y);
141 let w = (iw * factor).clamp(constraints.min_width, constraints.max_width);
142 let h = (ih * factor).clamp(constraints.min_height, constraints.max_height);
143 (w, h)
144 } else {
145 (cw, ch)
146 };
147
148 MeasureResult::new(Size { width, height }, vec![])
149 }
150
151 fn min_intrinsic_width(&self, _measurables: &[Box<dyn Measurable>], _height: f32) -> f32 {
152 self.intrinsic_size.width
153 }
154
155 fn max_intrinsic_width(&self, _measurables: &[Box<dyn Measurable>], _height: f32) -> f32 {
156 self.intrinsic_size.width
157 }
158
159 fn min_intrinsic_height(&self, _measurables: &[Box<dyn Measurable>], _width: f32) -> f32 {
160 self.intrinsic_size.height
161 }
162
163 fn max_intrinsic_height(&self, _measurables: &[Box<dyn Measurable>], _width: f32) -> f32 {
164 self.intrinsic_size.height
165 }
166}
167
168fn destination_rect(
169 src_size: Size,
170 dst_size: Size,
171 alignment: Alignment,
172 content_scale: ContentScale,
173) -> Rect {
174 let draw_size = content_scale.scaled_size(src_size, dst_size);
175 let offset_x = alignment.horizontal.align(dst_size.width, draw_size.width);
176 let offset_y = alignment.vertical.align(dst_size.height, draw_size.height);
177 Rect {
178 x: offset_x,
179 y: offset_y,
180 width: draw_size.width,
181 height: draw_size.height,
182 }
183}
184
185#[composable]
186pub fn Image<P>(
187 painter: P,
188 content_description: Option<String>,
189 modifier: Modifier,
190 alignment: Alignment,
191 content_scale: ContentScale,
192 alpha: f32,
193 color_filter: Option<ColorFilter>,
194) -> NodeId
195where
196 P: Into<Painter> + Clone + PartialEq + 'static,
197{
198 let painter = painter.into();
199 let intrinsic_dp = painter.intrinsic_size();
204 let draw_alpha = alpha.clamp(0.0, 1.0);
205 let draw_painter = painter.clone();
206
207 let semantics_modifier = if let Some(description) = content_description {
208 Modifier::empty().semantics(move |config| {
209 config.content_description = Some(description.clone());
210 })
211 } else {
212 Modifier::empty()
213 };
214
215 let image_modifier = modifier
216 .then(semantics_modifier)
217 .clip_to_bounds()
218 .draw_behind(move |scope: &mut dyn DrawScope| {
219 if draw_alpha <= 0.0 {
220 return;
221 }
222 let container_size = scope.size();
223 let rect = destination_rect(intrinsic_dp, container_size, alignment, content_scale);
224 if rect.width <= 0.0 || rect.height <= 0.0 {
225 return;
226 }
227 scope.draw_image_at(
228 rect,
229 draw_painter.bitmap().clone(),
230 draw_alpha,
231 color_filter,
232 );
233 });
234
235 Layout(
236 image_modifier,
237 ImageMeasurePolicy {
238 intrinsic_size: intrinsic_dp,
239 },
240 || {},
241 )
242}
243
244#[cfg(test)]
245mod tests {
246 use super::*;
247 use crate::layout::core::Alignment;
248
249 fn sample_bitmap() -> ImageBitmap {
250 ImageBitmap::from_rgba8(4, 2, vec![255; 4 * 2 * 4]).expect("bitmap")
251 }
252
253 #[test]
254 fn painter_reports_intrinsic_size_and_bitmap() {
255 let bitmap = sample_bitmap();
256 let painter = BitmapPainter(bitmap.clone());
257 assert_eq!(painter.intrinsic_size(), Size::new(4.0, 2.0));
258 assert_eq!(painter.bitmap(), &bitmap);
259 }
260
261 #[test]
262 fn fit_keeps_aspect_ratio() {
263 let src = Size::new(200.0, 100.0);
264 let dst = Size::new(300.0, 300.0);
265 let result = ContentScale::Fit.scaled_size(src, dst);
266 assert_eq!(result, Size::new(300.0, 150.0));
267 }
268
269 #[test]
270 fn crop_fills_bounds() {
271 let src = Size::new(200.0, 100.0);
272 let dst = Size::new(300.0, 300.0);
273 let result = ContentScale::Crop.scaled_size(src, dst);
274 assert_eq!(result, Size::new(600.0, 300.0));
275 }
276
277 #[test]
278 fn destination_rect_aligns_center() {
279 let src = Size::new(200.0, 100.0);
280 let dst = Size::new(300.0, 300.0);
281 let rect = destination_rect(src, dst, Alignment::CENTER, ContentScale::Fit);
282 assert_eq!(
283 rect,
284 Rect {
285 x: 0.0,
286 y: 75.0,
287 width: 300.0,
288 height: 150.0,
289 }
290 );
291 }
292
293 fn measure_image(intrinsic: Size, constraints: Constraints) -> Size {
296 let policy = ImageMeasurePolicy {
297 intrinsic_size: intrinsic,
298 };
299 policy.measure(&[], constraints).size
300 }
301
302 #[test]
303 fn image_measure_unconstrained() {
304 let size = measure_image(
305 Size::new(800.0, 600.0),
306 Constraints::loose(f32::INFINITY, f32::INFINITY),
307 );
308 assert_eq!(size, Size::new(800.0, 600.0));
309 }
310
311 #[test]
312 fn image_measure_width_constrained_preserves_aspect_ratio() {
313 let size = measure_image(
315 Size::new(800.0, 600.0),
316 Constraints::loose(400.0, f32::INFINITY),
317 );
318 assert_eq!(size, Size::new(400.0, 300.0));
319 }
320
321 #[test]
322 fn image_measure_height_constrained_preserves_aspect_ratio() {
323 let size = measure_image(
325 Size::new(800.0, 600.0),
326 Constraints::loose(f32::INFINITY, 300.0),
327 );
328 assert_eq!(size, Size::new(400.0, 300.0));
329 }
330
331 #[test]
332 fn image_measure_both_constrained_uses_smaller_factor() {
333 let size = measure_image(Size::new(800.0, 600.0), Constraints::loose(200.0, 400.0));
336 assert_eq!(size, Size::new(200.0, 150.0));
337 }
338
339 #[test]
340 fn image_measure_fits_within_constraints() {
341 let size = measure_image(Size::new(200.0, 100.0), Constraints::loose(400.0, 400.0));
343 assert_eq!(size, Size::new(200.0, 100.0));
344 }
345
346 #[test]
347 fn image_measure_zero_intrinsic() {
348 let size = measure_image(Size::ZERO, Constraints::loose(400.0, 400.0));
349 assert_eq!(size, Size::new(0.0, 0.0));
350 }
351}