remove spacers in image processing
All checks were successful
Rust / build (pull_request) Successful in 6m50s
All checks were successful
Rust / build (pull_request) Successful in 6m50s
This commit is contained in:
parent
e170835d13
commit
04c3da4f2c
|
@ -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)]
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in a new issue