image_blp/convert/
mod.rs

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