image_dds/
dds.rs

1use std::ops::Range;
2
3use ddsfile::{Caps2, D3DFormat, Dds, DxgiFormat, FourCC};
4use thiserror::Error;
5
6use crate::{
7    CreateImageError, ImageFormat, Mipmaps, Quality, Surface, SurfaceError, SurfaceRgba32Float,
8    SurfaceRgba8,
9};
10
11/// Errors that can occur when converting to DDS.
12#[derive(Debug, Error)]
13pub enum CreateDdsError {
14    #[error("error creating DDS: {0}")]
15    Dds(#[from] ddsfile::Error),
16
17    #[error("error compressing surface: {0}")]
18    CompressSurface(#[from] SurfaceError),
19}
20
21#[cfg(feature = "encode")]
22#[cfg(feature = "image")]
23/// Encode `image` to a 2D DDS file with the given `format`.
24///
25/// The number of mipmaps generated depends on the `mipmaps` parameter.
26pub fn dds_from_image(
27    image: &image::RgbaImage,
28    format: ImageFormat,
29    quality: Quality,
30    mipmaps: Mipmaps,
31) -> Result<Dds, CreateDdsError> {
32    // Assume all images are 2D for now.
33    SurfaceRgba8::from_image(image)
34        .encode(format, quality, mipmaps)?
35        .to_dds()
36}
37
38#[cfg(feature = "encode")]
39#[cfg(feature = "image")]
40/// Encode `image` to a 2D DDS file with the given `format`.
41///
42/// The number of mipmaps generated depends on the `mipmaps` parameter.
43pub fn dds_from_imagef32(
44    image: &image::Rgba32FImage,
45    format: ImageFormat,
46    quality: Quality,
47    mipmaps: Mipmaps,
48) -> Result<Dds, CreateDdsError> {
49    // Assume all images are 2D for now.
50    SurfaceRgba32Float::from_image(image)
51        .encode(format, quality, mipmaps)?
52        .to_dds()
53}
54
55#[cfg(feature = "image")]
56/// Decode the given mip level from `dds` to an RGBA8 image.
57/// Array layers are arranged vertically from top to bottom.
58pub fn image_from_dds(dds: &Dds, mipmap: u32) -> Result<image::RgbaImage, CreateImageError> {
59    let layers = array_layer_count(dds);
60    SurfaceRgba8::decode_layers_mipmaps_dds(dds, 0..layers, mipmap..mipmap + 1)?.into_image()
61}
62
63#[cfg(feature = "image")]
64/// Decode the given mip level from `dds` to an RGBAF32 image.
65/// Array layers are arranged vertically from top to bottom.
66pub fn imagef32_from_dds(dds: &Dds, mipmap: u32) -> Result<image::Rgba32FImage, CreateImageError> {
67    let layers = array_layer_count(dds);
68    SurfaceRgba32Float::decode_layers_mipmaps_dds(dds, 0..layers, mipmap..mipmap + 1)?.into_image()
69}
70
71impl<T: AsRef<[u8]>> Surface<T> {
72    /// Create a DDS file with the same image data and format.
73    ///
74    /// Creates a DXGI DDS for most formats and D3D DDS for some legacy formats.
75    pub fn to_dds(&self) -> Result<crate::ddsfile::Dds, CreateDdsError> {
76        let mut dds = dxgi_from_image_format(self.image_format)
77            .map(|format| {
78                Dds::new_dxgi(ddsfile::NewDxgiParams {
79                    height: self.height,
80                    width: self.width,
81                    depth: if self.depth > 1 {
82                        Some(self.depth)
83                    } else {
84                        None
85                    },
86                    format,
87                    mipmap_levels: (self.mipmaps > 1).then_some(self.mipmaps),
88                    array_layers: (self.layers > 1 && self.layers != 6).then_some(self.layers),
89                    caps2: (self.layers == 6).then_some(Caps2::CUBEMAP | Caps2::CUBEMAP_ALLFACES),
90                    is_cubemap: self.layers == 6,
91                    resource_dimension: if self.depth > 1 {
92                        ddsfile::D3D10ResourceDimension::Texture3D
93                    } else {
94                        ddsfile::D3D10ResourceDimension::Texture2D
95                    },
96                    alpha_mode: ddsfile::AlphaMode::Straight,
97                })
98            })
99            .or_else(|| {
100                // Not all surface formats are supported by DXGI.
101                d3d_from_image_format(self.image_format).map(|format| {
102                    Dds::new_d3d(ddsfile::NewD3dParams {
103                        height: self.height,
104                        width: self.width,
105                        depth: if self.depth > 1 {
106                            Some(self.depth)
107                        } else {
108                            None
109                        },
110                        format,
111                        mipmap_levels: (self.mipmaps > 1).then_some(self.mipmaps),
112                        caps2: (self.layers == 6)
113                            .then_some(Caps2::CUBEMAP | Caps2::CUBEMAP_ALLFACES),
114                    })
115                })
116            })
117            .unwrap()?;
118
119        dds.data = self.data.as_ref().to_vec();
120
121        Ok(dds)
122    }
123}
124
125impl<'a> Surface<&'a [u8]> {
126    /// Create a view over the data in `dds` without any copies.
127    pub fn from_dds(dds: &'a crate::ddsfile::Dds) -> Result<Self, SurfaceError> {
128        let width = dds.get_width();
129        let height = dds.get_height();
130        let depth = dds.get_depth();
131        let layers = array_layer_count(dds);
132        let mipmaps = dds.get_num_mipmap_levels();
133        let image_format = dds_image_format(dds).map_err(SurfaceError::UnsupportedDdsFormat)?;
134
135        Ok(Surface {
136            width,
137            height,
138            depth,
139            layers,
140            mipmaps,
141            image_format,
142            data: &dds.data,
143        })
144    }
145}
146
147#[cfg(feature = "encode")]
148impl<T: AsRef<[u8]>> SurfaceRgba8<T> {
149    /// Encode a `width` x `height` x `depth` RGBA8 surface to a DDS file with the given `format`.
150    ///
151    /// The number of mipmaps generated depends on the `mipmaps` parameter.
152    pub fn encode_dds(
153        &self,
154        format: ImageFormat,
155        quality: Quality,
156        mipmaps: Mipmaps,
157    ) -> Result<Dds, CreateDdsError> {
158        self.encode(format, quality, mipmaps)?.to_dds()
159    }
160}
161
162impl SurfaceRgba8<Vec<u8>> {
163    /// Decode all layers and mipmaps from `dds` to an RGBA8 surface.
164    pub fn decode_dds(dds: &Dds) -> Result<SurfaceRgba8<Vec<u8>>, SurfaceError> {
165        Surface::from_dds(dds)?.decode_rgba8()
166    }
167
168    /// Decode a specific range of layers and mipmaps from `dds` to an RGBA8 surface.
169    pub fn decode_layers_mipmaps_dds(
170        dds: &Dds,
171        layers: Range<u32>,
172        mipmaps: Range<u32>,
173    ) -> Result<SurfaceRgba8<Vec<u8>>, SurfaceError> {
174        Surface::from_dds(dds)?.decode_layers_mipmaps_rgba8(layers, mipmaps)
175    }
176}
177
178impl SurfaceRgba32Float<Vec<f32>> {
179    /// Decode all layers and mipmaps from `dds` to an RGBAF32 surface.
180    pub fn decode_dds(dds: &Dds) -> Result<SurfaceRgba32Float<Vec<f32>>, SurfaceError> {
181        Surface::from_dds(dds)?.decode_rgbaf32()
182    }
183
184    /// Decode a specific range of layers and mipmaps from `dds` to an RGBAF32 surface.
185    pub fn decode_layers_mipmaps_dds(
186        dds: &Dds,
187        layers: Range<u32>,
188        mipmaps: Range<u32>,
189    ) -> Result<SurfaceRgba32Float<Vec<f32>>, SurfaceError> {
190        Surface::from_dds(dds)?.decode_layers_mipmaps_rgbaf32(layers, mipmaps)
191    }
192}
193
194fn array_layer_count(dds: &Dds) -> u32 {
195    // Array layers for DDS are calculated differently for cube maps.
196    if matches!(&dds.header10, Some(header10) if header10.misc_flag == ddsfile::MiscFlag::TEXTURECUBE)
197    {
198        dds.get_num_array_layers().max(1) * 6
199    } else {
200        dds.get_num_array_layers().max(1)
201    }
202}
203
204/// Format information for all DDS variants.
205#[derive(Debug, PartialEq)]
206pub struct DdsFormatInfo {
207    pub dxgi: Option<DxgiFormat>,
208    pub d3d: Option<D3DFormat>,
209    pub fourcc: Option<FourCC>,
210}
211
212/// Returns the format of `dds` or `None` if the format is unrecognized.
213pub fn dds_image_format(dds: &Dds) -> Result<ImageFormat, DdsFormatInfo> {
214    // The format can be DXGI, D3D, or specified in the FOURCC.
215    let dxgi = dds.get_dxgi_format();
216    let d3d = dds.get_d3d_format();
217    let fourcc = dds.header.spf.fourcc.clone();
218
219    d3d.and_then(image_format_from_d3d)
220        .or_else(|| dxgi.and_then(image_format_from_dxgi))
221        .or_else(|| fourcc.clone().and_then(image_format_from_fourcc))
222        .ok_or(DdsFormatInfo { dxgi, d3d, fourcc })
223}
224
225fn image_format_from_dxgi(format: DxgiFormat) -> Option<ImageFormat> {
226    match format {
227        DxgiFormat::R8_UNorm => Some(ImageFormat::R8Unorm),
228        DxgiFormat::R8_SNorm => Some(ImageFormat::R8Snorm),
229        DxgiFormat::R8G8_UNorm => Some(ImageFormat::Rg8Unorm),
230        DxgiFormat::R8G8_SNorm => Some(ImageFormat::Rg8Snorm),
231        DxgiFormat::R8G8B8A8_UNorm => Some(ImageFormat::Rgba8Unorm),
232        DxgiFormat::R8G8B8A8_UNorm_sRGB => Some(ImageFormat::Rgba8UnormSrgb),
233        DxgiFormat::R16G16B16A16_Float => Some(ImageFormat::Rgba16Float),
234        DxgiFormat::R32G32B32A32_Float => Some(ImageFormat::Rgba32Float),
235        DxgiFormat::B8G8R8A8_UNorm => Some(ImageFormat::Bgra8Unorm),
236        DxgiFormat::B8G8R8A8_UNorm_sRGB => Some(ImageFormat::Bgra8UnormSrgb),
237        DxgiFormat::BC1_UNorm => Some(ImageFormat::BC1RgbaUnorm),
238        DxgiFormat::BC1_UNorm_sRGB => Some(ImageFormat::BC1RgbaUnormSrgb),
239        DxgiFormat::BC2_UNorm => Some(ImageFormat::BC2RgbaUnorm),
240        DxgiFormat::BC2_UNorm_sRGB => Some(ImageFormat::BC2RgbaUnormSrgb),
241        DxgiFormat::BC3_UNorm => Some(ImageFormat::BC3RgbaUnorm),
242        DxgiFormat::BC3_UNorm_sRGB => Some(ImageFormat::BC3RgbaUnormSrgb),
243        DxgiFormat::BC4_UNorm => Some(ImageFormat::BC4RUnorm),
244        DxgiFormat::BC4_SNorm => Some(ImageFormat::BC4RSnorm),
245        DxgiFormat::BC5_UNorm => Some(ImageFormat::BC5RgUnorm),
246        DxgiFormat::BC5_SNorm => Some(ImageFormat::BC5RgSnorm),
247        DxgiFormat::BC6H_SF16 => Some(ImageFormat::BC6hRgbSfloat),
248        DxgiFormat::BC6H_UF16 => Some(ImageFormat::BC6hRgbUfloat),
249        DxgiFormat::BC7_UNorm => Some(ImageFormat::BC7RgbaUnorm),
250        DxgiFormat::BC7_UNorm_sRGB => Some(ImageFormat::BC7RgbaUnormSrgb),
251        DxgiFormat::B4G4R4A4_UNorm => Some(ImageFormat::Bgra4Unorm),
252        DxgiFormat::R16G16B16A16_UNorm => Some(ImageFormat::Rgba16Unorm),
253        DxgiFormat::R16G16B16A16_SNorm => Some(ImageFormat::Rgba16Snorm),
254        DxgiFormat::R16G16_UNorm => Some(ImageFormat::Rg16Unorm),
255        DxgiFormat::R16G16_SNorm => Some(ImageFormat::Rg16Snorm),
256        DxgiFormat::R16_UNorm => Some(ImageFormat::R16Unorm),
257        DxgiFormat::R16_SNorm => Some(ImageFormat::R16Snorm),
258        DxgiFormat::R16_Float => Some(ImageFormat::R16Float),
259        DxgiFormat::R16G16_Float => Some(ImageFormat::Rg16Float),
260        DxgiFormat::R32_Float => Some(ImageFormat::R32Float),
261        DxgiFormat::R32G32_Float => Some(ImageFormat::Rg32Float),
262        DxgiFormat::R8G8B8A8_SNorm => Some(ImageFormat::Rgba8Snorm),
263        DxgiFormat::R32G32B32_Float => Some(ImageFormat::Rgb32Float),
264        DxgiFormat::B5G5R5A1_UNorm => Some(ImageFormat::Bgr5A1Unorm),
265        _ => None,
266    }
267}
268
269fn image_format_from_d3d(format: D3DFormat) -> Option<ImageFormat> {
270    match format {
271        D3DFormat::DXT1 => Some(ImageFormat::BC1RgbaUnorm),
272        D3DFormat::DXT2 => Some(ImageFormat::BC2RgbaUnorm),
273        D3DFormat::DXT3 => Some(ImageFormat::BC2RgbaUnorm),
274        D3DFormat::DXT4 => Some(ImageFormat::BC3RgbaUnorm),
275        D3DFormat::DXT5 => Some(ImageFormat::BC3RgbaUnorm),
276        // BGRA can also be written ARGB depending on how we look at the bytes.
277        D3DFormat::A4R4G4B4 => Some(ImageFormat::Bgra4Unorm),
278        D3DFormat::A8R8G8B8 => Some(ImageFormat::Bgra8Unorm),
279        D3DFormat::R8G8B8 => Some(ImageFormat::Bgr8Unorm),
280        D3DFormat::A8B8G8R8 => Some(ImageFormat::Rgba8Unorm),
281        D3DFormat::G16R16F => Some(ImageFormat::Rg16Float),
282        D3DFormat::A16B16G16R16F => Some(ImageFormat::Rgba16Float),
283        D3DFormat::G32R32F => Some(ImageFormat::Rg32Float),
284        D3DFormat::A32B32G32R32F => Some(ImageFormat::Rgba32Float),
285        D3DFormat::G16R16 => Some(ImageFormat::Rg16Unorm),
286        D3DFormat::A16B16G16R16 => Some(ImageFormat::Rgba16Unorm),
287        D3DFormat::A1R5G5B5 => Some(ImageFormat::Bgr5A1Unorm),
288        _ => None,
289    }
290}
291
292const BC5U: u32 = u32::from_le_bytes(*b"BC5U");
293const ATI2: u32 = u32::from_le_bytes(*b"ATI2");
294
295fn image_format_from_fourcc(fourcc: FourCC) -> Option<ImageFormat> {
296    match fourcc.0 {
297        FourCC::DXT1 => Some(ImageFormat::BC1RgbaUnorm),
298        FourCC::DXT2 => Some(ImageFormat::BC2RgbaUnorm),
299        FourCC::DXT3 => Some(ImageFormat::BC2RgbaUnorm),
300        FourCC::DXT4 => Some(ImageFormat::BC3RgbaUnorm),
301        FourCC::DXT5 => Some(ImageFormat::BC3RgbaUnorm),
302        FourCC::BC4_UNORM => Some(ImageFormat::BC4RUnorm),
303        FourCC::BC4_SNORM => Some(ImageFormat::BC4RSnorm),
304        ATI2 | BC5U => Some(ImageFormat::BC5RgUnorm),
305        FourCC::BC5_SNORM => Some(ImageFormat::BC5RgSnorm),
306        _ => None,
307    }
308}
309
310fn d3d_from_image_format(value: ImageFormat) -> Option<D3DFormat> {
311    // bc4 and bc5 are handled by fourcc.
312    match value {
313        ImageFormat::BC1RgbaUnorm => Some(D3DFormat::DXT1),
314        ImageFormat::BC1RgbaUnormSrgb => Some(D3DFormat::DXT1),
315        ImageFormat::BC2RgbaUnorm => Some(D3DFormat::DXT2),
316        ImageFormat::BC2RgbaUnormSrgb => Some(D3DFormat::DXT2),
317        ImageFormat::BC3RgbaUnorm => Some(D3DFormat::DXT5),
318        ImageFormat::BC3RgbaUnormSrgb => Some(D3DFormat::DXT5),
319        ImageFormat::BC4RUnorm => None,
320        ImageFormat::BC4RSnorm => None,
321        ImageFormat::BC5RgUnorm => None,
322        ImageFormat::BC5RgSnorm => None,
323        ImageFormat::BC6hRgbUfloat => None,
324        ImageFormat::BC6hRgbSfloat => None,
325        ImageFormat::BC7RgbaUnorm => None,
326        ImageFormat::BC7RgbaUnormSrgb => None,
327        ImageFormat::R8Unorm => None,
328        ImageFormat::R8Snorm => None,
329        ImageFormat::Rg8Unorm => None,
330        ImageFormat::Rg8Snorm => None,
331        ImageFormat::Rgba8Unorm => Some(D3DFormat::A8B8G8R8),
332        ImageFormat::Rgba8UnormSrgb => Some(D3DFormat::A8B8G8R8),
333        ImageFormat::Rgba16Float => Some(D3DFormat::A16B16G16R16F),
334        ImageFormat::Rgba32Float => Some(D3DFormat::A32B32G32R32F),
335        ImageFormat::Bgra8Unorm => Some(D3DFormat::A8R8G8B8),
336        ImageFormat::Bgra8UnormSrgb => Some(D3DFormat::A8R8G8B8),
337        ImageFormat::Bgra4Unorm => Some(D3DFormat::A4R4G4B4),
338        ImageFormat::Bgr8Unorm => Some(D3DFormat::R8G8B8),
339        ImageFormat::R16Unorm => None,
340        ImageFormat::R16Snorm => None,
341        ImageFormat::Rg16Unorm => Some(D3DFormat::G16R16),
342        ImageFormat::Rg16Snorm => None,
343        ImageFormat::Rgba16Unorm => Some(D3DFormat::A16B16G16R16),
344        ImageFormat::Rgba16Snorm => None,
345        ImageFormat::Rg16Float => Some(D3DFormat::G16R16F),
346        ImageFormat::Rg32Float => Some(D3DFormat::G32R32F),
347        ImageFormat::R16Float => Some(D3DFormat::R16F),
348        ImageFormat::R32Float => Some(D3DFormat::R32F),
349        ImageFormat::Rgba8Snorm => None,
350        ImageFormat::Rgb32Float => None,
351        ImageFormat::Bgr5A1Unorm => Some(D3DFormat::A1R5G5B5),
352    }
353}
354
355fn dxgi_from_image_format(value: ImageFormat) -> Option<DxgiFormat> {
356    match value {
357        ImageFormat::BC1RgbaUnorm => Some(DxgiFormat::BC1_UNorm),
358        ImageFormat::BC1RgbaUnormSrgb => Some(DxgiFormat::BC1_UNorm_sRGB),
359        ImageFormat::BC2RgbaUnorm => Some(DxgiFormat::BC2_UNorm),
360        ImageFormat::BC2RgbaUnormSrgb => Some(DxgiFormat::BC2_UNorm_sRGB),
361        ImageFormat::BC3RgbaUnorm => Some(DxgiFormat::BC3_UNorm),
362        ImageFormat::BC3RgbaUnormSrgb => Some(DxgiFormat::BC3_UNorm_sRGB),
363        ImageFormat::BC4RUnorm => Some(DxgiFormat::BC4_UNorm),
364        ImageFormat::BC4RSnorm => Some(DxgiFormat::BC4_SNorm),
365        ImageFormat::BC5RgUnorm => Some(DxgiFormat::BC5_UNorm),
366        ImageFormat::BC5RgSnorm => Some(DxgiFormat::BC5_SNorm),
367        ImageFormat::BC6hRgbUfloat => Some(DxgiFormat::BC6H_UF16),
368        ImageFormat::BC6hRgbSfloat => Some(DxgiFormat::BC6H_SF16),
369        ImageFormat::BC7RgbaUnorm => Some(DxgiFormat::BC7_UNorm),
370        ImageFormat::BC7RgbaUnormSrgb => Some(DxgiFormat::BC7_UNorm_sRGB),
371        ImageFormat::R8Unorm => Some(DxgiFormat::R8_UNorm),
372        ImageFormat::R8Snorm => Some(DxgiFormat::R8_SNorm),
373        ImageFormat::Rg8Unorm => Some(DxgiFormat::R8G8_UNorm),
374        ImageFormat::Rg8Snorm => Some(DxgiFormat::R8G8_SNorm),
375        ImageFormat::Rgba8Unorm => Some(DxgiFormat::R8G8B8A8_UNorm),
376        ImageFormat::Rgba8UnormSrgb => Some(DxgiFormat::R8G8B8A8_UNorm_sRGB),
377        ImageFormat::Rgba16Float => Some(DxgiFormat::R16G16B16A16_Float),
378        ImageFormat::Rgba32Float => Some(DxgiFormat::R32G32B32A32_Float),
379        ImageFormat::Bgra8Unorm => Some(DxgiFormat::B8G8R8A8_UNorm),
380        ImageFormat::Bgra8UnormSrgb => Some(DxgiFormat::B8G8R8A8_UNorm_sRGB),
381        ImageFormat::Bgra4Unorm => Some(DxgiFormat::B4G4R4A4_UNorm),
382        ImageFormat::Bgr8Unorm => None,
383        ImageFormat::R16Unorm => Some(DxgiFormat::R16_UNorm),
384        ImageFormat::R16Snorm => Some(DxgiFormat::R16_SNorm),
385        ImageFormat::Rg16Unorm => Some(DxgiFormat::R16G16_UNorm),
386        ImageFormat::Rg16Snorm => Some(DxgiFormat::R16G16_SNorm),
387        ImageFormat::Rgba16Unorm => Some(DxgiFormat::R16G16B16A16_UNorm),
388        ImageFormat::Rgba16Snorm => Some(DxgiFormat::R16G16B16A16_SNorm),
389        ImageFormat::Rg16Float => Some(DxgiFormat::R16G16_Float),
390        ImageFormat::Rg32Float => Some(DxgiFormat::R32G32_Float),
391        ImageFormat::R16Float => Some(DxgiFormat::R16_Float),
392        ImageFormat::R32Float => Some(DxgiFormat::R32_Float),
393        ImageFormat::Rgba8Snorm => Some(DxgiFormat::R8G8B8A8_SNorm),
394        ImageFormat::Rgb32Float => Some(DxgiFormat::R32G32B32_Float),
395        ImageFormat::Bgr5A1Unorm => Some(DxgiFormat::B5G5R5A1_UNorm),
396    }
397}
398
399#[cfg(test)]
400mod tests {
401    use super::*;
402
403    use strum::IntoEnumIterator;
404
405    #[test]
406    fn dds_to_from_surface() {
407        for image_format in ImageFormat::iter() {
408            let data = vec![0u8; 4 * 4 * 6 * image_format.block_size_in_bytes()];
409            let surface = Surface {
410                width: 4,
411                height: 4,
412                depth: 1,
413                layers: 1,
414                mipmaps: 1,
415                image_format,
416                data: data.as_slice(),
417            };
418            assert_eq!(
419                surface,
420                Surface::from_dds(&surface.to_dds().unwrap()).unwrap()
421            );
422        }
423    }
424
425    #[test]
426    fn dds_to_from_surface_cube() {
427        for image_format in ImageFormat::iter() {
428            let data = vec![0u8; 4 * 4 * 6 * image_format.block_size_in_bytes()];
429            let surface = Surface {
430                width: 4,
431                height: 4,
432                depth: 1,
433                layers: 6,
434                mipmaps: 1,
435                image_format,
436                data: data.as_slice(),
437            };
438            assert_eq!(
439                surface,
440                Surface::from_dds(&surface.to_dds().unwrap()).unwrap()
441            );
442        }
443    }
444}