Skip to main content

blp/core/decode/
direct.rs

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