add send image command
This commit is contained in:
parent
19f24f9331
commit
b1c3ac8538
26
README.md
26
README.md
|
@ -54,6 +54,8 @@ Options:
|
|||
### Stream
|
||||
|
||||
```
|
||||
Continuously send data to the display
|
||||
|
||||
Usage: servicepoint-cli stream <COMMAND>
|
||||
|
||||
Commands:
|
||||
|
@ -108,9 +110,27 @@ Commands for manipulating pixels
|
|||
Usage: servicepoint-cli pixels <COMMAND>
|
||||
|
||||
Commands:
|
||||
off Reset all pixels to the default (off) state [aliases: r, reset, clear]
|
||||
invert Invert the state of all pixels [aliases: i]
|
||||
on Set all pixels to the on state
|
||||
off Reset all pixels to the default (off) state [aliases: r, reset, clear]
|
||||
flip Invert the state of all pixels [aliases: f]
|
||||
on Set all pixels to the on state
|
||||
image Send an image file (e.g. jpeg or png) to the display. [aliases: i]
|
||||
```
|
||||
|
||||
#### Image
|
||||
|
||||
```
|
||||
Send an image file (e.g. jpeg or png) to the display.
|
||||
|
||||
Usage: servicepoint-cli pixels image [OPTIONS] <FILE_NAME>
|
||||
|
||||
Arguments:
|
||||
<FILE_NAME>
|
||||
|
||||
Options:
|
||||
--no-hist Disable histogram correction
|
||||
--no-blur Disable blur
|
||||
--no-sharp Disable sharpening
|
||||
--no-dither Disable dithering. Brightness will be adjusted so that around half of the pixels are on.
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
|
31
src/cli.rs
31
src/cli.rs
|
@ -57,10 +57,20 @@ pub enum PixelCommand {
|
|||
about = "Reset all pixels to the default (off) state"
|
||||
)]
|
||||
Off,
|
||||
#[command(visible_alias = "i", about = "Invert the state of all pixels")]
|
||||
Invert,
|
||||
#[command(visible_alias = "f", about = "Invert the state of all pixels")]
|
||||
Flip,
|
||||
#[command(about = "Set all pixels to the on state")]
|
||||
On,
|
||||
#[command(
|
||||
visible_alias = "i",
|
||||
about = "Send an image file (e.g. jpeg or png) to the display."
|
||||
)]
|
||||
Image {
|
||||
#[command(flatten)]
|
||||
send_image_options: SendImageOptions,
|
||||
#[command(flatten)]
|
||||
image_processing_options: ImageProcessingOptions,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(clap::Parser, std::fmt::Debug)]
|
||||
|
@ -91,7 +101,7 @@ pub enum Protocol {
|
|||
#[derive(clap::Parser, std::fmt::Debug)]
|
||||
#[clap(about = "Continuously send data to the display")]
|
||||
pub enum StreamCommand {
|
||||
#[clap(
|
||||
#[command(
|
||||
about = "Pipe text to the display, example: `journalctl | servicepoint-cli stream stdin`"
|
||||
)]
|
||||
Stdin {
|
||||
|
@ -103,12 +113,14 @@ pub enum StreamCommand {
|
|||
)]
|
||||
slow: bool,
|
||||
},
|
||||
#[clap(about = "Stream the default source to the display. \
|
||||
#[command(about = "Stream the default source to the display. \
|
||||
On Linux Wayland, this pops up a screen or window chooser, \
|
||||
but it also may directly start streaming your main screen.")]
|
||||
Screen {
|
||||
#[command(flatten)]
|
||||
options: StreamScreenOptions,
|
||||
stream_options: StreamScreenOptions,
|
||||
#[command(flatten)]
|
||||
image_processing: ImageProcessingOptions,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -121,9 +133,6 @@ pub struct StreamScreenOptions {
|
|||
help = "Show mouse pointer in video feed"
|
||||
)]
|
||||
pub pointer: bool,
|
||||
|
||||
#[transparent]
|
||||
pub image_processing: ImageProcessingOptions,
|
||||
}
|
||||
|
||||
#[derive(clap::Parser, std::fmt::Debug, Clone)]
|
||||
|
@ -143,3 +152,9 @@ pub struct ImageProcessingOptions {
|
|||
)]
|
||||
pub no_dither: bool,
|
||||
}
|
||||
|
||||
#[derive(clap::Parser, std::fmt::Debug, Clone)]
|
||||
pub struct SendImageOptions {
|
||||
#[arg()]
|
||||
pub file_name: String,
|
||||
}
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
use crate::cli::ImageProcessingOptions;
|
||||
use crate::ledwand_dither::{
|
||||
blur, histogram_correction, median_brightness, ostromoukhov_dither, sharpen,
|
||||
use crate::{
|
||||
cli::ImageProcessingOptions,
|
||||
ledwand_dither::{blur, histogram_correction, median_brightness, ostromoukhov_dither, sharpen},
|
||||
};
|
||||
use image::{
|
||||
imageops::{resize, FilterType},
|
||||
DynamicImage, ImageBuffer, Luma,
|
||||
};
|
||||
use image::imageops::{resize, FilterType};
|
||||
use image::{imageops, DynamicImage, ImageBuffer, Luma};
|
||||
use servicepoint::{Bitmap, PIXEL_HEIGHT, PIXEL_WIDTH};
|
||||
|
||||
pub struct ImageProcessingPipeline {
|
||||
|
@ -22,7 +24,7 @@ impl ImageProcessingPipeline {
|
|||
}
|
||||
|
||||
fn resize_grayscale(frame: &DynamicImage) -> ImageBuffer<Luma<u8>, Vec<u8>> {
|
||||
let frame = imageops::grayscale(&frame);
|
||||
let frame = frame.grayscale().to_luma8();
|
||||
let frame = resize(
|
||||
&frame,
|
||||
PIXEL_WIDTH as u32,
|
||||
|
@ -54,7 +56,7 @@ impl ImageProcessingPipeline {
|
|||
orig
|
||||
}
|
||||
|
||||
fn grayscale_to_bitmap(&self, mut orig: ImageBuffer<Luma<u8>, Vec<u8>>) -> Bitmap {
|
||||
fn grayscale_to_bitmap(&self, orig: ImageBuffer<Luma<u8>, Vec<u8>>) -> Bitmap {
|
||||
if self.options.no_dither {
|
||||
let cutoff = median_brightness(&orig);
|
||||
let bits = orig.iter().map(move |x| x > &cutoff).collect();
|
||||
|
|
|
@ -38,7 +38,10 @@ pub fn execute_mode(mode: Mode, connection: Connection) {
|
|||
Mode::Brightness { brightness_command } => brightness(&connection, brightness_command),
|
||||
Mode::Stream { stream_command } => match stream_command {
|
||||
StreamCommand::Stdin { slow } => stream_stdin(connection, slow),
|
||||
StreamCommand::Screen { options } => stream_window(&connection, options),
|
||||
StreamCommand::Screen {
|
||||
stream_options,
|
||||
image_processing,
|
||||
} => stream_window(&connection, stream_options, image_processing),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,17 @@
|
|||
use crate::cli::PixelCommand;
|
||||
use crate::cli::{ImageProcessingOptions, PixelCommand, SendImageOptions};
|
||||
use crate::image_processing::ImageProcessingPipeline;
|
||||
use log::info;
|
||||
use servicepoint::{BitVec, Command, CompressionCode, Connection, PIXEL_COUNT};
|
||||
use servicepoint::{BitVec, Command, CompressionCode, Connection, Origin, PIXEL_COUNT};
|
||||
|
||||
pub(crate) fn pixels(connection: &Connection, pixel_command: PixelCommand) {
|
||||
match pixel_command {
|
||||
PixelCommand::Off => pixels_off(connection),
|
||||
PixelCommand::Invert => pixels_invert(connection),
|
||||
PixelCommand::Flip => pixels_invert(connection),
|
||||
PixelCommand::On => pixels_on(connection),
|
||||
PixelCommand::Image {
|
||||
image_processing_options: processing_options,
|
||||
send_image_options: image_options,
|
||||
} => pixels_image(connection, image_options, processing_options),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -32,3 +37,20 @@ pub(crate) fn pixels_off(connection: &Connection) {
|
|||
.expect("failed to clear pixels");
|
||||
info!("reset pixels");
|
||||
}
|
||||
|
||||
fn pixels_image(
|
||||
connection: &Connection,
|
||||
options: SendImageOptions,
|
||||
processing_options: ImageProcessingOptions,
|
||||
) {
|
||||
let image = image::open(&options.file_name).expect("failed to open image file");
|
||||
let pipeline = ImageProcessingPipeline::new(processing_options);
|
||||
let bitmap = pipeline.process(image);
|
||||
connection
|
||||
.send(Command::BitmapLinearWin(
|
||||
Origin::ZERO,
|
||||
bitmap,
|
||||
CompressionCode::default(),
|
||||
))
|
||||
.expect("failed to send image command");
|
||||
}
|
||||
|
|
|
@ -1,12 +1,6 @@
|
|||
use crate::{
|
||||
cli::{ImageProcessingOptions, StreamScreenOptions},
|
||||
image_processing::ImageProcessingPipeline,
|
||||
ledwand_dither::*,
|
||||
};
|
||||
use image::{
|
||||
imageops::{resize, FilterType},
|
||||
DynamicImage, ImageBuffer, Luma, Rgb, Rgba,
|
||||
};
|
||||
use crate::cli::{ImageProcessingOptions, StreamScreenOptions};
|
||||
use crate::image_processing::ImageProcessingPipeline;
|
||||
use image::{DynamicImage, ImageBuffer, Rgb, Rgba};
|
||||
use log::{error, info, warn};
|
||||
use scap::{
|
||||
capturer::{Capturer, Options},
|
||||
|
@ -14,19 +8,26 @@ use scap::{
|
|||
frame::Frame,
|
||||
};
|
||||
use servicepoint::{
|
||||
Bitmap, Command, CompressionCode, Connection, Origin, FRAME_PACING, PIXEL_HEIGHT, PIXEL_WIDTH,
|
||||
TILE_HEIGHT, TILE_SIZE,
|
||||
Command, CompressionCode, Connection, Origin, FRAME_PACING, PIXEL_HEIGHT, TILE_HEIGHT,
|
||||
TILE_SIZE,
|
||||
};
|
||||
use std::time::Duration;
|
||||
|
||||
pub fn stream_window(connection: &Connection, options: StreamScreenOptions) {
|
||||
const SPACER_HEIGHT: usize = TILE_SIZE / 2;
|
||||
const PIXEL_HEIGHT_INCLUDING_SPACERS: usize = SPACER_HEIGHT * (TILE_HEIGHT - 1) + PIXEL_HEIGHT;
|
||||
|
||||
pub fn stream_window(
|
||||
connection: &Connection,
|
||||
options: StreamScreenOptions,
|
||||
processing_options: ImageProcessingOptions,
|
||||
) {
|
||||
info!("Starting capture with options: {:?}", options);
|
||||
let capturer = match start_capture(&options) {
|
||||
Some(value) => value,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let pipeline = ImageProcessingPipeline::new(options.image_processing);
|
||||
let pipeline = ImageProcessingPipeline::new(processing_options);
|
||||
|
||||
info!("now starting to stream images");
|
||||
loop {
|
||||
|
@ -57,10 +58,11 @@ fn start_capture(options: &StreamScreenOptions) -> Option<Capturer> {
|
|||
}
|
||||
}
|
||||
|
||||
// all options are more like a suggestion
|
||||
let mut capturer = Capturer::build(Options {
|
||||
fps: FRAME_PACING.div_duration_f32(Duration::from_secs(1)) as u32,
|
||||
show_cursor: options.pointer,
|
||||
output_type: scap::frame::FrameType::BGR0, // this is more like a suggestion
|
||||
output_type: scap::frame::FrameType::BGR0,
|
||||
..Default::default()
|
||||
})
|
||||
.expect("failed to create screen capture");
|
||||
|
|
Loading…
Reference in a new issue