use std::fs::File; use std::io::{BufRead, BufReader, Seek}; use std::marker::PhantomData; use std::ops::Deref; use std::path::Path; use image_rs::GenericImageView as _; use crate::color::{Color, Luma, LumaA, Pixel as ColorPixel, Rgb, Rgba}; use crate::data_types::dimens::Dimens; use crate::vector::Vec2; #[derive(Debug, Clone)] 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::Io)?); let image_reader = image_rs::ImageReader::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_reader(reader: impl BufRead + Seek, format: Format) -> Result { let image_reader = image_rs::ImageReader::with_format(reader, format.into_image_rs()); Ok(Self { inner: image_reader .decode() // TODO: err might be other errors than decode errors here .map_err(|err| Error::DecodeFailed(DecodeError(err)))?, }) } pub fn from_pixels( pixels: PixelBuffer, ) -> Result where PixelT: ColorPixel, Buf: Deref, Self: TryFrom, Error = PixelBufferLenIncorrectForSize>, { Self::try_from(pixels) } 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 from_color_and_alpha( dimens: impl Into>, color: impl Into>, alpha: u8, ) -> Self { let dimens: Dimens = dimens.into(); let color: Color = color.into(); Self { inner: image_rs::RgbaImage::from_pixel( dimens.width, dimens.height, image_rs::Rgba([color.red, color.green, color.blue, alpha]), ) .into(), } } pub fn dimensions(&self) -> Dimens { self.inner.dimensions().into() } pub fn color_type(&self) -> ColorType { self.inner.color().into() } pub fn color_space_is_srgb(&self) -> bool { match self.inner.color_space().primaries { image_rs::metadata::CicpColorPrimaries::SRgb => true, _ => false, } } pub fn to_rgba8(&self) -> Self { Self { inner: self.inner.to_rgba8().into() } } /// Consumes the image and returns a RGBA8 image. If it already is RGBA8, the image is /// returned as is. If not, the image is converted to RGBA8. pub fn into_rgba8(self) -> Self { Self { inner: self.inner.into_rgba8().into(), } } pub fn as_bytes(&self) -> &[u8] { self.inner.as_bytes() } pub fn into_bytes(self) -> Vec { self.inner.into_bytes() } } macro_rules! gen_try_from_pixel_buffer_impl { ($pixel: ident<$component: ty>) => { impl TryFrom, Buf>> for Image where Buf: Deref, { type Error = PixelBufferLenIncorrectForSize; fn try_from( pixels: PixelBuffer<$pixel<$component>, Buf>, ) -> Result { Ok( Image { inner: image_rs::ImageBuffer::< image_rs::$pixel<$component>, Vec<_>, >::from_raw( pixels.size.width, pixels.size.height, pixels.buf.deref().to_vec(), ) .ok_or(PixelBufferLenIncorrectForSize)? .into(), }, ) } } }; } gen_try_from_pixel_buffer_impl!(Rgb); gen_try_from_pixel_buffer_impl!(Rgb); gen_try_from_pixel_buffer_impl!(Rgb); gen_try_from_pixel_buffer_impl!(Rgba); gen_try_from_pixel_buffer_impl!(Rgba); gen_try_from_pixel_buffer_impl!(Rgba); gen_try_from_pixel_buffer_impl!(Luma); gen_try_from_pixel_buffer_impl!(Luma); gen_try_from_pixel_buffer_impl!(LumaA); gen_try_from_pixel_buffer_impl!(LumaA); #[derive(Debug)] pub struct PixelBuffer where PixelT: ColorPixel, Buf: Deref, { buf: Buf, size: Dimens, _pd: PhantomData, } impl PixelBuffer where PixelT: ColorPixel, Buf: Deref, { pub fn new(buf: Buf, size: Dimens) -> Self { Self { buf, size, _pd: PhantomData } } } /// 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"); } } } } #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] #[non_exhaustive] pub enum Format { Png, Jpeg, Ico, } impl Format { fn into_image_rs(self) -> image_rs::ImageFormat { match self { Self::Png => image_rs::ImageFormat::Png, Self::Jpeg => image_rs::ImageFormat::Jpeg, Self::Ico => image_rs::ImageFormat::Ico, } } } #[derive(Debug, thiserror::Error)] pub enum Error { #[error("I/O error")] Io(#[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); #[derive(Debug, thiserror::Error)] #[error("Length of pixel buffer is incorrect for the given image size")] pub struct PixelBufferLenIncorrectForSize;