image/codecs/
dds.rs

1//!  Decoding of DDS images
2//!
3//!  DDS (DirectDraw Surface) is a container format for storing DXT (S3TC) compressed images.
4//!
5//!  # Related Links
6//!  * <https://docs.microsoft.com/en-us/windows/win32/direct3ddds/dx-graphics-dds-pguide> - Description of the DDS format.
7
8use std::io::Read;
9use std::{error, fmt};
10
11use byteorder_lite::{LittleEndian, ReadBytesExt};
12
13#[allow(deprecated)]
14use crate::codecs::dxt::{DxtDecoder, DxtVariant};
15use crate::color::ColorType;
16use crate::error::{
17    DecodingError, ImageError, ImageFormatHint, ImageResult, UnsupportedError, UnsupportedErrorKind,
18};
19use crate::image::{ImageDecoder, ImageFormat};
20
21/// Errors that can occur during decoding and parsing a DDS image
22#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
23#[allow(clippy::enum_variant_names)]
24enum DecoderError {
25    /// Wrong DDS channel width
26    PixelFormatSizeInvalid(u32),
27    /// Wrong DDS header size
28    HeaderSizeInvalid(u32),
29    /// Wrong DDS header flags
30    HeaderFlagsInvalid(u32),
31
32    /// Invalid DXGI format in DX10 header
33    DxgiFormatInvalid(u32),
34    /// Invalid resource dimension
35    ResourceDimensionInvalid(u32),
36    /// Invalid flags in DX10 header
37    Dx10FlagsInvalid(u32),
38    /// Invalid array size in DX10 header
39    Dx10ArraySizeInvalid(u32),
40
41    /// DDS "DDS " signature invalid or missing
42    DdsSignatureInvalid,
43}
44
45impl fmt::Display for DecoderError {
46    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47        match self {
48            DecoderError::PixelFormatSizeInvalid(s) => {
49                f.write_fmt(format_args!("Invalid DDS PixelFormat size: {s}"))
50            }
51            DecoderError::HeaderSizeInvalid(s) => {
52                f.write_fmt(format_args!("Invalid DDS header size: {s}"))
53            }
54            DecoderError::HeaderFlagsInvalid(fs) => {
55                f.write_fmt(format_args!("Invalid DDS header flags: {fs:#010X}"))
56            }
57            DecoderError::DxgiFormatInvalid(df) => {
58                f.write_fmt(format_args!("Invalid DDS DXGI format: {df}"))
59            }
60            DecoderError::ResourceDimensionInvalid(d) => {
61                f.write_fmt(format_args!("Invalid DDS resource dimension: {d}"))
62            }
63            DecoderError::Dx10FlagsInvalid(fs) => {
64                f.write_fmt(format_args!("Invalid DDS DX10 header flags: {fs:#010X}"))
65            }
66            DecoderError::Dx10ArraySizeInvalid(s) => {
67                f.write_fmt(format_args!("Invalid DDS DX10 array size: {s}"))
68            }
69            DecoderError::DdsSignatureInvalid => f.write_str("DDS signature not found"),
70        }
71    }
72}
73
74impl From<DecoderError> for ImageError {
75    fn from(e: DecoderError) -> ImageError {
76        ImageError::Decoding(DecodingError::new(ImageFormat::Dds.into(), e))
77    }
78}
79
80impl error::Error for DecoderError {}
81
82/// Header used by DDS image files
83#[derive(Debug)]
84struct Header {
85    _flags: u32,
86    height: u32,
87    width: u32,
88    _pitch_or_linear_size: u32,
89    _depth: u32,
90    _mipmap_count: u32,
91    pixel_format: PixelFormat,
92    _caps: u32,
93    _caps2: u32,
94}
95
96/// Extended DX10 header used by some DDS image files
97#[derive(Debug)]
98struct DX10Header {
99    dxgi_format: u32,
100    resource_dimension: u32,
101    misc_flag: u32,
102    array_size: u32,
103    misc_flags_2: u32,
104}
105
106/// DDS pixel format
107#[derive(Debug)]
108struct PixelFormat {
109    flags: u32,
110    fourcc: [u8; 4],
111    _rgb_bit_count: u32,
112    _r_bit_mask: u32,
113    _g_bit_mask: u32,
114    _b_bit_mask: u32,
115    _a_bit_mask: u32,
116}
117
118impl PixelFormat {
119    fn from_reader(r: &mut dyn Read) -> ImageResult<Self> {
120        let size = r.read_u32::<LittleEndian>()?;
121        if size != 32 {
122            return Err(DecoderError::PixelFormatSizeInvalid(size).into());
123        }
124
125        Ok(Self {
126            flags: r.read_u32::<LittleEndian>()?,
127            fourcc: {
128                let mut v = [0; 4];
129                r.read_exact(&mut v)?;
130                v
131            },
132            _rgb_bit_count: r.read_u32::<LittleEndian>()?,
133            _r_bit_mask: r.read_u32::<LittleEndian>()?,
134            _g_bit_mask: r.read_u32::<LittleEndian>()?,
135            _b_bit_mask: r.read_u32::<LittleEndian>()?,
136            _a_bit_mask: r.read_u32::<LittleEndian>()?,
137        })
138    }
139}
140
141impl Header {
142    fn from_reader(r: &mut dyn Read) -> ImageResult<Self> {
143        let size = r.read_u32::<LittleEndian>()?;
144        if size != 124 {
145            return Err(DecoderError::HeaderSizeInvalid(size).into());
146        }
147
148        const REQUIRED_FLAGS: u32 = 0x1 | 0x2 | 0x4 | 0x1000;
149        const VALID_FLAGS: u32 = 0x1 | 0x2 | 0x4 | 0x8 | 0x1000 | 0x20000 | 0x80000 | 0x0080_0000;
150        let flags = r.read_u32::<LittleEndian>()?;
151        if flags & (REQUIRED_FLAGS | !VALID_FLAGS) != REQUIRED_FLAGS {
152            return Err(DecoderError::HeaderFlagsInvalid(flags).into());
153        }
154
155        let height = r.read_u32::<LittleEndian>()?;
156        let width = r.read_u32::<LittleEndian>()?;
157        let pitch_or_linear_size = r.read_u32::<LittleEndian>()?;
158        let depth = r.read_u32::<LittleEndian>()?;
159        let mipmap_count = r.read_u32::<LittleEndian>()?;
160        // Skip `dwReserved1`
161        {
162            let mut skipped = [0; 4 * 11];
163            r.read_exact(&mut skipped)?;
164        }
165        let pixel_format = PixelFormat::from_reader(r)?;
166        let caps = r.read_u32::<LittleEndian>()?;
167        let caps2 = r.read_u32::<LittleEndian>()?;
168        // Skip `dwCaps3`, `dwCaps4`, `dwReserved2` (unused)
169        {
170            let mut skipped = [0; 4 + 4 + 4];
171            r.read_exact(&mut skipped)?;
172        }
173
174        Ok(Self {
175            _flags: flags,
176            height,
177            width,
178            _pitch_or_linear_size: pitch_or_linear_size,
179            _depth: depth,
180            _mipmap_count: mipmap_count,
181            pixel_format,
182            _caps: caps,
183            _caps2: caps2,
184        })
185    }
186}
187
188impl DX10Header {
189    fn from_reader(r: &mut dyn Read) -> ImageResult<Self> {
190        let dxgi_format = r.read_u32::<LittleEndian>()?;
191        let resource_dimension = r.read_u32::<LittleEndian>()?;
192        let misc_flag = r.read_u32::<LittleEndian>()?;
193        let array_size = r.read_u32::<LittleEndian>()?;
194        let misc_flags_2 = r.read_u32::<LittleEndian>()?;
195
196        let dx10_header = Self {
197            dxgi_format,
198            resource_dimension,
199            misc_flag,
200            array_size,
201            misc_flags_2,
202        };
203        dx10_header.validate()?;
204
205        Ok(dx10_header)
206    }
207
208    fn validate(&self) -> Result<(), ImageError> {
209        // Note: see https://docs.microsoft.com/en-us/windows/win32/direct3ddds/dds-header-dxt10 for info on valid values
210        if self.dxgi_format > 132 {
211            // Invalid format
212            return Err(DecoderError::DxgiFormatInvalid(self.dxgi_format).into());
213        }
214
215        if self.resource_dimension < 2 || self.resource_dimension > 4 {
216            // Invalid dimension
217            // Only 1D (2), 2D (3) and 3D (4) resource dimensions are allowed
218            return Err(DecoderError::ResourceDimensionInvalid(self.resource_dimension).into());
219        }
220
221        if self.misc_flag != 0x0 && self.misc_flag != 0x4 {
222            // Invalid flag
223            // Only no (0x0) and DDS_RESOURCE_MISC_TEXTURECUBE (0x4) flags are allowed
224            return Err(DecoderError::Dx10FlagsInvalid(self.misc_flag).into());
225        }
226
227        if self.resource_dimension == 4 && self.array_size != 1 {
228            // Invalid array size
229            // 3D textures (resource dimension == 4) must have an array size of 1
230            return Err(DecoderError::Dx10ArraySizeInvalid(self.array_size).into());
231        }
232
233        if self.misc_flags_2 > 0x4 {
234            // Invalid alpha flags
235            return Err(DecoderError::Dx10FlagsInvalid(self.misc_flags_2).into());
236        }
237
238        Ok(())
239    }
240}
241
242/// The representation of a DDS decoder
243pub struct DdsDecoder<R: Read> {
244    #[allow(deprecated)]
245    inner: DxtDecoder<R>,
246}
247
248impl<R: Read> DdsDecoder<R> {
249    /// Create a new decoder that decodes from the stream `r`
250    pub fn new(mut r: R) -> ImageResult<Self> {
251        let mut magic = [0; 4];
252        r.read_exact(&mut magic)?;
253        if magic != b"DDS "[..] {
254            return Err(DecoderError::DdsSignatureInvalid.into());
255        }
256
257        let header = Header::from_reader(&mut r)?;
258
259        if header.pixel_format.flags & 0x4 != 0 {
260            #[allow(deprecated)]
261            let variant = match &header.pixel_format.fourcc {
262                b"DXT1" => DxtVariant::DXT1,
263                b"DXT3" => DxtVariant::DXT3,
264                b"DXT5" => DxtVariant::DXT5,
265                b"DX10" => {
266                    let dx10_header = DX10Header::from_reader(&mut r)?;
267                    // Format equivalents were taken from https://docs.microsoft.com/en-us/windows/win32/direct3d11/texture-block-compression-in-direct3d-11
268                    // The enum integer values were taken from https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format
269                    // DXT1 represents the different BC1 variants, DTX3 represents the different BC2 variants and DTX5 represents the different BC3 variants
270                    match dx10_header.dxgi_format {
271                        70..=72 => DxtVariant::DXT1, // DXGI_FORMAT_BC1_TYPELESS, DXGI_FORMAT_BC1_UNORM or DXGI_FORMAT_BC1_UNORM_SRGB
272                        73..=75 => DxtVariant::DXT3, // DXGI_FORMAT_BC2_TYPELESS, DXGI_FORMAT_BC2_UNORM or DXGI_FORMAT_BC2_UNORM_SRGB
273                        76..=78 => DxtVariant::DXT5, // DXGI_FORMAT_BC3_TYPELESS, DXGI_FORMAT_BC3_UNORM or DXGI_FORMAT_BC3_UNORM_SRGB
274                        _ => {
275                            return Err(ImageError::Unsupported(
276                                UnsupportedError::from_format_and_kind(
277                                    ImageFormat::Dds.into(),
278                                    UnsupportedErrorKind::GenericFeature(format!(
279                                        "DDS DXGI Format {}",
280                                        dx10_header.dxgi_format
281                                    )),
282                                ),
283                            ))
284                        }
285                    }
286                }
287                fourcc => {
288                    return Err(ImageError::Unsupported(
289                        UnsupportedError::from_format_and_kind(
290                            ImageFormat::Dds.into(),
291                            UnsupportedErrorKind::GenericFeature(format!("DDS FourCC {fourcc:?}")),
292                        ),
293                    ))
294                }
295            };
296
297            #[allow(deprecated)]
298            let bytes_per_pixel = variant.color_type().bytes_per_pixel();
299
300            if crate::utils::check_dimension_overflow(header.width, header.height, bytes_per_pixel)
301            {
302                return Err(ImageError::Unsupported(
303                    UnsupportedError::from_format_and_kind(
304                        ImageFormat::Dds.into(),
305                        UnsupportedErrorKind::GenericFeature(format!(
306                            "Image dimensions ({}x{}) are too large",
307                            header.width, header.height
308                        )),
309                    ),
310                ));
311            }
312
313            #[allow(deprecated)]
314            let inner = DxtDecoder::new(r, header.width, header.height, variant)?;
315            Ok(Self { inner })
316        } else {
317            // For now, supports only DXT variants
318            Err(ImageError::Unsupported(
319                UnsupportedError::from_format_and_kind(
320                    ImageFormat::Dds.into(),
321                    UnsupportedErrorKind::Format(ImageFormatHint::Name("DDS".to_string())),
322                ),
323            ))
324        }
325    }
326}
327
328impl<R: Read> ImageDecoder for DdsDecoder<R> {
329    fn dimensions(&self) -> (u32, u32) {
330        self.inner.dimensions()
331    }
332
333    fn color_type(&self) -> ColorType {
334        self.inner.color_type()
335    }
336
337    fn read_image(self, buf: &mut [u8]) -> ImageResult<()> {
338        self.inner.read_image(buf)
339    }
340
341    fn read_image_boxed(self: Box<Self>, buf: &mut [u8]) -> ImageResult<()> {
342        (*self).read_image(buf)
343    }
344}
345
346#[cfg(test)]
347mod test {
348    use super::*;
349
350    #[test]
351    fn dimension_overflow() {
352        // A DXT1 header set to 0xFFFF_FFFC width and height (the highest u32%4 == 0)
353        let header = [
354            0x44, 0x44, 0x53, 0x20, 0x7C, 0x0, 0x0, 0x0, 0x7, 0x10, 0x8, 0x0, 0xFC, 0xFF, 0xFF,
355            0xFF, 0xFC, 0xFF, 0xFF, 0xFF, 0x0, 0xC0, 0x12, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0,
356            0x0, 0x49, 0x4D, 0x41, 0x47, 0x45, 0x4D, 0x41, 0x47, 0x49, 0x43, 0x4B, 0x0, 0x0, 0x0,
357            0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
358            0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20, 0x0, 0x0, 0x0,
359            0x4, 0x0, 0x0, 0x0, 0x44, 0x58, 0x54, 0x31, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
360            0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0,
361            0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
362        ];
363
364        assert!(DdsDecoder::new(&header[..]).is_err());
365    }
366}