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(format:ImageFormat::Dds.into(), err: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: u32 = 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: [u8; 4] = [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}
367