use super::{LABS_A, LABS_B, LABS_L, ciede2000::delta_e_2000_sq};
use crate::generated::{LUT_CELL_INDICES, LUT_CELL_OFFSETS};
pub fn nearest_idx(rgb: [u8; 3], query: [f32; 3]) -> usize {
let cell =
(((rgb[0] >> 3) as usize) << 10) | (((rgb[1] >> 3) as usize) << 5) | ((rgb[2] >> 3) as usize);
let start = LUT_CELL_OFFSETS[cell] as usize;
let end = LUT_CELL_OFFSETS[cell + 1] as usize;
let first = LUT_CELL_INDICES[start] as usize;
let mut best_idx = first;
let mut best_d2 = delta_e_2000_sq([LABS_L[first], LABS_A[first], LABS_B[first]], query);
for &idx in &LUT_CELL_INDICES[start + 1..end] {
let i = idx as usize;
let d2 = delta_e_2000_sq([LABS_L[i], LABS_A[i], LABS_B[i]], query);
if d2 < best_d2 {
best_d2 = d2;
best_idx = i;
}
}
best_idx
}
#[cfg(test)]
mod tests {
use super::*;
use crate::generated::COLORS;
#[test]
fn lookup_returns_valid_index_across_grid() {
for r in (0..=255u32).step_by(64) {
for g in (0..=255u32).step_by(64) {
for b in (0..=255u32).step_by(64) {
let rgb = [r as u8, g as u8, b as u8];
let q = crate::rgb_to_lab(rgb);
let idx = nearest_idx(rgb, q);
assert!(
idx < COLORS.len(),
"rgb={rgb:?} idx={idx} >= {}",
COLORS.len()
);
}
}
}
}
#[test]
#[cfg_attr(miri, ignore = "949-entry palette × CIEDE2000 too slow under miri")]
fn idempotent_for_palette_rgb() {
for entry in COLORS.iter() {
let rgb = entry.rgb();
let q = crate::rgb_to_lab(rgb);
let idx = nearest_idx(rgb, q);
let returned = COLORS[idx];
assert_eq!(
returned.lab(),
entry.lab(),
"entry {:?} (rgb={:?}) returned {:?} via LUT",
entry.name(),
rgb,
returned.name(),
);
}
}
}