do not panic if bitmap has invalid parameters

This commit is contained in:
Vinzenz Schroeter 2025-03-08 10:06:25 +01:00
parent 427dd93088
commit 9bff9bd346
3 changed files with 84 additions and 82 deletions

View file

@ -27,23 +27,30 @@ pub struct Bitmap {
impl Bitmap {
/// Creates a new [Bitmap] with the specified dimensions.
/// The initial state of the contained pixels is false.
///
/// The width has to be a multiple of [TILE_SIZE], otherwise this function returns None.
///
/// # Arguments
///
/// - `width`: size in pixels in x-direction
/// - `height`: size in pixels in y-direction
///
/// returns: [Bitmap] initialized to all pixels off
///
/// # Panics
///
/// - when the width is not dividable by 8
pub fn new(width: usize, height: usize) -> Self {
assert_eq!(
width % 8,
0,
"width must be a multiple of 8, but is {width}"
);
#[must_use]
pub fn new(width: usize, height: usize) -> Option<Self> {
if width % 8 != 0 {
return None;
}
Some(Self::new_unchecked(width, height))
}
/// Creates a new pixel grid with the size of the whole screen.
#[must_use]
pub fn max_sized() -> Self {
Self::new_unchecked(PIXEL_WIDTH, PIXEL_HEIGHT)
}
#[must_use]
fn new_unchecked(width: usize, height: usize) -> Self {
Self {
width,
height,
@ -51,71 +58,57 @@ impl Bitmap {
}
}
/// Creates a new pixel grid with the size of the whole screen.
#[must_use]
pub fn max_sized() -> Self {
Self::new(PIXEL_WIDTH, PIXEL_HEIGHT)
}
/// Loads a [Bitmap] with the specified dimensions from the provided data.
///
/// The data cannot be loaded on the following cases:
/// - when the dimensions and data size do not match exactly.
/// - when the width is not dividable by 8
///
/// In those cases, an Err is returned.
///
/// Otherwise, this returns a [Bitmap] that contains a copy of the provided data
///
/// # Arguments
///
/// - `width`: size in pixels in x-direction
/// - `height`: size in pixels in y-direction
///
/// returns: [Bitmap] that contains a copy of the provided data
///
/// # Panics
///
/// - when the dimensions and data size do not match exactly.
/// - when the width is not dividable by 8
#[must_use]
pub fn load(width: usize, height: usize, data: &[u8]) -> Self {
assert_eq!(
width % 8,
0,
"width must be a multiple of 8, but is {width}"
);
assert_eq!(
data.len(),
height * width / 8,
"data length must match dimensions, with 8 pixels per byte."
);
Self {
pub fn load(width: usize, height: usize, data: &[u8]) -> Result<Self, LoadBitmapError> {
if width % 8 != 0 {
return Err(LoadBitmapError::InvalidWidth)
}
if data.len() != height * width / 8 {
return Err(LoadBitmapError::InvalidDataSize)
}
Ok(Self {
width,
height,
bit_vec: BitVec::from_slice(data),
}
})
}
/// Creates a [Bitmap] with the specified width from the provided [BitVec] without copying it.
///
/// returns: [Bitmap] that contains the provided data.
///
/// # Panics
///
/// - when the bitvec size is not dividable by the provided width
/// The data cannot be loaded on the following cases:
/// - when the data size is not divisible by the width (incomplete rows)
/// - when the width is not dividable by 8
#[must_use]
pub fn from_bitvec(width: usize, bit_vec: BitVec) -> Self {
assert_eq!(
width % 8,
0,
"width must be a multiple of 8, but is {width}"
);
///
/// In those cases, an Err is returned.
/// Otherwise, this returns a [Bitmap] that contains the provided data.
pub fn from_bitvec(width: usize, bit_vec: BitVec) -> Result<Self, LoadBitmapError> {
if width % 8 != 0 {
return Err(LoadBitmapError::InvalidWidth)
}
let len = bit_vec.len();
let height = len / width;
assert_eq!(
0,
len % width,
"dimension mismatch - len {len} is not dividable by {width}"
);
Self {
if len % width != 0 {
return Err(LoadBitmapError::InvalidDataSize)
}
Ok(Self {
width,
height,
bit_vec,
}
})
}
/// Iterate over all cells in [Bitmap].
@ -123,7 +116,7 @@ impl Bitmap {
/// Order is equivalent to the following loop:
/// ```
/// # use servicepoint::{Bitmap, Grid};
/// # let grid = Bitmap::new(8,2);
/// # let grid = Bitmap::new(8, 2).unwrap();
/// for y in 0..grid.height() {
/// for x in 0..grid.width() {
/// grid.get(x, y);
@ -139,7 +132,7 @@ impl Bitmap {
/// Order is equivalent to the following loop:
/// ```
/// # use servicepoint::{Bitmap, Grid};
/// # let mut grid = Bitmap::new(8,2);
/// # let mut grid = Bitmap::new(8, 2).unwrap();
/// # let value = false;
/// for y in 0..grid.height() {
/// for x in 0..grid.width() {
@ -151,7 +144,7 @@ impl Bitmap {
/// # Example
/// ```
/// # use servicepoint::{Bitmap, Grid};
/// # let mut grid = Bitmap::new(8,2);
/// # let mut grid = Bitmap::new(8, 2).unwrap();
/// # let value = false;
/// for (index, mut pixel) in grid.iter_mut().enumerate() {
/// pixel.set(index % 2 == 0)
@ -236,18 +229,19 @@ impl From<Bitmap> for BitVec {
}
}
impl From<&ValueGrid<bool>> for Bitmap {
impl TryFrom<&ValueGrid<bool>> for Bitmap {
type Error = ();
/// Converts a grid of [bool]s into a [Bitmap].
///
/// # Panics
///
/// - when the width of `value` is not dividable by 8
fn from(value: &ValueGrid<bool>) -> Self {
let mut result = Self::new(value.width(), value.height());
/// Returns Err if the width of `value` is not dividable by 8
fn try_from(value: &ValueGrid<bool>) -> Result<Self, Self::Error> {
let mut result = Self::new(value.width(), value.height())
.ok_or(())?;
for (mut to, from) in result.iter_mut().zip(value.iter()) {
*to = *from;
}
result
Ok(result)
}
}
@ -282,13 +276,21 @@ impl<'t> Iterator for IterRows<'t> {
}
}
#[derive(thiserror::Error, Debug)]
pub enum LoadBitmapError {
#[error("The provided width is not divisible by 8.")]
InvalidWidth,
#[error("The provided data has an incorrect size for the provided dimensions.")]
InvalidDataSize
}
#[cfg(test)]
mod tests {
use crate::{BitVec, Bitmap, DataRef, Grid, ValueGrid};
#[test]
fn fill() {
let mut grid = Bitmap::new(8, 2);
let mut grid = Bitmap::new(8, 2).unwrap();
assert_eq!(grid.data_ref(), [0x00, 0x00]);
grid.fill(true);
@ -300,7 +302,7 @@ mod tests {
#[test]
fn get_set() {
let mut grid = Bitmap::new(8, 2);
let mut grid = Bitmap::new(8, 2).unwrap();
assert!(!grid.get(0, 0));
assert!(!grid.get(1, 1));
@ -315,7 +317,7 @@ mod tests {
#[test]
fn load() {
let mut grid = Bitmap::new(8, 3);
let mut grid = Bitmap::new(8, 3).unwrap();
for x in 0..grid.width {
for y in 0..grid.height {
grid.set(x, y, (x + y) % 2 == 0);
@ -326,33 +328,33 @@ mod tests {
let data: Vec<u8> = grid.into();
let grid = Bitmap::load(8, 3, &data);
let grid = Bitmap::load(8, 3, &data).unwrap();
assert_eq!(grid.data_ref(), [0xAA, 0x55, 0xAA]);
}
#[test]
#[should_panic]
fn out_of_bounds_x() {
let vec = Bitmap::new(8, 2);
let vec = Bitmap::new(8, 2).unwrap();
vec.get(8, 1);
}
#[test]
#[should_panic]
fn out_of_bounds_y() {
let mut vec = Bitmap::new(8, 2);
let mut vec = Bitmap::new(8, 2).unwrap();
vec.set(1, 2, false);
}
#[test]
fn iter() {
let grid = Bitmap::new(8, 2);
let grid = Bitmap::new(8, 2).unwrap();
assert_eq!(16, grid.iter().count())
}
#[test]
fn iter_rows() {
let grid = Bitmap::load(8, 2, &[0x04, 0x40]);
let grid = Bitmap::load(8, 2, &[0x04, 0x40]).unwrap();
let mut iter = grid.iter_rows();
assert_eq!(iter.next().unwrap().count_ones(), 1);
@ -362,7 +364,7 @@ mod tests {
#[test]
fn iter_mut() {
let mut grid = Bitmap::new(8, 2);
let mut grid = Bitmap::new(8, 2).unwrap();
for (index, mut pixel) in grid.iter_mut().enumerate() {
pixel.set(index % 2 == 0);
}
@ -371,7 +373,7 @@ mod tests {
#[test]
fn data_ref_mut() {
let mut grid = Bitmap::new(8, 2);
let mut grid = Bitmap::new(8, 2).unwrap();
let data = grid.data_ref_mut();
data[1] = 0x0F;
assert!(grid.get(7, 1));
@ -379,7 +381,7 @@ mod tests {
#[test]
fn to_bitvec() {
let mut grid = Bitmap::new(8, 2);
let mut grid = Bitmap::new(8, 2).unwrap();
grid.set(0, 0, true);
let bitvec: BitVec = grid.into();
assert_eq!(bitvec.as_raw_slice(), [0x80, 0x00]);
@ -392,7 +394,7 @@ mod tests {
1,
&[true, false, true, false, true, false, true, false],
);
let converted = Bitmap::from(&original);
let converted = Bitmap::try_from(&original).unwrap();
let reconverted = ValueGrid::from(&converted);
assert_eq!(original, reconverted);
}

View file

@ -150,7 +150,7 @@ impl BitmapLinearWin {
tile_w as usize * TILE_SIZE,
pixel_h as usize,
&payload,
),
).unwrap(),
compression,
})
}

View file

@ -487,7 +487,7 @@ mod tests {
for compression in all_compressions().iter().copied() {
let p: Packet = commands::BitmapLinearWin {
origin: Origin::new(16, 8),
bitmap: Bitmap::new(8, 8),
bitmap: Bitmap::new(8, 8).unwrap(),
compression,
}
.into();