#[derive(Clone, PartialEq, Eq)]
pub struct TransferLut(pub [u8; 256]);
const _ASSERT_LUT_LEN: () = assert!(
std::mem::size_of::<TransferLut>() == 256,
"TransferLut must contain exactly 256 bytes"
);
impl TransferLut {
pub const IDENTITY: Self = {
let mut t = [0u8; 256];
let mut i = 0u8;
loop {
t[i as usize] = i;
if i == 255 {
break;
}
i += 1;
}
Self(t)
};
pub const INVERTED: Self = {
let mut t = [0u8; 256];
let mut i = 0u8;
loop {
t[i as usize] = 255 - i;
if i == 255 {
break;
}
i += 1;
}
Self(t)
};
#[inline]
#[must_use]
pub const fn apply(&self, v: u8) -> u8 {
self.0[v as usize]
}
#[must_use]
pub fn invert_complement(&self) -> Self {
Self(std::array::from_fn(|i| 255 - self.0[255 - i]))
}
#[must_use]
pub const fn as_array(&self) -> &[u8; 256] {
&self.0
}
#[must_use]
pub fn compose(&self, other: &Self) -> Self {
Self(std::array::from_fn(|i| other.apply(self.0[i])))
}
#[must_use]
pub fn compose_many(luts: &[&Self]) -> Self {
luts.iter()
.fold(Self::IDENTITY, |acc, lut| acc.compose(lut))
}
}
impl Default for TransferLut {
fn default() -> Self {
Self::IDENTITY
}
}
impl std::fmt::Debug for TransferLut {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"TransferLut([{}, {}, ..., {}])",
self.0[0], self.0[1], self.0[255]
)
}
}
impl From<[u8; 256]> for TransferLut {
fn from(arr: [u8; 256]) -> Self {
Self(arr)
}
}
impl From<TransferLut> for [u8; 256] {
fn from(lut: TransferLut) -> Self {
lut.0
}
}
impl AsRef<[u8]> for TransferLut {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn identity_all_256_entries() {
let lut = TransferLut::IDENTITY;
for i in 0u8..=255 {
assert_eq!(lut.apply(i), i, "IDENTITY should map {i} to {i}");
}
}
#[test]
fn invert_complement_of_identity_is_identity() {
let lut = TransferLut::IDENTITY;
let inv = lut.invert_complement();
for i in 0u8..=255 {
assert_eq!(
inv.apply(i),
i,
"invert_complement(identity)[{i}] should be {i}"
);
}
}
#[test]
fn invert_complement_nontrivial() {
let lut = TransferLut(std::array::from_fn(|i| {
u8::try_from(255 - i).expect("i < 256")
}));
let inv = lut.invert_complement();
for i in 0u8..=255 {
assert_eq!(inv.apply(i), 255 - i);
}
}
#[test]
fn compose_identity_is_neutral() {
let id = TransferLut::IDENTITY;
let lut = TransferLut::from(std::array::from_fn::<u8, 256, _>(|i| {
u8::try_from((i * 2) % 256).expect("(i*2)%256 < 256")
}));
assert_eq!(lut.compose(&id), lut, "lut ∘ id should equal lut");
assert_eq!(id.compose(&lut), lut, "id ∘ lut should equal lut");
}
#[test]
fn compose_inversion_twice_is_identity() {
let inv = TransferLut::from(std::array::from_fn::<u8, 256, _>(|i| {
u8::try_from(255 - i).expect("i < 256")
}));
let roundtrip = inv.compose(&inv);
assert_eq!(roundtrip, TransferLut::IDENTITY);
}
#[test]
fn compose_associativity() {
let double = TransferLut::from(std::array::from_fn::<u8, 256, _>(|i| {
u8::try_from((i * 2) % 256).expect("(i*2)%256 < 256")
}));
let inv = TransferLut::from(std::array::from_fn::<u8, 256, _>(|i| {
u8::try_from(255 - i).expect("i < 256")
}));
let half = TransferLut::from(std::array::from_fn::<u8, 256, _>(|i| {
u8::try_from(i / 2).expect("i/2 < 256")
}));
let left = double.compose(&inv).compose(&half);
let right = double.compose(&inv.compose(&half));
assert_eq!(left, right);
}
#[test]
fn compose_many_empty_is_identity() {
let result = TransferLut::compose_many(&[]);
assert_eq!(result, TransferLut::IDENTITY);
}
#[test]
fn compose_many_single() {
let inv = TransferLut::from(std::array::from_fn::<u8, 256, _>(|i| {
u8::try_from(255 - i).expect("i < 256")
}));
let result = TransferLut::compose_many(&[&inv]);
assert_eq!(result, inv);
}
#[test]
fn compose_many_matches_sequential_compose() {
let a = TransferLut::from(std::array::from_fn::<u8, 256, _>(|i| {
u8::try_from((i * 3) % 256).expect("(i*3)%256 < 256")
}));
let b = TransferLut::from(std::array::from_fn::<u8, 256, _>(|i| {
u8::try_from(255 - i).expect("i < 256")
}));
let c = TransferLut::from(std::array::from_fn::<u8, 256, _>(|i| {
u8::try_from(i / 2).expect("i/2 < 256")
}));
let expected = a.compose(&b).compose(&c);
let got = TransferLut::compose_many(&[&a, &b, &c]);
assert_eq!(got, expected);
}
#[test]
fn from_array_roundtrip() {
let arr =
std::array::from_fn::<u8, 256, _>(|i| u8::try_from(i ^ 0xAA).expect("i^0xAA < 256"));
let lut = TransferLut::from(arr);
let arr2: [u8; 256] = lut.into();
assert_eq!(arr, arr2);
}
#[test]
fn as_ref_length_and_content() {
let lut = TransferLut::IDENTITY;
let slice: &[u8] = lut.as_ref();
assert_eq!(slice.len(), 256);
for (i, &b) in slice.iter().enumerate() {
assert_eq!(b, u8::try_from(i).expect("IDENTITY has 256 entries"));
}
}
#[test]
fn debug_format_does_not_dump_256_entries() {
let s = format!("{:?}", TransferLut::IDENTITY);
assert!(s.len() < 80, "Debug output unexpectedly long: {s}");
assert!(s.contains("TransferLut"));
}
}