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::{BufWriter, Cursor, Write};
10use std::ptr::NonNull;
11use std::{io, slice};
12use thiserror::Error;
13
14#[cfg(feature = "image")]
15use crate::image::TextureMapDecoder;
16#[cfg(feature = "image")]
17use image::{DynamicImage, ImageResult};
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
95pub fn create_tga(tex: &TextureMap) -> Result<Vec<u8>, TextureConversionError> {
100 let dds = create_dds(tex)?;
101 let mut scratch_image = ScratchImage::load_dds(
102 dds.as_slice(),
103 DDS_FLAGS::DDS_FLAGS_FORCE_DX10_EXT,
104 None,
105 None,
106 )
107 .map_err(DirectXTexError)?;
108 scratch_image = decompress_dds(tex, scratch_image)?;
109 ensure_8bit_colors(&mut 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 )
127 .map_err(DirectXTexError)?;
128
129 let buf = Vec::new();
130 let cursor = Cursor::new(buf);
131 let mut w = BufWriter::new(cursor);
132
133 scratch_image = decompress_dds(tex, scratch_image)?;
134
135 let render_format: RenderFormat = scratch_image.metadata().format.try_into().unwrap();
136
137 let color_type = match render_format {
138 RenderFormat::A8 => Some(ColorType::Grayscale),
139 RenderFormat::R16G16B16A16 => Some(ColorType::Rgba),
140 RenderFormat::R8G8B8A8 => Some(ColorType::Rgb),
141 RenderFormat::R8G8 => Some(ColorType::Grayscale),
142 _ => None,
143 };
144
145 let bit_depth = match render_format {
146 RenderFormat::R16G16B16A16 => png::BitDepth::Sixteen,
147 _ => png::BitDepth::Eight,
148 };
149
150 let mut encoder = png::Encoder::new(
151 &mut w,
152 scratch_image.metadata().width as u32,
153 scratch_image.metadata().height as u32,
154 );
155 encoder.set_color(color_type.unwrap());
156 encoder.set_depth(bit_depth);
157 let mut writer = encoder.write_header().unwrap();
158
159 let blob = scratch_image
160 .image(0, 0, 0)
161 .unwrap()
162 .save_dds(DDS_FLAGS::DDS_FLAGS_FORCE_DX10_EXT)?;
163
164 writer.write_image_data(blob.buffer()).unwrap(); writer.finish().unwrap();
167 w.flush()?;
168
169 let cursor = w.into_inner().unwrap();
170 Ok(cursor.into_inner())
171}
172
173#[cfg(feature = "image")]
174pub fn create_dynamic_image(tex: &TextureMap) -> ImageResult<DynamicImage> {
175 DynamicImage::from_decoder(TextureMapDecoder::from_texture_map(tex.clone()))
176}
177
178pub(crate) fn decompress_dds(
179 tex: &TextureMap,
180 scratch_image: ScratchImage,
181) -> Result<ScratchImage, TextureConversionError> {
182 let mut scratch_image = scratch_image;
183 if tex.format().is_compressed() {
184 scratch_image = directxtex::decompress(
185 scratch_image.images(),
186 scratch_image.metadata(),
187 tex.format().decompressed_format().into(),
188 )
189 .map_err(DirectXTexError)?
190 }
191
192 if tex.format().num_channels() == 2 {
194 scratch_image = directxtex::convert(
195 scratch_image.images(),
196 scratch_image.metadata(),
197 DXGI_FORMAT::DXGI_FORMAT_R8G8B8A8_UNORM,
198 TEX_FILTER_FLAGS::TEX_FILTER_DEFAULT,
199 TEX_THRESHOLD_DEFAULT,
200 )
201 .map_err(DirectXTexError)?;
202
203 for pixel in scratch_image.pixels_mut().chunks_mut(4) {
204 if pixel.len() != 4 {
205 continue;
206 }
207 let x = pixel[0] as f64 / 255.0;
208 let y = pixel[1] as f64 / 255.0;
209 pixel[2] = (f64::sqrt(1.0 - (x * x - y * y)) * 255.0) as u8;
210 }
211 }
212 Ok(scratch_image)
213}
214
215pub(crate) fn ensure_8bit_colors(
216 scratch_image: &mut ScratchImage,
217) -> Result<(), TextureConversionError> {
218 if scratch_image.metadata().format == DXGI_FORMAT::DXGI_FORMAT_R16G16B16A16_FLOAT {
219 *scratch_image = directxtex::convert(
220 scratch_image.images(),
221 scratch_image.metadata(),
222 DXGI_FORMAT::DXGI_FORMAT_R8G8B8A8_UNORM,
223 TEX_FILTER_FLAGS::TEX_FILTER_DEFAULT | TEX_FILTER_FLAGS::TEX_FILTER_FORCE_NON_WIC,
224 TEX_THRESHOLD_DEFAULT,
225 )
226 .map_err(DirectXTexError)?;
227 }
228 Ok(())
229}
230
231pub(crate) fn image_pixels(image: &Image) -> Option<Vec<u8>> {
232 let pixels = NonNull::new(image.pixels)?;
233 let scanlines = image.format.compute_scanlines(image.height);
234 let buffer_size = image.row_pitch.checked_mul(scanlines)?;
235 let raw_slice = unsafe { slice::from_raw_parts(pixels.as_ptr(), buffer_size) };
236 let raw_buffer = raw_slice.to_vec();
237 Some(raw_buffer)
238}
239
240pub fn create_mip_dds(
241 tex: &TextureMap,
242 mip_level: usize,
243 decompress: bool,
244) -> Result<Vec<u8>, TextureConversionError> {
245 if let Ok(mut mip) = tex.mipmap(mip_level) {
246 let meta_data = TexMetadata {
247 width: mip.width,
248 height: mip.height,
249 depth: 0,
250 array_size: 1,
251 mip_levels: 1,
252 misc_flags: 0,
253 misc_flags2: 0,
254 format: tex.format().into(),
255 dimension: tex.dimensions().into(),
256 };
257 let pitch = DXGI_FORMAT::from(tex.format())
258 .compute_pitch(mip.width, mip.height, CP_FLAGS::CP_FLAGS_NONE)
259 .map_err(DirectXTexError)?;
260
261 let image = Image {
262 width: mip.width,
263 height: mip.height,
264 format: tex.format().into(),
265 row_pitch: pitch.row,
266 slice_pitch: pitch.slice,
267 pixels: mip.data.as_mut_ptr(),
268 };
269
270 let mut blob =
271 directxtex::save_dds(&[image], &meta_data, DDS_FLAGS::DDS_FLAGS_FORCE_DX10_EXT)
272 .map_err(DirectXTexError)?;
273 if decompress {
274 let dds = ScratchImage::load_dds(blob.buffer(), DDS_FLAGS::DDS_FLAGS_NONE, None, None)
275 .map_err(DirectXTexError)?;
276 let new_dds = decompress_dds(tex, dds)?;
277 blob = directxtex::save_dds(
278 new_dds.images(),
279 new_dds.metadata(),
280 DDS_FLAGS::DDS_FLAGS_NONE,
281 )
282 .map_err(DirectXTexError)?;
283 }
284 Ok(Vec::from(blob.buffer()))
285 } else {
286 Err(TextureConversionError::MipOutOfBounds(
287 mip_level,
288 tex.num_mip_levels(),
289 ))
290 }
291}