tessera_ui_basic_components/
image.rs

1use std::sync::Arc;
2
3use derive_builder::Builder;
4use image::GenericImageView;
5use tessera_ui::{ComputedData, Constraint, DimensionValue, Px};
6use tessera_ui_macros::tessera;
7
8use crate::pipelines::image::{ImageCommand, ImageData};
9
10/// Source of the image data.
11#[derive(Clone, Debug)]
12pub enum ImageSource {
13    /// Load image from a file path.
14    Path(String),
15    /// Load image from raw bytes.
16    Bytes(Arc<[u8]>),
17}
18
19/// Decodes an image from a given source.
20///
21/// This function should be called outside of the component's `measure` closure
22/// to avoid decoding the image on every frame.
23pub fn load_image_from_source(source: &ImageSource) -> Result<ImageData, image::ImageError> {
24    let decoded = match source {
25        ImageSource::Path(path) => image::open(path)?,
26        ImageSource::Bytes(bytes) => image::load_from_memory(bytes)?,
27    };
28    let (width, height) = decoded.dimensions();
29    Ok(ImageData {
30        data: Arc::new(decoded.to_rgba8().into_raw()),
31        width,
32        height,
33    })
34}
35
36/// Arguments for the `image` component.
37#[derive(Debug, Builder, Clone)]
38#[builder(pattern = "owned")]
39pub struct ImageArgs {
40    /// The decoded image data.
41    pub data: ImageData,
42
43    /// Optional explicit width for the image.
44    #[builder(default, setter(strip_option))]
45    pub width: Option<DimensionValue>,
46
47    /// Optional explicit height for the image.
48    #[builder(default, setter(strip_option))]
49    pub height: Option<DimensionValue>,
50}
51
52impl From<ImageData> for ImageArgs {
53    fn from(data: ImageData) -> Self {
54        ImageArgsBuilder::default().data(data).build().unwrap()
55    }
56}
57
58#[tessera]
59pub fn image(args: impl Into<ImageArgs>) {
60    let image_args: ImageArgs = args.into();
61
62    measure(Box::new(move |input| {
63        let intrinsic_width = Px(image_args.data.width as i32);
64        let intrinsic_height = Px(image_args.data.height as i32);
65
66        let image_intrinsic_width = image_args.width.unwrap_or(DimensionValue::Wrap {
67            min: Some(intrinsic_width),
68            max: Some(intrinsic_width),
69        });
70        let image_intrinsic_height = image_args.height.unwrap_or(DimensionValue::Wrap {
71            min: Some(intrinsic_height),
72            max: Some(intrinsic_height),
73        });
74
75        let image_intrinsic_constraint =
76            Constraint::new(image_intrinsic_width, image_intrinsic_height);
77        let effective_image_constraint = image_intrinsic_constraint.merge(input.parent_constraint);
78
79        let width = match effective_image_constraint.width {
80            DimensionValue::Fixed(value) => value,
81            DimensionValue::Wrap { min, max } => min
82                .unwrap_or(Px(0))
83                .max(intrinsic_width)
84                .min(max.unwrap_or(Px::MAX)),
85            DimensionValue::Fill { min, max } => {
86                let parent_max = input.parent_constraint.width.to_max_px(Px::MAX);
87                max.unwrap_or(parent_max)
88                    .max(min.unwrap_or(Px(0)))
89                    .max(intrinsic_width)
90            }
91        };
92
93        let height = match effective_image_constraint.height {
94            DimensionValue::Fixed(value) => value,
95            DimensionValue::Wrap { min, max } => min
96                .unwrap_or(Px(0))
97                .max(intrinsic_height)
98                .min(max.unwrap_or(Px::MAX)),
99            DimensionValue::Fill { min, max } => {
100                let parent_max = input.parent_constraint.height.to_max_px(Px::MAX);
101                max.unwrap_or(parent_max)
102                    .max(min.unwrap_or(Px(0)))
103                    .max(intrinsic_height)
104            }
105        };
106
107        let image_command = ImageCommand {
108            data: image_args.data.clone(),
109        };
110
111        input
112            .metadatas
113            .entry(input.current_node_id)
114            .or_default()
115            .push_draw_command(image_command);
116
117        Ok(ComputedData { width, height })
118    }));
119}