Skip to main content

tessera_components/
image.rs

1//! Raster image component and decoding utilities.
2//!
3//! ## Usage
4//!
5//! Use to display images from pre-decoded data or bytes/assets loaded once.
6use tessera_ui::{
7    Color, ComputedData, LayoutResult, MeasurementError, Modifier, Px,
8    layout::{MeasureScope, layout},
9    tessera,
10};
11
12use crate::{
13    image_vector::TintMode,
14    painter::Painter,
15    pipelines::{image::command::ImageCommand, image_vector::command::ImageVectorCommand},
16};
17
18pub use crate::pipelines::image::command::ImageData;
19
20fn placeholder_image_data() -> ImageData {
21    ImageData::from_rgba(vec![0, 0, 0, 0], 1, 1)
22}
23
24pub(crate) fn decode_raster(bytes: &[u8]) -> Result<ImageData, image::ImageError> {
25    let decoded = image::load_from_memory(bytes)?.into_rgba8();
26    let (width, height) = decoded.dimensions();
27    Ok(ImageData::from_rgba(decoded.into_raw(), width, height))
28}
29
30impl ImageBuilder {
31    /// Replaces the image content with a shared painter value.
32    pub fn painter(&self, painter: impl Into<Painter>) -> Self {
33        let value = Some(painter.into());
34        self.props
35            .configure("painter", move |props| props.painter = value);
36        Self { props: self.props }
37    }
38}
39
40#[derive(Clone, PartialEq)]
41struct ImageLayout {
42    painter: Painter,
43}
44
45impl tessera_ui::LayoutModifierNode for ImageLayout {
46    fn measure(&self, input: &mut MeasureScope<'_>) -> Result<LayoutResult, MeasurementError> {
47        let (intrinsic_width, intrinsic_height) = match &self.painter {
48            Painter::Source(_) => (Px::ZERO, Px::ZERO),
49            Painter::Vector(data) => (
50                clamp_f32_to_px(data.viewport_width),
51                clamp_f32_to_px(data.viewport_height),
52            ),
53            Painter::Raster(data) => (Px(data.width as i32), Px(data.height as i32)),
54        };
55
56        let width = input.parent_constraint().width().clamp(intrinsic_width);
57        let height = input.parent_constraint().height().clamp(intrinsic_height);
58
59        Ok(LayoutResult::new(ComputedData { width, height }))
60    }
61}
62
63impl tessera_ui::DrawModifierNode for ImageLayout {
64    fn draw(
65        &self,
66        ctx: &mut tessera_ui::DrawModifierContext<'_, '_>,
67        content: &mut dyn tessera_ui::DrawModifierContent,
68    ) {
69        let input = &mut *ctx.render_input;
70        match &self.painter {
71            Painter::Source(_) => {}
72            Painter::Raster(data) => {
73                let image_command = ImageCommand {
74                    data: data.clone(),
75                    opacity: 1.0,
76                };
77                input
78                    .metadata_mut()
79                    .fragment_mut()
80                    .push_draw_command(image_command);
81            }
82            Painter::Vector(data) => {
83                let vector_command = ImageVectorCommand {
84                    data: data.clone(),
85                    tint: Color::WHITE,
86                    tint_mode: TintMode::Multiply,
87                    rotation: 0.0,
88                };
89                input
90                    .metadata_mut()
91                    .fragment_mut()
92                    .push_draw_command(vector_command);
93            }
94        }
95        content.draw(input);
96    }
97}
98
99/// # image
100///
101/// Renders a raster image, fitting it to the available space or its intrinsic
102/// size.
103///
104/// ## Usage
105///
106/// Display a raster or vector asset using a shared painter payload.
107///
108/// ## Parameters
109///
110/// - `painter` - optional painter payload for vector or raster imagery.
111/// - `modifier` - node-local layout, drawing, and interaction modifiers.
112///
113/// ## Examples
114///
115/// ```
116/// # use tessera_ui::tessera;
117/// # #[tessera]
118/// # fn component() {
119/// use tessera_components::image::{ImageData, image};
120///
121/// let image_data = ImageData::from_rgba(vec![255, 255, 255, 255], 1, 1);
122///
123/// image().painter(image_data);
124/// # }
125/// ```
126#[tessera]
127pub fn image(#[prop(skip_setter)] painter: Option<Painter>, modifier: Option<Modifier>) {
128    let modifier = modifier.unwrap_or_default();
129    let painter = painter
130        .and_then(Painter::resolve)
131        .unwrap_or_else(|| Painter::Raster(placeholder_image_data()));
132    let policy = ImageLayout {
133        painter: painter.clone(),
134    };
135    layout()
136        .modifier(modifier)
137        .policy(policy.clone())
138        .draw(policy);
139}
140
141pub(crate) fn clamp_f32_to_px(value: f32) -> Px {
142    let clamped = value.max(0.0).min(i32::MAX as f32);
143    Px(clamped.round() as i32)
144}