file io might be done, untested

This commit is contained in:
Joe Ardent 2025-09-05 11:44:18 -07:00
parent 7d190cec88
commit 5492f3d4eb
4 changed files with 289 additions and 19 deletions

81
Cargo.lock generated
View file

@ -2,6 +2,87 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 4 version = 4
[[package]]
name = "justerror"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "befab2078d3ff679889e32730a1e9107b06a60a18ed7dfa3384f66ebe5e1062a"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "proc-macro2"
version = "1.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "2.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.106",
]
[[package]] [[package]]
name = "tinyrender" name = "tinyrender"
version = "0.1.0" version = "0.1.0"
dependencies = [
"justerror",
"thiserror",
]
[[package]]
name = "unicode-ident"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"

View file

@ -4,3 +4,5 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
justerror = "1.1.0"
thiserror = "2.0.16"

View file

@ -1,3 +1,6 @@
fn main() { #[macro_use]
println!("Hello, world!"); extern crate justerror;
}
mod tga;
fn main() {}

View file

@ -1,9 +1,11 @@
use std::{ use std::{
fs::File, fs::File,
io::Read, io::{Read, Write},
ops::{Deref, DerefMut}, ops::{Deref, DerefMut},
}; };
const HEADER_SIZE: usize = 18;
#[derive(Debug, Default, Clone, PartialEq, Eq)] #[derive(Debug, Default, Clone, PartialEq, Eq)]
#[repr(C, packed)] #[repr(C, packed)]
pub struct TGAHeader { pub struct TGAHeader {
@ -63,24 +65,70 @@ impl DerefMut for TGAColor {
} }
} }
impl From<[u8; 4]> for TGAColor {
fn from(value: [u8; 4]) -> Self {
Self { bgra: value }
}
}
impl TGAColor { impl TGAColor {
pub fn new() -> Self { pub fn new() -> Self {
Self { bgra: [0; 4] } Self { bgra: [0; 4] }
} }
pub fn b(&mut self) -> &mut u8 {
&mut self[0]
}
pub fn g(&mut self) -> &mut u8 {
&mut self[1]
}
pub fn r(&mut self) -> &mut u8 {
&mut self[2]
}
pub fn a(&mut self) -> &mut u8 {
&mut self[3]
}
} }
#[derive(Debug, Default, Clone, PartialEq, Eq)] #[derive(Debug, Default, Clone, PartialEq, Eq)]
struct TGAImage { struct TGAImage {
pub format: Format, pub format: Format,
pub width: u32, pub width: u16,
pub height: u32, pub height: u16,
data: Vec<u8>, data: Vec<u8>,
} }
pub type IoResult = std::io::Result<()>; pub type IoResult = std::result::Result<(), TGAError>;
#[Error]
pub enum TGAError {
IoError(std::io::Error),
Other(String),
}
impl From<String> for TGAError {
fn from(value: String) -> Self {
Self::Other(value)
}
}
impl From<&str> for TGAError {
fn from(value: &str) -> Self {
Self::Other(value.to_string())
}
}
impl From<std::io::Error> for TGAError {
fn from(value: std::io::Error) -> Self {
Self::IoError(value)
}
}
impl TGAImage { impl TGAImage {
pub fn new(width: u32, height: u32, format: Format) -> Self { pub fn new(width: u16, height: u16, format: Format) -> Self {
Self { Self {
format, format,
width, width,
@ -89,20 +137,20 @@ impl TGAImage {
} }
} }
pub fn bytes_per_pixel(&self) -> u8 { pub fn from_file(file: &str) -> Result<Self, TGAError> {
self.format as u8 let mut img = TGAImage::default();
img.read_file(file)?;
Ok(img)
} }
pub fn read_file(&mut self, file: &str) -> IoResult { pub fn read_file(&mut self, file: &str) -> IoResult {
let mut file = std::fs::File::open(file)?; let mut file = std::fs::File::open(file)?;
let header_size = size_of::<TGAHeader>(); let mut header_bytes = [0u8; HEADER_SIZE];
assert_eq!(header_size, 18); file.read_exact(&mut header_bytes)?;
let mut bytes = [0u8; header_size]; let header: TGAHeader = unsafe { std::mem::transmute(header_bytes) };
file.read_exact(&mut bytes)?;
let header: TGAHeader = unsafe { std::mem::transmute(bytes) };
if header.width < 1 || header.height < 1 { if header.width < 1 || header.height < 1 {
return Err("bad image metadata".into()); return Err("bad image metadata".to_string().into());
} }
self.format = (header.bits_per_pixel >> 3).try_into()?; self.format = (header.bits_per_pixel >> 3).try_into()?;
@ -123,10 +171,53 @@ impl TGAImage {
return Err("invalid TGA file".into()); return Err("invalid TGA file".into());
} }
} }
Ok(())
} }
pub fn write_file(&self, file: &str) -> IoResult { pub fn write_file(&mut self, file: &str, do_vflip: bool, do_rle: bool) -> IoResult {
todo!() 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 header = TGAHeader::default();
header.bits_per_pixel = self.bytes_per_pixel() << 3;
header.width = self.width;
header.height = self.height;
header.datatype_code = match self.format {
Format::Grayscale if do_rle => 11,
Format::Grayscale => 3,
_ if do_rle => 10,
_ => 2,
};
header.image_descriptor = if do_vflip { 0x00 } else { 0x20 };
let mut header: [u8; HEADER_SIZE] = unsafe { std::mem::transmute(header) };
file.write_all(&mut header)?;
if do_rle {
self.write_rle_file(&mut file)?;
} else {
file.write_all(&mut self.data)?;
}
file.write_all(&mut developer_area)?;
file.write_all(&mut extension_area)?;
file.write_all(&mut footer)?;
file.flush()?;
Ok(())
} }
pub fn flip_horizontal(&mut self) { pub fn flip_horizontal(&mut self) {
@ -145,7 +236,100 @@ impl TGAImage {
todo!() todo!()
} }
pub fn bytes_per_pixel(&self) -> u8 {
self.format as u8
}
fn read_rle_file(&mut self, file: &mut File) -> IoResult { fn read_rle_file(&mut self, file: &mut File) -> IoResult {
todo!() let num_pixels = (self.width * self.height) as usize;
let bpp = self.bytes_per_pixel() as usize;
self.data.resize(num_pixels * bpp, 0);
let mut current_pixel = 0;
let mut current_byte = 0;
let mut buffer = vec![0u8; bpp];
while current_pixel < num_pixels {
let mut chunk_header = [0u8; 1];
file.read_exact(&mut chunk_header)?;
let [mut chunk_header] = chunk_header;
if chunk_header < 128 {
chunk_header += 1;
for _ in 0..chunk_header {
file.read_exact(&mut buffer)?;
for &b in buffer.iter() {
self.data[current_byte] = b;
current_byte += 1;
}
current_pixel += 1;
if current_pixel > num_pixels {
return Err("too many pixels read".into());
}
}
} else {
chunk_header -= 127;
file.read_exact(&mut buffer)?;
for _ in 0..chunk_header {
for &b in buffer.iter() {
self.data[current_byte] = b;
current_byte += 1;
}
current_pixel += 1;
if current_pixel > num_pixels {
return Err("too many pixels read".into());
}
}
}
}
Ok(())
}
fn write_rle_file(&mut self, file: &mut File) -> IoResult {
let max_chunk_len = 128;
let num_pixels = (self.width * self.height) as usize;
let mut current_pixel = 0;
let bpp = self.bytes_per_pixel() as usize;
while current_pixel < num_pixels {
let chunk_start = current_pixel * bpp;
let mut current_byte = current_pixel * bpp;
let mut run_length = 1;
let mut raw = true;
while (current_pixel + run_length) < num_pixels && run_length < max_chunk_len {
let mut succ_eq = true;
let mut t = 0;
while succ_eq && t < bpp {
succ_eq = self.data[current_byte + t] == self.data[current_byte + t + bpp];
t += 1;
}
current_byte += bpp;
if 1 == run_length {
raw = !succ_eq;
}
if raw && succ_eq {
run_length -= 1;
break;
}
if !(raw || succ_eq) {
break;
}
run_length += 1;
}
current_pixel += run_length;
let mut out = if raw {
[run_length as u8 - 1]
} else {
[run_length as u8 + 127]
};
file.write_all(&mut out)?;
let chunk_end = if raw { run_length * bpp } else { bpp };
file.write_all(&mut self.data[chunk_start..chunk_end])?;
}
Ok(())
} }
} }