use crate::atlas::AtlasData;
use crate::box_reflection::{BoxReflection, BoxReflectionError, CubemapLayout};
use crate::convert::create_dds;
use crate::convert::TextureConversionError::DirectXTexError;
use crate::enums::RenderFormat;
use crate::mipblock::MipblockData;
use crate::pack::{TextureMapBuilder, TextureMapParameters, TexturePackerError};
use crate::texture_map::TextureMap;
use crate::GlacierGame;
use binrw::BinRead;
use directxtex::{
HResultError, ScratchImage, CP_FLAGS, DDS_FLAGS, DXGI_FORMAT, DXGI_FORMAT_R16G16B16A16_FLOAT,
DXGI_FORMAT_R32G32B32A32_FLOAT, TEX_FILTER_FLAGS, TEX_THRESHOLD_DEFAULT,
};
use image::error::{EncodingError, ImageFormatHint};
use image::{ColorType, ExtendedColorType, ImageDecoder, ImageEncoder, ImageError, ImageResult};
use std::io::{BufRead, Seek, Write};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum TextureMapEncodeError {
#[error("DXGI conversion failed for color type {0:?}")]
DxgiConversion(ExtendedColorType),
#[error("Failed DirectXTex operation {0}")]
DirectXTexError(#[from] HResultError),
#[error("Failed to pack texture")]
Packer(#[from] TexturePackerError),
#[error("IO error {0}")]
IOError(#[from] std::io::Error),
}
impl From<TextureMapEncodeError> for ImageError {
fn from(e: TextureMapEncodeError) -> Self {
ImageError::Encoding(EncodingError::new(
ImageFormatHint::Name("TextureMap".to_owned()),
e.to_string(),
))
}
}
pub struct TextureMapEncoder<TW: Write, DW: Write> {
text_writer: TW,
texd_writer: Option<DW>,
glacier_game: GlacierGame,
texture_parameters: Option<TextureMapParameters>,
atlas_data: Option<AtlasData>,
}
impl<TW: Write, DW: Write> TextureMapEncoder<TW, DW> {
pub fn new(
text_writer: TW,
texd_writer: Option<DW>,
glacier_game: GlacierGame,
texture_parameters: Option<TextureMapParameters>,
atlas_data: Option<AtlasData>,
) -> TextureMapEncoder<TW, DW> {
TextureMapEncoder {
text_writer,
texd_writer,
glacier_game,
texture_parameters,
atlas_data,
}
}
}
impl<TW: Write, DW: Write> ImageEncoder for TextureMapEncoder<TW, DW> {
fn write_image(
self,
buf: &[u8],
width: u32,
height: u32,
color_type: ExtendedColorType,
) -> ImageResult<()> {
let scratch_image =
helpers::dynamic_image_to_scratch_image(buf, width, height, color_type)?;
let mut builder = TextureMapBuilder::from_scratch_image(scratch_image)
.map_err(TextureMapEncodeError::Packer)?;
if let Some(params) = self.texture_parameters {
builder = builder.with_params(params);
}
if let Some(atlas_data) = self.atlas_data {
builder = builder.with_atlas(atlas_data);
}
let text = builder
.build(self.glacier_game)
.map_err(TextureMapEncodeError::Packer)?;
let text_data = text.pack_to_vec().map_err(TextureMapEncodeError::Packer)?;
let mut text_writer = self.text_writer;
text_writer.write_all(&text_data)?;
if let Some(mut texd_writer) = self.texd_writer {
if let Some(texd) = text.mipblock1() {
let texd_data = texd
.pack_to_vec(self.glacier_game)
.map_err(TextureMapEncodeError::Packer)?;
texd_writer.write_all(&texd_data)?;
}
}
Ok(())
}
}
#[derive(Debug, Error)]
pub enum BoxReflectionEncodeError {
#[error("DXGI conversion failed for color type {0:?}")]
DxgiConversion(ExtendedColorType),
#[error("Failed DirectXTex operation {0}")]
DirectXTexError(#[from] HResultError),
#[error("Failed to pack box reflection")]
BoxReflection(#[from] BoxReflectionError),
#[error("IO error {0}")]
IOError(#[from] std::io::Error),
#[error("Binrw error {0}")]
Binrw(#[from] binrw::Error),
}
impl From<BoxReflectionEncodeError> for ImageError {
fn from(e: BoxReflectionEncodeError) -> Self {
ImageError::Encoding(EncodingError::new(
ImageFormatHint::Name("BoxReflection".to_owned()),
e.to_string(),
))
}
}
pub struct TextureMapDecoder {
texture: TextureMap,
}
impl TextureMapDecoder {
pub fn new<TR: BufRead + Seek, DR: BufRead + Seek>(
mut text_reader: TR,
texd_reader: Option<DR>,
glacier_game: GlacierGame,
) -> Self {
let mut texture = TextureMap::read_le_args(&mut text_reader, (glacier_game,)).unwrap();
if let Some(mut texd_reader) = texd_reader {
let mut buf = Vec::new();
texd_reader.read_to_end(&mut buf).unwrap();
let mip_data = MipblockData::from_memory(&buf, glacier_game).unwrap();
texture.set_mipblock1(mip_data);
}
Self { texture }
}
pub fn from_texture_map(texture: TextureMap) -> Self {
Self { texture }
}
}
impl ImageDecoder for TextureMapDecoder {
fn dimensions(&self) -> (u32, u32) {
(self.texture.width() as u32, self.texture.height() as u32)
}
fn color_type(&self) -> ColorType {
match self.texture.format() {
RenderFormat::R32G32B32A32 => ColorType::Rgba32F,
RenderFormat::R16G16B16A16 => ColorType::Rgba32F,
RenderFormat::R8G8B8A8 => ColorType::Rgba8,
RenderFormat::R32 => ColorType::Rgb32F,
RenderFormat::R8G8 => ColorType::La8,
RenderFormat::A8 => ColorType::L8,
RenderFormat::BC1 => ColorType::Rgba8,
RenderFormat::BC2 => ColorType::Rgba8,
RenderFormat::BC3 => ColorType::Rgba8,
RenderFormat::BC4 => ColorType::L8,
RenderFormat::BC5 => ColorType::La8,
RenderFormat::BC7 => ColorType::Rgba8,
}
}
fn read_image(self, buf: &mut [u8]) -> ImageResult<()>
where
Self: Sized,
{
let dds = create_dds(&self.texture).map_err(|e| {
ImageError::IoError(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("Failed to read the image: {}", e),
))
})?;
let mut scratch_image = ScratchImage::load_dds(
dds.as_slice(),
DDS_FLAGS::DDS_FLAGS_FORCE_DX10_EXT,
None,
None,
)
.map_err(DirectXTexError)
.unwrap();
scratch_image = crate::convert::decompress_dds(&self.texture, scratch_image).unwrap();
if scratch_image.metadata().format == DXGI_FORMAT_R16G16B16A16_FLOAT {
scratch_image = scratch_image
.convert(
DXGI_FORMAT_R32G32B32A32_FLOAT,
TEX_FILTER_FLAGS::TEX_FILTER_DEFAULT,
TEX_THRESHOLD_DEFAULT,
)
.map_err(DirectXTexError)
.unwrap();
}
let blob = scratch_image
.image(0, 0, 0)
.unwrap()
.save_dds(DDS_FLAGS::DDS_FLAGS_FORCE_DX10_EXT)
.unwrap();
let data = blob.buffer();
if data.len() < buf.len() {
return Err(ImageError::IoError(std::io::Error::new(
std::io::ErrorKind::UnexpectedEof,
format!(
"DDS buffer too small: data has {} bytes, buf needs {}",
data.len(),
buf.len()
),
)));
}
buf.copy_from_slice(&data[data.len() - buf.len()..]);
Ok(())
}
fn read_image_boxed(self: Box<Self>, buf: &mut [u8]) -> ImageResult<()> {
(*self).read_image(buf)
}
}
pub struct BoxReflectionDecoder {
texture: BoxReflection,
layout: CubemapLayout,
}
impl BoxReflectionDecoder {
pub fn from_box_reflection(texture: BoxReflection, layout: CubemapLayout) -> Self {
Self { texture, layout }
}
}
impl ImageDecoder for BoxReflectionDecoder {
fn dimensions(&self) -> (u32, u32) {
let (cols, rows) = self.layout.tile_counts();
(
(BoxReflection::tile_width() * cols) as u32,
(BoxReflection::tile_height() * rows) as u32,
)
}
fn color_type(&self) -> ColorType {
ColorType::Rgba32F
}
fn read_image(self, buf: &mut [u8]) -> ImageResult<()>
where
Self: Sized,
{
let dds = self.texture.create_dds(Some(self.layout)).unwrap();
let mut scratch_image = ScratchImage::load_dds(
dds.as_slice(),
DDS_FLAGS::DDS_FLAGS_FORCE_DX10_EXT,
None,
None,
)
.map_err(DirectXTexError)
.unwrap();
scratch_image = scratch_image
.convert(
DXGI_FORMAT_R32G32B32A32_FLOAT,
TEX_FILTER_FLAGS::TEX_FILTER_DEFAULT,
TEX_THRESHOLD_DEFAULT,
)
.unwrap();
buf.copy_from_slice(scratch_image.pixels());
Ok(())
}
fn read_image_boxed(self: Box<Self>, buf: &mut [u8]) -> ImageResult<()> {
(*self).read_image(buf)
}
}
pub(crate) mod helpers {
use super::*;
pub(super) fn color_type_to_dxgi(color_type: ExtendedColorType) -> Option<DXGI_FORMAT> {
match color_type {
ExtendedColorType::A8 => Some(DXGI_FORMAT::DXGI_FORMAT_A8_UNORM),
ExtendedColorType::L8 => Some(DXGI_FORMAT::DXGI_FORMAT_R8_UNORM),
ExtendedColorType::La8 => Some(DXGI_FORMAT::DXGI_FORMAT_R8G8_UNORM),
ExtendedColorType::Rgb8 => Some(DXGI_FORMAT::DXGI_FORMAT_R8G8B8A8_UNORM), ExtendedColorType::Rgba8 => Some(DXGI_FORMAT::DXGI_FORMAT_R8G8B8A8_UNORM),
ExtendedColorType::L16 => Some(DXGI_FORMAT::DXGI_FORMAT_R16_UNORM),
ExtendedColorType::La16 => Some(DXGI_FORMAT::DXGI_FORMAT_R16G16_UNORM),
ExtendedColorType::Rgb16 => Some(DXGI_FORMAT::DXGI_FORMAT_R16G16B16A16_UNORM), ExtendedColorType::Rgba16 => Some(DXGI_FORMAT::DXGI_FORMAT_R16G16B16A16_UNORM),
ExtendedColorType::Bgr8 => Some(DXGI_FORMAT::DXGI_FORMAT_B8G8R8X8_UNORM), ExtendedColorType::Bgra8 => Some(DXGI_FORMAT::DXGI_FORMAT_B8G8R8A8_UNORM),
ExtendedColorType::Rgb32F => Some(DXGI_FORMAT::DXGI_FORMAT_R32G32B32_FLOAT),
ExtendedColorType::Rgba32F => Some(DXGI_FORMAT::DXGI_FORMAT_R32G32B32A32_FLOAT),
_ => None,
}
}
pub(super) fn rgb8_to_rgba8(rgb: &[u8]) -> Vec<u8> {
let mut rgba = Vec::with_capacity(rgb.len() / 3 * 4);
for chunk in rgb.chunks(3) {
rgba.push(chunk[0]);
rgba.push(chunk[1]);
rgba.push(chunk[2]);
rgba.push(0xFF);
}
rgba
}
pub(super) fn rgb16_to_rgba16(rgb: &[u8]) -> Vec<u8> {
assert_eq!(rgb.len() % 6, 0, "Input length must be divisible by 6.");
let mut rgba = Vec::with_capacity(rgb.len() / 3 * 4);
for chunk in rgb.chunks(6) {
rgba.extend_from_slice(&chunk[0..2]);
rgba.extend_from_slice(&chunk[2..4]);
rgba.extend_from_slice(&chunk[4..6]);
rgba.extend_from_slice(&0xFFFFu16.to_le_bytes());
}
rgba
}
pub(super) fn rgb32f_to_rgba32f(rgb: &[u8]) -> Vec<u8> {
assert_eq!(rgb.len() % 12, 0, "Input length must be divisible by 12.");
let mut rgba = Vec::with_capacity(rgb.len() / 3 * 4);
for chunk in rgb.chunks(12) {
rgba.extend_from_slice(&chunk[0..4]);
rgba.extend_from_slice(&chunk[4..8]);
rgba.extend_from_slice(&chunk[8..12]);
rgba.extend_from_slice(&1f32.to_le_bytes());
}
rgba
}
pub(crate) fn dynamic_image_to_scratch_image(
buf: &[u8],
width: u32,
height: u32,
color_type: ExtendedColorType,
) -> Result<ScratchImage, TextureMapEncodeError> {
let dxgi_format = helpers::color_type_to_dxgi(color_type)
.ok_or(TextureMapEncodeError::DxgiConversion(color_type))?;
let slice_pitch = dxgi_format
.compute_pitch(width as usize, height as usize, CP_FLAGS::CP_FLAGS_NONE)
.map_err(TextureMapEncodeError::DirectXTexError)?;
let width = width as usize;
let height = height as usize;
let maybe_converted;
let pixels = match color_type {
ExtendedColorType::Rgb8 | ExtendedColorType::Bgr8 => {
maybe_converted = Some(helpers::rgb8_to_rgba8(buf));
maybe_converted.as_ref().unwrap().as_ptr() as *mut u8
}
ExtendedColorType::Rgb16 => {
maybe_converted = Some(helpers::rgb16_to_rgba16(buf));
maybe_converted.as_ref().unwrap().as_ptr() as *mut u8
}
ExtendedColorType::Rgb32F => {
maybe_converted = Some(helpers::rgb32f_to_rgba32f(buf));
maybe_converted.as_ref().unwrap().as_ptr() as *mut u8
}
_ => buf.as_ptr() as *mut u8,
};
let image = directxtex::Image {
width,
height,
format: dxgi_format,
row_pitch: slice_pitch.row,
slice_pitch: slice_pitch.slice,
pixels,
};
let mut scratch_image = ScratchImage::default();
scratch_image.initialize_from_image(&image, false, CP_FLAGS::CP_FLAGS_NONE)?;
Ok(scratch_image)
}
}