Merge pull request #13 from cccb/fonts
More CP437, PrimitiveGrid::map, renamings
This commit is contained in:
commit
30d74ff07d
45 changed files with 2396 additions and 1557 deletions
10
Cargo.lock
generated
10
Cargo.lock
generated
|
|
@ -147,9 +147,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.1.29"
|
||||
version = "1.1.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "58e804ac3194a48bb129643eb1d62fcc20d18c6b8c181704489353d13120bcd1"
|
||||
checksum = "b16803a61b81d9eabb7eae2588776c4c1e584b738ede45fdbb4c972cec1e9945"
|
||||
dependencies = [
|
||||
"jobserver",
|
||||
"libc",
|
||||
|
|
@ -610,7 +610,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "servicepoint"
|
||||
version = "0.9.1"
|
||||
version = "0.10.0"
|
||||
dependencies = [
|
||||
"bitvec",
|
||||
"bzip2",
|
||||
|
|
@ -626,7 +626,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "servicepoint_binding_c"
|
||||
version = "0.9.1"
|
||||
version = "0.10.0"
|
||||
dependencies = [
|
||||
"cbindgen",
|
||||
"servicepoint",
|
||||
|
|
@ -634,7 +634,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "servicepoint_binding_cs"
|
||||
version = "0.9.1"
|
||||
version = "0.10.0"
|
||||
dependencies = [
|
||||
"csbindgen",
|
||||
"servicepoint",
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ members = [
|
|||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.9.1"
|
||||
version = "0.10.0"
|
||||
|
||||
[workspace.lints.rust]
|
||||
missing-docs = "warn"
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ cargo add servicepoint
|
|||
or
|
||||
```toml
|
||||
[dependencies]
|
||||
servicepoint = "0.9.1"
|
||||
servicepoint = "0.10.0"
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
|
|
|||
|
|
@ -6,16 +6,26 @@ use servicepoint::{CharGrid, Command, Connection, Cp437Grid, Origin};
|
|||
|
||||
#[derive(Parser, Debug)]
|
||||
struct Cli {
|
||||
#[arg(short, long, default_value = "localhost:2342")]
|
||||
#[arg(
|
||||
short,
|
||||
long,
|
||||
default_value = "localhost:2342",
|
||||
help = "Address of the display"
|
||||
)]
|
||||
destination: String,
|
||||
#[arg(short, long, num_args = 1.., value_delimiter = '\n')]
|
||||
#[arg(short, long, num_args = 1.., value_delimiter = '\n',
|
||||
help = "Text to send - specify multiple times for multiple lines")]
|
||||
text: Vec<String>,
|
||||
#[arg(short, long, default_value_t = true)]
|
||||
#[arg(
|
||||
short,
|
||||
long,
|
||||
default_value_t = true,
|
||||
help = "Clear screen before sending text"
|
||||
)]
|
||||
clear: bool,
|
||||
}
|
||||
|
||||
/// example: `cargo run -- --text "Hallo,
|
||||
/// CCCB"`
|
||||
/// example: `cargo run -- --text "Hallo" --text "CCCB"`
|
||||
fn main() {
|
||||
let mut cli = Cli::parse();
|
||||
if cli.text.is_empty() {
|
||||
|
|
@ -31,15 +41,11 @@ fn main() {
|
|||
.expect("sending clear failed");
|
||||
}
|
||||
|
||||
let text = cli.text.iter().fold(String::new(), move |str, line| {
|
||||
let is_first = str.is_empty();
|
||||
str + if is_first { "" } else { "\n" } + line
|
||||
});
|
||||
|
||||
let text = cli.text.join("\n");
|
||||
let grid = CharGrid::from(&*text);
|
||||
let cp437_grid = Cp437Grid::from(&grid);
|
||||
|
||||
connection
|
||||
.send(Command::Cp437Data(Origin::new(0, 0), cp437_grid))
|
||||
.send(Command::Cp437Data(Origin::ZERO, cp437_grid))
|
||||
.expect("sending text failed");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,23 +15,24 @@ fn main() {
|
|||
let connection = Connection::open(cli.destination)
|
||||
.expect("could not connect to display");
|
||||
|
||||
let mut pixels = PixelGrid::max_sized();
|
||||
let mut pixels = Bitmap::max_sized();
|
||||
pixels.fill(true);
|
||||
|
||||
let command = Command::BitmapLinearWin(
|
||||
Origin::new(0, 0),
|
||||
Origin::ZERO,
|
||||
pixels,
|
||||
CompressionCode::Uncompressed,
|
||||
);
|
||||
connection.send(command).expect("send failed");
|
||||
|
||||
let max_brightness = usize::from(u8::from(Brightness::MAX));
|
||||
let max_brightness: u8 = Brightness::MAX.into();
|
||||
let mut brightnesses = BrightnessGrid::new(TILE_WIDTH, TILE_HEIGHT);
|
||||
for (index, byte) in brightnesses.data_ref_mut().iter_mut().enumerate() {
|
||||
*byte = Brightness::try_from((index % max_brightness) as u8).unwrap();
|
||||
let level = index as u8 % max_brightness;
|
||||
*byte = Brightness::try_from(level).unwrap();
|
||||
}
|
||||
|
||||
connection
|
||||
.send(Command::CharBrightness(Origin::new(0, 0), brightnesses))
|
||||
.send(Command::CharBrightness(Origin::ZERO, brightnesses))
|
||||
.expect("send failed");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ fn main() {
|
|||
|
||||
loop {
|
||||
let command = Command::BitmapLinearWin(
|
||||
Origin::new(0, 0),
|
||||
Origin::ZERO,
|
||||
field.clone(),
|
||||
CompressionCode::Lzma,
|
||||
);
|
||||
|
|
@ -34,7 +34,7 @@ fn main() {
|
|||
}
|
||||
}
|
||||
|
||||
fn iteration(field: PixelGrid) -> PixelGrid {
|
||||
fn iteration(field: Bitmap) -> Bitmap {
|
||||
let mut next = field.clone();
|
||||
for x in 0..field.width() {
|
||||
for y in 0..field.height() {
|
||||
|
|
@ -51,7 +51,7 @@ fn iteration(field: PixelGrid) -> PixelGrid {
|
|||
next
|
||||
}
|
||||
|
||||
fn count_neighbors(field: &PixelGrid, x: i32, y: i32) -> i32 {
|
||||
fn count_neighbors(field: &Bitmap, x: i32, y: i32) -> i32 {
|
||||
let mut count = 0;
|
||||
for nx in x - 1..=x + 1 {
|
||||
for ny in y - 1..=y + 1 {
|
||||
|
|
@ -78,8 +78,8 @@ fn count_neighbors(field: &PixelGrid, x: i32, y: i32) -> i32 {
|
|||
count
|
||||
}
|
||||
|
||||
fn make_random_field(probability: f64) -> PixelGrid {
|
||||
let mut field = PixelGrid::max_sized();
|
||||
fn make_random_field(probability: f64) -> Bitmap {
|
||||
let mut field = Bitmap::max_sized();
|
||||
let mut rng = rand::thread_rng();
|
||||
let d = distributions::Bernoulli::new(probability).unwrap();
|
||||
for x in 0..field.width() {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ fn main() {
|
|||
let connection = Connection::open(Cli::parse().destination)
|
||||
.expect("could not connect to display");
|
||||
|
||||
let mut pixels = PixelGrid::max_sized();
|
||||
let mut pixels = Bitmap::max_sized();
|
||||
for x_offset in 0..usize::MAX {
|
||||
pixels.fill(false);
|
||||
|
||||
|
|
@ -25,7 +25,7 @@ fn main() {
|
|||
}
|
||||
|
||||
let command = Command::BitmapLinearWin(
|
||||
Origin::new(0, 0),
|
||||
Origin::ZERO,
|
||||
pixels.clone(),
|
||||
CompressionCode::Lzma,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -28,11 +28,11 @@ fn main() {
|
|||
|
||||
// put all pixels in on state
|
||||
if cli.enable_all {
|
||||
let mut filled_grid = PixelGrid::max_sized();
|
||||
let mut filled_grid = Bitmap::max_sized();
|
||||
filled_grid.fill(true);
|
||||
|
||||
let command = BitmapLinearWin(
|
||||
Origin::new(0, 0),
|
||||
Origin::ZERO,
|
||||
filled_grid,
|
||||
CompressionCode::Lzma,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
//! Example for how to use the WebSocket connection
|
||||
|
||||
use servicepoint::{
|
||||
Command, CompressionCode, Connection, Grid, Origin, PixelGrid,
|
||||
Bitmap, Command, CompressionCode, Connection, Grid, Origin,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
|
|
@ -13,7 +13,7 @@ fn main() {
|
|||
// use send_mut instead of send
|
||||
connection.send_mut(Command::Clear).unwrap();
|
||||
|
||||
let mut pixels = PixelGrid::max_sized();
|
||||
let mut pixels = Bitmap::max_sized();
|
||||
pixels.fill(true);
|
||||
|
||||
// use send_mut instead of send
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ fn main() {
|
|||
let connection = Connection::open(cli.destination)
|
||||
.expect("could not connect to display");
|
||||
|
||||
let mut enabled_pixels = PixelGrid::new(PIXEL_WIDTH, PIXEL_HEIGHT);
|
||||
let mut enabled_pixels = Bitmap::max_sized();
|
||||
enabled_pixels.fill(true);
|
||||
|
||||
for x_offset in 0..PIXEL_WIDTH {
|
||||
|
|
|
|||
292
crates/servicepoint/src/bitmap.rs
Normal file
292
crates/servicepoint/src/bitmap.rs
Normal file
|
|
@ -0,0 +1,292 @@
|
|||
use bitvec::order::Msb0;
|
||||
use bitvec::prelude::BitSlice;
|
||||
use bitvec::slice::IterMut;
|
||||
|
||||
use crate::{BitVec, DataRef, Grid, SpBitVec, PIXEL_HEIGHT, PIXEL_WIDTH};
|
||||
|
||||
/// A grid of pixels stored in packed bytes.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Bitmap {
|
||||
width: usize,
|
||||
height: usize,
|
||||
bit_vec: SpBitVec,
|
||||
}
|
||||
|
||||
impl Bitmap {
|
||||
/// Creates a new [Bitmap] with the specified dimensions.
|
||||
///
|
||||
/// # 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);
|
||||
Self {
|
||||
width,
|
||||
height,
|
||||
bit_vec: BitVec::repeat(false, width * height),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// # 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);
|
||||
assert_eq!(data.len(), height * width / 8);
|
||||
Self {
|
||||
width,
|
||||
height,
|
||||
bit_vec: BitVec::from_slice(data),
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterate over all cells in [Bitmap].
|
||||
///
|
||||
/// Order is equivalent to the following loop:
|
||||
/// ```
|
||||
/// # use servicepoint::{Bitmap, Grid};
|
||||
/// # let grid = Bitmap::new(8,2);
|
||||
/// for y in 0..grid.height() {
|
||||
/// for x in 0..grid.width() {
|
||||
/// grid.get(x, y);
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn iter(&self) -> impl Iterator<Item = &bool> {
|
||||
self.bit_vec.iter().by_refs()
|
||||
}
|
||||
|
||||
/// Iterate over all cells in [Bitmap] mutably.
|
||||
///
|
||||
/// Order is equivalent to the following loop:
|
||||
/// ```
|
||||
/// # use servicepoint::{Bitmap, Grid};
|
||||
/// # let mut grid = Bitmap::new(8,2);
|
||||
/// # let value = false;
|
||||
/// for y in 0..grid.height() {
|
||||
/// for x in 0..grid.width() {
|
||||
/// grid.set(x, y, value);
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use servicepoint::{Bitmap, Grid};
|
||||
/// # let mut grid = Bitmap::new(8,2);
|
||||
/// # let value = false;
|
||||
/// for (index, mut pixel) in grid.iter_mut().enumerate() {
|
||||
/// pixel.set(index % 2 == 0)
|
||||
/// }
|
||||
/// ```
|
||||
pub fn iter_mut(&mut self) -> IterMut<u8, Msb0> {
|
||||
self.bit_vec.iter_mut()
|
||||
}
|
||||
|
||||
/// Iterate over all rows in [Bitmap] top to bottom.
|
||||
pub fn iter_rows(&self) -> IterRows {
|
||||
IterRows {
|
||||
bitmap: self,
|
||||
row: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Grid<bool> for Bitmap {
|
||||
/// Sets the value of the specified position in the [Bitmap].
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// - `x` and `y`: position of the cell
|
||||
/// - `value`: the value to write to the cell
|
||||
///
|
||||
/// returns: old value of the cell
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// When accessing `x` or `y` out of bounds.
|
||||
fn set(&mut self, x: usize, y: usize, value: bool) {
|
||||
self.assert_in_bounds(x, y);
|
||||
self.bit_vec.set(x + y * self.width, value)
|
||||
}
|
||||
|
||||
fn get(&self, x: usize, y: usize) -> bool {
|
||||
self.assert_in_bounds(x, y);
|
||||
self.bit_vec[x + y * self.width]
|
||||
}
|
||||
|
||||
/// Sets the state of all pixels in the [Bitmap].
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// - `this`: instance to write to
|
||||
/// - `value`: the value to set all pixels to
|
||||
fn fill(&mut self, value: bool) {
|
||||
self.bit_vec.fill(value);
|
||||
}
|
||||
|
||||
fn width(&self) -> usize {
|
||||
self.width
|
||||
}
|
||||
|
||||
fn height(&self) -> usize {
|
||||
self.height
|
||||
}
|
||||
}
|
||||
|
||||
impl DataRef<u8> for Bitmap {
|
||||
fn data_ref_mut(&mut self) -> &mut [u8] {
|
||||
self.bit_vec.as_raw_mut_slice()
|
||||
}
|
||||
|
||||
fn data_ref(&self) -> &[u8] {
|
||||
self.bit_vec.as_raw_slice()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Bitmap> for Vec<u8> {
|
||||
/// Turns a [Bitmap] into the underlying [`Vec<u8>`].
|
||||
fn from(value: Bitmap) -> Self {
|
||||
value.bit_vec.into()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct IterRows<'t> {
|
||||
bitmap: &'t Bitmap,
|
||||
row: usize,
|
||||
}
|
||||
|
||||
impl<'t> Iterator for IterRows<'t> {
|
||||
type Item = &'t BitSlice<u8, Msb0>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.row >= self.bitmap.height {
|
||||
return None;
|
||||
}
|
||||
|
||||
let start = self.row * self.bitmap.width;
|
||||
let end = start + self.bitmap.width;
|
||||
self.row += 1;
|
||||
Some(&self.bitmap.bit_vec[start..end])
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{Bitmap, DataRef, Grid};
|
||||
|
||||
#[test]
|
||||
fn fill() {
|
||||
let mut grid = Bitmap::new(8, 2);
|
||||
assert_eq!(grid.data_ref(), [0x00, 0x00]);
|
||||
|
||||
grid.fill(true);
|
||||
assert_eq!(grid.data_ref(), [0xFF, 0xFF]);
|
||||
|
||||
grid.fill(false);
|
||||
assert_eq!(grid.data_ref(), [0x00, 0x00]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_set() {
|
||||
let mut grid = Bitmap::new(8, 2);
|
||||
assert!(!grid.get(0, 0));
|
||||
assert!(!grid.get(1, 1));
|
||||
|
||||
grid.set(5, 0, true);
|
||||
grid.set(1, 1, true);
|
||||
assert_eq!(grid.data_ref(), [0x04, 0x40]);
|
||||
|
||||
assert!(grid.get(5, 0));
|
||||
assert!(grid.get(1, 1));
|
||||
assert!(!grid.get(1, 0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load() {
|
||||
let mut grid = Bitmap::new(8, 3);
|
||||
for x in 0..grid.width {
|
||||
for y in 0..grid.height {
|
||||
grid.set(x, y, (x + y) % 2 == 0);
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(grid.data_ref(), [0xAA, 0x55, 0xAA]);
|
||||
|
||||
let data: Vec<u8> = grid.into();
|
||||
|
||||
let grid = Bitmap::load(8, 3, &data);
|
||||
assert_eq!(grid.data_ref(), [0xAA, 0x55, 0xAA]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn out_of_bounds_x() {
|
||||
let vec = Bitmap::new(8, 2);
|
||||
vec.get(8, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn out_of_bounds_y() {
|
||||
let mut vec = Bitmap::new(8, 2);
|
||||
vec.set(1, 2, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iter() {
|
||||
let grid = Bitmap::new(8, 2);
|
||||
assert_eq!(16, grid.iter().count())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iter_rows() {
|
||||
let grid = Bitmap::load(8, 2, &[0x04, 0x40]);
|
||||
let mut iter = grid.iter_rows();
|
||||
|
||||
assert_eq!(iter.next().unwrap().count_ones(), 1);
|
||||
assert_eq!(iter.next().unwrap().count_ones(), 1);
|
||||
assert_eq!(None, iter.next());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iter_mut() {
|
||||
let mut grid = Bitmap::new(8, 2);
|
||||
for (index, mut pixel) in grid.iter_mut().enumerate() {
|
||||
pixel.set(index % 2 == 0);
|
||||
}
|
||||
assert_eq!(grid.data_ref(), [0xAA, 0xAA]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn data_ref_mut() {
|
||||
let mut grid = Bitmap::new(8, 2);
|
||||
let data = grid.data_ref_mut();
|
||||
data[1] = 0x0F;
|
||||
assert!(grid.get(7, 1));
|
||||
}
|
||||
}
|
||||
|
|
@ -60,6 +60,17 @@ impl Brightness {
|
|||
pub const MAX: Brightness = Brightness(11);
|
||||
/// lowest possible brightness value, 0
|
||||
pub const MIN: Brightness = Brightness(0);
|
||||
|
||||
/// Create a brightness value without returning an error for brightnesses above [Brightness::MAX].
|
||||
///
|
||||
/// returns: the specified value as a [Brightness], or [Brightness::MAX].
|
||||
pub fn saturating_from(value: u8) -> Brightness {
|
||||
if value > Brightness::MAX.into() {
|
||||
Brightness::MAX
|
||||
} else {
|
||||
Brightness(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Brightness {
|
||||
|
|
@ -138,4 +149,10 @@ mod tests {
|
|||
let actual = PrimitiveGrid::from(&grid);
|
||||
assert_eq!(actual.data_ref(), &[11, 0, 11, 11]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn saturating_convert() {
|
||||
assert_eq!(Brightness::MAX, Brightness::saturating_from(100));
|
||||
assert_eq!(Brightness(5), Brightness::saturating_from(5));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use crate::{
|
|||
command_code::CommandCode,
|
||||
compression::into_decompressed,
|
||||
packet::{Header, Packet},
|
||||
Brightness, BrightnessGrid, CompressionCode, Cp437Grid, Origin, PixelGrid,
|
||||
Bitmap, Brightness, BrightnessGrid, CompressionCode, Cp437Grid, Origin,
|
||||
Pixels, PrimitiveGrid, SpBitVec, Tiles, TILE_SIZE,
|
||||
};
|
||||
|
||||
|
|
@ -76,12 +76,7 @@ pub enum Command {
|
|||
|
||||
/// Show text on the screen.
|
||||
///
|
||||
/// The text is sent in the form of a 2D grid of characters.
|
||||
///
|
||||
/// <div class="warning">
|
||||
/// The library does not currently convert between UTF-8 and CP-437.
|
||||
/// Because Rust expects UTF-8 strings, it might be necessary to only send ASCII for now.
|
||||
/// </div>
|
||||
/// The text is sent in the form of a 2D grid of [CP-437] encoded characters.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
|
|
@ -100,6 +95,7 @@ pub enum Command {
|
|||
/// let grid = Cp437Grid::load_ascii("Hello\nWorld", 5, false).unwrap();
|
||||
/// connection.send(Command::Cp437Data(Origin::new(2, 2), grid)).unwrap();
|
||||
/// ```
|
||||
/// [CP-437]: https://en.wikipedia.org/wiki/Code_page_437
|
||||
Cp437Data(Origin<Tiles>, Cp437Grid),
|
||||
|
||||
/// Overwrites a rectangular region of pixels.
|
||||
|
|
@ -109,23 +105,23 @@ pub enum Command {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use servicepoint::{Command, CompressionCode, Grid, PixelGrid};
|
||||
/// # use servicepoint::{Command, CompressionCode, Grid, Bitmap};
|
||||
/// # let connection = servicepoint::Connection::Fake;
|
||||
/// #
|
||||
/// let mut pixels = PixelGrid::max_sized();
|
||||
/// let mut pixels = Bitmap::max_sized();
|
||||
/// // draw something to the pixels here
|
||||
/// # pixels.set(2, 5, true);
|
||||
///
|
||||
/// // create command to send pixels
|
||||
/// let command = Command::BitmapLinearWin(
|
||||
/// servicepoint::Origin::new(0, 0),
|
||||
/// servicepoint::Origin::ZERO,
|
||||
/// pixels,
|
||||
/// CompressionCode::Uncompressed
|
||||
/// );
|
||||
///
|
||||
/// connection.send(command).expect("send failed");
|
||||
/// ```
|
||||
BitmapLinearWin(Origin<Pixels>, PixelGrid, CompressionCode),
|
||||
BitmapLinearWin(Origin<Pixels>, Bitmap, CompressionCode),
|
||||
|
||||
/// Set the brightness of all tiles to the same value.
|
||||
///
|
||||
|
|
@ -217,9 +213,8 @@ pub enum Command {
|
|||
BitmapLegacy,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Err values for [Command::try_from].
|
||||
#[derive(PartialEq)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum TryFromPacketError {
|
||||
/// the contained command code does not correspond to a known command
|
||||
InvalidCommand(u16),
|
||||
|
|
@ -342,7 +337,7 @@ impl Command {
|
|||
|
||||
Ok(Command::BitmapLinearWin(
|
||||
Origin::new(tiles_x as usize * TILE_SIZE, pixels_y as usize),
|
||||
PixelGrid::load(
|
||||
Bitmap::load(
|
||||
tile_w as usize * TILE_SIZE,
|
||||
pixel_h as usize,
|
||||
&payload,
|
||||
|
|
@ -376,7 +371,7 @@ impl Command {
|
|||
}
|
||||
}
|
||||
|
||||
/// Helper method for Packets into `BitMapLinear*`-Commands
|
||||
/// Helper method for Packets into `BitmapLinear*`-Commands
|
||||
fn packet_into_linear_bitmap(
|
||||
packet: Packet,
|
||||
) -> Result<(SpBitVec, CompressionCode), TryFromPacketError> {
|
||||
|
|
@ -500,7 +495,8 @@ mod tests {
|
|||
command_code::CommandCode,
|
||||
origin::Pixels,
|
||||
packet::{Header, Packet},
|
||||
Brightness, Command, CompressionCode, Origin, PixelGrid, PrimitiveGrid,
|
||||
Bitmap, Brightness, BrightnessGrid, Command, CompressionCode, Origin,
|
||||
PrimitiveGrid,
|
||||
};
|
||||
|
||||
fn round_trip(original: Command) {
|
||||
|
|
@ -592,8 +588,8 @@ mod tests {
|
|||
compression,
|
||||
));
|
||||
round_trip(Command::BitmapLinearWin(
|
||||
Origin::new(0, 0),
|
||||
PixelGrid::max_sized(),
|
||||
Origin::ZERO,
|
||||
Bitmap::max_sized(),
|
||||
compression,
|
||||
));
|
||||
}
|
||||
|
|
@ -718,7 +714,7 @@ mod tests {
|
|||
for compression in all_compressions().to_owned() {
|
||||
let p: Packet = Command::BitmapLinearWin(
|
||||
Origin::new(16, 8),
|
||||
PixelGrid::new(8, 8),
|
||||
Bitmap::new(8, 8),
|
||||
compression,
|
||||
)
|
||||
.into();
|
||||
|
|
@ -907,4 +903,28 @@ mod tests {
|
|||
Origin::new(1, 0) + Origin::new(3, 2)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn packet_into_char_brightness_invalid() {
|
||||
let grid = BrightnessGrid::new(2, 2);
|
||||
let command = Command::CharBrightness(Origin::ZERO, grid);
|
||||
let mut packet: Packet = command.into();
|
||||
let slot = packet.payload.get_mut(1).unwrap();
|
||||
*slot = 23;
|
||||
assert_eq!(
|
||||
Command::try_from(packet),
|
||||
Err(TryFromPacketError::InvalidBrightness(23))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn packet_into_brightness_invalid() {
|
||||
let mut packet: Packet = Command::Brightness(Brightness::MAX).into();
|
||||
let slot = packet.payload.get_mut(0).unwrap();
|
||||
*slot = 42;
|
||||
assert_eq!(
|
||||
Command::try_from(packet),
|
||||
Err(TryFromPacketError::InvalidBrightness(42))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,14 +3,14 @@
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use servicepoint::{Command, CompressionCode, Origin, PixelGrid};
|
||||
/// # use servicepoint::{Command, CompressionCode, Origin, Bitmap};
|
||||
/// // create command without payload compression
|
||||
/// # let pixels = PixelGrid::max_sized();
|
||||
/// _ = Command::BitmapLinearWin(Origin::new(0, 0), pixels, CompressionCode::Uncompressed);
|
||||
/// # let pixels = Bitmap::max_sized();
|
||||
/// _ = Command::BitmapLinearWin(Origin::ZERO, pixels, CompressionCode::Uncompressed);
|
||||
///
|
||||
/// // create command with payload compressed with lzma and appropriate header flags
|
||||
/// # let pixels = PixelGrid::max_sized();
|
||||
/// _ = Command::BitmapLinearWin(Origin::new(0, 0), pixels, CompressionCode::Lzma);
|
||||
/// # let pixels = Bitmap::max_sized();
|
||||
/// _ = Command::BitmapLinearWin(Origin::ZERO, pixels, CompressionCode::Lzma);
|
||||
/// ```
|
||||
#[repr(u16)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
use crate::cp437::Cp437LoadError::InvalidChar;
|
||||
//! Conversion between UTF-8 and CP-437.
|
||||
//!
|
||||
//! Most of the functionality is only available with feature "cp437" enabled.
|
||||
|
||||
use crate::{Grid, PrimitiveGrid};
|
||||
use std::collections::HashMap;
|
||||
|
||||
|
|
@ -10,9 +13,16 @@ pub type Cp437Grid = PrimitiveGrid<u8>;
|
|||
/// A grid containing UTF-8 characters.
|
||||
pub type CharGrid = PrimitiveGrid<char>;
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Errors that can occur when loading CP-437.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Cp437LoadError {
|
||||
InvalidChar { index: usize, char: char },
|
||||
/// Invalid character in input prevented loading
|
||||
InvalidChar {
|
||||
/// invalid character is at this position in input
|
||||
index: usize,
|
||||
/// the invalid character
|
||||
char: char,
|
||||
},
|
||||
}
|
||||
|
||||
impl Cp437Grid {
|
||||
|
|
@ -36,7 +46,7 @@ impl Cp437Grid {
|
|||
|
||||
for (index, char) in value.chars().enumerate() {
|
||||
if !char.is_ascii() {
|
||||
return Err(InvalidChar { index, char });
|
||||
return Err(Cp437LoadError::InvalidChar { index, char });
|
||||
}
|
||||
|
||||
let is_lf = char == '\n';
|
||||
|
|
@ -85,21 +95,15 @@ mod feature_cp437 {
|
|||
|
||||
/// An array of 256 elements, mapping most of the CP437 values to UTF-8 characters
|
||||
///
|
||||
/// Mostly follows CP437, except for:
|
||||
/// * 0x0A & 0x0D are kept for use as line endings.
|
||||
/// * 0x1A is used for SAUCE.
|
||||
/// * 0x1B is used for ANSI escape sequences.
|
||||
///
|
||||
/// These exclusions should be fine since most programs can't even use them
|
||||
/// without issues. And this makes rendering simpler too.
|
||||
/// Mostly follows CP437, except 0x0A, which is kept for use as line ending.
|
||||
///
|
||||
/// See <https://en.wikipedia.org/wiki/Code_page_437#Character_set>
|
||||
///
|
||||
/// Copied from https://github.com/kip93/cp437-tools. License: GPL-3.0
|
||||
/// Mostly copied from https://github.com/kip93/cp437-tools. License: GPL-3.0
|
||||
#[rustfmt::skip]
|
||||
const CP437_TO_UTF8: [char; 256] = [
|
||||
/* 0X */ '\0', '☺', '☻', '♥', '♦', '♣', '♠', '•', '◘', '○', '\n', '♂', '♀', '\r', '♫', '☼',
|
||||
/* 1X */ '►', '◄', '↕', '‼', '¶', '§', '▬', '↨', '↑', '↓', '', '', '∟', '↔', '▲', '▼',
|
||||
pub const CP437_TO_UTF8: [char; 256] = [
|
||||
/* 0X */ '\0', '☺', '☻', '♥', '♦', '♣', '♠', '•', '◘', '○', '\n', '♂', '♀', '♪', '♫', '☼',
|
||||
/* 1X */ '►', '◄', '↕', '‼', '¶', '§', '▬', '↨', '↑', '↓', '→', '←', '∟', '↔', '▲', '▼',
|
||||
/* 2X */ ' ', '!', '"', '#', '$', '%', '&', '\'','(', ')', '*', '+', ',', '-', '.', '/',
|
||||
/* 3X */ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?',
|
||||
/* 4X */ '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
|
||||
|
|
@ -116,7 +120,7 @@ mod feature_cp437 {
|
|||
/* FX */ '≡', '±', '≥', '≤', '⌠', '⌡', '÷', '≈', '°', '∙', '·', '√', 'ⁿ', '²', '■', ' ',
|
||||
];
|
||||
|
||||
const UTF8_TO_CP437: once_cell::sync::Lazy<HashMap<char, u8>> =
|
||||
static UTF8_TO_CP437: once_cell::sync::Lazy<HashMap<char, u8>> =
|
||||
once_cell::sync::Lazy::new(|| {
|
||||
let pairs = CP437_TO_UTF8
|
||||
.iter()
|
||||
|
|
@ -125,48 +129,34 @@ mod feature_cp437 {
|
|||
HashMap::from_iter(pairs)
|
||||
});
|
||||
|
||||
const MISSING_CHAR_CP437: u8 = 0x3F;
|
||||
const MISSING_CHAR_CP437: u8 = 0x3F; // '?'
|
||||
|
||||
impl From<&Cp437Grid> for CharGrid {
|
||||
fn from(value: &Cp437Grid) -> Self {
|
||||
let mut grid = Self::new(value.width(), value.height());
|
||||
|
||||
for y in 0..grid.height() {
|
||||
for x in 0..grid.width() {
|
||||
let converted = CP437_TO_UTF8[value.get(x, y) as usize];
|
||||
grid.set(x, y, converted);
|
||||
}
|
||||
}
|
||||
|
||||
grid
|
||||
value.map(cp437_to_char)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&CharGrid> for Cp437Grid {
|
||||
fn from(value: &CharGrid) -> Self {
|
||||
let mut grid = Self::new(value.width(), value.height());
|
||||
|
||||
for y in 0..grid.height() {
|
||||
for x in 0..grid.width() {
|
||||
let char = value.get(x, y);
|
||||
let converted = *UTF8_TO_CP437
|
||||
.get(&char)
|
||||
.unwrap_or(&MISSING_CHAR_CP437);
|
||||
grid.set(x, y, converted);
|
||||
}
|
||||
}
|
||||
|
||||
grid
|
||||
value.map(char_to_cp437)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for CharGrid {
|
||||
fn from(value: &str) -> Self {
|
||||
let value = value.replace("\r\n", "\n");
|
||||
let lines = value.split('\n').collect::<Vec<_>>();
|
||||
let mut lines = value
|
||||
.split('\n')
|
||||
.map(move |line| line.trim_end())
|
||||
.collect::<Vec<_>>();
|
||||
let width =
|
||||
lines.iter().fold(0, move |a, x| std::cmp::max(a, x.len()));
|
||||
|
||||
while lines.last().is_some_and(move |line| line.is_empty()) {
|
||||
_ = lines.pop();
|
||||
}
|
||||
|
||||
let mut grid = Self::new(width, lines.len());
|
||||
for (y, line) in lines.iter().enumerate() {
|
||||
for (x, char) in line.chars().enumerate() {
|
||||
|
|
@ -177,6 +167,44 @@ mod feature_cp437 {
|
|||
grid
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&CharGrid> for String {
|
||||
fn from(value: &CharGrid) -> Self {
|
||||
value
|
||||
.iter_rows()
|
||||
.map(move |chars| {
|
||||
chars
|
||||
.collect::<String>()
|
||||
.replace('\0', " ")
|
||||
.trim_end()
|
||||
.to_string()
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert the provided bytes to UTF-8.
|
||||
pub fn cp437_to_str(cp437: &[u8]) -> String {
|
||||
cp437.iter().map(move |char| cp437_to_char(*char)).collect()
|
||||
}
|
||||
|
||||
/// Convert a single CP-437 character to UTF-8.
|
||||
pub fn cp437_to_char(cp437: u8) -> char {
|
||||
CP437_TO_UTF8[cp437 as usize]
|
||||
}
|
||||
|
||||
/// Convert the provided text to CP-437 bytes.
|
||||
///
|
||||
/// Characters that are not available are mapped to '?'.
|
||||
pub fn str_to_cp437(utf8: &str) -> Vec<u8> {
|
||||
utf8.chars().map(char_to_cp437).collect()
|
||||
}
|
||||
|
||||
/// Convert a single UTF-8 character to CP-437.
|
||||
pub fn char_to_cp437(utf8: char) -> u8 {
|
||||
*UTF8_TO_CP437.get(&utf8).unwrap_or(&MISSING_CHAR_CP437)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
@ -204,12 +232,23 @@ mod tests {
|
|||
// line break will be added
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_ascii_invalid() {
|
||||
assert_eq!(
|
||||
Err(Cp437LoadError::InvalidChar {
|
||||
char: '🥶',
|
||||
index: 2
|
||||
}),
|
||||
Cp437Grid::load_ascii("?#🥶42", 3, false)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(feature = "cp437")]
|
||||
mod tests_feature_cp437 {
|
||||
use crate::{CharGrid, Cp437Grid};
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn round_trip_cp437() {
|
||||
|
|
@ -218,4 +257,48 @@ mod tests_feature_cp437 {
|
|||
let actual = CharGrid::from(&cp437);
|
||||
assert_eq!(actual, utf8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn convert_str() {
|
||||
// test text from https://int10h.org/oldschool-pc-fonts/fontlist/font?ibm_bios
|
||||
let utf8 = r#"A quick brown fox jumps over the lazy dog.
|
||||
0123456789 ¿?¡!`'"., <>()[]{} &@%*^#$\/
|
||||
|
||||
* Wieniläinen sioux'ta puhuva ökyzombie diggaa Åsan roquefort-tacoja.
|
||||
* Ça me fait peur de fêter noël là, sur cette île bizarroïde où une mère et sa môme essaient de me tuer avec un gâteau à la cigüe brûlé.
|
||||
* Zwölf Boxkämpfer jagten Eva quer über den Sylter Deich.
|
||||
* El pingüino Wenceslao hizo kilómetros bajo exhaustiva lluvia y frío, añoraba a su querido cachorro.
|
||||
|
||||
┌─┬─┐ ╔═╦═╗ ╒═╤═╕ ╓─╥─╖
|
||||
│ │ │ ║ ║ ║ │ │ │ ║ ║ ║
|
||||
├─┼─┤ ╠═╬═╣ ╞═╪═╡ ╟─╫─╢
|
||||
└─┴─┘ ╚═╩═╝ ╘═╧═╛ ╙─╨─╜
|
||||
|
||||
░░░░░ ▐▀█▀▌ .·∙•○°○•∙·.
|
||||
▒▒▒▒▒ ▐ █ ▌ ☺☻ ♥♦♣♠ ♪♫☼
|
||||
▓▓▓▓▓ ▐▀█▀▌ $ ¢ £ ¥ ₧
|
||||
█████ ▐▄█▄▌ ◄►▲▼ ←→↑↓↕↨
|
||||
|
||||
⌠
|
||||
│dx ≡ Σ √x²ⁿ·δx
|
||||
⌡"#;
|
||||
|
||||
let cp437 = str_to_cp437(utf8);
|
||||
let actual = cp437_to_str(&*cp437);
|
||||
assert_eq!(utf8, actual)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn convert_invalid() {
|
||||
assert_eq!(cp437_to_char(char_to_cp437('😜')), '?');
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn str_to_char_grid() {
|
||||
let original = "Hello\r\nWorld!\n...\n";
|
||||
let grid = CharGrid::from(original);
|
||||
assert_eq!(3, grid.height());
|
||||
let actual = String::from(&grid);
|
||||
assert_eq!("Hello\nWorld!\n...", actual);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -76,15 +76,9 @@ pub trait Grid<T> {
|
|||
///
|
||||
/// When the specified position is out of bounds for this grid.
|
||||
fn assert_in_bounds(&self, x: usize, y: usize) {
|
||||
assert!(
|
||||
x < self.width(),
|
||||
"cannot access index [{x}, {y}] because x is outside of bounds 0..{}",
|
||||
self.width() - 1
|
||||
);
|
||||
assert!(
|
||||
y < self.height(),
|
||||
"cannot access index [{x}, {y}] because y is outside of bounds 0..{}",
|
||||
self.height() - 1
|
||||
);
|
||||
let width = self.width();
|
||||
assert!(x < width, "cannot access index [{x}, {y}] because x is outside of bounds [0..{width})");
|
||||
let height = self.height();
|
||||
assert!(y < height, "cannot access index [{x}, {y}] because x is outside of bounds [0..{height})");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
//! # Examples
|
||||
//!
|
||||
//! ```rust
|
||||
//! use servicepoint::{Command, CompressionCode, Grid, PixelGrid};
|
||||
//! use servicepoint::{Command, CompressionCode, Grid, Bitmap};
|
||||
//!
|
||||
//! let connection = servicepoint::Connection::open("127.0.0.1:2342")
|
||||
//! .expect("connection failed");
|
||||
|
|
@ -18,15 +18,15 @@
|
|||
//! ```
|
||||
//!
|
||||
//! ```rust
|
||||
//! # use servicepoint::{Command, CompressionCode, Grid, PixelGrid};
|
||||
//! # use servicepoint::{Command, CompressionCode, Grid, Bitmap};
|
||||
//! # let connection = servicepoint::Connection::open("127.0.0.1:2342").expect("connection failed");
|
||||
//! // turn on all pixels in a grid
|
||||
//! let mut pixels = PixelGrid::max_sized();
|
||||
//! let mut pixels = Bitmap::max_sized();
|
||||
//! pixels.fill(true);
|
||||
//!
|
||||
//! // create command to send pixels
|
||||
//! let command = Command::BitmapLinearWin(
|
||||
//! servicepoint::Origin::new(0, 0),
|
||||
//! servicepoint::Origin::ZERO,
|
||||
//! pixels,
|
||||
//! CompressionCode::Uncompressed
|
||||
//! );
|
||||
|
|
@ -40,6 +40,7 @@ use std::time::Duration;
|
|||
pub use bitvec;
|
||||
use bitvec::prelude::{BitVec, Msb0};
|
||||
|
||||
pub use crate::bitmap::Bitmap;
|
||||
pub use crate::brightness::{Brightness, BrightnessGrid};
|
||||
pub use crate::command::{Command, Offset};
|
||||
pub use crate::compression_code::CompressionCode;
|
||||
|
|
@ -48,23 +49,22 @@ pub use crate::cp437::{CharGrid, Cp437Grid};
|
|||
pub use crate::data_ref::DataRef;
|
||||
pub use crate::grid::Grid;
|
||||
pub use crate::origin::{Origin, Pixels, Tiles};
|
||||
pub use crate::pixel_grid::PixelGrid;
|
||||
pub use crate::primitive_grid::PrimitiveGrid;
|
||||
|
||||
type SpBitVec = BitVec<u8, Msb0>;
|
||||
|
||||
mod bitmap;
|
||||
mod brightness;
|
||||
mod command;
|
||||
mod command_code;
|
||||
mod compression;
|
||||
mod compression_code;
|
||||
mod connection;
|
||||
mod cp437;
|
||||
pub mod cp437;
|
||||
mod data_ref;
|
||||
mod grid;
|
||||
mod origin;
|
||||
pub mod packet;
|
||||
mod pixel_grid;
|
||||
mod primitive_grid;
|
||||
|
||||
/// size of a single tile in one dimension
|
||||
|
|
@ -95,8 +95,8 @@ pub const TILE_HEIGHT: usize = 20;
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use servicepoint::{PIXEL_HEIGHT, PIXEL_WIDTH, PixelGrid};
|
||||
/// let grid = PixelGrid::new(PIXEL_WIDTH, PIXEL_HEIGHT);
|
||||
/// # use servicepoint::{PIXEL_HEIGHT, PIXEL_WIDTH, Bitmap};
|
||||
/// let grid = Bitmap::new(PIXEL_WIDTH, PIXEL_HEIGHT);
|
||||
/// ```
|
||||
pub const PIXEL_WIDTH: usize = TILE_WIDTH * TILE_SIZE;
|
||||
|
||||
|
|
@ -105,8 +105,8 @@ pub const PIXEL_WIDTH: usize = TILE_WIDTH * TILE_SIZE;
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use servicepoint::{PIXEL_HEIGHT, PIXEL_WIDTH, PixelGrid};
|
||||
/// let grid = PixelGrid::new(PIXEL_WIDTH, PIXEL_HEIGHT);
|
||||
/// # use servicepoint::{PIXEL_HEIGHT, PIXEL_WIDTH, Bitmap};
|
||||
/// let grid = Bitmap::new(PIXEL_WIDTH, PIXEL_HEIGHT);
|
||||
/// ```
|
||||
pub const PIXEL_HEIGHT: usize = TILE_HEIGHT * TILE_SIZE;
|
||||
|
||||
|
|
@ -119,10 +119,10 @@ pub const PIXEL_COUNT: usize = PIXEL_WIDTH * PIXEL_HEIGHT;
|
|||
///
|
||||
/// ```rust
|
||||
/// # use std::time::Instant;
|
||||
/// # use servicepoint::{Command, CompressionCode, FRAME_PACING, Origin, PixelGrid};
|
||||
/// # use servicepoint::{Command, CompressionCode, FRAME_PACING, Origin, Bitmap};
|
||||
/// # let connection = servicepoint::Connection::open("172.23.42.29:2342")
|
||||
/// # .expect("connection failed");
|
||||
/// # let pixels = PixelGrid::max_sized();
|
||||
/// # let pixels = Bitmap::max_sized();
|
||||
/// loop {
|
||||
/// let start = Instant::now();
|
||||
///
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ pub struct Origin<Unit: DisplayUnit> {
|
|||
}
|
||||
|
||||
impl<Unit: DisplayUnit> Origin<Unit> {
|
||||
/// Top-left. Equivalent to `Origin::new(0, 0)`.
|
||||
/// Top-left. Equivalent to `Origin::ZERO`.
|
||||
pub const ZERO: Self = Self {
|
||||
x: 0,
|
||||
y: 0,
|
||||
|
|
|
|||
|
|
@ -27,8 +27,8 @@ use std::mem::size_of;
|
|||
|
||||
use crate::compression::into_compressed;
|
||||
use crate::{
|
||||
command_code::CommandCode, Command, CompressionCode, Grid, Offset, Origin,
|
||||
PixelGrid, Pixels, Tiles, TILE_SIZE,
|
||||
command_code::CommandCode, Bitmap, Command, CompressionCode, Grid, Offset,
|
||||
Origin, Pixels, Tiles, TILE_SIZE,
|
||||
};
|
||||
|
||||
/// A raw header.
|
||||
|
|
@ -214,7 +214,7 @@ impl From<Command> for Packet {
|
|||
}
|
||||
|
||||
impl Packet {
|
||||
/// Helper method for `BitMapLinear*`-Commands into [Packet]
|
||||
/// Helper method for `BitmapLinear*`-Commands into [Packet]
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
fn bitmap_linear_into_packet(
|
||||
command: CommandCode,
|
||||
|
|
@ -239,7 +239,7 @@ impl Packet {
|
|||
#[allow(clippy::cast_possible_truncation)]
|
||||
fn bitmap_win_into_packet(
|
||||
origin: Origin<Pixels>,
|
||||
pixels: PixelGrid,
|
||||
pixels: Bitmap,
|
||||
compression: CompressionCode,
|
||||
) -> Packet {
|
||||
debug_assert_eq!(origin.x % 8, 0);
|
||||
|
|
|
|||
|
|
@ -110,6 +110,34 @@ impl<T: PrimitiveGridType> PrimitiveGrid<T> {
|
|||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert between PrimitiveGrid types.
|
||||
///
|
||||
/// See also [Iterator::map].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Use logic written for u8s and then convert to [Brightness] values for sending in a [Command].
|
||||
/// ```
|
||||
/// # fn foo(grid: &mut PrimitiveGrid<u8>) {}
|
||||
/// # use servicepoint::{Brightness, BrightnessGrid, Command, Origin, PrimitiveGrid, TILE_HEIGHT, TILE_WIDTH};
|
||||
/// let mut grid: PrimitiveGrid<u8> = PrimitiveGrid::new(TILE_WIDTH, TILE_HEIGHT);
|
||||
/// foo(&mut grid);
|
||||
/// let grid: BrightnessGrid = grid.map(Brightness::saturating_from);
|
||||
/// let command = Command::CharBrightness(Origin::ZERO, grid);
|
||||
/// ```
|
||||
pub fn map<TConverted, F>(&self, f: F) -> PrimitiveGrid<TConverted>
|
||||
where
|
||||
TConverted: PrimitiveGridType,
|
||||
F: Fn(T) -> TConverted,
|
||||
{
|
||||
let data = self
|
||||
.data_ref()
|
||||
.iter()
|
||||
.map(|elem| f(*elem))
|
||||
.collect::<Vec<_>>();
|
||||
PrimitiveGrid::load(self.width(), self.height(), &data)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: PrimitiveGridType> Grid<T> for PrimitiveGrid<T> {
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue