1pub(crate) mod plugin;
8
9use std::borrow::Cow;
10use std::sync::Arc;
11
12use crate::{Resource, ResourceId, ResourceTable};
13
14#[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 &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 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 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 #[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 #[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 pub fn rgba(&'a self) -> &'a [u8] {
103 &self.rgba
104 }
105
106 pub fn width(&self) -> u32 {
108 self.width
109 }
110
111 pub fn height(&self) -> u32 {
113 self.height
114 }
115
116 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#[derive(serde::Deserialize)]
167#[serde(untagged)]
168#[non_exhaustive]
169pub enum JsImage {
170 #[non_exhaustive]
172 Path(std::path::PathBuf),
173 #[non_exhaustive]
175 Bytes(Vec<u8>),
176 #[non_exhaustive]
178 Resource(ResourceId),
179 #[non_exhaustive]
181 Rgba {
182 rgba: Vec<u8>,
184 width: u32,
186 height: u32,
188 },
189}
190
191impl JsImage {
192 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}