initial commit
This commit is contained in:
commit
b647d95713
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/target
|
18
Cargo.toml
Normal file
18
Cargo.toml
Normal file
|
@ -0,0 +1,18 @@
|
|||
[package]
|
||||
name = "grisly"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.93"
|
||||
bytemuck = "1.20.0"
|
||||
clap = "4.5.21"
|
||||
env_logger = "0.11.5"
|
||||
image = "0.25.5"
|
||||
libc = "0.2.165"
|
||||
log = "0.4.22"
|
||||
rand = "0.8.5"
|
||||
thiserror = "2.0.3"
|
||||
wayland-client = "0.31.7"
|
||||
wayland-protocols = { version = "0.32.5", features = ["client", "unstable"] }
|
||||
wayland-protocols-wlr = {version = "0.3.5", features = ["client"]}
|
940
src/main.rs
Normal file
940
src/main.rs
Normal file
|
@ -0,0 +1,940 @@
|
|||
use std::{
|
||||
collections::HashMap,
|
||||
ffi::CString,
|
||||
io::BufWriter,
|
||||
os::fd::{AsFd, AsRawFd, FromRawFd},
|
||||
};
|
||||
|
||||
use image::{math::Rect, GenericImage, GenericImageView};
|
||||
use rand::Rng;
|
||||
use wayland_client::{
|
||||
backend::ObjectId,
|
||||
globals::{registry_queue_init, GlobalListContents},
|
||||
protocol::{
|
||||
wl_buffer::WlBuffer,
|
||||
wl_output::{self, Mode, Transform, WlOutput},
|
||||
wl_registry::{self, WlRegistry},
|
||||
wl_shm::{self, WlShm},
|
||||
wl_shm_pool::WlShmPool,
|
||||
},
|
||||
Connection, Dispatch, Proxy, QueueHandle, WEnum,
|
||||
};
|
||||
|
||||
use wayland_protocols::xdg::xdg_output::zv1::client::{
|
||||
zxdg_output_manager_v1::ZxdgOutputManagerV1, zxdg_output_v1::ZxdgOutputV1,
|
||||
};
|
||||
use wayland_protocols_wlr::screencopy::v1::client::{
|
||||
zwlr_screencopy_frame_v1::{self, ZwlrScreencopyFrameV1},
|
||||
zwlr_screencopy_manager_v1::ZwlrScreencopyManagerV1,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
struct BufferInfo {
|
||||
format: wl_shm::Format,
|
||||
width: u32,
|
||||
height: u32,
|
||||
stride: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
enum FrameState {
|
||||
Pre,
|
||||
BufferDone,
|
||||
Copied,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum FrameData {
|
||||
Pre(Vec<BufferInfo>),
|
||||
Buffer(BufferInfo),
|
||||
Copying {
|
||||
buffer: WlBuffer,
|
||||
info: BufferInfo,
|
||||
offset: usize,
|
||||
},
|
||||
Copied {
|
||||
buffer: WlBuffer,
|
||||
info: BufferInfo,
|
||||
offset: usize,
|
||||
},
|
||||
Done,
|
||||
}
|
||||
|
||||
impl FrameData {
|
||||
fn select_buffer(&mut self) {
|
||||
if let Self::Pre(infos) = self {
|
||||
*self = Self::Buffer(infos.pop().unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Frame {
|
||||
frame: ZwlrScreencopyFrameV1,
|
||||
buffer_infos: Vec<BufferInfo>,
|
||||
inverted_y: bool,
|
||||
data: FrameData,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Output {
|
||||
output: WlOutput,
|
||||
xdg_output: ZxdgOutputV1,
|
||||
logical_size: Option<(i32, i32)>,
|
||||
physical_size: Option<(i32, i32)>,
|
||||
logical_position: Option<(i32, i32)>,
|
||||
physical_position: Option<(i32, i32)>,
|
||||
transform: Option<Transform>,
|
||||
scale: Option<i32>,
|
||||
frame: Frame,
|
||||
}
|
||||
|
||||
impl Output {
|
||||
fn bind<
|
||||
U: Send + Sync + 'static,
|
||||
D: Dispatch<WlOutput, U>
|
||||
+ Dispatch<ZxdgOutputV1, ObjectId>
|
||||
+ Dispatch<ZwlrScreencopyFrameV1, ObjectId>
|
||||
+ 'static,
|
||||
>(
|
||||
registry: &WlRegistry,
|
||||
name: u32,
|
||||
version: u32,
|
||||
qh: &QueueHandle<D>,
|
||||
output_data: U,
|
||||
xdg_manager: &ZxdgOutputManagerV1,
|
||||
screencopy: &ZwlrScreencopyManagerV1,
|
||||
) -> Self {
|
||||
let output = registry.bind(name, version, &qh, output_data);
|
||||
let xdg_output = xdg_manager.get_xdg_output(&output, &qh, output.id());
|
||||
let frame = screencopy.capture_output(0, &output, &qh, output.id());
|
||||
Output {
|
||||
output,
|
||||
xdg_output,
|
||||
logical_size: None,
|
||||
physical_position: None,
|
||||
transform: None,
|
||||
scale: Some(1),
|
||||
logical_position: None,
|
||||
physical_size: None,
|
||||
frame: Frame {
|
||||
frame,
|
||||
inverted_y: false,
|
||||
buffer_infos: Vec::new(),
|
||||
data: FrameData::Pre(vec![]),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct App {
|
||||
screencopy_mgr: ZwlrScreencopyManagerV1,
|
||||
xdg_manager: ZxdgOutputManagerV1,
|
||||
outputs: HashMap<ObjectId, Output>,
|
||||
shm: WlShm,
|
||||
}
|
||||
|
||||
impl Dispatch<ZwlrScreencopyManagerV1, ()> for App {
|
||||
fn event(
|
||||
_state: &mut Self,
|
||||
_proxy: &ZwlrScreencopyManagerV1,
|
||||
_event: <ZwlrScreencopyManagerV1 as Proxy>::Event,
|
||||
_data: &(),
|
||||
_conn: &Connection,
|
||||
_qhandle: &wayland_client::QueueHandle<Self>,
|
||||
) {
|
||||
// this does nothing
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<ZwlrScreencopyFrameV1, ObjectId> for App {
|
||||
fn event(
|
||||
state: &mut Self,
|
||||
proxy: &ZwlrScreencopyFrameV1,
|
||||
event: <ZwlrScreencopyFrameV1 as Proxy>::Event,
|
||||
data: &ObjectId,
|
||||
_conn: &Connection,
|
||||
_qhandle: &wayland_client::QueueHandle<Self>,
|
||||
) {
|
||||
let Some(output) = state.outputs.get_mut(data) else {
|
||||
return;
|
||||
};
|
||||
match event {
|
||||
zwlr_screencopy_frame_v1::Event::Buffer {
|
||||
format,
|
||||
width,
|
||||
height,
|
||||
stride,
|
||||
} => {
|
||||
if let FrameData::Pre(ref mut infos) = output.frame.data {
|
||||
infos.push(BufferInfo {
|
||||
format: format.into_result().unwrap(),
|
||||
width,
|
||||
height,
|
||||
stride,
|
||||
});
|
||||
}
|
||||
}
|
||||
zwlr_screencopy_frame_v1::Event::Flags { flags } => {
|
||||
output.frame.inverted_y = flags
|
||||
.into_result()
|
||||
.unwrap()
|
||||
.contains(zwlr_screencopy_frame_v1::Flags::YInvert);
|
||||
}
|
||||
zwlr_screencopy_frame_v1::Event::Ready { .. } => {
|
||||
if let FrameData::Copying {
|
||||
buffer,
|
||||
info,
|
||||
offset,
|
||||
} = core::mem::replace(&mut output.frame.data, FrameData::Done)
|
||||
{
|
||||
output.frame.data = FrameData::Copied {
|
||||
buffer,
|
||||
info,
|
||||
offset,
|
||||
};
|
||||
}
|
||||
}
|
||||
zwlr_screencopy_frame_v1::Event::Failed => {
|
||||
output.frame.data = FrameData::Done;
|
||||
panic!("screencopy failed.")
|
||||
}
|
||||
zwlr_screencopy_frame_v1::Event::BufferDone => {
|
||||
// FIXME: pick best buffer format
|
||||
// proxy.copy(buffer)
|
||||
if let FrameData::Pre(infos) =
|
||||
core::mem::replace(&mut output.frame.data, FrameData::Done)
|
||||
{
|
||||
let info = infos
|
||||
.iter()
|
||||
.max_by_key(|info| match info.format {
|
||||
wl_shm::Format::Xrgb8888 => 20,
|
||||
wl_shm::Format::Argb8888 => 10,
|
||||
_ => 0,
|
||||
})
|
||||
.unwrap()
|
||||
.clone();
|
||||
output.frame.data = FrameData::Buffer(info);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<ZxdgOutputManagerV1, ()> for App {
|
||||
fn event(
|
||||
state: &mut Self,
|
||||
proxy: &ZxdgOutputManagerV1,
|
||||
event: <ZxdgOutputManagerV1 as Proxy>::Event,
|
||||
data: &(),
|
||||
conn: &Connection,
|
||||
qhandle: &wayland_client::QueueHandle<Self>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<ZxdgOutputV1, ObjectId> for App {
|
||||
fn event(
|
||||
state: &mut Self,
|
||||
proxy: &ZxdgOutputV1,
|
||||
event: <ZxdgOutputV1 as Proxy>::Event,
|
||||
data: &ObjectId,
|
||||
conn: &Connection,
|
||||
qhandle: &wayland_client::QueueHandle<Self>,
|
||||
) {
|
||||
let Some(output) = state.outputs.get_mut(data) else {
|
||||
return;
|
||||
};
|
||||
match event {
|
||||
wayland_protocols::xdg::xdg_output::zv1::client::zxdg_output_v1::Event::LogicalPosition { x, y } => {
|
||||
output.logical_position = Some((x,y));
|
||||
},
|
||||
wayland_protocols::xdg::xdg_output::zv1::client::zxdg_output_v1::Event::LogicalSize { width, height } => {
|
||||
output.logical_size = Some((width, height));
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<wl_output::WlOutput, u32> for App {
|
||||
fn event(
|
||||
state: &mut Self,
|
||||
proxy: &wl_output::WlOutput,
|
||||
event: <wl_output::WlOutput as Proxy>::Event,
|
||||
_data: &u32,
|
||||
_conn: &Connection,
|
||||
_qhandle: &wayland_client::QueueHandle<Self>,
|
||||
) {
|
||||
let Some(output) = state.outputs.get_mut(&proxy.id()) else {
|
||||
return;
|
||||
};
|
||||
match event {
|
||||
wl_output::Event::Geometry {
|
||||
x, y, transform, ..
|
||||
} => {
|
||||
output.physical_position = Some((x, y));
|
||||
output.transform = Some(transform.into_result().unwrap());
|
||||
}
|
||||
wl_output::Event::Mode {
|
||||
flags,
|
||||
width,
|
||||
height,
|
||||
..
|
||||
} => {
|
||||
if flags == WEnum::Value(Mode::Current) {
|
||||
output.physical_size = Some((width, height));
|
||||
}
|
||||
}
|
||||
wl_output::Event::Scale { factor } => {
|
||||
output.scale = Some(factor);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<WlShm, ()> for App {
|
||||
fn event(
|
||||
state: &mut Self,
|
||||
proxy: &WlShm,
|
||||
event: <WlShm as Proxy>::Event,
|
||||
data: &(),
|
||||
conn: &Connection,
|
||||
qhandle: &wayland_client::QueueHandle<Self>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<WlShmPool, ()> for App {
|
||||
fn event(
|
||||
_state: &mut Self,
|
||||
_proxy: &WlShmPool,
|
||||
event: <WlShmPool as Proxy>::Event,
|
||||
_data: &(),
|
||||
_conn: &Connection,
|
||||
_qhandle: &wayland_client::QueueHandle<Self>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<WlBuffer, ()> for App {
|
||||
fn event(
|
||||
_state: &mut Self,
|
||||
_proxy: &WlBuffer,
|
||||
event: <WlBuffer as Proxy>::Event,
|
||||
_data: &(),
|
||||
_conn: &Connection,
|
||||
_qhandle: &wayland_client::QueueHandle<Self>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for App {
|
||||
fn event(
|
||||
state: &mut Self,
|
||||
registry: &wl_registry::WlRegistry,
|
||||
event: <wl_registry::WlRegistry as Proxy>::Event,
|
||||
globals: &GlobalListContents,
|
||||
_conn: &Connection,
|
||||
qh: &wayland_client::QueueHandle<Self>,
|
||||
) {
|
||||
match event {
|
||||
wl_registry::Event::Global { name, version, .. } => {
|
||||
let output = Output::bind(
|
||||
registry,
|
||||
name,
|
||||
version,
|
||||
qh,
|
||||
name,
|
||||
&state.xdg_manager,
|
||||
&state.screencopy_mgr,
|
||||
);
|
||||
state.outputs.insert(output.output.id(), output);
|
||||
}
|
||||
wl_registry::Event::GlobalRemove { name } => globals.with_list(|globals| {
|
||||
if globals
|
||||
.iter()
|
||||
.find(|global| global.name == name)
|
||||
.map(|global| &global.interface == wl_output::WlOutput::interface().name)
|
||||
== Some(true)
|
||||
{
|
||||
state
|
||||
.outputs
|
||||
.retain(|_, output| output.output.data() != Some(&name));
|
||||
}
|
||||
}),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Shm {
|
||||
fd: std::os::unix::io::OwnedFd,
|
||||
name: CString,
|
||||
size: usize,
|
||||
}
|
||||
|
||||
impl Shm {
|
||||
fn new(size: usize) -> std::io::Result<Self> {
|
||||
let name = CString::new(format!(
|
||||
"grisly-{}",
|
||||
rand::thread_rng()
|
||||
.sample_iter(&rand::distributions::Alphanumeric)
|
||||
.take(8)
|
||||
.map(char::from)
|
||||
.collect::<String>()
|
||||
))
|
||||
.unwrap();
|
||||
let fd = match unsafe {
|
||||
libc::shm_open(
|
||||
name.as_ptr(),
|
||||
libc::O_RDWR | libc::O_CREAT | libc::O_EXCL,
|
||||
0600,
|
||||
)
|
||||
} {
|
||||
-1 => Err(std::io::Error::last_os_error()),
|
||||
fd => Ok(unsafe { std::os::unix::io::OwnedFd::from_raw_fd(fd) }),
|
||||
}?;
|
||||
|
||||
match unsafe { libc::ftruncate(fd.as_raw_fd(), size as i64) } {
|
||||
-1 => Err(std::io::Error::last_os_error()),
|
||||
_ => Ok(()),
|
||||
}?;
|
||||
|
||||
Ok(Self { fd, name, size })
|
||||
}
|
||||
|
||||
fn map<'a>(&'a self) -> std::io::Result<&'a mut [u8]> {
|
||||
match unsafe {
|
||||
libc::mmap(
|
||||
core::ptr::null_mut(),
|
||||
self.size,
|
||||
libc::PROT_READ | libc::PROT_WRITE,
|
||||
libc::MAP_SHARED,
|
||||
self.fd.as_raw_fd(),
|
||||
0,
|
||||
)
|
||||
} {
|
||||
libc::MAP_FAILED => Err(std::io::Error::last_os_error()),
|
||||
ptr => Ok(unsafe { core::slice::from_raw_parts_mut(ptr as *mut _, self.size) }),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Shm {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
libc::shm_unlink(self.name.as_ptr());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Post-Processing transforms & scalings:
|
||||
// wl_output gives an integer scale to apply to buffer contents.
|
||||
// - question: does this apply after the xdg_output logical size?
|
||||
// - we use a sampled image to copy to a differently sized logical size anyway
|
||||
//
|
||||
// wl_output gives a transform which is applied to buffer contents.
|
||||
//
|
||||
|
||||
fn main() {
|
||||
let conn = Connection::connect_to_env().unwrap();
|
||||
|
||||
let (globals, mut queue) = registry_queue_init(&conn).unwrap();
|
||||
|
||||
let qh = queue.handle();
|
||||
|
||||
let screencopy_mgr = globals
|
||||
.bind::<ZwlrScreencopyManagerV1, _, _>(&queue.handle(), 1..=3, ())
|
||||
.unwrap();
|
||||
let xdg_manager = globals
|
||||
.bind::<ZxdgOutputManagerV1, _, _>(&queue.handle(), 3..=3, ())
|
||||
.unwrap();
|
||||
|
||||
let outputs = globals.contents().with_list(|list| {
|
||||
list.iter()
|
||||
.filter(|global| &global.interface == wl_output::WlOutput::interface().name)
|
||||
.map(|global| {
|
||||
let output = Output::bind(
|
||||
globals.registry(),
|
||||
global.name,
|
||||
global.version,
|
||||
&qh,
|
||||
global.name,
|
||||
&xdg_manager,
|
||||
&screencopy_mgr,
|
||||
);
|
||||
|
||||
(output.output.id(), output)
|
||||
})
|
||||
.collect::<HashMap<_, _>>()
|
||||
});
|
||||
|
||||
let shm = globals.bind(&queue.handle(), 0..=2, ()).unwrap();
|
||||
|
||||
let mut app = App {
|
||||
outputs,
|
||||
xdg_manager,
|
||||
screencopy_mgr,
|
||||
shm,
|
||||
};
|
||||
|
||||
while !app
|
||||
.outputs
|
||||
.iter()
|
||||
.all(|(_, output)| matches!(output.frame.data, FrameData::Buffer(_)))
|
||||
{
|
||||
_ = queue.roundtrip(&mut app).unwrap();
|
||||
}
|
||||
|
||||
let mut total_size = 0;
|
||||
let buffers = app
|
||||
.outputs
|
||||
.iter_mut()
|
||||
.map(|(_, output)| {
|
||||
let FrameData::Buffer(info) =
|
||||
core::mem::replace(&mut output.frame.data, FrameData::Done)
|
||||
else {
|
||||
unreachable!();
|
||||
};
|
||||
let size = info.height * info.stride;
|
||||
let offset = total_size;
|
||||
total_size += size;
|
||||
|
||||
(output, info, offset)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let shm = Shm::new(total_size as usize).unwrap();
|
||||
let buffer = shm.map().unwrap();
|
||||
|
||||
let pool = app
|
||||
.shm
|
||||
.create_pool(shm.fd.as_fd(), total_size as i32, &qh, ());
|
||||
|
||||
for (output, info, offset) in buffers {
|
||||
let buffer = pool.create_buffer(
|
||||
offset as i32,
|
||||
info.width as i32,
|
||||
info.height as i32,
|
||||
info.stride as i32,
|
||||
info.format,
|
||||
&qh,
|
||||
(),
|
||||
);
|
||||
|
||||
output.frame.frame.copy(&buffer);
|
||||
|
||||
output.frame.data = FrameData::Copying {
|
||||
buffer,
|
||||
info,
|
||||
offset: offset as usize,
|
||||
};
|
||||
}
|
||||
|
||||
while !app
|
||||
.outputs
|
||||
.iter()
|
||||
.all(|(_, output)| matches!(output.frame.data, FrameData::Copied { .. }))
|
||||
{
|
||||
_ = queue.roundtrip(&mut app).unwrap();
|
||||
}
|
||||
|
||||
fn rect_union(lhs: Rect, rhs: Rect) -> Rect {
|
||||
struct AABB {
|
||||
ax: u32,
|
||||
ay: u32,
|
||||
bx: u32,
|
||||
by: u32,
|
||||
}
|
||||
let lhs = AABB {
|
||||
ax: lhs.x,
|
||||
ay: lhs.y,
|
||||
bx: lhs.x + lhs.width,
|
||||
by: lhs.y + lhs.height,
|
||||
};
|
||||
let rhs = AABB {
|
||||
ax: rhs.x,
|
||||
ay: rhs.y,
|
||||
bx: rhs.x + rhs.width,
|
||||
by: rhs.y + rhs.height,
|
||||
};
|
||||
let union = AABB {
|
||||
ax: lhs.ax.min(rhs.ax),
|
||||
ay: lhs.ay.min(rhs.ay),
|
||||
bx: lhs.bx.max(rhs.bx),
|
||||
by: lhs.by.max(rhs.by),
|
||||
};
|
||||
|
||||
Rect {
|
||||
x: union.ax,
|
||||
y: union.ay,
|
||||
width: union.bx - union.ax,
|
||||
height: union.by - union.ay,
|
||||
}
|
||||
}
|
||||
|
||||
let mut image_rect = Rect {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
};
|
||||
let buffers = app
|
||||
.outputs
|
||||
.values_mut()
|
||||
.map(|output| {
|
||||
let FrameData::Copied { info, offset, .. } =
|
||||
core::mem::replace(&mut output.frame.data, FrameData::Done)
|
||||
else {
|
||||
unreachable!();
|
||||
};
|
||||
|
||||
let (px, py) = output.physical_position.unwrap_or((0, 0));
|
||||
let (lx, ly) = output.logical_position.unwrap_or((0, 0));
|
||||
// image destination
|
||||
let (x, y) = (lx - px, ly - py);
|
||||
|
||||
let (width, height) = output
|
||||
.logical_size
|
||||
.unwrap_or((info.width as i32, info.height as i32));
|
||||
|
||||
let rect = image::math::Rect {
|
||||
x: x as u32,
|
||||
y: y as u32,
|
||||
width: width as u32,
|
||||
height: height as u32,
|
||||
};
|
||||
|
||||
println!("buffer: {:?}", info);
|
||||
println!(" - invert-y: {}", output.frame.inverted_y);
|
||||
println!("destination rect: {:?}", rect);
|
||||
println!(" - transform: {:?}", output.transform);
|
||||
|
||||
image_rect = rect_union(image_rect, rect);
|
||||
|
||||
(info, offset, rect, output.frame.inverted_y)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut image = image::RgbImage::new(image_rect.width, image_rect.height);
|
||||
|
||||
let now = std::time::Instant::now();
|
||||
for (info, offset, rect, inverted_y) in buffers {
|
||||
let size = info.stride * info.height;
|
||||
let view = BufferView {
|
||||
info,
|
||||
inverted_y,
|
||||
bytes: &buffer[offset..][..size as usize],
|
||||
};
|
||||
|
||||
if rect.width != info.width || rect.height != info.height {
|
||||
let sampled = SampledView {
|
||||
view: &view,
|
||||
dimensions: (rect.width, rect.height),
|
||||
};
|
||||
image.copy_from(&sampled, rect.x, rect.y).unwrap();
|
||||
} else {
|
||||
image.copy_from(&view, rect.x, rect.y).unwrap();
|
||||
}
|
||||
}
|
||||
println!("copying: {}s", now.elapsed().as_secs_f64());
|
||||
|
||||
let file = std::fs::File::create("out.png").unwrap();
|
||||
let writer = BufWriter::new(file);
|
||||
let now = std::time::Instant::now();
|
||||
image
|
||||
.write_with_encoder(image::codecs::png::PngEncoder::new_with_quality(
|
||||
writer,
|
||||
image::codecs::png::CompressionType::Default,
|
||||
image::codecs::png::FilterType::Adaptive,
|
||||
))
|
||||
.unwrap();
|
||||
println!("encoding: {}s", now.elapsed().as_secs_f64());
|
||||
}
|
||||
|
||||
struct SampledView<'a, V: GenericImageView> {
|
||||
view: &'a V,
|
||||
dimensions: (u32, u32),
|
||||
}
|
||||
|
||||
impl<'a, V: GenericImageView> GenericImageView for SampledView<'a, V> {
|
||||
type Pixel = V::Pixel;
|
||||
|
||||
fn dimensions(&self) -> (u32, u32) {
|
||||
self.dimensions
|
||||
}
|
||||
|
||||
fn get_pixel(&self, x: u32, y: u32) -> Self::Pixel {
|
||||
let u = x as f32 / self.dimensions.0 as f32;
|
||||
let v = y as f32 / self.dimensions.1 as f32;
|
||||
|
||||
image::imageops::sample_bilinear(self.view, u, v).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
struct BufferView<'a> {
|
||||
info: BufferInfo,
|
||||
inverted_y: bool,
|
||||
bytes: &'a [u8],
|
||||
}
|
||||
|
||||
impl BufferView<'_> {
|
||||
fn pixel_offset(&self, x: u32, y: u32) -> usize {
|
||||
let y = if self.inverted_y {
|
||||
self.info.height - y
|
||||
} else {
|
||||
y
|
||||
};
|
||||
|
||||
self.info.stride as usize * y as usize + (x as usize * self.pixel_stride() as usize)
|
||||
}
|
||||
|
||||
fn pixel_stride(&self) -> u32 {
|
||||
(match self.info.format {
|
||||
wl_shm::Format::Argb8888 => 32,
|
||||
wl_shm::Format::Xrgb8888 => 32,
|
||||
wl_shm::Format::C8 => 8,
|
||||
wl_shm::Format::Rgb332 => 8,
|
||||
wl_shm::Format::Bgr233 => 8,
|
||||
wl_shm::Format::Xrgb4444 => 16,
|
||||
wl_shm::Format::Xbgr4444 => 16,
|
||||
wl_shm::Format::Rgbx4444 => 16,
|
||||
wl_shm::Format::Bgrx4444 => 16,
|
||||
wl_shm::Format::Argb4444 => 16,
|
||||
wl_shm::Format::Abgr4444 => 16,
|
||||
wl_shm::Format::Rgba4444 => 16,
|
||||
wl_shm::Format::Bgra4444 => 16,
|
||||
wl_shm::Format::Xrgb1555 => 16,
|
||||
wl_shm::Format::Xbgr1555 => 16,
|
||||
wl_shm::Format::Rgbx5551 => 16,
|
||||
wl_shm::Format::Bgrx5551 => 16,
|
||||
wl_shm::Format::Argb1555 => 16,
|
||||
wl_shm::Format::Abgr1555 => 16,
|
||||
wl_shm::Format::Rgba5551 => 16,
|
||||
wl_shm::Format::Bgra5551 => 16,
|
||||
wl_shm::Format::Rgb565 => 16,
|
||||
wl_shm::Format::Bgr565 => 16,
|
||||
wl_shm::Format::Rgb888 => 24,
|
||||
wl_shm::Format::Bgr888 => 24,
|
||||
wl_shm::Format::Xbgr8888 => 32,
|
||||
wl_shm::Format::Rgbx8888 => 32,
|
||||
wl_shm::Format::Bgrx8888 => 32,
|
||||
wl_shm::Format::Abgr8888 => 32,
|
||||
wl_shm::Format::Rgba8888 => 32,
|
||||
wl_shm::Format::Bgra8888 => 32,
|
||||
wl_shm::Format::Xrgb2101010 => 32,
|
||||
wl_shm::Format::Xbgr2101010 => 32,
|
||||
wl_shm::Format::Rgbx1010102 => 32,
|
||||
wl_shm::Format::Bgrx1010102 => 32,
|
||||
wl_shm::Format::Argb2101010 => 32,
|
||||
wl_shm::Format::Abgr2101010 => 32,
|
||||
wl_shm::Format::Rgba1010102 => 32,
|
||||
wl_shm::Format::Bgra1010102 => 32,
|
||||
wl_shm::Format::R8 => 8,
|
||||
wl_shm::Format::R16 => 16,
|
||||
wl_shm::Format::Rg88 => 16,
|
||||
wl_shm::Format::Gr88 => 16,
|
||||
wl_shm::Format::Rg1616 => 32,
|
||||
wl_shm::Format::Gr1616 => 32,
|
||||
wl_shm::Format::Xrgb16161616f => 64,
|
||||
wl_shm::Format::Xbgr16161616f => 64,
|
||||
wl_shm::Format::Argb16161616f => 64,
|
||||
wl_shm::Format::Abgr16161616f => 64,
|
||||
wl_shm::Format::Xrgb8888A8 => 40,
|
||||
wl_shm::Format::Xbgr8888A8 => 40,
|
||||
wl_shm::Format::Rgbx8888A8 => 40,
|
||||
wl_shm::Format::Bgrx8888A8 => 40,
|
||||
wl_shm::Format::Rgb888A8 => 32,
|
||||
wl_shm::Format::Bgr888A8 => 32,
|
||||
wl_shm::Format::Rgb565A8 => 24,
|
||||
wl_shm::Format::Bgr565A8 => 24,
|
||||
wl_shm::Format::Axbxgxrx106106106106 => 64,
|
||||
wl_shm::Format::Xrgb16161616 => 64,
|
||||
wl_shm::Format::Xbgr16161616 => 64,
|
||||
wl_shm::Format::Argb16161616 => 64,
|
||||
wl_shm::Format::Abgr16161616 => 64,
|
||||
_ => 1,
|
||||
}) / 8
|
||||
}
|
||||
|
||||
fn sample_r(&self, x: u32, y: u32) -> u8 {
|
||||
let offset = self.pixel_offset(x, y);
|
||||
let (shift, width) = match self.info.format {
|
||||
// (shift, width)
|
||||
wl_shm::Format::Argb8888 => (8 * 3, u8::BITS),
|
||||
wl_shm::Format::Xrgb8888 => (8 * 3, u8::BITS),
|
||||
wl_shm::Format::Xbgr8888 => (8 * 0, u8::BITS),
|
||||
wl_shm::Format::Abgr8888 => (8 * 0, u8::BITS),
|
||||
wl_shm::Format::Bgr888 => (8 * 2, u8::BITS),
|
||||
wl_shm::Format::Rgb888 => (0, u8::BITS),
|
||||
wl_shm::Format::Bgra8888 => (8 * 2, u8::BITS),
|
||||
wl_shm::Format::Bgrx8888 => (8 * 2, u8::BITS),
|
||||
wl_shm::Format::Rgba4444 => (0, u8::BITS / 2),
|
||||
wl_shm::Format::Rgbx4444 => (0, u8::BITS / 2),
|
||||
wl_shm::Format::Bgra4444 => (8, u8::BITS / 2),
|
||||
wl_shm::Format::Bgrx4444 => (8, u8::BITS / 2),
|
||||
wl_shm::Format::Argb4444 => (4, u8::BITS / 2),
|
||||
wl_shm::Format::Xrgb4444 => (4, u8::BITS / 2),
|
||||
wl_shm::Format::Rgba5551 => (0, 5),
|
||||
wl_shm::Format::Rgbx5551 => (0, 5),
|
||||
wl_shm::Format::Xrgb2101010 => (2, 10),
|
||||
wl_shm::Format::Argb2101010 => (2, 10),
|
||||
wl_shm::Format::Xrgb16161616 => (16, 16),
|
||||
wl_shm::Format::Argb16161616 => (16, 16),
|
||||
wl_shm::Format::Rgb332 => (0, 3),
|
||||
wl_shm::Format::Bgr233 => (5, 3),
|
||||
_ => unimplemented!(),
|
||||
};
|
||||
|
||||
let offset = offset + (shift / 8) as usize;
|
||||
let shift = shift % 8;
|
||||
|
||||
let acc = (self.bytes[offset] >> shift) & !(!1u8 << (width - 1).min(7));
|
||||
let bits = (1..(width / 8)).fold(acc as u64, |acc, i| {
|
||||
// byte chunks:
|
||||
let mask = !(!1u8 << (width - 1 - i * 8).min(8));
|
||||
|
||||
let bits = self.bytes[offset + i as usize] & mask;
|
||||
(acc << 8) & bits as u64
|
||||
});
|
||||
|
||||
let bits = if width > 8 {
|
||||
bits >> (width - 8)
|
||||
} else if width < 8 {
|
||||
bits << (8 - width)
|
||||
} else {
|
||||
bits
|
||||
};
|
||||
|
||||
bits as u8
|
||||
}
|
||||
fn sample_g(&self, x: u32, y: u32) -> u8 {
|
||||
let offset = self.pixel_offset(x, y);
|
||||
let (shift, width) = match self.info.format {
|
||||
// (shift, width)
|
||||
wl_shm::Format::Argb8888 => (8 * 1, u8::BITS),
|
||||
wl_shm::Format::Xrgb8888 => (8 * 1, u8::BITS),
|
||||
wl_shm::Format::Xbgr8888 => (8 * 1, u8::BITS),
|
||||
wl_shm::Format::Abgr8888 => (8 * 1, u8::BITS),
|
||||
wl_shm::Format::Bgr888 => (8 * 1, u8::BITS),
|
||||
wl_shm::Format::Rgb888 => (8, u8::BITS),
|
||||
wl_shm::Format::Bgra8888 => (8 * 1, u8::BITS),
|
||||
wl_shm::Format::Bgrx8888 => (8 * 1, u8::BITS),
|
||||
wl_shm::Format::Rgba4444 => (4, u8::BITS / 2),
|
||||
wl_shm::Format::Rgbx4444 => (4, u8::BITS / 2),
|
||||
wl_shm::Format::Bgra4444 => (4, u8::BITS / 2),
|
||||
wl_shm::Format::Bgrx4444 => (4, u8::BITS / 2),
|
||||
wl_shm::Format::Argb4444 => (8, u8::BITS / 2),
|
||||
wl_shm::Format::Xrgb4444 => (8, u8::BITS / 2),
|
||||
wl_shm::Format::Rgba5551 => (5, 5),
|
||||
wl_shm::Format::Rgbx5551 => (5, 5),
|
||||
wl_shm::Format::Xrgb2101010 => (12, 10),
|
||||
wl_shm::Format::Argb2101010 => (12, 10),
|
||||
wl_shm::Format::Xrgb16161616 => (32, 16),
|
||||
wl_shm::Format::Argb16161616 => (32, 16),
|
||||
wl_shm::Format::Rgb332 => (3, 3),
|
||||
wl_shm::Format::Bgr233 => (2, 3),
|
||||
_ => unimplemented!(),
|
||||
};
|
||||
|
||||
let offset = offset + (shift / 8) as usize;
|
||||
let shift = shift % 8;
|
||||
|
||||
let acc = (self.bytes[offset] >> shift) & !(!1u8 << (width - 1).min(7));
|
||||
let bits = (1..(width / 8)).fold(acc as u64, |acc, i| {
|
||||
// byte chunks:
|
||||
let mask = !(!1u8 << (width - 1 - i * 8).min(8));
|
||||
|
||||
let bits = self.bytes[offset + i as usize] & mask;
|
||||
(acc << 8) & bits as u64
|
||||
});
|
||||
|
||||
let bits = if width > 8 {
|
||||
bits >> (width - 8)
|
||||
} else if width < 8 {
|
||||
bits << (8 - width)
|
||||
} else {
|
||||
bits
|
||||
};
|
||||
|
||||
bits as u8
|
||||
}
|
||||
fn sample_b(&self, x: u32, y: u32) -> u8 {
|
||||
let offset = self.pixel_offset(x, y);
|
||||
let (shift, width) = match self.info.format {
|
||||
// (shift, width)
|
||||
wl_shm::Format::Argb8888 => (8 * 0, u8::BITS),
|
||||
wl_shm::Format::Xrgb8888 => (8 * 0, u8::BITS),
|
||||
wl_shm::Format::Xbgr8888 => (8 * 2, u8::BITS),
|
||||
wl_shm::Format::Abgr8888 => (8 * 2, u8::BITS),
|
||||
wl_shm::Format::Bgr888 => (8 * 0, u8::BITS),
|
||||
wl_shm::Format::Rgb888 => (8 * 2, u8::BITS),
|
||||
wl_shm::Format::Bgra8888 => (8 * 0, u8::BITS),
|
||||
wl_shm::Format::Bgrx8888 => (8 * 0, u8::BITS),
|
||||
wl_shm::Format::Rgba4444 => (8, u8::BITS / 2),
|
||||
wl_shm::Format::Rgbx4444 => (8, u8::BITS / 2),
|
||||
wl_shm::Format::Bgra4444 => (0, u8::BITS / 2),
|
||||
wl_shm::Format::Bgrx4444 => (0, u8::BITS / 2),
|
||||
wl_shm::Format::Argb4444 => (12, u8::BITS / 2),
|
||||
wl_shm::Format::Xrgb4444 => (12, u8::BITS / 2),
|
||||
wl_shm::Format::Rgba5551 => (10, 5),
|
||||
wl_shm::Format::Rgbx5551 => (10, 5),
|
||||
wl_shm::Format::Xrgb2101010 => (22, 10),
|
||||
wl_shm::Format::Argb2101010 => (22, 10),
|
||||
wl_shm::Format::Xrgb16161616 => (48, 16),
|
||||
wl_shm::Format::Argb16161616 => (48, 16),
|
||||
wl_shm::Format::Rgb332 => (6, 2),
|
||||
wl_shm::Format::Bgr233 => (0, 2),
|
||||
_ => unimplemented!(),
|
||||
};
|
||||
|
||||
let offset = offset + (shift / 8) as usize;
|
||||
let shift = shift % 8;
|
||||
|
||||
let acc = (self.bytes[offset] >> shift) & !(!1u8 << (width - 1).min(7));
|
||||
let bits = (1..(width / 8)).fold(acc as u64, |acc, i| {
|
||||
// byte chunks:
|
||||
let mask = !(!1u8 << (width - 1 - i * 8).min(8));
|
||||
|
||||
let bits = self.bytes[offset + i as usize] & mask;
|
||||
(acc << 8) & bits as u64
|
||||
});
|
||||
|
||||
let bits = if width > 8 {
|
||||
bits >> (width - 8)
|
||||
} else if width < 8 {
|
||||
bits << (8 - width)
|
||||
} else {
|
||||
bits
|
||||
};
|
||||
|
||||
bits as u8
|
||||
}
|
||||
}
|
||||
|
||||
impl GenericImageView for BufferView<'_> {
|
||||
type Pixel = image::Rgb<u8>;
|
||||
|
||||
fn dimensions(&self) -> (u32, u32) {
|
||||
(self.info.width, self.info.height)
|
||||
}
|
||||
|
||||
fn get_pixel(&self, x: u32, y: u32) -> Self::Pixel {
|
||||
match self.info.format {
|
||||
wl_shm::Format::Xbgr8888 => Self::Pixel::from(
|
||||
<[u8; 3] as TryFrom<&[u8]>>::try_from(&self.bytes[self.pixel_offset(x, y)..][..3])
|
||||
.unwrap(),
|
||||
),
|
||||
_ => Self::Pixel::from([
|
||||
self.sample_r(x, y),
|
||||
self.sample_g(x, y),
|
||||
self.sample_b(x, y),
|
||||
]),
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue