Compare commits

..

3 commits

Author SHA1 Message Date
Joe Ardent
ba91baa302 first not-great line impl 2025-09-05 15:26:38 -07:00
Joe Ardent
d51a2dd5f4 tweak 2025-09-05 12:42:25 -07:00
Joe Ardent
ea3c4e89e0 can write and read RLE TGA files 2025-09-05 12:39:01 -07:00
4 changed files with 181 additions and 40 deletions

1
.gitignore vendored
View file

@ -1 +1,2 @@
/target /target
*.tga

View file

@ -2,5 +2,68 @@
extern crate justerror; extern crate justerror;
mod tga; mod tga;
use tga::*;
fn main() {} mod point;
use point::*;
const WHITE: TGAColor = TGAColor {
bgra: [255, 255, 255, 255],
};
const GREEN: TGAColor = TGAColor {
bgra: [0, 255, 0, 255],
};
const RED: TGAColor = TGAColor {
bgra: [0, 0, 255, 255],
};
const BLUE: TGAColor = TGAColor {
bgra: [255, 128, 64, 255],
};
const YELLOW: TGAColor = TGAColor {
bgra: [0, 200, 255, 255],
};
fn main() {
let w = 64;
let h = 64;
let mut framebuffer = TGAImage::new(w, h, TGAFormat::RGB);
let ax = 7;
let ay = 3;
let bx = 12;
let by = 37;
let cx = 62;
let cy = 53;
framebuffer.set(ax, ay, WHITE);
framebuffer.set(bx, by, WHITE);
framebuffer.set(cx, cy, WHITE);
let a = Point::new(ax, ay);
let b = Point::new(bx, by);
let c = Point::new(cx, cy);
line(a, b, &mut framebuffer, BLUE);
line(c, b, &mut framebuffer, GREEN);
line(c, a, &mut framebuffer, YELLOW);
line(a, c, &mut framebuffer, RED);
framebuffer
.write_file("framebuffer.tga", true, true)
.unwrap();
}
fn line(a: Point, b: Point, fb: &mut TGAImage, color: TGAColor) {
let mut t = 0.0;
let step = 0.02;
while t < 1.0 {
let x = (a.x as f32 + (b.x - a.x) as f32 * t).round_ties_even() as u32;
let y = (a.y as f32 + (b.y - a.y) as f32 * t).round_ties_even() as u32;
fb.set(x, y, color);
t += step;
}
}

49
src/point.rs Normal file
View file

@ -0,0 +1,49 @@
use std::ops::{Add, Mul, Sub};
#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Point {
pub x: i32,
pub y: i32,
}
impl Point {
pub fn new(x: u32, y: u32) -> Self {
Self {
x: x as i32,
y: y as i32,
}
}
}
impl Add for Point {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self {
x: self.x + rhs.x,
y: self.y + rhs.y,
}
}
}
impl Sub for Point {
type Output = Self;
fn sub(self, rhs: Self) -> Self::Output {
Self {
x: self.x.saturating_sub(rhs.x),
y: self.y.saturating_sub(rhs.y),
}
}
}
impl Mul for Point {
type Output = Self;
fn mul(self, rhs: Self) -> Self::Output {
Self {
x: self.x * rhs.x,
y: self.y * rhs.y,
}
}
}

View file

