From 8bea7b0838ca83af9abf2ec64b679b98c92d472e Mon Sep 17 00:00:00 2001 From: metamuffin Date: Wed, 12 Mar 2025 12:41:39 +0100 Subject: many more texture formats --- Cargo.lock | 9 ++++++ Cargo.toml | 1 + src/bin/textures.rs | 15 +++++----- src/classes/texture2d.rs | 77 +++++++++++++++++++++++++++++++++++++++--------- src/lib.rs | 2 +- 5 files changed, 81 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 101ab5d..1916329 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1032,6 +1032,14 @@ dependencies = [ "libm", ] +[[package]] +name = "texture2ddecoder" +version = "0.1.1" +source = "git+https://github.com/UniversalGameExtraction/texture2ddecoder#d2b4653fda298f1da39917da86bc509b17879808" +dependencies = [ + "paste", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -1129,6 +1137,7 @@ dependencies = [ "serde_json", "serde_yml", "texpresso", + "texture2ddecoder", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index a621dd5..e6aa46f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,3 +16,4 @@ serde_yml = "0.0.12" glam = { version = "0.30.0", features = ["serde"] } texpresso = "2.0.1" image = "0.25.5" +texture2ddecoder = { git = "https://github.com/UniversalGameExtraction/texture2ddecoder" } diff --git a/src/bin/textures.rs b/src/bin/textures.rs index 83ffb07..4a35de7 100644 --- a/src/bin/textures.rs +++ b/src/bin/textures.rs @@ -31,11 +31,10 @@ fn main() -> anyhow::Result<()> { .nodes() .iter() .find(|n| n.name.ends_with(".resS")) - .ok_or(anyhow!("no resS file found"))? - .to_owned(); + .cloned(); let mut cab = fs.read(&cabfile)?; - let mut ress = fs2.read(&ressfile)?; + let mut ress = ressfile.map(|p| fs2.read(&p)).transpose()?; let file = read_serialized_file(&mut cab)?; for ob in file.objects { cab.seek(SeekFrom::Start(ob.data_offset))?; @@ -51,6 +50,7 @@ fn main() -> anyhow::Result<()> { let value = read_value(typetree, file.endianness, &mut cab)?; let mut texture = Texture2D::from_value(value).unwrap(); if texture.image_data.len() == 0 { + let ress = ress.as_mut().unwrap(); ress.seek(SeekFrom::Start(texture.stream_data.offset as u64))?; ress.by_ref() .take(texture.stream_data.size as u64) @@ -60,12 +60,11 @@ fn main() -> anyhow::Result<()> { "/tmp/tex/{}_{i}.png", texture.name.replace("/", "-").replace(".", "-") ); - println!( - "{path} {}x{} {:?}", - texture.width, texture.height, texture.format - ); match texture.to_image() { - Ok(im) => im.save(path).unwrap(), + Ok(im) => { + im.save(&path).unwrap(); + println!("{path}"); + } Err(e) => warn!("{e}"), } i += 1; diff --git a/src/classes/texture2d.rs b/src/classes/texture2d.rs index 2bc1ade..e3f3225 100644 --- a/src/classes/texture2d.rs +++ b/src/classes/texture2d.rs @@ -1,7 +1,8 @@ use super::{FromValue, streaminginfo::StreamingInfo}; use crate::object::Value; use anyhow::{Result, bail}; -use image::{DynamicImage, Rgb, Rgba}; +use image::{DynamicImage, ImageBuffer, Luma, Rgb, Rgba}; +use log::info; use serde::Serialize; use std::mem::transmute; @@ -11,6 +12,7 @@ pub struct Texture2D { pub height: i32, pub mip_count: i32, pub name: String, + #[serde(skip)] pub image_data: Vec, pub format: TextureFormat, pub texture_dimension: i32, @@ -45,23 +47,70 @@ impl Texture2D { let w = self.width as usize; let h = self.height as usize; use TextureFormat::*; + let u32_rgba_buf_to_image = |buf: Vec| { + let buf = buf.into_iter().flat_map(u32::to_be_bytes).collect(); + let im = ImageBuffer::, Vec<_>>::from_raw(w as u32, h as u32, buf).unwrap(); + im.into() + }; match self.format { - DXT1 | DXT3 | DXT5 => { - use texpresso::Format::*; - let mut buf = vec![0u8; w * h * 4]; + Alpha8 => { + let buf = self.image_data.clone(); + let im = + ImageBuffer::, Vec<_>>::from_raw(w as u32, h as u32, buf).unwrap(); + Ok(im.into()) + } + RGBAHalf => { + let buf = self + .image_data + .clone() + .into_iter() + .array_chunks::<2>() + .map(|x| u16::from_be_bytes(x)) + .map(|x| f16::from_bits(x) as f32) + .collect::>(); + Ok( + ImageBuffer::, Vec<_>>::from_raw(w as u32, h as u32, buf) + .unwrap() + .into(), + ) + } + DXT1 | DXT3 | DXT5 | BC4 | BC5 => { + use texpresso::Format as F; let format = match self.format { - DXT1 => Bc1, - DXT3 => Bc2, - DXT5 => Bc3, + DXT1 => F::Bc1, + DXT3 => F::Bc2, + DXT5 => F::Bc3, + BC4 => F::Bc4, + BC5 => F::Bc5, _ => unreachable!(), }; + info!( + "decompressing {w}x{h} {:?} ({:?}) texture", + format, self.format + ); + let mut buf = vec![0u8; w * h * 4]; format.decompress(&self.image_data, w, h, &mut buf); - let im = image::ImageBuffer::, Vec<_>>::from_raw(w as u32, h as u32, buf) - .unwrap(); + let im = + ImageBuffer::, Vec<_>>::from_raw(w as u32, h as u32, buf).unwrap(); Ok(im.into()) } + DXT1Crunched | DXT5Crunched => { + let mut buf = vec![0u32; w * h]; + texture2ddecoder::decode_unity_crunch(&self.image_data, w, h, &mut buf).unwrap(); + Ok(u32_rgba_buf_to_image(buf)) + } + BC7 => { + let mut buf = vec![0u32; w * h]; + texture2ddecoder::decode_bc7(&self.image_data, w, h, &mut buf).unwrap(); + Ok(u32_rgba_buf_to_image(buf)) + } + BC6H => { + let mut buf = vec![0u32; w * h]; + texture2ddecoder::decode_bc6(&self.image_data, w, h, &mut buf, false).unwrap(); + Ok(u32_rgba_buf_to_image(buf)) + } RGB24 => { - let im = image::ImageBuffer::, Vec<_>>::from_raw( + let im = ImageBuffer::, Vec<_>>::from_raw( w as u32, h as u32, self.image_data.clone(), @@ -78,14 +127,14 @@ impl Texture2D { pix[2] = pix[3]; pix[3] = a; } - let im = image::ImageBuffer::, Vec<_>>::from_raw(w as u32, h as u32, buf) - .unwrap(); + let im = + ImageBuffer::, Vec<_>>::from_raw(w as u32, h as u32, buf).unwrap(); Ok(im.into()) } RGBA32 => { let buf = self.image_data.clone(); - let im = image::ImageBuffer::, Vec<_>>::from_raw(w as u32, h as u32, buf) - .unwrap(); + let im = + ImageBuffer::, Vec<_>>::from_raw(w as u32, h as u32, buf).unwrap(); Ok(im.into()) } x => bail!("texture format {x:?} not supported"), diff --git a/src/lib.rs b/src/lib.rs index 638c3cb..f01eaae 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -#![feature(array_chunks)] +#![feature(array_chunks, f16, iter_array_chunks)] pub mod classes; pub mod common_strings; pub mod helper; -- cgit v1.2.3-70-g09d2