Skip to main content

glacier_texture/
image.rs

1use crate::atlas::AtlasData;
2use crate::box_reflection::{BoxReflection, BoxReflectionError, CubemapLayout};
3use crate::convert::create_dds;
4use crate::convert::TextureConversionError::DirectXTexError;
5use crate::enums::RenderFormat;
6use crate::mipblock::MipblockData;
7use crate::pack::{TextureMapBuilder, TextureMapParameters, TexturePackerError};
8use crate::texture_map::TextureMap;
9use crate::GlacierGame;
10use binrw::BinRead;
11use directxtex::{
12    HResultError, ScratchImage, CP_FLAGS, DDS_FLAGS, DXGI_FORMAT, DXGI_FORMAT_R16G16B16A16_FLOAT,
13    DXGI_FORMAT_R32G32B32A32_FLOAT, TEX_FILTER_FLAGS, TEX_THRESHOLD_DEFAULT,
14};
15use image::error::{EncodingError, ImageFormatHint};
16use image::{ColorType, ExtendedColorType, ImageDecoder, ImageEncoder, ImageError, ImageResult};
17use std::io::{BufRead, Seek, Write};
18use thiserror::Error;
19
20#[derive(Debug, Error)]
21pub enum TextureMapEncodeError {
22    #[error("DXGI conversion failed for color type {0:?}")]
23    DxgiConversion(ExtendedColorType),
24    #[error("Failed DirectXTex operation {0}")]
25    DirectXTexError(#[from] HResultError),
26    #[error("Failed to pack texture")]
27    Packer(#[from] TexturePackerError),
28    #[error("IO error {0}")]
29    IOError(#[from] std::io::Error),
30}
31
32impl From<TextureMapEncodeError> for ImageError {
33    fn from(e: TextureMapEncodeError) -> Self {
34        ImageError::Encoding(EncodingError::new(
35            ImageFormatHint::Name("TextureMap".to_owned()),
36            e.to_string(),
37        ))
38    }
39}
40
41pub struct TextureMapEncoder<TW: Write, DW: Write> {
42    text_writer: TW,
43    texd_writer: Option<DW>,
44    glacier_game: GlacierGame,
45    texture_parameters: Option<TextureMapParameters>,
46    atlas_data: Option<AtlasData>,
47}
48
49impl<TW: Write, DW: Write> TextureMapEncoder<TW, DW> {
50    pub fn new(
51        text_writer: TW,
52        texd_writer: Option<DW>,
53        glacier_game: GlacierGame,
54        texture_parameters: Option<TextureMapParameters>,
55        atlas_data: Option<AtlasData>,
56    ) -> TextureMapEncoder<TW, DW> {
57        TextureMapEncoder {
58            text_writer,
59            texd_writer,
60            glacier_game,
61            texture_parameters,
62            atlas_data,
63        }
64    }
65}
66
67impl<TW: Write, DW: Write> ImageEncoder for TextureMapEncoder<TW, DW> {
68    fn write_image(
69        self,
70        buf: &[u8],
71        width: u32,
72        height: u32,
73        color_type: ExtendedColorType,
74    ) -> ImageResult<()> {
75        let scratch_image =
76            helpers::dynamic_image_to_scratch_image(buf, width, height, color_type)?;
77        let mut builder = TextureMapBuilder::from_scratch_image(scratch_image)
78            .map_err(TextureMapEncodeError::Packer)?;
79
80        if let Some(params) = self.texture_parameters {
81            builder = builder.with_params(params);
82        }
83
84        if let Some(atlas_data) = self.atlas_data {
85            builder = builder.with_atlas(atlas_data);
86        }
87
88        let text = builder
89            .build(self.glacier_game)
90            .map_err(TextureMapEncodeError::Packer)?;
91        let text_data = text.pack_to_vec().map_err(TextureMapEncodeError::Packer)?;
92
93        let mut text_writer = self.text_writer;
94        text_writer.write_all(&text_data)?;
95
96        if let Some(mut texd_writer) = self.texd_writer {
97            if let Some(texd) = text.mipblock1() {
98                let texd_data = texd
99                    .pack_to_vec(self.glacier_game)
100                    .map_err(TextureMapEncodeError::Packer)?;
101                texd_writer.write_all(&texd_data)?;
102            }
103        }
104        Ok(())
105    }
106}
107
108#[derive(Debug, Error)]
109pub enum BoxReflectionEncodeError {
110    #[error("DXGI conversion failed for color type {0:?}")]
111    DxgiConversion(ExtendedColorType),
112    #[error("Failed DirectXTex operation {0}")]
113    DirectXTexError(#[from] HResultError),
114    #[error("Failed to pack box reflection")]
115    BoxReflection(#[from] BoxReflectionError),
116    #[error("IO error {0}")]
117    IOError(#[from] std::io::Error),
118    #[error("Binrw error {0}")]
119    Binrw(#[from] binrw::Error),
120}
121
122impl From<BoxReflectionEncodeError> for ImageError {
123    fn from(e: BoxReflectionEncodeError) -> Self {
124        ImageError::Encoding(EncodingError::new(
125            ImageFormatHint::Name("BoxReflection".to_owned()),
126            e.to_string(),
127        ))
128    }
129}
130
131pub struct TextureMapDecoder {
132    texture: TextureMap,
133}
134
135impl TextureMapDecoder {
136    pub fn new<TR: BufRead + Seek, DR: BufRead + Seek>(
137        mut text_reader: TR,
138        texd_reader: Option<DR>,
139        glacier_game: GlacierGame,
140    ) -> Self {
141        let mut texture = TextureMap::read_le_args(&mut text_reader, (glacier_game,)).unwrap();
142        if let Some(mut texd_reader) = texd_reader {
143            let mut buf = Vec::new();
144            texd_reader.read_to_end(&mut buf).unwrap();
145            let mip_data = MipblockData::from_memory(&buf, glacier_game).unwrap();
146            texture.set_mipblock1(mip_data);
147        }
148        Self { texture }
149    }
150
151    pub fn from_texture_map(texture: TextureMap) -> Self {
152        Self { texture }
153    }
154}
155
156impl ImageDecoder for TextureMapDecoder {
157    fn dimensions(&self) -> (u32, u32) {
158        (self.texture.width() as u32, self.texture.height() as u32)
159    }
160
161    fn color_type(&self) -> ColorType {
162        match self.texture.format() {
163            RenderFormat::R32G32B32A32 => ColorType::Rgba32F,
164            RenderFormat::R16G16B16A16 => ColorType::Rgba32F,
165            RenderFormat::R8G8B8A8 => ColorType::Rgba8,
166            RenderFormat::R32 => ColorType::Rgb32F,
167            RenderFormat::R8G8 => ColorType::La8,
168            RenderFormat::A8 => ColorType::L8,
169            RenderFormat::BC1 => ColorType::Rgba8,
170            RenderFormat::BC2 => ColorType::Rgba8,
171            RenderFormat::BC3 => ColorType::Rgba8,
172            RenderFormat::BC4 => ColorType::L8,
173            RenderFormat::BC5 => ColorType::La8,
174            RenderFormat::BC6 => ColorType::Rgba32F,
175            RenderFormat::BC7 => ColorType::Rgba8,
176        }
177    }
178
179    fn read_image(self, buf: &mut [u8]) -> ImageResult<()>
180    where
181        Self: Sized,
182    {
183        let dds = create_dds(&self.texture).map_err(|e| {
184            ImageError::IoError(std::io::Error::new(
185                std::io::ErrorKind::InvalidData,
186                format!("Failed to read the image: {}", e),
187            ))
188        })?;
189        let mut scratch_image = ScratchImage::load_dds(
190            dds.as_slice(),
191            DDS_FLAGS::DDS_FLAGS_FORCE_DX10_EXT,
192            None,
193            None,
194        )
195        .map_err(DirectXTexError)
196        .unwrap();
197
198        scratch_image = crate::convert::decompress_dds(&self.texture, scratch_image).unwrap();
199
200        if scratch_image.metadata().format == DXGI_FORMAT_R16G16B16A16_FLOAT {
201            scratch_image = scratch_image
202                .convert(
203                    DXGI_FORMAT_R32G32B32A32_FLOAT,
204                    TEX_FILTER_FLAGS::TEX_FILTER_DEFAULT,
205                    TEX_THRESHOLD_DEFAULT,
206                )
207                .map_err(DirectXTexError)
208                .unwrap();
209        }
210
211        let blob = scratch_image
212            .image(0, 0, 0)
213            .unwrap()
214            .save_dds(DDS_FLAGS::DDS_FLAGS_FORCE_DX10_EXT)
215            .unwrap();
216
217        let data = blob.buffer();
218
219        if data.len() < buf.len() {
220            return Err(ImageError::IoError(std::io::Error::new(
221                std::io::ErrorKind::UnexpectedEof,
222                format!(
223                    "DDS buffer too small: data has {} bytes, buf needs {}",
224                    data.len(),
225                    buf.len()
226                ),
227            )));
228        }
229
230        buf.copy_from_slice(&data[data.len() - buf.len()..]);
231
232        Ok(())
233    }
234
235    fn read_image_boxed(self: Box<Self>, buf: &mut [u8]) -> ImageResult<()> {
236        (*self).read_image(buf)
237    }
238}
239
240pub struct BoxReflectionDecoder {
241    texture: BoxReflection,
242    layout: CubemapLayout,
243}
244
245impl BoxReflectionDecoder {
246    pub fn from_box_reflection(texture: BoxReflection, layout: CubemapLayout) -> Self {
247        Self { texture, layout }
248    }
249}
250
251impl ImageDecoder for BoxReflectionDecoder {
252    fn dimensions(&self) -> (u32, u32) {
253        let (cols, rows) = self.layout.tile_counts();
254        (
255            (BoxReflection::tile_width() * cols) as u32,
256            (BoxReflection::tile_height() * rows) as u32,
257        )
258    }
259
260    fn color_type(&self) -> ColorType {
261        ColorType::Rgba32F
262    }
263
264    fn read_image(self, buf: &mut [u8]) -> ImageResult<()>
265    where
266        Self: Sized,
267    {
268        let dds = self.texture.create_dds(Some(self.layout)).unwrap();
269        let mut scratch_image = ScratchImage::load_dds(
270            dds.as_slice(),
271            DDS_FLAGS::DDS_FLAGS_FORCE_DX10_EXT,
272            None,
273            None,
274        )
275        .map_err(DirectXTexError)
276        .unwrap();
277
278        scratch_image = scratch_image
279            .convert(
280                DXGI_FORMAT_R32G32B32A32_FLOAT,
281                TEX_FILTER_FLAGS::TEX_FILTER_DEFAULT,
282                TEX_THRESHOLD_DEFAULT,
283            )
284            .unwrap();
285
286        buf.copy_from_slice(scratch_image.pixels());
287
288        Ok(())
289    }
290
291    fn read_image_boxed(self: Box<Self>, buf: &mut [u8]) -> ImageResult<()> {
292        (*self).read_image(buf)
293    }
294}
295
296pub(crate) mod helpers {
297    use super::*;
298    pub(super) fn color_type_to_dxgi(color_type: ExtendedColorType) -> Option<DXGI_FORMAT> {
299        match color_type {
300            ExtendedColorType::A8 => Some(DXGI_FORMAT::DXGI_FORMAT_A8_UNORM),
301            ExtendedColorType::L8 => Some(DXGI_FORMAT::DXGI_FORMAT_R8_UNORM),
302            ExtendedColorType::La8 => Some(DXGI_FORMAT::DXGI_FORMAT_R8G8_UNORM),
303            ExtendedColorType::Rgb8 => Some(DXGI_FORMAT::DXGI_FORMAT_R8G8B8A8_UNORM), // Needs additional alpha channel
304            ExtendedColorType::Rgba8 => Some(DXGI_FORMAT::DXGI_FORMAT_R8G8B8A8_UNORM),
305            ExtendedColorType::L16 => Some(DXGI_FORMAT::DXGI_FORMAT_R16_UNORM),
306            ExtendedColorType::La16 => Some(DXGI_FORMAT::DXGI_FORMAT_R16G16_UNORM),
307            ExtendedColorType::Rgb16 => Some(DXGI_FORMAT::DXGI_FORMAT_R16G16B16A16_UNORM), // Needs additional alpha channel
308            ExtendedColorType::Rgba16 => Some(DXGI_FORMAT::DXGI_FORMAT_R16G16B16A16_UNORM),
309            ExtendedColorType::Bgr8 => Some(DXGI_FORMAT::DXGI_FORMAT_B8G8R8X8_UNORM), // Needs additional alpha channel
310            ExtendedColorType::Bgra8 => Some(DXGI_FORMAT::DXGI_FORMAT_B8G8R8A8_UNORM),
311            ExtendedColorType::Rgb32F => Some(DXGI_FORMAT::DXGI_FORMAT_R32G32B32_FLOAT),
312            ExtendedColorType::Rgba32F => Some(DXGI_FORMAT::DXGI_FORMAT_R32G32B32A32_FLOAT),
313            _ => None,
314        }
315    }
316
317    pub(super) fn rgb8_to_rgba8(rgb: &[u8]) -> Vec<u8> {
318        let mut rgba = Vec::with_capacity(rgb.len() / 3 * 4);
319        for chunk in rgb.chunks(3) {
320            rgba.push(chunk[0]);
321            rgba.push(chunk[1]);
322            rgba.push(chunk[2]);
323            rgba.push(0xFF);
324        }
325        rgba
326    }
327
328    pub(super) fn rgb16_to_rgba16(rgb: &[u8]) -> Vec<u8> {
329        assert_eq!(rgb.len() % 6, 0, "Input length must be divisible by 6.");
330        let mut rgba = Vec::with_capacity(rgb.len() / 3 * 4);
331        for chunk in rgb.chunks(6) {
332            rgba.extend_from_slice(&chunk[0..2]);
333            rgba.extend_from_slice(&chunk[2..4]);
334            rgba.extend_from_slice(&chunk[4..6]);
335            rgba.extend_from_slice(&0xFFFFu16.to_le_bytes());
336        }
337        rgba
338    }
339
340    pub(super) fn rgb32f_to_rgba32f(rgb: &[u8]) -> Vec<u8> {
341        assert_eq!(rgb.len() % 12, 0, "Input length must be divisible by 12.");
342        let mut rgba = Vec::with_capacity(rgb.len() / 3 * 4);
343        for chunk in rgb.chunks(12) {
344            rgba.extend_from_slice(&chunk[0..4]);
345            rgba.extend_from_slice(&chunk[4..8]);
346            rgba.extend_from_slice(&chunk[8..12]);
347            rgba.extend_from_slice(&1f32.to_le_bytes());
348        }
349        rgba
350    }
351
352    pub(crate) fn dynamic_image_to_scratch_image(
353        buf: &[u8],
354        width: u32,
355        height: u32,
356        color_type: ExtendedColorType,
357    ) -> Result<ScratchImage, TextureMapEncodeError> {
358        let dxgi_format = helpers::color_type_to_dxgi(color_type)
359            .ok_or(TextureMapEncodeError::DxgiConversion(color_type))?;
360        let slice_pitch = dxgi_format
361            .compute_pitch(width as usize, height as usize, CP_FLAGS::CP_FLAGS_NONE)
362            .map_err(TextureMapEncodeError::DirectXTexError)?;
363
364        let width = width as usize;
365        let height = height as usize;
366
367        let maybe_converted;
368        let pixels = match color_type {
369            ExtendedColorType::Rgb8 | ExtendedColorType::Bgr8 => {
370                maybe_converted = Some(helpers::rgb8_to_rgba8(buf));
371                maybe_converted.as_ref().unwrap().as_ptr() as *mut u8
372            }
373            ExtendedColorType::Rgb16 => {
374                maybe_converted = Some(helpers::rgb16_to_rgba16(buf));
375                maybe_converted.as_ref().unwrap().as_ptr() as *mut u8
376            }
377            ExtendedColorType::Rgb32F => {
378                maybe_converted = Some(helpers::rgb32f_to_rgba32f(buf));
379                maybe_converted.as_ref().unwrap().as_ptr() as *mut u8
380            }
381            _ => buf.as_ptr() as *mut u8,
382        };
383
384        let image = directxtex::Image {
385            width,
386            height,
387            format: dxgi_format,
388            row_pitch: slice_pitch.row,
389            slice_pitch: slice_pitch.slice,
390            pixels,
391        };
392
393        let mut scratch_image = ScratchImage::default();
394        scratch_image.initialize_from_image(&image, false, CP_FLAGS::CP_FLAGS_NONE)?;
395        Ok(scratch_image)
396    }
397}