From 596da96f8cdecf796ffae8e0266f45ff6799eb7f Mon Sep 17 00:00:00 2001 From: Ivan Li Date: Sun, 19 Mar 2023 10:17:13 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=9B=B4=E6=94=B9=E6=88=AA=E5=9B=BE?= =?UTF-8?q?=E9=80=BB=E8=BE=91=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src-tauri/Cargo.lock | 36 +++++++++ src-tauri/Cargo.toml | 2 + src-tauri/src/picker/config/display_config.rs | 7 +- src-tauri/src/picker/display_picker.rs | 73 ++++++++++++++++--- src-tauri/src/picker/manager.rs | 10 ++- 5 files changed, 113 insertions(+), 15 deletions(-) diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index cc1fba3..0a11be8 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -856,7 +856,9 @@ dependencies = [ "base64 0.13.1", "bmp", "color_space", + "core-graphics", "ddc-hi", + "display-info", "either", "futures", "hex", @@ -880,6 +882,20 @@ dependencies = [ "webp", ] +[[package]] +name = "display-info" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15453e90755c09fc70a6dbc9a307b0d4ec0fa65c5e36fb7cf8246109c442c331" +dependencies = [ + "anyhow", + "core-graphics", + "fxhash", + "widestring", + "windows 0.44.0", + "xcb", +] + [[package]] name = "dns-parser" version = "0.8.0" @@ -2804,6 +2820,15 @@ version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" +[[package]] +name = "quick-xml" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8533f14c8382aaad0d592c812ac3b826162128b65662331e1127b45c3d18536b" +dependencies = [ + "memchr 2.5.0", +] + [[package]] name = "quick-xml" version = "0.23.1" @@ -4709,6 +4734,17 @@ dependencies = [ "libc", ] +[[package]] +name = "xcb" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0faeb4d7e2d54fff4a0584f61297e86b106914af2029778de7b427f72564d6c5" +dependencies = [ + "bitflags", + "libc", + "quick-xml 0.22.0", +] + [[package]] name = "yaml-rust" version = "0.4.5" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 08df5b8..9c53ca6 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -38,6 +38,8 @@ macos-app-nap = "0.0.1" ddc-hi = "0.4.1" redb = "0.13.0" paho-mqtt = "0.12.0" +core-graphics = "0.22.3" +display-info = "0.4.1" [features] # by default Tauri runs in production mode diff --git a/src-tauri/src/picker/config/display_config.rs b/src-tauri/src/picker/config/display_config.rs index e6dda63..43104d5 100644 --- a/src-tauri/src/picker/config/display_config.rs +++ b/src-tauri/src/picker/config/display_config.rs @@ -17,11 +17,12 @@ pub struct LedStripConfig { #[derive(Clone, Copy, Serialize, Deserialize, Debug)] pub struct DisplayConfig { - pub id: usize, + pub id: u32, pub index_of_display: usize, pub display_width: usize, pub display_height: usize, pub led_strip_of_borders: LedStripConfigOfBorders, + pub scale_factor: f32, } impl LedStripConfigOfBorders { @@ -37,10 +38,11 @@ impl LedStripConfigOfBorders { impl DisplayConfig { pub fn default( - id: usize, + id: u32, index_of_display: usize, display_width: usize, display_height: usize, + scale_factor: f32, ) -> Self { Self { id, @@ -48,6 +50,7 @@ impl DisplayConfig { display_width, display_height, led_strip_of_borders: LedStripConfigOfBorders::default(), + scale_factor, } } } diff --git a/src-tauri/src/picker/display_picker.rs b/src-tauri/src/picker/display_picker.rs index e1b44be..732862b 100644 --- a/src-tauri/src/picker/display_picker.rs +++ b/src-tauri/src/picker/display_picker.rs @@ -1,5 +1,9 @@ +use core_graphics::display::{ + kCGNullWindowID, kCGWindowImageDefault, kCGWindowListOptionOnScreenOnly, CGDisplay, +}; use paris::info; use scrap::{Capturer, Display}; +use tracing::debug; use super::{config::DisplayConfig, screen::Screen, screenshot::Screenshot}; @@ -12,10 +16,7 @@ impl DisplayPicker { pub fn from_config(config: DisplayConfig) -> anyhow::Result { let displays = Display::all() .map_err(|error| anyhow::anyhow!("Can not get all of displays. {}", error))?; - let display = displays - .into_iter() - .skip(config.index_of_display) - .next(); + let display = displays.into_iter().skip(config.index_of_display).next(); match display { Some(display) => { @@ -35,13 +36,67 @@ impl DisplayPicker { } pub fn take_screenshot(&mut self) -> anyhow::Result { - let bitmap = self - .screen - .take() - .map_err(|error| anyhow::anyhow!("take screenshot for display failed. {}", error))?; + debug!("take_screenshot"); + let start_at = std::time::Instant::now(); + + let cg_display = CGDisplay::new(self.config.id); + let cg_image = CGDisplay::screenshot( + cg_display.bounds(), + kCGWindowListOptionOnScreenOnly, + kCGNullWindowID, + kCGWindowImageDefault, + ) + .ok_or_else(|| anyhow::anyhow!("Display#{}: take screenshot failed", self.config.id))?; + debug!("take screenshot took {}ms", start_at.elapsed().as_millis()); + + let buffer = cg_image.data(); + let bytes_per_row = cg_image.bytes_per_row() as f32; + + let height = cg_image.height(); + let width = cg_image.width(); + + let scale_factor = self.config.scale_factor; + let image_height = (height as f32 / scale_factor) as u32; + let image_width = (width as f32 / scale_factor) as u32; + + debug!( + "raw image: {}x{}, output image: {}x{}", + width, height, image_width, image_height + ); + // // from bitmap vec + let mut image_buffer = vec![0u8; (image_width * image_height * 3) as usize]; + + for y in 0..image_height { + for x in 0..image_width { + let offset = + (((y as f32) * bytes_per_row + (x as f32) * 4.0) * scale_factor) as usize; + let b = buffer[offset]; + let g = buffer[offset + 1]; + let r = buffer[offset + 2]; + let a = buffer[offset + 3]; + let offset = (y * image_width + x) as usize; + image_buffer[offset * 3] = r; + image_buffer[offset * 3 + 1] = g; + image_buffer[offset * 3 + 2] = b; + } + } + debug!( + "convert to image buffer took {}ms", + start_at.elapsed().as_millis() + ); + + // println!("encode to png took {}ms", start_at.elapsed().as_millis()); + // // // base64 image + // let mut image_base64 = String::new(); + // image_base64.push_str("data:image/png;base64,"); + // let encoded = base64::engine::general_purpose::STANDARD_NO_PAD.encode(image_png); + // image_base64.push_str(encoded.as_str()); + + // println!("took {}ms", start_at.elapsed().as_millis()); + // println!("image_base64: {}", image_base64.len()); // info!("bitmap size {}", bitmap.len()); - let screenshot = Screenshot::new(bitmap, self.config); + let screenshot = Screenshot::new(image_buffer, self.config); Ok(screenshot) } } diff --git a/src-tauri/src/picker/manager.rs b/src-tauri/src/picker/manager.rs index 6680edd..4e80297 100644 --- a/src-tauri/src/picker/manager.rs +++ b/src-tauri/src/picker/manager.rs @@ -1,3 +1,5 @@ +use core_graphics::display::CGDisplay; +use display_info::DisplayInfo; use futures::{stream::FuturesUnordered, StreamExt}; use paris::info; use scrap::Display; @@ -43,16 +45,16 @@ impl Picker { pub async fn list_displays(&self) -> anyhow::Result> { let mut configs = vec![]; - let displays = Display::all() + let displays = DisplayInfo::all() .map_err(|error| anyhow::anyhow!("Can not get all of displays. {}", error))?; // configs.clear(); let mut futs = FuturesUnordered::new(); for (index, display) in displays.iter().enumerate() { - let height = display.height(); - let width = display.width(); - let config = DisplayConfig::default(index, index, width, height); + let height = display.height as usize; + let width = display.width as usize; + let config = DisplayConfig::default(display.id, index, width, height, display.scale_factor); configs.push(config); }