wow_blp/types/
image.rs

1use super::direct::*;
2use super::header::*;
3use super::jpeg::*;
4pub use super::version::BlpVersion;
5
6/// Maximum width that BLP image can have due limitation
7/// of mipmaping storage.
8pub const BLP_MAX_WIDTH: u32 = 65535;
9/// Maximum height that BLP image can have due limitation
10/// of mipmaping storage.
11pub const BLP_MAX_HEIGHT: u32 = 65535;
12
13/// Parsed information from BLP file. The structure of the type
14/// strictly follows how the file is stored on the disk for
15/// easy encoding/decoding and further transformations.
16#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
17pub struct BlpImage {
18    /// File header containing metadata
19    pub header: BlpHeader,
20    /// Actual image data
21    pub content: BlpContent,
22}
23
24impl BlpImage {
25    /// Get total amount of images encoded in the content
26    pub fn image_count(&self) -> usize {
27        match &self.content {
28            BlpContent::Dxt1(v) => v.images.len(),
29            BlpContent::Dxt3(v) => v.images.len(),
30            BlpContent::Dxt5(v) => v.images.len(),
31            BlpContent::Raw1(v) => v.images.len(),
32            BlpContent::Raw3(v) => v.images.len(),
33            BlpContent::Jpeg(v) => v.images.len(),
34        }
35    }
36
37    /// If the image is encoded jpeg, return the content
38    pub fn content_jpeg(&self) -> Option<&BlpJpeg> {
39        self.content.jpeg()
40    }
41
42    /// If the image is direct encoded with BLP1 format, return the content
43    pub fn content_raw1(&self) -> Option<&BlpRaw1> {
44        self.content.raw1()
45    }
46
47    /// If the image is direct encoded with raw3 BLP2 format, return the content
48    pub fn content_raw3(&self) -> Option<&BlpRaw3> {
49        self.content.raw3()
50    }
51
52    /// If the image is DXT1 encoded, return the content
53    pub fn content_dxt1(&self) -> Option<&BlpDxtn> {
54        self.content.dxt1()
55    }
56
57    /// If the image is DXT3 encoded, return the content
58    pub fn content_dxt3(&self) -> Option<&BlpDxtn> {
59        self.content.dxt3()
60    }
61
62    /// If the image is DXT5 encoded, return the content
63    pub fn content_dxt5(&self) -> Option<&BlpDxtn> {
64        self.content.dxt5()
65    }
66
67    /// Get the compression type used for this BLP image
68    pub fn compression_type(&self) -> CompressionType {
69        match &self.content {
70            BlpContent::Jpeg(_) => CompressionType::Jpeg,
71            BlpContent::Raw1(_) => CompressionType::Raw1,
72            BlpContent::Raw3(_) => CompressionType::Raw3,
73            BlpContent::Dxt1(_) => CompressionType::Dxt1,
74            BlpContent::Dxt3(_) => CompressionType::Dxt3,
75            BlpContent::Dxt5(_) => CompressionType::Dxt5,
76        }
77    }
78
79    /// Get the alpha bit depth for this BLP image
80    pub fn alpha_bit_depth(&self) -> u8 {
81        self.header.alpha_bits() as u8
82    }
83
84    /// Find the best mipmap level for a target resolution.
85    /// Returns the mipmap level closest to the target size.
86    pub fn best_mipmap_for_size(&self, target_size: u32) -> usize {
87        let image_count = self.image_count();
88        if image_count == 0 {
89            return 0;
90        }
91
92        let mut best_level = 0;
93        let mut best_diff = u32::MAX;
94
95        for level in 0..image_count {
96            let (width, height) = self.header.mipmap_size(level);
97            let size = width.max(height);
98            let diff = size.abs_diff(target_size);
99
100            if diff < best_diff {
101                best_diff = diff;
102                best_level = level;
103            }
104        }
105
106        best_level
107    }
108
109    /// Get information about all mipmap levels
110    pub fn mipmap_info(&self) -> Vec<MipMapInfo> {
111        let mut info = Vec::new();
112
113        for level in 0..self.image_count() {
114            let (width, height) = self.header.mipmap_size(level);
115            let data_size = match &self.content {
116                BlpContent::Jpeg(jpeg) => jpeg.images.get(level).map(|img| img.len()).unwrap_or(0),
117                BlpContent::Raw1(raw) => raw
118                    .images
119                    .get(level)
120                    .map(|img| img.indexed_rgb.len() + img.indexed_alpha.len())
121                    .unwrap_or(0),
122                BlpContent::Raw3(raw) => raw
123                    .images
124                    .get(level)
125                    .map(|img| img.pixels.len() * 4)
126                    .unwrap_or(0),
127                BlpContent::Dxt1(dxt) => dxt
128                    .images
129                    .get(level)
130                    .map(|img| img.content.len())
131                    .unwrap_or(0),
132                BlpContent::Dxt3(dxt) => dxt
133                    .images
134                    .get(level)
135                    .map(|img| img.content.len())
136                    .unwrap_or(0),
137                BlpContent::Dxt5(dxt) => dxt
138                    .images
139                    .get(level)
140                    .map(|img| img.content.len())
141                    .unwrap_or(0),
142            };
143
144            info.push(MipMapInfo {
145                level,
146                width,
147                height,
148                data_size,
149                pixel_count: width * height,
150            });
151        }
152
153        info
154    }
155
156    /// Get total file size estimation (excluding external mipmaps)
157    pub fn estimated_file_size(&self) -> usize {
158        let header_size = BlpHeader::size(self.header.version);
159        let content_size = match &self.content {
160            BlpContent::Jpeg(jpeg) => {
161                jpeg.header.len() + jpeg.images.iter().map(|img| img.len()).sum::<usize>()
162            }
163            BlpContent::Raw1(raw) => {
164                raw.cmap.len() * 4 + // palette size
165                raw.images.iter().map(|img| {
166                    img.indexed_rgb.len() + img.indexed_alpha.len()
167                }).sum::<usize>()
168            }
169            BlpContent::Raw3(raw) => raw
170                .images
171                .iter()
172                .map(|img| img.pixels.len() * 4)
173                .sum::<usize>(),
174            BlpContent::Dxt1(dxt) => dxt
175                .images
176                .iter()
177                .map(|img| img.content.len())
178                .sum::<usize>(),
179            BlpContent::Dxt3(dxt) => dxt
180                .images
181                .iter()
182                .map(|img| img.content.len())
183                .sum::<usize>(),
184            BlpContent::Dxt5(dxt) => dxt
185                .images
186                .iter()
187                .map(|img| img.content.len())
188                .sum::<usize>(),
189        };
190
191        header_size + content_size
192    }
193
194    /// Get compression efficiency (uncompressed size vs compressed size)
195    pub fn compression_ratio(&self) -> f32 {
196        let uncompressed_size = self
197            .mipmap_info()
198            .iter()
199            .map(|info| info.width * info.height * 4) // RGBA
200            .sum::<u32>() as f32;
201
202        let compressed_size = self.estimated_file_size() as f32;
203
204        if compressed_size > 0.0 {
205            uncompressed_size / compressed_size
206        } else {
207            1.0
208        }
209    }
210}
211
212/// Information about a single mipmap level
213#[derive(Debug, Clone, PartialEq, Eq)]
214pub struct MipMapInfo {
215    /// Mipmap level (0 = original)
216    pub level: usize,
217    /// Width in pixels
218    pub width: u32,
219    /// Height in pixels
220    pub height: u32,
221    /// Size of compressed data in bytes
222    pub data_size: usize,
223    /// Total pixel count
224    pub pixel_count: u32,
225}
226
227/// Compression type enumeration for easy inspection
228#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
229pub enum CompressionType {
230    /// JPEG compressed image data
231    Jpeg,
232    /// Palettized 256-color format with alpha
233    Raw1,
234    /// Uncompressed RGBA format  
235    Raw3,
236    /// DXT1 compression (no alpha or 1-bit alpha)
237    Dxt1,
238    /// DXT3 compression (explicit alpha)
239    Dxt3,
240    /// DXT5 compression (interpolated alpha)
241    Dxt5,
242}
243
244/// Collects all possible content types with actual data
245#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
246pub enum BlpContent {
247    /// JPEG compressed image data
248    Jpeg(BlpJpeg),
249    /// Used with direct type for BLP0/BLP1 and raw compression in BLP2
250    Raw1(BlpRaw1),
251    /// Used with direct type for BLP2, encodes RGBA bitmap.
252    Raw3(BlpRaw3),
253    /// BLP2 DXT1 compression (no alpha)
254    Dxt1(BlpDxtn),
255    /// BLP2 DXT3 compression (with alpha)
256    Dxt3(BlpDxtn),
257    /// BLP2 DXT5 compression (with alpha)
258    Dxt5(BlpDxtn),
259}
260
261impl BlpContent {
262    /// Get the content tag type for this content
263    pub fn tag(&self) -> BlpContentTag {
264        match self {
265            BlpContent::Jpeg { .. } => BlpContentTag::Jpeg,
266            BlpContent::Raw1 { .. } => BlpContentTag::Direct,
267            BlpContent::Raw3 { .. } => BlpContentTag::Direct,
268            BlpContent::Dxt1 { .. } => BlpContentTag::Direct,
269            BlpContent::Dxt3 { .. } => BlpContentTag::Direct,
270            BlpContent::Dxt5 { .. } => BlpContentTag::Direct,
271        }
272    }
273
274    /// Get JPEG content if this is JPEG encoded
275    pub fn jpeg(&self) -> Option<&BlpJpeg> {
276        match self {
277            BlpContent::Jpeg(v) => Some(v),
278            _ => None,
279        }
280    }
281
282    /// Get RAW1 content if this is RAW1 encoded
283    pub fn raw1(&self) -> Option<&BlpRaw1> {
284        match self {
285            BlpContent::Raw1(v) => Some(v),
286            _ => None,
287        }
288    }
289
290    /// Get RAW3 content if this is RAW3 encoded
291    pub fn raw3(&self) -> Option<&BlpRaw3> {
292        match self {
293            BlpContent::Raw3(v) => Some(v),
294            _ => None,
295        }
296    }
297
298    /// Get DXT1 content if this is DXT1 encoded
299    pub fn dxt1(&self) -> Option<&BlpDxtn> {
300        match self {
301            BlpContent::Dxt1(v) => Some(v),
302            _ => None,
303        }
304    }
305
306    /// Get DXT3 content if this is DXT3 encoded
307    pub fn dxt3(&self) -> Option<&BlpDxtn> {
308        match self {
309            BlpContent::Dxt3(v) => Some(v),
310            _ => None,
311        }
312    }
313
314    /// Get DXT5 content if this is DXT5 encoded
315    pub fn dxt5(&self) -> Option<&BlpDxtn> {
316        match self {
317            BlpContent::Dxt5(v) => Some(v),
318            _ => None,
319        }
320    }
321}