iced_widget/
image.rs

1//! Images display raster graphics in different formats (PNG, JPG, etc.).
2//!
3//! # Example
4//! ```no_run
5//! # mod iced { pub mod widget { pub use iced_widget::*; } }
6//! # pub type State = ();
7//! # pub type Element<'a, Message> = iced_widget::core::Element<'a, Message, iced_widget::Theme, iced_widget::Renderer>;
8//! use iced::widget::image;
9//!
10//! enum Message {
11//!     // ...
12//! }
13//!
14//! fn view(state: &State) -> Element<'_, Message> {
15//!     image("ferris.png").into()
16//! }
17//! ```
18//! <img src="https://github.com/iced-rs/iced/blob/9712b319bb7a32848001b96bd84977430f14b623/examples/resources/ferris.png?raw=true" width="300">
19pub mod viewer;
20pub use viewer::Viewer;
21
22use crate::core::image;
23use crate::core::layout;
24use crate::core::mouse;
25use crate::core::renderer;
26use crate::core::widget::Tree;
27use crate::core::{
28    ContentFit, Element, Layout, Length, Point, Rectangle, Rotation, Size,
29    Vector, Widget,
30};
31
32pub use image::{FilterMethod, Handle};
33
34/// Creates a new [`Viewer`] with the given image `Handle`.
35pub fn viewer<Handle>(handle: Handle) -> Viewer<Handle> {
36    Viewer::new(handle)
37}
38
39/// A frame that displays an image while keeping aspect ratio.
40///
41/// # Example
42/// ```no_run
43/// # mod iced { pub mod widget { pub use iced_widget::*; } }
44/// # pub type State = ();
45/// # pub type Element<'a, Message> = iced_widget::core::Element<'a, Message, iced_widget::Theme, iced_widget::Renderer>;
46/// use iced::widget::image;
47///
48/// enum Message {
49///     // ...
50/// }
51///
52/// fn view(state: &State) -> Element<'_, Message> {
53///     image("ferris.png").into()
54/// }
55/// ```
56/// <img src="https://github.com/iced-rs/iced/blob/9712b319bb7a32848001b96bd84977430f14b623/examples/resources/ferris.png?raw=true" width="300">
57#[allow(missing_debug_implementations)]
58pub struct Image<Handle = image::Handle> {
59    handle: Handle,
60    width: Length,
61    height: Length,
62    content_fit: ContentFit,
63    filter_method: FilterMethod,
64    rotation: Rotation,
65    opacity: f32,
66    scale: f32,
67    expand: bool,
68}
69
70impl<Handle> Image<Handle> {
71    /// Creates a new [`Image`] with the given path.
72    pub fn new(handle: impl Into<Handle>) -> Self {
73        Image {
74            handle: handle.into(),
75            width: Length::Shrink,
76            height: Length::Shrink,
77            content_fit: ContentFit::default(),
78            filter_method: FilterMethod::default(),
79            rotation: Rotation::default(),
80            opacity: 1.0,
81            scale: 1.0,
82            expand: false,
83        }
84    }
85
86    /// Sets the width of the [`Image`] boundaries.
87    pub fn width(mut self, width: impl Into<Length>) -> Self {
88        self.width = width.into();
89        self
90    }
91
92    /// Sets the height of the [`Image`] boundaries.
93    pub fn height(mut self, height: impl Into<Length>) -> Self {
94        self.height = height.into();
95        self
96    }
97
98    /// Sets whether the [`Image`] should try to fill as much space
99    /// available as possible while keeping aspect ratio and without
100    /// allocating extra space in any axis with a [`Length::Shrink`]
101    /// sizing strategy.
102    ///
103    /// This is similar to using [`Length::Fill`] for both the
104    /// [`width`](Self::width) and the [`height`](Self::height),
105    /// but without the downside of blank space.
106    pub fn expand(mut self, expand: bool) -> Self {
107        self.expand = expand;
108        self
109    }
110
111    /// Sets the [`ContentFit`] of the [`Image`].
112    ///
113    /// Defaults to [`ContentFit::Contain`]
114    pub fn content_fit(mut self, content_fit: ContentFit) -> Self {
115        self.content_fit = content_fit;
116        self
117    }
118
119    /// Sets the [`FilterMethod`] of the [`Image`].
120    pub fn filter_method(mut self, filter_method: FilterMethod) -> Self {
121        self.filter_method = filter_method;
122        self
123    }
124
125    /// Applies the given [`Rotation`] to the [`Image`].
126    pub fn rotation(mut self, rotation: impl Into<Rotation>) -> Self {
127        self.rotation = rotation.into();
128        self
129    }
130
131    /// Sets the opacity of the [`Image`].
132    ///
133    /// It should be in the [0.0, 1.0] range—`0.0` meaning completely transparent,
134    /// and `1.0` meaning completely opaque.
135    pub fn opacity(mut self, opacity: impl Into<f32>) -> Self {
136        self.opacity = opacity.into();
137        self
138    }
139
140    /// Sets the scale of the [`Image`].
141    ///
142    /// The region of the [`Image`] drawn will be scaled from the center by the given scale factor.
143    /// This can be useful to create certain effects and animations, like smooth zoom in / out.
144    pub fn scale(mut self, scale: impl Into<f32>) -> Self {
145        self.scale = scale.into();
146        self
147    }
148}
149
150/// Computes the layout of an [`Image`].
151pub fn layout<Renderer, Handle>(
152    renderer: &Renderer,
153    limits: &layout::Limits,
154    handle: &Handle,
155    width: Length,
156    height: Length,
157    content_fit: ContentFit,
158    rotation: Rotation,
159    expand: bool,
160) -> layout::Node
161where
162    Renderer: image::Renderer<Handle = Handle>,
163{
164    // The raw w/h of the underlying image
165    let image_size = renderer.measure_image(handle);
166    let image_size =
167        Size::new(image_size.width as f32, image_size.height as f32);
168
169    // The rotated size of the image
170    let rotated_size = rotation.apply(image_size);
171
172    // The size to be available to the widget prior to `Shrink`ing
173    let bounds = if expand {
174        limits.max()
175    } else {
176        limits.resolve(width, height, rotated_size)
177    };
178
179    // The uncropped size of the image when fit to the bounds above
180    let full_size = content_fit.fit(rotated_size, bounds);
181
182    // Shrink the widget to fit the resized image, if requested
183    let final_size = Size {
184        width: match width {
185            Length::Shrink => f32::min(bounds.width, full_size.width),
186            _ => bounds.width,
187        },
188        height: match height {
189            Length::Shrink => f32::min(bounds.height, full_size.height),
190            _ => bounds.height,
191        },
192    };
193
194    layout::Node::new(final_size)
195}
196
197fn drawing_bounds<Renderer, Handle>(
198    renderer: &Renderer,
199    bounds: Rectangle,
200    handle: &Handle,
201    content_fit: ContentFit,
202    rotation: Rotation,
203    scale: f32,
204) -> Rectangle
205where
206    Renderer: image::Renderer<Handle = Handle>,
207{
208    let Size { width, height } = renderer.measure_image(handle);
209    let image_size = Size::new(width as f32, height as f32);
210    let rotated_size = rotation.apply(image_size);
211    let adjusted_fit = content_fit.fit(rotated_size, bounds.size());
212
213    let fit_scale = Vector::new(
214        adjusted_fit.width / rotated_size.width,
215        adjusted_fit.height / rotated_size.height,
216    );
217
218    let final_size = image_size * fit_scale * scale;
219
220    let position = match content_fit {
221        ContentFit::None => Point::new(
222            bounds.x + (rotated_size.width - adjusted_fit.width) / 2.0,
223            bounds.y + (rotated_size.height - adjusted_fit.height) / 2.0,
224        ),
225        _ => Point::new(
226            bounds.center_x() - final_size.width / 2.0,
227            bounds.center_y() - final_size.height / 2.0,
228        ),
229    };
230
231    Rectangle::new(position, final_size)
232}
233
234fn must_clip(bounds: Rectangle, drawing_bounds: Rectangle) -> bool {
235    drawing_bounds.width > bounds.width || drawing_bounds.height > bounds.height
236}
237
238/// Draws an [`Image`]
239pub fn draw<Renderer, Handle>(
240    renderer: &mut Renderer,
241    layout: Layout<'_>,
242    viewport: &Rectangle,
243    handle: &Handle,
244    content_fit: ContentFit,
245    filter_method: FilterMethod,
246    rotation: Rotation,
247    opacity: f32,
248    scale: f32,
249) where
250    Renderer: image::Renderer<Handle = Handle>,
251    Handle: Clone,
252{
253    let bounds = layout.bounds();
254    let drawing_bounds =
255        drawing_bounds(renderer, bounds, handle, content_fit, rotation, scale);
256
257    if must_clip(bounds, drawing_bounds) {
258        if let Some(bounds) = bounds.intersection(viewport) {
259            renderer.with_layer(bounds, |renderer| {
260                render(
261                    renderer,
262                    handle,
263                    filter_method,
264                    rotation,
265                    opacity,
266                    drawing_bounds,
267                );
268            });
269        }
270    } else {
271        render(
272            renderer,
273            handle,
274            filter_method,
275            rotation,
276            opacity,
277            drawing_bounds,
278        );
279    }
280}
281
282fn render<Renderer, Handle>(
283    renderer: &mut Renderer,
284    handle: &Handle,
285    filter_method: FilterMethod,
286    rotation: Rotation,
287    opacity: f32,
288    drawing_bounds: Rectangle,
289) where
290    Renderer: image::Renderer<Handle = Handle>,
291    Handle: Clone,
292{
293    renderer.draw_image(
294        image::Image {
295            handle: handle.clone(),
296            filter_method,
297            rotation: rotation.radians(),
298            opacity,
299            snap: true,
300        },
301        drawing_bounds,
302    );
303}
304
305impl<Message, Theme, Renderer, Handle> Widget<Message, Theme, Renderer>
306    for Image<Handle>
307where
308    Renderer: image::Renderer<Handle = Handle>,
309    Handle: Clone,
310{
311    fn size(&self) -> Size<Length> {
312        Size {
313            width: self.width,
314            height: self.height,
315        }
316    }
317
318    fn layout(
319        &self,
320        _tree: &mut Tree,
321        renderer: &Renderer,
322        limits: &layout::Limits,
323    ) -> layout::Node {
324        layout(
325            renderer,
326            limits,
327            &self.handle,
328            self.width,
329            self.height,
330            self.content_fit,
331            self.rotation,
332            self.expand,
333        )
334    }
335
336    fn draw(
337        &self,
338        _state: &Tree,
339        renderer: &mut Renderer,
340        _theme: &Theme,
341        _style: &renderer::Style,
342        layout: Layout<'_>,
343        _cursor: mouse::Cursor,
344        viewport: &Rectangle,
345    ) {
346        draw(
347            renderer,
348            layout,
349            viewport,
350            &self.handle,
351            self.content_fit,
352            self.filter_method,
353            self.rotation,
354            self.opacity,
355            self.scale,
356        );
357    }
358}
359
360impl<'a, Message, Theme, Renderer, Handle> From<Image<Handle>>
361    for Element<'a, Message, Theme, Renderer>
362where
363    Renderer: image::Renderer<Handle = Handle>,
364    Handle: Clone + 'a,
365{
366    fn from(image: Image<Handle>) -> Element<'a, Message, Theme, Renderer> {
367        Element::new(image)
368    }
369}