#![cfg_attr(not(feature = "std"), no_std)]
#![deny(unsafe_code)]
#[cfg(not(feature = "std"))]
extern crate alloc;
#[cfg(not(feature = "std"))]
use alloc::{format, vec, vec::Vec};
#[cfg(feature = "std")]
use std::{format, vec, vec::Vec};
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Bitmap {
pub width: u32,
pub height: u32,
pub data: Vec<u8>,
}
impl Bitmap {
pub fn new(width: u32, height: u32) -> Self {
let stride = Self::compute_row_stride(width);
Bitmap {
width,
height,
data: vec![0u8; stride * height as usize],
}
}
#[inline]
pub fn row_stride(&self) -> usize {
Self::compute_row_stride(self.width)
}
fn compute_row_stride(width: u32) -> usize {
(width as usize).div_ceil(8)
}
#[inline]
pub fn get(&self, x: u32, y: u32) -> bool {
debug_assert!(x < self.width && y < self.height);
let stride = self.row_stride();
let byte_idx = y as usize * stride + (x as usize / 8);
let bit_idx = 7 - (x % 8);
(self.data[byte_idx] >> bit_idx) & 1 == 1
}
pub fn set(&mut self, x: u32, y: u32, val: bool) {
debug_assert!(x < self.width && y < self.height);
let stride = self.row_stride();
let byte_idx = y as usize * stride + (x as usize / 8);
let bit_idx = 7 - (x % 8);
if val {
self.data[byte_idx] |= 1 << bit_idx;
} else {
self.data[byte_idx] &= !(1 << bit_idx);
}
}
pub fn set_black(&mut self, x: u32, y: u32) {
let stride = self.row_stride();
let byte_idx = y as usize * stride + (x as usize / 8);
let bit_idx = 7 - (x % 8);
self.data[byte_idx] |= 1 << bit_idx;
}
pub fn dilate(&self) -> Bitmap {
let mut out = self.clone();
for y in 0..self.height {
for x in 0..self.width {
if self.get(x, y) {
if x > 0 {
out.set_black(x - 1, y);
}
if x + 1 < self.width {
out.set_black(x + 1, y);
}
if y > 0 {
out.set_black(x, y - 1);
}
if y + 1 < self.height {
out.set_black(x, y + 1);
}
}
}
}
out
}
pub fn to_pbm(&self) -> Vec<u8> {
let header = format!("P4\n{} {}\n", self.width, self.height);
let stride = self.row_stride();
let mut out = Vec::with_capacity(header.len() + stride * self.height as usize);
out.extend_from_slice(header.as_bytes());
for y in 0..self.height as usize {
let row_start = y * stride;
out.extend_from_slice(&self.data[row_start..row_start + stride]);
}
out
}
pub fn dilate_n(self, passes: u32) -> Self {
if passes == 0 {
return self;
}
let width = self.width;
let height = self.height;
let stride = self.row_stride();
let h = height as usize;
let mut src = self.data;
let mut dst = vec![0u8; stride * h];
for _ in 0..passes {
for b in dst.iter_mut() {
*b = 0;
}
for y in 0..h {
let row = y * stride;
for i in 0..stride {
let v = src[row + i];
if v == 0 {
continue;
}
dst[row + i] |= v; dst[row + i] |= v >> 1; dst[row + i] |= v << 1;
if i + 1 < stride {
dst[row + i + 1] |= (v & 0x01) << 7;
}
if i > 0 {
dst[row + i - 1] |= (v & 0x80) >> 7;
}
if y + 1 < h {
dst[(y + 1) * stride + i] |= v;
}
if y > 0 {
dst[(y - 1) * stride + i] |= v;
}
}
}
core::mem::swap(&mut src, &mut dst);
}
Bitmap {
width,
height,
data: src,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_bitmap_is_all_white() {
let bm = Bitmap::new(10, 5);
for y in 0..5 {
for x in 0..10 {
assert!(!bm.get(x, y));
}
}
}
#[test]
fn set_and_get_roundtrip() {
let mut bm = Bitmap::new(16, 8);
bm.set(0, 0, true);
bm.set(15, 0, true);
bm.set(7, 3, true);
bm.set(0, 7, true);
bm.set(15, 7, true);
assert!(bm.get(0, 0));
assert!(bm.get(15, 0));
assert!(bm.get(7, 3));
assert!(bm.get(0, 7));
assert!(bm.get(15, 7));
assert!(!bm.get(1, 0));
assert!(!bm.get(14, 0));
assert!(!bm.get(6, 3));
assert!(!bm.get(8, 3));
}
#[test]
fn set_and_clear() {
let mut bm = Bitmap::new(8, 1);
bm.set(3, 0, true);
assert!(bm.get(3, 0));
bm.set(3, 0, false);
assert!(!bm.get(3, 0));
}
#[test]
fn non_byte_aligned_width() {
let mut bm = Bitmap::new(10, 2);
assert_eq!(bm.row_stride(), 2);
bm.set(9, 0, true);
bm.set(9, 1, true);
assert!(bm.get(9, 0));
assert!(bm.get(9, 1));
}
#[test]
fn to_pbm_format() {
let mut bm = Bitmap::new(8, 2);
bm.set(0, 0, true);
bm.set(7, 0, true);
for x in 0..8 {
bm.set(x, 1, true);
}
let pbm = bm.to_pbm();
let hdr = b"P4\n8 2\n";
assert_eq!(&pbm[..hdr.len()], hdr);
assert_eq!(pbm[hdr.len()], 0x81);
assert_eq!(pbm[hdr.len() + 1], 0xFF);
assert_eq!(pbm.len(), hdr.len() + 2);
}
#[test]
fn to_pbm_non_byte_aligned() {
let mut bm = Bitmap::new(3, 1);
bm.set(0, 0, true);
bm.set(2, 0, true);
let pbm = bm.to_pbm();
let hdr = b"P4\n3 1\n";
assert_eq!(&pbm[..hdr.len()], hdr);
assert_eq!(pbm[hdr.len()], 0xA0);
}
fn set_pixels(bm: &Bitmap) -> Vec<(u32, u32)> {
let mut out = Vec::new();
for y in 0..bm.height {
for x in 0..bm.width {
if bm.get(x, y) {
out.push((x, y));
}
}
}
out
}
#[test]
fn dilate_n_zero_passes_is_identity() {
let mut bm = Bitmap::new(8, 8);
bm.set(3, 3, true);
let orig = set_pixels(&bm);
let result = set_pixels(&bm.clone().dilate_n(0));
assert_eq!(orig, result);
}
#[test]
fn dilate_n_one_pass_matches_dilate() {
let mut bm = Bitmap::new(16, 16);
bm.set(7, 7, true);
bm.set(0, 0, true);
bm.set(15, 15, true);
let expected = set_pixels(&bm.dilate());
let got = set_pixels(&bm.clone().dilate_n(1));
assert_eq!(expected, got);
}
#[test]
fn dilate_n_two_passes_matches_dilate_twice() {
let mut bm = Bitmap::new(20, 20);
bm.set(10, 10, true);
bm.set(1, 1, true);
let expected = set_pixels(&bm.dilate().dilate());
let got = set_pixels(&bm.clone().dilate_n(2));
assert_eq!(expected, got);
}
#[test]
fn dilate_n_three_passes_matches_dilate_three_times() {
let mut bm = Bitmap::new(20, 20);
bm.set(10, 10, true);
let expected = set_pixels(&bm.dilate().dilate().dilate());
let got = set_pixels(&bm.clone().dilate_n(3));
assert_eq!(expected, got);
}
#[test]
fn dilate_n_cross_byte_boundary() {
let mut bm = Bitmap::new(16, 1);
bm.set(7, 0, true);
let result = bm.clone().dilate_n(1);
assert!(result.get(6, 0), "x-1 neighbour");
assert!(result.get(7, 0), "original");
assert!(result.get(8, 0), "x+1 neighbour (cross-byte)");
let mut bm2 = Bitmap::new(16, 1);
bm2.set(8, 0, true);
let result2 = bm2.clone().dilate_n(1);
assert!(result2.get(7, 0), "x-1 neighbour (cross-byte)");
assert!(result2.get(8, 0), "original");
assert!(result2.get(9, 0), "x+1 neighbour");
}
#[test]
fn dilate_n_boundary_pixels_dont_wrap() {
let mut bm = Bitmap::new(16, 16);
bm.set(0, 0, true);
let result = bm.dilate_n(1);
assert!(result.get(0, 0));
assert!(result.get(1, 0));
assert!(result.get(0, 1));
assert!(!result.get(15, 15));
}
}