stitch: a selection over several outputs as one image

This commit is contained in:
elwrcl committed 2026-10-04 20:45:38 +03:00
1 parent b47e5d8250
commit 3495b42afa
2 files changed
+117

No files matched your search

+1
View File
@@ -20,6 +20,7 @@ mod selection;
mod shadow;
mod sound;
mod stack;
mod stitch;
mod theme;
mod thumbnail;
mod toolbar;
+116
View File
@@ -0,0 +1,116 @@
//! A selection over several outputs as one image.
use image::{RgbaImage, imageops};
use crate::frame::Frame;
use crate::selection::Span;
/// The whole selection: each output's part from its frozen frame at its
/// place (resized when its output's scale is smaller), transparent where
/// no output is.
pub fn stitch(frames: &[Frame], span: &Span) -> RgbaImage {
let mut image = RgbaImage::new(span.size.0, span.size.1);
for part in &span.parts {
let mut crop = frames[part.output].to_rgba(part.rect);
if crop.dimensions() != part.size {
crop = imageops::resize(
&crop,
part.size.0,
part.size.1,
imageops::FilterType::Triangle,
);
}
imageops::replace(&mut image, &crop, part.at.0 as i64, part.at.1 as i64);
}
image
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use image::RgbaImage;
use super::*;
use crate::frame::{Frame, OutputGeom, PixelRect};
use crate::selection::{Part, Span};
/// A frame of one colour, given as R, G, B (stored as B, G, R, X).
fn frame(w: u32, h: u32, rgb: [u8; 3]) -> Frame {
Frame {
output: OutputGeom {
name: "X".into(),
x: 0,
y: 0,
width: w as i32,
height: h as i32,
},
pixels: Arc::new(RgbaImage::from_pixel(
w,
h,
image::Rgba([rgb[2], rgb[1], rgb[0], 0]),
)),
}
}
fn rect(x: u32, y: u32, width: u32, height: u32) -> PixelRect {
PixelRect {
x,
y,
width,
height,
}
}
#[test]
fn parts_land_in_place_and_gaps_stay_clear() {
let frames = [frame(10, 10, [255, 0, 0]), frame(10, 10, [0, 0, 255])];
let span = Span {
size: (8, 6),
parts: vec![
Part {
output: 0,
rect: rect(0, 0, 4, 3),
at: (4, 3),
size: (4, 3),
},
Part {
output: 1,
rect: rect(2, 2, 8, 3),
at: (0, 0),
size: (8, 3),
},
],
};
let img = stitch(&frames, &span);
assert_eq!(img.dimensions(), (8, 6));
assert_eq!(
img.get_pixel(0, 0).0,
[0, 0, 255, 255],
"the monitor's part, opaque"
);
assert_eq!(img.get_pixel(7, 2).0, [0, 0, 255, 255]);
assert_eq!(img.get_pixel(5, 4).0, [255, 0, 0, 255], "the laptop's part");
assert_eq!(img.get_pixel(1, 4).0[3], 0, "no output there: transparent");
}
#[test]
fn a_smaller_scale_part_is_resized_to_its_place() {
let frames = [frame(4, 4, [0, 255, 0])];
let span = Span {
size: (8, 8),
parts: vec![Part {
output: 0,
rect: rect(0, 0, 4, 4),
at: (0, 0),
size: (8, 8),
}],
};
let img = stitch(&frames, &span);
assert_eq!(
img.get_pixel(7, 7).0,
[0, 255, 0, 255],
"filled to the corner"
);
}
}