use embedded_graphics_core::{
Pixel,
draw_target::DrawTarget,
geometry::{OriginDimensions, Point, Size},
pixelcolor::BinaryColor,
primitives::{PointsIter, Rectangle},
};
use st7305_lcd::framebuffer::{FrameBuffer, Landscape, PixelMap, Rotation};
const N: usize = 15_000;
type L = Landscape<400, 300, 0x12, 0>;
type Fb = FrameBuffer<L, N>;
fn locate(x: u32, y: u32) -> Option<(usize, u8)> {
<L as PixelMap>::locate(x, y)
}
fn ones(fb: &Fb) -> u32 {
fb.as_bytes().iter().map(|b| b.count_ones()).sum()
}
fn reference_locate(x: u32, y: u32) -> (usize, u8) {
let height = 300;
let inv_y = height - 1 - y;
let h4 = height >> 2;
let byte_x = x >> 1;
let block_y = inv_y >> 2;
let index = byte_x * h4 + block_y;
let local_x = x & 1;
let local_y = inv_y & 3;
let bit = 7 - ((local_y << 1) | local_x);
(index as usize, bit as u8)
}
#[test]
fn locate_matches_the_reference_formula_for_every_pixel() {
for y in 0..300 {
for x in 0..400 {
assert_eq!(
locate(x, y),
Some(reference_locate(x, y)),
"pixel ({x}, {y})"
);
}
}
}
#[test]
fn locate_is_a_bijection_onto_all_bits_of_the_buffer() {
let mut seen = vec![false; N * 8];
for y in 0..300 {
for x in 0..400 {
let (i, bit) = locate(x, y).unwrap();
assert!(i < N && bit < 8);
let slot = i * 8 + usize::from(bit);
assert!(!seen[slot], "pixel ({x}, {y}) collides");
seen[slot] = true;
}
}
assert!(seen.iter().all(|&s| s));
}
#[test]
fn locate_rejects_out_of_range() {
assert_eq!(locate(400, 0), None);
assert_eq!(locate(0, 300), None);
assert_eq!(locate(u32::MAX, u32::MAX), None);
}
#[test]
fn buffer_len_follows_the_geometry() {
assert_eq!(
<Landscape<400, 300, 0x12, 0> as PixelMap>::BUFFER_LEN,
15_000
);
assert_eq!(<Landscape<8, 8, 0, 0> as PixelMap>::BUFFER_LEN, 8);
}
fn pattern(seed: u32) -> Vec<bool> {
(0..400u32).map(|x| (x * 31 + seed * 17) % 5 < 2).collect()
}
#[test]
fn write_row_then_read_row_round_trips_and_agrees_with_locate() {
let mut buf = vec![0u8; N];
let rows = [0u32, 1, 2, 3, 4, 150, 296, 299];
for &y in &rows {
L::write_row(&mut buf, y, &pattern(y));
}
for &y in &rows {
let mut out = vec![false; 400];
let any = L::read_row(&buf, y, &mut out);
assert_eq!(out, pattern(y), "row {y}");
assert!(any);
for x in 0..400u32 {
let (i, bit) = locate(x, y).unwrap();
assert_eq!(
(buf[i] >> bit) & 1 != 0,
out[x as usize],
"pixel ({x}, {y})"
);
}
}
}
#[test]
fn read_row_reports_whether_the_row_has_active_pixels() {
let mut buf = vec![0u8; N];
let mut out = vec![false; 400];
assert!(!L::read_row(&buf, 10, &mut out));
let (i, bit) = locate(399, 10).unwrap();
buf[i] |= 1 << bit;
assert!(L::read_row(&buf, 10, &mut out));
assert!(out[399] && out.iter().filter(|&&p| p).count() == 1);
assert!(!L::read_row(&buf, 11, &mut out));
}
#[test]
fn write_row_does_not_clear_existing_pixels() {
let mut buf = vec![0u8; N];
let mut ones = vec![false; 400];
ones[7] = true;
L::write_row(&mut buf, 42, &ones);
L::write_row(&mut buf, 42, &vec![false; 400]);
let mut out = vec![false; 400];
L::read_row(&buf, 42, &mut out);
assert!(out[7]);
}
#[test]
#[should_panic]
fn read_row_panics_on_wrong_output_length() {
let buf = vec![0u8; N];
L::read_row(&buf, 0, &mut vec![false; 399]);
}
#[test]
fn new_buffer_is_blank_and_has_the_panel_size() {
let fb = Fb::new();
assert_eq!(fb.as_bytes().len(), N);
assert_eq!(ones(&fb), 0);
assert_eq!(fb.size(), Size::new(400, 300));
}
#[test]
fn set_pixel_changes_exactly_one_bit_and_get_pixel_sees_it() {
let mut fb = Fb::new();
fb.set_pixel(5, 100, true);
assert_eq!(ones(&fb), 1);
assert_eq!(fb.get_pixel(5, 100), Some(true));
assert_eq!(fb.get_pixel(4, 100), Some(false));
assert_eq!(fb.get_pixel(5, 101), Some(false));
fb.set_pixel(5, 100, false);
assert_eq!(ones(&fb), 0);
assert_eq!(fb.get_pixel(5, 100), Some(false));
}
#[test]
fn corner_pixels_land_in_the_expected_bytes() {
let mut fb = Fb::new();
fb.set_pixel(0, 299, true);
assert_eq!(fb.as_bytes()[0], 1 << 7);
fb.fill(false);
fb.set_pixel(399, 0, true);
assert_eq!(fb.as_bytes()[14_999], 1 << 0);
}
#[test]
fn out_of_range_pixels_are_ignored() {
let mut fb = Fb::new();
fb.set_pixel(400, 0, true);
fb.set_pixel(0, 300, true);
fb.set_pixel(u32::MAX, u32::MAX, true);
assert_eq!(ones(&fb), 0);
assert_eq!(fb.get_pixel(400, 0), None);
assert_eq!(fb.get_pixel(0, 300), None);
}
#[test]
fn fill_sets_all_ones_or_zeros() {
let mut fb = Fb::new();
fb.fill(true);
assert!(fb.as_bytes().iter().all(|&b| b == 0xFF));
assert_eq!(fb.get_pixel(399, 299), Some(true));
fb.fill(false);
assert_eq!(ones(&fb), 0);
}
#[test]
fn draw_target_clear_fills_the_whole_buffer() {
for rotation in [
Rotation::Deg0,
Rotation::Deg90,
Rotation::Deg180,
Rotation::Deg270,
] {
let mut fb = Fb::new().with_rotation(rotation);
let Ok(()) = fb.clear(BinaryColor::On);
assert!(fb.as_bytes().iter().all(|&b| b == 0xFF));
let Ok(()) = fb.clear(BinaryColor::Off);
assert_eq!(ones(&fb), 0);
}
}
#[test]
fn fill_with_calls_the_closure_once_per_byte_in_order() {
let mut fb = Fb::new();
let mut n = 0u8;
fb.fill_with(|| {
n = n.wrapping_add(1);
n
});
assert_eq!(fb.as_bytes()[0], 1);
assert_eq!(fb.as_bytes()[255], 0); assert_eq!(fb.as_bytes()[N - 1], (N % 256) as u8);
}
#[test]
fn as_bytes_mut_writes_through() {
let mut fb = Fb::new();
fb.as_bytes_mut()[0] = 0b1000_0000;
assert_eq!(fb.get_pixel(0, 299), Some(true));
}
#[test]
fn row_api_agrees_with_pixel_api() {
let mut fb = Fb::new();
for x in (0..400).step_by(3) {
fb.set_pixel(x, 77, true);
}
let mut row = [false; 400];
assert!(fb.read_row(77, &mut row));
for (x, &on) in row.iter().enumerate() {
assert_eq!(on, x % 3 == 0, "x = {x}");
}
assert!(!fb.read_row(78, &mut [false; 400]));
let mut copy = Fb::new();
copy.write_row(77, &row);
assert_eq!(copy.as_bytes(), fb.as_bytes());
}
#[test]
fn draw_iter_sets_and_clears_pixels_and_clips() {
let mut fb = Fb::new();
fb.draw_iter([
Pixel(Point::new(10, 10), BinaryColor::On),
Pixel(Point::new(11, 10), BinaryColor::On),
Pixel(Point::new(-1, 10), BinaryColor::On), Pixel(Point::new(10, -1), BinaryColor::On),
Pixel(Point::new(400, 10), BinaryColor::On), Pixel(Point::new(10, 300), BinaryColor::On),
])
.unwrap();
assert_eq!(ones(&fb), 2);
fb.draw_iter([Pixel(Point::new(10, 10), BinaryColor::Off)])
.unwrap();
assert_eq!(ones(&fb), 1);
assert_eq!(fb.get_pixel(11, 10), Some(true));
}
#[test]
fn filled_rectangle_draws_exactly_its_area_through_embedded_graphics() {
let mut fb = Fb::new();
let rect = Rectangle::new(Point::new(20, 30), Size::new(7, 5));
fb.fill_solid(&rect, BinaryColor::On).unwrap();
assert_eq!(ones(&fb), 35);
for p in rect.points() {
assert_eq!(fb.get_pixel(p.x as u32, p.y as u32), Some(true));
}
}
const ALL: [Rotation; 4] = [
Rotation::Deg0,
Rotation::Deg90,
Rotation::Deg180,
Rotation::Deg270,
];
#[test]
fn rotation_size_swaps_the_axes_for_90_and_270() {
assert_eq!(Rotation::Deg0.size(400, 300), (400, 300));
assert_eq!(Rotation::Deg90.size(400, 300), (300, 400));
assert_eq!(Rotation::Deg180.size(400, 300), (400, 300));
assert_eq!(Rotation::Deg270.size(400, 300), (300, 400));
assert_eq!(Rotation::default(), Rotation::Deg0);
}
#[test]
fn rotation_deg0_is_the_identity() {
for y in 0..300 {
for x in 0..400 {
assert_eq!(Rotation::Deg0.to_native(400, 300, x, y), Some((x, y)));
}
}
}
#[test]
fn every_rotation_is_a_bijection_onto_the_native_pixels() {
for rot in ALL {
let (lw, lh) = rot.size(400, 300);
let mut seen = vec![false; 400 * 300];
for y in 0..lh {
for x in 0..lw {
let (xn, yn) = rot.to_native(400, 300, x, y).unwrap();
assert!(xn < 400 && yn < 300, "{rot:?}: ({x}, {y}) -> ({xn}, {yn})");
let slot = (yn * 400 + xn) as usize;
assert!(!seen[slot], "{rot:?}: ({x}, {y}) collides");
seen[slot] = true;
}
}
assert!(
seen.iter().all(|&s| s),
"{rot:?} leaves native pixels unused"
);
}
}
#[test]
fn rotation_rejects_pixels_outside_the_logical_size() {
for rot in ALL {
let (lw, lh) = rot.size(400, 300);
assert!(rot.to_native(400, 300, lw - 1, lh - 1).is_some(), "{rot:?}");
assert_eq!(rot.to_native(400, 300, lw, 0), None, "{rot:?}");
assert_eq!(rot.to_native(400, 300, 0, lh), None, "{rot:?}");
assert_eq!(rot.to_native(400, 300, u32::MAX, u32::MAX), None, "{rot:?}");
}
}
#[test]
fn deg180_is_the_point_reflection_of_deg0() {
for y in (0..300).step_by(7) {
for x in (0..400).step_by(5) {
let (a, b) = Rotation::Deg0.to_native(400, 300, x, y).unwrap();
let (c, d) = Rotation::Deg180.to_native(400, 300, x, y).unwrap();
assert_eq!((a + c, b + d), (399, 299));
}
}
}
fn reference_portrait_locate(x: u32, y: u32) -> (usize, u8) {
let width = 300;
let w4 = width >> 2;
let byte_x = x >> 2;
let byte_y = y >> 1;
let index = byte_y * w4 + byte_x;
let local_x = x & 3;
let local_y = y & 1;
let bit = 7 - ((local_x << 1) | local_y);
(index as usize, bit as u8)
}
#[test]
fn deg270_through_landscape_matches_the_reference_portrait_formula() {
for y in 0..400 {
for x in 0..300 {
let (xn, yn) = Rotation::Deg270.to_native(400, 300, x, y).unwrap();
assert_eq!(
locate(xn, yn),
Some(reference_portrait_locate(x, y)),
"logical ({x}, {y})"
);
}
}
}
#[test]
fn framebuffer_defaults_to_deg0_and_reports_the_logical_size() {
let fb = Fb::new();
assert_eq!(fb.rotation(), Rotation::Deg0);
assert_eq!(fb.logical_size(), (400, 300));
for (rot, size) in [
(Rotation::Deg0, Size::new(400, 300)),
(Rotation::Deg90, Size::new(300, 400)),
(Rotation::Deg180, Size::new(400, 300)),
(Rotation::Deg270, Size::new(300, 400)),
] {
let fb = Fb::new().with_rotation(rot);
assert_eq!(fb.rotation(), rot);
assert_eq!(fb.size(), size, "{rot:?}");
}
}
#[test]
fn set_pixel_writes_the_native_bit_chosen_by_the_rotation() {
for rot in ALL {
let (lw, lh) = rot.size(400, 300);
for (x, y) in [
(0, 0),
(lw - 1, 0),
(0, lh - 1),
(lw - 1, lh - 1),
(123, 77 % lh),
] {
let mut fb = Fb::new().with_rotation(rot);
fb.set_pixel(x, y, true);
let (xn, yn) = rot.to_native(400, 300, x, y).unwrap();
let (i, bit) = locate(xn, yn).unwrap();
let mut expected = vec![0u8; N];
expected[i] = 1 << bit;
assert_eq!(fb.as_bytes(), expected, "{rot:?} ({x}, {y})");
assert_eq!(fb.get_pixel(x, y), Some(true), "{rot:?} ({x}, {y})");
}
}
}
#[test]
fn pixels_outside_the_logical_size_are_ignored_for_every_rotation() {
for rot in ALL {
let (lw, lh) = rot.size(400, 300);
let mut fb = Fb::new().with_rotation(rot);
fb.set_pixel(lw, 0, true);
fb.set_pixel(0, lh, true);
assert_eq!(ones(&fb), 0, "{rot:?}");
assert_eq!(fb.get_pixel(lw, 0), None, "{rot:?}");
assert_eq!(fb.get_pixel(0, lh), None, "{rot:?}");
fb.set_pixel(lw - 1, lh - 1, true);
assert_eq!(ones(&fb), 1, "{rot:?}");
}
}
#[test]
fn a_portrait_buffer_accepts_what_a_landscape_one_rejects() {
let mut landscape = Fb::new();
landscape.set_pixel(0, 399, true);
assert_eq!(ones(&landscape), 0);
let mut portrait = Fb::new().with_rotation(Rotation::Deg90);
portrait.set_pixel(0, 399, true);
assert_eq!(ones(&portrait), 1);
portrait.set_pixel(300, 0, true);
assert_eq!(ones(&portrait), 1);
}
#[test]
fn set_rotation_does_not_touch_the_content() {
let mut fb = Fb::new();
fb.set_pixel(1, 2, true);
let before = fb.as_bytes().to_vec();
fb.set_rotation(Rotation::Deg90);
assert_eq!(fb.as_bytes(), before);
assert_eq!(fb.get_pixel(1, 2), Some(false));
assert_eq!(fb.get_pixel(2, 398), Some(true));
}
#[test]
fn deg90_turns_the_picture_clockwise_and_deg270_counter_clockwise() {
let native = |rot: Rotation, x, y| rot.to_native(400, 300, x, y).unwrap();
for t in 0..300 {
assert_eq!(native(Rotation::Deg90, t, 0).0, 399, "top -> right");
assert_eq!(native(Rotation::Deg90, t, 399).0, 0, "bottom -> left");
assert_eq!(native(Rotation::Deg270, t, 0).0, 0, "top -> left");
assert_eq!(native(Rotation::Deg270, t, 399).0, 399, "bottom -> right");
}
for t in 0..400 {
assert_eq!(native(Rotation::Deg90, 0, t).1, 0, "left -> top");
assert_eq!(native(Rotation::Deg90, 299, t).1, 299, "right -> bottom");
assert_eq!(native(Rotation::Deg270, 0, t).1, 299, "left -> bottom");
assert_eq!(native(Rotation::Deg270, 299, t).1, 0, "right -> top");
}
}
#[test]
fn rows_round_trip_and_agree_with_pixels_for_every_rotation() {
for rot in ALL {
let (lw, lh) = rot.size(400, 300);
let row_pattern =
|seed: u32| -> Vec<bool> { (0..lw).map(|x| (x * 29 + seed * 13) % 7 < 3).collect() };
let mut fb = Fb::new().with_rotation(rot);
for y in 0..lh {
fb.write_row(y, &row_pattern(y));
}
for y in 0..lh {
let expected = row_pattern(y);
let mut out = vec![false; lw as usize];
let any = fb.read_row(y, &mut out);
assert_eq!(out, expected, "{rot:?} row {y}");
assert_eq!(any, expected.iter().any(|&p| p), "{rot:?} row {y}");
for x in 0..lw {
assert_eq!(
fb.get_pixel(x, y),
Some(out[x as usize]),
"{rot:?} ({x}, {y})"
);
}
}
}
}
#[test]
#[should_panic]
fn read_row_checks_the_logical_width() {
let fb = Fb::new().with_rotation(Rotation::Deg90);
fb.read_row(0, &mut [false; 400]); }
#[test]
#[should_panic]
fn read_row_checks_the_logical_height() {
let fb = Fb::new().with_rotation(Rotation::Deg90);
fb.read_row(400, &mut [false; 300]);
}
#[test]
fn draw_iter_clips_by_the_logical_size() {
let mut fb = Fb::new().with_rotation(Rotation::Deg90);
fb.draw_iter([
Pixel(Point::new(299, 399), BinaryColor::On),
Pixel(Point::new(300, 0), BinaryColor::On), Pixel(Point::new(0, 400), BinaryColor::On), Pixel(Point::new(-1, 5), BinaryColor::On),
])
.unwrap();
assert_eq!(ones(&fb), 1);
assert_eq!(fb.get_pixel(299, 399), Some(true));
}
#[test]
fn filled_rectangle_in_every_rotation_draws_exactly_its_area() {
for rot in ALL {
let mut fb = Fb::new().with_rotation(rot);
let rect = Rectangle::new(Point::new(20, 30), Size::new(7, 5));
fb.fill_solid(&rect, BinaryColor::On).unwrap();
assert_eq!(ones(&fb), 35, "{rot:?}");
for p in rect.points() {
assert_eq!(fb.get_pixel(p.x as u32, p.y as u32), Some(true), "{rot:?}");
}
}
}
fn noise(len: usize, mut seed: u32) -> Vec<u8> {
(0..len)
.map(|_| {
seed ^= seed << 13;
seed ^= seed >> 17;
seed ^= seed << 5;
seed as u8
})
.collect()
}
fn col_pattern(len: usize, seed: u32) -> Vec<bool> {
(0..len as u32)
.map(|y| (y * 11 + seed * 7) % 5 < 2)
.collect()
}
fn assert_read_col_matches_locate<M: PixelMap>() {
let buf = noise(M::BUFFER_LEN, 0x1234_5678);
for x in 0..M::WIDTH {
let mut out = vec![false; M::HEIGHT as usize];
let any = M::read_col(&buf, x, &mut out);
for y in 0..M::HEIGHT {
let (i, bit) = M::locate(x, y).unwrap();
assert_eq!(
out[y as usize],
(buf[i] >> bit) & 1 != 0,
"pixel ({x}, {y})"
);
}
assert_eq!(any, out.iter().any(|&p| p), "column {x}");
}
}
#[test]
fn read_col_matches_locate_for_every_column() {
assert_read_col_matches_locate::<L>();
}
#[test]
fn read_col_matches_locate_on_small_geometries() {
assert_read_col_matches_locate::<Landscape<2, 4, 0, 0>>();
assert_read_col_matches_locate::<Landscape<6, 8, 0, 0>>();
assert_read_col_matches_locate::<Landscape<8, 12, 0, 0>>();
}
#[test]
fn write_col_sets_exactly_the_given_pixels() {
for x in [0u32, 1, 2, 3, 198, 199, 200, 398, 399] {
let col = col_pattern(300, x);
let mut buf = vec![0u8; N];
L::write_col(&mut buf, x, &col);
let on: u32 = buf.iter().map(|b| b.count_ones()).sum();
assert_eq!(
on as usize,
col.iter().filter(|&&p| p).count(),
"column {x}"
);
for (y, &p) in col.iter().enumerate() {
let (i, bit) = locate(x, y as u32).unwrap();
assert_eq!((buf[i] >> bit) & 1 != 0, p, "pixel ({x}, {y})");
}
}
}
#[test]
fn neighbour_columns_of_a_byte_do_not_disturb_each_other() {
let (a, b) = (col_pattern(300, 1), col_pattern(300, 2));
let mut buf = vec![0u8; N];
L::write_col(&mut buf, 4, &a);
L::write_col(&mut buf, 5, &b);
let (mut out_a, mut out_b) = (vec![false; 300], vec![false; 300]);
L::read_col(&buf, 4, &mut out_a);
L::read_col(&buf, 5, &mut out_b);
assert_eq!(out_a, a);
assert_eq!(out_b, b);
assert!(!L::read_col(&buf, 3, &mut out_a));
assert!(!L::read_col(&buf, 6, &mut out_a));
}
#[test]
fn columns_and_rows_describe_the_same_picture() {
let cols: Vec<Vec<bool>> = (0..400).map(|x| col_pattern(300, x)).collect();
let mut buf = vec![0u8; N];
for (x, col) in cols.iter().enumerate() {
L::write_col(&mut buf, x as u32, col);
}
for y in 0..300u32 {
let mut row = vec![false; 400];
L::read_row(&buf, y, &mut row);
for x in 0..400usize {
assert_eq!(row[x], cols[x][y as usize], "pixel ({x}, {y})");
}
}
}
#[test]
fn read_col_reports_whether_the_column_has_active_pixels() {
let mut buf = vec![0u8; N];
let mut out = vec![false; 300];
assert!(!L::read_col(&buf, 7, &mut out));
let (i, bit) = locate(7, 123).unwrap();
buf[i] |= 1 << bit;
assert!(L::read_col(&buf, 7, &mut out));
assert!(out[123] && out.iter().filter(|&&p| p).count() == 1);
assert!(!L::read_col(&buf, 6, &mut out));
}
#[test]
fn write_col_does_not_clear_existing_pixels() {
let mut buf = vec![0u8; N];
let mut one = vec![false; 300];
one[42] = true;
L::write_col(&mut buf, 9, &one);
L::write_col(&mut buf, 9, &vec![false; 300]);
let mut out = vec![false; 300];
L::read_col(&buf, 9, &mut out);
assert!(out[42]);
}
#[test]
#[should_panic]
fn read_col_panics_on_a_column_out_of_range() {
L::read_col(&vec![0u8; N], 400, &mut vec![false; 300]);
}
#[test]
#[should_panic]
fn read_col_panics_on_wrong_output_length() {
L::read_col(&vec![0u8; N], 0, &mut vec![false; 400]); }
#[test]
#[should_panic]
fn write_col_panics_on_wrong_input_length() {
L::write_col(&mut vec![0u8; N], 0, &vec![false; 400]);
}
fn reversed(v: &[bool]) -> Vec<bool> {
v.iter().rev().copied().collect()
}
#[test]
fn write_row_rev_equals_write_row_of_the_reversed_row() {
for y in [0u32, 1, 2, 3, 4, 149, 296, 299] {
let row = col_pattern(400, y);
let (mut a, mut b) = (vec![0u8; N], vec![0u8; N]);
L::write_row_rev(&mut a, y, &row);
L::write_row(&mut b, y, &reversed(&row));
assert_eq!(a, b, "row {y}");
}
}
#[test]
fn write_col_rev_equals_write_col_of_the_reversed_column() {
for x in [0u32, 1, 2, 3, 198, 199, 200, 398, 399] {
let col = col_pattern(300, x);
let (mut a, mut b) = (vec![0u8; N], vec![0u8; N]);
L::write_col_rev(&mut a, x, &col);
L::write_col(&mut b, x, &reversed(&col));
assert_eq!(a, b, "column {x}");
}
}
#[test]
fn mirrored_writes_use_the_documented_pixels() {
let mut buf = vec![0u8; N];
let mut row = vec![false; 400];
row[0] = true; L::write_row_rev(&mut buf, 10, &row);
let (i, bit) = locate(399, 10).unwrap();
assert_eq!(buf[i], 1 << bit);
assert_eq!(buf.iter().map(|b| b.count_ones()).sum::<u32>(), 1);
let mut buf = vec![0u8; N];
let mut col = vec![false; 300];
col[0] = true; L::write_col_rev(&mut buf, 7, &col);
let (i, bit) = locate(7, 299).unwrap();
assert_eq!(buf[i], 1 << bit);
assert_eq!(buf.iter().map(|b| b.count_ones()).sum::<u32>(), 1);
}
#[test]
fn mirrored_writes_panic_on_wrong_lengths() {
let r = std::panic::catch_unwind(|| L::write_row_rev(&mut vec![0u8; N], 0, &vec![false; 399]));
assert!(r.is_err());
let r = std::panic::catch_unwind(|| L::write_col_rev(&mut vec![0u8; N], 0, &vec![false; 299]));
assert!(r.is_err());
}
#[test]
fn rotated_rows_report_whether_the_row_has_active_pixels() {
for rot in ALL {
let (lw, lh) = rot.size(400, 300);
let mut fb = Fb::new().with_rotation(rot);
let mut out = vec![false; lw as usize];
assert!(!fb.read_row(5, &mut out), "{rot:?} empty");
fb.set_pixel(lw - 1, 5, true);
assert!(fb.read_row(5, &mut out), "{rot:?} one pixel");
assert!(
out[lw as usize - 1] && out.iter().filter(|&&p| p).count() == 1,
"{rot:?}"
);
assert!(!fb.read_row(6, &mut out), "{rot:?} neighbour row");
assert!(!fb.read_row(lh - 1, &mut out), "{rot:?} last row");
}
}
#[test]
fn rotated_write_row_only_sets_pixels() {
for rot in ALL {
let (lw, _) = rot.size(400, 300);
let mut fb = Fb::new().with_rotation(rot);
let mut one = vec![false; lw as usize];
one[3] = true;
fb.write_row(8, &one);
fb.write_row(8, &vec![false; lw as usize]);
assert_eq!(fb.get_pixel(3, 8), Some(true), "{rot:?}");
assert_eq!(ones(&fb), 1, "{rot:?}");
}
}
#[test]
fn waveshare_layout_matches_the_init_table_window() {
use st7305_lcd::{InitStep::Cmd, presets::waveshare_rlcd_42 as panel};
let params = |opcode: u8| {
panel::INIT
.iter()
.find_map(|s| match s {
Cmd(op, p) if *op == opcode => Some(*p),
_ => None,
})
.unwrap()
};
assert_eq!(params(0x2A)[0], <panel::Map as PixelMap>::CASET_FIRST);
assert_eq!(params(0x2B)[0], <panel::Map as PixelMap>::RASET_FIRST);
assert_eq!(
<panel::Map as PixelMap>::BUFFER_LEN,
panel::FrameBuffer::new().as_bytes().len()
);
}
mod window {
use super::*;
use st7305_lcd::cmd::base::{ColumnAddressSet, RowAddressSet};
use st7305_lcd::framebuffer::Window;
fn rect(x: i32, y: i32, w: u32, h: u32) -> Rectangle {
Rectangle::new(Point::new(x, y), Size::new(w, h))
}
fn covering(area: Rectangle) -> Option<Window> {
Window::covering::<L>(area)
}
#[test]
fn full_screen() {
let w = covering(rect(0, 0, 400, 300)).unwrap();
assert_eq!(w.column_address_set(), ColumnAddressSet::new(0x12, 0x2A));
assert_eq!(w.row_address_set(), RowAddressSet::new(0, 199));
assert_eq!(w.byte_len(), 15_000);
}
#[test]
fn full_window_is_the_whole_panel() {
let full = Window::full::<L>();
assert_eq!(full, covering(rect(0, 0, 400, 300)).unwrap());
assert_eq!(full.byte_len(), N);
}
#[test]
fn unaligned_grows_outwards() {
let w = covering(rect(3, 5, 10, 6)).unwrap();
assert_eq!(w.bounds(), rect(2, 0, 12, 12));
assert_eq!(w.column_address_set(), ColumnAddressSet::new(0x12, 0x12));
assert_eq!(w.row_address_set(), RowAddressSet::new(1, 6));
}
#[test]
fn clipped_and_outside() {
let w = covering(rect(390, 290, 50, 50)).unwrap();
assert_eq!(w.bounds(), rect(390, 288, 10, 12));
assert!(covering(rect(400, 0, 5, 5)).is_none());
assert!(covering(rect(10, 10, 0, 5)).is_none());
assert!(covering(rect(-5, -5, 5, 5)).is_none());
assert!(covering(rect(-5, -5, 10, 10)).is_some());
}
#[test]
fn window_of_a_logical_rectangle_covers_it_in_every_rotation() {
for rotation in [
Rotation::Deg0,
Rotation::Deg90,
Rotation::Deg180,
Rotation::Deg270,
] {
let mut fb = Fb::new().with_rotation(rotation);
let (lw, lh) = (fb.size().width, fb.size().height);
let areas = [
rect(0, 0, lw, lh),
rect(3, 5, 10, 6),
rect(120, 130, 40, 30),
rect(lw as i32 - 7, lh as i32 - 9, 50, 50),
rect(-4, 20, 10, 10),
rect(1, 1, 1, 1),
];
for area in areas {
fb.fill(false);
let clipped = area.intersection(&Rectangle::new(Point::zero(), fb.size()));
for p in clipped.points() {
fb.set_pixel(p.x as u32, p.y as u32, true);
}
let win = fb.window(area).unwrap();
let mut out = [0u8; N];
let data = fb.copy_window(&win, &mut out);
let in_window: u32 = data.iter().map(|b| b.count_ones()).sum();
assert_eq!(in_window, ones(&fb), "{rotation:?} {area:?}");
assert_eq!(in_window, clipped.size.width * clipped.size.height);
}
assert!(fb.window(rect(lw as i32, 0, 5, 5)).is_none());
}
}
#[test]
fn bytes_match_pixels() {
let mut fb = Fb::new();
for y in 0..300 {
for x in 0..400 {
fb.set_pixel(x, y, (x * 7 + y * 13) % 5 < 2);
}
}
let mut out = [0u8; N];
let areas = [
rect(0, 0, 400, 300),
rect(3, 5, 10, 6),
rect(3, 5, 10, 10),
rect(120, 130, 40, 30),
rect(390, 290, 50, 50),
rect(0, 120, 400, 36),
];
for area in areas {
let win = covering(area).unwrap();
let data = fb.copy_window(&win, &mut out);
assert_eq!(data.len(), win.byte_len());
let b = win.bounds();
let (x0, y0) = (b.top_left.x as u32, b.top_left.y as u32);
let bands = b.size.height / 12;
for y in y0..y0 + b.size.height {
for x in x0..x0 + b.size.width {
let rel = (299 - y) - (300 - (y0 / 12 + bands) * 12);
let idx = ((x - x0) / 2) as usize * (bands * 3) as usize + (rel / 4) as usize;
let bit = 7 - rel % 4 * 2 - x % 2;
assert_eq!(
data[idx] >> bit & 1 != 0,
fb.get_pixel(x, y).unwrap(),
"{area:?} at ({x}, {y})"
);
}
}
}
}
}