glacier_texture/
pack.rs

1use crate::atlas::AtlasData;
2use crate::enums::*;
3use crate::mipblock::MipblockData;
4use crate::pack::TexturePackerError::{DirectXTexError, PackingError};
5use crate::texture_map::{
6    TextureData, TextureMap, TextureMapHeaderV1, TextureMapHeaderV2, TextureMapHeaderV3,
7    TextureMapInner,
8};
9use crate::{convert, WoaVersion};
10use directxtex::{
11    Image, ScratchImage, DDS_FLAGS, DXGI_FORMAT, TEX_COMPRESS_FLAGS, TEX_FILTER_FLAGS,
12    TEX_THRESHOLD_DEFAULT, TGA_FLAGS,
13};
14#[cfg(feature = "image")]
15use image::DynamicImage;
16use lz4::block::CompressionMode;
17use std::cmp::max;
18use std::io::{Cursor, Read};
19use std::ptr::NonNull;
20use std::{io, slice};
21use thiserror::Error;
22
23#[derive(Debug, Error)]
24pub enum TexturePackerError {
25    #[error("Error serializing the texture: {0}")]
26    SerializationError(#[from] binrw::Error),
27
28    #[error("Failed to read data: {0}")]
29    IoError(#[from] io::Error),
30
31    #[error("DirectX error: {0}")]
32    DirectXTexError(#[from] directxtex::HResultError),
33
34    #[error("Error building texture: {0}")]
35    PackingError(String),
36}
37
38#[derive(Copy, Clone, Debug)]
39pub enum MipLevels {
40    All,
41    Limit(u8),
42}
43
44#[derive(Copy, Clone, Debug)]
45pub enum MipFilter {
46    Nearest,
47    Linear,
48    Cubic,
49    Box,
50}
51
52#[derive(Copy, Clone, Debug)]
53pub struct TextureMapParameters {
54    texture_type: TextureType,
55    interpret_as: InterpretAs,
56    dimensions: Dimensions,
57    flags: TextureFlagsInner,
58    format: RenderFormat,
59    num_mip_levels: MipLevels,
60    default_mip_level: u8,
61    texd_identifier: u32,
62    mip_filter: MipFilter,
63}
64
65impl TextureMapParameters {
66    pub fn new(format: RenderFormat) -> Self {
67        Self {
68            texture_type: TextureType::Colour,
69            interpret_as: InterpretAs::Normal,
70            dimensions: Dimensions::_2D,
71
72            flags: TextureFlagsInner::default().with_unknown3(true),
73            format,
74            num_mip_levels: MipLevels::All,
75            default_mip_level: 0,
76            texd_identifier: 0x4000,
77            mip_filter: MipFilter::Box,
78        }
79    }
80
81    pub fn from_texture_map(texture: &TextureMap) -> Self {
82        Self {
83            texture_type: texture.texture_type(),
84            interpret_as: texture.interpret_as().unwrap_or(InterpretAs::Normal),
85            dimensions: Dimensions::_2D,
86
87            flags: texture.flags().inner,
88            format: texture.format(),
89            num_mip_levels: MipLevels::All,
90            default_mip_level: 0,
91            texd_identifier: 0x4000,
92            mip_filter: MipFilter::Box,
93        }
94    }
95
96    pub fn texture_type(&self) -> TextureType {
97        self.texture_type
98    }
99    pub fn interpret_as(&self) -> InterpretAs {
100        self.interpret_as
101    }
102    pub fn dimensions(&self) -> Dimensions {
103        self.dimensions
104    }
105    pub fn flags(&self) -> TextureFlags {
106        TextureFlags { inner: self.flags }
107    }
108    pub fn format(&self) -> RenderFormat {
109        self.format
110    }
111    pub fn num_mip_levels(&self) -> MipLevels {
112        self.num_mip_levels
113    }
114    pub fn default_mip_level(&self) -> u8 {
115        self.default_mip_level
116    }
117
118    pub fn texd_identifier(&self) -> u32 {
119        self.texd_identifier
120    }
121
122    pub fn mip_filter(&self) -> MipFilter {
123        self.mip_filter
124    }
125
126    pub fn set_texture_type(&mut self, texture_type: TextureType) {
127        self.texture_type = texture_type;
128    }
129
130    pub fn set_interpret_as(&mut self, interpret_as: InterpretAs) {
131        self.interpret_as = interpret_as;
132    }
133
134    #[cfg(feature = "unstable")]
135    pub fn set_dimensions(&mut self, dimensions: Dimensions) {
136        self.dimensions = dimensions;
137    }
138
139    pub fn set_flags(&mut self, flags: TextureFlags) {
140        self.flags = flags.inner;
141    }
142
143    pub fn set_format(&mut self, format: RenderFormat) {
144        self.format = format;
145    }
146
147    pub fn set_num_mip_levels(&mut self, num_mip_levels: MipLevels) {
148        self.num_mip_levels = num_mip_levels;
149    }
150
151    pub fn set_default_mip_level(&mut self, default_mip_level: u8) {
152        self.default_mip_level = default_mip_level;
153    }
154
155    #[cfg(feature = "unstable")]
156    pub fn set_texd_identifier(&mut self, texd_identifier: u32) {
157        self.texd_identifier = texd_identifier;
158    }
159
160    pub fn set_mip_filter(&mut self, mip_filter: MipFilter) {
161        self.mip_filter = mip_filter;
162    }
163}
164
165/// Builder struct for constructing TextureMap instances.
166/// Will enable the [`unknown3`] flag by default.
167pub struct TextureMapBuilder {
168    params: TextureMapParameters,
169    atlas_data: Option<AtlasData>,
170    image: ScratchImage,
171    use_mipblock1: bool,
172}
173
174impl TextureMapBuilder {
175    pub fn from_dds<R: Read>(mut reader: R) -> Result<Self, TexturePackerError> {
176        let mut image_data = vec![];
177        reader
178            .read_to_end(&mut image_data)
179            .map_err(TexturePackerError::IoError)?;
180        let image = ScratchImage::load_dds(
181            image_data.as_slice(),
182            DDS_FLAGS::DDS_FLAGS_FORCE_DX10_EXT,
183            None,
184            None,
185        )
186        .map_err(DirectXTexError)?;
187
188        Self::from_scratch_image(image)
189    }
190
191    pub fn from_tga<R: Read>(mut reader: R) -> Result<Self, TexturePackerError> {
192        let mut image_data = vec![];
193        reader
194            .read_to_end(&mut image_data)
195            .map_err(TexturePackerError::IoError)?;
196        let image = ScratchImage::load_tga(image_data.as_slice(), TGA_FLAGS::TGA_FLAGS_NONE, None)
197            .map_err(DirectXTexError)?;
198        Self::from_scratch_image(image)
199    }
200
201    #[cfg(feature = "image")]
202    pub fn from_dynamic_image(image: DynamicImage) -> Result<Self, TexturePackerError> {
203        let scratch_image = crate::image::dynamic_image_to_scratch_image(
204            image.as_bytes(),
205            image.width(),
206            image.height(),
207            image.color().into(),
208        )
209        .map_err(|e| PackingError(e.to_string()))?;
210        Self::from_scratch_image(scratch_image)
211    }
212
213    pub(crate) fn from_scratch_image(image: ScratchImage) -> Result<Self, TexturePackerError> {
214        let metadata = image.metadata();
215        let render_format = metadata.format.try_into().or_else(|_err| {
216            let bits_per_pixel = metadata.format.bits_per_pixel();
217            let bits_per_color = metadata.format.bits_per_color();
218            let num_channels = bits_per_pixel.checked_div(bits_per_color).unwrap_or(0);
219
220            match (bits_per_pixel, bits_per_color) {
221                (8, 8) => Ok(RenderFormat::A8),
222                (16, 8) => Ok(RenderFormat::R8G8),
223                (24, 8) => Ok(RenderFormat::R8G8B8A8),
224                (32, 8) => Ok(RenderFormat::R8G8B8A8),
225                (64, 16) => Ok(RenderFormat::R16G16B16A16),
226                _ => Err(PackingError(format!(
227                    "Unsupported render format: bpp={}, bpc={}, channels={:?}, format={:?}",
228                    bits_per_pixel, bits_per_color, num_channels, metadata.format
229                ))),
230            }
231        })?;
232
233        Ok(Self {
234            params: TextureMapParameters::new(render_format),
235            atlas_data: None,
236            image,
237            use_mipblock1: true,
238        })
239    }
240
241    pub fn from_texture_map(texture: &TextureMap) -> Result<Self, TexturePackerError> {
242        let mut builder = convert::create_dds(texture)
243            .map(|dds| {
244                let reader = Cursor::new(dds);
245                Self::from_dds(reader)
246            })
247            .map_err(|e| PackingError(format!("Failed to convert texture: {e}")))??;
248
249        builder.atlas_data = texture.atlas().clone();
250        builder.params.texture_type = texture.texture_type();
251        if let Some(interpret_as) = texture.interpret_as() {
252            builder.params.interpret_as = interpret_as;
253        }
254        Ok(builder)
255    }
256
257    pub fn with_params(mut self, params: TextureMapParameters) -> Self {
258        self.params = params;
259        self
260    }
261
262    // Builder methods for each field
263    pub fn texture_type(mut self, texture_type: TextureType) -> Self {
264        self.params.set_texture_type(texture_type);
265        self
266    }
267
268    pub fn with_texture_type(mut self, texture_type: TextureType) -> Self {
269        self.params.set_texture_type(texture_type);
270        self
271    }
272
273    pub fn with_default_mip_level(mut self, level: u8) -> Self {
274        self.params.set_default_mip_level(level);
275        self
276    }
277
278    pub fn with_num_mip_levels(mut self, levels: MipLevels) -> Self {
279        self.params.set_num_mip_levels(levels);
280        self
281    }
282
283    pub fn with_format(mut self, format: RenderFormat) -> Self {
284        self.params.set_format(format);
285        self
286    }
287
288    pub fn interpret_as(mut self, interpret_as: InterpretAs) -> Self {
289        self.params.set_interpret_as(interpret_as);
290        self
291    }
292
293    pub fn with_mip_filter(mut self, mip_filter: MipFilter) -> Self {
294        self.params.set_mip_filter(mip_filter);
295        self
296    }
297
298    pub fn with_atlas(mut self, atlas_data: AtlasData) -> Self {
299        self.atlas_data = Some(atlas_data);
300        self.params.flags = self.params.flags.with_atlas(true);
301        self
302    }
303
304    pub fn with_mipblock1(mut self, enabled: bool) -> Self {
305        self.use_mipblock1 = enabled;
306        self
307    }
308
309    pub fn with_flags(mut self, flags: TextureFlags) -> Self {
310        self.params.set_flags(flags);
311        self
312    }
313
314    #[cfg(feature = "unstable")]
315    pub fn with_dimensions(mut self, dimensions: Dimensions) -> Self {
316        self.params.set_dimensions(dimensions);
317        self
318    }
319
320    #[cfg(feature = "unstable")]
321    pub fn with_texd_id(mut self, texd_id: u32) -> Self {
322        self.params.set_texd_identifier(texd_id);
323        self
324    }
325
326    ///Convert the image to a different format.
327    /// It is assumed that the input image is not compressed
328    fn convert_to_format(
329        image: ScratchImage,
330        new_format: DXGI_FORMAT,
331    ) -> Result<ScratchImage, TexturePackerError> {
332        let reqs = [
333            new_format.is_typeless(false),
334            new_format.is_planar(),
335            new_format.is_palettized(),
336        ];
337        if reqs.iter().any(|b| *b) {
338            return Err(PackingError(format!("Invalid compression format provided, the provided format is [typeless: {}, planar: {}, palettized: {}]", reqs[0], reqs[1], reqs[2])));
339        }
340
341        Ok(match new_format.is_compressed() {
342            true => image
343                .compress(
344                    new_format,
345                    TEX_COMPRESS_FLAGS::TEX_COMPRESS_BC7_QUICK,
346                    TEX_THRESHOLD_DEFAULT,
347                )
348                .map_err(DirectXTexError)?,
349            false => image
350                .convert(
351                    new_format,
352                    TEX_FILTER_FLAGS::TEX_FILTER_DEFAULT,
353                    TEX_THRESHOLD_DEFAULT,
354                )
355                .map_err(DirectXTexError)?,
356        })
357    }
358
359    /// Final build method to create a TextureMap.
360    pub fn build(self, woa_version: WoaVersion) -> Result<TextureMap, TexturePackerError> {
361        let width = self.image.metadata().width as u16;
362        let height = self.image.metadata().height as u16;
363
364        if !width.is_power_of_two() {
365            return Err(PackingError(format!(
366                "Width ({width}) is not a power of two!"
367            )));
368        }
369
370        if !height.is_power_of_two() {
371            return Err(PackingError(format!(
372                "Height ({height}) is not a power of two!"
373            )));
374        }
375
376        let mut filter = match self.params.mip_filter {
377            MipFilter::Nearest => TEX_FILTER_FLAGS::TEX_FILTER_POINT,
378            MipFilter::Linear => TEX_FILTER_FLAGS::TEX_FILTER_LINEAR,
379            MipFilter::Cubic => TEX_FILTER_FLAGS::TEX_FILTER_CUBIC,
380            MipFilter::Box => TEX_FILTER_FLAGS::TEX_FILTER_BOX,
381        };
382
383        // if cfg!(windows) {
384            filter |= TEX_FILTER_FLAGS::TEX_FILTER_FORCE_NON_WIC;
385        // }
386
387        let mut image = self.image.generate_mip_maps(
388            filter,
389            match self.params.num_mip_levels {
390                MipLevels::All => 0,
391                MipLevels::Limit(n) => n as usize,
392            },
393        )?;
394
395        let target_format = self.params.format.into();
396        if self.image.metadata().format != target_format {
397            image = Self::convert_to_format(image, target_format)?;
398        }
399
400        let generated_mip_levels = image.metadata().mip_levels.clamp(0, 14) as u8;
401        let num_mip_levels = generated_mip_levels;
402
403        // Handle mip sizes
404        let mut mip_sizes = [0u32; 14];
405        for i in 0..generated_mip_levels as usize {
406            let last: u32 = i
407                .checked_sub(1)
408                .and_then(|index| mip_sizes.get(index))
409                .copied()
410                .unwrap_or(0);
411            mip_sizes[i] =
412                last + image.image(i, 0, 0).map(|img| img.slice_pitch).unwrap_or(0) as u32;
413        }
414
415        let mut data = Self::serialize_mipmaps(&image, generated_mip_levels)?;
416        let mut compressed_mip_sizes = mip_sizes;
417        if woa_version == WoaVersion::HM3 {
418            let mut compressed_image_buffer = vec![];
419            let mut cursor = Cursor::new(&data);
420            for mip in 0..generated_mip_levels as usize {
421                if let Some(mip_image) = image.image(mip, 0, 0) {
422                    let mut mip_data = vec![0u8; mip_image.slice_pitch];
423                    cursor
424                        .read(mip_data.as_mut_slice())
425                        .map_err(TexturePackerError::IoError)?;
426                    let mip_compressed = lz4::block::compress(
427                        &mip_data,
428                        Some(CompressionMode::HIGHCOMPRESSION(12)),
429                        false,
430                    )
431                    .map_err(|_| PackingError(format!("Failed to compress mip level {mip}")))?;
432
433                    let last: u32 = mip
434                        .checked_sub(1)
435                        .and_then(|index| compressed_mip_sizes.get(index))
436                        .copied()
437                        .unwrap_or(0);
438                    compressed_mip_sizes[mip] = last
439                        + image
440                            .image(mip, 0, 0)
441                            .map(|_| mip_compressed.len())
442                            .unwrap_or(0) as u32;
443
444                    compressed_image_buffer.extend(mip_compressed);
445                }
446            }
447            data = compressed_image_buffer;
448        }
449
450        let texture_data = if self.use_mipblock1 {
451            TextureData::Mipblock1(MipblockData {
452                video_memory_requirement: (mip_sizes.first().copied().unwrap_or(0x0)
453                    + mip_sizes.get(1).copied().unwrap_or(0x0))
454                    as usize,
455                header: vec![],
456                data,
457            })
458        } else {
459            TextureData::Tex(data)
460        };
461
462        let texture_map_inner = match woa_version {
463            WoaVersion::HM2016 => {
464                let header = TextureMapHeaderV1 {
465                    type_: self.params.texture_type,
466                    texd_identifier: self.params.texd_identifier,
467                    #[cfg(feature = "unstable")]
468                    flags: self.params.flags,
469                    #[cfg(not(feature = "unstable"))]
470                    flags: TextureFlagsInner::default(), //detached from builder
471                    width,
472                    height,
473                    format: self.params.format,
474                    num_mip_levels,
475                    default_mip_level: self.params.default_mip_level,
476                    interpret_as: self.params.interpret_as,
477                    dimensions: self.params.dimensions,
478                    mip_sizes,
479                    has_atlas: self.atlas_data.is_some(),
480                };
481                TextureMapInner {
482                    header,
483                    atlas_data: self.atlas_data,
484                    data: texture_data,
485                }
486                .into()
487            }
488            WoaVersion::HM2 => {
489                let header = TextureMapHeaderV2 {
490                    type_: self.params.texture_type,
491                    texd_identifier: self.params.texd_identifier,
492                    #[cfg(feature = "unstable")]
493                    flags: self.params.flags,
494                    #[cfg(not(feature = "unstable"))]
495                    flags: TextureFlagsInner::default(), //detached from builder
496                    width,
497                    height,
498                    format: self.params.format,
499                    num_mip_levels,
500                    default_mip_level: max(self.params.default_mip_level, 1), //H2 crashes with index 0
501                    mip_sizes,
502                    compressed_mip_sizes,
503                    has_atlas: self.atlas_data.is_some(),
504                };
505                TextureMapInner {
506                    header,
507                    atlas_data: self.atlas_data,
508                    data: texture_data,
509                }
510                .into()
511            }
512            WoaVersion::HM3 => {
513                let header = TextureMapHeaderV3 {
514                    type_: self.params.texture_type,
515                    flags: self.params.flags,
516                    width,
517                    height,
518                    format: self.params.format,
519                    num_mip_levels,
520                    default_mip_level: self.params.default_mip_level,
521                    interpret_as: self.params.interpret_as,
522                    dimensions: self.params.dimensions,
523                    mip_sizes,
524                    compressed_mip_sizes,
525                    has_atlas: self.atlas_data.is_some(),
526                };
527                TextureMapInner {
528                    header,
529                    atlas_data: self.atlas_data,
530                    data: texture_data,
531                }
532                .into()
533            }
534        };
535
536        Ok(texture_map_inner)
537    }
538
539    fn process_mip_image(mip_image: &Image) -> Option<Vec<u8>> {
540        let pixels = NonNull::new(mip_image.pixels)?;
541        let scanlines = mip_image.format.compute_scanlines(mip_image.height);
542        let buffer_size = mip_image.row_pitch.checked_mul(scanlines)?;
543        let raw_slice = unsafe { slice::from_raw_parts(pixels.as_ptr(), buffer_size) };
544        let raw_buffer = raw_slice.to_vec();
545        Some(raw_buffer)
546    }
547
548    fn serialize_mipmaps(
549        image: &directxtex::ScratchImage,
550        mip_levels: u8,
551    ) -> Result<Vec<u8>, TexturePackerError> {
552        let mut serialized = Vec::new();
553        for mip in 0..mip_levels {
554            if let Some(mip_image) = image.image(mip as usize, 0, 0) {
555                let buffer = Self::process_mip_image(mip_image).unwrap_or(vec![]);
556                serialized.extend_from_slice(buffer.as_slice());
557            } else {
558                return Err(PackingError(format!("Missing mip level {mip}")));
559            }
560        }
561        Ok(serialized)
562    }
563}