use crate::color::Color;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum PickId {
#[default]
Skip,
Block,
Id(u32),
}
pub fn id_to_color(id: u32) -> Color {
let r = (id & 0xFF) as f32 / 255.0;
let g = ((id >> 8) & 0xFF) as f32 / 255.0;
let b = ((id >> 16) & 0xFF) as f32 / 255.0;
Color::new([r, g, b, 1.0])
}
pub fn decode(px: u32) -> Option<u32> {
let id = px & 0x00FF_FFFF;
(id != 0).then_some(id)
}
pub fn raw_id(pick: PickId) -> Option<u32> {
match pick {
PickId::Skip => None,
PickId::Block => Some(0),
PickId::Id(n) => Some(n),
}
}
#[cfg(test)]
mod tests {
use super::*;
fn readback_u32(c: Color) -> u32 {
let px = c.to_rgba8();
((px.a as u32) << 24) | ((px.b as u32) << 16) | ((px.g as u32) << 8) | px.r as u32
}
const BOUNDARIES: [u32; 10] = [
1, 0x7F, 0xFF, 0x100, 0x101, 0xFFFF, 0x1_0000, 0x1_0001, 0xFE_FFFF, 0xFF_FFFF,
];
#[test]
fn boundary_ids_round_trip_through_the_encoded_pixel() {
for id in BOUNDARIES {
let got = decode(readback_u32(id_to_color(id)));
assert_eq!(got, Some(id), "id {id:#x} did not round-trip");
}
}
#[test]
fn every_sampled_id_across_the_24_bit_space_round_trips() {
let mut id = 1u32;
while id <= 0xFF_FFFF {
assert_eq!(decode(readback_u32(id_to_color(id))), Some(id));
id += 7919;
}
}
#[test]
fn encoded_pixel_is_opaque_with_the_id_in_the_low_24_bits() {
for id in BOUNDARIES {
assert_eq!(
readback_u32(id_to_color(id)),
(0xFF << 24) | (id & 0x00FF_FFFF),
"id {id:#x} encoded to an unexpected pixel"
);
}
}
#[test]
fn id_zero_encodes_the_same_pixel_as_block() {
let block = id_to_color(raw_id(PickId::Block).unwrap());
let id_zero = id_to_color(raw_id(PickId::Id(0)).unwrap());
assert_eq!(raw_id(PickId::Id(0)), raw_id(PickId::Block));
assert_eq!(readback_u32(id_zero), readback_u32(block));
assert_eq!(readback_u32(block), 0xFF00_0000);
assert_eq!(decode(readback_u32(block)), None);
}
#[test]
fn ids_past_24_bits_lose_their_high_byte() {
assert_eq!(
readback_u32(id_to_color(0x0100_0001)),
readback_u32(id_to_color(1))
);
assert_eq!(decode(readback_u32(id_to_color(0x0100_0001))), Some(1));
assert_eq!(decode(readback_u32(id_to_color(u32::MAX))), Some(0xFF_FFFF));
assert_eq!(decode(readback_u32(id_to_color(0x0100_0000))), None);
}
#[test]
fn decode_ignores_the_alpha_byte() {
assert_eq!(decode(0x0000_1234), Some(0x1234));
assert_eq!(decode(0xFF00_1234), Some(0x1234));
assert_eq!(decode(0x0000_0000), None);
assert_eq!(decode(0xFF00_0000), None);
}
#[test]
fn raw_id_omits_skip_and_maps_block_to_zero() {
assert_eq!(raw_id(PickId::Skip), None);
assert_eq!(raw_id(PickId::Block), Some(0));
assert_eq!(raw_id(PickId::Id(7)), Some(7));
}
#[test]
fn default_pick_id_stays_out_of_the_hitmap() {
assert_eq!(PickId::default(), PickId::Skip);
assert_eq!(raw_id(PickId::default()), None);
}
}