1#[allow(dead_code)]
8pub struct PixelBuffer {
10 pub width: u32,
11 pub height: u32,
12 pub pixels: Vec<u8>,
14}
15
16impl PixelBuffer {
17 pub fn new(width: u32, height: u32) -> Self {
19 Self {
20 width,
21 height,
22 pixels: vec![0u8; (width * height * 4) as usize],
23 }
24 }
25
26 pub fn set_pixel(&mut self, x: u32, y: u32, r: u8, g: u8, b: u8, a: u8) {
28 let idx = ((y * self.width + x) * 4) as usize;
29 self.pixels[idx] = r;
30 self.pixels[idx + 1] = g;
31 self.pixels[idx + 2] = b;
32 self.pixels[idx + 3] = a;
33 }
34
35 pub fn get_pixel(&self, x: u32, y: u32) -> [u8; 4] {
37 let idx = ((y * self.width + x) * 4) as usize;
38 [
39 self.pixels[idx],
40 self.pixels[idx + 1],
41 self.pixels[idx + 2],
42 self.pixels[idx + 3],
43 ]
44 }
45
46 pub fn fill(&mut self, r: u8, g: u8, b: u8, a: u8) {
48 for chunk in self.pixels.chunks_exact_mut(4) {
49 chunk[0] = r;
50 chunk[1] = g;
51 chunk[2] = b;
52 chunk[3] = a;
53 }
54 }
55
56 pub fn byte_len(&self) -> usize {
58 self.pixels.len()
59 }
60
61 pub fn to_tga_bytes(&self) -> Vec<u8> {
65 let width = self.width as u16;
66 let height = self.height as u16;
67 let pixel_count = (self.width * self.height) as usize;
68
69 let mut out = Vec::with_capacity(18 + pixel_count * 4);
71
72 out.push(0u8); out.push(0u8); out.push(2u8); out.extend_from_slice(&[0u8, 0u8]); out.extend_from_slice(&[0u8, 0u8]); out.push(0u8); out.extend_from_slice(&[0u8, 0u8]); out.extend_from_slice(&[0u8, 0u8]); out.extend_from_slice(&width.to_le_bytes()); out.extend_from_slice(&height.to_le_bytes()); out.push(32u8); out.push(0x28u8); for chunk in self.pixels.chunks_exact(4) {
87 let r = chunk[0];
88 let g = chunk[1];
89 let b = chunk[2];
90 let a = chunk[3];
91 out.push(b);
92 out.push(g);
93 out.push(r);
94 out.push(a);
95 }
96
97 out
98 }
99
100 pub fn save_tga(&self, path: &std::path::Path) -> anyhow::Result<()> {
102 std::fs::write(path, self.to_tga_bytes()).map_err(Into::into)
103 }
104}
105
106pub fn generate_skin_texture(width: u32, height: u32, r: u8, g: u8, b: u8) -> PixelBuffer {
108 let mut buf = PixelBuffer::new(width, height);
109 buf.fill(r, g, b, 255);
110 buf
111}
112
113pub fn generate_checker_texture(width: u32, height: u32, cell_size: u32) -> PixelBuffer {
115 let mut buf = PixelBuffer::new(width, height);
116 let cell_size = cell_size.max(1);
117 for y in 0..height {
118 for x in 0..width {
119 let cx = x / cell_size;
120 let cy = y / cell_size;
121 if (cx + cy).is_multiple_of(2) {
122 buf.set_pixel(x, y, 255, 255, 255, 255);
123 } else {
124 buf.set_pixel(x, y, 0, 0, 0, 255);
125 }
126 }
127 }
128 buf
129}
130
131pub fn generate_gradient_texture(
133 width: u32,
134 height: u32,
135 top: [u8; 3],
136 bottom: [u8; 3],
137) -> PixelBuffer {
138 let mut buf = PixelBuffer::new(width, height);
139 for y in 0..height {
140 let t = if height <= 1 {
141 0.0f32
142 } else {
143 y as f32 / (height - 1) as f32
144 };
145 let r = (top[0] as f32 * (1.0 - t) + bottom[0] as f32 * t).round() as u8;
146 let g = (top[1] as f32 * (1.0 - t) + bottom[1] as f32 * t).round() as u8;
147 let b = (top[2] as f32 * (1.0 - t) + bottom[2] as f32 * t).round() as u8;
148 for x in 0..width {
149 buf.set_pixel(x, y, r, g, b, 255);
150 }
151 }
152 buf
153}
154
155pub fn generate_uv_texture(width: u32, height: u32) -> PixelBuffer {
158 let mut buf = PixelBuffer::new(width, height);
159 for y in 0..height {
160 for x in 0..width {
161 let r = if width <= 1 {
162 0u8
163 } else {
164 (x as f32 / (width - 1) as f32 * 255.0).round() as u8
165 };
166 let g = if height <= 1 {
167 0u8
168 } else {
169 (y as f32 / (height - 1) as f32 * 255.0).round() as u8
170 };
171 buf.set_pixel(x, y, r, g, 0, 255);
172 }
173 }
174 buf
175}
176
177pub fn generate_flat_normal_map(width: u32, height: u32) -> PixelBuffer {
179 let mut buf = PixelBuffer::new(width, height);
180 buf.fill(128, 128, 255, 255);
181 buf
182}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187
188 #[test]
189 fn pixel_buffer_size() {
190 let buf = PixelBuffer::new(4, 4);
191 assert_eq!(buf.byte_len(), 64);
192 }
193
194 #[test]
195 fn set_get_pixel() {
196 let mut buf = PixelBuffer::new(4, 4);
197 buf.set_pixel(1, 1, 255, 0, 128, 255);
198 assert_eq!(buf.get_pixel(1, 1), [255, 0, 128, 255]);
199 }
200
201 #[test]
202 fn fill_sets_all() {
203 let mut buf = PixelBuffer::new(4, 4);
204 buf.fill(255, 0, 0, 255);
205 assert_eq!(buf.get_pixel(0, 0), [255, 0, 0, 255]);
206 assert_eq!(buf.get_pixel(3, 3), [255, 0, 0, 255]);
207 }
208
209 #[test]
210 fn checker_alternates() {
211 let cell_size = 4u32;
212 let buf = generate_checker_texture(16, 16, cell_size);
213 let p0 = buf.get_pixel(0, 0);
214 let p1 = buf.get_pixel(cell_size, 0);
215 assert_ne!(p0, p1);
216 }
217
218 #[test]
219 fn gradient_top_equals_top_color() {
220 let top = [255u8, 0, 0];
221 let bottom = [0u8, 0, 255];
222 let buf = generate_gradient_texture(8, 8, top, bottom);
223 let p = buf.get_pixel(0, 0);
224 assert_eq!([p[0], p[1], p[2]], top);
225 }
226
227 #[test]
228 fn gradient_bottom_equals_bottom_color() {
229 let top = [255u8, 0, 0];
230 let bottom = [0u8, 0, 255];
231 let buf = generate_gradient_texture(8, 8, top, bottom);
232 let p = buf.get_pixel(0, 7);
233 assert_eq!([p[0], p[1], p[2]], bottom);
234 }
235
236 #[test]
237 fn uv_texture_top_left_is_zero() {
238 let buf = generate_uv_texture(8, 8);
239 let p = buf.get_pixel(0, 0);
240 assert_eq!(p[0], 0); assert_eq!(p[1], 0); }
243
244 #[test]
245 fn flat_normal_map_is_blue() {
246 let buf = generate_flat_normal_map(4, 4);
247 let p = buf.get_pixel(0, 0);
248 assert_eq!(p[2], 255); }
250
251 #[test]
252 fn tga_header_magic() {
253 let buf = generate_skin_texture(4, 4, 200, 150, 130);
254 let bytes = buf.to_tga_bytes();
255 assert_eq!(bytes[2], 2); }
257
258 #[test]
259 fn save_tga_creates_file() {
260 let buf = generate_uv_texture(16, 16);
261 let path = std::path::Path::new("/tmp/test_texture.tga");
262 buf.save_tga(path).expect("save_tga failed");
263 assert!(path.exists());
264 }
265}