#[derive(Clone, Copy, PartialEq, Eq)]
pub enum DxtFlags {
Dxt1,
Dxt3,
Dxt5,
}
pub fn decompress_image(width: u32, height: u32, data: &[u8], flags: DxtFlags) -> Option<Vec<u8>> {
let n_pixels = (width as usize).checked_mul(height as usize)?;
let n_bytes = n_pixels.checked_mul(4)?;
let mut rgba = vec![0u8; n_bytes];
let bytes_per_block: usize = if flags == DxtFlags::Dxt1 { 8 } else { 16 };
let mut source_pos = 0usize;
let mut target_rgba = [0u8; 64];
for y in (0..height).step_by(4) {
for x in (0..width).step_by(4) {
if source_pos.saturating_add(bytes_per_block) > data.len() {
continue;
}
decompress_block(&mut target_rgba, data, source_pos, flags);
let mut target_pos = 0usize;
for py in 0..4u32 {
for px in 0..4u32 {
let sx = x + px;
let sy = y + py;
if sx < width && sy < height {
let dst = 4 * (width * sy + sx) as usize;
rgba[dst..dst + 4]
.copy_from_slice(&target_rgba[target_pos..target_pos + 4]);
}
target_pos += 4;
}
}
source_pos += bytes_per_block;
}
}
Some(rgba)
}
fn decompress_block(rgba: &mut [u8; 64], block: &[u8], block_idx: usize, flags: DxtFlags) {
let color_idx = match flags {
DxtFlags::Dxt3 | DxtFlags::Dxt5 => block_idx + 8,
DxtFlags::Dxt1 => block_idx,
};
decompress_color(rgba, block, color_idx, flags == DxtFlags::Dxt1);
match flags {
DxtFlags::Dxt3 => decompress_alpha_dxt3(rgba, block, block_idx),
DxtFlags::Dxt5 => decompress_alpha_dxt5(rgba, block, block_idx),
DxtFlags::Dxt1 => {}
}
}
fn unpack565(
block: &[u8],
block_idx: usize,
packed_offset: usize,
colour: &mut [u8],
colour_offset: usize,
) -> u16 {
let lo = block[block_idx + packed_offset] as u16;
let hi = block[block_idx + packed_offset + 1] as u16;
let value = lo | (hi << 8);
let red = ((value >> 11) & 0x1F) as u8;
let green = ((value >> 5) & 0x3F) as u8;
let blue = (value & 0x1F) as u8;
colour[colour_offset] = (red << 3) | (red >> 2);
colour[colour_offset + 1] = (green << 2) | (green >> 4);
colour[colour_offset + 2] = (blue << 3) | (blue >> 2);
colour[colour_offset + 3] = 255;
value
}
fn decompress_color(rgba: &mut [u8; 64], block: &[u8], block_idx: usize, is_dxt1: bool) {
let mut codes = [0u8; 16]; let a = unpack565(block, block_idx, 0, &mut codes, 0);
let b = unpack565(block, block_idx, 2, &mut codes, 4);
for i in 0..3 {
let c = codes[i] as i32;
let d = codes[4 + i] as i32;
if is_dxt1 && a <= b {
codes[8 + i] = ((c + d) / 2) as u8;
codes[12 + i] = 0;
} else {
codes[8 + i] = ((2 * c + d) / 3) as u8;
codes[12 + i] = ((c + 2 * d) / 3) as u8;
}
}
codes[8 + 3] = 255;
codes[12 + 3] = if is_dxt1 && a <= b { 0 } else { 255 };
let mut indices = [0u8; 16];
for i in 0..4 {
let packed = block[block_idx + 4 + i];
indices[i * 4] = packed & 0x3;
indices[i * 4 + 1] = (packed >> 2) & 0x3;
indices[i * 4 + 2] = (packed >> 4) & 0x3;
indices[i * 4 + 3] = (packed >> 6) & 0x3;
}
for i in 0..16 {
let offset = 4 * indices[i] as usize;
rgba[4 * i..4 * i + 4].copy_from_slice(&codes[offset..offset + 4]);
}
}
fn decompress_alpha_dxt3(rgba: &mut [u8; 64], block: &[u8], block_idx: usize) {
for i in 0..8 {
let quant = block[block_idx + i];
let lo = quant & 0x0F;
let hi = quant & 0xF0;
rgba[8 * i + 3] = lo | (lo << 4);
rgba[8 * i + 7] = hi | (hi >> 4);
}
}
fn decompress_alpha_dxt5(rgba: &mut [u8; 64], block: &[u8], block_idx: usize) {
let alpha0 = block[block_idx];
let alpha1 = block[block_idx + 1];
let mut codes = [0u8; 8];
codes[0] = alpha0;
codes[1] = alpha1;
if alpha0 <= alpha1 {
for i in 1..5usize {
codes[1 + i] = (((5 - i) as u32 * alpha0 as u32 + i as u32 * alpha1 as u32) / 5) as u8;
}
codes[6] = 0;
codes[7] = 255;
} else {
for i in 1..7usize {
codes[i + 1] = (((7 - i) as u32 * alpha0 as u32 + i as u32 * alpha1 as u32) / 7) as u8;
}
}
let mut indices = [0u8; 16];
let mut src_pos = 2usize;
let mut idx_pos = 0usize;
for _ in 0..2 {
let mut value = 0u32;
for j in 0..3usize {
value |= (block[block_idx + src_pos] as u32) << (8 * j);
src_pos += 1;
}
for j in 0..8usize {
indices[idx_pos] = ((value >> (3 * j)) & 0x07) as u8;
idx_pos += 1;
}
}
for i in 0..16 {
rgba[4 * i + 3] = codes[indices[i] as usize];
}
}