From 4f31b4d8fda2aceaf2bbacc42232936f2778a9eb Mon Sep 17 00:00:00 2001 From: Vinzenz Schroeter Date: Sun, 2 Mar 2025 13:46:06 +0100 Subject: [PATCH] keep aspect is optional --- README.md | 22 ++++++++++------- src/cli.rs | 3 +++ src/image_processing.rs | 54 ++++++++++++++++++++++++----------------- src/ledwand_dither.rs | 2 +- 4 files changed, 49 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 6cf3ca7..873fd0f 100644 --- a/README.md +++ b/README.md @@ -71,11 +71,13 @@ Stream the default source to the display. On Linux Wayland, this pops up a scree Usage: servicepoint-cli stream screen [OPTIONS] Options: - -p, --pointer Show mouse pointer in video feed - --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. + -p, --pointer Show mouse pointer in video feed + --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. + --no-spacers Do not remove the spacers from the image. + --no-aspect Do not keep aspect ratio when resizing. ``` #### Stdin @@ -127,10 +129,12 @@ Arguments: 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. + --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. + --no-spacers Do not remove the spacers from the image. + --no-aspect Do not keep aspect ratio when resizing. ``` ## Contributing diff --git a/src/cli.rs b/src/cli.rs index 0f8dc8a..a919664 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -154,6 +154,9 @@ pub struct ImageProcessingOptions { #[arg(long, help = "Do not remove the spacers from the image.")] pub no_spacers: bool, + + #[arg(long, help = "Do not keep aspect ratio when resizing.")] + pub no_aspect: bool, } #[derive(clap::Parser, std::fmt::Debug, Clone)] diff --git a/src/image_processing.rs b/src/image_processing.rs index bcc3104..4e6baa8 100644 --- a/src/image_processing.rs +++ b/src/image_processing.rs @@ -12,7 +12,7 @@ use std::{default::Default, time::Instant}; pub struct ImageProcessingPipeline { options: ImageProcessingOptions, resizer: Resizer, - render_size: (usize, usize), + render_size: (u32, u32), } const SPACER_HEIGHT: usize = TILE_SIZE / 2; @@ -21,16 +21,17 @@ impl ImageProcessingPipeline { pub fn new(options: ImageProcessingOptions) -> Self { debug!("Creating image pipeline: {:?}", options); - let spacers_height = if options.no_spacers { - 0 - } else { - SPACER_HEIGHT * (TILE_HEIGHT - 1) - }; + let height = PIXEL_HEIGHT + + if options.no_spacers { + 0 + } else { + SPACER_HEIGHT * (TILE_HEIGHT - 1) + }; Self { options, resizer: Resizer::new(), - render_size: (PIXEL_WIDTH, PIXEL_HEIGHT + spacers_height), + render_size: (PIXEL_WIDTH as u32, height as u32), } } @@ -52,7 +53,11 @@ impl ImageProcessingPipeline { fn resize_grayscale(&mut self, frame: DynamicImage) -> GrayImage { let start_time = Instant::now(); - let (scaled_width, scaled_height) = self.fit_size((frame.width(), frame.height())); + let (scaled_width, scaled_height) = if self.options.no_aspect { + self.render_size + } else { + self.calc_scaled_size_keep_aspect((frame.width(), frame.height())) + }; let mut dst_image = DynamicImage::new(scaled_width, scaled_height, frame.color()); self.resizer @@ -106,20 +111,13 @@ impl ImageProcessingPipeline { fn remove_spacers(source: Bitmap) -> Bitmap { let start_time = Instant::now(); - let full_tile_rows_with_spacers = source.height() / (TILE_SIZE + SPACER_HEIGHT); - let remaining_pixel_rows = source.height() % (TILE_SIZE + SPACER_HEIGHT); - let total_spacer_height = full_tile_rows_with_spacers * SPACER_HEIGHT - + remaining_pixel_rows.saturating_sub(TILE_SIZE); - let height_without_spacers = source.height() - total_spacer_height; - trace!( - "spacers take up {total_spacer_height}, resulting in height {height_without_spacers}" - ); - - let mut result = Bitmap::new(source.width(), height_without_spacers); + let width = source.width(); + let result_height = Self::calc_height_without_spacers(source.height()); + let mut result = Bitmap::new(width, result_height); let mut source_y = 0; - for result_y in 0..result.height() { - for x in 0..result.width() { + for result_y in 0..result_height { + for x in 0..width { result.set(x, result_y, source.get(x, source_y)); } @@ -133,10 +131,22 @@ impl ImageProcessingPipeline { result } - fn fit_size(&self, source: (u32, u32)) -> (u32, u32) { + fn calc_height_without_spacers(height: usize) -> usize { + let full_tile_rows_with_spacers = height / (TILE_SIZE + SPACER_HEIGHT); + let remaining_pixel_rows = height % (TILE_SIZE + SPACER_HEIGHT); + let total_spacer_height = full_tile_rows_with_spacers * SPACER_HEIGHT + + remaining_pixel_rows.saturating_sub(TILE_SIZE); + let height_without_spacers = height - total_spacer_height; + trace!( + "spacers take up {total_spacer_height}, resulting in final height {height_without_spacers}" + ); + height_without_spacers + } + + fn calc_scaled_size_keep_aspect(&self, source: (u32, u32)) -> (u32, u32) { let (source_width, source_height) = source; let (target_width, target_height) = self.render_size; - debug_assert_eq!(target_width % TILE_SIZE, 0); + debug_assert_eq!(target_width % TILE_SIZE as u32, 0); let width_scale = target_width as f32 / source_width as f32; let height_scale = target_height as f32 / source_height as f32; diff --git a/src/ledwand_dither.rs b/src/ledwand_dither.rs index 6dbf370..d0e4b43 100644 --- a/src/ledwand_dither.rs +++ b/src/ledwand_dither.rs @@ -215,7 +215,7 @@ fn ostromoukhov_dither_pixel( ) { let (destination_value, error) = gray_to_bit(source[position], bias); destination.set(position, destination_value); - + let mut diffuse = |to: usize, mat: i16| { let diffuse_value = source[to] as i16 + mat; source[to] = diffuse_value.clamp(u8::MIN.into(), u8::MAX.into()) as u8;