remove spacers in image processing
This commit is contained in:
		
							parent
							
								
									b1c3ac8538
								
							
						
					
					
						commit
						0521e103ec
					
				
					 4 changed files with 61 additions and 17 deletions
				
			
		|  | @ -151,6 +151,9 @@ pub struct ImageProcessingOptions { | |||
|         help = "Disable dithering. Brightness will be adjusted so that around half of the pixels are on." | ||||
|     )] | ||||
|     pub no_dither: bool, | ||||
| 
 | ||||
|     #[arg(long, help = "Do not remove the spacers from the image.")] | ||||
|     pub no_spacers: bool, | ||||
| } | ||||
| 
 | ||||
| #[derive(clap::Parser, std::fmt::Debug, Clone)] | ||||
|  |  | |||
|  | @ -6,32 +6,57 @@ use image::{ | |||
|     imageops::{resize, FilterType}, | ||||
|     DynamicImage, ImageBuffer, Luma, | ||||
| }; | ||||
| use servicepoint::{Bitmap, PIXEL_HEIGHT, PIXEL_WIDTH}; | ||||
| use log::{debug, trace}; | ||||
| use servicepoint::{Bitmap, Grid, PIXEL_HEIGHT, PIXEL_WIDTH, TILE_HEIGHT, TILE_SIZE}; | ||||
| use std::time::Instant; | ||||
| 
 | ||||
| #[derive(Debug)] | ||||
| pub struct ImageProcessingPipeline { | ||||
|     options: ImageProcessingOptions, | ||||
| } | ||||
| 
 | ||||
| const SPACER_HEIGHT: usize = TILE_SIZE / 2; | ||||
| const PIXEL_HEIGHT_INCLUDING_SPACERS: usize = SPACER_HEIGHT * (TILE_HEIGHT - 1) + PIXEL_HEIGHT; | ||||
| 
 | ||||
| impl ImageProcessingPipeline { | ||||
|     pub fn new(options: ImageProcessingOptions) -> Self { | ||||
|         debug!("Creating image pipeline: {:?}", options); | ||||
|         Self { options } | ||||
|     } | ||||
| 
 | ||||
|     pub fn process(&self, frame: DynamicImage) -> Bitmap { | ||||
|         let frame = Self::resize_grayscale(&frame); | ||||
|         let start_time = Instant::now(); | ||||
| 
 | ||||
|         let frame = self.resize_grayscale(frame); | ||||
|         let frame = self.grayscale_processing(frame); | ||||
|         self.grayscale_to_bitmap(frame) | ||||
|         let mut result = self.grayscale_to_bitmap(frame); | ||||
| 
 | ||||
|         if !self.options.no_spacers { | ||||
|             result = Self::remove_spacers(result); | ||||
|         } | ||||
| 
 | ||||
|         trace!("image processing took {:?}", start_time.elapsed()); | ||||
|         result | ||||
|     } | ||||
| 
 | ||||
|     fn resize_grayscale(frame: &DynamicImage) -> ImageBuffer<Luma<u8>, Vec<u8>> { | ||||
|     fn resize_grayscale(&self, frame: DynamicImage) -> ImageBuffer<Luma<u8>, Vec<u8>> { | ||||
|         // TODO: keep aspect ratio
 | ||||
|         // TODO: make it work for non-maximum sizes
 | ||||
| 
 | ||||
|         let frame = frame.grayscale().to_luma8(); | ||||
|         let frame = resize( | ||||
| 
 | ||||
|         let target_height = if self.options.no_spacers { | ||||
|             PIXEL_HEIGHT | ||||
|         } else { | ||||
|             PIXEL_HEIGHT_INCLUDING_SPACERS | ||||
|         }; | ||||
| 
 | ||||
|         resize( | ||||
|             &frame, | ||||
|             PIXEL_WIDTH as u32, | ||||
|             PIXEL_HEIGHT as u32, | ||||
|             target_height as u32, | ||||
|             FilterType::Nearest, | ||||
|         ); | ||||
|         frame | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     fn grayscale_processing( | ||||
|  | @ -65,4 +90,23 @@ impl ImageProcessingPipeline { | |||
|             ostromoukhov_dither(orig, u8::MAX / 2) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fn remove_spacers(bitmap: Bitmap) -> Bitmap { | ||||
|         let mut result = Bitmap::max_sized(); | ||||
| 
 | ||||
|         let mut source_y = 0; | ||||
|         for result_y in 0..result.height() { | ||||
|             if result_y != 0 && result_y % TILE_SIZE == 0 { | ||||
|                 source_y += 4; | ||||
|             } | ||||
| 
 | ||||
|             for x in 0..result.width() { | ||||
|                 result.set(x, result_y, bitmap.get(x, source_y)); | ||||
|             } | ||||
| 
 | ||||
|             source_y += 1; | ||||
|         } | ||||
| 
 | ||||
|         result | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -53,4 +53,5 @@ fn pixels_image( | |||
|             CompressionCode::default(), | ||||
|         )) | ||||
|         .expect("failed to send image command"); | ||||
|     info!("sent image to display"); | ||||
| } | ||||
|  |  | |||
|  | @ -1,5 +1,7 @@ | |||
| use crate::cli::{ImageProcessingOptions, StreamScreenOptions}; | ||||
| use crate::image_processing::ImageProcessingPipeline; | ||||
| use crate::{ | ||||
|     cli::{ImageProcessingOptions, StreamScreenOptions}, | ||||
|     image_processing::ImageProcessingPipeline, | ||||
| }; | ||||
| use image::{DynamicImage, ImageBuffer, Rgb, Rgba}; | ||||
| use log::{error, info, warn}; | ||||
| use scap::{ | ||||
|  | @ -7,15 +9,9 @@ use scap::{ | |||
|     frame::convert_bgra_to_rgb, | ||||
|     frame::Frame, | ||||
| }; | ||||
| use servicepoint::{ | ||||
|     Command, CompressionCode, Connection, Origin, FRAME_PACING, PIXEL_HEIGHT, TILE_HEIGHT, | ||||
|     TILE_SIZE, | ||||
| }; | ||||
| use servicepoint::{Command, CompressionCode, Connection, Origin, FRAME_PACING}; | ||||
| use std::time::Duration; | ||||
| 
 | ||||
| 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, | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Vinzenz Schroeter
						Vinzenz Schroeter