This commit is contained in:
parent
2f7a2dfd62
commit
c069c1966b
96 changed files with 102 additions and 12425 deletions
|
|
@ -1 +0,0 @@
|
|||
use flake
|
||||
17
.github/workflows/rust.yml
vendored
17
.github/workflows/rust.yml
vendored
|
|
@ -28,18 +28,7 @@ jobs:
|
|||
run: sudo apt-get update && sudo apt-get install -y liblzma-dev
|
||||
|
||||
- name: Run Clippy
|
||||
run: cargo clippy --all-targets --all-features
|
||||
run: cargo clippy
|
||||
|
||||
- name: build default features
|
||||
run: cargo build --all --verbose
|
||||
- name: build default features -- examples
|
||||
run: cargo build --examples --verbose
|
||||
- name: test default features
|
||||
run: cargo test --all --verbose
|
||||
|
||||
- name: build all features
|
||||
run: cargo build --all-features --verbose
|
||||
- name: build all features -- examples
|
||||
run: cargo build --all-features --examples --verbose
|
||||
- name: test all features
|
||||
run: cargo test --all --all-features --verbose
|
||||
- name: build
|
||||
run: cargo build
|
||||
|
|
|
|||
1097
Cargo.lock
generated
1097
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
37
Cargo.toml
37
Cargo.toml
|
|
@ -1,17 +1,28 @@
|
|||
[workspace]
|
||||
resolver = "2"
|
||||
members = [
|
||||
"crates/servicepoint",
|
||||
"crates/servicepoint_binding_c",
|
||||
"crates/servicepoint_binding_c/examples/lang_c",
|
||||
"crates/servicepoint_binding_uniffi"
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
[package]
|
||||
name = "servicepoint_binding_c"
|
||||
version = "0.13.1"
|
||||
publish = true
|
||||
edition = "2021"
|
||||
license = "GPL-3.0-or-later"
|
||||
description = "C bindings for the servicepoint crate."
|
||||
homepage = "https://docs.rs/crate/servicepoint_binding_c"
|
||||
repository = "https://git.berlin.ccc.de/servicepoint/servicepoint"
|
||||
readme = "README.md"
|
||||
links = "servicepoint"
|
||||
keywords = ["cccb", "cccb-servicepoint", "cbindgen"]
|
||||
|
||||
[workspace.lints.rust]
|
||||
[lib]
|
||||
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||
|
||||
[build-dependencies]
|
||||
cbindgen = "0.27.0"
|
||||
|
||||
[dependencies.servicepoint]
|
||||
version = "0.13.1"
|
||||
features = ["all_compressions"]
|
||||
|
||||
[lints.rust]
|
||||
missing-docs = "warn"
|
||||
|
||||
[workspace.dependencies]
|
||||
thiserror = "2.0"
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
|
|
|||
84
README.md
84
README.md
|
|
@ -1,49 +1,63 @@
|
|||
# servicepoint
|
||||
# servicepoint_binding_c
|
||||
|
||||
[](https://crates.io/crates/servicepoint)
|
||||
[](https://crates.io/crates/servicepoint)
|
||||
[](https://docs.rs/servicepoint/latest/servicepoint/)
|
||||
[](./LICENSE)
|
||||
[](https://crates.io/crates/servicepoint)
|
||||
[](https://crates.io/crates/servicepoint)
|
||||
[](https://docs.rs/servicepoint/latest/servicepoint/)
|
||||
[](../LICENSE)
|
||||
|
||||
In [CCCB](https://berlin.ccc.de/), there is a big pixel matrix hanging on the wall. It is called "Service Point
|
||||
Display" or "Airport Display".
|
||||
This repository contains a library for parsing, encoding and sending packets to this display via UDP in multiple
|
||||
programming languages.
|
||||
In [CCCB](https://berlin.ccc.de/), there is a big pixel matrix hanging on the wall.
|
||||
It is called "Service Point Display" or "Airport Display".
|
||||
|
||||
This project moved to [git.berlin.ccc.de/servicepoint/servicepoint](https://git.berlin.ccc.de/servicepoint/servicepoint).
|
||||
The [GitHub repository](https://github.com/cccb/servicepoint) remains available as a mirror.
|
||||
This crate contains C bindings for the `servicepoint` library, enabling users to parse, encode and send packets to this display via UDP.
|
||||
|
||||
Take a look at the contained crates for language specific information:
|
||||
## Examples
|
||||
|
||||
| Crate | Languages | Readme |
|
||||
|-----------------------------|-----------------------------------|-----------------------------------------------------------------------------|
|
||||
| servicepoint | Rust | [servicepoint](crates/servicepoint/README.md) |
|
||||
| servicepoint_binding_c | C / C++ | [servicepoint_binding_c](crates/servicepoint_binding_c/README.md) |
|
||||
| servicepoint_binding_uniffi | C# / Python / Go / Kotlin / Swift | [servicepoint_binding_uniffi](crates/servicepoint_binding_uniffi/README.md) |
|
||||
```c++
|
||||
#include <stdio.h>
|
||||
#include "servicepoint.h"
|
||||
|
||||
## Projects using the library
|
||||
int main(void) {
|
||||
SPConnection *connection = sp_connection_open("172.23.42.29:2342");
|
||||
if (connection == NULL)
|
||||
return 1;
|
||||
|
||||
- screen simulator (rust): [servicepoint-simulator](https://git.berlin.ccc.de/servicepoint/servicepoint-simulator)
|
||||
- A bunch of projects (C): [arfst23/ServicePoint](https://github.com/arfst23/ServicePoint), including
|
||||
- a CLI tool to display image files on the display or use the display as a TTY
|
||||
- a BSD games robots clone
|
||||
- a split-flap-display simulator
|
||||
- animations that play on the display
|
||||
- tanks game (C#): [servicepoint-tanks](https://github.com/kaesaecracker/cccb-tanks-cs)
|
||||
- cellular automata slideshow (rust): [servicepoint-life](https://github.com/kaesaecracker/servicepoint-life)
|
||||
- partial typescript implementation inspired by this library and browser stream: [cccb-servicepoint-browser](https://github.com/SamuelScheit/cccb-servicepoint-browser)
|
||||
- a CLI: [servicepoint-cli](https://git.berlin.ccc.de/servicepoint/servicepoint-cli)
|
||||
SPBitmap *pixels = sp_bitmap_new(SP_PIXEL_WIDTH, SP_PIXEL_HEIGHT);
|
||||
sp_bitmap_fill(pixels, true);
|
||||
|
||||
To add yourself to the list, open a pull request.
|
||||
SPCommand *command = sp_command_bitmap_linear_win(0, 0, pixels, Uncompressed);
|
||||
while (sp_connection_send_command(connection, sp_command_clone(command)));
|
||||
|
||||
You can also check out [awesome-servicepoint](https://github.com/stars/kaesaecracker/lists/awesome-servicepoint) for a bigger collection of projects, including some not related to this library.
|
||||
sp_command_free(command);
|
||||
sp_connection_free(connection);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
If you have access, there is even more software linked in [the wiki](https://wiki.berlin.ccc.de/LED-Riesendisplay).
|
||||
A full example including Makefile is available as part of this crate.
|
||||
|
||||
## Contributing
|
||||
## Note on stability
|
||||
|
||||
See [CONTRIBUTING.md](CONTRIBUTING.md).
|
||||
This library is still in early development.
|
||||
You can absolutely use it, and it works, but expect minor breaking changes with every version bump.
|
||||
Please specify the full version including patch in your Cargo.toml until 1.0 is released.
|
||||
|
||||
## What happened to servicepoint2?
|
||||
## Installation
|
||||
|
||||
After `servicepoint2` has been merged into `servicepoint`, `servicepoint2` will not continue to get any updates.
|
||||
Copy the header to your project and compile against.
|
||||
|
||||
You have the choice of linking statically (recommended) or dynamically.
|
||||
- The C example shows how to link statically against the `staticlib` variant.
|
||||
- When linked dynamically, you have to provide the `cdylib` at runtime in the _same_ version, as there are no API/ABI guarantees yet.
|
||||
|
||||
## Notes on differences to rust library
|
||||
|
||||
- function names are: `sp_` \<struct_name\> \<rust name\>.
|
||||
- Instances get consumed in the same way they do when writing rust code. Do not use an instance after an (implicit!) free.
|
||||
- Option<T> or Result<T, E> turn into nullable return values - check for NULL!
|
||||
- There are no specifics for C++ here yet. You might get a nicer header when generating directly for C++, but it should be usable.
|
||||
- Reading and writing to instances concurrently is not safe. Only reading concurrently is safe.
|
||||
- documentation is included in the header and available [online](https://docs.rs/servicepoint_binding_c/latest/servicepoint_binding_c/)
|
||||
|
||||
## Everything else
|
||||
|
||||
Look at the main project [README](https://git.berlin.ccc.de/servicepoint/servicepoint/src/branch/main/README.md) for further information.
|
||||
|
|
|
|||
33
build.rs
Normal file
33
build.rs
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
//! Build script generating the header for the `servicepoint` C library.
|
||||
//!
|
||||
//! When the environment variable `SERVICEPOINT_HEADER_OUT` is set, the header is copied there from
|
||||
//! the out directory. This can be used to use the build script as a command line tool from other
|
||||
//! build tools.
|
||||
|
||||
use std::{env, fs::copy};
|
||||
|
||||
use cbindgen::{generate_with_config, Config};
|
||||
|
||||
fn main() {
|
||||
let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
|
||||
println!("cargo::rerun-if-changed={crate_dir}");
|
||||
|
||||
let config =
|
||||
Config::from_file(crate_dir.clone() + "/cbindgen.toml").unwrap();
|
||||
|
||||
let output_dir = env::var("OUT_DIR").unwrap();
|
||||
let header_file = output_dir.clone() + "/servicepoint.h";
|
||||
|
||||
generate_with_config(crate_dir, config)
|
||||
.unwrap()
|
||||
.write_to_file(&header_file);
|
||||
println!("cargo:include={output_dir}");
|
||||
|
||||
println!("cargo::rerun-if-env-changed=SERVICEPOINT_HEADER_OUT");
|
||||
if let Ok(header_out) = env::var("SERVICEPOINT_HEADER_OUT") {
|
||||
let header_copy = header_out + "/servicepoint.h";
|
||||
println!("cargo:warning=Copying header to {header_copy}");
|
||||
copy(header_file, &header_copy).unwrap();
|
||||
println!("cargo::rerun-if-changed={header_copy}");
|
||||
}
|
||||
}
|
||||
36
cbindgen.toml
Normal file
36
cbindgen.toml
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
language = "C"
|
||||
include_version = true
|
||||
cpp_compat = true
|
||||
|
||||
autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */"
|
||||
|
||||
############################ Code Style Options ################################
|
||||
|
||||
braces = "SameLine"
|
||||
line_length = 80
|
||||
tab_width = 4
|
||||
documentation = true
|
||||
documentation_style = "auto"
|
||||
documentation_length = "full"
|
||||
line_endings = "LF"
|
||||
|
||||
############################# Codegen Options ##################################
|
||||
|
||||
style = "type"
|
||||
usize_is_size_t = true
|
||||
|
||||
# this is needed because otherwise the order in the C# bindings is different on different machines
|
||||
sort_by = "Name"
|
||||
|
||||
[parse]
|
||||
parse_deps = false
|
||||
|
||||
[parse.expand]
|
||||
all_features = true
|
||||
|
||||
[export]
|
||||
include = []
|
||||
exclude = []
|
||||
|
||||
[enum]
|
||||
rename_variants = "QualifiedScreamingSnakeCase"
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
[package]
|
||||
name = "servicepoint"
|
||||
version.workspace = true
|
||||
publish = true
|
||||
edition = "2021"
|
||||
license = "GPL-3.0-or-later"
|
||||
description = "A rust library for the CCCB Service Point Display."
|
||||
homepage = "https://docs.rs/crate/servicepoint"
|
||||
repository = "https://git.berlin.ccc.de/servicepoint/servicepoint"
|
||||
readme = "README.md"
|
||||
keywords = ["cccb", "cccb-servicepoint"]
|
||||
|
||||
[lib]
|
||||
crate-type = ["rlib"]
|
||||
|
||||
[dependencies]
|
||||
log = "0.4"
|
||||
bitvec = "1.0"
|
||||
flate2 = { version = "1.0", optional = true }
|
||||
bzip2 = { version = "0.5", optional = true }
|
||||
zstd = { version = "0.13", optional = true }
|
||||
rust-lzma = { version = "0.6", optional = true }
|
||||
rand = { version = "0.8", optional = true }
|
||||
tungstenite = { version = "0.26", optional = true }
|
||||
once_cell = { version = "1.20", optional = true }
|
||||
thiserror.workspace = true
|
||||
|
||||
[features]
|
||||
default = ["compression_lzma", "protocol_udp", "cp437"]
|
||||
compression_zlib = ["dep:flate2"]
|
||||
compression_bzip2 = ["dep:bzip2"]
|
||||
compression_lzma = ["dep:rust-lzma"]
|
||||
compression_zstd = ["dep:zstd"]
|
||||
all_compressions = ["compression_zlib", "compression_bzip2", "compression_lzma", "compression_zstd"]
|
||||
rand = ["dep:rand"]
|
||||
protocol_udp = []
|
||||
protocol_websocket = ["dep:tungstenite"]
|
||||
cp437 = ["dep:once_cell"]
|
||||
|
||||
[[example]]
|
||||
name = "random_brightness"
|
||||
required-features = ["rand"]
|
||||
|
||||
[[example]]
|
||||
name = "game_of_life"
|
||||
required-features = ["rand"]
|
||||
|
||||
[[example]]
|
||||
name = "websocket"
|
||||
required-features = ["protocol_websocket"]
|
||||
|
||||
[dev-dependencies]
|
||||
# for examples
|
||||
clap = { version = "4.5", features = ["derive"] }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
|
@ -1,67 +0,0 @@
|
|||
# servicepoint
|
||||
|
||||
[](https://crates.io/crates/servicepoint)
|
||||
[](https://crates.io/crates/servicepoint)
|
||||
[](https://docs.rs/servicepoint/latest/servicepoint/)
|
||||
[](../../LICENSE)
|
||||
|
||||
In [CCCB](https://berlin.ccc.de/), there is a big pixel matrix hanging on the wall. It is called "Service Point
|
||||
Display" or "Airport Display".
|
||||
This crate contains a library for parsing, encoding and sending packets to this display via UDP.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
cargo add servicepoint
|
||||
```
|
||||
or
|
||||
```toml
|
||||
[dependencies]
|
||||
servicepoint = "0.13.1"
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
```rust no_run
|
||||
fn main() {
|
||||
// establish connection
|
||||
let connection = servicepoint::Connection::open("172.23.42.29:2342")
|
||||
.expect("connection failed");
|
||||
|
||||
// clear screen content
|
||||
connection.send(servicepoint::Command::Clear)
|
||||
.expect("send failed");
|
||||
}
|
||||
```
|
||||
|
||||
More examples are available in the crate.
|
||||
Execute `cargo run --example` for a list of available examples and `cargo run --example <name>` to run one.
|
||||
|
||||
## Note on stability
|
||||
|
||||
This library can be used for creative project or just to play around with the display.
|
||||
A decent coverage by unit tests prevents major problems and I also test this with my own projects, which mostly use up-to-date versions.
|
||||
|
||||
That being said, the API is still being worked on.
|
||||
Expect minor breaking changes with every version bump.
|
||||
Please specify the full version including patch in your Cargo.toml until 1.0 is released.
|
||||
|
||||
## Features
|
||||
|
||||
This library has multiple optional dependencies.
|
||||
You can choose to (not) include them by toggling the related features.
|
||||
|
||||
| Name | Default | Description |
|
||||
|--------------------|---------|--------------------------------------------|
|
||||
| compression_zlib | false | Enable additional compression algo |
|
||||
| compression_bzip2 | false | Enable additional compression algo |
|
||||
| compression_lzma | true | Enable additional compression algo |
|
||||
| compression_zstd | false | Enable additional compression algo |
|
||||
| protocol_udp | true | Connection::Udp |
|
||||
| protocol_websocket | false | Connection::WebSocket |
|
||||
| rand | false | impl Distribution<Brightness> for Standard |
|
||||
| cp437 | true | Conversion to and from CP-437 |
|
||||
|
||||
## Everything else
|
||||
|
||||
Look at the main project [README](https://git.berlin.ccc.de/servicepoint/servicepoint/src/branch/main/README.md) for further information.
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
//! An example for how to send text to the display.
|
||||
|
||||
use clap::Parser;
|
||||
use servicepoint::*;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
struct Cli {
|
||||
#[arg(
|
||||
short,
|
||||
long,
|
||||
default_value = "localhost:2342",
|
||||
help = "Address of the display"
|
||||
)]
|
||||
destination: String,
|
||||
#[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,
|
||||
help = "Clear screen before sending text"
|
||||
)]
|
||||
clear: bool,
|
||||
}
|
||||
|
||||
/// example: `cargo run -- --text "Hallo" --text "CCCB"`
|
||||
fn main() {
|
||||
let mut cli = Cli::parse();
|
||||
if cli.text.is_empty() {
|
||||
cli.text.push("Hello, CCCB!".to_string());
|
||||
}
|
||||
|
||||
let connection = Connection::open(&cli.destination)
|
||||
.expect("could not connect to display");
|
||||
|
||||
if cli.clear {
|
||||
connection
|
||||
.send(Command::Clear)
|
||||
.expect("sending clear failed");
|
||||
}
|
||||
|
||||
let text = cli.text.join("\n");
|
||||
let grid = CharGrid::wrap_str(TILE_WIDTH, &text);
|
||||
connection
|
||||
.send(Command::Utf8Data(Origin::ZERO, grid))
|
||||
.expect("sending text failed");
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
//! Show a brightness level test pattern on screen
|
||||
|
||||
use clap::Parser;
|
||||
use servicepoint::*;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
struct Cli {
|
||||
#[arg(short, long, default_value = "localhost:2342")]
|
||||
destination: String,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let cli = Cli::parse();
|
||||
let connection = Connection::open(cli.destination)
|
||||
.expect("could not connect to display");
|
||||
|
||||
let mut pixels = Bitmap::max_sized();
|
||||
pixels.fill(true);
|
||||
|
||||
let command = Command::BitmapLinearWin(
|
||||
Origin::ZERO,
|
||||
pixels,
|
||||
CompressionCode::Uncompressed,
|
||||
);
|
||||
connection.send(command).expect("send failed");
|
||||
|
||||
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() {
|
||||
let level = index as u8 % max_brightness;
|
||||
*byte = Brightness::try_from(level).unwrap();
|
||||
}
|
||||
|
||||
connection
|
||||
.send(Command::CharBrightness(Origin::ZERO, brightnesses))
|
||||
.expect("send failed");
|
||||
}
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
//! A simple game of life implementation to show how to render graphics to the display.
|
||||
|
||||
use clap::Parser;
|
||||
use rand::{distributions, Rng};
|
||||
use servicepoint::*;
|
||||
use std::thread;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
struct Cli {
|
||||
#[arg(short, long, default_value = "localhost:2342")]
|
||||
destination: String,
|
||||
#[arg(short, long, default_value_t = 0.5f64)]
|
||||
probability: f64,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let cli = Cli::parse();
|
||||
|
||||
let connection = Connection::open(&cli.destination)
|
||||
.expect("could not connect to display");
|
||||
let mut field = make_random_field(cli.probability);
|
||||
|
||||
loop {
|
||||
let command = Command::BitmapLinearWin(
|
||||
Origin::ZERO,
|
||||
field.clone(),
|
||||
CompressionCode::Lzma,
|
||||
);
|
||||
connection.send(command).expect("could not send");
|
||||
thread::sleep(FRAME_PACING);
|
||||
field = iteration(field);
|
||||
}
|
||||
}
|
||||
|
||||
fn iteration(field: Bitmap) -> Bitmap {
|
||||
let mut next = field.clone();
|
||||
for x in 0..field.width() {
|
||||
for y in 0..field.height() {
|
||||
let old_state = field.get(x, y);
|
||||
let neighbors = count_neighbors(&field, x as i32, y as i32);
|
||||
|
||||
let new_state = matches!(
|
||||
(old_state, neighbors),
|
||||
(true, 2) | (true, 3) | (false, 3)
|
||||
);
|
||||
next.set(x, y, new_state);
|
||||
}
|
||||
}
|
||||
next
|
||||
}
|
||||
|
||||
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 {
|
||||
if nx == x && ny == y {
|
||||
continue; // the cell itself does not count
|
||||
}
|
||||
|
||||
if nx < 0
|
||||
|| ny < 0
|
||||
|| nx >= field.width() as i32
|
||||
|| ny >= field.height() as i32
|
||||
{
|
||||
continue; // pixels outside the grid do not count
|
||||
}
|
||||
|
||||
if !field.get(nx as usize, ny as usize) {
|
||||
continue; // dead cells do not count
|
||||
}
|
||||
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
count
|
||||
}
|
||||
|
||||
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() {
|
||||
for y in 0..field.height() {
|
||||
field.set(x, y, rng.sample(d));
|
||||
}
|
||||
}
|
||||
field
|
||||
}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
//! A simple example for how to send pixel data to the display.
|
||||
|
||||
use clap::Parser;
|
||||
use servicepoint::*;
|
||||
use std::thread;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
struct Cli {
|
||||
#[arg(short, long, default_value = "localhost:2342")]
|
||||
destination: String,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let connection = Connection::open(Cli::parse().destination)
|
||||
.expect("could not connect to display");
|
||||
|
||||
let mut pixels = Bitmap::max_sized();
|
||||
for x_offset in 0..usize::MAX {
|
||||
pixels.fill(false);
|
||||
|
||||
for y in 0..PIXEL_HEIGHT {
|
||||
pixels.set((y + x_offset) % PIXEL_WIDTH, y, true);
|
||||
}
|
||||
|
||||
let command = Command::BitmapLinearWin(
|
||||
Origin::ZERO,
|
||||
pixels.clone(),
|
||||
CompressionCode::Lzma,
|
||||
);
|
||||
connection.send(command).expect("send failed");
|
||||
thread::sleep(FRAME_PACING);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
//! A simple example for how to set brightnesses for tiles on the screen.
|
||||
//! Continuously changes the tiles in a random window to random brightnesses.
|
||||
|
||||
use clap::Parser;
|
||||
use rand::Rng;
|
||||
use servicepoint::*;
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
struct Cli {
|
||||
#[arg(short, long, default_value = "localhost:2342")]
|
||||
destination: String,
|
||||
#[arg(short, long, default_value_t = true)]
|
||||
enable_all: bool,
|
||||
#[arg(short, long, default_value_t = 100, allow_negative_numbers = false)]
|
||||
wait_ms: u64,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let cli = Cli::parse();
|
||||
|
||||
let connection = Connection::open(cli.destination)
|
||||
.expect("could not connect to display");
|
||||
let wait_duration = Duration::from_millis(cli.wait_ms);
|
||||
|
||||
// put all pixels in on state
|
||||
if cli.enable_all {
|
||||
let mut filled_grid = Bitmap::max_sized();
|
||||
filled_grid.fill(true);
|
||||
|
||||
let command = Command::BitmapLinearWin(
|
||||
Origin::ZERO,
|
||||
filled_grid,
|
||||
CompressionCode::Lzma,
|
||||
);
|
||||
connection.send(command).expect("send failed");
|
||||
}
|
||||
|
||||
// set all pixels to the same random brightness
|
||||
let mut rng = rand::thread_rng();
|
||||
connection.send(Command::Brightness(rng.gen())).unwrap();
|
||||
|
||||
// continuously update random windows to new random brightness
|
||||
loop {
|
||||
let min_size = 1;
|
||||
let x = rng.gen_range(0..TILE_WIDTH - min_size);
|
||||
let y = rng.gen_range(0..TILE_HEIGHT - min_size);
|
||||
|
||||
let w = rng.gen_range(min_size..=TILE_WIDTH - x);
|
||||
let h = rng.gen_range(min_size..=TILE_HEIGHT - y);
|
||||
|
||||
let origin = Origin::new(x, y);
|
||||
let mut luma = BrightnessGrid::new(w, h);
|
||||
|
||||
for y in 0..h {
|
||||
for x in 0..w {
|
||||
luma.set(x, y, rng.gen());
|
||||
}
|
||||
}
|
||||
|
||||
connection
|
||||
.send(Command::CharBrightness(origin, luma))
|
||||
.unwrap();
|
||||
std::thread::sleep(wait_duration);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
//! Example for how to use the WebSocket connection
|
||||
|
||||
use servicepoint::{
|
||||
Bitmap, Command, CompressionCode, Connection, Grid, Origin,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
let connection =
|
||||
Connection::open_websocket("ws://localhost:8080".parse().unwrap())
|
||||
.unwrap();
|
||||
|
||||
connection.send(Command::Clear).unwrap();
|
||||
|
||||
let mut pixels = Bitmap::max_sized();
|
||||
pixels.fill(true);
|
||||
|
||||
connection
|
||||
.send(Command::BitmapLinearWin(
|
||||
Origin::ZERO,
|
||||
pixels,
|
||||
CompressionCode::Lzma,
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
//! An example on how to modify the image on screen without knowing the current content.
|
||||
use clap::Parser;
|
||||
use servicepoint::*;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
struct Cli {
|
||||
#[arg(short, long, default_value = "localhost:2342")]
|
||||
destination: String,
|
||||
#[arg(short, long = "duration-ms", default_value_t = 5000)]
|
||||
time: u64,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let cli = Cli::parse();
|
||||
|
||||
let sleep_duration = Duration::max(
|
||||
FRAME_PACING,
|
||||
Duration::from_millis(cli.time / PIXEL_WIDTH as u64),
|
||||
);
|
||||
|
||||
let connection = Connection::open(cli.destination)
|
||||
.expect("could not connect to display");
|
||||
|
||||
let mut enabled_pixels = Bitmap::max_sized();
|
||||
enabled_pixels.fill(true);
|
||||
|
||||
for x_offset in 0..PIXEL_WIDTH {
|
||||
for y in 0..PIXEL_HEIGHT {
|
||||
enabled_pixels.set(x_offset % PIXEL_WIDTH, y, false);
|
||||
}
|
||||
|
||||
connection
|
||||
.send(Command::BitmapLinearWin(
|
||||
Origin::ZERO,
|
||||
enabled_pixels.clone(),
|
||||
CompressionCode::Lzma,
|
||||
))
|
||||
.expect("could not send command to display");
|
||||
thread::sleep(sleep_duration);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
/// A byte-packed vector of booleans.
|
||||
///
|
||||
/// The implementation is provided by [bitvec].
|
||||
/// This is an alias for the specific type of [bitvec::BitVec] used in this crate.
|
||||
pub type BitVec = bitvec::BitVec<u8, bitvec::Msb0>;
|
||||
|
||||
pub mod bitvec {
|
||||
//! Re-export of the used library [mod@bitvec].
|
||||
pub use bitvec::prelude::*;
|
||||
}
|
||||
|
|
@ -1,379 +0,0 @@
|
|||
use crate::data_ref::DataRef;
|
||||
use crate::BitVec;
|
||||
use crate::*;
|
||||
use ::bitvec::order::Msb0;
|
||||
use ::bitvec::prelude::BitSlice;
|
||||
use ::bitvec::slice::IterMut;
|
||||
|
||||
/// A fixed-size 2D grid of booleans.
|
||||
///
|
||||
/// The values are stored in packed bytes (8 values per byte) in the same order as used by the display for storing pixels.
|
||||
/// This means that no conversion is necessary for sending the data to the display.
|
||||
/// The downside is that the width has to be a multiple of 8.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use servicepoint::Bitmap;
|
||||
/// let mut bitmap = Bitmap::new(8, 2);
|
||||
///
|
||||
/// ```
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Bitmap {
|
||||
width: usize,
|
||||
height: usize,
|
||||
bit_vec: BitVec,
|
||||
}
|
||||
|
||||
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,
|
||||
"width must be a multiple of 8, but is {width}"
|
||||
);
|
||||
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, "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 {
|
||||
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
|
||||
/// - 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}");
|
||||
let len = bit_vec.len();
|
||||
let height = len / width;
|
||||
assert_eq!(0, len % width, "dimension mismatch - len {len} is not dividable by {width}");
|
||||
Self { width, height, bit_vec }
|
||||
}
|
||||
|
||||
/// 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()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Bitmap> for BitVec {
|
||||
/// Turns a [Bitmap] into the underlying [BitVec].
|
||||
fn from(value: Bitmap) -> Self {
|
||||
value.bit_vec
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ValueGrid<bool>> for Bitmap {
|
||||
/// 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());
|
||||
for (mut to, from) in result.iter_mut().zip(value.iter()) {
|
||||
*to = *from;
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Bitmap> for ValueGrid<bool> {
|
||||
/// Converts a [Bitmap] into a grid of [bool]s.
|
||||
fn from(value: &Bitmap) -> Self {
|
||||
let mut result = Self::new(value.width(), value.height());
|
||||
for (to, from) in result.iter_mut().zip(value.iter()) {
|
||||
*to = *from;
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
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::{BitVec, Bitmap, DataRef, Grid, ValueGrid};
|
||||
|
||||
#[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));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_bitvec() {
|
||||
let mut grid = Bitmap::new(8, 2);
|
||||
grid.set(0, 0, true);
|
||||
let bitvec: BitVec = grid.into();
|
||||
assert_eq!(bitvec.as_raw_slice(), [0x80, 0x00]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_bool_grid() {
|
||||
let original = ValueGrid::load(
|
||||
8,
|
||||
1,
|
||||
&[true, false, true, false, true, false, true, false],
|
||||
);
|
||||
let converted = Bitmap::from(&original);
|
||||
let reconverted = ValueGrid::from(&converted);
|
||||
assert_eq!(original, reconverted);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,109 +0,0 @@
|
|||
#[cfg(feature = "rand")]
|
||||
use rand::{
|
||||
distributions::{Distribution, Standard},
|
||||
Rng,
|
||||
};
|
||||
|
||||
/// A display brightness value, checked for correct value range
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use servicepoint::{Brightness, Command, Connection};
|
||||
/// let b = Brightness::MAX;
|
||||
/// let val: u8 = b.into();
|
||||
///
|
||||
/// let b = Brightness::try_from(7).unwrap();
|
||||
/// # let connection = Connection::open("127.0.0.1:2342").unwrap();
|
||||
/// let result = connection.send(Command::Brightness(b));
|
||||
/// ```
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd)]
|
||||
pub struct Brightness(u8);
|
||||
|
||||
impl From<Brightness> for u8 {
|
||||
fn from(brightness: Brightness) -> Self {
|
||||
Self::from(&brightness)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Brightness> for u8 {
|
||||
fn from(brightness: &Brightness) -> Self {
|
||||
brightness.0
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for Brightness {
|
||||
type Error = u8;
|
||||
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
if value > Self::MAX.0 {
|
||||
Err(value)
|
||||
} else {
|
||||
Ok(Brightness(value))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Brightness {
|
||||
/// highest possible brightness value, 11
|
||||
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 {
|
||||
fn default() -> Self {
|
||||
Self::MAX
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rand")]
|
||||
impl Distribution<Brightness> for Standard {
|
||||
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Brightness {
|
||||
Brightness(rng.gen_range(Brightness::MIN.0..=Brightness::MAX.0))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn brightness_from_u8() {
|
||||
assert_eq!(Err(100), Brightness::try_from(100));
|
||||
assert_eq!(Ok(Brightness(1)), Brightness::try_from(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "rand")]
|
||||
fn rand_brightness() {
|
||||
let mut rng = rand::thread_rng();
|
||||
for _ in 0..100 {
|
||||
let _: Brightness = rng.gen();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn saturating_convert() {
|
||||
assert_eq!(Brightness::MAX, Brightness::saturating_from(100));
|
||||
assert_eq!(Brightness(5), Brightness::saturating_from(5));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "rand")]
|
||||
fn test() {
|
||||
let mut rng = rand::thread_rng();
|
||||
assert_ne!(rng.gen::<Brightness>(), rng.gen());
|
||||
}
|
||||
}
|
||||
|
|
@ -1,93 +0,0 @@
|
|||
use crate::brightness::Brightness;
|
||||
use crate::grid::Grid;
|
||||
use crate::value_grid::ValueGrid;
|
||||
use crate::ByteGrid;
|
||||
|
||||
/// A grid containing brightness values.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use servicepoint::{Brightness, BrightnessGrid, Command, Connection, Grid, Origin};
|
||||
/// let mut grid = BrightnessGrid::new(2,2);
|
||||
/// grid.set(0, 0, Brightness::MIN);
|
||||
/// grid.set(1, 1, Brightness::MIN);
|
||||
///
|
||||
/// # let connection = Connection::open("127.0.0.1:2342").unwrap();
|
||||
/// connection.send(Command::CharBrightness(Origin::new(3, 7), grid)).unwrap()
|
||||
/// ```
|
||||
pub type BrightnessGrid = ValueGrid<Brightness>;
|
||||
|
||||
impl BrightnessGrid {
|
||||
/// Like [Self::load], but ignoring any out-of-range brightness values
|
||||
pub fn saturating_load(width: usize, height: usize, data: &[u8]) -> Self {
|
||||
ValueGrid::load(width, height, data).map(Brightness::saturating_from)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BrightnessGrid> for Vec<u8> {
|
||||
fn from(value: BrightnessGrid) -> Self {
|
||||
value
|
||||
.iter()
|
||||
.map(|brightness| (*brightness).into())
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&BrightnessGrid> for ByteGrid {
|
||||
fn from(value: &BrightnessGrid) -> Self {
|
||||
let u8s = value
|
||||
.iter()
|
||||
.map(|brightness| (*brightness).into())
|
||||
.collect::<Vec<u8>>();
|
||||
ValueGrid::load(value.width(), value.height(), &u8s)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<ByteGrid> for BrightnessGrid {
|
||||
type Error = u8;
|
||||
|
||||
fn try_from(value: ByteGrid) -> Result<Self, Self::Error> {
|
||||
let brightnesses = value
|
||||
.iter()
|
||||
.map(|b| Brightness::try_from(*b))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
Ok(BrightnessGrid::load(
|
||||
value.width(),
|
||||
value.height(),
|
||||
&brightnesses,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::value_grid::ValueGrid;
|
||||
use crate::{Brightness, BrightnessGrid, DataRef, Grid};
|
||||
|
||||
#[test]
|
||||
fn to_u8_grid() {
|
||||
let mut grid = BrightnessGrid::new(2, 2);
|
||||
grid.set(1, 0, Brightness::MIN);
|
||||
grid.set(0, 1, Brightness::MAX);
|
||||
let actual = ValueGrid::from(&grid);
|
||||
assert_eq!(actual.data_ref(), &[11, 0, 11, 11]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn saturating_load() {
|
||||
assert_eq!(
|
||||
BrightnessGrid::load(
|
||||
2,
|
||||
2,
|
||||
&[
|
||||
Brightness::MAX,
|
||||
Brightness::MAX,
|
||||
Brightness::MIN,
|
||||
Brightness::MAX
|
||||
]
|
||||
),
|
||||
BrightnessGrid::saturating_load(2, 2, &[255u8, 23, 0, 42])
|
||||
);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue