better dithering, keep aspect ratio, send image #2
					 4 changed files with 87 additions and 44 deletions
				
			
		|  | @ -122,6 +122,12 @@ pub struct StreamScreenOptions { | |||
|     )] | ||||
|     pub pointer: bool, | ||||
| 
 | ||||
|     #[transparent] | ||||
|     pub image_processing: ImageProcessingOptions, | ||||
| } | ||||
| 
 | ||||
| #[derive(clap::Parser, std::fmt::Debug, Clone)] | ||||
| pub struct ImageProcessingOptions { | ||||
|     #[arg(long, help = "Disable histogram correction")] | ||||
|     pub no_hist: bool, | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										66
									
								
								src/image_processing.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								src/image_processing.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,66 @@ | |||
| use crate::cli::ImageProcessingOptions; | ||||
| use crate::ledwand_dither::{ | ||||
|     blur, histogram_correction, median_brightness, ostromoukhov_dither, sharpen, | ||||
| }; | ||||
| use image::imageops::{resize, FilterType}; | ||||
| use image::{imageops, DynamicImage, ImageBuffer, Luma}; | ||||
| use servicepoint::{Bitmap, PIXEL_HEIGHT, PIXEL_WIDTH}; | ||||
| 
 | ||||
| pub struct ImageProcessingPipeline { | ||||
|     options: ImageProcessingOptions, | ||||
| } | ||||
| 
 | ||||
| impl ImageProcessingPipeline { | ||||
|     pub fn new(options: ImageProcessingOptions) -> Self { | ||||
|         Self { options } | ||||
|     } | ||||
| 
 | ||||
|     pub fn process(&self, frame: DynamicImage) -> Bitmap { | ||||
|         let frame = Self::resize_grayscale(&frame); | ||||
|         let frame = self.grayscale_processing(frame); | ||||
|         self.grayscale_to_bitmap(frame) | ||||
|     } | ||||
| 
 | ||||
|     fn resize_grayscale(frame: &DynamicImage) -> ImageBuffer<Luma<u8>, Vec<u8>> { | ||||
|         let frame = imageops::grayscale(&frame); | ||||
|         let frame = resize( | ||||
|             &frame, | ||||
|             PIXEL_WIDTH as u32, | ||||
|             PIXEL_HEIGHT as u32, | ||||
|             FilterType::Nearest, | ||||
|         ); | ||||
|         frame | ||||
|     } | ||||
| 
 | ||||
|     fn grayscale_processing( | ||||
|         &self, | ||||
|         mut frame: ImageBuffer<Luma<u8>, Vec<u8>>, | ||||
|     ) -> ImageBuffer<Luma<u8>, Vec<u8>> { | ||||
|         if !self.options.no_hist { | ||||
|             histogram_correction(&mut frame); | ||||
|         } | ||||
| 
 | ||||
|         let mut orig = frame.clone(); | ||||
| 
 | ||||
|         if !self.options.no_blur { | ||||
|             blur(&orig, &mut frame); | ||||
|             std::mem::swap(&mut frame, &mut orig); | ||||
|         } | ||||
| 
 | ||||
|         if !self.options.no_sharp { | ||||
|             sharpen(&orig, &mut frame); | ||||
|             std::mem::swap(&mut frame, &mut orig); | ||||
|         } | ||||
|         orig | ||||
|     } | ||||
| 
 | ||||
|     fn grayscale_to_bitmap(&self, mut 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(); | ||||
|             Bitmap::from_bitvec(orig.width() as usize, bits) | ||||
|         } else { | ||||
|             ostromoukhov_dither(orig, u8::MAX / 2) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -11,6 +11,7 @@ use servicepoint::{Brightness, Connection}; | |||
| 
 | ||||
| mod brightness; | ||||
| mod cli; | ||||
| mod image_processing; | ||||
| mod ledwand_dither; | ||||
| mod pixels; | ||||
| mod stream_stdin; | ||||
|  |  | |||
|  | @ -1,5 +1,8 @@ | |||
| use crate::cli::StreamScreenOptions; | ||||
| use crate::ledwand_dither::*; | ||||
| use crate::{ | ||||
|     cli::{ImageProcessingOptions, StreamScreenOptions}, | ||||
|     image_processing::ImageProcessingPipeline, | ||||
|     ledwand_dither::*, | ||||
| }; | ||||
| use image::{ | ||||
|     imageops::{resize, FilterType}, | ||||
|     DynamicImage, ImageBuffer, Luma, Rgb, Rgba, | ||||
|  | @ -10,45 +13,26 @@ use scap::{ | |||
|     frame::convert_bgra_to_rgb, | ||||
|     frame::Frame, | ||||
| }; | ||||
| use servicepoint::{Bitmap, Command, CompressionCode, Connection, Origin, FRAME_PACING, PIXEL_HEIGHT, PIXEL_WIDTH}; | ||||
| use servicepoint::{ | ||||
|     Bitmap, Command, CompressionCode, Connection, Origin, FRAME_PACING, PIXEL_HEIGHT, PIXEL_WIDTH, | ||||
|     TILE_HEIGHT, TILE_SIZE, | ||||
| }; | ||||
| use std::time::Duration; | ||||
| 
 | ||||
| pub fn stream_window(connection: &Connection, options: StreamScreenOptions) { | ||||
|     info!("Starting capture with options: {:?}", options); | ||||
| 
 | ||||
|     let capturer = match start_capture(&options) { | ||||
|         Some(value) => value, | ||||
|         None => return, | ||||
|     }; | ||||
| 
 | ||||
|     let pipeline = ImageProcessingPipeline::new(options.image_processing); | ||||
| 
 | ||||
|     info!("now starting to stream images"); | ||||
|     loop { | ||||
|         let mut frame = get_next_frame(&capturer); | ||||
| 
 | ||||
|         if !options.no_hist { | ||||
|             histogram_correction(&mut frame); | ||||
|         } | ||||
| 
 | ||||
|         let mut orig = frame.clone(); | ||||
| 
 | ||||
|         if !options.no_blur { | ||||
|             blur(&orig, &mut frame); | ||||
|             std::mem::swap(&mut frame, &mut orig); | ||||
|         } | ||||
| 
 | ||||
|         if !options.no_sharp { | ||||
|             sharpen(&orig, &mut frame); | ||||
|             std::mem::swap(&mut frame, &mut orig); | ||||
|         } | ||||
| 
 | ||||
|         let bitmap = if options.no_dither { | ||||
|             let cutoff = median_brightness(&orig); | ||||
|             let bits = orig.iter().map(move |x| x > &cutoff).collect(); | ||||
|             Bitmap::from_bitvec(orig.width() as usize, bits) | ||||
|         } else { | ||||
|             ostromoukhov_dither(orig, u8::MAX / 2) | ||||
|         }; | ||||
| 
 | ||||
|         let frame = capturer.get_next_frame().expect("failed to capture frame"); | ||||
|         let frame = frame_to_image(frame); | ||||
|         let bitmap = pipeline.process(frame); | ||||
|         connection | ||||
|             .send(Command::BitmapLinearWin( | ||||
|                 Origin::ZERO, | ||||
|  | @ -59,20 +43,6 @@ pub fn stream_window(connection: &Connection, options: StreamScreenOptions) { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| /// returns next frame from the capturer, resized and grayscale
 | ||||
| fn get_next_frame(capturer: &Capturer) -> ImageBuffer<Luma<u8>, Vec<u8>> { | ||||
|     let frame = capturer.get_next_frame().expect("failed to capture frame"); | ||||
|     let frame = frame_to_image(frame); | ||||
|     let frame = frame.grayscale().to_luma8(); | ||||
| 
 | ||||
|     resize( | ||||
|         &frame, | ||||
|         PIXEL_WIDTH as u32, | ||||
|         PIXEL_HEIGHT as u32, | ||||
|         FilterType::Nearest, | ||||
|     ) | ||||
| } | ||||
| 
 | ||||
| fn start_capture(options: &StreamScreenOptions) -> Option<Capturer> { | ||||
|     if !scap::is_supported() { | ||||
|         error!("platform not supported by scap"); | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue