Skip to main content

goud_engine/assets/loaders/texture/
asset.rs

1//! [`TextureAsset`] — decoded image data ready for GPU upload.
2
3use crate::assets::{Asset, AssetType};
4
5use super::format::TextureFormat;
6
7/// A loaded texture asset containing image data.
8///
9/// `TextureAsset` stores decoded image data in memory. It does not contain
10/// GPU resources - those should be created separately from this data.
11///
12/// # Fields
13///
14/// - `data`: Raw pixel data in RGBA8 format (4 bytes per pixel)
15/// - `width`: Image width in pixels
16/// - `height`: Image height in pixels
17/// - `format`: The original image format (PNG, JPG, etc.)
18///
19/// # Example
20///
21/// ```
22/// use goud_engine::assets::{Asset, loaders::TextureAsset};
23///
24/// let texture = TextureAsset {
25///     data: vec![255; 64 * 64 * 4], // 64x64 white texture
26///     width: 64,
27///     height: 64,
28///     format: goud_engine::assets::loaders::TextureFormat::Png,
29/// };
30///
31/// assert_eq!(texture.pixel_count(), 64 * 64);
32/// assert_eq!(texture.bytes_per_pixel(), 4);
33/// ```
34#[derive(Debug, Clone)]
35pub struct TextureAsset {
36    /// Raw pixel data in RGBA8 format (4 bytes per pixel).
37    pub data: Vec<u8>,
38
39    /// Width of the texture in pixels.
40    pub width: u32,
41
42    /// Height of the texture in pixels.
43    pub height: u32,
44
45    /// The original image format this texture was loaded from.
46    pub format: TextureFormat,
47}
48
49impl TextureAsset {
50    /// Creates a new texture asset from raw RGBA8 data.
51    ///
52    /// # Arguments
53    ///
54    /// - `data`: Raw pixel data in RGBA8 format (must be width × height × 4 bytes)
55    /// - `width`: Image width in pixels
56    /// - `height`: Image height in pixels
57    /// - `format`: The image format this data represents
58    ///
59    /// # Panics
60    ///
61    /// Panics if data length doesn't match width × height × 4.
62    ///
63    /// # Example
64    ///
65    /// ```
66    /// use goud_engine::assets::loaders::{TextureAsset, TextureFormat};
67    ///
68    /// let data = vec![255; 4 * 4 * 4]; // 4x4 white texture
69    /// let texture = TextureAsset::new(data, 4, 4, TextureFormat::Png);
70    /// assert_eq!(texture.pixel_count(), 16);
71    /// ```
72    pub fn new(data: Vec<u8>, width: u32, height: u32, format: TextureFormat) -> Self {
73        assert_eq!(
74            data.len(),
75            (width * height * 4) as usize,
76            "Texture data length mismatch: expected {} bytes for {}x{} RGBA8, got {}",
77            width * height * 4,
78            width,
79            height,
80            data.len()
81        );
82        Self {
83            data,
84            width,
85            height,
86            format,
87        }
88    }
89
90    /// Returns the total number of pixels in the texture.
91    #[inline]
92    pub fn pixel_count(&self) -> u32 {
93        self.width * self.height
94    }
95
96    /// Returns the number of bytes per pixel (always 4 for RGBA8).
97    #[inline]
98    pub const fn bytes_per_pixel(&self) -> u32 {
99        4
100    }
101
102    /// Returns the total size of the texture data in bytes.
103    #[inline]
104    pub fn size_bytes(&self) -> usize {
105        self.data.len()
106    }
107
108    /// Returns the aspect ratio (width / height) of the texture.
109    #[inline]
110    pub fn aspect_ratio(&self) -> f32 {
111        self.width as f32 / self.height as f32
112    }
113
114    /// Returns true if the texture dimensions are powers of two.
115    ///
116    /// Power-of-two textures are required by some older GPUs and can
117    /// be more efficient for certain operations.
118    pub fn is_power_of_two(&self) -> bool {
119        self.width.is_power_of_two() && self.height.is_power_of_two()
120    }
121
122    /// Returns a slice of the pixel data for a specific pixel.
123    ///
124    /// Returns `None` if the coordinates are out of bounds.
125    ///
126    /// # Example
127    ///
128    /// ```
129    /// use goud_engine::assets::loaders::{TextureAsset, TextureFormat};
130    ///
131    /// let data = vec![255, 0, 0, 255, 0, 255, 0, 255]; // 2 pixels: red, green
132    /// let texture = TextureAsset::new(data, 2, 1, TextureFormat::Png);
133    ///
134    /// let pixel = texture.get_pixel(0, 0).unwrap();
135    /// assert_eq!(pixel, &[255, 0, 0, 255]); // Red
136    ///
137    /// let pixel = texture.get_pixel(1, 0).unwrap();
138    /// assert_eq!(pixel, &[0, 255, 0, 255]); // Green
139    /// ```
140    pub fn get_pixel(&self, x: u32, y: u32) -> Option<&[u8]> {
141        if x >= self.width || y >= self.height {
142            return None;
143        }
144        let index = ((y * self.width + x) * 4) as usize;
145        Some(&self.data[index..index + 4])
146    }
147}
148
149impl Asset for TextureAsset {
150    fn asset_type_name() -> &'static str {
151        "Texture"
152    }
153
154    fn asset_type() -> AssetType {
155        AssetType::Texture
156    }
157
158    fn extensions() -> &'static [&'static str] {
159        &[
160            "png", "jpg", "jpeg", "bmp", "tga", "gif", "webp", "ico", "tiff",
161        ]
162    }
163}