image_blp/types/
header.rs

1pub use super::locator::MipmapLocator;
2pub use super::version::BlpVersion;
3use std::fmt;
4
5/// The content field determines how the image data is stored. CONTENT_JPEG
6/// uses non-standard JPEG (JFIF) file compression of BGRA colour component
7/// values rather than the usual Y′CbCr color component values.
8/// CONTENT_DIRECT refers to a variety of storage formats which can be
9/// directly read as pixel values.
10#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
11pub enum BlpContentTag {
12    Jpeg,
13    Direct,
14}
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
17pub struct UnknownContent(u32);
18
19impl fmt::Display for UnknownContent {
20    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
21        write!(f, "Unknown content field value: {}", self.0)
22    }
23}
24
25impl TryFrom<u32> for BlpContentTag {
26    type Error = UnknownContent;
27
28    fn try_from(val: u32) -> Result<BlpContentTag, Self::Error> {
29        match val {
30            0 => Ok(BlpContentTag::Jpeg),
31            1 => Ok(BlpContentTag::Direct),
32            _ => Err(UnknownContent(val)),
33        }
34    }
35}
36
37impl From<BlpContentTag> for u32 {
38    fn from(val: BlpContentTag) -> u32 {
39        match val {
40            BlpContentTag::Jpeg => 0,
41            BlpContentTag::Direct => 1,
42        }
43    }
44}
45
46#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
47pub struct BlpHeader {
48    pub version: BlpVersion,
49    pub content: BlpContentTag,
50    pub flags: BlpFlags,
51    pub width: u32,
52    pub height: u32,
53    pub mipmap_locator: MipmapLocator,
54}
55
56impl BlpHeader {
57    /// Calculate needed count of mipmaps for the defined size
58    pub fn mipmaps_count(&self) -> usize {
59        if self.has_mipmaps() {
60            let width_n = (self.width as f32).log2() as usize;
61            let height_n = (self.height as f32).log2() as usize;
62            width_n.max(height_n)
63        } else {
64            0
65        }
66    }
67
68    /// Returns 'true' if the header defines that the image has mipmaps
69    pub fn has_mipmaps(&self) -> bool {
70        self.flags.has_mipmaps()
71    }
72
73    /// Return expected size of mipmap for the given mipmap level.
74    /// 0 level means original image.
75    pub fn mipmap_size(&self, i: usize) -> (u32, u32) {
76        if i == 0 {
77            (self.width, self.height)
78        } else {
79            ((self.width >> i).max(1), (self.height >> i).max(1))
80        }
81    }
82
83    /// Return expected count of pixels in mipmap at the level i.
84    /// 0 level means original image.
85    pub fn mipmap_pixels(&self, i: usize) -> u32 {
86        let (w, h) = self.mipmap_size(i);
87        w * h
88    }
89
90    /// Return alpha bits count in encoding
91    pub fn alpha_bits(&self) -> u32 {
92        self.flags.alpha_bits()
93    }
94
95    /// Return offsets and sizes of internal mipmaps. For external returns [None]
96    pub fn internal_mipmaps(&self) -> Option<([u32; 16], [u32; 16])> {
97        match self.mipmap_locator {
98            MipmapLocator::Internal { offsets, sizes } => Some((offsets, sizes)),
99            MipmapLocator::External => None,
100        }
101    }
102
103    /// Get size of header in bytes. Doesn't count jpeg header or color map.
104    pub fn size(version: BlpVersion) -> usize {
105        4 // magic
106        + 4 // content
107        + 4 // flags or alpha_bits
108        + 4 // width 
109        + 4 // height
110        + if version < BlpVersion::Blp2 {8} else {0} // extra and has_mipmaps
111        + if version > BlpVersion::Blp0 {16*4*2} else {0} // mipmap locator
112    }
113}
114
115impl Default for BlpHeader {
116    fn default() -> Self {
117        BlpHeader {
118            version: BlpVersion::Blp1,
119            content: BlpContentTag::Jpeg,
120            flags: Default::default(),
121            width: 1,
122            height: 1,
123            mipmap_locator: Default::default(),
124        }
125    }
126}
127
128#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
129pub enum Compression {
130    Jpeg, // adhoc compression, never met in BLP2
131    Raw1,
132    Raw3,
133    Dxtc,
134}
135
136#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
137pub struct UnknownCompression(u8);
138
139impl fmt::Display for UnknownCompression {
140    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
141        write!(f, "Unknown compression field value: {}", self.0)
142    }
143}
144
145impl TryFrom<u8> for Compression {
146    type Error = UnknownCompression;
147
148    fn try_from(val: u8) -> Result<Compression, Self::Error> {
149        match val {
150            0 => Ok(Compression::Jpeg),
151            1 => Ok(Compression::Raw1),
152            2 => Ok(Compression::Dxtc),
153            3 => Ok(Compression::Raw3),
154            _ => Err(UnknownCompression(val)),
155        }
156    }
157}
158
159impl From<Compression> for u8 {
160    fn from(val: Compression) -> u8 {
161        match val {
162            Compression::Jpeg => 0,
163            Compression::Raw1 => 1,
164            Compression::Dxtc => 2,
165            Compression::Raw3 => 3,
166        }
167    }
168}
169
170/// Part of header that depends on the version
171#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
172pub enum BlpFlags {
173    /// For version >= 2
174    Blp2 {
175        compression: Compression,
176        alpha_bits: u8, // 0, 1, 7, or 8
177        alpha_type: u8, // 0, 1, 7, or 8
178        has_mipmaps: u8,
179    },
180    /// For version < 2
181    Old {
182        alpha_bits: u32,
183        extra: u32,       // no purpose, default is 5
184        has_mipmaps: u32, // boolean
185    },
186}
187
188impl Default for BlpFlags {
189    fn default() -> Self {
190        BlpFlags::Old {
191            alpha_bits: 8,
192            extra: 8,
193            has_mipmaps: 1,
194        }
195    }
196}
197
198impl BlpFlags {
199    /// Returns 'true' if the header defines that the image has mipmaps
200    pub fn has_mipmaps(&self) -> bool {
201        match self {
202            BlpFlags::Blp2 { has_mipmaps, .. } => *has_mipmaps != 0,
203            BlpFlags::Old { has_mipmaps, .. } => *has_mipmaps != 0,
204        }
205    }
206
207    /// Get count of bits alpha channel is encoded in content
208    pub fn alpha_bits(&self) -> u32 {
209        match self {
210            BlpFlags::Blp2 { compression, .. } if *compression == Compression::Raw3 => 4,
211            BlpFlags::Blp2 { alpha_bits, .. } => *alpha_bits as u32,
212            BlpFlags::Old { alpha_bits, .. } => *alpha_bits,
213        }
214    }
215}
216
217#[cfg(test)]
218mod tests {
219    use super::*;
220
221    #[test]
222    fn test_mipmap_count() {
223        let header = BlpHeader {
224            width: 512,
225            height: 256,
226            version: BlpVersion::Blp0,
227            ..Default::default()
228        };
229        assert_eq!(header.mipmaps_count(), 9);
230
231        let header = BlpHeader {
232            width: 512,
233            height: 256,
234            version: BlpVersion::Blp1,
235            ..Default::default()
236        };
237        assert_eq!(header.mipmaps_count(), 9);
238
239        let header = BlpHeader {
240            width: 1,
241            height: 4,
242            ..Default::default()
243        };
244        assert_eq!(header.mipmaps_count(), 2);
245
246        let header = BlpHeader {
247            width: 4,
248            height: 7,
249            ..Default::default()
250        };
251        assert_eq!(header.mipmaps_count(), 2);
252
253        let header = BlpHeader {
254            width: 768,
255            height: 128,
256            ..Default::default()
257        };
258        assert_eq!(header.mipmaps_count(), 9);
259    }
260}