1use crate::convert::TextureConversionError::DirectXTexError;
2use crate::enums::RenderFormat;
3use crate::texture_map::{MipLevel, TextureMap};
4use directxtex::{
5 HResultError, Image, ScratchImage, TexMetadata, CP_FLAGS, DDS_FLAGS, DXGI_FORMAT,
6 TEX_FILTER_FLAGS, TEX_THRESHOLD_DEFAULT, TGA_FLAGS,
7};
8use png::ColorType;
9use std::io;
10use std::io::{BufWriter, Cursor, Write};
11use thiserror::Error;
12
13#[cfg(feature = "image")]
14use crate::image::TextureMapDecoder;
15#[cfg(feature = "image")]
16use image::{DynamicImage, ImageResult};
17
18
19#[derive(Error, Debug)]
20pub enum TextureConversionError {
21 #[error("Io error {0}")]
22 IoError(#[from] io::Error),
23
24 #[error("DirectxTex error {0}")]
25 DirectXTexError(#[from] HResultError),
26
27 #[error("Invalid texture: {0}")]
28 InvalidTexture(String),
29
30 #[error("Tried to read mip level {0}, which is out of bounds [0..{0}]")]
31 MipOutOfBounds(usize, usize),
32}
33
34pub fn create_dds(tex: &TextureMap) -> Result<Vec<u8>, TextureConversionError> {
36 let mut mips = (0..tex.num_mip_levels())
37 .filter_map(|i| -> Option<MipLevel> {
38 if let Ok(mip) = tex.mipmap(i) {
39 if mip.height > 0 && mip.width > 0 {
40 Some(mip)
41 } else {
42 None
43 }
44 } else {
45 None
46 }
47 })
48 .collect::<Vec<_>>();
49
50 let first_mip = mips.first().ok_or(TextureConversionError::InvalidTexture(
51 "There are no textures in the data".to_string(),
52 ))?;
53
54 let meta_data = TexMetadata {
55 width: first_mip.width,
56 height: first_mip.height,
57 depth: 0,
58 array_size: 1,
59 mip_levels: mips.len(),
60 misc_flags: 0,
61 misc_flags2: 0,
62 format: tex.format().into(),
63 dimension: tex.dimensions().into(),
64 };
65
66 let images_result: Result<Vec<Image>, TextureConversionError> = mips
67 .iter_mut()
68 .map(|mip| -> Result<Image, TextureConversionError> {
69 let pitch = DXGI_FORMAT::from(tex.format())
70 .compute_pitch(mip.width, mip.height, CP_FLAGS::CP_FLAGS_NONE)
71 .map_err(DirectXTexError)?;
72
73 Ok(Image {
74 width: mip.width,
75 height: mip.height,
76 format: tex.format().into(),
77 row_pitch: pitch.row,
78 slice_pitch: pitch.slice,
79 pixels: mip.data.as_mut_ptr(),
80 })
81 })
82 .collect();
83
84 let images = images_result?;
85
86 let blob = directxtex::save_dds(
87 images.as_slice(),
88 &meta_data,
89 DDS_FLAGS::DDS_FLAGS_FORCE_DX10_EXT,
90 )
91 .map_err(DirectXTexError)?;
92 Ok(Vec::from(blob.buffer()))
93}
94
95
96pub fn create_tga(tex: &TextureMap) -> Result<Vec<u8>, TextureConversionError> {
101 let dds = create_dds(tex)?;
102 let mut scratch_image = ScratchImage::load_dds(
103 dds.as_slice(),
104 DDS_FLAGS::DDS_FLAGS_FORCE_DX10_EXT,
105 None,
106 None,
107 )
108 .map_err(DirectXTexError)?;
109 scratch_image = decompress_dds(tex, scratch_image)?;
110 let blob = scratch_image
111 .image(0, 0, 0)
112 .unwrap()
113 .save_tga(TGA_FLAGS::TGA_FLAGS_NONE, None)
114 .map_err(DirectXTexError)?;
115 Ok(Vec::from(blob.buffer()))
116}
117
118pub fn create_png(tex: &TextureMap) -> Result<Vec<u8>, TextureConversionError> {
120 let dds = create_dds(tex)?;
121 let mut scratch_image = ScratchImage::load_dds(
122 dds.as_slice(),
123 DDS_FLAGS::DDS_FLAGS_FORCE_DX10_EXT,
124 None,
125 None,
126 ).map_err(DirectXTexError)?;
127
128 let buf = Vec::new();
129 let cursor = Cursor::new(buf);
130 let mut w = BufWriter::new(cursor);
131
132 scratch_image = decompress_dds(tex, scratch_image)?;
133
134 let render_format: RenderFormat = scratch_image.metadata().format.try_into().unwrap();
135
136 let color_type = match render_format {
137 RenderFormat::A8 => Some(ColorType::Grayscale),
138 RenderFormat::R16G16B16A16 => Some(ColorType::Rgba),
139 RenderFormat::R8G8B8A8 => Some(ColorType::Rgb),
140 RenderFormat::R8G8 => Some(ColorType::Grayscale),
141 _ => None,
142 };
143
144 let bit_depth = match render_format {
145 RenderFormat::R16G16B16A16 => png::BitDepth::Sixteen,
146 _ => png::BitDepth::Eight,
147 };
148
149 let mut encoder = png::Encoder::new(
150 &mut w,
151 scratch_image.metadata().width as u32,
152 scratch_image.metadata().height as u32,
153 );
154 encoder.set_color(color_type.unwrap());
155 encoder.set_depth(bit_depth);
156 let mut writer = encoder.write_header().unwrap();
157
158 let blob = scratch_image
159 .image(0, 0, 0)
160 .unwrap()
161 .save_dds(DDS_FLAGS::DDS_FLAGS_FORCE_DX10_EXT)?;
162
163 writer.write_image_data(blob.buffer()).unwrap(); writer.finish().unwrap();
166 w.flush()?;
167
168 let cursor = w.into_inner().unwrap();
169 Ok(cursor.into_inner())
170}
171
172#[cfg(feature = "image")]
173pub fn create_dynamic_image(tex: &TextureMap) -> ImageResult<DynamicImage> {
174 DynamicImage::from_decoder(TextureMapDecoder::from_texture_map(tex.clone()))
175}
176
177pub(crate) fn decompress_dds(
178 tex: &TextureMap,
179 scratch_image: ScratchImage,
180) -> Result<ScratchImage, TextureConversionError> {
181 let mut scratch_image = scratch_image;
182 if tex.format().is_compressed() {
183 scratch_image = directxtex::decompress(
184 scratch_image.images(),
185 scratch_image.metadata(),
186 match tex.format().num_channels() {
187 1 => DXGI_FORMAT::DXGI_FORMAT_A8_UNORM,
188 2 => DXGI_FORMAT::DXGI_FORMAT_R8G8_UNORM,
189 4 => DXGI_FORMAT::DXGI_FORMAT_R8G8B8A8_UNORM,
190 _ => DXGI_FORMAT::DXGI_FORMAT_UNKNOWN,
191 },
192 )
193 .map_err(DirectXTexError)?
194 }
195
196 if tex.format() == RenderFormat::R16G16B16A16 {
197 scratch_image = directxtex::convert(
198 scratch_image.images(),
199 scratch_image.metadata(),
200 DXGI_FORMAT::DXGI_FORMAT_R8G8B8A8_UNORM,
201 TEX_FILTER_FLAGS::TEX_FILTER_DEFAULT | TEX_FILTER_FLAGS::TEX_FILTER_FORCE_NON_WIC,
202 TEX_THRESHOLD_DEFAULT,
203 )
204 .map_err(DirectXTexError)?;
205 }
206
207 if tex.format().num_channels() == 2 {
209 scratch_image = directxtex::convert(
210 scratch_image.images(),
211 scratch_image.metadata(),
212 DXGI_FORMAT::DXGI_FORMAT_R8G8B8A8_UNORM,
213 TEX_FILTER_FLAGS::TEX_FILTER_DEFAULT,
214 TEX_THRESHOLD_DEFAULT,
215 )
216 .map_err(DirectXTexError)?;
217
218 for pixel in scratch_image.pixels_mut().chunks_mut(4) {
219 if pixel.len() != 4 {
220 continue;
221 }
222 let x = pixel[0] as f64 / 255.0;
223 let y = pixel[1] as f64 / 255.0;
224 pixel[2] = (f64::sqrt(1.0 - (x * x - y * y)) * 255.0) as u8;
225 }
226 }
227 Ok(scratch_image)
228}
229
230pub fn create_mip_dds(
231 tex: &TextureMap,
232 mip_level: usize,
233 decompress: bool,
234) -> Result<Vec<u8>, TextureConversionError> {
235 if let Ok(mut mip) = tex.mipmap(mip_level) {
236 let meta_data = TexMetadata {
237 width: mip.width,
238 height: mip.height,
239 depth: 0,
240 array_size: 1,
241 mip_levels: 1,
242 misc_flags: 0,
243 misc_flags2: 0,
244 format: tex.format().into(),
245 dimension: tex.dimensions().into(),
246 };
247 let pitch = DXGI_FORMAT::from(tex.format())
248 .compute_pitch(mip.width, mip.height, CP_FLAGS::CP_FLAGS_NONE)
249 .map_err(DirectXTexError)?;
250
251 let image = Image {
252 width: mip.width,
253 height: mip.height,
254 format: tex.format().into(),
255 row_pitch: pitch.row,
256 slice_pitch: pitch.slice,
257 pixels: mip.data.as_mut_ptr(),
258 };
259
260 let mut blob =
261 directxtex::save_dds(&[image], &meta_data, DDS_FLAGS::DDS_FLAGS_FORCE_DX10_EXT)
262 .map_err(DirectXTexError)?;
263 if decompress {
264 let dds = ScratchImage::load_dds(blob.buffer(), DDS_FLAGS::DDS_FLAGS_NONE, None, None)
265 .map_err(DirectXTexError)?;
266 let new_dds = decompress_dds(tex, dds)?;
267 blob = directxtex::save_dds(
268 new_dds.images(),
269 new_dds.metadata(),
270 DDS_FLAGS::DDS_FLAGS_NONE,
271 )
272 .map_err(DirectXTexError)?;
273 }
274 Ok(Vec::from(blob.buffer()))
275 } else {
276 Err(TextureConversionError::MipOutOfBounds(
277 mip_level,
278 tex.num_mip_levels(),
279 ))
280 }
281}