use core::{
cmp::{max, min},
convert::Infallible,
};
use embedded_graphics::{
pixelcolor::BinaryColor,
prelude::{Dimensions, DrawTarget, Point, Size},
primitives::Rectangle,
Pixel,
};
#[derive(Clone)]
pub struct BinaryBuffer<const L: usize> {
size: Size,
bytes_per_row: usize,
data: [u8; L],
}
pub const fn binary_buffer_length(size: Size) -> usize {
(size.width as usize / 8) * size.height as usize
}
impl<const L: usize> BinaryBuffer<L> {
pub fn new(dimensions: Size) -> Self {
debug_assert_eq!(
dimensions.width % 8,
0,
"Width must be a multiple of 8 for binary packing."
);
debug_assert_eq!(
binary_buffer_length(dimensions),
L,
"Size must match given dimensions"
);
Self {
bytes_per_row: dimensions.width as usize / 8,
size: dimensions,
data: [0; L],
}
}
pub fn data(&self) -> &[u8] {
&self.data
}
}
impl<const L: usize> Dimensions for BinaryBuffer<L> {
fn bounding_box(&self) -> Rectangle {
Rectangle::new(Point::zero(), self.size)
}
}
impl<const L: usize> DrawTarget for BinaryBuffer<L> {
type Color = BinaryColor;
type Error = Infallible;
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = Pixel<Self::Color>>,
{
for Pixel(point, color) in pixels.into_iter() {
if point.x < 0
|| point.x >= self.size.width as i32
|| point.y < 0
|| point.y >= self.size.height as i32
{
continue; }
let byte_index = (point.x as usize) / 8 + (point.y as usize * self.bytes_per_row);
let bit_index = (point.x as usize) % 8;
if color == BinaryColor::On {
self.data[byte_index] |= 0x80 >> bit_index;
} else {
self.data[byte_index] &= !(0x80 >> bit_index);
}
}
Ok(())
}
fn fill_contiguous<I>(&mut self, area: &Rectangle, colors: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = Self::Color>,
{
let drawable_area = self.bounding_box().intersection(area);
if drawable_area.size.width == 0 || drawable_area.size.height == 0 {
return Ok(()); }
let y_start = area.top_left.y;
let y_end = area.top_left.y + area.size.height as i32;
let x_start = area.top_left.x;
let x_end = area.top_left.x + area.size.width as i32;
let mut colors_iter = colors.into_iter();
let mut byte_index = max(y_start, 0) as usize * self.bytes_per_row;
let row_start_byte_offset = max(x_start, 0) as usize / 8;
let row_end_byte_offset =
self.bytes_per_row - (min(x_end, self.size.width as i32) as usize / 8);
for y in y_start..y_end {
if y < 0 || y >= self.size.height as i32 {
for _ in x_start..x_end {
colors_iter.next();
}
continue;
}
byte_index += row_start_byte_offset;
let mut bit_index = (max(x_start, 0) as usize) % 8;
for x in x_start..x_end {
if x < 0 || x >= self.size.width as i32 {
colors_iter.next();
continue;
}
let Some(color) = colors_iter.next() else {
return Ok(());
};
if color == BinaryColor::On {
self.data[byte_index] |= 0x80 >> bit_index;
} else {
self.data[byte_index] &= !(0x80 >> bit_index);
}
bit_index += 1;
if bit_index == 8 {
byte_index += 1;
bit_index = 0;
}
}
byte_index += row_end_byte_offset;
}
Ok(())
}
fn fill_solid(&mut self, area: &Rectangle, color: Self::Color) -> Result<(), Self::Error> {
let drawable_area = self.bounding_box().intersection(area);
if drawable_area.size.width == 0 || drawable_area.size.height == 0 {
return Ok(()); }
let y_start = drawable_area.top_left.y;
let y_end = drawable_area.top_left.y + drawable_area.size.height as i32;
let x_start = drawable_area.top_left.x;
let x_end = drawable_area.top_left.x + drawable_area.size.width as i32;
let x_full_bytes_start = min(x_start + x_start % 8, x_end);
let x_full_bytes_end = max(x_end - (x_end % 8), x_start);
let num_full_bytes_per_row = (x_full_bytes_end - x_full_bytes_start) / 8;
let mut byte_index = y_start as usize * self.bytes_per_row;
let row_start_byte_offset = x_start as usize / 8;
let row_end_byte_offset = self.bytes_per_row - (x_end as usize / 8);
for _y in y_start..y_end {
byte_index += row_start_byte_offset;
let mut bit_index = (x_start as usize) % 8;
macro_rules! set_next_bit {
() => {
if color == BinaryColor::On {
self.data[byte_index] |= 0x80 >> bit_index;
} else {
self.data[byte_index] &= !(0x80 >> bit_index);
}
bit_index += 1;
if bit_index == 8 {
byte_index += 1;
bit_index = 0;
}
};
}
if num_full_bytes_per_row == 0 {
for _x in x_start..x_end {
set_next_bit!();
}
} else {
for _x in x_start..x_full_bytes_start {
set_next_bit!();
}
for _ in 0..num_full_bytes_per_row {
if color == BinaryColor::On {
self.data[byte_index] = 0xFF;
} else {
self.data[byte_index] = 0x00;
}
byte_index += 1;
}
bit_index = x_full_bytes_end as usize % 8;
for _x in x_full_bytes_end..x_end {
set_next_bit!();
}
}
byte_index += row_end_byte_offset;
}
Ok(())
}
}
pub trait Rotation {
fn inverse(&self) -> Self;
fn rotate_size(&self, size: Size) -> Size;
fn rotate_point(&self, point: Point, bounds: Size) -> Point;
fn rotate_rectangle(&self, rectangle: Rectangle, bounds: Size) -> Rectangle;
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Rotate {
R90,
R180,
R270,
}
impl Rotation for Rotate {
fn inverse(&self) -> Self {
match self {
Rotate::R90 => Rotate::R270,
Rotate::R180 => Rotate::R180,
Rotate::R270 => Rotate::R90,
}
}
fn rotate_size(&self, size: Size) -> Size {
match self {
Rotate::R90 | Rotate::R270 => Size::new(size.height, size.width),
Rotate::R180 => size,
}
}
fn rotate_point(&self, point: Point, source_bounds: Size) -> Point {
match self {
Rotate::R90 => Point::new(source_bounds.height as i32 - point.y - 1, point.x),
Rotate::R180 => Point::new(
source_bounds.width as i32 - point.x - 1,
source_bounds.height as i32 - point.y - 1,
),
Rotate::R270 => Point::new(point.y, source_bounds.width as i32 - point.x - 1),
}
}
fn rotate_rectangle(&self, rectangle: Rectangle, source_bounds: Size) -> Rectangle {
match self {
Rotate::R90 => {
let old_bottom_left =
rectangle.top_left + Point::new(0, rectangle.size.height as i32 - 1);
let new_top_left = self.rotate_point(old_bottom_left, source_bounds);
Rectangle::new(new_top_left, self.rotate_size(rectangle.size))
}
Rotate::R180 => {
let old_bottom_right = rectangle.top_left + rectangle.size - Point::new(1, 1);
let new_top_left = self.rotate_point(old_bottom_right, source_bounds);
Rectangle::new(new_top_left, self.rotate_size(rectangle.size))
}
Rotate::R270 => {
let old_top_right =
rectangle.top_left + Point::new(rectangle.size.width as i32 - 1, 0);
let new_top_left = self.rotate_point(old_top_right, source_bounds);
Rectangle::new(new_top_left, self.rotate_size(rectangle.size))
}
}
}
}
#[derive(Clone)]
pub struct RotatedBuffer<B: DrawTarget, R: Rotation> {
buffer: B,
rotation: R,
}
impl<B: DrawTarget, R: Rotation> RotatedBuffer<B, R> {
pub fn new(buffer: B, rotation: R) -> Self {
Self { buffer, rotation }
}
pub fn inner(&self) -> &B {
&self.buffer
}
}
impl<B: DrawTarget, R: Rotation> Dimensions for RotatedBuffer<B, R> {
fn bounding_box(&self) -> Rectangle {
let internal = self.buffer.bounding_box();
self.rotation
.inverse()
.rotate_rectangle(internal, internal.size)
}
}
impl<B: DrawTarget, R: Rotation> DrawTarget for RotatedBuffer<B, R> {
type Color = B::Color;
type Error = B::Error;
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = Pixel<Self::Color>>,
{
let source_bounds = self
.rotation
.inverse()
.rotate_size(self.buffer.bounding_box().size);
let rotated_pixels = pixels.into_iter().map(|Pixel(point, color)| {
let rotated_point = self.rotation.rotate_point(point, source_bounds);
Pixel(rotated_point, color)
});
self.buffer.draw_iter(rotated_pixels)
}
}
#[cfg(test)]
mod tests {
use super::*;
use embedded_graphics::pixelcolor::BinaryColor;
#[test]
fn test_binary_buffer_draw_iter_singles() {
const SIZE: Size = Size::new(16, 4);
const BUFFER_LENGTH: usize = binary_buffer_length(SIZE);
let mut buffer = BinaryBuffer::<{ BUFFER_LENGTH }>::new(SIZE);
buffer
.draw_iter([Pixel(Point::new(0, 0), BinaryColor::On)])
.unwrap();
assert_eq!(buffer.data[0], 0b10000000);
buffer
.draw_iter([Pixel(Point::new(10, 2), BinaryColor::On)])
.unwrap();
assert_eq!(buffer.data[5], 0b00100000);
buffer
.draw_iter([Pixel(Point::new(15, 3), BinaryColor::On)])
.unwrap();
assert_eq!(buffer.data[7], 0b1);
}
#[test]
fn test_binary_buffer_draw_iter_multiple() {
const SIZE: Size = Size::new(16, 4);
const BUFFER_LENGTH: usize = binary_buffer_length(SIZE);
let mut buffer = BinaryBuffer::<{ BUFFER_LENGTH }>::new(SIZE);
buffer
.draw_iter([
Pixel(Point::new(1, 0), BinaryColor::On),
Pixel(Point::new(2, 0), BinaryColor::On),
Pixel(Point::new(3, 0), BinaryColor::On),
Pixel(Point::new(2, 0), BinaryColor::Off),
Pixel(Point::new(1, 1), BinaryColor::On),
])
.unwrap();
assert_eq!(buffer.data[0], 0b01010000);
assert_eq!(buffer.data[2], 0b01000000);
}
#[test]
fn test_binary_buffer_draw_iter_out_of_bounds() {
const SIZE: Size = Size::new(16, 4);
const BUFFER_LENGTH: usize = binary_buffer_length(SIZE);
let mut buffer = BinaryBuffer::<{ BUFFER_LENGTH }>::new(SIZE);
let previous_data = buffer.data;
buffer
.draw_iter([
Pixel(Point::new(-1, 0), BinaryColor::On),
Pixel(Point::new(0, -1), BinaryColor::On),
Pixel(Point::new(16, 0), BinaryColor::On),
Pixel(Point::new(0, 4), BinaryColor::On),
])
.unwrap();
assert_eq!(
buffer.data, previous_data,
"Data should not change when drawing out-of-bounds pixels."
);
}
#[cfg(debug_assertions)]
#[test]
#[should_panic]
fn test_binary_buffer_must_have_aligned_width() {
let _ = BinaryBuffer::<16>::new(Size::new(10, 10));
}
#[cfg(debug_assertions)]
#[test]
#[should_panic]
fn test_binary_buffer_size_must_match_dimensions() {
let _ = BinaryBuffer::<16>::new(Size::new(16, 10));
}
#[test]
fn test_binary_buffer_fill_continguous() {
const SIZE: Size = Size::new(24, 8);
const BUFFER_LENGTH: usize = binary_buffer_length(SIZE);
let mut buffer = BinaryBuffer::<{ BUFFER_LENGTH }>::new(SIZE);
buffer
.fill_contiguous(
&Rectangle::new(Point::new(-4, -4), Size::new(8, 8)),
[BinaryColor::On; 8 * 8],
)
.unwrap();
buffer
.fill_contiguous(
&Rectangle::new(Point::new(6, 2), Size::new(12, 4)),
[BinaryColor::On; 12 * 4],
)
.unwrap();
buffer
.fill_contiguous(
&Rectangle::new(Point::new(20, 4), Size::new(8, 8)),
[BinaryColor::On; 8 * 8],
)
.unwrap();
#[rustfmt::skip]
let expected: [u8; 3 * 8] = [
0b11110000, 0b00000000, 0b00000000,
0b11110000, 0b00000000, 0b00000000,
0b11110011, 0b11111111, 0b11000000,
0b11110011, 0b11111111, 0b11000000,
0b00000011, 0b11111111, 0b11001111,
0b00000011, 0b11111111, 0b11001111,
0b00000000, 0b00000000, 0b00001111,
0b00000000, 0b00000000, 0b00001111,
];
assert_eq!(buffer.data(), &expected);
}
#[test]
fn test_binary_buffer_fill_solid() {
const SIZE: Size = Size::new(24, 8);
const BUFFER_LENGTH: usize = binary_buffer_length(SIZE);
let mut buffer = BinaryBuffer::<{ BUFFER_LENGTH }>::new(SIZE);
buffer
.fill_solid(
&Rectangle::new(Point::new(-4, -4), Size::new(8, 8)),
BinaryColor::On,
)
.unwrap();
buffer
.fill_solid(
&Rectangle::new(Point::new(6, 2), Size::new(12, 4)),
BinaryColor::On,
)
.unwrap();
buffer
.fill_solid(
&Rectangle::new(Point::new(20, 4), Size::new(8, 8)),
BinaryColor::On,
)
.unwrap();
#[rustfmt::skip]
let expected: [u8; 3 * 8] = [
0b11110000, 0b00000000, 0b00000000,
0b11110000, 0b00000000, 0b00000000,
0b11110011, 0b11111111, 0b11000000,
0b11110011, 0b11111111, 0b11000000,
0b00000011, 0b11111111, 0b11001111,
0b00000011, 0b11111111, 0b11001111,
0b00000000, 0b00000000, 0b00001111,
0b00000000, 0b00000000, 0b00001111,
];
assert_eq!(buffer.data(), &expected);
}
#[test]
fn test_rotated_buffer_bounds() {
const SIZE: Size = Size::new(8, 24);
const BUFFER_LENGTH: usize = binary_buffer_length(SIZE);
let mut rotated_buffer =
RotatedBuffer::new(BinaryBuffer::<{ BUFFER_LENGTH }>::new(SIZE), Rotate::R90);
assert_eq!(
rotated_buffer.bounding_box(),
Rectangle::new(Point::new(0, 0), Size::new(24, 8))
);
rotated_buffer =
RotatedBuffer::new(BinaryBuffer::<{ BUFFER_LENGTH }>::new(SIZE), Rotate::R180);
assert_eq!(
rotated_buffer.bounding_box(),
Rectangle::new(Point::new(0, 0), Size::new(8, 24))
);
rotated_buffer =
RotatedBuffer::new(BinaryBuffer::<{ BUFFER_LENGTH }>::new(SIZE), Rotate::R270);
assert_eq!(
rotated_buffer.bounding_box(),
Rectangle::new(Point::new(0, 0), Size::new(24, 8))
);
}
#[test]
fn test_rotated_buffer_draw_iter() {
const SIZE: Size = Size::new(8, 4);
const BUFFER_LENGTH: usize = binary_buffer_length(SIZE);
let mut rotated_buffer =
RotatedBuffer::new(BinaryBuffer::<{ BUFFER_LENGTH }>::new(SIZE), Rotate::R90);
rotated_buffer
.draw_iter([
Pixel(Point::new(-1, -1), BinaryColor::On), Pixel(Point::new(0, 0), BinaryColor::On),
Pixel(Point::new(1, 1), BinaryColor::On),
Pixel(Point::new(2, 2), BinaryColor::On),
])
.unwrap();
#[rustfmt::skip]
let expected: [u8; 4] = [
0b00000001,
0b00000010,
0b00000100,
0b00000000,
];
assert_eq!(rotated_buffer.inner().data(), &expected);
rotated_buffer =
RotatedBuffer::new(BinaryBuffer::<{ BUFFER_LENGTH }>::new(SIZE), Rotate::R180);
rotated_buffer
.draw_iter([
Pixel(Point::new(-1, -1), BinaryColor::On), Pixel(Point::new(0, 0), BinaryColor::On),
Pixel(Point::new(1, 1), BinaryColor::On),
Pixel(Point::new(2, 2), BinaryColor::On),
])
.unwrap();
#[rustfmt::skip]
let expected: [u8; 4] = [
0b00000000,
0b00000100,
0b00000010,
0b00000001,
];
assert_eq!(rotated_buffer.inner().data(), &expected);
rotated_buffer =
RotatedBuffer::new(BinaryBuffer::<{ BUFFER_LENGTH }>::new(SIZE), Rotate::R270);
rotated_buffer
.draw_iter([
Pixel(Point::new(-1, -1), BinaryColor::On), Pixel(Point::new(0, 0), BinaryColor::On),
Pixel(Point::new(1, 1), BinaryColor::On),
Pixel(Point::new(2, 2), BinaryColor::On),
])
.unwrap();
#[rustfmt::skip]
let expected: [u8; 4] = [
0b00000000,
0b00100000,
0b01000000,
0b10000000,
];
assert_eq!(rotated_buffer.inner().data(), &expected);
}
#[test]
fn test_rotated_buffer_fill_contiguous() {
const SIZE: Size = Size::new(8, 6);
const BUFFER_LENGTH: usize = binary_buffer_length(SIZE);
let buffer = BinaryBuffer::<{ BUFFER_LENGTH }>::new(SIZE);
let mut rotated_buffer = RotatedBuffer::new(buffer.clone(), Rotate::R90);
rotated_buffer
.fill_contiguous(
&Rectangle::new(Point::new(-4, -4), Size::new(8, 8)),
[BinaryColor::On; 8 * 8],
)
.unwrap();
#[rustfmt::skip]
let expected: [u8; 6] = [
0b00001111,
0b00001111,
0b00001111,
0b00001111,
0b00000000,
0b00000000,
];
assert_eq!(rotated_buffer.inner().data(), &expected);
rotated_buffer = RotatedBuffer::new(buffer.clone(), Rotate::R180);
rotated_buffer
.fill_contiguous(
&Rectangle::new(Point::new(-4, -4), Size::new(8, 8)),
[BinaryColor::On; 8 * 8],
)
.unwrap();
#[rustfmt::skip]
let expected: [u8; 6] = [
0b00000000,
0b00000000,
0b00001111,
0b00001111,
0b00001111,
0b00001111,
];
assert_eq!(rotated_buffer.inner().data(), &expected);
rotated_buffer = RotatedBuffer::new(buffer.clone(), Rotate::R270);
rotated_buffer
.fill_contiguous(
&Rectangle::new(Point::new(-4, -4), Size::new(8, 8)),
[BinaryColor::On; 8 * 8],
)
.unwrap();
#[rustfmt::skip]
let expected: [u8; 6] = [
0b00000000,
0b00000000,
0b11110000,
0b11110000,
0b11110000,
0b11110000,
];
assert_eq!(rotated_buffer.inner().data(), &expected);
}
#[test]
fn test_rotated_buffer_fill_solid() {
const SIZE: Size = Size::new(8, 6);
const BUFFER_LENGTH: usize = binary_buffer_length(SIZE);
let buffer = BinaryBuffer::<{ BUFFER_LENGTH }>::new(SIZE);
let mut rotated_buffer = RotatedBuffer::new(buffer.clone(), Rotate::R90);
rotated_buffer
.fill_solid(
&Rectangle::new(Point::new(-4, -4), Size::new(8, 8)),
BinaryColor::On,
)
.unwrap();
#[rustfmt::skip]
let expected: [u8; 6] = [
0b00001111,
0b00001111,
0b00001111,
0b00001111,
0b00000000,
0b00000000,
];
assert_eq!(rotated_buffer.inner().data(), &expected);
rotated_buffer = RotatedBuffer::new(buffer.clone(), Rotate::R180);
rotated_buffer
.fill_solid(
&Rectangle::new(Point::new(-4, -4), Size::new(8, 8)),
BinaryColor::On,
)
.unwrap();
#[rustfmt::skip]
let expected: [u8; 6] = [
0b00000000,
0b00000000,
0b00001111,
0b00001111,
0b00001111,
0b00001111,
];
assert_eq!(rotated_buffer.inner().data(), &expected);
rotated_buffer = RotatedBuffer::new(buffer.clone(), Rotate::R270);
rotated_buffer
.fill_solid(
&Rectangle::new(Point::new(-4, -4), Size::new(8, 8)),
BinaryColor::On,
)
.unwrap();
#[rustfmt::skip]
let expected: [u8; 6] = [
0b00000000,
0b00000000,
0b11110000,
0b11110000,
0b11110000,
0b11110000,
];
assert_eq!(rotated_buffer.inner().data(), &expected);
}
#[test]
fn test_rotate_near_corner() {
let mut r = Rotate::R90;
assert_eq!(
Point::new(18, 1),
r.rotate_point(Point::new(1, 1), Size::new(10, 20))
);
r = Rotate::R180;
assert_eq!(
Point::new(8, 18),
r.rotate_point(Point::new(1, 1), Size::new(10, 20))
);
r = Rotate::R270;
assert_eq!(
Point::new(1, 8),
r.rotate_point(Point::new(1, 1), Size::new(10, 20))
);
}
#[test]
fn test_rotate_centre() {
let mut r = Rotate::R90;
assert_eq!(
Point::new(2, 2),
r.rotate_point(Point::new(2, 2), Size::new(5, 5))
);
r = Rotate::R180;
assert_eq!(
Point::new(2, 2),
r.rotate_point(Point::new(2, 2), Size::new(5, 5))
);
r = Rotate::R270;
assert_eq!(
Point::new(2, 2),
r.rotate_point(Point::new(2, 2), Size::new(5, 5))
);
}
#[test]
fn test_rotate_size() {
let mut r = Rotate::R90;
assert_eq!(Size::new(5, 10), r.rotate_size(Size::new(10, 5)));
r = Rotate::R180;
assert_eq!(Size::new(10, 5), r.rotate_size(Size::new(10, 5)));
r = Rotate::R270;
assert_eq!(Size::new(5, 10), r.rotate_size(Size::new(10, 5)));
}
#[test]
fn test_rotate_rectangle() {
let mut r = Rotate::R90;
let rect = Rectangle::new(Point::new(1, 1), Size::new(3, 2));
let _dest_bounds = Size::new(8, 4);
let mut source_bounds = Size::new(4, 8);
let rotated = r.rotate_rectangle(rect, source_bounds);
assert_eq!(rotated.top_left, Point::new(5, 1));
assert_eq!(rotated.size, Size::new(2, 3));
r = Rotate::R180;
source_bounds = Size::new(8, 4);
let rotated = r.rotate_rectangle(rect, source_bounds);
assert_eq!(rotated.top_left, Point::new(4, 1));
assert_eq!(rotated.size, Size::new(3, 2));
r = Rotate::R270;
source_bounds = Size::new(4, 8);
let rotated = r.rotate_rectangle(rect, source_bounds);
assert_eq!(rotated.top_left, Point::new(1, 0));
assert_eq!(rotated.size, Size::new(2, 3));
}
}