use binrw::BinResult;
use image_dds::{CreateDdsError, Surface, ddsfile::Dds, error::CreateImageError};
use xc3_lib::{
error::CreateMiblError,
mibl::Mibl,
msrd::streaming::ExtractedTexture,
mtxt::Mtxt,
mxmd::{PackedTexture, legacy::MxmdLegacy},
};
pub use xc3_lib::mibl::{ImageFormat, ViewDimension};
pub use xc3_lib::mxmd::TextureUsage;
use crate::{error::CreateImageTextureError, get_bytes};
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Debug)]
pub enum ExtractedTextures {
Switch(Vec<ExtractedTexture<Mibl, TextureUsage>>),
Pc(
#[cfg_attr(feature = "arbitrary", arbitrary(with = arbitrary_dds_textures))]
Vec<ExtractedTexture<Dds, TextureUsage>>,
),
}
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Debug, PartialEq, Clone)]
pub struct ImageTexture {
pub name: Option<String>,
pub usage: Option<TextureUsage>,
pub width: u32,
pub height: u32,
pub depth: u32,
pub view_dimension: ViewDimension, pub image_format: ImageFormat,
pub mipmap_count: u32,
pub image_data: Vec<u8>,
}
impl ImageTexture {
pub fn from_mibl(
mibl: &Mibl,
name: Option<String>,
usage: Option<TextureUsage>,
) -> Result<Self, CreateImageTextureError> {
Ok(Self {
name,
usage,
width: mibl.footer.width,
height: mibl.footer.height,
depth: mibl.footer.depth,
view_dimension: mibl.footer.view_dimension,
image_format: mibl.footer.image_format,
mipmap_count: mibl.footer.mipmap_count,
image_data: mibl.deswizzled_image_data()?,
})
}
pub fn from_mtxt(
mtxt: &Mtxt,
name: Option<String>,
usage: Option<xc3_lib::mxmd::legacy::TextureUsage>,
) -> Result<Self, CreateImageTextureError> {
Ok(Self {
name,
usage: usage.and_then(mtxt_usage),
width: mtxt.footer.width,
height: mtxt.footer.height,
depth: mtxt.footer.depth_or_array_layers,
view_dimension: ViewDimension::D2,
image_format: mtxt_image_format(mtxt.footer.surface_format),
mipmap_count: mtxt.footer.mipmap_count,
image_data: mtxt.deswizzled_image_data()?,
})
}
pub(crate) fn from_packed_texture(
texture: &PackedTexture,
) -> Result<Self, CreateImageTextureError> {
let mibl = Mibl::from_bytes(&texture.mibl_data)?;
Self::from_mibl(&mibl, Some(texture.name.clone()), Some(texture.usage))
}
pub fn to_image(&self) -> Result<image_dds::image::RgbaImage, CreateImageError> {
self.to_surface()
.decode_layers_mipmaps_rgba8(0..self.layers(), 0..1)?
.into_image()
}
pub fn layers(&self) -> u32 {
if self.view_dimension == ViewDimension::Cube {
6
} else {
1
}
}
pub fn to_surface(&self) -> image_dds::Surface<&[u8]> {
Surface {
width: self.width,
height: self.height,
depth: self.depth,
layers: self.layers(),
mipmaps: self.mipmap_count,
image_format: self.image_format.into(),
data: &self.image_data,
}
}
pub fn to_dds(&self) -> Result<Dds, CreateDdsError> {
self.to_surface().to_dds()
}
pub fn from_surface<T: AsRef<[u8]>>(
surface: Surface<T>,
name: Option<String>,
usage: Option<TextureUsage>,
) -> Result<Self, CreateImageTextureError> {
Ok(Self {
name,
usage,
width: surface.width,
height: surface.height,
depth: surface.depth,
view_dimension: if surface.layers == 6 {
ViewDimension::Cube
} else if surface.depth > 1 {
ViewDimension::D3
} else {
ViewDimension::D2
},
image_format: surface.image_format.try_into()?,
mipmap_count: surface.mipmaps,
image_data: surface.data.as_ref().to_vec(),
})
}
pub fn from_dds(
dds: &Dds,
name: Option<String>,
usage: Option<TextureUsage>,
) -> Result<Self, CreateImageTextureError> {
Self::from_surface(Surface::from_dds(dds)?, name, usage)
}
pub fn to_mibl(&self) -> Result<Mibl, CreateMiblError> {
Mibl::from_surface(self.to_surface())
}
pub(crate) fn to_extracted_texture(&self) -> ExtractedTexture<Mibl, TextureUsage> {
let mibl = self.to_mibl().unwrap();
ExtractedTexture::from_mibl(
&mibl,
self.name.clone().unwrap_or_default(),
self.usage.unwrap_or(TextureUsage::Col),
)
}
}
fn mtxt_image_format(image_format: xc3_lib::mtxt::SurfaceFormat) -> ImageFormat {
match image_format {
xc3_lib::mtxt::SurfaceFormat::R8G8B8A8Unorm => ImageFormat::R8G8B8A8Unorm,
xc3_lib::mtxt::SurfaceFormat::BC1Unorm => ImageFormat::BC1Unorm,
xc3_lib::mtxt::SurfaceFormat::BC2Unorm => ImageFormat::BC2Unorm,
xc3_lib::mtxt::SurfaceFormat::BC3Unorm => ImageFormat::BC3Unorm,
xc3_lib::mtxt::SurfaceFormat::BC4Unorm => ImageFormat::BC4Unorm,
xc3_lib::mtxt::SurfaceFormat::BC5Unorm => ImageFormat::BC5Unorm,
}
}
fn mtxt_usage(usage: xc3_lib::mxmd::legacy::TextureUsage) -> Option<TextureUsage> {
match usage {
xc3_lib::mxmd::legacy::TextureUsage::Nrm => Some(TextureUsage::Nrm),
xc3_lib::mxmd::legacy::TextureUsage::Unk32 => Some(TextureUsage::Col),
xc3_lib::mxmd::legacy::TextureUsage::Unk48 => Some(TextureUsage::Col),
xc3_lib::mxmd::legacy::TextureUsage::Col => Some(TextureUsage::Col),
xc3_lib::mxmd::legacy::TextureUsage::Unk96 => Some(TextureUsage::Col),
xc3_lib::mxmd::legacy::TextureUsage::Nrm2 => Some(TextureUsage::Nrm),
xc3_lib::mxmd::legacy::TextureUsage::Cube => None,
_ => None,
}
}
pub fn load_textures(
textures: &ExtractedTextures,
) -> Result<Vec<ImageTexture>, CreateImageTextureError> {
match textures {
ExtractedTextures::Switch(textures) => textures
.iter()
.map(|texture| {
ImageTexture::from_surface(
texture.surface_final()?,
Some(texture.name.clone()),
Some(texture.usage),
)
})
.collect(),
ExtractedTextures::Pc(textures) => textures
.iter()
.map(|texture| {
ImageTexture::from_dds(
texture.dds_final(),
Some(texture.name.clone()),
Some(texture.usage),
)
})
.collect(),
}
}
pub fn load_textures_legacy(
mxmd: &MxmdLegacy,
casmt: Option<Vec<u8>>,
) -> Result<(Vec<u16>, Vec<ImageTexture>), CreateImageTextureError> {
let mut image_textures: Vec<_> = mxmd
.packed_textures
.as_ref()
.map(|textures| {
textures
.textures
.iter()
.map(|t| {
let mtxt = Mtxt::from_bytes(&t.mtxt_data)?;
ImageTexture::from_mtxt(&mtxt, Some(t.name.clone()), Some(t.usage))
})
.collect()
})
.transpose()?
.unwrap_or_default();
let mut low_texture_indices: Vec<_> = (0..image_textures.len() as u16).collect();
if let Some(streaming) = &mxmd.streaming
&& let Some(casmt) = casmt
{
let low_data =
get_bytes(&casmt, streaming.low_texture_data_offset, None).map_err(binrw::Error::Io)?;
let high_data =
get_bytes(&casmt, streaming.texture_data_offset, None).map_err(binrw::Error::Io)?;
let (indices, textures) =
streaming
.inner
.extract_textures(low_data, high_data, |bytes| Mtxt::from_bytes(bytes))?;
image_textures = textures
.into_iter()
.map(|t| ImageTexture::from_mtxt(t.mtxt_final(), Some(t.name.clone()), Some(t.usage)))
.collect::<Result<Vec<_>, _>>()?;
low_texture_indices = indices;
}
Ok((low_texture_indices, image_textures))
}
pub fn load_packed_textures(
packed_textures: Option<&xc3_lib::mxmd::PackedTextures>,
) -> BinResult<Vec<ExtractedTexture<Mibl, xc3_lib::mxmd::TextureUsage>>> {
match packed_textures {
Some(textures) => textures
.textures
.iter()
.map(|t| {
let mibl = Mibl::from_bytes(&t.mibl_data)?;
Ok(ExtractedTexture::from_mibl(&mibl, t.name.clone(), t.usage))
})
.collect(),
None => Ok(Vec::new()),
}
}
#[cfg(feature = "arbitrary")]
fn arbitrary_dds_textures(
_u: &mut arbitrary::Unstructured,
) -> arbitrary::Result<Vec<ExtractedTexture<Dds, TextureUsage>>> {
Ok(Vec::new())
}