Skip to main content

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