1mod dxtn;
2pub 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
21pub 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#[derive(Clone, PartialEq, Eq)]
36pub enum BlpTarget {
37 Blp0(BlpOldFormat),
40 Blp1(BlpOldFormat),
43 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#[derive(Debug, Clone, PartialEq, Eq)]
66pub enum BlpOldFormat {
67 Raw1 {
69 alpha_bits: AlphaBits,
71 },
72 Jpeg {
74 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#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
102pub enum AlphaBits {
103 NoAlpha,
105 Bit1,
107 Bit4,
109 #[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#[derive(Clone, PartialEq, Eq)]
149pub enum Blp2Format {
150 Raw1 {
152 alpha_bits: AlphaBits,
154 },
155 Raw3,
157 Jpeg {
159 has_alpha: bool,
161 },
162 Dxt1 {
164 has_alpha: bool,
166 compress_algorithm: DxtAlgorithm,
168 },
169 Dxt3 {
171 has_alpha: bool,
173 compress_algorithm: DxtAlgorithm,
175 },
176 Dxt5 {
178 has_alpha: bool,
180 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
255pub 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}