use std::{convert::Infallible, mem::swap};
use crate::{
cluster_fit::cluster_fit,
math::{R32F, R8U},
};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[repr(C)]
pub struct Block {
pub color0: R8U,
pub color1: R8U,
pub texels: [u8; 6],
}
impl_fixedcode_struct!(
Block {
color0: R8U,
color1: R8U,
texels: [u8; 6],
} | Infallible
);
impl Block {
pub const BLACK: Block = Block {
color0: R8U::WHITE,
color1: R8U::BLACK,
texels: [0xFF; 6],
};
pub const WHITE: Block = Block {
color0: R8U::WHITE,
color1: R8U::BLACK,
texels: [0x00; 6],
};
pub fn bytes(&self) -> [u8; 8] {
let color0 = self.color0.bits();
let color1 = self.color1.bits();
let texels = self.texels;
[
color0, color1, texels[0], texels[1], texels[2], texels[3], texels[4], texels[5],
]
}
pub fn from_bytes(bytes: [u8; 8]) -> Block {
let color0 = R8U::new(bytes[0]);
let color1 = R8U::new(bytes[1]);
let texels = [bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7]];
Block {
color0,
color1,
texels,
}
}
pub fn decode(self) -> [[R32F; 4]; 4] {
#![allow(clippy::needless_range_loop)]
let color0 = self.color0.into_f32();
let color1 = self.color1.into_f32();
let mut colors = [[R32F::BLACK; 4]; 4];
let texels = self.texels;
let palette = if self.color0.bits() > self.color1.bits() {
[
color0,
color1,
R32F::lerp(color0, color1, 1.0 / 7.0),
R32F::lerp(color0, color1, 2.0 / 7.0),
R32F::lerp(color0, color1, 3.0 / 7.0),
R32F::lerp(color0, color1, 4.0 / 7.0),
R32F::lerp(color0, color1, 5.0 / 7.0),
R32F::lerp(color0, color1, 6.0 / 7.0),
]
} else {
[
color0,
color1,
R32F::lerp(color0, color1, 1.0 / 5.0),
R32F::lerp(color0, color1, 2.0 / 5.0),
R32F::lerp(color0, color1, 3.0 / 5.0),
R32F::lerp(color0, color1, 4.0 / 5.0),
R32F::BLACK,
R32F::WHITE,
]
};
for x in 0..4 {
for y in 0..4 {
let start_bit = (y * 4 + x) * 3;
let start_byte = start_bit / 8;
let mut index = (texels[start_byte] >> (start_bit & 7)) & 0b111;
if start_bit & 7 > 5 {
index |= (texels[start_byte + 1] << (8 - (start_bit & 7))) & 0b111;
}
colors[y][x] = palette[index as usize];
}
}
colors
}
pub fn encode(colors: [[R32F; 4]; 4]) -> Self {
let mut samples = [0.0; 16];
for y in 0..4 {
for x in 0..4 {
samples[y * 4 + x] = colors[y][x].r();
}
}
let mut cf = cluster_fit::<f32, 8, 16>(
&samples,
|a: f32, b: f32| {
let a = R8U::from_f32(R32F::new(a));
let b = R8U::from_f32(R32F::new(b));
(a.into_f32().r(), b.into_f32().r())
},
|a: f32, b: f32| {
let a = R32F::new(a);
let b = R32F::new(b);
R32F::distance(a, b)
},
);
let (color0, color1) = cf.endpoints;
let mut color0 = R8U::from_f32(R32F::new(color0));
let mut color1 = R8U::from_f32(R32F::new(color1));
if color0 == color1 {
return Block {
color0,
color1: R8U::BLACK,
texels: [0x00; 6],
};
} else if color0.bits() < color1.bits() {
swap(&mut color0, &mut color1);
for index in &mut cf.indices {
*index = 7 - *index;
}
}
let mut texels = [0; 6];
for y in 0..4 {
for x in 0..4 {
let idx = match cf.indices[y * 4 + x] {
0 => 0,
1 => 2,
2 => 3,
3 => 4,
4 => 5,
5 => 6,
6 => 7,
7 => 1,
_ => unreachable!(),
};
let start_bit = (y * 4 + x) * 3;
let start_byte = start_bit / 8;
texels[start_byte] |= idx << (start_bit & 7);
if start_bit & 7 > 5 {
texels[start_byte + 1] |= idx >> (8 - (start_bit & 7));
}
}
}
Block {
color0,
color1,
texels,
}
}
}