use std::{borrow::Cow, sync::Arc};
use dashmap::DashMap;
use image::RgbaImage;
use crate::{
layout::style::ImageScalingAlgorithm,
rendering::{fast_resize, unpremultiply_alpha},
};
use thiserror::Error;
pub type ImageResult = Result<Arc<ImageSource>, ImageResourceError>;
#[derive(Debug, Clone)]
pub enum ImageSource {
#[cfg(feature = "svg")]
Svg(Box<resvg::usvg::Tree>),
Bitmap(RgbaImage),
}
pub type PersistentImageStore = DashMap<String, Arc<ImageSource>>;
impl From<RgbaImage> for ImageSource {
fn from(bitmap: RgbaImage) -> Self {
ImageSource::Bitmap(bitmap)
}
}
impl ImageSource {
pub fn size(&self) -> (f32, f32) {
match self {
#[cfg(feature = "svg")]
ImageSource::Svg(svg) => (svg.size().width(), svg.size().height()),
ImageSource::Bitmap(bitmap) => (bitmap.width() as f32, bitmap.height() as f32),
}
}
pub fn render_to_rgba_image<'i>(
&'i self,
width: u32,
height: u32,
algorithm: ImageScalingAlgorithm,
) -> Result<Cow<'i, RgbaImage>, ImageResourceError> {
match self {
ImageSource::Bitmap(bitmap) => {
if bitmap.width() == width && bitmap.height() == height {
return Ok(Cow::Borrowed(bitmap));
}
Ok(Cow::Owned(fast_resize(bitmap, width, height, algorithm)?))
}
#[cfg(feature = "svg")]
ImageSource::Svg(svg) => {
use resvg::{tiny_skia::Pixmap, usvg::Transform};
let mut pixmap = Pixmap::new(width, height).ok_or(ImageResourceError::InvalidPixmapSize)?;
let original_size = svg.size();
let sx = width as f32 / original_size.width();
let sy = height as f32 / original_size.height();
resvg::render(svg, Transform::from_scale(sx, sy), &mut pixmap.as_mut());
let mut image = RgbaImage::from_raw(width, height, pixmap.take())
.ok_or(ImageResourceError::MismatchedBufferSize)?;
for pixel in image.pixels_mut() {
unpremultiply_alpha(pixel);
}
Ok(Cow::Owned(image))
}
}
}
}
pub fn load_image_source_from_bytes(bytes: &[u8]) -> ImageResult {
#[cfg(feature = "svg")]
{
use std::str::from_utf8;
if let Ok(text) = from_utf8(bytes)
&& is_svg_like(text)
{
return parse_svg_str(text);
}
}
let img = image::load_from_memory(bytes).map_err(ImageResourceError::DecodeError)?;
Ok(Arc::new(img.into_rgba8().into()))
}
pub(crate) fn is_svg_like(src: &str) -> bool {
src.contains("<svg") && src.contains("xmlns")
}
#[cfg(feature = "svg")]
pub fn parse_svg_str(src: &str) -> ImageResult {
use resvg::usvg::Tree;
let tree = Tree::from_str(src, &Default::default()).map_err(ImageResourceError::SvgParseError)?;
Ok(Arc::new(ImageSource::Svg(Box::new(tree))))
}
#[derive(Debug, Error)]
pub enum ImageResourceError {
#[error("An error occurred while decoding the image data: {0}")]
DecodeError(#[from] image::ImageError),
#[error("The image data URI is in an invalid format")]
InvalidDataUriFormat,
#[error("The image data URI is malformed and cannot be parsed")]
MalformedDataUri,
#[cfg(feature = "svg")]
#[error("An error occurred while parsing an SVG image: {0}")]
SvgParseError(#[from] resvg::usvg::Error),
#[cfg(not(feature = "svg"))]
#[error("SVG parsing is not supported in this build")]
SvgParseNotSupported,
#[error("The image source is unknown")]
Unknown,
#[error("The pixmap size is invalid")]
InvalidPixmapSize,
#[error("The buffer size does not match the target image size")]
MismatchedBufferSize,
#[error("An error occurred while resizing the image: {0}")]
ResizeError(#[from] fast_image_resize::ResizeError),
}