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}