Coverage Report

Created: 2026-01-19 07:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/image/src/codecs/dds.rs
Line
Count
Source
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
8
use std::io::Read;
9
use std::{error, fmt};
10
11
use byteorder_lite::{LittleEndian, ReadBytesExt};
12
13
#[allow(deprecated)]
14
use crate::codecs::dxt::{DxtDecoder, DxtVariant};
15
use crate::color::ColorType;
16
use crate::error::{
17
    DecodingError, ImageError, ImageFormatHint, ImageResult, UnsupportedError, UnsupportedErrorKind,
18
};
19
use crate::{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)]
24
enum 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
45
impl fmt::Display for DecoderError {
46
0
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47
0
        match self {
48
0
            DecoderError::PixelFormatSizeInvalid(s) => {
49
0
                f.write_fmt(format_args!("Invalid DDS PixelFormat size: {s}"))
50
            }
51
0
            DecoderError::HeaderSizeInvalid(s) => {
52
0
                f.write_fmt(format_args!("Invalid DDS header size: {s}"))
53
            }
54
0
            DecoderError::HeaderFlagsInvalid(fs) => {
55
0
                f.write_fmt(format_args!("Invalid DDS header flags: {fs:#010X}"))
56
            }
57
0
            DecoderError::DxgiFormatInvalid(df) => {
58
0
                f.write_fmt(format_args!("Invalid DDS DXGI format: {df}"))
59
            }
60
0
            DecoderError::ResourceDimensionInvalid(d) => {
61
0
                f.write_fmt(format_args!("Invalid DDS resource dimension: {d}"))
62
            }
63
0
            DecoderError::Dx10FlagsInvalid(fs) => {
64
0
                f.write_fmt(format_args!("Invalid DDS DX10 header flags: {fs:#010X}"))
65
            }
66
0
            DecoderError::Dx10ArraySizeInvalid(s) => {
67
0
                f.write_fmt(format_args!("Invalid DDS DX10 array size: {s}"))
68
            }
69
0
            DecoderError::DdsSignatureInvalid => f.write_str("DDS signature not found"),
70
        }
71
0
    }
