use std::fs::File; use std::io::BufReader; use std::path::Path; use image_rs::GenericImageView as _; use crate::asset::{Assets, Submitter as AssetSubmitter}; use crate::color::Color; use crate::data_types::dimens::Dimens; use crate::builder; #[derive(Debug)] pub struct Image { inner: image_rs::DynamicImage, } impl Image { pub fn open(path: impl AsRef) -> Result { let buffered_reader = BufReader::new(File::open(&path).map_err(Error::ReadFailed)?); let image_reader = image_rs::io::Reader::with_format( buffered_reader, image_rs::ImageFormat::from_path(path) .map_err(|_| Error::UnsupportedFormat)?, ); Ok(Self { inner: image_reader .decode() .map_err(|err| Error::DecodeFailed(DecodeError(err)))?, }) } pub fn from_color(dimens: impl Into>, color: impl Into>) -> Self { let dimens: Dimens = dimens.into(); let color: Color = color.into(); Self { inner: image_rs::RgbImage::from_pixel( dimens.width, dimens.height, image_rs::Rgb([color.red, color.green, color.blue]), ) .into(), } } pub fn dimensions(&self) -> Dimens { self.inner.dimensions().into() } pub fn color_type(&self) -> ColorType { self.inner.color().into() } pub fn as_bytes(&self) -> &[u8] { self.inner.as_bytes() } } builder! { #[builder(name = SettingsBuilder, derives=(Debug, Clone))] #[derive(Debug, Default, Clone)] #[non_exhaustive] pub struct Settings { } } impl Settings { pub fn builder() -> SettingsBuilder { SettingsBuilder::default() } } impl Default for SettingsBuilder { fn default() -> Self { Settings::default().into() } } /// An enumeration over supported color types and bit depths #[derive(Copy, PartialEq, Eq, Debug, Clone, Hash)] #[non_exhaustive] pub enum ColorType { /// Pixel is 8-bit luminance L8, /// Pixel is 8-bit luminance with an alpha channel La8, /// Pixel contains 8-bit R, G and B channels Rgb8, /// Pixel is 8-bit RGB with an alpha channel Rgba8, /// Pixel is 16-bit luminance L16, /// Pixel is 16-bit luminance with an alpha channel La16, /// Pixel is 16-bit RGB Rgb16, /// Pixel is 16-bit RGBA Rgba16, /// Pixel is 32-bit float RGB Rgb32F, /// Pixel is 32-bit float RGBA Rgba32F, } impl From for ColorType { fn from(color_type: image_rs::ColorType) -> Self { match color_type { image_rs::ColorType::L8 => Self::L8, image_rs::ColorType::La8 => Self::La8, image_rs::ColorType::Rgb8 => Self::Rgb8, image_rs::ColorType::Rgba8 => Self::Rgba8, image_rs::ColorType::L16 => Self::L16, image_rs::ColorType::La16 => Self::La16, image_rs::ColorType::Rgb16 => Self::Rgb16, image_rs::ColorType::Rgba16 => Self::Rgba16, image_rs::ColorType::Rgb32F => Self::Rgb32F, image_rs::ColorType::Rgba32F => Self::Rgba32F, _ => { panic!("Unrecognized image_rs::ColorType variant"); } } } } pub fn set_asset_importers(assets: &mut Assets) { assets.set_importer::<_, _>(["png", "jpg"], import_asset); } #[derive(Debug, thiserror::Error)] pub enum Error { #[error("Failed to read image file")] ReadFailed(#[source] std::io::Error), #[error("Failed to decode image")] DecodeFailed(DecodeError), #[error("Unsupported image format")] UnsupportedFormat, } #[derive(Debug, thiserror::Error)] #[error(transparent)] pub struct DecodeError(image_rs::ImageError); fn import_asset( asset_submitter: &mut AssetSubmitter<'_>, path: &Path, _settings: Option<&'_ Settings>, ) -> Result<(), Error> { asset_submitter.submit_store::(Image::open(path)?); Ok(()) }