screen capture full screen

This commit is contained in:
Michael Zhang 2020-06-26 21:14:32 -05:00
parent 8dc13a1ad6
commit 505f56766a
Signed by: michael
GPG key ID: BDA47A31A3C8EE6B
10 changed files with 145 additions and 32 deletions

1
Cargo.lock generated
View file

@ -244,6 +244,7 @@ name = "leanshot_x11"
version = "0.2.0"
dependencies = [
"libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"thiserror 1.0.20 (registry+https://github.com/rust-lang/crates.io-index)",
"x11 2.18.2 (registry+https://github.com/rust-lang/crates.io-index)",
]

View file

@ -2,10 +2,10 @@
name = "leanshot"
description = "Screenshot capture for Linux."
version = "0.4.0"
repository = "https://github.com/iptq/leanshot"
repository = "https://git.sr.ht/~iptq/leanshot"
license-file = "LICENSE"
edition = "2018"
authors = ["Michael Zhang <failed.down@gmail.com>"]
authors = ["Michael Zhang <iptq@protonmail.com>"]
[workspace]
members = [".", "x11"]
@ -15,15 +15,11 @@ default = ["x11"]
x11 = ["leanshot_x11"]
[dependencies]
# gl = "0.11"
# glutin = "0.19"
# nanovg = { version = "1.0.2", features = ["gl3"] }
png = "0.14"
structopt = "0.2"
# time = "0.1"
anyhow = "1.0.31"
image = "0.23.5"
leanshot_x11 = { version = "0.2", path = "x11", optional = true, features = ["xlib", "xinerama"] }
log = "0.4.8"
stderrlog = "0.4.3"
leanshot_x11 = { version = "0.2", path = "x11", optional = true, features = ["xlib", "xinerama"] }

View file

@ -4,14 +4,15 @@
#[macro_use]
extern crate anyhow;
#[macro_use]
extern crate log;
extern crate leanshot_x11 as x11;
extern crate structopt;
mod platform;
use anyhow::Result;
pub use crate::platform::{x11::X11, Platform};
pub use crate::platform::{x11::X11, Image, Platform};
type Rectangle = x11::Rectangle;

View file

@ -1,19 +1,27 @@
#[macro_use]
extern crate log;
use std::path::PathBuf;
use anyhow::Result;
use leanshot::{Platform, Region, X11};
use image::{imageops, RgbImage};
use leanshot::{Image, Platform, Region, X11};
use structopt::StructOpt;
#[allow(missing_docs)]
pub fn main() -> Result<()> {
stderrlog::new().module(module_path!()).init().unwrap();
let opt = Options::from_args();
stderrlog::new()
.module(module_path!())
.module("leanshot_x11")
.verbosity(opt.verbose)
.init()
.unwrap();
let gui = X11::new()?;
let screen_layout = gui.get_screen_layout()?;
println!("{:?}", screen_layout);
debug!("screen layout: {:?}", screen_layout);
let image = match opt.region {
Region::ActiveWindow => {
@ -22,14 +30,57 @@ pub fn main() -> Result<()> {
}
Region::Fullscreen | Region::Selection => {
let mut image = gui.capture_full_screen()?;
if let Region::Fullscreen = opt.region {
let rect = gui.show_interactive_selection(image)?;
// TODO: crop the image
// calculate the full size of the image
let mut min_x = 0;
let mut min_y = 0;
let mut max_x = 0;
let mut max_y = 0;
for (i, (_, screen)) in screen_layout.iter().enumerate() {
let left = screen.x;
let right = screen.x + screen.width as i16;
let top = screen.y;
let bottom = screen.y + screen.height as i16;
if i == 0 {
min_x = left;
min_y = top;
max_x = right;
max_y = bottom;
} else {
min_x = min_x.min(left);
min_y = min_y.min(top);
max_x = max_x.max(right);
max_y = max_y.max(bottom);
}
}
debug!(
"full virtual screen rect : ({}, {}) to ({}, {})",
min_x, min_y, max_x, max_y
);
// make a new image
let width = (max_x - min_x) as u32;
let height = (max_y - min_y) as u32;
let mut base_image = RgbImage::new(width, height);
// copy all of the images into it
for (id, image) in gui.capture_full_screen()? {
let screen = screen_layout.get(&id).expect("shouldn't fail");
let x = (screen.x + min_x) as u32;
let y = (screen.y + min_y) as u32;
let image = image.into_rgb_image();
imageops::overlay(&mut base_image, &image, x, y);
}
// save the image
base_image.save(&opt.outfile);
}
}
};
unimplemented!("leanshot::capture")
Ok(())
}
/// Options for screenshot
@ -46,4 +97,8 @@ pub struct Options {
/// Whether or not to also copy it to the clipboard
#[structopt(short = "c")]
pub clipboard: bool,
/// Verbosity of output
#[structopt(short = "v", long = "verbose", parse(from_occurrences))]
verbose: usize,
}

View file

@ -5,7 +5,7 @@ use std::collections::HashMap;
use std::hash::Hash;
use anyhow::Result;
use image::RgbaImage;
use image::RgbImage;
use crate::Rectangle;
@ -25,7 +25,7 @@ pub trait Platform {
/// Type of images
type Image: Image;
/// Type of screen handles
/// Type of screen handles (must be hashable and uniquely identifying)
type ScreenId: Hash;
/// Get a handle to the currently active window
@ -38,7 +38,7 @@ pub trait Platform {
fn get_screen_layout(&self) -> Result<HashMap<Self::ScreenId, ScreenInfo>>;
/// Capture full screen
fn capture_full_screen(&self) -> Result<Self::Image>;
fn capture_full_screen(&self) -> Result<HashMap<Self::ScreenId, Self::Image>>;
/// Open the interactive selection interface
fn show_interactive_selection(&self, image: Self::Image) -> Result<Rectangle>;
@ -46,5 +46,6 @@ pub trait Platform {
/// Set of functions platform-specific images must implement
pub trait Image {
fn into_rgba_image(self) -> RgbaImage;
/// Convert the image into an image::RgbImage
fn into_rgb_image(self) -> RgbImage;
}

View file

@ -1,10 +1,10 @@
use std::collections::HashMap;
use anyhow::Result;
use image::RgbaImage;
use image::{Rgb, RgbImage};
use x11::{
xinerama::ScreensInfo,
xlib::{Display, Window},
xlib::{Display, ImageByteOrder, Window},
};
use crate::platform::{Image as ImageT, Platform, ScreenInfo};
@ -40,9 +40,15 @@ impl Platform for X11 {
Ok(Image(image))
}
fn capture_full_screen(&self) -> Result<Self::Image> {
let window = self.inner.get_root_window(0);
unimplemented!()
fn capture_full_screen(&self) -> Result<HashMap<Self::ScreenId, Self::Image>> {
let mut result = HashMap::new();
// wait what?
// x11 doesn't have a root window for each screen apparently
let window = self.inner.get_default_root_window()?;
result.insert(0, Image(window.get_image()?));
Ok(result)
}
fn get_screen_layout(&self) -> Result<HashMap<Self::ScreenId, ScreenInfo>> {
@ -77,7 +83,14 @@ impl Platform for X11 {
pub struct Image(x11::xlib::Image);
impl ImageT for Image {
fn into_rgba_image(self) -> RgbaImage {
todo!()
fn into_rgb_image(self) -> RgbImage {
let width = self.0.get_width();
let height = self.0.get_height();
let pixbuf = self.0.buffer();
let byte_order = self.0.get_byte_order().unwrap();
RgbImage::from_fn(width, height, |x, y| {
let (r, g, b) = pixbuf.get_pixel(x, y).unwrap();
[r, g, b].into()
})
}
}

View file

@ -15,3 +15,4 @@ xinerama = ["x11/xlib", "x11/xinerama"]
libc = "0.2"
x11 = { version = "2.18" }
thiserror = "1.0.20"
log = "0.4.8"

View file

@ -2,6 +2,8 @@
#[macro_use]
extern crate thiserror;
#[macro_use]
extern crate log;
pub extern crate x11;

View file

@ -1,3 +1,5 @@
use std::os::raw::c_ulong;
use x11::xlib;
use crate::errors::{Error, Result};
@ -16,11 +18,17 @@ pub enum ImageByteOrder {
}
/// The buffer pointed to by an XImage.
pub struct PixBuffer {
pub struct PixBuffer<'a> {
/// The raw pointer to the buffer
pub buf: *mut u8,
/// The size of the buffer
pub size: usize,
/// Rgb masks
pub masks: (u64, u64, u64),
pub image: &'a Image,
}
impl Image {
@ -54,11 +62,24 @@ impl Image {
}
}
fn get_rgb_masks(&self) -> (u64, u64, u64) {
let red_mask = unsafe { (*self.inner).red_mask };
let green_mask = unsafe { (*self.inner).green_mask };
let blue_mask = unsafe { (*self.inner).blue_mask };
(red_mask, blue_mask, green_mask)
}
/// Produces a PixBuffer
pub fn buffer(&self) -> PixBuffer {
let size = self.get_size();
let buf = unsafe { (*self.inner).data as *mut u8 };
PixBuffer { buf, size }
let masks = self.get_rgb_masks();
PixBuffer {
buf,
size,
masks,
image: self,
}
}
}
@ -68,7 +89,7 @@ impl Drop for Image {
}
}
impl PixBuffer {
impl<'a> PixBuffer<'a> {
/// Gets the byte at the index of the data buffer.
pub fn get_byte(&self, index: usize) -> Option<u8> {
if index > self.size {
@ -76,4 +97,26 @@ impl PixBuffer {
}
Some(unsafe { *self.buf.offset(index as isize) as u8 })
}
/// Gets the rgb value of a particular pixel
pub fn get_pixel(&self, x: u32, y: u32) -> Option<(u8, u8, u8)> {
// let bytes_per_line = unsafe{(*self.image.inner).bytes_per_line} as u32;
// let bits_per_pixel=unsafe{(*self.image.inner).bits_per_pixel} as u32;
// let pixel_ptr = (y * bytes_per_line + x * bits_per_pixel) / 8;
// let pixel = unsafe{*((*self.image.inner).data.offset(pixel_ptr as isize) as *mut c_ulong)};
let get_pixel = unsafe { (*self.image.inner).funcs.get_pixel }.unwrap();
let pixel = unsafe { get_pixel(self.image.inner, x as i32, y as i32) };
// warn!("pixel: {:?} {:#b}", pixel, pixel);
let red_mask = unsafe { (*self.image.inner).red_mask };
let green_mask = unsafe { (*self.image.inner).green_mask };
let blue_mask = unsafe { (*self.image.inner).blue_mask };
// warn!("masks: {:b} {:b} {:b}", red_mask, green_mask, blue_mask);
let red = ((pixel & unsafe { (*self.image.inner).red_mask }) >> 16) as u8;
let green = ((pixel & unsafe { (*self.image.inner).green_mask }) >> 8) as u8;
let blue = ((pixel & unsafe { (*self.image.inner).blue_mask }) >> 0) as u8;
// warn!("rgb: {:?}", (red, green, blue));
Some((red, green, blue))
}
}

View file

@ -12,7 +12,7 @@ pub use self::cursor::Cursor;
pub use self::display::{Display, GetDisplay};
pub use self::drawable::Drawable;
pub use self::event::{Event, EventKind};
pub use self::image::Image;
pub use self::image::{Image, ImageByteOrder};
pub use self::visual::Visual;
pub use self::window::Window;