use std::path::Path; use image::io::Reader as ImageReader; use image::{DynamicImage, EncodableLayout, ImageError, Rgb, RgbImage}; use crate::color::Color; use crate::opengl::texture::{PixelDataFormat, Texture as InnerTexture}; use crate::vector::Vec2; mod reexports { pub use crate::opengl::texture::{Filtering, Wrapping}; } pub use reexports::*; #[derive(Debug)] pub struct Texture { inner: InnerTexture, pixel_data_format: PixelDataFormat, dimensions: Vec2, } 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 inner = InnerTexture::new(); let dimensions = Vec2 { x: image.width(), y: image.height() }; inner.bind(|texture_curr_bound| { InnerTexture::generate( &texture_curr_bound, &dimensions, image.as_bytes(), pixel_data_format, ); }); let me = Self { inner, pixel_data_format, dimensions }; me.set_wrap(Wrapping::Repeat); me.set_magnifying_filter(Filtering::Linear); me.set_minifying_filter(Filtering::Nearest); Ok(me) } #[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]), ); let inner = InnerTexture::new(); inner.bind(|texture_curr_bound| { InnerTexture::generate( &texture_curr_bound, dimensions, image.as_bytes(), PixelDataFormat::Rgb, ); }); let me = Self { inner, pixel_data_format: PixelDataFormat::Rgb, dimensions: dimensions.clone(), }; me.set_wrap(Wrapping::Repeat); me.set_magnifying_filter(Filtering::Linear); me.set_minifying_filter(Filtering::Nearest); me } pub fn set_wrap(&self, wrapping: Wrapping) { self.inner.bind(|texture_curr_bound| { InnerTexture::set_wrap(texture_curr_bound, wrapping); }); } pub fn set_magnifying_filter(&self, filtering: Filtering) { self.inner.bind(|texture_curr_bound| { InnerTexture::set_magnifying_filter(texture_curr_bound, filtering); }); } pub fn set_minifying_filter(&self, filtering: Filtering) { self.inner.bind(|texture_curr_bound| { InnerTexture::set_minifying_filter(texture_curr_bound, filtering); }); } pub(crate) fn inner(&self) -> &InnerTexture { &self.inner } } impl Clone for Texture { fn clone(&self) -> Self { let inner = self.inner.copy( &self.dimensions, self.pixel_data_format, &Vec2::ZERO, &Vec2::ZERO, ); Self { inner, pixel_data_format: self.pixel_data_format, dimensions: self.dimensions.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 kind")] UnsupportedImageDataKind, } /// 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 } }