use std::fmt::Display; use std::path::Path; use std::sync::atomic::{AtomicU32, Ordering}; use ecs::Component; 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; 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 { /// Opens a texture image. /// /// # Errors /// Will return `Err` if: /// - Opening the image fails /// - The image data is not 8-bit/color RGB #[allow(clippy::new_without_default)] pub fn open(path: &Path) -> 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::UnsupportedImageDataKind); } }; let dimensions = Dimens { width: image.width(), height: image.height(), }; let id = NEXT_ID.fetch_add(1, Ordering::Relaxed); Ok(Self { id: Id::new(id), image, pixel_data_format, dimensions, properties: Properties::default(), }) } #[must_use] pub fn new_from_color(dimensions: &Dimens, color: &Color) -> Self { 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); Self { id: Id::new(id), image: image.into(), pixel_data_format: PixelDataFormat::Rgb8, dimensions: *dimensions, properties: Properties::default(), } } #[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 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 kind")] UnsupportedImageDataKind, } /// Texture properties #[derive(Debug, Clone)] #[non_exhaustive] pub struct Properties { pub wrap: Wrapping, pub magnifying_filter: Filtering, pub minifying_filter: Filtering, } impl Default for Properties { fn default() -> Self { Self { wrap: Wrapping::Repeat, magnifying_filter: Filtering::Linear, minifying_filter: Filtering::Nearest, } } } /// 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 } } pub(crate) fn into_inner(self) -> u32 { self.id } } impl Display for Id { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.id.fmt(formatter) } }