#[cfg(feature = "dbus")] use dbus::arg::messageitem::{MessageItem, MessageItemArray}; pub use image::DynamicImage; use std::cmp::Ordering; use std::convert::TryFrom; use std::error::Error; use std::fmt; use std::path::Path; use super::miniver::Version; mod constants { pub const IMAGE_DATA: &str = "image-data"; pub const IMAGE_DATA_1_1: &str = "image_data"; pub const IMAGE_DATA_1_0: &str = "icon_data"; } /// Image representation for images. Send via `Notification::image_data()` #[derive(PartialEq, Eq, Debug, Clone, Hash)] pub struct Image { width: i32, height: i32, rowstride: i32, alpha: bool, bits_per_sample: i32, channels: i32, data: Vec, } impl Image { fn from_raw_data( width: i32, height: i32, data: Vec, channels: i32, bits_per_sample: i32, alpha: bool, ) -> Result { const MAX_SIZE: i32 = 0x0fff_ffff; if width > MAX_SIZE || height > MAX_SIZE { return Err(ImageError::TooBig); } if data.len() != (width * height * channels) as usize { Err(ImageError::WrongDataSize) } else { Ok(Self { width, height, bits_per_sample, channels, data, rowstride: width * channels, alpha, }) } } /// Creates an image from a raw vector of bytes pub fn from_rgb(width: i32, height: i32, data: Vec) -> Result { let channels = 3i32; let bits_per_sample = 8; Self::from_raw_data(width, height, data, channels, bits_per_sample, false) } /// Creates an image from a raw vector of bytes with alpha pub fn from_rgba(width: i32, height: i32, data: Vec) -> Result { let channels = 4i32; let bits_per_sample = 8; Self::from_raw_data(width, height, data, channels, bits_per_sample, true) } /// Attempts to open the given path as image pub fn open + Sized>(path: T) -> Result { let dyn_img = image::open(&path).map_err(ImageError::CantOpen)?; Image::try_from(dyn_img) } #[cfg(all(feature = "images", feature = "zbus"))] pub(crate) fn to_tuple(&self) -> (i32, i32, i32, bool, i32, i32, Vec) { ( self.width, self.height, self.rowstride, self.alpha, self.bits_per_sample, self.channels, self.data.clone(), ) } } impl TryFrom for Image { type Error = ImageError; fn try_from(dyn_img: DynamicImage) -> Result { match dyn_img { DynamicImage::ImageRgb8(img) => Self::try_from(img), DynamicImage::ImageRgba8(img) => Self::try_from(img), _ => Err(ImageError::CantConvert), } } } impl TryFrom for Image { type Error = ImageError; fn try_from(img: image::RgbImage) -> Result { let (width, height) = img.dimensions(); let image_data = img.into_raw(); Image::from_rgb(width as i32, height as i32, image_data) } } impl TryFrom for Image { type Error = ImageError; fn try_from(img: image::RgbaImage) -> Result { let (width, height) = img.dimensions(); let image_data = img.into_raw(); Image::from_rgba(width as i32, height as i32, image_data) } } /// Errors that can occur when creating an Image #[derive(Debug)] pub enum ImageError { /// The given image is too big. DBus only has 32 bits for width / height TooBig, /// The given bytes don't match the width, height and channel count WrongDataSize, /// Can't open given path CantOpen(image::ImageError), /// Can't convert from given input CantConvert, } impl Error for ImageError { fn source(&self) -> Option<&(dyn Error + 'static)> { use ImageError::*; match self { TooBig | WrongDataSize | CantConvert => None, CantOpen(e) => Some(e), } } } impl fmt::Display for ImageError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use ImageError::*; match self { TooBig => writeln!( f, "The given image is too big. DBus only has 32 bits for width / height" ), WrongDataSize => writeln!( f, "The given bytes don't match the width, height and channel count" ), CantOpen(e) => writeln!(f, "Can't open given path {}", e), CantConvert => writeln!(f, "Can't convert from given input"), } } } /// matching image data key for each spec version #[cfg(feature = "dbus")] pub(crate) fn image_spec(version: Version) -> String { match version.cmp(&Version::new(1, 1)) { Ordering::Less => constants::IMAGE_DATA_1_0.to_owned(), Ordering::Equal => constants::IMAGE_DATA_1_1.to_owned(), Ordering::Greater => constants::IMAGE_DATA.to_owned(), } } /// matching image data key for each spec version #[cfg(feature = "zbus")] pub(crate) fn image_spec_str(version: Version) -> &'static str { match version.cmp(&Version::new(1, 1)) { Ordering::Less => constants::IMAGE_DATA_1_0, Ordering::Equal => constants::IMAGE_DATA_1_1, Ordering::Greater => constants::IMAGE_DATA, } } #[cfg(feature = "dbus")] pub struct ImageMessage(Image); #[cfg(feature = "dbus")] impl From for ImageMessage { fn from(hint: Image) -> Self { ImageMessage(hint) } } impl From for ImageError { fn from(image_error: image::ImageError) -> Self { ImageError::CantOpen(image_error) } } #[cfg(feature = "dbus")] impl std::ops::Deref for ImageMessage { type Target = Image; fn deref(&self) -> &Self::Target { &self.0 } } #[cfg(feature = "dbus")] impl From for MessageItem { fn from(img_msg: ImageMessage) -> Self { let img = img_msg.0; let bytes = img.data.into_iter().map(MessageItem::Byte).collect(); MessageItem::Struct(vec![ MessageItem::Int32(img.width), MessageItem::Int32(img.height), MessageItem::Int32(img.rowstride), MessageItem::Bool(img.alpha), MessageItem::Int32(img.bits_per_sample), MessageItem::Int32(img.channels), MessageItem::Array(MessageItemArray::new(bytes, "ay".into()).unwrap()), ]) } }