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}