#[must_use]
pub fn get_bits(data: &[u8], bit_pos: usize, nbits: u32) -> u32 {
let byte_index = bit_pos / 8;
let byte = data.get(byte_index).copied().unwrap_or(0);
match nbits {
8 => u32::from(byte),
16 => u32::from(byte) * 256 + u32::from(data.get(byte_index + 1).copied().unwrap_or(0)),
_ => {
let shift = 8u32
.saturating_sub(nbits)
.saturating_sub(u32::try_from(bit_pos % 8).unwrap_or(0));
(u32::from(byte) >> shift) & ((1u32 << nbits.min(31)) - 1)
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Availability {
Whole,
Partial,
Absent,
}
#[must_use]
pub fn scanline(data: &[u8], line: u32, pitch: usize) -> (Vec<u8>, Availability) {
let Some(start) = usize::try_from(line)
.ok()
.and_then(|l| l.checked_mul(pitch))
else {
return (vec![0; pitch], Availability::Absent);
};
if start >= data.len() {
return (vec![0; pitch], Availability::Absent);
}
let available = data.get(start..).unwrap_or(&[]);
if available.len() >= pitch {
return (
available.get(..pitch).unwrap_or(&[]).to_vec(),
Availability::Whole,
);
}
let mut out = vec![0u8; pitch];
if let Some(dest) = out.get_mut(..available.len()) {
dest.copy_from_slice(available);
}
(out, Availability::Partial)
}
pub fn scanline_into(data: &[u8], line: usize, pitch: usize, out: &mut [u8]) -> Availability {
let Some(start) = line.checked_mul(pitch) else {
out.fill(0);
return Availability::Absent;
};
if start >= data.len() {
out.fill(0);
return Availability::Absent;
}
let available = data.get(start..).unwrap_or(&[]);
if available.len() >= pitch {
let n = out.len().min(pitch);
if let (Some(dest), Some(src)) = (out.get_mut(..n), available.get(..n)) {
dest.copy_from_slice(src);
}
if let Some(tail) = out.get_mut(n..) {
tail.fill(0);
}
return Availability::Whole;
}
let n = out.len().min(available.len());
if let (Some(dest), Some(src)) = (out.get_mut(..n), available.get(..n)) {
dest.copy_from_slice(src);
}
if let Some(tail) = out.get_mut(n..) {
tail.fill(0);
}
Availability::Partial
}
pub fn invert_line(line: &mut [u8]) {
for b in line.iter_mut() {
*b = !*b;
}
}
#[cfg(test)]
mod tests {
#![allow(
clippy::unreadable_literal,
clippy::float_cmp,
clippy::indexing_slicing,
clippy::cast_precision_loss,
clippy::cast_possible_truncation,
reason = "test fixtures quote oracle vectors verbatim and compare exactly"
)]
use super::{Availability, get_bits, invert_line, scanline};
fn palette_index(line: &[u8], pixel: usize, components: u32, bpc: u32) -> u32 {
let mut index = 0u32;
for j in 0..components {
let bit_pos = (pixel * components as usize + j as usize) * bpc as usize;
index |= get_bits(line, bit_pos, bpc) << (j * bpc);
}
index
}
#[test]
fn bits_are_read_msb_first_and_zero_past_the_end() {
let data = [0b1011_0010u8, 0x34];
assert_eq!(get_bits(&data, 0, 1), 1);
assert_eq!(get_bits(&data, 1, 1), 0);
assert_eq!(get_bits(&data, 0, 2), 0b10);
assert_eq!(get_bits(&data, 0, 4), 0b1011);
assert_eq!(get_bits(&data, 4, 4), 0b0010);
assert_eq!(get_bits(&data, 0, 8), 0b1011_0010);
assert_eq!(get_bits(&data, 0, 16), 0xB234);
assert_eq!(get_bits(&data, 800, 8), 0);
assert_eq!(get_bits(&[], 0, 8), 0);
}
#[test]
fn a_truncated_scanline_is_zero_padded() {
let data = [1u8, 2, 3];
let (line, had) = scanline(&data, 0, 2);
assert_eq!(line, vec![1, 2]);
assert_eq!(had, Availability::Whole);
let (line, had) = scanline(&data, 1, 2);
assert_eq!(line, vec![3, 0]);
assert_eq!(had, Availability::Partial);
let (line, had) = scanline(&data, 9, 2);
assert_eq!(line, vec![0, 0]);
assert_eq!(had, Availability::Absent);
assert_eq!(scanline(&data, 2, 2).1, Availability::Absent);
assert_eq!(scanline(&[], 0, 4).1, Availability::Absent);
}
#[test]
fn inversion_is_bitwise() {
let mut line = [0b1010_1010u8, 0x00];
invert_line(&mut line);
assert_eq!(line, [0b0101_0101, 0xFF]);
}
#[test]
fn the_palette_index_puts_the_first_component_in_the_low_bits() {
let line = [0b0110_0000u8];
assert_eq!(palette_index(&line, 0, 2, 2), 0b10_01);
}
#[test]
fn a_packed_palette_lookup_and_the_general_path_agree_on_every_index() {
use crate::color::ColorSpace;
use crate::image::decode_array::DecodeMap;
for (space, components, bpc) in [
(ColorSpace::DeviceRgb, 3u32, 2u32),
(ColorSpace::DeviceCmyk, 4, 1),
] {
let n = components as usize;
let map = DecodeMap::new(Some(&space), n, bpc, None);
let entries = 1u32 << (bpc * components);
let palette: Vec<_> = (0..entries)
.map(|index| {
let mut rest = index;
let comps: Vec<f32> = (0..n)
.map(|j| {
let raw = rest % (1 << bpc);
rest /= 1 << bpc;
map.apply(j, raw as f32)
})
.collect();
space.to_rgb(&comps)
})
.collect();
for index in 0..entries {
let mut line = [0u8; 2];
for j in 0..components {
let v = (index >> (j * bpc)) & ((1 << bpc) - 1);
let bit_pos = (j * bpc) as usize;
let shift = 8 - bpc as usize - (bit_pos % 8);
line[bit_pos / 8] |= u8::try_from(v).unwrap() << shift;
}
assert_eq!(
palette_index(&line, 0, components, bpc),
index,
"{space:?} bpc {bpc}: packing and enumeration disagree"
);
let comps: Vec<f32> = (0..n)
.map(|j| {
let raw = get_bits(&line, j * bpc as usize, bpc);
map.apply(j, raw as f32)
})
.collect();
assert_eq!(
space.to_rgb(&comps),
palette[index as usize],
"{space:?} bpc {bpc}: index {index} differs between paths"
);
}
}
}
}