diff --git a/src/cli.rs b/src/cli.rs index 664916d..0f8dc8a 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -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)] diff --git a/src/image_processing.rs b/src/image_processing.rs index 2b3b1b7..4c06e68 100644 --- a/src/image_processing.rs +++ b/src/image_processing.rs @@ -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, Vec> { + fn resize_grayscale(&self, frame: DynamicImage) -> ImageBuffer, Vec> { + // 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 + } } diff --git a/src/pixels.rs b/src/pixels.rs index ea0242e..a4fece0 100644 --- a/src/pixels.rs +++ b/src/pixels.rs @@ -53,4 +53,5 @@ fn pixels_image( CompressionCode::default(), )) .expect("failed to send image command"); + info!("sent image to display"); } diff --git a/src/stream_window.rs b/src/stream_window.rs index c89368c..e66cf26 100644 --- a/src/stream_window.rs +++ b/src/stream_window.rs @@ -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,