use std::fmt::Display; use std::path::Path; use std::sync::atomic::{AtomicU32, Ordering}; use image::io::Reader as ImageReader; use image::{DynamicImage, ImageError, Rgb, RgbImage}; use crate::color::Color; use crate::data_types::dimens::Dimens; use crate::opengl::texture::PixelDataFormat; use crate::util::builder; static NEXT_ID: AtomicU32 = AtomicU32::new(0); mod reexports { pub use crate::opengl::texture::{Filtering, Wrapping}; } pub use reexports::*; #[derive(Debug, Clone)] pub struct Texture { id: Id, image: DynamicImage, pixel_data_format: PixelDataFormat, dimensions: Dimens, properties: Properties, } impl Texture { pub fn builder() -> Builder { Builder::default() } /// Opens a texture image. /// /// # Errors /// Will return `Err` if: /// - Opening the image fails /// - The image data is not 8-bit/color RGB pub fn open(path: &Path) -> Result { Self::builder().open(path) } #[must_use] pub fn new_from_color(dimensions: &Dimens, color: &Color) -> Self { Self::builder().build_with_single_color(dimensions, color) } #[must_use] pub fn id(&self) -> Id { self.id } #[must_use] pub fn properties(&self) -> &Properties { &self.properties } pub fn properties_mut(&mut self) -> &mut Properties { &mut self.properties } #[must_use] pub fn dimensions(&self) -> &Dimens { &self.dimensions } #[must_use] pub fn pixel_data_format(&self) -> PixelDataFormat { self.pixel_data_format } #[must_use] pub fn image(&self) -> &DynamicImage { &self.image } } impl Drop for Texture { fn drop(&mut self) { NEXT_ID.fetch_sub(1, Ordering::Relaxed); } } /// Texture builder. #[derive(Debug, Default, Clone)] pub struct Builder { properties: Properties, } impl Builder { pub fn properties(mut self, properties: Properties) -> Self { self.properties = properties; self } /// Opens a image as a texture. /// /// # Errors /// Will return `Err` if: /// - Opening the image fails /// - Decoding the image fails /// - The image data is in a unsupported format pub fn open(&self, path: &(impl AsRef + ?Sized)) -> Result { let image = ImageReader::open(path) .map_err(Error::OpenImageFailed)? .decode() .map_err(Error::DecodeImageFailed)?; let pixel_data_format = match &image { DynamicImage::ImageRgb8(_) => PixelDataFormat::Rgb8, DynamicImage::ImageRgba8(_) => PixelDataFormat::Rgba8, _ => { return Err(Error::UnsupportedImageDataFormat); } }; let dimensions = Dimens { width: image.width(), height: image.height(), }; let id = NEXT_ID.fetch_add(1, Ordering::Relaxed); Ok(Texture { id: Id::new(id), image, pixel_data_format, dimensions, properties: self.properties.clone(), }) } #[must_use] pub fn build_with_single_color( &self, dimensions: &Dimens, color: &Color, ) -> Texture { let image = RgbImage::from_pixel( dimensions.width, dimensions.height, Rgb([color.red, color.green, color.blue]), ); let id = NEXT_ID.fetch_add(1, Ordering::Relaxed); Texture { id: Id::new(id), image: image.into(), pixel_data_format: PixelDataFormat::Rgb8, dimensions: *dimensions, properties: self.properties.clone(), } } } /// Texture error. #[derive(Debug, thiserror::Error)] pub enum Error { #[error("Failed to open texture image")] OpenImageFailed(#[source] std::io::Error), #[error("Failed to decode texture image")] DecodeImageFailed(#[source] ImageError), #[error("Unsupported image data format")] UnsupportedImageDataFormat, } builder! { /// Texture properties #[builder(name = PropertiesBuilder, derives=(Debug, Clone))] #[derive(Debug, Clone)] #[non_exhaustive] pub struct Properties { pub wrap: Wrapping, pub magnifying_filter: Filtering, pub minifying_filter: Filtering, } } impl Properties { pub fn builder() -> PropertiesBuilder { PropertiesBuilder::default() } } impl Default for Properties { fn default() -> Self { Self { wrap: Wrapping::Repeat, magnifying_filter: Filtering::Linear, minifying_filter: Filtering::Nearest, } } } impl Default for PropertiesBuilder { fn default() -> Self { Properties::default().into() } } /// Texture ID. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Id { id: u32, } impl Id { fn new(id: u32) -> Self { Self { id } } } impl Display for Id { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.id.fmt(formatter) } }