add send image command
This commit is contained in:
		
							parent
							
								
									19f24f9331
								
							
						
					
					
						commit
						b1c3ac8538
					
				
					 6 changed files with 100 additions and 36 deletions
				
			
		
							
								
								
									
										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…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Vinzenz Schroeter
						Vinzenz Schroeter