pub(crate) mod plugin;
use std::borrow::Cow;
use std::sync::Arc;
use crate::{Resource, ResourceId, ResourceTable};
#[derive(Clone)]
pub struct Image<'a> {
rgba: Cow<'a, [u8]>,
width: u32,
height: u32,
}
impl std::fmt::Debug for Image<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Image")
.field(
"rgba",
&format_args!(
"Cow::{}([u8; {}])",
match &self.rgba {
Cow::Borrowed(_) => "Borrowed",
Cow::Owned(_) => "Owned",
},
self.rgba.len()
),
)
.field("width", &self.width)
.field("height", &self.height)
.finish()
}
}
impl Resource for Image<'static> {}
impl Image<'static> {
pub const fn new_owned(rgba: Vec<u8>, width: u32, height: u32) -> Self {
Self {
rgba: Cow::Owned(rgba),
width,
height,
}
}
}
impl<'a> Image<'a> {
pub const fn new(rgba: &'a [u8], width: u32, height: u32) -> Self {
Self {
rgba: Cow::Borrowed(rgba),
width,
height,
}
}
#[cfg(any(feature = "image-ico", feature = "image-png"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "image-ico", feature = "image-png"))))]
pub fn from_bytes(bytes: &[u8]) -> crate::Result<Self> {
use image::GenericImageView;
let img = image::load_from_memory(bytes)?;
let pixels = img
.pixels()
.flat_map(|(_, _, pixel)| pixel.0)
.collect::<Vec<_>>();
Ok(Self {
rgba: Cow::Owned(pixels),
width: img.width(),
height: img.height(),
})
}
#[cfg(any(feature = "image-ico", feature = "image-png"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "image-ico", feature = "image-png"))))]
pub fn from_path<P: AsRef<std::path::Path>>(path: P) -> crate::Result<Self> {
let bytes = std::fs::read(path)?;
Self::from_bytes(&bytes)
}
pub fn rgba(&'a self) -> &'a [u8] {
&self.rgba
}
pub fn width(&self) -> u32 {
self.width
}
pub fn height(&self) -> u32 {
self.height
}
pub fn to_owned(self) -> Image<'static> {
Image {
rgba: match self.rgba {
Cow::Owned(v) => Cow::Owned(v),
Cow::Borrowed(v) => Cow::Owned(v.to_vec()),
},
height: self.height,
width: self.width,
}
}
}
impl<'a> From<Image<'a>> for crate::runtime::Icon<'a> {
fn from(img: Image<'a>) -> Self {
Self {
rgba: img.rgba,
width: img.width,
height: img.height,
}
}
}
#[cfg(desktop)]
impl TryFrom<Image<'_>> for muda::Icon {
type Error = crate::Error;
fn try_from(img: Image<'_>) -> Result<Self, Self::Error> {
muda::Icon::from_rgba(img.rgba.to_vec(), img.width, img.height).map_err(Into::into)
}
}
#[cfg(all(desktop, feature = "tray-icon"))]
impl TryFrom<Image<'_>> for tray_icon::Icon {
type Error = crate::Error;
fn try_from(img: Image<'_>) -> Result<Self, Self::Error> {
tray_icon::Icon::from_rgba(img.rgba.to_vec(), img.width, img.height).map_err(Into::into)
}
}
#[derive(serde::Deserialize)]
#[serde(untagged)]
#[non_exhaustive]
pub enum JsImage {
#[non_exhaustive]
Path(std::path::PathBuf),
#[non_exhaustive]
Bytes(Vec<u8>),
#[non_exhaustive]
Resource(ResourceId),
#[non_exhaustive]
Rgba {
rgba: Vec<u8>,
width: u32,
height: u32,
},
}
impl JsImage {
pub fn into_img(self, resources_table: &ResourceTable) -> crate::Result<Arc<Image<'_>>> {
match self {
Self::Resource(rid) => resources_table.get::<Image<'static>>(rid),
#[cfg(any(feature = "image-ico", feature = "image-png"))]
Self::Path(path) => Image::from_path(path).map(Arc::new),
#[cfg(any(feature = "image-ico", feature = "image-png"))]
Self::Bytes(bytes) => Image::from_bytes(&bytes).map(Arc::new),
Self::Rgba {
rgba,
width,
height,
} => Ok(Arc::new(Image::new_owned(rgba, width, height))),
#[cfg(not(any(feature = "image-ico", feature = "image-png")))]
_ => Err(
std::io::Error::new(
std::io::ErrorKind::InvalidInput,
format!(
"expected RGBA image data, found {}",
match self {
JsImage::Path(_) => "a file path",
JsImage::Bytes(_) => "raw bytes",
_ => unreachable!(),
}
),
)
.into(),
),
}
}
}