use crate::lossless::constants::subsample_size;
use crate::lossless::prelude::*;
use crate::lossless::transform::{add_pixels, sub_pixels};
#[must_use]
pub(crate) fn expand_color_map(num_colors: usize, raw: &[u32], bits: u32) -> Vec<u32> {
let final_num_colors = 1usize << (8u32 >> bits);
let mut map = vec![0u32; final_num_colors];
let n = num_colors.min(final_num_colors).min(raw.len());
if n > 0 {
map[0] = raw[0];
for k in 1..n {
map[k] = add_pixels(map[k - 1], raw[k]);
}
}
map
}
#[must_use]
pub(crate) fn inverse(src: &[u32], dst_width: u32, bits: u32, palette: &[u32]) -> Vec<u32> {
let dst_w = dst_width as usize;
let src_w = subsample_size(dst_width, bits) as usize;
if src_w == 0 {
return Vec::new();
}
let height = src.len() / src_w;
let mut dst = Vec::with_capacity(dst_w * height);
for row in src.chunks_exact(src_w) {
dst.extend(inverse_row(row, dst_width, bits, palette));
}
dst
}
#[must_use]
pub(crate) fn inverse_row(src_row: &[u32], dst_width: u32, bits: u32, palette: &[u32]) -> Vec<u32> {
let dst_w = dst_width as usize;
let bpp = 8u32 >> bits;
let mut dst = vec![0u32; dst_w];
if bpp == 8 {
for (d, &s) in dst.iter_mut().zip(src_row) {
*d = lookup(palette, (s >> 8) & 0xff);
}
} else {
let pixels_per_byte = 1usize << bits;
let count_mask = pixels_per_byte - 1;
let bit_mask = (1u32 << bpp) - 1;
let mut si = 0usize;
let mut packed = 0u32;
for (x, d) in dst.iter_mut().enumerate() {
if x & count_mask == 0 {
packed = (src_row[si] >> 8) & 0xff;
si += 1;
}
*d = lookup(palette, packed & bit_mask);
packed >>= bpp;
}
}
dst
}
fn lookup(palette: &[u32], index: u32) -> u32 {
palette.get(index as usize).copied().unwrap_or(0)
}
pub(crate) struct Palette {
pub(crate) bits: u32,
pub(crate) num_colors: u32,
pub(crate) colormap: Vec<u32>,
pub(crate) bundled: Vec<u32>,
}
#[must_use]
pub(crate) fn forward(argb: &[u32], width: u32) -> Option<Palette> {
if width == 0 {
return None;
}
let mut colors = argb.to_vec();
colors.sort_unstable();
colors.dedup();
if colors.len() > 256 {
return None;
}
let num_colors = u32::try_from(colors.len()).ok()?;
let bits: u32 = if colors.len() > 16 {
0
} else if colors.len() > 4 {
1
} else if colors.len() > 2 {
2
} else {
3
};
let mut colormap = Vec::with_capacity(colors.len());
if !colors.is_empty() {
colormap.push(colors[0]);
for k in 1..colors.len() {
colormap.push(sub_pixels(colors[k], colors[k - 1]));
}
}
let bpp = 8u32 >> bits;
let pixels_per_byte = 1u32 << bits;
let w = width as usize;
let src_w = subsample_size(width, bits) as usize;
let mut bundled = Vec::with_capacity(src_w * (argb.len() / w));
for row in argb.chunks_exact(w) {
let mut bundle = 0u32;
let mut count = 0u32;
for &pixel in row {
let pos = colors.binary_search(&pixel).unwrap_or(0);
let index = u32::try_from(pos).unwrap_or(0);
bundle |= index << (count * bpp);
count += 1;
if count == pixels_per_byte {
bundled.push(bundle << 8);
bundle = 0;
count = 0;
}
}
if count != 0 {
bundled.push(bundle << 8);
}
}
Some(Palette {
bits,
num_colors,
colormap,
bundled,
})
}
#[cfg(test)]
mod tests {
use super::{expand_color_map, forward, inverse, inverse_row};
use crate::lossless::constants::subsample_size;
use proptest::prelude::*;
#[test]
fn expand_color_map_cumulative_delta_with_transparent_tail() {
let raw = [0x0102_0304, 0x1020_3040, 0x0101_0101];
let map = expand_color_map(3, &raw, 1);
assert_eq!(map.len(), 16);
assert_eq!(map[0], 0x0102_0304);
assert_eq!(map[1], 0x1122_3344); assert_eq!(map[2], 0x1223_3445); for &entry in &map[3..16] {
assert_eq!(entry, 0x0000_0000);
}
}
#[test]
fn expand_color_map_zero_colors_stays_all_transparent() {
let raw = [0x1122_3344u32, 0x5566_7788];
let map = expand_color_map(0, &raw, 1);
assert_eq!(map.len(), 16);
assert_eq!(map[0], 0x0000_0000);
for &entry in &map {
assert_eq!(entry, 0x0000_0000);
}
}
#[test]
fn inverse_bundled_bpp2_unpacks_lsb_first_and_refreshes_each_row() {
let palette = [0x0000_00aa, 0x0000_00bb, 0x0000_00cc, 0x0000_00dd];
let src = [0x0000_e400, 0x0000_1b00];
let dst = inverse(&src, 4, 2, &palette);
assert_eq!(
dst,
[
0x0000_00aa,
0x0000_00bb,
0x0000_00cc,
0x0000_00dd, 0x0000_00dd,
0x0000_00cc,
0x0000_00bb,
0x0000_00aa, ]
);
}
#[test]
fn whole_buffer_inverse_equals_looping_inverse_row_bpp2() {
let palette = [0x0000_00aa, 0x0000_00bb, 0x0000_00cc, 0x0000_00dd];
let src = [0x0000_e400u32, 0x0000_1b00];
let batch = inverse(&src, 4, 2, &palette);
let mut rows = Vec::new();
rows.extend(inverse_row(&src[0..1], 4, 2, &palette));
rows.extend(inverse_row(&src[1..2], 4, 2, &palette));
assert_eq!(batch, rows);
}
#[test]
fn inverse_8bpp_direct_lookup_with_out_of_range_index() {
let palette = [0x0000_00aa, 0x0000_00bb, 0x0000_00cc];
let src = [0x0000_0000, 0x0000_0200, 0x0000_0500];
let dst = inverse(&src, 3, 0, &palette);
assert_eq!(dst, [0x0000_00aa, 0x0000_00cc, 0x0000_0000]);
}
fn expected_bits(num_colors: u32) -> u32 {
if num_colors > 16 {
0
} else if num_colors > 4 {
1
} else if num_colors > 2 {
2
} else {
3
}
}
fn distinct_color(k: usize) -> u32 {
0x9E37_79B1u32
.wrapping_mul(u32::try_from(k).unwrap())
.wrapping_add(0x1234_5678)
}
#[test]
fn forward_2_colors_uses_bits3_and_round_trips() {
let a = 0x1122_3344;
let b = 0xaabb_ccdd;
let argb = [a, b, a, b, a, b];
let p = forward(&argb, 3).unwrap();
assert_eq!(p.num_colors, 2);
assert_eq!(p.bits, 3);
let expanded = expand_color_map(p.num_colors as usize, &p.colormap, p.bits);
assert_eq!(inverse(&p.bundled, 3, p.bits, &expanded), argb.to_vec());
}
#[test]
fn forward_16_colors_uses_bits1_and_round_trips() {
let argb: Vec<u32> = (0..16).map(distinct_color).collect();
let p = forward(&argb, 4).unwrap();
assert_eq!(p.num_colors, 16);
assert_eq!(p.bits, 1);
let expanded = expand_color_map(p.num_colors as usize, &p.colormap, p.bits);
assert_eq!(inverse(&p.bundled, 4, p.bits, &expanded), argb);
}
#[test]
fn forward_256_colors_uses_bits0_and_round_trips() {
let argb: Vec<u32> = (0..256).map(distinct_color).collect();
let p = forward(&argb, 16).unwrap();
assert_eq!(p.num_colors, 256);
assert_eq!(p.bits, 0);
let expanded = expand_color_map(p.num_colors as usize, &p.colormap, p.bits);
assert_eq!(inverse(&p.bundled, 16, p.bits, &expanded), argb);
}
#[test]
fn forward_over_256_colors_is_none() {
let argb: Vec<u32> = (0..257).map(distinct_color).collect();
assert!(forward(&argb, 257).is_none());
}
#[test]
fn forward_zero_width_is_none() {
assert!(forward(&[0x0000_00aa], 0).is_none());
}
#[test]
fn forward_reverses_the_bpp2_inverse_fixture() {
let dst = [
0x0000_00aa,
0x0000_00bb,
0x0000_00cc,
0x0000_00dd, 0x0000_00dd,
0x0000_00cc,
0x0000_00bb,
0x0000_00aa, ];
let p = forward(&dst, 4).unwrap();
assert_eq!(p.num_colors, 4);
assert_eq!(p.bits, 2);
assert_eq!(p.bundled, [0x0000_e400, 0x0000_1b00]);
assert_eq!(
p.colormap,
[0x0000_00aa, 0x0000_0011, 0x0000_0011, 0x0000_0011]
);
let expanded = expand_color_map(p.num_colors as usize, &p.colormap, p.bits);
assert_eq!(inverse(&p.bundled, 4, p.bits, &expanded), dst.to_vec());
}
proptest! {
#[test]
fn forward_then_inverse_is_identity(
pool in prop::collection::vec(any::<u32>(), 1..=64),
picks in prop::collection::vec(any::<u16>(), 1..96),
width in 1u32..=9,
) {
let w = width as usize;
let argb: Vec<u32> =
picks.iter().map(|&v| pool[v as usize % pool.len()]).collect();
let usable = argb.len() / w * w;
prop_assume!(usable > 0);
let argb = &argb[..usable];
let p = forward(argb, width).expect("<= 256 distinct colors yields Some");
prop_assert_eq!(p.bits, expected_bits(p.num_colors));
let expanded = expand_color_map(p.num_colors as usize, &p.colormap, p.bits);
let restored = inverse(&p.bundled, width, p.bits, &expanded);
prop_assert_eq!(restored, argb.to_vec());
}
#[test]
fn whole_buffer_inverse_equals_looping_inverse_row(
pool in prop::collection::vec(any::<u32>(), 1..=64),
picks in prop::collection::vec(any::<u16>(), 1..96),
width in 1u32..=9,
) {
let w = width as usize;
let argb: Vec<u32> =
picks.iter().map(|&v| pool[v as usize % pool.len()]).collect();
let usable = argb.len() / w * w;
prop_assume!(usable > 0);
let argb = &argb[..usable];
let p = forward(argb, width).expect("<= 256 distinct colors yields Some");
let expanded = expand_color_map(p.num_colors as usize, &p.colormap, p.bits);
let batch = inverse(&p.bundled, width, p.bits, &expanded);
let src_w = subsample_size(width, p.bits) as usize;
let mut rows = Vec::with_capacity(batch.len());
for row in p.bundled.chunks_exact(src_w) {
rows.extend(inverse_row(row, width, p.bits, &expanded));
}
prop_assert_eq!(batch, rows);
}
}
}