Skip to main content

image_extras/
dds.rs

1//! Decoding and encoding DDS images
2//!
3//! DDS (DirectDraw Surface) is a container format for storing uncompressed and
4//! BCn/DXT (S3TC) compressed images for use in graphics applications.
5//!
6//! # Related Links
7//!
8//! * <https://docs.microsoft.com/en-us/windows/win32/direct3ddds/dx-graphics-dds-pguide> - Description of the DDS format.
9//! * <https://microsoft.github.io/DirectX-Specs/d3d/archive/D3D11_3_FunctionalSpec.htm> - Direct3D 11.3 Functional Specification
10
11use std::io::{Read, Seek};
12
13use dds::header::ParseOptions;
14use dds::{
15    Channels, ColorFormat, DataLayout, Decoder, Format, ImageViewMut, Offset, Size,
16    TextureArrayKind,
17};
18
19use image::error::{
20    DecodingError, ImageError, ImageFormatHint, ImageResult, LimitError, LimitErrorKind,
21    UnsupportedError, UnsupportedErrorKind,
22};
23use image::{ColorType, ExtendedColorType, ImageDecoder, ImageDecoderRect};
24
25/// DDS decoder.
26///
27/// This decoder supports decoding DDS files with a single texture, including
28/// cube maps. Texture arrays and volumes are not supported.
29///
30/// It's possible to set the color type the image is decoded as using
31/// [`DdsDecoder::set_color_type`].
32pub struct DdsDecoder<R> {
33    inner: Decoder<R>,
34    is_cubemap: bool,
35    size: Size,
36    color: SupportedColor,
37}
38
39impl<R: Read> DdsDecoder<R> {
40    /// Create a new decoder that decodes from the stream `r`
41    pub fn new(r: R) -> ImageResult<Self> {
42        let options = ParseOptions::new_permissive(None);
43        let decoder = Decoder::new_with_options(r, &options).map_err(to_image_error)?;
44        let layout = decoder.layout();
45
46        // We only support DDS files with:
47        // - A single main image with any number of mipmaps
48        // - A texture array of length 1 representing a cube map
49        match &layout {
50            DataLayout::Volume(_) => {
51                return Err(ImageError::Decoding(DecodingError::new(
52                    format_hint(),
53                    "DDS volume textures are not supported for decoding",
54                )))
55            }
56            DataLayout::TextureArray(texture_array) => {
57                let supported_length = match texture_array.kind() {
58                    TextureArrayKind::Textures => 1,
59                    TextureArrayKind::CubeMaps => 6,
60                    TextureArrayKind::PartialCubeMap(cube_map_faces) => cube_map_faces.count(),
61                };
62                if texture_array.len() != supported_length as usize {
63                    return Err(ImageError::Decoding(DecodingError::new(
64                        format_hint(),
65                        "DDS texture arrays are not supported for decoding",
66                    )));
67                }
68            }
69            DataLayout::Texture(_) => {}
70        }
71
72        let mut size = decoder.main_size();
73        let mut color = decoder.native_color();
74        let is_cubemap = layout.is_cube_map();
75
76        // all cube map faces are read as one RGBA image
77        if is_cubemap {
78            if let (Some(width), Some(height)) =
79                (size.width.checked_mul(4), size.height.checked_mul(3))
80            {
81                size.width = width;
82                size.height = height;
83                color.channels = Channels::Rgba;
84            } else {
85                return Err(ImageError::Decoding(DecodingError::new(
86                    format_hint(),
87                    "DDS cube map faces are too large to decode",
88                )));
89            }
90        }
91
92        Ok(DdsDecoder {
93            inner: decoder,
94            is_cubemap,
95            size,
96            color: SupportedColor::from_dds_widen(color),
97        })
98    }
99
100    /// Set the color type for the decoder.
101    ///
102    /// The DDS decoder supports decoding images not just in their native color
103    /// format, but any user-defined color format. This is useful for decoding
104    /// images that do not cleanly fit into the native formats. E.g. the DDS
105    /// format `B5G6R5_UNORM` is decoded as [`ColorType::Rgb8`] by default, but
106    /// you may want to decode it as [`ColorType::Rgb32F`] instead to avoid the
107    /// rounding error when converting to `u8`. Similarly, your application may
108    /// only support 8-bit images, while the DDS file is in a 16/32-bit format.
109    /// Decoding directly into the final color type is more efficient than
110    /// decoding into the native format and then converting.
111    ///
112    /// # Errors
113    ///
114    /// Currently, [`ColorType::La8`] and [`ColorType::La16`] are not supported
115    /// for decoding DDS files. If these color types (or other unsupported types)
116    /// are provided, this function will return [`ImageError::Unsupported`] with
117    /// [`UnsupportedErrorKind::Color`].
118    pub fn set_color_type(&mut self, color: ColorType) -> ImageResult<()> {
119        let Some(supported_color) = SupportedColor::from_image_exact(color) else {
120            return Err(ImageError::Unsupported(
121                UnsupportedError::from_format_and_kind(
122                    format_hint(),
123                    UnsupportedErrorKind::Color(color.into()),
124                ),
125            ));
126        };
127        self.color = supported_color;
128        Ok(())
129    }
130}
131
132impl<R: Read + Seek> ImageDecoder for DdsDecoder<R> {
133    fn dimensions(&self) -> (u32, u32) {
134        (self.size.width, self.size.height)
135    }
136
137    fn color_type(&self) -> ColorType {
138        self.color.image
139    }
140
141    fn original_color_type(&self) -> ExtendedColorType {
142        match self.inner.format() {
143            Format::R1_UNORM => ExtendedColorType::L1,
144            Format::B4G4R4A4_UNORM | Format::A4B4G4R4_UNORM => ExtendedColorType::Rgba4,
145            Format::A8_UNORM => ExtendedColorType::A8,
146            _ => SupportedColor::from_dds_widen(self.inner.native_color())
147                .image
148                .into(),
149        }
150    }
151
152    fn set_limits(&mut self, limits: image::Limits) -> ImageResult<()> {
153        limits.check_dimensions(self.size.width, self.size.height)?;
154
155        if let Some(max_alloc) = limits.max_alloc {
156            self.inner.options.memory_limit = max_alloc.try_into().unwrap_or(usize::MAX);
157        }
158
159        Ok(())
160    }
161
162    #[track_caller]
163    fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()> {
164        let color = self.color.dds;
165        let size = self.size;
166
167        assert_eq!(
168            buf.len(),
169            color.buffer_size(size).unwrap(),
170            "Buffer len does not match for {:?} and {:?}",
171            size,
172            color
173        );
174
175        let image = ImageViewMut::new(buf, size, color).expect("Invalid buffer length");
176
177        if self.is_cubemap {
178            self.inner.read_cube_map(image).map_err(to_image_error)?;
179        } else {
180            self.inner.read_surface(image).map_err(to_image_error)?;
181        }
182
183        Ok(())
184    }
185
186    fn read_image_boxed(self: Box<Self>, buf: &mut [u8]) -> ImageResult<()> {
187        (*self).read_image(buf)
188    }
189}
190
191impl<R: Read + Seek> ImageDecoderRect for DdsDecoder<R> {
192    fn read_rect(
193        &mut self,
194        x: u32,
195        y: u32,
196        width: u32,
197        height: u32,
198        buf: &mut [u8],
199        row_pitch: usize,
200    ) -> ImageResult<()> {
201        // reading rectangles is not supported for cube maps
202        if self.is_cubemap {
203            return Err(ImageError::Decoding(DecodingError::new(
204                format_hint(),
205                "read_rect is not supported for DDS cubemaps",
206            )));
207        }
208
209        let Some(view) =
210            ImageViewMut::new_with(buf, row_pitch, Size::new(width, height), self.color.dds)
211        else {
212            return Err(ImageError::Decoding(DecodingError::new(
213                format_hint(),
214                "Invalid buffer length for reading rect",
215            )));
216        };
217
218        self.inner
219            .read_surface_rect(view, Offset::new(x, y))
220            .map_err(to_image_error)?;
221        self.inner
222            .rewind_to_previous_surface()
223            .map_err(to_image_error)?;
224
225        Ok(())
226    }
227}
228
229/// A color type supported by both the `image` and `dds` crates.
230struct SupportedColor {
231    image: ColorType,
232    dds: ColorFormat,
233}
234impl SupportedColor {
235    fn new(image: ColorType, dds: ColorFormat) -> SupportedColor {
236        debug_assert_eq!(image.bytes_per_pixel(), dds.bytes_per_pixel());
237        debug_assert_eq!(image.channel_count(), dds.channels.count());
238
239        SupportedColor { image, dds }
240    }
241
242    /// Returns a supported color format that exactly matches the given `image`
243    /// color format.
244    fn from_image_exact(color: ColorType) -> Option<Self> {
245        fn to_color_format_exact(color: ColorType) -> Option<ColorFormat> {
246            match color {
247                ColorType::L8 => Some(ColorFormat::GRAYSCALE_U8),
248                ColorType::Rgb8 => Some(ColorFormat::RGB_U8),
249                ColorType::Rgba8 => Some(ColorFormat::RGBA_U8),
250                ColorType::L16 => Some(ColorFormat::GRAYSCALE_U16),
251                ColorType::Rgb16 => Some(ColorFormat::RGB_U16),
252                ColorType::Rgba16 => Some(ColorFormat::RGBA_U16),
253                ColorType::Rgb32F => Some(ColorFormat::RGB_F32),
254                ColorType::Rgba32F => Some(ColorFormat::RGBA_F32),
255                _ => None,
256            }
257        }
258
259        to_color_format_exact(color).map(|dds| Self::new(color, dds))
260    }
261    /// Returns a supported color format that is the narrowest superset of the
262    /// given `image` color format.
263    fn from_dds_widen(color: ColorFormat) -> Self {
264        match color {
265            // exact
266            ColorFormat::RGB_U8 => Self::new(ColorType::Rgb8, color),
267            ColorFormat::RGB_U16 => Self::new(ColorType::Rgb16, color),
268            ColorFormat::RGB_F32 => Self::new(ColorType::Rgb32F, color),
269            ColorFormat::GRAYSCALE_U8 => Self::new(ColorType::L8, color),
270            ColorFormat::GRAYSCALE_U16 => Self::new(ColorType::L16, color),
271            ColorFormat::RGBA_U8 => Self::new(ColorType::Rgba8, color),
272            ColorFormat::RGBA_U16 => Self::new(ColorType::Rgba16, color),
273            ColorFormat::RGBA_F32 => Self::new(ColorType::Rgba32F, color),
274            // widen
275            ColorFormat::ALPHA_U8 => Self::new(ColorType::Rgba8, ColorFormat::RGBA_U8),
276            ColorFormat::ALPHA_U16 => Self::new(ColorType::Rgba16, ColorFormat::RGBA_U16),
277            ColorFormat::ALPHA_F32 => Self::new(ColorType::Rgba32F, ColorFormat::RGBA_F32),
278            ColorFormat::GRAYSCALE_F32 => Self::new(ColorType::Rgb32F, ColorFormat::RGB_F32),
279        }
280    }
281}
282
283fn to_image_error(e: dds::DecodingError) -> ImageError {
284    match e {
285        dds::DecodingError::Io(e) => ImageError::IoError(e),
286        dds::DecodingError::MemoryLimitExceeded => {
287            ImageError::Limits(LimitError::from_kind(LimitErrorKind::InsufficientMemory))
288        }
289        _ => ImageError::Decoding(DecodingError::new(format_hint(), e)),
290    }
291}
292
293fn format_hint() -> ImageFormatHint {
294    ImageFormatHint::Name("DDS".into())
295}