1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
use crate::core::image::ImageBlp;
use crate::error::error::BlpError;
use byteorder::{LittleEndian, ReadBytesExt};
use image::RgbaImage;
use std::io::{Cursor, Read};
impl ImageBlp {
/// DIRECT (paletted) decoding.
///
/// Reads palette from the header area and, for each mipmap, reads indices + alpha,
/// then reconstructs RGBA images.
///
/// - `mip_visible[i] == false` → skip decoding for that mipmap (image stays `None`).
/// - If `mip_visible` has no entry for index `i`, we treat it as `true`.
pub fn decode_direct(&mut self, buf: &[u8], mip_visible: &[bool]) -> Result<(), BlpError> {
// --- Read palette ---
// Palette is located at `self.header_offset` with expected length = 256 * 4.
if self.header_offset + self.header_length > buf.len() {
return Err(BlpError::new("direct.header.oob"));
}
let mut cur = Cursor::new(&buf[..]);
cur.set_position(self.header_offset as u64);
let mut palette = [[0u8; 3]; 256];
for i in 0..256 {
let color = cur.read_u32::<LittleEndian>()?;
// Format: R = bits 16..23, G = bits 8..15, B = bits 0..7
let r = ((color >> 16) & 0xFF) as u8;
let g = ((color >> 8) & 0xFF) as u8;
let b = (color & 0xFF) as u8;
palette[i] = [r, g, b];
}
let buf_len = buf.len();
let alpha_bits = self.alpha_bits;
// --- Process mipmaps ---
for i in 0..self.mipmaps.len() {
// Check if this mipmap should be decoded
let visible = mip_visible
.get(i)
.copied()
.unwrap_or(true);
if !visible {
self.mipmaps[i].image = None;
continue;
}
let off = self.mipmaps[i].offset;
let len = self.mipmaps[i].length;
if len == 0 {
continue; // no data for this mip
}
if off.checked_add(len).is_none() || off + len > buf_len {
continue; // invalid offset/length
}
cur.set_position(off as u64);
let (w, h) = (self.mipmaps[i].width, self.mipmaps[i].height);
let pixel_count = (w as usize) * (h as usize);
// --- Read indices (one byte per pixel) ---
let mut indices = vec![0u8; pixel_count];
cur.read_exact(&mut indices)
.map_err(|_| BlpError::new("direct.indices.truncated"))?;
// --- Read alpha data depending on alpha_bits ---
let alpha_bytes = match alpha_bits {
0 => 0,
1 => (pixel_count + 7) / 8, // 1 bit per pixel
4 => (pixel_count + 1) / 2, // 4 bits per pixel
8 => pixel_count, // 1 byte per pixel
_ => return Err(BlpError::new("blp.version.invalid").with_arg("msg", "unsupported alpha bits")),
};
let mut alpha_raw = vec![0u8; alpha_bytes];
if alpha_bytes > 0 {
cur.read_exact(&mut alpha_raw)
.map_err(|_| BlpError::new("direct.alpha.truncated"))?;
}
// --- Assemble RGBA image ---
let mut img = RgbaImage::new(w, h);
for p in 0..pixel_count {
let idx = indices[p] as usize;
let [r, g, b] = palette[idx];
let a = match alpha_bits {
0 => 255,
1 => {
let byte = alpha_raw[p / 8];
let bit = (byte >> (p % 8)) & 1;
if bit == 1 { 255 } else { 0 }
}
4 => {
let byte = alpha_raw[p / 2];
let nibble = if (p & 1) == 0 { byte & 0x0F } else { byte >> 4 };
(nibble << 4) | nibble
}
8 => alpha_raw[p],
_ => 255,
};
img.get_pixel_mut((p as u32) % w, (p as u32) / w)
.0 = [r, g, b, a];
}
self.mipmaps[i].image = Some(img);
}
Ok(())
}
}