72
}
73
74
impl From<DecoderError> for ImageError {
75
0
    fn from(e: DecoderError) -> ImageError {
76
0
        ImageError::Decoding(DecodingError::new(ImageFormat::Dds.into(), e))
77
0
    }
78
}
79
80
impl error::Error for DecoderError {}
81
82
/// Header used by DDS image files
83
#[derive(Debug)]
84
struct 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)]
98
struct 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)]
108
struct 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
118
impl PixelFormat {
119
0
    fn from_reader(r: &mut dyn Read) -> ImageResult<Self> {
120
0
        let size = r.read_u32::<LittleEndian>()?;
121
0
        if size != 32 {
122
0
            return Err(DecoderError::PixelFormatSizeInvalid(size).into());
123
0
        }
124
125
        Ok(Self {
126
0
            flags: r.read_u32::<LittleEndian>()?,
127
            fourcc: {
128
0
                let mut v = [0; 4];
129
0
                r.read_exact(&mut v)?;
130
0
                v
131
            },
132
0
            _rgb_bit_count: r.read_u32::<LittleEndian>()?,
133
0
            _r_bit_mask: r.read_u32::<LittleEndian>()?,
134
0
            _g_bit_mask: r.read_u32::<LittleEndian>()?,
135
0
            _b_bit_mask: r.read_u32::<LittleEndian>()?,
136
0
            _a_bit_mask: r.read_u32::<LittleEndian>()?,
137
        })
138
0
    }
139
}
140
141
impl Header {
142
0
    fn from_reader(r: &mut dyn Read) -> ImageResult<Self> {
143
0
        let size = r.read_u32::<LittleEndian>()?;
144
0
        if size != 124 {
145
0
            return Err(DecoderError::HeaderSizeInvalid(size).into());
146
0
        }
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
0
        let flags = r.read_u32::<LittleEndian>()?;
151
0
        if flags & (REQUIRED_FLAGS | !VALID_FLAGS) != REQUIRED_FLAGS {
152
0
            return Err(DecoderError::HeaderFlagsInvalid(flags).into());
153
0
        }
154
155
0
        let height = r.read_u32::<LittleEndian>()?;
156
0
        let width = r.read_u32::<LittleEndian>()?;
157
0
        let pitch_or_linear_size = r.read_u32::<LittleEndian>()?;
158
0
        let depth = r.read_u32::<LittleEndian>()?;
159
0
        let mipmap_count = r.read_u32::<LittleEndian>()?;
160
        // Skip `dwReserved1`
161
        {
162
0
            let mut skipped = [0; 4 * 11];
163
0
            r.read_exact(&mut skipped)?;
164
        }
165
0
        let pixel_format = PixelFormat::from_reader(r)?;
166
0
        let caps = r.read_u32::<LittleEndian>()?;
167
0
        let caps2 = r.read_u32::<LittleEndian>()?;
168
        // Skip `dwCaps3`, `dwCaps4`, `dwReserved2` (unused)
169
        {
170
0
            let mut skipped = [0; 4 + 4 + 4];
171
0
            r.read_exact(&mut skipped)?;
172
        }
173
174
0
        Ok(Self {
175
0
            _flags: flags,
176
0
            height,
177
0
            width,
178
0
            _pitch_or_linear_size: pitch_or_linear_size,
179
0
            _depth: depth,
180
0
            _mipmap_count: mipmap_count,
181
0
            pixel_format,
182
0
            _caps: caps,
183
0
            _caps2: caps2,
184
0
        })
185
0
    }
186
}
187
188
impl DX10Header {
189
0
    fn from_reader(r: &mut dyn Read) -> ImageResult<Self> {
190
0
        let dxgi_format = r.read_u32::<LittleEndian>()?;
191
0
        let resource_dimension = r.read_u32::<LittleEndian>()?;
192
0
        let misc_flag = r.read_u32::<LittleEndian>()?;
193
0
        let array_size = r.read_u32::<LittleEndian>()?;
194
0
        let misc_flags_2 = r.read_u32::<LittleEndian>()?;
195
196
0
        let dx10_header = Self {
197
0
            dxgi_format,
198
0
            resource_dimension,
199
0
            misc_flag,
200
0
            array_size,
201
0
            misc_flags_2,
202
0
        };
203
0
        dx10_header.validate()?;
204
205
0
        Ok(dx10_header)
206
0
    }
207
208
0
    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
0
        if self.dxgi_format > 132 {
211
            // Invalid format
212
0
            return Err(DecoderError::DxgiFormatInvalid(self.dxgi_format).into());
213
0
        }
214
215
0
        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
0
            return Err(DecoderError::ResourceDimensionInvalid(self.resource_dimension).into());
219
0
        }
220
221
0
        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
0
            return Err(DecoderError::Dx10FlagsInvalid(self.misc_flag).into());
225
0
        }
226
227
0
        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
0
            return Err(DecoderError::Dx10ArraySizeInvalid(self.array_size).into());
231
0
        }
232
233
0
        if self.misc_flags_2 > 0x4 {
234
            // Invalid alpha flags
235
0
            return Err(DecoderError::Dx10FlagsInvalid(self.misc_flags_2).into());
236
0
        }
237
238
0
        Ok(())
239
0
    }
