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#[derive(Debug)]
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}
68
69impl<Handle> Image<Handle> {
70    /// Creates a new [`Image`] with the given path.
71    pub fn new(handle: impl Into<Handle>) -> Self {
72        Image {
73            handle: handle.into(),
74            width: Length::Shrink,
75            height: Length::Shrink,
76            content_fit: ContentFit::default(),
77            filter_method: FilterMethod::default(),
78            rotation: Rotation::default(),
79            opacity: 1.0,
80            scale: 1.0,
81        }
82    }
83
84    /// Sets the width of the [`Image`] boundaries.
85    pub fn width(mut self, width: impl Into<Length>) -> Self {
86        self.width = width.into();
87        self
88    }
89
90    /// Sets the height of the [`Image`] boundaries.
91    pub fn height(mut self, height: impl Into<Length>) -> Self {
92        self.height = height.into();
93        self
94    }
95
96    /// Sets the [`ContentFit`] of the [`Image`].
97    ///
98    /// Defaults to [`ContentFit::Contain`]
99    pub fn content_fit(mut self, content_fit: ContentFit) -> Self {
100        self.content_fit = content_fit;
101        self
102    }
103
104    /// Sets the [`FilterMethod`] of the [`Image`].
105    pub fn filter_method(mut self, filter_method: FilterMethod) -> Self {
106        self.filter_method = filter_method;
107        self
108    }
109
110    /// Applies the given [`Rotation`] to the [`Image`].
111    pub fn rotation(mut self, rotation: impl Into<Rotation>) -> Self {
112        self.rotation = rotation.into();
113        self
114    }
115
116    /// Sets the opacity of the [`Image`].
117    ///
118    /// It should be in the [0.0, 1.0] range—`0.0` meaning completely transparent,
119    /// and `1.0` meaning completely opaque.
120    pub fn opacity(mut self, opacity: impl Into<f32>) -> Self {
121        self.opacity = opacity.into();
122        self
123    }
124
125    /// Sets the scale of the [`Image`].
126    ///
127    /// The region of the [`Image`] drawn will be scaled from the center by the given scale factor.
128    /// This can be useful to create certain effects and animations, like smooth zoom in / out.
129    pub fn scale(mut self, scale: impl Into<f32>) -> Self {
130        self.scale = scale.into();
131        self
132    }
133}
134
135/// Computes the layout of an [`Image`].
136pub fn layout<Renderer, Handle>(
137    renderer: &Renderer,
138    limits: &layout::Limits,
139    handle: &Handle,
140    width: Length,
141    height: Length,
142    content_fit: ContentFit,
143    rotation: Rotation,
144) -> layout::Node
145where
146    Renderer: image::Renderer<Handle = Handle>,
147{
148    // The raw w/h of the underlying image
149    let image_size = renderer.measure_image(handle);
150    let image_size =
151        Size::new(image_size.width as f32, image_size.height as f32);
152
153    // The rotated size of the image
154    let rotated_size = rotation.apply(image_size);
155
156    // The size to be available to the widget prior to `Shrink`ing
157    let raw_size = limits.resolve(width, height, rotated_size);
158
159    // The uncropped size of the image when fit to the bounds above
160    let full_size = content_fit.fit(rotated_size, raw_size);
161
162    // Shrink the widget to fit the resized image, if requested
163    let final_size = Size {
164        width: match width {
165            Length::Shrink => f32::min(raw_size.width, full_size.width),
166            _ => raw_size.width,
167        },
168        height: match height {
169            Length::Shrink => f32::min(raw_size.height, full_size.height),
170            _ => raw_size.height,
171        },
172    };
173
174    layout::Node::new(final_size)
175}
176
177/// Draws an [`Image`]
178pub fn draw<Renderer, Handle>(
179    renderer: &mut Renderer,
180    layout: Layout<'_>,
181    viewport: &Rectangle,
182    handle: &Handle,
183    content_fit: ContentFit,
184    filter_method: FilterMethod,
185    rotation: Rotation,
186    opacity: f32,
187    scale: f32,
188) where
189    Renderer: image::Renderer<Handle = Handle>,
190    Handle: Clone,
191{
192    let Size { width, height } = renderer.measure_image(handle);
193    let image_size = Size::new(width as f32, height as f32);
194    let rotated_size = rotation.apply(image_size);
195
196    let bounds = layout.bounds();
197    let adjusted_fit = content_fit.fit(rotated_size, bounds.size());
198
199    let fit_scale = Vector::new(
200        adjusted_fit.width / rotated_size.width,
201        adjusted_fit.height / rotated_size.height,
202    );
203
204    let final_size = image_size * fit_scale * scale;
205
206    let position = match content_fit {
207        ContentFit::None => Point::new(
208            bounds.x + (rotated_size.width - adjusted_fit.width) / 2.0,
209            bounds.y + (rotated_size.height - adjusted_fit.height) / 2.0,
210        ),
211        _ => Point::new(
212            bounds.center_x() - final_size.width / 2.0,
213            bounds.center_y() - final_size.height / 2.0,
214        ),
215    };
216
217    let drawing_bounds = Rectangle::new(position, final_size);
218
219    let render = |renderer: &mut Renderer| {
220        renderer.draw_image(
221            image::Image {
222                handle: handle.clone(),
223                filter_method,
224                rotation: rotation.radians(),
225                opacity,
226                snap: true,
227            },
228            drawing_bounds,
229        );
230    };
231
232    if adjusted_fit.width > bounds.width || adjusted_fit.height > bounds.height
233    {
234        if let Some(bounds) = bounds.intersection(viewport) {
235            renderer.with_layer(bounds, render);
236        }
237    } else {
238        render(renderer);
239    }
240}
241
242impl<Message, Theme, Renderer, Handle> Widget<Message, Theme, Renderer>
243    for Image<Handle>
244where
245    Renderer: image::Renderer<Handle = Handle>,
246    Handle: Clone,
247{
248    fn size(&self) -> Size<Length> {
249        Size {
250            width: self.width,
251            height: self.height,
252        }
253    }
254
255    fn layout(
256        &self,
257        _tree: &mut Tree,
258        renderer: &Renderer,
259        limits: &layout::Limits,
260    ) -> layout::Node {
261        layout(
262            renderer,
263            limits,
264            &self.handle,
265            self.width,
266            self.height,
267            self.content_fit,
268            self.rotation,
269        )
270    }
271
272    fn draw(
273        &self,
274        _state: &Tree,
275        renderer: &mut Renderer,
276        _theme: &Theme,
277        _style: &renderer::Style,
278        layout: Layout<'_>,
279        _cursor: mouse::Cursor,
280        viewport: &Rectangle,
281    ) {
282        draw(
283            renderer,
284            layout,
285            viewport,
286            &self.handle,
287            self.content_fit,
288            self.filter_method,
289            self.rotation,
290            self.opacity,
291            self.scale,
292        );
293    }
294}
295
296impl<'a, Message, Theme, Renderer, Handle> From<Image<Handle>>
297    for Element<'a, Message, Theme, Renderer>
298where
299    Renderer: image::Renderer<Handle = Handle>,
300    Handle: Clone + 'a,
301{
302    fn from(image: Image<Handle>) -> Element<'a, Message, Theme, Renderer> {
303        Element::new(image)
304    }
305}