#[cfg(feature = "splash-build")]
pub mod build;
pub const MAGIC: [u8; 4] = *b"RLE5";
const HEADER: usize = 12;
#[derive(Clone, Copy, Debug)]
pub struct Image<'a> {
width: usize,
height: usize,
runs: &'a [u8],
}
impl<'a> Image<'a> {
pub const fn new(bytes: &'a [u8]) -> Self {
assert!(
bytes.len() >= HEADER,
"splash image: shorter than its header"
);
let mut i = 0;
while i < MAGIC.len() {
assert!(bytes[i] == MAGIC[i], "splash image: no RLE5 magic");
i += 1;
}
let width = u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]) as usize;
let height = u32::from_le_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]) as usize;
let (_, runs) = bytes.split_at(HEADER);
assert!(
runs.len() % 3 == 0,
"splash image: runs are not whole triples"
);
let mut total = 0;
let mut i = 0;
while i < runs.len() {
assert!(runs[i] != 0, "splash image: a run of zero pixels");
total += runs[i] as usize;
i += 3;
}
assert!(
total == width * height,
"splash image: runs do not cover width * height"
);
Self {
width,
height,
runs,
}
}
pub const fn width(&self) -> usize {
self.width
}
pub const fn height(&self) -> usize {
self.height
}
pub fn pixels(&self) -> impl Iterator<Item = u32> + 'a {
self.runs
.as_chunks::<3>()
.0
.iter()
.flat_map(|&[count, lo, hi]| {
core::iter::repeat_n(expand(u16::from_le_bytes([lo, hi])), usize::from(count))
})
}
}
const fn expand(pixel: u16) -> u32 {
let (r, g, b) = ((pixel >> 11) & 0x1F, (pixel >> 5) & 0x3F, pixel & 0x1F);
let r = (r << 3) | (r >> 2);
let g = (g << 2) | (g >> 4);
let b = (b << 3) | (b >> 2);
((r as u32) << 16) | ((g as u32) << 8) | b as u32
}
#[cfg(test)]
mod tests {
use super::*;
fn image(width: u32, height: u32, runs: &[u8]) -> alloc::vec::Vec<u8> {
let mut bytes = MAGIC.to_vec();
bytes.extend_from_slice(&width.to_le_bytes());
bytes.extend_from_slice(&height.to_le_bytes());
bytes.extend_from_slice(runs);
bytes
}
#[test]
fn expands_runs_in_order() {
let bytes = image(3, 1, &[2, 0xFF, 0xFF, 1, 0x00, 0xF8]);
let image = Image::new(&bytes);
assert_eq!((image.width(), image.height()), (3, 1));
let pixels: alloc::vec::Vec<u32> = image.pixels().collect();
assert_eq!(pixels, [0xFF_FFFF, 0xFF_FFFF, 0xFF_0000]);
}
#[test]
fn expand_keeps_saturated_channels_saturated() {
assert_eq!(expand(0x0000), 0x00_0000);
assert_eq!(expand(0xFFFF), 0xFF_FFFF);
assert_eq!(expand(0x07E0), 0x00_FF00);
assert_eq!(expand(0x001F), 0x00_00FF);
}
#[test]
#[should_panic(expected = "no RLE5 magic")]
fn refuses_a_file_without_the_magic() {
let mut bytes = image(1, 1, &[1, 0, 0]);
bytes[0] = b'P';
Image::new(&bytes);
}
#[test]
#[should_panic(expected = "do not cover")]
fn refuses_runs_that_do_not_cover_the_image() {
Image::new(&image(2, 2, &[3, 0, 0]));
}
#[test]
#[should_panic(expected = "whole triples")]
fn refuses_a_partial_triple() {
Image::new(&image(1, 1, &[1, 0, 0, 1]));
}
#[test]
#[should_panic(expected = "zero pixels")]
fn refuses_a_zero_length_run() {
Image::new(&image(1, 1, &[0, 0, 0, 1, 0, 0]));
}
}