240
}
241
242
/// The representation of a DDS decoder
243
pub struct DdsDecoder<R: Read> {
244
    #[allow(deprecated)]
245
    inner: DxtDecoder<R>,
246
}
247
248
impl<R: Read> DdsDecoder<R> {
249
    /// Create a new decoder that decodes from the stream `r`
250
0
    pub fn new(mut r: R) -> ImageResult<Self> {
251
0
        let mut magic = [0; 4];
252
0
        r.read_exact(&mut magic)?;
253
0
        if magic != b"DDS "[..] {
254
0
            return Err(DecoderError::DdsSignatureInvalid.into());
255
0
        }
256
257
0
        let header = Header::from_reader(&mut r)?;
258
259
0
        if header.pixel_format.flags & 0x4 != 0 {
260
            #[allow(deprecated)]
261
0
            let variant = match &header.pixel_format.fourcc {
262
0
                b"DXT1" => DxtVariant::DXT1,
263
0
                b"DXT3" => DxtVariant::DXT3,
264
0
                b"DXT5" => DxtVariant::DXT5,
265
                b"DX10" => {
266
0
                    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
0
                    match dx10_header.dxgi_format {
271
0
                        70..=72 => DxtVariant::DXT1, // DXGI_FORMAT_BC1_TYPELESS, DXGI_FORMAT_BC1_UNORM or DXGI_FORMAT_BC1_UNORM_SRGB
272
0
                        73..=75 => DxtVariant::DXT3, // DXGI_FORMAT_BC2_TYPELESS, DXGI_FORMAT_BC2_UNORM or DXGI_FORMAT_BC2_UNORM_SRGB
273
0
                        76..=78 => DxtVariant::DXT5, // DXGI_FORMAT_BC3_TYPELESS, DXGI_FORMAT_BC3_UNORM or DXGI_FORMAT_BC3_UNORM_SRGB
274
                        _ => {
275
0
                            return Err(ImageError::Unsupported(
276
0
                                UnsupportedError::from_format_and_kind(
277
0
                                    ImageFormat::Dds.into(),
278
0
                                    UnsupportedErrorKind::GenericFeature(format!(
279
0
                                        "DDS DXGI Format {}",
280
0
                                        dx10_header.dxgi_format
281
0
                                    )),
282
0
                                ),
283
0
                            ))
284
                        }
285
                    }
286
                }
287
0
                fourcc => {
288
0
                    return Err(ImageError::Unsupported(
289
0
                        UnsupportedError::from_format_and_kind(
290
0
                            ImageFormat::Dds.into(),
291
0
                            UnsupportedErrorKind::GenericFeature(format!("DDS FourCC {fourcc:?}")),
292
0
                        ),
293
0
                    ))
294
                }
295
            };
296
297
            #[allow(deprecated)]
298
0
            let bytes_per_pixel = variant.color_type().bytes_per_pixel();
299
300
0
            if crate::utils::check_dimension_overflow(header.width, header.height, bytes_per_pixel)
301
            {
302
0
                return Err(ImageError::Unsupported(
303
0
                    UnsupportedError::from_format_and_kind(
304
0
                        ImageFormat::Dds.into(),
305
0
                        UnsupportedErrorKind::GenericFeature(format!(
306
0
                            "Image dimensions ({}x{}) are too large",
307
0
                            header.width, header.height
308
0
                        )),
309
0
                    ),
310
0
                ));
311
0
            }
312
313
            #[allow(deprecated)]
314
0
            let inner = DxtDecoder::new(r, header.width, header.height, variant)?;
315
0
            Ok(Self { inner })
316
        } else {
317
            // For now, supports only DXT variants
318
0
            Err(ImageError::Unsupported(
319
0
                UnsupportedError::from_format_and_kind(
320
0
                    ImageFormat::Dds.into(),
321
0
                    UnsupportedErrorKind::Format(ImageFormatHint::Name("DDS".to_string())),
322
0
                ),
323
0
            ))
324
        }
325
0
    }
326
}
327
328
impl<R: Read> ImageDecoder for DdsDecoder<R> {
329
0
    fn dimensions(&self) -> (u32, u32) {
330
0
        self.inner.dimensions()
331
0
    }
332
333
0
    fn color_type(&self) -> ColorType {
334
0
        self.inner.color_type()
335
0
    }
336
337
0
    fn read_image(self, buf: &mut [u8]) -> ImageResult<()> {
338
0
        self.inner.read_image(buf)
339
0
    }
340
341
0
    fn read_image_boxed(self: Box<Self>, buf: &mut [u8]) -> ImageResult<()> {
342
0
        (*self).read_image(buf)
343
0
    }
344
}
345
346
#[cfg(test)]
347
mod 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
}