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.
|
# 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"
|
||||||
|
|
|
@ -4,3 +4,5 @@ version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
justerror = "1.1.0"
|
||||||
|
thiserror = "2.0.16"
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
fn main() {
|
#[macro_use]
|
||||||
println!("Hello, world!");
|
extern crate justerror;
|
||||||
}
|
|
||||||
|
mod tga;
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
|
|
216
src/tga.rs
216
src/tga.rs
|
@ -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(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue