Skip to main content

tauri/image/
mod.rs

1// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
2// SPDX-License-Identifier: Apache-2.0
3// SPDX-License-Identifier: MIT
4
5//! Image types used by this crate and also referenced by the JavaScript API layer.
6
7pub(crate) mod plugin;
8
9use std::borrow::Cow;
10use std::sync::Arc;
11
12use crate::{Resource, ResourceId, ResourceTable};
13
14/// An RGBA Image in row-major order from top to bottom.
15#[derive(Clone)]
16pub struct Image<'a> {
17  rgba: Cow<'a, [u8]>,
18  width: u32,
19  height: u32,
20}
21
22impl std::fmt::Debug for Image<'_> {
23  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24    f.debug_struct("Image")
25      .field(
26        "rgba",
27        // Reduces the debug size compared to the derived default, as the default
28        // would format the raw bytes as numbers `[0, 0, 0, 0]` for 1 pixel.
29        // The custom format doesn't grow as much with larger images:
30        // `Image { rgba: Cow::Borrowed([u8; 4096]), width: 32, height: 32 }`
31        &format_args!(
32          "Cow::{}([u8; {}])",
33          match &self.rgba {
34            Cow::Borrowed(_) => "Borrowed",
35            Cow::Owned(_) => "Owned",
36          },
37          self.rgba.len()
38        ),
39      )
40      .field("width", &self.width)
41      .field("height", &self.height)
42      .finish()
43  }
44}
45
46impl Resource for Image<'static> {}
47
48impl Image<'static> {
49  /// Creates a new Image using RGBA data, in row-major order from top to bottom, and with specified width and height.
50  ///
51  /// Similar to [`Self::new`] but avoids cloning the rgba data to get an owned Image.
52  pub const fn new_owned(rgba: Vec<u8>, width: u32, height: u32) -> Self {
53    Self {
54      rgba: Cow::Owned(rgba),
55      width,
56      height,
57    }
58  }
59}
60
61impl<'a> Image<'a> {
62  /// Creates a new Image using RGBA data, in row-major order from top to bottom, and with specified width and height.
63  pub const fn new(rgba: &'a [u8], width: u32, height: u32) -> Self {
64    Self {
65      rgba: Cow::Borrowed(rgba),
66      width,
67      height,
68    }
69  }
70
71  /// Creates a new image using the provided bytes.
72  ///
73  /// Only `ico` and `png` are supported (based on activated feature flag).
74  #[cfg(any(feature = "image-ico", feature = "image-png"))]
75  #[cfg_attr(docsrs, doc(cfg(any(feature = "image-ico", feature = "image-png"))))]
76  pub fn from_bytes(bytes: &[u8]) -> crate::Result<Self> {
77    use image::GenericImageView;
78
79    let img = image::load_from_memory(bytes)?;
80    let pixels = img
81      .pixels()
82      .flat_map(|(_, _, pixel)| pixel.0)
83      .collect::<Vec<_>>();
84    Ok(Self {
85      rgba: Cow::Owned(pixels),
86      width: img.width(),
87      height: img.height(),
88    })
89  }
90
91  /// Creates a new image using the provided path.
92  ///
93  /// Only `ico` and `png` are supported (based on activated feature flag).
94  #[cfg(any(feature = "image-ico", feature = "image-png"))]
95  #[cfg_attr(docsrs, doc(cfg(any(feature = "image-ico", feature = "image-png"))))]
96  pub fn from_path<P: AsRef<std::path::Path>>(path: P) -> crate::Result<Self> {
97    let bytes = std::fs::read(path)?;
98    Self::from_bytes(&bytes)
99  }
100
101  /// Returns the RGBA data for this image, in row-major order from top to bottom.
102  pub fn rgba(&'a self) -> &'a [u8] {
103    &self.rgba
104  }
105
106  /// Returns the width of this image.
107  pub fn width(&self) -> u32 {
108    self.width
109  }
110
111  /// Returns the height of this image.
112  pub fn height(&self) -> u32 {
113    self.height
114  }
115
116  /// Convert into a 'static owned [`Image`].
117  /// This will allocate.
118  pub fn to_owned(self) -> Image<'static> {
119    Image {
120      rgba: match self.rgba {
121        Cow::Owned(v) => Cow::Owned(v),
122        Cow::Borrowed(v) => Cow::Owned(v.to_vec()),
123      },
124      height: self.height,
125      width: self.width,
126    }
127  }
128}
129
130impl<'a> From<Image<'a>> for crate::runtime::Icon<'a> {
131  fn from(img: Image<'a>) -> Self {
132    Self {
133      rgba: img.rgba,
134      width: img.width,
135      height: img.height,
136    }
137  }
138}
139
140#[cfg(desktop)]
141impl TryFrom<Image<'_>> for muda::Icon {
142  type Error = crate::Error;
143
144  fn try_from(img: Image<'_>) -> Result<Self, Self::Error> {
145    muda::Icon::from_rgba(img.rgba.to_vec(), img.width, img.height).map_err(Into::into)
146  }
147}
148
149#[cfg(all(desktop, feature = "tray-icon"))]
150impl TryFrom<Image<'_>> for tray_icon::Icon {
151  type Error = crate::Error;
152
153  fn try_from(img: Image<'_>) -> Result<Self, Self::Error> {
154    tray_icon::Icon::from_rgba(img.rgba.to_vec(), img.width, img.height).map_err(Into::into)
155  }
156}
157
158/// An image type that accepts file paths, raw bytes, previously loaded images and image objects.
159///
160/// This type is meant to be used along the [transformImage](https://v2.tauri.app/reference/javascript/api/namespaceimage/#transformimage) API.
161///
162/// # Stability
163///
164/// The stability of the variants are not guaranteed, and matching against them is not recommended.
165/// Use [`JsImage::into_img`] instead.
166#[derive(serde::Deserialize)]
167#[serde(untagged)]
168#[non_exhaustive]
169pub enum JsImage {
170  /// A reference to a image in the filesystem.
171  #[non_exhaustive]
172  Path(std::path::PathBuf),
173  /// Image from raw bytes.
174  #[non_exhaustive]
175  Bytes(Vec<u8>),
176  /// An image that was previously loaded with the API and is stored in the resource table.
177  #[non_exhaustive]
178  Resource(ResourceId),
179  /// Raw RGBA definition of an image.
180  #[non_exhaustive]
181  Rgba {
182    /// Image bytes.
183    rgba: Vec<u8>,
184    /// Image width.
185    width: u32,
186    /// Image height.
187    height: u32,
188  },
189}
190
191impl JsImage {
192  /// Converts this intermediate image format into an actual [`Image`].
193  ///
194  /// This will retrieve the image from the passed [`ResourceTable`] if it is [`JsImage::Resource`]
195  /// and will return an error if it doesn't exist in the passed [`ResourceTable`] so make sure
196  /// the passed [`ResourceTable`] is the same one used to store the image, usually this should be
197  /// the webview resources table.
198  pub fn into_img(self, resources_table: &ResourceTable) -> crate::Result<Arc<Image<'_>>> {
199    match self {
200      Self::Resource(rid) => resources_table.get::<Image<'static>>(rid),
201      #[cfg(any(feature = "image-ico", feature = "image-png"))]
202      Self::Path(path) => Image::from_path(path).map(Arc::new),
203
204      #[cfg(any(feature = "image-ico", feature = "image-png"))]
205      Self::Bytes(bytes) => Image::from_bytes(&bytes).map(Arc::new),
206
207      Self::Rgba {
208        rgba,
209        width,
210        height,
211      } => Ok(Arc::new(Image::new_owned(rgba, width, height))),
212
213      #[cfg(not(any(feature = "image-ico", feature = "image-png")))]
214      _ => Err(
215        std::io::Error::new(
216          std::io::ErrorKind::InvalidInput,
217          format!(
218            "expected RGBA image data, found {}",
219            match self {
220              JsImage::Path(_) => "a file path",
221              JsImage::Bytes(_) => "raw bytes",
222              _ => unreachable!(),
223            }
224          ),
225        )
226        .into(),
227      ),
228    }
229  }
230}