file io might be done, untested
This commit is contained in:
parent
7d190cec88
commit
5492f3d4eb
4 changed files with 289 additions and 19 deletions
81
Cargo.lock
generated
81
Cargo.lock
generated
|
@ -2,6 +2,87 @@
|
|||
# It is not intended for manual editing.
|
||||
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]]
|
||||
name = "tinyrender"
|
||||
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"
|
||||
|
|
|
@ -4,3 +4,5 @@ version = "0.1.0"
|
|||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
justerror = "1.1.0"
|
||||
thiserror = "2.0.16"
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
||||
#[macro_use]
|
||||
extern crate justerror;
|
||||
|
||||
mod tga;
|
||||
|
||||
fn main() {}
|
||||
|
|
216
src/tga.rs
216
src/tga.rs
|
@ -1,9 +1,11 @@
|
|||
use std::{
|
||||
fs::File,
|
||||
io::Read,
|
||||
io::{Read, Write},
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
|
||||
const HEADER_SIZE: usize = 18;
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
||||
#[repr(C, packed)]
|
||||
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 {
|
||||
pub fn new() -> Self {
|
||||
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)]
|
||||
struct TGAImage {
|
||||
pub format: Format,
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub width: u16,
|
||||
pub height: u16,
|
||||
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 {
|
||||
pub fn new(width: u32, height: u32, format: Format) -> Self {
|
||||
pub fn new(width: u16, height: u16, format: Format) -> Self {
|
||||
Self {
|
||||
format,
|
||||
width,
|
||||
|
@ -89,20 +137,20 @@ impl TGAImage {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn bytes_per_pixel(&self) -> u8 {
|
||||
self.format as u8
|
||||
pub fn from_file(file: &str) -> Result<Self, TGAError> {
|
||||
let mut img = TGAImage::default();
|
||||
img.read_file(file)?;
|
||||
Ok(img)
|
||||
}
|
||||
|
||||
pub fn read_file(&mut self, file: &str) -> IoResult {
|
||||
let mut file = std::fs::File::open(file)?;
|
||||
let header_size = size_of::<TGAHeader>();
|
||||
assert_eq!(header_size, 18);
|
||||
let mut bytes = [0u8; header_size];
|
||||
file.read_exact(&mut bytes)?;
|
||||
let header: TGAHeader = unsafe { std::mem::transmute(bytes) };
|
||||
let mut header_bytes = [0u8; HEADER_SIZE];
|
||||
file.read_exact(&mut header_bytes)?;
|
||||
let header: TGAHeader = unsafe { std::mem::transmute(header_bytes) };
|
||||
|
||||
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()?;
|
||||
|
||||
|
@ -123,10 +171,53 @@ impl TGAImage {
|
|||
return Err("invalid TGA file".into());
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_file(&self, file: &str) -> IoResult {
|
||||
todo!()
|
||||
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 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) {
|
||||
|
@ -145,7 +236,100 @@ impl TGAImage {
|
|||
todo!()
|
||||
}
|
||||
|
||||
pub fn bytes_per_pixel(&self) -> u8 {
|
||||
self.format as u8
|
||||
}
|
||||
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue