iced_core/image.rs
1//! Load and draw raster graphics.
2use crate::border;
3use crate::{Bytes, Radians, Rectangle, Size};
4
5use rustc_hash::FxHasher;
6
7use std::hash::{Hash, Hasher};
8use std::io;
9use std::path::{Path, PathBuf};
10use std::sync::{Arc, Weak};
11
12/// A raster image that can be drawn.
13#[derive(Debug, Clone, PartialEq)]
14pub struct Image<H = Handle> {
15 /// The handle of the image.
16 pub handle: H,
17
18 /// The filter method of the image.
19 pub filter_method: FilterMethod,
20
21 /// The rotation to be applied to the image; on its center.
22 pub rotation: Radians,
23
24 /// The border radius of the [`Image`].
25 ///
26 /// Currently, this will only be applied to the `clip_bounds`.
27 pub border_radius: border::Radius,
28
29 /// The opacity of the image.
30 ///
31 /// 0 means transparent. 1 means opaque.
32 pub opacity: f32,
33
34 /// If set to `true`, the image will be snapped to the pixel grid.
35 ///
36 /// This can avoid graphical glitches, specially when using
37 /// [`FilterMethod::Nearest`].
38 pub snap: bool,
39}
40
41impl Image<Handle> {
42 /// Creates a new [`Image`] with the given handle.
43 pub fn new(handle: impl Into<Handle>) -> Self {
44 Self {
45 handle: handle.into(),
46 filter_method: FilterMethod::default(),
47 rotation: Radians(0.0),
48 border_radius: border::Radius::default(),
49 opacity: 1.0,
50 snap: false,
51 }
52 }
53
54 /// Sets the filter method of the [`Image`].
55 pub fn filter_method(mut self, filter_method: FilterMethod) -> Self {
56 self.filter_method = filter_method;
57 self
58 }
59
60 /// Sets the rotation of the [`Image`].
61 pub fn rotation(mut self, rotation: impl Into<Radians>) -> Self {
62 self.rotation = rotation.into();
63 self
64 }
65
66 /// Sets the opacity of the [`Image`].
67 pub fn opacity(mut self, opacity: impl Into<f32>) -> Self {
68 self.opacity = opacity.into();
69 self
70 }
71
72 /// Sets whether the [`Image`] should be snapped to the pixel grid.
73 pub fn snap(mut self, snap: bool) -> Self {
74 self.snap = snap;
75 self
76 }
77}
78
79impl From<&Handle> for Image {
80 fn from(handle: &Handle) -> Self {
81 Image::new(handle.clone())
82 }
83}
84
85/// A handle of some image data.
86#[derive(Clone, PartialEq, Eq)]
87pub enum Handle {
88 /// A file handle. The image data will be read
89 /// from the file path.
90 ///
91 /// Use [`from_path`] to create this variant.
92 ///
93 /// [`from_path`]: Self::from_path
94 Path(Id, PathBuf),
95
96 /// A handle pointing to some encoded image bytes in-memory.
97 ///
98 /// Use [`from_bytes`] to create this variant.
99 ///
100 /// [`from_bytes`]: Self::from_bytes
101 Bytes(Id, Bytes),
102
103 /// A handle pointing to decoded image pixels in RGBA format.
104 ///
105 /// Use [`from_rgba`] to create this variant.
106 ///
107 /// [`from_rgba`]: Self::from_rgba
108 Rgba {
109 /// The id of this handle.
110 id: Id,
111 /// The width of the image.
112 width: u32,
113 /// The height of the image.
114 height: u32,
115 /// The pixels.
116 pixels: Bytes,
117 },
118}
119
120impl Handle {
121 /// Creates an image [`Handle`] pointing to the image of the given path.
122 ///
123 /// Makes an educated guess about the image format by examining the data in the file.
124 pub fn from_path<T: Into<PathBuf>>(path: T) -> Handle {
125 let path = path.into();
126
127 Self::Path(Id::path(&path), path)
128 }
129
130 /// Creates an image [`Handle`] containing the encoded image data directly.
131 ///
132 /// Makes an educated guess about the image format by examining the given data.
133 ///
134 /// This is useful if you already have your image loaded in-memory, maybe
135 /// because you downloaded or generated it procedurally.
136 pub fn from_bytes(bytes: impl Into<Bytes>) -> Handle {
137 Self::Bytes(Id::unique(), bytes.into())
138 }
139
140 /// Creates an image [`Handle`] containing the decoded image pixels directly.
141 ///
142 /// This function expects the pixel data to be provided as a collection of [`Bytes`]
143 /// of RGBA pixels. Therefore, the length of the pixel data should always be
144 /// `width * height * 4`.
145 ///
146 /// This is useful if you have already decoded your image.
147 pub fn from_rgba(width: u32, height: u32, pixels: impl Into<Bytes>) -> Handle {
148 Self::Rgba {
149 id: Id::unique(),
150 width,
151 height,
152 pixels: pixels.into(),
153 }
154 }
155
156 /// Returns the unique identifier of the [`Handle`].
157 pub fn id(&self) -> Id {
158 match self {
159 Handle::Path(id, _) | Handle::Bytes(id, _) | Handle::Rgba { id, .. } => *id,
160 }
161 }
162}
163
164impl<T> From<T> for Handle
165where
166 T: Into<PathBuf>,
167{
168 fn from(path: T) -> Handle {
169 Handle::from_path(path.into())
170 }
171}
172
173impl From<&Handle> for Handle {
174 fn from(value: &Handle) -> Self {
175 value.clone()
176 }
177}
178
179impl std::fmt::Debug for Handle {
180 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
181 match self {
182 Self::Path(id, path) => write!(f, "Path({id:?}, {path:?})"),
183 Self::Bytes(id, _) => write!(f, "Bytes({id:?}, ...)"),
184 Self::Rgba {
185 id, width, height, ..
186 } => {
187 write!(f, "Pixels({id:?}, {width} * {height})")
188 }
189 }
190 }
191}
192
193/// The unique identifier of some [`Handle`] data.
194#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
195pub struct Id(_Id);
196
197#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
198enum _Id {
199 Unique(u64),
200 Hash(u64),
201}
202
203impl Id {
204 fn unique() -> Self {
205 use std::sync::atomic::{self, AtomicU64};
206
207 static NEXT_ID: AtomicU64 = AtomicU64::new(0);
208
209 Self(_Id::Unique(NEXT_ID.fetch_add(1, atomic::Ordering::Relaxed)))
210 }
211
212 fn path(path: impl AsRef<Path>) -> Self {
213 let hash = {
214 let mut hasher = FxHasher::default();
215 path.as_ref().hash(&mut hasher);
216
217 hasher.finish()
218 };
219
220 Self(_Id::Hash(hash))
221 }
222}
223
224/// Image filtering strategy.
225#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
226pub enum FilterMethod {
227 /// Bilinear interpolation.
228 #[default]
229 Linear,
230 /// Nearest neighbor.
231 Nearest,
232}
233
234/// A memory allocation of a [`Handle`], often in GPU memory.
235///
236/// Renderers tend to decode and upload image data concurrently to
237/// avoid blocking the user interface. This means that when you use a
238/// [`Handle`] in a widget, there may be a slight frame delay until it
239/// is finally visible. If you are animating images, this can cause
240/// undesirable flicker.
241///
242/// When you obtain an [`Allocation`] explicitly, you get the guarantee
243/// that using a [`Handle`] will draw the corresponding [`Image`]
244/// immediately in the next frame.
245///
246/// This guarantee is valid as long as you hold an [`Allocation`].
247/// Only when you drop all its clones, the renderer may choose to free
248/// the memory of the [`Handle`]. Be careful!
249#[derive(Debug, Clone, PartialEq, Eq)]
250pub struct Allocation(Arc<Memory>);
251
252/// Some memory taken by an [`Allocation`].
253#[derive(Debug, Clone, PartialEq, Eq)]
254pub struct Memory {
255 handle: Handle,
256 size: Size<u32>,
257}
258
259impl Allocation {
260 /// Returns a weak reference to the [`Memory`] of the [`Allocation`].
261 pub fn downgrade(&self) -> Weak<Memory> {
262 Arc::downgrade(&self.0)
263 }
264
265 /// Upgrades a [`Weak`] memory reference to an [`Allocation`].
266 pub fn upgrade(weak: &Weak<Memory>) -> Option<Allocation> {
267 Weak::upgrade(weak).map(Allocation)
268 }
269
270 /// Returns the [`Handle`] of this [`Allocation`].
271 pub fn handle(&self) -> &Handle {
272 &self.0.handle
273 }
274
275 /// Returns the [`Size`] of the image of this [`Allocation`].
276 pub fn size(&self) -> Size<u32> {
277 self.0.size
278 }
279}
280
281/// Creates a new [`Allocation`] for the given handle.
282///
283/// This should only be used internally by renderer implementations.
284///
285/// # Safety
286/// Must only be created once the [`Handle`] is allocated in memory.
287#[allow(unsafe_code)]
288pub unsafe fn allocate(handle: &Handle, size: Size<u32>) -> Allocation {
289 Allocation(Arc::new(Memory {
290 handle: handle.clone(),
291 size,
292 }))
293}
294
295/// A [`Renderer`] that can render raster graphics.
296///
297/// [renderer]: crate::renderer
298pub trait Renderer: crate::Renderer {
299 /// The image Handle to be displayed. Iced exposes its own default implementation of a [`Handle`]
300 ///
301 /// [`Handle`]: Self::Handle
302 type Handle: Clone;
303
304 /// Loads an image and returns an explicit [`Allocation`] to it.
305 ///
306 /// If the image is not already loaded, this method will block! You should
307 /// generally not use it in drawing logic if you want to avoid frame drops.
308 fn load_image(&self, handle: &Self::Handle) -> Result<Allocation, Error>;
309
310 /// Returns the dimensions of an image for the given [`Handle`].
311 ///
312 /// If the image is not already loaded, the [`Renderer`] may choose to return
313 /// `None`, load the image in the background, and then trigger a relayout.
314 ///
315 /// If you need a measurement right away, consider using [`Renderer::load_image`].
316 fn measure_image(&self, handle: &Self::Handle) -> Option<Size<u32>>;
317
318 /// Draws an [`Image`] inside the provided `bounds`.
319 ///
320 /// If the image is not already loaded, the [`Renderer`] may choose to render
321 /// nothing, load the image in the background, and then trigger a redraw.
322 ///
323 /// If you need to draw an image right away, consider using [`Renderer::load_image`]
324 /// and hold on to an [`Allocation`] first.
325 fn draw_image(&mut self, image: Image<Self::Handle>, bounds: Rectangle, clip_bounds: Rectangle);
326}
327
328/// An image loading error.
329#[derive(Debug, Clone, thiserror::Error)]
330pub enum Error {
331 /// The image data was invalid or could not be decoded.
332 #[error("the image data was invalid or could not be decoded: {0}")]
333 Invalid(Arc<dyn std::error::Error + Send + Sync>),
334 /// The image file was not found.
335 #[error("the image file could not be opened: {0}")]
336 Inaccessible(Arc<io::Error>),
337 /// Loading images is unsupported.
338 #[error("loading images is unsupported")]
339 Unsupported,
340 /// The image is empty.
341 #[error("the image is empty")]
342 Empty,
343 /// Not enough memory to allocate the image.
344 #[error("not enough memory to allocate the image")]
345 OutOfMemory,
346}