use std::collections::HashMap; use std::fmt::Display; use std::path::Path; use ecs::Component; use image::io::Reader as ImageReader; use image::{DynamicImage, ImageError, Rgb, RgbImage}; use crate::color::Color; use crate::opengl::texture::PixelDataFormat; use crate::vector::Vec2; mod reexports { pub use crate::opengl::texture::{Filtering, Wrapping}; } pub use reexports::*; #[derive(Debug, Clone)] pub struct Texture { image: DynamicImage, pixel_data_format: PixelDataFormat, dimensions: Vec2, 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::Rgb, DynamicImage::ImageRgba8(_) => PixelDataFormat::Rgba, _ => { return Err(Error::UnsupportedImageDataKind); } }; let dimensions = Vec2 { x: image.width(), y: image.height() }; Ok(Self { image, pixel_data_format, dimensions, properties: Properties::default(), }) } #[must_use] pub fn new_from_color(dimensions: &Vec2, color: &Color) -> Self { let image = RgbImage::from_pixel( dimensions.x, dimensions.y, Rgb([color.red, color.green, color.blue]), ); Self { image: image.into(), pixel_data_format: PixelDataFormat::Rgb, dimensions: dimensions.clone(), properties: Properties::default(), } } #[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) -> &Vec2 { &self.dimensions } #[must_use] pub fn pixel_data_format(&self) -> PixelDataFormat { self.pixel_data_format } #[must_use] pub fn image(&self) -> &DynamicImage { &self.image } } /// 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 { /// Returns a new texture ID. #[must_use] pub 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) } } /// Texture map. #[derive(Component)] pub struct Map { pub inner: HashMap, } impl FromIterator<(Id, Texture)> for Map { fn from_iter(iter: Iter) -> Self where Iter: IntoIterator, { Self { inner: iter.into_iter().collect() } } }