remove spacers in image processing

This commit is contained in:
Vinzenz Schroeter 2025-03-01 12:53:16 +01:00
parent b1c3ac8538
commit 0521e103ec
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."
)]
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)]

View file

@ -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
}
}

View file

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

View file

@ -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,