1pub use super::locator::MipmapLocator;
2pub use super::version::BlpVersion;
3use std::fmt;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
11pub enum BlpContentTag {
12 Jpeg,
14 Direct,
16}
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
20pub struct UnknownContent(u32);
21
22impl fmt::Display for UnknownContent {
23 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
24 write!(f, "Unknown content field value: {}", self.0)
25 }
26}
27
28impl TryFrom<u32> for BlpContentTag {
29 type Error = UnknownContent;
30
31 fn try_from(val: u32) -> Result<BlpContentTag, Self::Error> {
32 match val {
33 0 => Ok(BlpContentTag::Jpeg),
34 1 => Ok(BlpContentTag::Direct),
35 _ => Err(UnknownContent(val)),
36 }
37 }
38}
39
40impl From<BlpContentTag> for u32 {
41 fn from(val: BlpContentTag) -> u32 {
42 match val {
43 BlpContentTag::Jpeg => 0,
44 BlpContentTag::Direct => 1,
45 }
46 }
47}
48
49#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
51pub struct BlpHeader {
52 pub version: BlpVersion,
54 pub content: BlpContentTag,
56 pub flags: BlpFlags,
58 pub width: u32,
60 pub height: u32,
62 pub mipmap_locator: MipmapLocator,
64}
65
66impl BlpHeader {
67 pub fn mipmaps_count(&self) -> usize {
69 if self.has_mipmaps() {
70 let width_n = (self.width as f32).log2() as usize;
71 let height_n = (self.height as f32).log2() as usize;
72 width_n.max(height_n)
73 } else {
74 0
75 }
76 }
77
78 pub fn has_mipmaps(&self) -> bool {
80 self.flags.has_mipmaps()
81 }
82
83 pub fn mipmap_size(&self, i: usize) -> (u32, u32) {
86 if i == 0 {
87 (self.width, self.height)
88 } else {
89 ((self.width >> i).max(1), (self.height >> i).max(1))
90 }
91 }
92
93 pub fn mipmap_pixels(&self, i: usize) -> u32 {
96 let (w, h) = self.mipmap_size(i);
97 w * h
98 }
99
100 pub fn alpha_bits(&self) -> u32 {
102 self.flags.alpha_bits()
103 }
104
105 pub fn alpha_type(&self) -> Option<AlphaType> {
107 self.flags.alpha_type()
108 }
109
110 pub fn validate_for_wow_version(&self, wow_version: WowVersion) -> Result<(), String> {
112 if let Some(alpha_type) = self.alpha_type()
113 && !alpha_type.is_supported_in_version(wow_version)
114 {
115 return Err(format!(
116 "Alpha type {alpha_type:?} not supported in WoW version {wow_version:?}"
117 ));
118 }
119 Ok(())
120 }
121
122 pub fn internal_mipmaps(&self) -> Option<([u32; 16], [u32; 16])> {
124 match self.mipmap_locator {
125 MipmapLocator::Internal { offsets, sizes } => Some((offsets, sizes)),
126 MipmapLocator::External => None,
127 }
128 }
129
130 pub fn size(version: BlpVersion) -> usize {
132 4 + 4 + 4 + 4 + 4 + if version < BlpVersion::Blp2 {8} else {0} + if version > BlpVersion::Blp0 {16*4*2} else {0} }
140}
141
142impl Default for BlpHeader {
143 fn default() -> Self {
144 BlpHeader {
145 version: BlpVersion::Blp1,
146 content: BlpContentTag::Jpeg,
147 flags: Default::default(),
148 width: 1,
149 height: 1,
150 mipmap_locator: Default::default(),
151 }
152 }
153}
154
155#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
158pub enum AlphaType {
159 None = 0,
161 OneBit = 1,
163 Enhanced = 7,
165 EightBit = 8,
167}
168
169impl std::fmt::Display for AlphaType {
170 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
171 match self {
172 AlphaType::None => write!(f, "None (0)"),
173 AlphaType::OneBit => write!(f, "1-bit (1)"),
174 AlphaType::Enhanced => write!(f, "Enhanced (7)"),
175 AlphaType::EightBit => write!(f, "8-bit (8)"),
176 }
177 }
178}
179
180impl AlphaType {
181 pub fn is_supported_in_version(self, wow_version: WowVersion) -> bool {
183 match self {
184 AlphaType::None | AlphaType::OneBit | AlphaType::EightBit => true,
185 AlphaType::Enhanced => wow_version >= WowVersion::TBC,
186 }
187 }
188
189 pub fn alpha_bits(self) -> u8 {
191 match self {
192 AlphaType::None => 0,
193 AlphaType::OneBit => 1,
194 AlphaType::Enhanced => 8, AlphaType::EightBit => 8,
196 }
197 }
198}
199
200#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
202pub struct UnknownAlphaType(u8);
203
204impl fmt::Display for UnknownAlphaType {
205 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
206 write!(f, "Unknown alpha_type field value: {}", self.0)
207 }
208}
209
210impl TryFrom<u8> for AlphaType {
211 type Error = UnknownAlphaType;
212
213 fn try_from(val: u8) -> Result<AlphaType, Self::Error> {
214 match val {
215 0 => Ok(AlphaType::None),
216 1 => Ok(AlphaType::OneBit),
217 7 => Ok(AlphaType::Enhanced),
218 8 => Ok(AlphaType::EightBit),
219 _ => Err(UnknownAlphaType(val)),
220 }
221 }
222}
223
224impl From<AlphaType> for u8 {
225 fn from(val: AlphaType) -> u8 {
226 val as u8
227 }
228}
229
230#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
232pub enum WowVersion {
233 Vanilla,
235 TBC,
237 WotLK,
239 Cataclysm,
241 MoP,
243}
244
245#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
247pub enum Compression {
248 Jpeg, Raw1,
252 Raw3,
254 Dxtc,
256}
257
258#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
260pub struct UnknownCompression(u8);
261
262impl fmt::Display for UnknownCompression {
263 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
264 write!(f, "Unknown compression field value: {}", self.0)
265 }
266}
267
268impl TryFrom<u8> for Compression {
269 type Error = UnknownCompression;
270
271 fn try_from(val: u8) -> Result<Compression, Self::Error> {
272 match val {
273 0 => Ok(Compression::Jpeg),
274 1 => Ok(Compression::Raw1),
275 2 => Ok(Compression::Dxtc),
276 3 => Ok(Compression::Raw3),
277 _ => Err(UnknownCompression(val)),
278 }
279 }
280}
281
282impl From<Compression> for u8 {
283 fn from(val: Compression) -> u8 {
284 match val {
285 Compression::Jpeg => 0,
286 Compression::Raw1 => 1,
287 Compression::Dxtc => 2,
288 Compression::Raw3 => 3,
289 }
290 }
291}
292
293#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
295pub enum BlpFlags {
296 Blp2 {
298 compression: Compression,
300 alpha_bits: u8,
302 alpha_type: AlphaType,
304 has_mipmaps: u8,
306 },
307 Old {
309 alpha_bits: u32,
311 extra: u32, has_mipmaps: u32, },
316}
317
318impl Default for BlpFlags {
319 fn default() -> Self {
320 BlpFlags::Old {
321 alpha_bits: 8,
322 extra: 8,
323 has_mipmaps: 1,
324 }
325 }
326}
327
328impl BlpFlags {
329 pub fn has_mipmaps(&self) -> bool {
331 match self {
332 BlpFlags::Blp2 { has_mipmaps, .. } => *has_mipmaps != 0,
333 BlpFlags::Old { has_mipmaps, .. } => *has_mipmaps != 0,
334 }
335 }
336
337 pub fn alpha_bits(&self) -> u32 {
339 match self {
340 BlpFlags::Blp2 { compression, .. } if *compression == Compression::Raw3 => 4,
341 BlpFlags::Blp2 { alpha_bits, .. } => *alpha_bits as u32,
342 BlpFlags::Old { alpha_bits, .. } => *alpha_bits,
343 }
344 }
345
346 pub fn alpha_type(&self) -> Option<AlphaType> {
348 match self {
349 BlpFlags::Blp2 { alpha_type, .. } => Some(*alpha_type),
350 BlpFlags::Old { .. } => None,
351 }
352 }
353}
354
355#[cfg(test)]
356mod tests {
357 use super::*;
358
359 #[test]
360 fn test_alpha_type_conversion() {
361 assert_eq!(AlphaType::None as u8, 0);
362 assert_eq!(AlphaType::OneBit as u8, 1);
363 assert_eq!(AlphaType::Enhanced as u8, 7);
364 assert_eq!(AlphaType::EightBit as u8, 8);
365
366 assert_eq!(AlphaType::try_from(0).unwrap(), AlphaType::None);
368 assert_eq!(AlphaType::try_from(1).unwrap(), AlphaType::OneBit);
369 assert_eq!(AlphaType::try_from(7).unwrap(), AlphaType::Enhanced);
370 assert_eq!(AlphaType::try_from(8).unwrap(), AlphaType::EightBit);
371
372 assert!(AlphaType::try_from(9).is_err());
374 }
375
376 #[test]
377 fn test_alpha_type_wow_version_support() {
378 assert!(AlphaType::None.is_supported_in_version(WowVersion::Vanilla));
380 assert!(AlphaType::OneBit.is_supported_in_version(WowVersion::Vanilla));
381 assert!(!AlphaType::Enhanced.is_supported_in_version(WowVersion::Vanilla));
382 assert!(AlphaType::EightBit.is_supported_in_version(WowVersion::Vanilla));
383
384 assert!(AlphaType::Enhanced.is_supported_in_version(WowVersion::TBC));
386 assert!(AlphaType::Enhanced.is_supported_in_version(WowVersion::Cataclysm));
387 }
388
389 #[test]
390 fn test_mipmap_count() {
391 let header = BlpHeader {
392 width: 512,
393 height: 256,
394 version: BlpVersion::Blp0,
395 ..Default::default()
396 };
397 assert_eq!(header.mipmaps_count(), 9);
398
399 let header = BlpHeader {
400 width: 512,
401 height: 256,
402 version: BlpVersion::Blp1,
403 ..Default::default()
404 };
405 assert_eq!(header.mipmaps_count(), 9);
406
407 let header = BlpHeader {
408 width: 1,
409 height: 4,
410 ..Default::default()
411 };
412 assert_eq!(header.mipmaps_count(), 2);
413
414 let header = BlpHeader {
415 width: 4,
416 height: 7,
417 ..Default::default()
418 };
419 assert_eq!(header.mipmaps_count(), 2);
420
421 let header = BlpHeader {
422 width: 768,
423 height: 128,
424 ..Default::default()
425 };
426 assert_eq!(header.mipmaps_count(), 9);
427 }
428}