use super::streaminginfo::StreamingInfo; use crate::object::{Value, parser::FromValue}; use anyhow::{Result, anyhow, bail}; use image::{DynamicImage, ImageBuffer, Luma, Rgb, Rgba}; use log::info; use serde::Serialize; #[derive(Debug, Serialize)] pub struct Texture2D { pub width: i32, pub height: i32, pub mip_count: i32, pub name: String, #[serde(skip)] pub image_data: Vec, pub format: TextureFormat, pub texture_dimension: i32, pub stream_data: StreamingInfo, } impl FromValue for Texture2D { fn from_value(v: Value) -> Result { let mut fields = v.as_class("Texture2D")?; Ok(Texture2D { width: fields.field("m_Width")?, height: fields.field("m_Height")?, mip_count: fields.field("m_MipCount")?, texture_dimension: fields.field("m_TextureDimension")?, format: fields.field("m_TextureFormat")?, name: fields.field("m_Name")?, stream_data: fields.field("m_StreamData")?, image_data: fields.remove("image data").unwrap().as_typeless().unwrap(), }) } } impl Texture2D { pub fn to_image(&self) -> Result { 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 { 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 => 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 = 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 = ImageBuffer::, Vec<_>>::from_raw( w as u32, h as u32, self.image_data.clone(), ) .unwrap(); Ok(im.into()) } ARGB32 => { let mut buf = self.image_data.clone(); for pix in buf.array_chunks_mut::<4>() { let a = pix[0]; pix[0] = pix[1]; pix[1] = pix[2]; pix[2] = pix[3]; pix[3] = a; } 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 = ImageBuffer::, Vec<_>>::from_raw(w as u32, h as u32, buf).unwrap(); Ok(im.into()) } x => bail!("texture format {x:?} not supported"), } } } impl FromValue for TextureFormat { fn from_value(v: Value) -> Result { let x = v.as_i32().ok_or(anyhow!("expected i32 TextureFormat"))?; if x < 72 { Ok(unsafe { std::mem::transmute(x) }) } else { bail!("TextureFormat out of range") } } } #[allow(non_camel_case_types)] #[repr(i32)] #[derive(Debug, Serialize, PartialEq, Clone, Copy)] pub enum TextureFormat { Alpha8 = 1, ARGB4444, RGB24, RGBA32, ARGB32, ARGBFloat, RGB565, BGR24, R16, DXT1, // Bc1 DXT3, // Bc2 DXT5, // Bc3 RGBA4444, BGRA32, RHalf, RGHalf, RGBAHalf, RFloat, RGFloat, RGBAFloat, YUY2, RGB9e5Float, RGBFloat, BC6H, BC7, BC4, BC5, DXT1Crunched, DXT5Crunched, PVRTC_RGB2, PVRTC_RGBA2, PVRTC_RGB4, PVRTC_RGBA4, ETC_RGB4, ATC_RGB4, ATC_RGBA8, EAC_R = 41, EAC_R_SIGNED, EAC_RG, EAC_RG_SIGNED, ETC2_RGB, ETC2_RGBA1, ETC2_RGBA8, ASTC_RGB_4x4, ASTC_RGB_5x5, ASTC_RGB_6x6, ASTC_RGB_8x8, ASTC_RGB_10x10, ASTC_RGB_12x12, ASTC_RGBA_4x4, ASTC_RGBA_5x5, ASTC_RGBA_6x6, ASTC_RGBA_8x8, ASTC_RGBA_10x10, ASTC_RGBA_12x12, ETC_RGB4_3DS, ETC_RGBA8_3DS, RG16, R8, ETC_RGB4Crunched, ETC2_RGBA8Crunched, R16_Alt, ASTC_HDR_4x4, ASTC_HDR_5x5, ASTC_HDR_6x6, ASTC_HDR_8x8, ASTC_HDR_10x10, ASTC_HDR_12x12, RG32, RGB48, RGBA64, }