Skip to main content

wow_blp/convert/
mod.rs

1mod dxtn;
2/// Conversion error types
3pub mod error;
4mod jpeg;
5mod mipmap;
6mod palette;
7mod raw1;
8mod raw3;
9
10use crate::types::*;
11use ::image::DynamicImage;
12pub use ::image::imageops::FilterType;
13use dxtn::*;
14pub use error::Error;
15use jpeg::*;
16use raw1::*;
17use raw3::*;
18use std::fmt;
19pub use texpresso::Algorithm as DxtAlgorithm;
20
21/// Convert from parsed raw BLP image to useful [DynamicImage]
22pub fn blp_to_image(image: &BlpImage, mipmap_level: usize) -> Result<DynamicImage, Error> {
23    match &image.content {
24        BlpContent::Raw1(content) => raw1_to_image(&image.header, content, mipmap_level),
25        BlpContent::Raw3(content) => raw3_to_image(&image.header, content, mipmap_level),
26        BlpContent::Jpeg(content) => jpeg_to_image(content, mipmap_level),
27        BlpContent::Dxt1(content) => dxtn_to_image(&image.header, content, mipmap_level),
28        BlpContent::Dxt3(content) => dxtn_to_image(&image.header, content, mipmap_level),
29        BlpContent::Dxt5(content) => dxtn_to_image(&image.header, content, mipmap_level),
30    }
31}
32
33/// A way to specify [image_to_blp] which BLP type you want to
34/// get in a result.
35#[derive(Clone, PartialEq, Eq)]
36pub enum BlpTarget {
37    /// BLP0 format variation. War3 RoC Beta builds. External
38    /// mipmaps.
39    Blp0(BlpOldFormat),
40    /// BLP1 format variation. War3 TFT usual textures. Internal
41    /// mipmaps.
42    Blp1(BlpOldFormat),
43    /// BLP2 format variation. WoW usual textures. Internal
44    /// mipmaps.
45    Blp2(Blp2Format),
46}
47
48impl Default for BlpTarget {
49    fn default() -> Self {
50        BlpTarget::Blp1(Default::default())
51    }
52}
53
54impl fmt::Display for BlpTarget {
55    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56        match self {
57            BlpTarget::Blp0(format) => write!(f, "BLP0 {format}"),
58            BlpTarget::Blp1(format) => write!(f, "BLP1 {format}"),
59            BlpTarget::Blp2(format) => write!(f, "BLP2 {format}"),
60        }
61    }
62}
63
64/// Encoding options for BLP0 and BLP1 formats.
65#[derive(Debug, Clone, PartialEq, Eq)]
66pub enum BlpOldFormat {
67    /// Paletted 256 colors image with/without alpha.  
68    Raw1 {
69        /// Alpha channel bit depth
70        alpha_bits: AlphaBits,
71    },
72    /// JPEG encoding with/without alpha.
73    Jpeg {
74        /// Whether the JPEG has an alpha channel
75        has_alpha: bool,
76    },
77}
78
79impl Default for BlpOldFormat {
80    fn default() -> Self {
81        BlpOldFormat::Jpeg { has_alpha: true }
82    }
83}
84
85impl fmt::Display for BlpOldFormat {
86    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87        match self {
88            BlpOldFormat::Raw1 { alpha_bits } => write!(f, "Palleted image with {alpha_bits}"),
89            BlpOldFormat::Jpeg { has_alpha } => {
90                if *has_alpha {
91                    write!(f, "Jpeg image with alpha")
92                } else {
93                    write!(f, "Jpeg image without alpha")
94                }
95            }
96        }
97    }
98}
99
100/// Allowed alpha bits values for Raw1 encoding
101#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
102pub enum AlphaBits {
103    /// No alpha channel. 0 bits.
104    NoAlpha,
105    /// 1 bit. Pixel is transparent or opaque.
106    Bit1,
107    /// 4 bits per pixel.
108    Bit4,
109    /// 8 bits per pixel.
110    #[default]
111    Bit8,
112}
113
114impl fmt::Display for AlphaBits {
115    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116        match self {
117            AlphaBits::NoAlpha => write!(f, "no alpha"),
118            AlphaBits::Bit1 => write!(f, "1 bit alpha"),
119            AlphaBits::Bit4 => write!(f, "4 bits alpha"),
120            AlphaBits::Bit8 => write!(f, "8 bits alpha"),
121        }
122    }
123}
124
125impl From<AlphaBits> for u32 {
126    fn from(value: AlphaBits) -> u32 {
127        match value {
128            AlphaBits::NoAlpha => 0,
129            AlphaBits::Bit1 => 1,
130            AlphaBits::Bit4 => 4,
131            AlphaBits::Bit8 => 8,
132        }
133    }
134}
135
136impl From<AlphaBits> for u8 {
137    fn from(value: AlphaBits) -> u8 {
138        match value {
139            AlphaBits::NoAlpha => 0,
140            AlphaBits::Bit1 => 1,
141            AlphaBits::Bit4 => 4,
142            AlphaBits::Bit8 => 8,
143        }
144    }
145}
146
147/// BLP2 format compression options.
148#[derive(Clone, PartialEq, Eq)]
149pub enum Blp2Format {
150    /// Paletted 256 colors image with/without alpha.  
151    Raw1 {
152        /// Alpha channel bit depth
153        alpha_bits: AlphaBits,
154    },
155    /// RGBA bitmap
156    Raw3,
157    /// JPEG encoded image. Although, it is never used in real files.
158    Jpeg {
159        /// Whether the JPEG has an alpha channel
160        has_alpha: bool,
161    },
162    /// ST3C compression, type with 1 bit alpha or 0 bit alpha.
163    Dxt1 {
164        /// Whether the texture has an alpha channel
165        has_alpha: bool,
166        /// Compression speed/quality setting
167        compress_algorithm: DxtAlgorithm,
168    },
169    /// ST3C compression, type with paletted alpha.
170    Dxt3 {
171        /// Whether the texture has an alpha channel
172        has_alpha: bool,
173        /// Compression speed/quality setting
174        compress_algorithm: DxtAlgorithm,
175    },
176    /// ST3C compression, type with interpolated alpha.
177    Dxt5 {
178        /// Whether the texture has an alpha channel
179        has_alpha: bool,
180        /// Compression speed/quality setting
181        compress_algorithm: DxtAlgorithm,
182    },
183}
184
185impl Default for Blp2Format {
186    fn default() -> Self {
187        Blp2Format::Dxt5 {
188            has_alpha: true,
189            compress_algorithm: Default::default(),
190        }
191    }
192}
193
194impl fmt::Display for Blp2Format {
195    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
196        match self {
197            Blp2Format::Raw1 { alpha_bits } => write!(f, "Palleted image with {alpha_bits}"),
198            Blp2Format::Raw3 => write!(f, "RGBA raw data"),
199            Blp2Format::Jpeg { has_alpha } => {
200                if *has_alpha {
201                    write!(f, "Jpeg image with alpha")
202                } else {
203                    write!(f, "Jpeg image without alpha")
204                }
205            }
206            Blp2Format::Dxt1 {
207                has_alpha,
208                compress_algorithm,
209            } => {
210                let compress_str = match *compress_algorithm {
211                    DxtAlgorithm::RangeFit => "fast/low quality",
212                    DxtAlgorithm::ClusterFit => "slow/high quality",
213                    DxtAlgorithm::IterativeClusterFit => "very slow/best quality",
214                };
215                if *has_alpha {
216                    write!(f, "DXT1 image with alpha and compression {compress_str}")
217                } else {
218                    write!(f, "DXT1 image without alpha and compression {compress_str}")
219                }
220            }
221            Blp2Format::Dxt3 {
222                has_alpha,
223                compress_algorithm,
224            } => {
225                let compress_str = match *compress_algorithm {
226                    DxtAlgorithm::RangeFit => "fast/low quality",
227                    DxtAlgorithm::ClusterFit => "slow/high quality",
228                    DxtAlgorithm::IterativeClusterFit => "very slow/best quality",
229                };
230                if *has_alpha {
231                    write!(f, "DXT3 image with alpha and compression {compress_str}")
232                } else {
233                    write!(f, "DXT3 image without alpha and compression {compress_str}")
234                }
235            }
236            Blp2Format::Dxt5 {
237                has_alpha,
238                compress_algorithm,
239            } => {
240                let compress_str = match *compress_algorithm {
241                    DxtAlgorithm::RangeFit => "fast/low quality",
242                    DxtAlgorithm::ClusterFit => "slow/high quality",
243                    DxtAlgorithm::IterativeClusterFit => "very slow/best quality",
244                };
245                if *has_alpha {
246                    write!(f, "DXT5 image with alpha and compression {compress_str}")
247                } else {
248                    write!(f, "DXT5 image without alpha and compression {compress_str}")
249                }
250            }
251        }
252    }
253}
254
255/// Convert from unpacked pixels into BLP image ready for writing down
256pub fn image_to_blp(
257    image: DynamicImage,
258    make_mipmaps: bool,
259    target: BlpTarget,
260    mipmap_filter: FilterType,
261) -> Result<BlpImage, Error> {
262    if image.width() > BLP_MAX_WIDTH {
263        return Err(Error::WidthTooLarge(image.width()));
264    }
265    if image.height() > BLP_MAX_HEIGHT {
266        return Err(Error::HeightTooLarge(image.height()));
267    }
268
269    match target {
270        BlpTarget::Blp0(format) => match format {
271            BlpOldFormat::Raw1 { alpha_bits } => {
272                let header = BlpHeader {
273                    version: BlpVersion::Blp0,
274                    content: BlpContentTag::Direct,
275                    flags: BlpFlags::Old {
276                        alpha_bits: alpha_bits.into(),
277                        extra: 4,
278                        has_mipmaps: if make_mipmaps { 1 } else { 0 },
279                    },
280                    width: image.width(),
281                    height: image.height(),
282                    mipmap_locator: MipmapLocator::External,
283                };
284                let blp_raw1 =
285                    image_to_raw1(image, alpha_bits.into(), make_mipmaps, mipmap_filter)?;
286                Ok(BlpImage {
287                    header,
288                    content: BlpContent::Raw1(blp_raw1),
289                })
290            }
291            BlpOldFormat::Jpeg { has_alpha } => {
292                let alpha_bits = if has_alpha { 8 } else { 0 };
293                let blp_jpeg = image_to_jpeg(&image, make_mipmaps, alpha_bits, mipmap_filter)?;
294                Ok(BlpImage {
295                    header: BlpHeader {
296                        version: BlpVersion::Blp0,
297                        content: BlpContentTag::Jpeg,
298                        flags: BlpFlags::Old {
299                            alpha_bits: alpha_bits as u32,
300                            extra: 5,
301                            has_mipmaps: if make_mipmaps { 1 } else { 0 },
302                        },
303                        width: image.width(),
304                        height: image.height(),
305                        mipmap_locator: MipmapLocator::External,
306                    },
307                    content: BlpContent::Jpeg(blp_jpeg),
308                })
309            }
310        },
311        BlpTarget::Blp1(format) => match format {
312            BlpOldFormat::Raw1 { alpha_bits } => {
313                let width = image.width();
314                let height = image.height();
315                let blp_raw1 =
316                    image_to_raw1(image, alpha_bits.into(), make_mipmaps, mipmap_filter)?;
317                let header = BlpHeader {
318                    version: BlpVersion::Blp1,
319                    content: BlpContentTag::Direct,
320                    flags: BlpFlags::Old {
321                        alpha_bits: alpha_bits.into(),
322                        extra: 4,
323                        has_mipmaps: if make_mipmaps { 1 } else { 0 },
324                    },
325                    width,
326                    height,
327                    mipmap_locator: blp_raw1.mipmap_locator(BlpVersion::Blp1),
328                };
329                Ok(BlpImage {
330                    header,
331                    content: BlpContent::Raw1(blp_raw1),
332                })
333            }
334            BlpOldFormat::Jpeg { has_alpha } => {
335                let alpha_bits = if has_alpha { 8 } else { 0 };
336                let blp_jpeg = image_to_jpeg(&image, make_mipmaps, alpha_bits, mipmap_filter)?;
337                Ok(BlpImage {
338                    header: BlpHeader {
339                        version: BlpVersion::Blp1,
340                        content: BlpContentTag::Jpeg,
341                        flags: BlpFlags::Old {
342                            alpha_bits: alpha_bits as u32,
343                            extra: 5,
344                            has_mipmaps: if make_mipmaps { 1 } else { 0 },
345                        },
346                        width: image.width(),
347                        height: image.height(),
348                        mipmap_locator: blp_jpeg.mipmap_locator(BlpVersion::Blp1),
349                    },
350                    content: BlpContent::Jpeg(blp_jpeg),
351                })
352            }
353        },
354        BlpTarget::Blp2(format) => match format {
355            Blp2Format::Raw1 { alpha_bits } => {
356                let width = image.width();
357                let height = image.height();
358                let blp_raw1 =
359                    image_to_raw1(image, alpha_bits.into(), make_mipmaps, mipmap_filter)?;
360                let header = BlpHeader {
361                    version: BlpVersion::Blp2,
362                    content: BlpContentTag::Direct,
363                    flags: BlpFlags::Blp2 {
364                        compression: Compression::Raw1,
365                        alpha_bits: alpha_bits.into(),
366                        alpha_type: AlphaType::None,
367                        has_mipmaps: if make_mipmaps { 1 } else { 0 },
368                    },
369                    width,
370                    height,
371                    mipmap_locator: blp_raw1.mipmap_locator(BlpVersion::Blp2),
372                };
373                Ok(BlpImage {
374                    header,
375                    content: BlpContent::Raw1(blp_raw1),
376                })
377            }
378            Blp2Format::Raw3 => {
379                let width = image.width();
380                let height = image.height();
381                let blp_raw3 = image_to_raw3(image, make_mipmaps, mipmap_filter)?;
382                Ok(BlpImage {
383                    header: BlpHeader {
384                        version: BlpVersion::Blp2,
385                        content: BlpContentTag::Direct,
386                        flags: BlpFlags::Blp2 {
387                            compression: Compression::Raw3,
388                            alpha_bits: 8,
389                            alpha_type: AlphaType::None,
390                            has_mipmaps: if make_mipmaps { 1 } else { 0 },
391                        },
392                        width,
393                        height,
394                        mipmap_locator: blp_raw3.mipmap_locator(BlpVersion::Blp2),
395                    },
396                    content: BlpContent::Raw3(blp_raw3),
397                })
398            }
399            Blp2Format::Jpeg { has_alpha } => {
400                let alpha_bits = if has_alpha { 8 } else { 0 };
401                let blp_jpeg = image_to_jpeg(&image, make_mipmaps, alpha_bits, mipmap_filter)?;
402                Ok(BlpImage {
403                    header: BlpHeader {
404                        version: BlpVersion::Blp2,
405                        content: BlpContentTag::Jpeg,
406                        flags: BlpFlags::Blp2 {
407                            compression: Compression::Jpeg,
408                            alpha_bits: 8,
409                            alpha_type: AlphaType::None,
410                            has_mipmaps: if make_mipmaps { 1 } else { 0 },
411                        },
412                        width: image.width(),
413                        height: image.height(),
414                        mipmap_locator: blp_jpeg.mipmap_locator(BlpVersion::Blp2),
415                    },
416                    content: BlpContent::Jpeg(blp_jpeg),
417                })
418            }
419            Blp2Format::Dxt1 {
420                has_alpha,
421                compress_algorithm,
422            } => {
423                let width = image.width();
424                let height = image.height();
425                let alpha_bits = if has_alpha { 1 } else { 0 };
426                let blp_dxtn = image_to_dxtn(
427                    image,
428                    DxtnFormat::Dxt1,
429                    make_mipmaps,
430                    mipmap_filter,
431                    compress_algorithm,
432                )?;
433                Ok(BlpImage {
434                    header: BlpHeader {
435                        version: BlpVersion::Blp2,
436                        content: BlpContentTag::Direct,
437                        flags: BlpFlags::Blp2 {
438                            compression: Compression::Dxtc,
439                            alpha_bits,
440                            alpha_type: AlphaType::None,
441                            has_mipmaps: if make_mipmaps { 1 } else { 0 },
442                        },
443                        width,
444                        height,
445                        mipmap_locator: blp_dxtn.mipmap_locator(BlpVersion::Blp2),
446                    },
447                    content: BlpContent::Dxt1(blp_dxtn),
448                })
449            }
450            Blp2Format::Dxt3 {
451                has_alpha,
452                compress_algorithm,
453            } => {
454                let width = image.width();
455                let height = image.height();
456                let alpha_bits = if has_alpha { 8 } else { 0 };
457                let blp_dxtn = image_to_dxtn(
458                    image,
459                    DxtnFormat::Dxt3,
460                    make_mipmaps,
461                    mipmap_filter,
462                    compress_algorithm,
463                )?;
464                Ok(BlpImage {
465                    header: BlpHeader {
466                        version: BlpVersion::Blp2,
467                        content: BlpContentTag::Direct,
468                        flags: BlpFlags::Blp2 {
469                            compression: Compression::Dxtc,
470                            alpha_bits,
471                            alpha_type: AlphaType::OneBit,
472                            has_mipmaps: if make_mipmaps { 1 } else { 0 },
473                        },
474                        width,
475                        height,
476                        mipmap_locator: blp_dxtn.mipmap_locator(BlpVersion::Blp2),
477                    },
478                    content: BlpContent::Dxt3(blp_dxtn),
479                })
480            }
481            Blp2Format::Dxt5 {
482                has_alpha,
483                compress_algorithm,
484            } => {
485                let width = image.width();
486                let height = image.height();
487                let alpha_bits = if has_alpha { 8 } else { 0 };
488                let blp_dxtn = image_to_dxtn(
489                    image,
490                    DxtnFormat::Dxt5,
491                    make_mipmaps,
492                    mipmap_filter,
493                    compress_algorithm,
494                )?;
495                Ok(BlpImage {
496                    header: BlpHeader {
497                        version: BlpVersion::Blp2,
498                        content: BlpContentTag::Direct,
499                        flags: BlpFlags::Blp2 {
500                            compression: Compression::Dxtc,
501                            alpha_bits,
502                            alpha_type: AlphaType::Enhanced,
503                            has_mipmaps: if make_mipmaps { 1 } else { 0 },
504                        },
505                        width,
506                        height,
507                        mipmap_locator: blp_dxtn.mipmap_locator(BlpVersion::Blp2),
508                    },
509                    content: BlpContent::Dxt5(blp_dxtn),
510                })
511            }
512        },
513    }
514}