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
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::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,
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 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 filter |= TEX_FILTER_FLAGS::TEX_FILTER_FORCE_NON_WIC;
385 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 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(), 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(), width,
497 height,
498 format: self.params.format,
499 num_mip_levels,
500 default_mip_level: max(self.params.default_mip_level, 1), 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}