@ -6,6 +6,8 @@ use std::{
const HEADER_SIZE: usize = 18; const HEADER_SIZE: usize = 18;
pub type ColorBuf = [u8; 4];
#[derive(Debug, Default, Clone, PartialEq, Eq)] #[derive(Debug, Default, Clone, PartialEq, Eq)]
#[repr(C, packed)] #[repr(C, packed)]
pub struct TGAHeader { pub struct TGAHeader {
@ -25,14 +27,14 @@ pub struct TGAHeader {
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
#[repr(u8)] #[repr(u8)]
pub enum Format { pub enum TGAFormat {
Grayscale = 1, Grayscale = 1,
RGB = 3, RGB = 3,
#[default] #[default]
RGBA = 4, RGBA = 4,
} }
impl TryFrom<u8> for Format { impl TryFrom<u8> for TGAFormat {
type Error = String; type Error = String;
fn try_from(value: u8) -> Result<Self, Self::Error> { fn try_from(value: u8) -> Result<Self, Self::Error> {
@ -48,11 +50,11 @@ impl TryFrom<u8> for Format {
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
#[repr(transparent)] #[repr(transparent)]
pub struct TGAColor { pub struct TGAColor {
pub bgra: [u8; 4], pub bgra: ColorBuf,
} }
impl Deref for TGAColor { impl Deref for TGAColor {
type Target = [u8; 4]; type Target = ColorBuf;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
&self.bgra &self.bgra
@ -65,15 +67,17 @@ impl DerefMut for TGAColor {
} }
} }
impl From<[u8; 4]> for TGAColor { impl From<ColorBuf> for TGAColor {
fn from(value: [u8; 4]) -> Self { fn from(value: ColorBuf) -> Self {
Self { bgra: value } Self { bgra: value }
} }
} }
impl TGAColor { impl TGAColor {
pub fn new() -> Self { pub fn new() -> Self {
Self { bgra: [0; 4] } Self {
bgra: ColorBuf::default(),
}
} }
pub fn b(&mut self) -> &mut u8 { pub fn b(&mut self) -> &mut u8 {
@ -94,10 +98,10 @@ impl TGAColor {
} }
#[derive(Debug, Default, Clone, PartialEq, Eq)] #[derive(Debug, Default, Clone, PartialEq, Eq)]
struct TGAImage { pub struct TGAImage {
pub format: Format, pub format: TGAFormat,
pub width: u16, pub width: u32,
pub height: u16, pub height: u32,
data: Vec<u8>, data: Vec<u8>,
} }
@ -128,12 +132,13 @@ impl From<std::io::Error> for TGAError {
} }
impl TGAImage { impl TGAImage {
pub fn new(width: u16, height: u16, format: Format) -> Self { pub fn new(width: u32, height: u32, format: TGAFormat) -> Self {
let size = format as usize * (width * height) as usize;
Self { Self {
format, format,
width, width,
height, height,
data: Vec::with_capacity((format as usize) * (width * height) as usize), data: vec![0u8; size],
} }
} }
@ -154,11 +159,11 @@ impl TGAImage {
} }
self.format = (header.bits_per_pixel >> 3).try_into()?; self.format = (header.bits_per_pixel >> 3).try_into()?;
self.width = header.width; self.width = header.width as u32;
self.height = header.height; self.height = header.height as u32;
let nbytes = self.format as usize * (self.width * self.height) as usize; let nbytes = self.format as usize * (self.width * self.height) as usize;
let mut image_data = Vec::with_capacity(nbytes); let mut image_data = vec![0u8; nbytes];
match header.datatype_code { match header.datatype_code {
2 | 3 => { 2 | 3 => {
file.read_to_end(&mut image_data)?; file.read_to_end(&mut image_data)?;
@ -175,26 +180,16 @@ impl TGAImage {
} }
pub fn write_file(&mut self, file: &str, do_vflip: bool, do_rle: bool) -> IoResult { pub fn write_file(&mut self, file: &str, do_vflip: bool, do_rle: bool) -> IoResult {
let mut developer_area = [0u8; 4];
let mut extension_area = [0u8; 4];
let mut footer: Vec<u8> = vec![
'T', 'R', 'U', 'E', 'V', 'I', 'S', 'I', 'O', 'N', '-', 'X', 'F', 'I', 'L', 'E', '.',
'\0',
]
.into_iter()
.map(|b| b as u8)
.collect();
let mut file = std::fs::File::create(file)?; let mut file = std::fs::File::create(file)?;
let mut header = TGAHeader::default(); let mut header = TGAHeader::default();
header.bits_per_pixel = self.bytes_per_pixel() << 3; header.bits_per_pixel = self.bytes_per_pixel() << 3;
header.width = self.width; header.width = self.width as u16;
header.height = self.height; header.height = self.height as u16;
header.datatype_code = match self.format { header.datatype_code = match self.format {
Format::Grayscale if do_rle => 11, TGAFormat::Grayscale if do_rle => 11,
Format::Grayscale => 3, TGAFormat::Grayscale => 3,
_ if do_rle => 10, _ if do_rle => 10,
_ => 2, _ => 2,
}; };
@ -211,6 +206,18 @@ impl TGAImage {
file.write_all(&mut self.data)?; file.write_all(&mut self.data)?;
} }
file.flush()?;
let mut developer_area = ColorBuf::default();
let mut extension_area = ColorBuf::default();
let mut footer: Vec<u8> = vec![
'T', 'R', 'U', 'E', 'V', 'I', 'S', 'I', 'O', 'N', '-', 'X', 'F', 'I', 'L', 'E', '.',
'\0',
]
.into_iter()
.map(|b| b as u8)
.collect();
file.write_all(&mut developer_area)?; file.write_all(&mut developer_area)?;
file.write_all(&mut extension_area)?; file.write_all(&mut extension_area)?;
file.write_all(&mut footer)?; file.write_all(&mut footer)?;
@ -228,12 +235,31 @@ impl TGAImage {
todo!() todo!()
} }
pub fn set(&mut self, x: u32, y: u32, color: &TGAColor) { pub fn set(&mut self, x: u32, y: u32, color: TGAColor) {
todo!() if self.data.is_empty() || x > self.width || y > self.height {
return;
}
let bpp = self.bytes_per_pixel() as usize;
let start = (x + y * self.width) as usize * bpp;
let end = start + bpp;
self.data[start..end].copy_from_slice(&color[0..bpp]);
} }
pub fn get(&self, x: u32, y: u32) -> Option<TGAColor> { pub fn get(&self, x: u32, y: u32) -> Option<TGAColor> {
todo!() if self.data.is_empty() || x >= self.width || y >= self.height {
return None;
}
let bpp = self.bytes_per_pixel() as usize;
let mut color = TGAColor::new();
let start = bpp * (x + y * self.width) as usize;
let end = start + bpp;
let data = &self.data[start..end];
color[0..bpp].copy_from_slice(data);
Some(color)
} }
pub fn bytes_per_pixel(&self) -> u8 { pub fn bytes_per_pixel(&self) -> u8 {
@ -296,7 +322,7 @@ impl TGAImage {
let mut current_byte = current_pixel * bpp; let mut current_byte = current_pixel * bpp;
let mut run_length = 1; let mut run_length = 1;
let mut raw = true; let mut is_raw = true;
while (current_pixel + run_length) < num_pixels && run_length < max_chunk_len { while (current_pixel + run_length) < num_pixels && run_length < max_chunk_len {
let mut succ_eq = true; let mut succ_eq = true;
@ -307,29 +333,31 @@ impl TGAImage {
} }
current_byte += bpp; current_byte += bpp;
if 1 == run_length { if 1 == run_length {
raw = !succ_eq; is_raw = !succ_eq;
} }
if raw && succ_eq { if is_raw && succ_eq {
run_length -= 1; run_length -= 1;
break; break;
} }
if !(raw || succ_eq) { if !(is_raw || succ_eq) {
break; break;
} }
run_length += 1; run_length += 1;
} }
current_pixel += run_length; current_pixel += run_length;
let mut out = if raw { let mut out = if is_raw {
[run_length as u8 - 1] [run_length as u8 - 1]
} else { } else {
[run_length as u8 + 127] [run_length as u8 + 127]
}; };
file.write_all(&mut out)?; file.write_all(&mut out)?;
let chunk_end = if raw { run_length * bpp } else { bpp }; let chunk_end = chunk_start + if is_raw { run_length * bpp } else { bpp };
file.write_all(&mut self.data[chunk_start..chunk_end])?; file.write_all(&mut self.data[chunk_start..chunk_end])?;
} }
file.flush()?;
Ok(()) Ok(())
} }
} }