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