remove spacers in image processing
All checks were successful
Rust / build (pull_request) Successful in 6m50s

This commit is contained in:
Vinzenz Schroeter 2025-03-01 12:53:16 +01:00
parent e170835d13
commit 04c3da4f2c
4 changed files with 61 additions and 17 deletions

View file

@ -151,6 +151,9 @@ pub struct ImageProcessingOptions {
help = "Disable dithering. Brightness will be adjusted so that around half of the pixels are on." help = "Disable dithering. Brightness will be adjusted so that around half of the pixels are on."
)] )]
pub no_dither: bool, 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)] #[derive(clap::Parser, std::fmt::Debug, Clone)]

View file

@ -6,32 +6,57 @@ use image::{
imageops::{resize, FilterType}, imageops::{resize, FilterType},
DynamicImage, ImageBuffer, Luma, 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 { pub struct ImageProcessingPipeline {
options: ImageProcessingOptions, options: ImageProcessingOptions,
} }
const SPACER_HEIGHT: usize = TILE_SIZE / 2;
const PIXEL_HEIGHT_INCLUDING_SPACERS: usize = SPACER_HEIGHT * (TILE_HEIGHT - 1) + PIXEL_HEIGHT;
impl ImageProcessingPipeline { impl ImageProcessingPipeline {
pub fn new(options: ImageProcessingOptions) -> Self { pub fn new(options: ImageProcessingOptions) -> Self {
debug!("Creating image pipeline: {:?}", options);
Self { options } Self { options }
} }
pub fn process(&self, frame: DynamicImage) -> Bitmap { 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); 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 = frame.grayscale().to_luma8();
let frame = resize(
let target_height = if self.options.no_spacers {
PIXEL_HEIGHT
} else {
PIXEL_HEIGHT_INCLUDING_SPACERS
};
resize(
&frame, &frame,
PIXEL_WIDTH as u32, PIXEL_WIDTH as u32,
PIXEL_HEIGHT as u32, target_height as u32,
FilterType::Nearest, FilterType::Nearest,
); )
frame
} }
fn grayscale_processing( fn grayscale_processing(
@ -65,4 +90,23 @@ impl ImageProcessingPipeline {
ostromoukhov_dither(orig, u8::MAX / 2) 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
}
} }

View file

@ -53,4 +53,5 @@ fn pixels_image(
CompressionCode::default(), CompressionCode::default(),
)) ))
.expect("failed to send image command"); .expect("failed to send image command");
info!("sent image to display");
} }

View file

@ -1,5 +1,7 @@
use crate::cli::{ImageProcessingOptions, StreamScreenOptions}; use crate::{
use crate::image_processing::ImageProcessingPipeline; cli::{ImageProcessingOptions, StreamScreenOptions},
image_processing::ImageProcessingPipeline,
};
use image::{DynamicImage, ImageBuffer, Rgb, Rgba}; use image::{DynamicImage, ImageBuffer, Rgb, Rgba};
use log::{error, info, warn}; use log::{error, info, warn};
use scap::{ use scap::{
@ -7,15 +9,9 @@ use scap::{
frame::convert_bgra_to_rgb, frame::convert_bgra_to_rgb,
frame::Frame, frame::Frame,
}; };
use servicepoint::{ use servicepoint::{Command, CompressionCode, Connection, Origin, FRAME_PACING};
Command, CompressionCode, Connection, Origin, FRAME_PACING, PIXEL_HEIGHT, TILE_HEIGHT,
TILE_SIZE,
};
use std::time::Duration; 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( pub fn stream_window(
connection: &Connection, connection: &Connection,
options: StreamScreenOptions, options: StreamScreenOptions,