pub use super::locator::MipmapLocator;
pub use super::version::BlpVersion;
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum BlpContentTag {
Jpeg,
Direct,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct UnknownContent(u32);
impl fmt::Display for UnknownContent {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Unknown content field value: {}", self.0)
}
}
impl TryFrom<u32> for BlpContentTag {
type Error = UnknownContent;
fn try_from(val: u32) -> Result<BlpContentTag, Self::Error> {
match val {
0 => Ok(BlpContentTag::Jpeg),
1 => Ok(BlpContentTag::Direct),
_ => Err(UnknownContent(val)),
}
}
}
impl From<BlpContentTag> for u32 {
fn from(val: BlpContentTag) -> u32 {
match val {
BlpContentTag::Jpeg => 0,
BlpContentTag::Direct => 1,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct BlpHeader {
pub version: BlpVersion,
pub content: BlpContentTag,
pub flags: BlpFlags,
pub width: u32,
pub height: u32,
pub mipmap_locator: MipmapLocator,
}
impl BlpHeader {
pub fn mipmaps_count(&self) -> usize {
if self.has_mipmaps() {
let width_n = (self.width as f32).log2() as usize;
let height_n = (self.height as f32).log2() as usize;
width_n.max(height_n)
} else {
0
}
}
pub fn has_mipmaps(&self) -> bool {
self.flags.has_mipmaps()
}
pub fn mipmap_size(&self, i: usize) -> (u32, u32) {
if i == 0 {
(self.width, self.height)
} else {
((self.width >> i).max(1), (self.height >> i).max(1))
}
}
pub fn mipmap_pixels(&self, i: usize) -> u32 {
let (w, h) = self.mipmap_size(i);
w * h
}
pub fn alpha_bits(&self) -> u32 {
self.flags.alpha_bits()
}
pub fn alpha_type(&self) -> Option<AlphaType> {
self.flags.alpha_type()
}
pub fn validate_for_wow_version(&self, wow_version: WowVersion) -> Result<(), String> {
if let Some(alpha_type) = self.alpha_type()
&& !alpha_type.is_supported_in_version(wow_version)
{
return Err(format!(
"Alpha type {alpha_type:?} not supported in WoW version {wow_version:?}"
));
}
Ok(())
}
pub fn internal_mipmaps(&self) -> Option<([u32; 16], [u32; 16])> {
match self.mipmap_locator {
MipmapLocator::Internal { offsets, sizes } => Some((offsets, sizes)),
MipmapLocator::External => None,
}
}
pub fn size(version: BlpVersion) -> usize {
4 + 4 + 4 + 4 + 4 + if version < BlpVersion::Blp2 {8} else {0} + if version > BlpVersion::Blp0 {16*4*2} else {0} }
}
impl Default for BlpHeader {
fn default() -> Self {
BlpHeader {
version: BlpVersion::Blp1,
content: BlpContentTag::Jpeg,
flags: Default::default(),
width: 1,
height: 1,
mipmap_locator: Default::default(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum AlphaType {
None = 0,
OneBit = 1,
Enhanced = 7,
EightBit = 8,
}
impl std::fmt::Display for AlphaType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
AlphaType::None => write!(f, "None (0)"),
AlphaType::OneBit => write!(f, "1-bit (1)"),
AlphaType::Enhanced => write!(f, "Enhanced (7)"),
AlphaType::EightBit => write!(f, "8-bit (8)"),
}
}
}
impl AlphaType {
pub fn is_supported_in_version(self, wow_version: WowVersion) -> bool {
match self {
AlphaType::None | AlphaType::OneBit | AlphaType::EightBit => true,
AlphaType::Enhanced => wow_version >= WowVersion::TBC,
}
}
pub fn alpha_bits(self) -> u8 {
match self {
AlphaType::None => 0,
AlphaType::OneBit => 1,
AlphaType::Enhanced => 8, AlphaType::EightBit => 8,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct UnknownAlphaType(u8);
impl fmt::Display for UnknownAlphaType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Unknown alpha_type field value: {}", self.0)
}
}
impl TryFrom<u8> for AlphaType {
type Error = UnknownAlphaType;
fn try_from(val: u8) -> Result<AlphaType, Self::Error> {
match val {
0 => Ok(AlphaType::None),
1 => Ok(AlphaType::OneBit),
7 => Ok(AlphaType::Enhanced),
8 => Ok(AlphaType::EightBit),
_ => Err(UnknownAlphaType(val)),
}
}
}
impl From<AlphaType> for u8 {
fn from(val: AlphaType) -> u8 {
val as u8
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum WowVersion {
Vanilla,
TBC,
WotLK,
Cataclysm,
MoP,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Compression {
Jpeg, Raw1,
Raw3,
Dxtc,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct UnknownCompression(u8);
impl fmt::Display for UnknownCompression {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Unknown compression field value: {}", self.0)
}
}
impl TryFrom<u8> for Compression {
type Error = UnknownCompression;
fn try_from(val: u8) -> Result<Compression, Self::Error> {
match val {
0 => Ok(Compression::Jpeg),
1 => Ok(Compression::Raw1),
2 => Ok(Compression::Dxtc),
3 => Ok(Compression::Raw3),
_ => Err(UnknownCompression(val)),
}
}
}
impl From<Compression> for u8 {
fn from(val: Compression) -> u8 {
match val {
Compression::Jpeg => 0,
Compression::Raw1 => 1,
Compression::Dxtc => 2,
Compression::Raw3 => 3,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum BlpFlags {
Blp2 {
compression: Compression,
alpha_bits: u8,
alpha_type: AlphaType,
has_mipmaps: u8,
},
Old {
alpha_bits: u32,
extra: u32, has_mipmaps: u32, },
}
impl Default for BlpFlags {
fn default() -> Self {
BlpFlags::Old {
alpha_bits: 8,
extra: 8,
has_mipmaps: 1,
}
}
}
impl BlpFlags {
pub fn has_mipmaps(&self) -> bool {
match self {
BlpFlags::Blp2 { has_mipmaps, .. } => *has_mipmaps != 0,
BlpFlags::Old { has_mipmaps, .. } => *has_mipmaps != 0,
}
}
pub fn alpha_bits(&self) -> u32 {
match self {
BlpFlags::Blp2 { compression, .. } if *compression == Compression::Raw3 => 4,
BlpFlags::Blp2 { alpha_bits, .. } => *alpha_bits as u32,
BlpFlags::Old { alpha_bits, .. } => *alpha_bits,
}
}
pub fn alpha_type(&self) -> Option<AlphaType> {
match self {
BlpFlags::Blp2 { alpha_type, .. } => Some(*alpha_type),
BlpFlags::Old { .. } => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_alpha_type_conversion() {
assert_eq!(AlphaType::None as u8, 0);
assert_eq!(AlphaType::OneBit as u8, 1);
assert_eq!(AlphaType::Enhanced as u8, 7);
assert_eq!(AlphaType::EightBit as u8, 8);
assert_eq!(AlphaType::try_from(0).unwrap(), AlphaType::None);
assert_eq!(AlphaType::try_from(1).unwrap(), AlphaType::OneBit);
assert_eq!(AlphaType::try_from(7).unwrap(), AlphaType::Enhanced);
assert_eq!(AlphaType::try_from(8).unwrap(), AlphaType::EightBit);
assert!(AlphaType::try_from(9).is_err());
}
#[test]
fn test_alpha_type_wow_version_support() {
assert!(AlphaType::None.is_supported_in_version(WowVersion::Vanilla));
assert!(AlphaType::OneBit.is_supported_in_version(WowVersion::Vanilla));
assert!(!AlphaType::Enhanced.is_supported_in_version(WowVersion::Vanilla));
assert!(AlphaType::EightBit.is_supported_in_version(WowVersion::Vanilla));
assert!(AlphaType::Enhanced.is_supported_in_version(WowVersion::TBC));
assert!(AlphaType::Enhanced.is_supported_in_version(WowVersion::Cataclysm));
}
#[test]
fn test_mipmap_count() {
let header = BlpHeader {
width: 512,
height: 256,
version: BlpVersion::Blp0,
..Default::default()
};
assert_eq!(header.mipmaps_count(), 9);
let header = BlpHeader {
width: 512,
height: 256,
version: BlpVersion::Blp1,
..Default::default()
};
assert_eq!(header.mipmaps_count(), 9);
let header = BlpHeader {
width: 1,
height: 4,
..Default::default()
};
assert_eq!(header.mipmaps_count(), 2);
let header = BlpHeader {
width: 4,
height: 7,
..Default::default()
};
assert_eq!(header.mipmaps_count(), 2);
let header = BlpHeader {
width: 768,
height: 128,
..Default::default()
};
assert_eq!(header.mipmaps_count(), 9);
}
}