1use crate::atlas::AtlasData;
2use crate::mipblock::MipblockData;
3use crate::pack::TexturePackerError::{DirectXTexError, PackingError};
4use crate::texture_map::{
5 TextureData, TextureMap, TextureMapHeaderV1, TextureMapHeaderV2, TextureMapHeaderV3,
6 TextureMapHeaderV4, TextureMapInner,
7};
8use crate::{convert, GlacierGame};
9use directxtex::{
10 Image, ScratchImage, DDS_FLAGS, DXGI_FORMAT, TEX_COMPRESS_FLAGS, TEX_FILTER_FLAGS,
11 TEX_THRESHOLD_DEFAULT, TGA_FLAGS,
12};
13#[cfg(feature = "image")]
14use image::DynamicImage;
15use lz4::block::CompressionMode;
16use std::cmp::max;
17use std::io;
18use std::io::{Cursor, Read};
19use thiserror::Error;
20use crate::enums::{Dimensions, InterpretAs, RenderFormat, TextureType, TextureFlagsInner, TextureFlags, WoaRenderFormat, BondRenderFormat};
21use crate::pack::MipLevels::Limit;
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_lz4_compression(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
165pub 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::helpers::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 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 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,
348 )
349 .map_err(DirectXTexError)?,
350 false => image
351 .convert(
352 new_format,
353 TEX_FILTER_FLAGS::TEX_FILTER_DEFAULT,
354 TEX_THRESHOLD_DEFAULT,
355 )
356 .map_err(DirectXTexError)?,
357 })
358 }
359
360 pub fn build(self, glacier_game: GlacierGame) -> Result<TextureMap, TexturePackerError> {
362 let width = self.image.metadata().width as u16;
363 let height = self.image.metadata().height as u16;
364
365 if !width.is_power_of_two() {
366 return Err(PackingError(format!(
367 "Width ({width}) is not a power of two!"
368 )));
369 }
370
371 if !height.is_power_of_two() {
372 return Err(PackingError(format!(
373 "Height ({height}) is not a power of two!"
374 )));
375 }
376
377 let mut filter = match self.params.mip_filter {
378 MipFilter::Nearest => TEX_FILTER_FLAGS::TEX_FILTER_POINT,
379 MipFilter::Linear => TEX_FILTER_FLAGS::TEX_FILTER_LINEAR,
380 MipFilter::Cubic => TEX_FILTER_FLAGS::TEX_FILTER_CUBIC,
381 MipFilter::Box => TEX_FILTER_FLAGS::TEX_FILTER_BOX,
382 };
383
384 filter |= TEX_FILTER_FLAGS::TEX_FILTER_FORCE_NON_WIC;
386 let mut image = self.image;
389 if image.metadata().format.is_compressed() {
390 image = directxtex::decompress(
391 image.images(),
392 image.metadata(),
393 match image.metadata().format {
394 DXGI_FORMAT::DXGI_FORMAT_BC1_UNORM
395 | DXGI_FORMAT::DXGI_FORMAT_BC2_UNORM
396 | DXGI_FORMAT::DXGI_FORMAT_BC3_UNORM
397 | DXGI_FORMAT::DXGI_FORMAT_BC7_UNORM => DXGI_FORMAT::DXGI_FORMAT_R8G8B8A8_UNORM,
398 DXGI_FORMAT::DXGI_FORMAT_BC4_UNORM => DXGI_FORMAT::DXGI_FORMAT_A8_UNORM,
399 DXGI_FORMAT::DXGI_FORMAT_BC5_UNORM => DXGI_FORMAT::DXGI_FORMAT_R8G8_UNORM,
400 _ => DXGI_FORMAT::DXGI_FORMAT_UNKNOWN,
401 },
402 )?;
403 }
404
405 let should_generate_mips = self.use_mipblock1 && !matches!(self.params.num_mip_levels, Limit(1));
406 if should_generate_mips {
407 image = image.generate_mip_maps(
408 filter,
409 match self.params.num_mip_levels {
410 MipLevels::All => 0,
411 Limit(n) => n as usize,
412 },
413 )?;
414 }
415
416 let target_format = self.params.format.into();
417 if image.metadata().format != target_format {
418 image = Self::convert_to_format(image, target_format)?;
419 }
420
421 let generated_mip_levels = image.metadata().mip_levels.clamp(0, 14) as u8;
422 let num_mip_levels = generated_mip_levels;
423
424 let mut mip_sizes = [0u32; 14];
426 for i in 0..generated_mip_levels as usize {
427 let last: u32 = i
428 .checked_sub(1)
429 .and_then(|index| mip_sizes.get(index))
430 .copied()
431 .unwrap_or(0);
432 mip_sizes[i] =
433 last + image.image(i, 0, 0).map(|img| img.slice_pitch).unwrap_or(0) as u32;
434 }
435
436 let mut data = Self::serialize_mipmaps(&image, generated_mip_levels)?;
437 let mut compressed_mip_sizes = mip_sizes;
438 if glacier_game.supports_lz4_compression() && self.params.flags.lz4_compression() {
439 let mut compressed_image_buffer = vec![];
440 let mut cursor = Cursor::new(&data);
441 for mip in 0..generated_mip_levels as usize {
442 if let Some(mip_image) = image.image(mip, 0, 0) {
443 let mut mip_data = vec![0u8; mip_image.slice_pitch];
444 cursor
445 .read(mip_data.as_mut_slice())
446 .map_err(TexturePackerError::IoError)?;
447 let mip_compressed = lz4::block::compress(
448 &mip_data,
449 Some(CompressionMode::HIGHCOMPRESSION(12)),
450 false,
451 )
452 .map_err(|_| PackingError(format!("Failed to compress mip level {mip}")))?;
453
454 let last: u32 = mip
455 .checked_sub(1)
456 .and_then(|index| compressed_mip_sizes.get(index))
457 .copied()
458 .unwrap_or(0);
459 compressed_mip_sizes[mip] = last
460 + image
461 .image(mip, 0, 0)
462 .map(|_| mip_compressed.len())
463 .unwrap_or(0) as u32;
464
465 compressed_image_buffer.extend(mip_compressed);
466 }
467 }
468 data = compressed_image_buffer;
469 }
470
471 let texture_data = if self.use_mipblock1 {
472 TextureData::Mipblock1(MipblockData {
473 video_memory_requirement: (mip_sizes.first().copied().unwrap_or(0x0)
474 + mip_sizes.get(1).copied().unwrap_or(0x0))
475 as usize,
476 header: vec![],
477 data,
478 })
479 } else {
480 TextureData::Tex(data)
481 };
482
483 let texture_map_inner = match glacier_game {
484 GlacierGame::HM2016 => {
485 let header = TextureMapHeaderV1 {
486 type_: self.params.texture_type,
487 texd_identifier: self.params.texd_identifier,
488 #[cfg(feature = "unstable")]
489 flags: self.params.flags,
490 #[cfg(not(feature = "unstable"))]
491 flags: TextureFlagsInner::default(), width,
493 height,
494 format: WoaRenderFormat {
495 format: self.params.format,
496 },
497 num_mip_levels,
498 default_mip_level: self.params.default_mip_level,
499 interpret_as: self.params.interpret_as,
500 dimensions: self.params.dimensions,
501 mip_sizes,
502 has_atlas: self.atlas_data.is_some(),
503 };
504 TextureMapInner {
505 header,
506 atlas_data: self.atlas_data,
507 data: texture_data,
508 }
509 .into()
510 }
511 GlacierGame::HM2 => {
512 let header = TextureMapHeaderV2 {
513 type_: self.params.texture_type,
514 texd_identifier: self.params.texd_identifier,
515 #[cfg(feature = "unstable")]
516 flags: self.params.flags,
517 #[cfg(not(feature = "unstable"))]
518 flags: TextureFlagsInner::default(), width,
520 height,
521 format: WoaRenderFormat {
522 format: self.params.format,
523 },
524 num_mip_levels,
525 default_mip_level: max(self.params.default_mip_level, 1), mip_sizes,
527 compressed_mip_sizes,
528 has_atlas: self.atlas_data.is_some(),
529 };
530 TextureMapInner {
531 header,
532 atlas_data: self.atlas_data,
533 data: texture_data,
534 }
535 .into()
536 }
537 GlacierGame::HM3 => {
538 let header = TextureMapHeaderV3 {
539 type_: self.params.texture_type,
540 flags: self.params.flags,
541 width,
542 height,
543 format: WoaRenderFormat {
544 format: self.params.format,
545 },
546 num_mip_levels,
547 default_mip_level: self.params.default_mip_level,
548 interpret_as: self.params.interpret_as,
549 dimensions: self.params.dimensions,
550 mip_sizes,
551 compressed_mip_sizes,
552 has_atlas: self.atlas_data.is_some(),
553 };
554 TextureMapInner {
555 header,
556 atlas_data: self.atlas_data,
557 data: texture_data,
558 }
559 .into()
560 }
561 GlacierGame::KNT => {
562 let header = TextureMapHeaderV4 {
563 type_: self.params.texture_type,
564 flags: self.params.flags,
565 width,
566 height,
567 format: BondRenderFormat {
568 format: self.params.format,
569 },
570 num_mip_levels,
571 default_mip_level: self.params.default_mip_level,
572 interpret_as: self.params.interpret_as,
573 dimensions: self.params.dimensions,
574 mip_sizes,
575 compressed_mip_sizes,
576 has_atlas: self.atlas_data.is_some(),
577 };
578 TextureMapInner {
579 header,
580 atlas_data: self.atlas_data,
581 data: texture_data,
582 }
583 .into()
584 }
585 };
586
587 Ok(texture_map_inner)
588 }
589
590 fn process_mip_image(mip_image: &Image) -> Option<Vec<u8>> {
591 convert::image_pixels(mip_image)
592 }
593
594 fn serialize_mipmaps(
595 image: &directxtex::ScratchImage,
596 mip_levels: u8,
597 ) -> Result<Vec<u8>, TexturePackerError> {
598 let mut serialized = Vec::new();
599 for mip in 0..mip_levels {
600 if let Some(mip_image) = image.image(mip as usize, 0, 0) {
601 let buffer = Self::process_mip_image(mip_image).unwrap_or(vec![]);
602 serialized.extend_from_slice(buffer.as_slice());
603 } else {
604 return Err(PackingError(format!("Missing mip level {mip}")));
605 }
606 }
607 Ok(serialized)
608 }
609}