#[allow(dead_code)]
pub struct PixelBuffer {
pub width: u32,
pub height: u32,
pub pixels: Vec<u8>,
}
impl PixelBuffer {
pub fn new(width: u32, height: u32) -> Self {
Self {
width,
height,
pixels: vec![0u8; (width * height * 4) as usize],
}
}
pub fn set_pixel(&mut self, x: u32, y: u32, r: u8, g: u8, b: u8, a: u8) {
let idx = ((y * self.width + x) * 4) as usize;
self.pixels[idx] = r;
self.pixels[idx + 1] = g;
self.pixels[idx + 2] = b;
self.pixels[idx + 3] = a;
}
pub fn get_pixel(&self, x: u32, y: u32) -> [u8; 4] {
let idx = ((y * self.width + x) * 4) as usize;
[
self.pixels[idx],
self.pixels[idx + 1],
self.pixels[idx + 2],
self.pixels[idx + 3],
]
}
pub fn fill(&mut self, r: u8, g: u8, b: u8, a: u8) {
for chunk in self.pixels.chunks_exact_mut(4) {
chunk[0] = r;
chunk[1] = g;
chunk[2] = b;
chunk[3] = a;
}
}
pub fn byte_len(&self) -> usize {
self.pixels.len()
}
pub fn to_tga_bytes(&self) -> Vec<u8> {
let width = self.width as u16;
let height = self.height as u16;
let pixel_count = (self.width * self.height) as usize;
let mut out = Vec::with_capacity(18 + pixel_count * 4);
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) {
let r = chunk[0];
let g = chunk[1];
let b = chunk[2];
let a = chunk[3];
out.push(b);
out.push(g);
out.push(r);
out.push(a);
}
out
}
pub fn save_tga(&self, path: &std::path::Path) -> anyhow::Result<()> {
std::fs::write(path, self.to_tga_bytes()).map_err(Into::into)
}
}
pub fn generate_skin_texture(width: u32, height: u32, r: u8, g: u8, b: u8) -> PixelBuffer {
let mut buf = PixelBuffer::new(width, height);
buf.fill(r, g, b, 255);
buf
}
pub fn generate_checker_texture(width: u32, height: u32, cell_size: u32) -> PixelBuffer {
let mut buf = PixelBuffer::new(width, height);
let cell_size = cell_size.max(1);
for y in 0..height {
for x in 0..width {
let cx = x / cell_size;
let cy = y / cell_size;
if (cx + cy).is_multiple_of(2) {
buf.set_pixel(x, y, 255, 255, 255, 255);
} else {
buf.set_pixel(x, y, 0, 0, 0, 255);
}
}
}
buf
}
pub fn generate_gradient_texture(
width: u32,
height: u32,
top: [u8; 3],
bottom: [u8; 3],
) -> PixelBuffer {
let mut buf = PixelBuffer::new(width, height);
for y in 0..height {
let t = if height <= 1 {
0.0f32
} else {
y as f32 / (height - 1) as f32
};
let r = (top[0] as f32 * (1.0 - t) + bottom[0] as f32 * t).round() as u8;
let g = (top[1] as f32 * (1.0 - t) + bottom[1] as f32 * t).round() as u8;
let b = (top[2] as f32 * (1.0 - t) + bottom[2] as f32 * t).round() as u8;
for x in 0..width {
buf.set_pixel(x, y, r, g, b, 255);
}
}
buf
}
pub fn generate_uv_texture(width: u32, height: u32) -> PixelBuffer {
let mut buf = PixelBuffer::new(width, height);
for y in 0..height {
for x in 0..width {
let r = if width <= 1 {
0u8
} else {
(x as f32 / (width - 1) as f32 * 255.0).round() as u8
};
let g = if height <= 1 {
0u8
} else {
(y as f32 / (height - 1) as f32 * 255.0).round() as u8
};
buf.set_pixel(x, y, r, g, 0, 255);
}
}
buf
}
pub fn generate_flat_normal_map(width: u32, height: u32) -> PixelBuffer {
let mut buf = PixelBuffer::new(width, height);
buf.fill(128, 128, 255, 255);
buf
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pixel_buffer_size() {
let buf = PixelBuffer::new(4, 4);
assert_eq!(buf.byte_len(), 64);
}
#[test]
fn set_get_pixel() {
let mut buf = PixelBuffer::new(4, 4);
buf.set_pixel(1, 1, 255, 0, 128, 255);
assert_eq!(buf.get_pixel(1, 1), [255, 0, 128, 255]);
}
#[test]
fn fill_sets_all() {
let mut buf = PixelBuffer::new(4, 4);
buf.fill(255, 0, 0, 255);
assert_eq!(buf.get_pixel(0, 0), [255, 0, 0, 255]);
assert_eq!(buf.get_pixel(3, 3), [255, 0, 0, 255]);
}
#[test]
fn checker_alternates() {
let cell_size = 4u32;
let buf = generate_checker_texture(16, 16, cell_size);
let p0 = buf.get_pixel(0, 0);
let p1 = buf.get_pixel(cell_size, 0);
assert_ne!(p0, p1);
}
#[test]
fn gradient_top_equals_top_color() {
let top = [255u8, 0, 0];
let bottom = [0u8, 0, 255];
let buf = generate_gradient_texture(8, 8, top, bottom);
let p = buf.get_pixel(0, 0);
assert_eq!([p[0], p[1], p[2]], top);
}
#[test]
fn gradient_bottom_equals_bottom_color() {
let top = [255u8, 0, 0];
let bottom = [0u8, 0, 255];
let buf = generate_gradient_texture(8, 8, top, bottom);
let p = buf.get_pixel(0, 7);
assert_eq!([p[0], p[1], p[2]], bottom);
}
#[test]
fn uv_texture_top_left_is_zero() {
let buf = generate_uv_texture(8, 8);
let p = buf.get_pixel(0, 0);
assert_eq!(p[0], 0); assert_eq!(p[1], 0); }
#[test]
fn flat_normal_map_is_blue() {
let buf = generate_flat_normal_map(4, 4);
let p = buf.get_pixel(0, 0);
assert_eq!(p[2], 255); }
#[test]
fn tga_header_magic() {
let buf = generate_skin_texture(4, 4, 200, 150, 130);
let bytes = buf.to_tga_bytes();
assert_eq!(bytes[2], 2); }
#[test]
fn save_tga_creates_file() {
let buf = generate_uv_texture(16, 16);
let path = std::path::Path::new("/tmp/test_texture.tga");
buf.save_tga(path).expect("save_tga failed");
assert!(path.exists());
}
}