pub const PALETTE_COLORS: usize = 8;
const PALETTE_NUM_NEIGHBORS: usize = 3;
const HASH_MULTIPLIERS: [u32; PALETTE_NUM_NEIGHBORS] = [1, 2, 2];
const PALETTE_COLOR_CONTEXT: [i8; 9] = [-1, -1, 0, -1, -1, 4, 3, 2, 1];
#[must_use]
pub fn palette_cache(above: &[u16], left: &[u16]) -> Vec<u16> {
let mut cache = Vec::with_capacity(above.len() + left.len());
let mut ai = 0;
let mut li = 0;
let push = |cache: &mut Vec<u16>, v: u16| {
if cache.last() != Some(&v) {
cache.push(v);
}
};
while ai < above.len() && li < left.len() {
let a = above.get(ai).copied().unwrap_or(0);
let l = left.get(li).copied().unwrap_or(0);
if l < a {
push(&mut cache, l);
li += 1;
} else {
push(&mut cache, a);
ai += 1;
if l == a {
li += 1;
}
}
}
while ai < above.len() {
if let Some(&v) = above.get(ai) {
push(&mut cache, v);
}
ai += 1;
}
while li < left.len() {
if let Some(&v) = left.get(li) {
push(&mut cache, v);
}
li += 1;
}
cache
}
#[must_use]
pub fn color_context(
left: Option<u8>,
above_left: Option<u8>,
above: Option<u8>,
n: usize,
) -> ([u8; PALETTE_COLORS], usize) {
let mut scores = [0_u32; PALETTE_COLORS];
let mut order = [0_u8; PALETTE_COLORS];
for (i, o) in order.iter_mut().enumerate() {
*o = i as u8;
}
let mut add = |idx: Option<u8>, weight: u32| {
if let Some(v) = idx {
if let Some(s) = scores.get_mut(usize::from(v)) {
*s += weight;
}
}
};
add(left, 2);
add(above_left, 1);
add(above, 2);
for i in 0..PALETTE_NUM_NEIGHBORS.min(n) {
let mut max_idx = i;
let mut max_score = scores.get(i).copied().unwrap_or(0);
for j in (i + 1)..n {
let sj = scores.get(j).copied().unwrap_or(0);
if sj > max_score {
max_score = sj;
max_idx = j;
}
}
if max_idx != i {
let saved_order = order.get(max_idx).copied().unwrap_or(0);
let mut k = max_idx;
while k > i {
let prev_s = scores.get(k - 1).copied().unwrap_or(0);
let prev_o = order.get(k - 1).copied().unwrap_or(0);
if let Some(s) = scores.get_mut(k) {
*s = prev_s;
}
if let Some(o) = order.get_mut(k) {
*o = prev_o;
}
k -= 1;
}
if let Some(s) = scores.get_mut(i) {
*s = max_score;
}
if let Some(o) = order.get_mut(i) {
*o = saved_order;
}
}
}
let mut hash = 0_u32;
for i in 0..PALETTE_NUM_NEIGHBORS {
hash += scores.get(i).copied().unwrap_or(0) * HASH_MULTIPLIERS.get(i).copied().unwrap_or(0);
}
let ctx = PALETTE_COLOR_CONTEXT
.get(hash as usize)
.copied()
.filter(|&c| c >= 0)
.unwrap_or(0) as usize;
(order, ctx)
}
#[cfg(test)]
#[allow(
clippy::unwrap_used,
clippy::indexing_slicing,
clippy::panic,
reason = "tests operate on known-good values and assert shapes directly"
)]
mod tests {
use super::*;
#[test]
fn cache_merges_and_dedups_ascending() {
assert_eq!(
palette_cache(&[10, 20, 30], &[15, 20, 25]),
vec![10, 15, 20, 25, 30]
);
assert_eq!(palette_cache(&[], &[5, 9]), vec![5, 9]);
assert_eq!(palette_cache(&[1, 2], &[]), vec![1, 2]);
}
#[test]
fn color_context_of_no_neighbours_orders_identity() {
let (order, ctx) = color_context(None, None, None, 4);
assert_eq!(order, [0, 1, 2, 3, 4, 5, 6, 7]);
assert_eq!(ctx, 0);
}
#[test]
fn a_dominant_neighbour_sorts_to_the_front() {
let (order, _ctx) = color_context(Some(2), None, Some(2), 4);
assert_eq!(order[0], 2);
}
}