a
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,3 +2,4 @@
|
||||
**/*.rs.bk
|
||||
/testserver
|
||||
.*.swp
|
||||
video/*
|
||||
|
||||
4015
Cargo.lock
generated
4015
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
12
Cargo.toml
12
Cargo.toml
@@ -16,10 +16,22 @@ categories = [
|
||||
[dependencies]
|
||||
bufstream = "0.1"
|
||||
clap = { version = "4.4", features = [ "derive" ] }
|
||||
eframe = "0.26"
|
||||
glob = "0.3"
|
||||
image = "0.23"
|
||||
num_cpus = "1.13.1"
|
||||
regex = "1.5"
|
||||
rayon = "1.5.1"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
|
||||
[[bin]]
|
||||
name = "pixelpwnr"
|
||||
path = "src/main.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "pixelpwnr-gui"
|
||||
path = "src/gui.rs"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
|
||||
@@ -37,6 +37,10 @@ pub struct Arguments {
|
||||
#[arg(short, value_name = "PIXELS", default_value_t = 0)]
|
||||
y: u16,
|
||||
|
||||
/// Tile images across the screen, ignoring offsets
|
||||
#[arg(short, long)]
|
||||
tile: bool,
|
||||
|
||||
/// Number of concurrent threads [default: number of CPUs]
|
||||
#[arg(short, long, aliases = ["thread", "threads"])]
|
||||
count: Option<usize>,
|
||||
@@ -99,6 +103,11 @@ impl ArgHandler {
|
||||
(self.data.x, self.data.y)
|
||||
}
|
||||
|
||||
/// Whether to tile images across the screen.
|
||||
pub fn tile(&self) -> bool {
|
||||
self.data.tile
|
||||
}
|
||||
|
||||
/// Get the FPS.
|
||||
pub fn fps(&self) -> u32 {
|
||||
self.data.fps
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
use image::codecs::gif::GifDecoder;
|
||||
use rayon::prelude::*;
|
||||
use std::fs::File;
|
||||
use std::path::Path;
|
||||
use glob::glob;
|
||||
use std::fs::{self, File};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::thread::sleep;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::pix::canvas::Canvas;
|
||||
use image::imageops::FilterType;
|
||||
use image::{AnimationDecoder, DynamicImage};
|
||||
use image::{AnimationDecoder, DynamicImage, RgbaImage};
|
||||
|
||||
/// A manager that manages all images to print.
|
||||
pub struct ImageManager {
|
||||
@@ -31,7 +32,7 @@ impl ImageManager {
|
||||
/// Instantiate the image manager, and load the images from the given paths.
|
||||
pub fn load(paths: &[&str], size: (u16, u16)) -> ImageManager {
|
||||
// Show a status message
|
||||
println!("Load and process {} image(s)...", paths.len());
|
||||
println!("Load and process {} path(s)...", paths.len());
|
||||
|
||||
// Load the images from the paths
|
||||
let image_manager = ImageManager::from(
|
||||
@@ -44,7 +45,36 @@ impl ImageManager {
|
||||
// TODO: process the image slices
|
||||
|
||||
// We succeeded
|
||||
println!("All images have been loaded successfully");
|
||||
println!(
|
||||
"Loaded {} frame(s) from {} path(s).",
|
||||
image_manager.images.len(),
|
||||
paths.len()
|
||||
);
|
||||
|
||||
image_manager
|
||||
}
|
||||
|
||||
/// Instantiate the image manager, and load and tile the images from the given paths.
|
||||
pub fn load_tiled(
|
||||
paths: &[&str],
|
||||
tile_size: (u16, u16),
|
||||
canvas_size: (u16, u16),
|
||||
) -> ImageManager {
|
||||
// Show a status message
|
||||
println!("Load and process {} path(s)...", paths.len());
|
||||
|
||||
let image_manager = ImageManager::from(
|
||||
paths
|
||||
.par_iter()
|
||||
.flat_map(|path| load_image_tiled(path, tile_size, canvas_size))
|
||||
.collect(),
|
||||
);
|
||||
|
||||
println!(
|
||||
"Loaded {} frame(s) from {} path(s).",
|
||||
image_manager.images.len(),
|
||||
paths.len()
|
||||
);
|
||||
|
||||
image_manager
|
||||
}
|
||||
@@ -96,22 +126,99 @@ impl ImageManager {
|
||||
|
||||
/// Load the image at the given path, and size it correctly
|
||||
fn load_image(path: &str, size: (u16, u16)) -> Vec<(DynamicImage, Option<Duration>)> {
|
||||
// Create a path instance
|
||||
load_image_frames(path)
|
||||
.into_iter()
|
||||
.map(|(image, frame_delay)| {
|
||||
(
|
||||
image.resize_exact(size.0 as u32, size.1 as u32, FilterType::Gaussian),
|
||||
frame_delay,
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn load_image_tiled(
|
||||
path: &str,
|
||||
tile_size: (u16, u16),
|
||||
canvas_size: (u16, u16),
|
||||
) -> Vec<(DynamicImage, Option<Duration>)> {
|
||||
load_image_frames(path)
|
||||
.into_iter()
|
||||
.map(|(image, frame_delay)| {
|
||||
(
|
||||
tile_image(
|
||||
image,
|
||||
(tile_size.0 as u32, tile_size.1 as u32),
|
||||
(canvas_size.0 as u32, canvas_size.1 as u32),
|
||||
),
|
||||
frame_delay,
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn load_image_frames(path: &str) -> Vec<(DynamicImage, Option<Duration>)> {
|
||||
if has_glob_pattern(path) {
|
||||
let mut entries: Vec<PathBuf> = glob(path)
|
||||
.expect("failed to read frames glob")
|
||||
.filter_map(Result::ok)
|
||||
.filter(|entry| entry.is_file())
|
||||
.collect();
|
||||
entries.sort_by(|a, b| a.file_name().cmp(&b.file_name()));
|
||||
|
||||
let mut frames = Vec::new();
|
||||
for entry in entries {
|
||||
frames.extend(load_file_frames(&entry));
|
||||
}
|
||||
|
||||
if frames.is_empty() {
|
||||
panic!("The given frames glob matched no files");
|
||||
}
|
||||
|
||||
return frames;
|
||||
}
|
||||
|
||||
let path = Path::new(&path);
|
||||
|
||||
// Check whether the path exists
|
||||
if path.is_dir() {
|
||||
let mut entries: Vec<PathBuf> = fs::read_dir(path)
|
||||
.expect("failed to read frames directory")
|
||||
.filter_map(|entry| entry.ok())
|
||||
.map(|entry| entry.path())
|
||||
.filter(|entry| entry.is_file())
|
||||
.collect();
|
||||
entries.sort_by(|a, b| a.file_name().cmp(&b.file_name()));
|
||||
|
||||
let mut frames = Vec::new();
|
||||
for entry in entries {
|
||||
frames.extend(load_file_frames(&entry));
|
||||
}
|
||||
|
||||
if frames.is_empty() {
|
||||
panic!("The given frames directory is empty");
|
||||
}
|
||||
|
||||
return frames;
|
||||
}
|
||||
|
||||
if !path.is_file() {
|
||||
panic!("The given path does not exist or is not a file");
|
||||
}
|
||||
|
||||
load_file_frames(path)
|
||||
}
|
||||
|
||||
fn has_glob_pattern(path: &str) -> bool {
|
||||
path.contains('*') || path.contains('?') || path.contains('[')
|
||||
}
|
||||
|
||||
fn load_file_frames(path: &Path) -> Vec<(DynamicImage, Option<Duration>)> {
|
||||
let extension = path
|
||||
.extension()
|
||||
.and_then(|e| e.to_str())
|
||||
.map(|e| e.to_lowercase());
|
||||
|
||||
// Load image(s)
|
||||
let images = match extension.as_deref() {
|
||||
// Load all GIF frames
|
||||
match extension.as_deref() {
|
||||
Some("gif") => GifDecoder::new(File::open(path).unwrap())
|
||||
.expect("failed to decode GIF file")
|
||||
.into_frames()
|
||||
@@ -126,19 +233,27 @@ fn load_image(path: &str, size: (u16, u16)) -> Vec<(DynamicImage, Option<Duratio
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
|
||||
// Load single image
|
||||
_ => vec![(image::open(path).unwrap(), None)],
|
||||
};
|
||||
|
||||
// Resize images to fit the screen
|
||||
images
|
||||
.into_iter()
|
||||
.map(|(image, frame_delay)| {
|
||||
(
|
||||
image.resize_exact(size.0 as u32, size.1 as u32, FilterType::Gaussian),
|
||||
frame_delay,
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
fn tile_image(image: DynamicImage, tile_size: (u32, u32), canvas_size: (u32, u32)) -> DynamicImage {
|
||||
let tile = image
|
||||
.resize_exact(tile_size.0, tile_size.1, FilterType::Gaussian)
|
||||
.to_rgba8();
|
||||
let mut tiled = RgbaImage::new(canvas_size.0, canvas_size.1);
|
||||
let tile_w = tile.width();
|
||||
let tile_h = tile.height();
|
||||
|
||||
let mut y = 0;
|
||||
while y < canvas_size.1 {
|
||||
let mut x = 0;
|
||||
while x < canvas_size.0 {
|
||||
image::imageops::overlay(&mut tiled, &tile, x, y);
|
||||
x += tile_w;
|
||||
}
|
||||
y += tile_h;
|
||||
}
|
||||
|
||||
DynamicImage::ImageRgba8(tiled)
|
||||
}
|
||||
|
||||
16
src/main.rs
16
src/main.rs
@@ -30,21 +30,29 @@ fn start(arg_handler: &ArgHandler) {
|
||||
let screen_size =
|
||||
gather_host_facts(arg_handler).expect("Failed to gather facts about pixelflut server");
|
||||
|
||||
// Determine the size to use
|
||||
let size = arg_handler.size(Some(screen_size));
|
||||
let tile_size = arg_handler.size(Some(screen_size));
|
||||
let (size, offset) = if arg_handler.tile() {
|
||||
(screen_size, (0, 0))
|
||||
} else {
|
||||
(tile_size, arg_handler.offset())
|
||||
};
|
||||
|
||||
// Create a new pixelflut canvas
|
||||
let mut canvas = Canvas::new(
|
||||
arg_handler.host(),
|
||||
arg_handler.count(),
|
||||
size,
|
||||
arg_handler.offset(),
|
||||
offset,
|
||||
arg_handler.binary(),
|
||||
arg_handler.flush(),
|
||||
);
|
||||
|
||||
// Load the image manager
|
||||
let mut image_manager = ImageManager::load(&arg_handler.image_paths(), size);
|
||||
let mut image_manager = if arg_handler.tile() {
|
||||
ImageManager::load_tiled(&arg_handler.image_paths(), tile_size, size)
|
||||
} else {
|
||||
ImageManager::load(&arg_handler.image_paths(), size)
|
||||
};
|
||||
|
||||
// Start the work in the image manager, to walk through the frames
|
||||
image_manager.work(&mut canvas, arg_handler.fps());
|
||||
|
||||
@@ -50,6 +50,31 @@ impl Canvas {
|
||||
|
||||
/// Spawn the painters for this canvas
|
||||
fn spawn_painters(&mut self, binary: bool, flush: bool) {
|
||||
let grid = (self.painter_count as f64).sqrt() as usize;
|
||||
if grid * grid == self.painter_count {
|
||||
let tile_w = self.size.0 / (grid as u16);
|
||||
let tile_h = self.size.1 / (grid as u16);
|
||||
|
||||
for row in 0..grid {
|
||||
for col in 0..grid {
|
||||
let x = (col as u16) * tile_w;
|
||||
let y = (row as u16) * tile_h;
|
||||
let w = if col == grid - 1 {
|
||||
self.size.0 - x
|
||||
} else {
|
||||
tile_w
|
||||
};
|
||||
let h = if row == grid - 1 {
|
||||
self.size.1 - y
|
||||
} else {
|
||||
tile_h
|
||||
};
|
||||
|
||||
let painter_area = Rect::from(x, y, w, h);
|
||||
self.spawn_painter(painter_area, binary, flush);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Spawn some painters
|
||||
for i in 0..self.painter_count {
|
||||
// Determine the slice width
|
||||
@@ -62,6 +87,7 @@ impl Canvas {
|
||||
self.spawn_painter(painter_area, binary, flush);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Spawn a single painter in a thread.
|
||||
fn spawn_painter(&mut self, area: Rect, binary: bool, flush: bool) {
|
||||
|
||||
Reference in New Issue
Block a user