Skip to main content

ai_imagesize/container/
dds.rs

1use no_std_io::io::{BufRead, Seek, SeekFrom};
2
3use crate::{
4    util::{read_u32, Endian},
5    ImageResult, ImageSize,
6};
7
8/// Compression formats for DDS containers
9///
10/// DirectDraw Surface (DDS) files can contain various compressed and uncompressed formats.
11/// This enum identifies the specific compression algorithm used within the DDS container.
12#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
13pub enum DdsCompression {
14    /// Block Compression 1 (DXT1) - RGB, 1-bit alpha
15    Bc1,
16    /// Block Compression 2 (DXT3) - RGBA with explicit alpha
17    Bc2,
18    /// Block Compression 3 (DXT5) - RGBA with interpolated alpha
19    Bc3,
20    /// Block Compression 4 (ATI1) - Single channel
21    Bc4,
22    /// Block Compression 5 (ATI2) - Two channel (RG)
23    Bc5,
24    /// Block Compression 6H - HDR format
25    Bc6h,
26    /// Block Compression 7 - High quality RGB/RGBA
27    Bc7,
28    /// Uncompressed RGBA32
29    Rgba32,
30    /// Uncompressed RGB24
31    Rgb24,
32    /// Other/Unknown DDS format
33    Unknown,
34}
35
36pub fn size<R: BufRead + Seek>(reader: &mut R) -> ImageResult<ImageSize> {
37    reader.seek(SeekFrom::Start(12))?;
38    let height = read_u32(reader, &Endian::Little)? as usize;
39    let width = read_u32(reader, &Endian::Little)? as usize;
40    Ok(ImageSize { width, height })
41}
42
43pub fn matches(header: &[u8]) -> bool {
44    header.starts_with(b"DDS ")
45}
46
47pub fn detect_compression<R: BufRead + Seek>(reader: &mut R) -> ImageResult<DdsCompression> {
48    // DDS header structure:
49    // Signature: "DDS " (4 bytes)
50    // Header size: 124 (4 bytes)
51    // Flags: various flags (4 bytes)
52    // Height, Width: (4 bytes each)
53    // PitchOrLinearSize: (4 bytes)
54    // Depth: (4 bytes)
55    // MipMapCount: (4 bytes)
56    // Reserved1: (44 bytes)
57    // Pixel Format: (32 bytes)
58    //   - Size: 32 (4 bytes)
59    //   - Flags: (4 bytes)
60    //   - FourCC: (4 bytes) - this tells us the compression format
61    //   - RGBBitCount: (4 bytes)
62    //   - RBitMask, GBitMask, BBitMask, ABitMask: (16 bytes)
63
64    reader.seek(SeekFrom::Start(84))?; // Skip to pixel format FourCC
65    let mut fourcc = [0u8; 4];
66    reader.read_exact(&mut fourcc)?;
67
68    let compression = match &fourcc {
69        b"DXT1" => DdsCompression::Bc1,
70        b"DXT3" => DdsCompression::Bc2,
71        b"DXT5" => DdsCompression::Bc3,
72        b"ATI1" | b"BC4U" | b"BC4S" => DdsCompression::Bc4,
73        b"ATI2" | b"BC5U" | b"BC5S" => DdsCompression::Bc5,
74        b"BC6H" => DdsCompression::Bc6h,
75        b"BC7U" | b"BC7L" => DdsCompression::Bc7,
76        b"DX10" => {
77            // DX10 extended header starts right after the main DDS header (128 bytes from start)
78            // Skip the rest of the pixel format, caps, and reserved2 fields first
79            reader.seek(SeekFrom::Start(128))?; // Jump to DX10 extended header
80            let dxgi_format = read_u32(reader, &Endian::Little)?;
81            match dxgi_format {
82                95 => DdsCompression::Bc6h, // DXGI_FORMAT_BC6H_UF16
83                96 => DdsCompression::Bc6h, // DXGI_FORMAT_BC6H_SF16
84                98 => DdsCompression::Bc7,  // DXGI_FORMAT_BC7_UNORM
85                99 => DdsCompression::Bc7,  // DXGI_FORMAT_BC7_UNORM_SRGB
86                _ => DdsCompression::Unknown,
87            }
88        }
89        [0, 0, 0, 0] => {
90            // No FourCC, check if it's uncompressed
91            // We need to check the RGB bit count and masks
92            let rgb_bit_count = read_u32(reader, &Endian::Little)?;
93            match rgb_bit_count {
94                32 => DdsCompression::Rgba32,
95                24 => DdsCompression::Rgb24,
96                _ => DdsCompression::Unknown,
97            }
98        }
99        _ => DdsCompression::Unknown,
100    };
101
102    Ok(compression)
103}