diff --git a/src/cli.rs b/src/cli.rs index 6830f98..db64ca7 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -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, diff --git a/src/image_processing.rs b/src/image_processing.rs new file mode 100644 index 0000000..f36bdc9 --- /dev/null +++ b/src/image_processing.rs @@ -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, Vec> { + 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, Vec>, + ) -> ImageBuffer, Vec> { + 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, Vec>) -> 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) + } + } +} diff --git a/src/main.rs b/src/main.rs index cd7937c..2a86e33 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,6 +11,7 @@ use servicepoint::{Brightness, Connection}; mod brightness; mod cli; +mod image_processing; mod ledwand_dither; mod pixels; mod stream_stdin; diff --git a/src/stream_window.rs b/src/stream_window.rs index bfa9806..da8d866 100644 --- a/src/stream_window.rs +++ b/src/stream_window.rs @@ -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, Vec> { - 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 { if !scap::is_supported() { error!("platform not supported by scap");