diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 39a6d99..b80ffff 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -573,6 +573,7 @@ dependencies = [ "base64", "bmp", "color_space", + "futures", "hex", "once_cell", "paris", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index b888429..9803e70 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -31,6 +31,7 @@ hex = "0.4.3" rumqttc = "0.17.0" time = { version = "0.3.17", features = ["formatting"] } color_space = "0.5.3" +futures = "0.3.25" [features] # by default Tauri runs in production mode diff --git a/src-tauri/src/core/core.rs b/src-tauri/src/core/core.rs index 5ee4b11..0565426 100644 --- a/src-tauri/src/core/core.rs +++ b/src-tauri/src/core/core.rs @@ -1,16 +1,23 @@ +use futures::{future::join_all, stream::FuturesUnordered, StreamExt}; use once_cell::sync::OnceCell; use paris::info; use serde::{Deserialize, Serialize}; -use std::{sync::Arc, time::Duration}; +use serde_json::value::Index; +use std::{collections::HashMap, iter::Map, sync::Arc, thread, time::Duration}; use tauri::async_runtime::RwLock; use tokio::{ + join, + sync::mpsc, task, time::{sleep, Instant}, }; use tracing::warn; use crate::{ - picker::{led_color::LedColor, manager::Picker}, + picker::{ + config::DisplayConfig, display_picker::DisplayPicker, led_color::LedColor, manager::Picker, + screenshot::Screenshot, + }, rpc, }; @@ -84,51 +91,100 @@ impl CoreManager { } pub async fn play_follow(&self) -> anyhow::Result<()> { - { - let lock = self.ambient_light_mode.read().await; - if let AmbientLightMode::Follow = *lock { - Picker::global().refresh_displays().await?; - } else { - return Ok(()); + let lock = self.ambient_light_mode.read().await; + let mut futs = vec![]; + if let AmbientLightMode::Follow = *lock { + drop(lock); + let configs = Picker::global().display_configs.lock().await; + + let (tx, mut rx) = mpsc::channel(10); + + for config in configs.to_owned() { + let tx = tx.clone(); + let fut = tokio::spawn(async move { + match Self::follow_display_by_config(config, tx).await { + Ok(_) => {} + Err(error) => { + warn!("following failed. {}", error); + } + } + }); + futs.push(fut); } - }; + + let configs = configs.clone(); + + tokio::spawn(async move { + let mut global_colors = HashMap::new(); + while let Some(screenshot) = rx.recv().await { + let start_at = Instant::now(); + match screenshot.get_top_colors().await { + Ok(colors) => { + let start = screenshot.get_top_of_led_strip_range().min().unwrap_or(0); + + let colors_len = colors.len(); + for (index, color) in colors.into_iter().enumerate() { + global_colors.insert(index + start, color); + } + + if global_colors.len() == 60 { + let mut colors = vec![]; + for index in 0..global_colors.len() { + colors.push(*global_colors.get(&index).unwrap()); + } + global_colors = HashMap::new(); + match rpc::manager::Manager::global() + .publish_led_colors(&colors) + .await + { + Ok(_) => { + info!("publish successful",); + } + Err(error) => { + warn!("publish led colors failed. {}", error); + } + } + } + } + Err(_) => {} + }; + } + }); + + join_all(futs).await; + } else { + drop(lock); + return Ok(()); + } + + Ok(()) + } + + async fn follow_display_by_config( + config: DisplayConfig, + tx: mpsc::Sender, + ) -> anyhow::Result<()> { + let mut picker = DisplayPicker::from_config(config)?; loop { let start = Instant::now(); let next_tick = start + Duration::from_millis(16); - info!("Following"); - let lock = self.ambient_light_mode.read().await; + let lock = Self::global().ambient_light_mode.read().await; if let AmbientLightMode::Follow = *lock { - task::spawn(async { - let start = Instant::now(); - match Self::follow_once().await { - Ok(_) => {} - Err(error) => { - warn!("take screenshots failed. {}", error); - } - }; - println!( - "Time elapsed in expensive_function() is: {:?}", - start.elapsed() - ); - }); + drop(lock); + let screenshot = picker.take_screenshot()?; + // info!("Take Screenshot Spend: {:?}", start.elapsed()); + tx.send(screenshot).await; } else { break; } tokio::time::sleep_until(next_tick).await; } - Ok(()) - } + // // Picker::global().take_screenshots_for_all().await?; + // // let colors = Picker::global().get_led_strip_colors().await?; - async fn follow_once() -> anyhow::Result<()> { - Picker::global().take_screenshots_for_all().await?; - let colors = Picker::global().get_led_strip_colors().await?; - - let colors = colors.into_iter().rev().collect(); - rpc::manager::Manager::global() - .publish_led_colors(&colors) - .await?; + // // let colors = colors.into_iter().rev().collect(); Ok(()) } } diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 5bec1cb..033f184 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -7,12 +7,12 @@ mod core; mod picker; mod rpc; -use crate::core::CoreManager; use crate::core::AmbientLightMode; +use crate::core::CoreManager; use paris::*; use picker::led_color::LedColor; use picker::manager::Picker; -use std::time::Instant; +use std::vec; #[tauri::command] async fn refresh_displays() { @@ -26,24 +26,22 @@ async fn refresh_displays() { #[tauri::command] async fn take_snapshot() -> Vec { - let start = Instant::now(); let manager = Picker::global(); - match manager.take_screenshots_for_all().await { - Ok(screenshots) => { - info!("screenshots len: {}", screenshots.len()); - let mut futures = Vec::new(); - for screenshot in screenshots { - let future = screenshot.to_webp_base64().await; - futures.push(future); - } - futures + let start = time::Instant::now(); + let base64_bitmap_list = match manager.list_displays().await { + Ok(base64_bitmap_list) => { + info!("screenshots len: {}", base64_bitmap_list.len()); + base64_bitmap_list } Err(error) => { error!("can not take screenshots for all. {}", error); + vec![] } - } + }; + info!("截图花费 {} s", start.elapsed().as_seconds_f32()); + base64_bitmap_list } #[tauri::command] diff --git a/src-tauri/src/picker/config.rs b/src-tauri/src/picker/config.rs new file mode 100644 index 0000000..2b1dea5 --- /dev/null +++ b/src-tauri/src/picker/config.rs @@ -0,0 +1,58 @@ +#[derive(Clone, Copy)] +pub struct LedStripConfig { + pub index: usize, + pub global_start_position: usize, + pub global_end_position: usize, +} + +#[derive(Clone, Copy)] +pub struct DisplayConfig { + pub index_of_display: usize, + pub display_width: usize, + pub display_height: usize, + pub top_led_strip: LedStripConfig, + pub bottom_led_strip: LedStripConfig, + pub left_led_strip: LedStripConfig, + pub right_led_strip: LedStripConfig, +} + +#[derive(Clone, Copy)] +pub enum LedFlowX { + LR, // from left to right + RL, // from right to left +} +#[derive(Clone, Copy)] +pub enum LedFlowY { + TB, // from top to bottom + BT, // from bottom to top +} + +impl DisplayConfig { + pub fn default(index_of_display: usize, display_width: usize, display_height: usize) -> Self { + Self { + index_of_display, + display_width, + display_height, + top_led_strip: LedStripConfig { + index: 0, + global_start_position: 0, + global_end_position: 0, + }, + bottom_led_strip: LedStripConfig { + index: 0, + global_start_position: 0, + global_end_position: 0, + }, + left_led_strip: LedStripConfig { + index: 0, + global_start_position: 0, + global_end_position: 0, + }, + right_led_strip: LedStripConfig { + index: 0, + global_start_position: 0, + global_end_position: 0, + }, + } + } +} diff --git a/src-tauri/src/picker/display_picker.rs b/src-tauri/src/picker/display_picker.rs new file mode 100644 index 0000000..db16244 --- /dev/null +++ b/src-tauri/src/picker/display_picker.rs @@ -0,0 +1,51 @@ +use paris::info; +use scrap::{Capturer, Display}; + +use super::{config::DisplayConfig, screen::Screen, screenshot::Screenshot}; + +pub struct DisplayPicker { + pub screen: Screen, + pub config: DisplayConfig, +} + +impl DisplayPicker { + pub fn new(screen: Screen, config: DisplayConfig) -> Self { + Self { screen, config } + } + + 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(); + + match display { + Some(display) => { + let height = display.height(); + let width = display.width(); + info!("dw: {}, cw: {}", width, config.display_height); + assert_eq!(width, config.display_width); + let capturer = Capturer::new(display)?; + let screen = Screen::new(capturer, width, height); + + Ok(Self { screen, config }) + } + None => { + anyhow::bail!("Index out of displays range.") + } + } + } + + pub fn take_screenshot(&mut self) -> anyhow::Result { + let bitmap = self + .screen + .take() + .map_err(|error| anyhow::anyhow!("take screenshot for display failed. {}", error))?; + + // info!("bitmap size {}", bitmap.len()); + let screenshot = Screenshot::new(bitmap, self.config); + Ok(screenshot) + } +} diff --git a/src-tauri/src/picker/manager.rs b/src-tauri/src/picker/manager.rs index 33a448a..44f1dfa 100644 --- a/src-tauri/src/picker/manager.rs +++ b/src-tauri/src/picker/manager.rs @@ -1,16 +1,24 @@ +use futures::{stream::FuturesUnordered, StreamExt}; use once_cell::sync::OnceCell; -use paris::*; -use scrap::{Capturer, Display}; +use paris::info; +use scrap::Display; use std::sync::Arc; -use tokio::sync::Mutex; +use tokio::{sync::Mutex, task}; -use crate::picker::screen::Screen; +use crate::picker::{ + config::{LedFlowX, LedFlowY, LedStripConfig}, + screen::Screen, +}; -use super::{led_color::LedColor, screenshot::Screenshot}; +use super::{ + config::DisplayConfig, display_picker::DisplayPicker, led_color::LedColor, + screenshot::Screenshot, +}; pub struct Picker { pub screens: Arc>>, pub screenshots: Arc>>, + pub display_configs: Arc>>, } impl Picker { @@ -20,46 +28,144 @@ impl Picker { SCREEN_COLOR_PICKER.get_or_init(|| Picker { screens: Arc::new(Mutex::new(vec![])), screenshots: Arc::new(Mutex::new(vec![])), + display_configs: Arc::new(Mutex::new(vec![ + DisplayConfig { + index_of_display: 1, + display_width: 1920, + display_height: 1200, + top_led_strip: LedStripConfig { + index: 1, + global_start_position: 32, + global_end_position: 60, + }, + bottom_led_strip: LedStripConfig { + index: 0, + global_start_position: 0, + global_end_position: 0, + }, + left_led_strip: LedStripConfig { + index: 0, + global_start_position: 0, + global_end_position: 0, + }, + right_led_strip: LedStripConfig { + index: 0, + global_start_position: 0, + global_end_position: 0, + }, + }, + DisplayConfig { + index_of_display: 0, + display_width: 3008, + display_height: 1692, + top_led_strip: LedStripConfig { + index: 0, + global_start_position: 0, + global_end_position: 32, + }, + bottom_led_strip: LedStripConfig { + index: 0, + global_start_position: 0, + global_end_position: 0, + }, + left_led_strip: LedStripConfig { + index: 0, + global_start_position: 0, + global_end_position: 0, + }, + right_led_strip: LedStripConfig { + index: 0, + global_start_position: 0, + global_end_position: 0, + }, + }, + ])), }) } - pub async fn refresh_displays(&self) -> anyhow::Result<()> { + pub async fn list_displays(&self) -> anyhow::Result> { + let mut configs = self.display_configs.lock().await; + let screenshots = self.screenshots.lock().await; + let displays = Display::all() .map_err(|error| anyhow::anyhow!("Can not get all of displays. {}", error))?; - let mut screens = self.screens.lock().await; - let mut screenshots = self.screenshots.lock().await; - screens.clear(); - info!("number of displays: {}", displays.len()); - for display in displays { + + configs.clear(); + let mut futs = FuturesUnordered::new(); + + for (index, display) in displays.iter().enumerate() { let height = display.height(); let width = display.width(); - match Capturer::new(display) { - Ok(capturer) => screens.push(Screen::new(capturer, width, height)), - Err(error) => screens.push(Screen::new_failed( - anyhow::anyhow!("{}", error), - width, - height, - )), - }; - screenshots.push(Screenshot::new(width, height)); + let config = DisplayConfig::default(index, width, height); + configs.push(config); } - screens.reverse(); - screenshots.reverse(); - screenshots[0].set_number_of_leds(22, 0); - screenshots[1].set_number_of_leds(38, 0); + for (index, display) in displays.iter().enumerate() { + let height = display.height(); + let width = display.width(); + let config = configs[index]; + futs.push(async move { + let join = task::spawn(Self::preview_display_by_config(config)); + join.await? + }); + } + let mut bitmap_string_list = vec![]; + while let Some(bitmap_string) = futs.next().await { + match bitmap_string { + Ok(bitmap_string) => { + bitmap_string_list.push(bitmap_string); + } + Err(error) => { + anyhow::bail!("can not convert to base64 image. {}", error); + } + } + } + Ok(bitmap_string_list) + } + + pub async fn preview_display_by_config(config: DisplayConfig) -> anyhow::Result { + let start = time::Instant::now(); + let mut picker = DisplayPicker::from_config(config)?; + let screenshot = picker.take_screenshot()?; + info!("Take Screenshot Spend: {}", start.elapsed()); + + anyhow::Ok(screenshot.to_webp_base64().await) + } + + pub async fn refresh_displays(&self) -> anyhow::Result<()> { + // let displays = Display::all() + // .map_err(|error| anyhow::anyhow!("Can not get all of displays. {}", error))?; + // let mut screens = self.screens.lock().await; + // let mut screenshots = self.screenshots.lock().await; + // screens.clear(); + // info!("number of displays: {}", displays.len()); + // for display in displays { + // let height = display.height(); + // let width = display.width(); + // match Capturer::new(display) { + // Ok(capturer) => screens.push(Screen::new(capturer, width, height)), + // Err(error) => screens.push(Screen::new_failed( + // anyhow::anyhow!("{}", error), + // width, + // height, + // )), + // }; + // screenshots.push(Screenshot::new(width, height)); + // } + + // screens.reverse(); + // screenshots.reverse(); Ok(()) } pub async fn take_screenshots_for_all(&self) -> anyhow::Result> { let mut screens = self.screens.lock().await; - let mut screenshots = self.screenshots.lock().await; + let screenshots = self.screenshots.lock().await; for (index, screen) in screens.iter_mut().enumerate() { let bitmap = screen.take().map_err(|error| { anyhow::anyhow!("take screenshot for display failed. {}", error) })?; - screenshots[index].set_bitmap(bitmap).await } Ok(screenshots.to_vec()) } diff --git a/src-tauri/src/picker/mod.rs b/src-tauri/src/picker/mod.rs index d76db0e..1b64049 100644 --- a/src-tauri/src/picker/mod.rs +++ b/src-tauri/src/picker/mod.rs @@ -1,4 +1,6 @@ pub mod led_color; pub mod screen; pub mod manager; -pub mod screenshot; \ No newline at end of file +pub mod screenshot; +pub mod display_picker; +pub mod config; \ No newline at end of file diff --git a/src-tauri/src/picker/screen.rs b/src-tauri/src/picker/screen.rs index 3950c61..7583bd2 100644 --- a/src-tauri/src/picker/screen.rs +++ b/src-tauri/src/picker/screen.rs @@ -1,5 +1,5 @@ - use scrap::Capturer; +use std::{io::ErrorKind::WouldBlock, time::Duration, thread}; pub struct Screen { capturer: Option, @@ -29,15 +29,24 @@ impl Screen { pub fn take(&mut self) -> anyhow::Result> { match self.capturer.as_mut() { - Some(capturer) => { - let buffer = capturer - .frame() - .map_err(|error| anyhow::anyhow!("failed to frame of display. {}", error))?; - anyhow::Ok(buffer.to_vec()) - } + Some(capturer) => loop { + match capturer.frame() { + Ok(buffer) => { + return anyhow::Ok(buffer.to_vec()); + } + Err(error) => { + if error.kind() == WouldBlock { + thread::sleep(Duration::from_millis(16)); + continue; + } else { + anyhow::bail!("failed to frame of display. {}", error); + } + } + } + }, None => anyhow::bail!("Do not initialized"), } } } -unsafe impl Send for Screen {} \ No newline at end of file +unsafe impl Send for Screen {} diff --git a/src-tauri/src/picker/screenshot.rs b/src-tauri/src/picker/screenshot.rs index 6005ce9..4dcca16 100644 --- a/src-tauri/src/picker/screenshot.rs +++ b/src-tauri/src/picker/screenshot.rs @@ -1,126 +1,106 @@ -use color_space::{Hsv, Rgb}; -use paris::info; -use std::sync::Arc; -use tokio::sync::Mutex; -use super::led_color::LedColor; +use std::ops::Range; + +use color_space::{Hsv, Rgb}; + +use super::{ + config::{DisplayConfig, LedStripConfig}, + led_color::LedColor, +}; #[derive(Clone)] pub struct Screenshot { - bitmap: Arc>>>, - width: usize, - height: usize, - led_number_of_x: usize, - led_number_of_y: usize, + bitmap: Vec, + config: DisplayConfig, } impl Screenshot { - pub fn new(width: usize, height: usize) -> Self { - Self { - bitmap: Arc::new(Mutex::new(None)), - led_number_of_x: 0, - led_number_of_y: 0, - width, - height, - } + pub fn new(bitmap: Vec, config: DisplayConfig) -> Self { + Self { bitmap, config } } - pub fn get_size(&self) -> (usize, usize) { - (self.width, self.height) - } - - pub fn get_number_of_leds(&self) -> (usize, usize) { - (self.led_number_of_x, self.led_number_of_y) - } - - pub fn set_number_of_leds(&mut self, led_number_of_x: usize, led_number_of_y: usize) { - self.led_number_of_x = led_number_of_x; - self.led_number_of_y = led_number_of_y; - } pub async fn get_top_colors(&self) -> anyhow::Result> { - self.get_x_colors(XPosition::Top).await + self.get_x_colors(XPosition::Top, self.config.top_led_strip) + .await } pub async fn get_bottom_colors(&self) -> anyhow::Result> { - self.get_x_colors(XPosition::Bottom).await + self.get_x_colors(XPosition::Bottom, self.config.bottom_led_strip) + .await } - async fn get_x_colors(&self, position: XPosition) -> anyhow::Result> { - if self.led_number_of_x == 0 { - return Ok(vec![]); - } + pub fn get_top_of_led_strip_range(&self) -> Range { + self.config.top_led_strip.global_start_position + ..self.config.top_led_strip.global_end_position + } - let bitmap = self.bitmap.lock().await; - match bitmap.as_ref() { - Some(bitmap) => { - let cell_size_x = self.width / self.led_number_of_x; - let cell_size_y = self.height / 8; - let cell_size = cell_size_x * cell_size_y; - let y_range = match position { - XPosition::Top => 20..cell_size_y + 20, - XPosition::Bottom => self.height - 20 - cell_size_y..self.height - 20, - }; - - let mut colors = Vec::new(); - let stride = bitmap.len() / self.height; - - for pos in 0..self.led_number_of_x { - let mut r = 0.0; - let mut g = 0.0; - let mut b = 0.0; - for x in pos * cell_size_x..(pos + 1) * cell_size_x { - for y in y_range.to_owned() { - let i = stride * y + 4 * x; - r += bitmap[i + 2] as f64; - g += bitmap[i + 1] as f64; - b += bitmap[i] as f64; - } - } - let rgb = Rgb::new( - r / cell_size as f64, - g / cell_size as f64, - b / cell_size as f64, - ); - let hsv = Hsv::from(rgb); - // info!("HSV: {:?}", [hsv.h, hsv.s, hsv.v]); - let color = LedColor::from_hsv(hsv.h, hsv.s, hsv.v); - // info!("color: {:?}", color.get_rgb()); - colors.push(color); - } - return Ok(colors); + async fn get_x_colors( + &self, + position: XPosition, + strip_config: LedStripConfig, + ) -> anyhow::Result> { + let bitmap = &self.bitmap; + let number_of_leds = strip_config + .global_start_position + .abs_diff(strip_config.global_end_position); + let cell_size_x = self.config.display_width / number_of_leds; + let cell_size_y = self.config.display_height / 8; + let cell_size = cell_size_x * cell_size_y; + let y_range = match position { + XPosition::Top => 20..cell_size_y + 20, + XPosition::Bottom => { + self.config.display_height - 20 - cell_size_y..self.config.display_height - 20 } - None => Ok(vec![]), - } - } + }; - pub async fn set_bitmap(&mut self, bitmap: Vec) { - let mut self_bitmap = self.bitmap.lock().await; - *self_bitmap = Some(bitmap); + let mut colors = Vec::new(); + let stride = bitmap.len() / self.config.display_height; + + for pos in strip_config.global_start_position..strip_config.global_end_position { + let mut r = 0.0; + let mut g = 0.0; + let mut b = 0.0; + for x in pos * cell_size_x..(pos + 1) * cell_size_x { + for y in y_range.to_owned() { + let i = stride * y + 4 * x; + r += bitmap[i + 2] as f64; + g += bitmap[i + 1] as f64; + b += bitmap[i] as f64; + } + } + let rgb = Rgb::new( + r / cell_size as f64, + g / cell_size as f64, + b / cell_size as f64, + ); + let hsv = Hsv::from(rgb); + // info!("HSV: {:?}", [hsv.h, hsv.s, hsv.v]); + let color = LedColor::from_hsv(hsv.h, hsv.s, hsv.v); + // info!("color: {:?}", color.get_rgb()); + colors.push(color); + } + return Ok(colors); } pub async fn to_webp_base64(&self) -> String { - let bitmap = self.bitmap.lock().await; - match bitmap.to_owned() { - Some(bitmap) => { - let mut bitflipped = Vec::with_capacity(self.width * self.height * 3); - let stride = bitmap.len() / self.height; + let bitmap = &self.bitmap; + let mut bitflipped = + Vec::with_capacity(self.config.display_width * self.config.display_height * 3); + let stride = bitmap.len() / self.config.display_height; - for y in 0..self.height { - for x in 0..self.width { - let i = stride * y + 4 * x; - bitflipped.extend_from_slice(&[bitmap[i + 2], bitmap[i + 1], bitmap[i]]); - } - } - - let webp_memory = webp::Encoder::from_rgb( - bitflipped.as_slice(), - self.width as u32, - self.height as u32, - ) - .encode(100.0); - return base64::encode(&*webp_memory); + for y in 0..self.config.display_height { + for x in 0..self.config.display_width { + let i = stride * y + 4 * x; + bitflipped.extend_from_slice(&[bitmap[i + 2], bitmap[i + 1], bitmap[i]]); } - None => "".to_owned(), } + + let webp_memory = webp::Encoder::from_rgb( + bitflipped.as_slice(), + self.config.display_width as u32, + self.config.display_height as u32, + ) + .encode(100.0); + return base64::encode(&*webp_memory); } }