From 505f56766a910fc6639f95bd5e2159f7c36fc053 Mon Sep 17 00:00:00 2001 From: Michael Zhang Date: Fri, 26 Jun 2020 21:14:32 -0500 Subject: [PATCH] screen capture full screen --- Cargo.lock | 1 + Cargo.toml | 12 +++----- src/lib.rs | 5 ++-- src/main.rs | 69 ++++++++++++++++++++++++++++++++++++++----- src/platform/mod.rs | 9 +++--- src/platform/x11.rs | 27 ++++++++++++----- x11/Cargo.toml | 1 + x11/src/lib.rs | 2 ++ x11/src/xlib/image.rs | 49 ++++++++++++++++++++++++++++-- x11/src/xlib/mod.rs | 2 +- 10 files changed, 145 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 33fbd74..fe17bda 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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)", ] diff --git a/Cargo.toml b/Cargo.toml index 5b9621a..f7362af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 "] +authors = ["Michael Zhang "] [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"] } diff --git a/src/lib.rs b/src/lib.rs index 2d8c4d6..ff3cbe3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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; diff --git a/src/main.rs b/src/main.rs index 7e4dc34..667d54a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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, } diff --git a/src/platform/mod.rs b/src/platform/mod.rs index b2fb143..f6e476e 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -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>; /// Capture full screen - fn capture_full_screen(&self) -> Result; + fn capture_full_screen(&self) -> Result>; /// Open the interactive selection interface fn show_interactive_selection(&self, image: Self::Image) -> Result; @@ -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; } diff --git a/src/platform/x11.rs b/src/platform/x11.rs index 609fc5e..9239ad1 100644 --- a/src/platform/x11.rs +++ b/src/platform/x11.rs @@ -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 { - let window = self.inner.get_root_window(0); - unimplemented!() + fn capture_full_screen(&self) -> Result> { + 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> { @@ -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() + }) } } diff --git a/x11/Cargo.toml b/x11/Cargo.toml index 58df0d3..a144c4a 100644 --- a/x11/Cargo.toml +++ b/x11/Cargo.toml @@ -15,3 +15,4 @@ xinerama = ["x11/xlib", "x11/xinerama"] libc = "0.2" x11 = { version = "2.18" } thiserror = "1.0.20" +log = "0.4.8" diff --git a/x11/src/lib.rs b/x11/src/lib.rs index 240f905..2227a9f 100644 --- a/x11/src/lib.rs +++ b/x11/src/lib.rs @@ -2,6 +2,8 @@ #[macro_use] extern crate thiserror; +#[macro_use] +extern crate log; pub extern crate x11; diff --git a/x11/src/xlib/image.rs b/x11/src/xlib/image.rs index 42a2d4a..227e902 100644 --- a/x11/src/xlib/image.rs +++ b/x11/src/xlib/image.rs @@ -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 { 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)) + } } diff --git a/x11/src/xlib/mod.rs b/x11/src/xlib/mod.rs index 5ed8e83..e7d6f24 100644 --- a/x11/src/xlib/mod.rs +++ b/x11/src/xlib/mod.rs @@ -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;