feat: 生成 LED 灯条颜色。

This commit is contained in:
Ivan Li 2022-11-19 23:06:09 +08:00
parent f8a479f58b
commit d4709b9404
7 changed files with 258 additions and 62 deletions

7
src-tauri/Cargo.lock generated
View File

@ -1055,6 +1055,12 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]] [[package]]
name = "html5ever" name = "html5ever"
version = "0.25.2" version = "0.25.2"
@ -2300,6 +2306,7 @@ dependencies = [
"anyhow", "anyhow",
"base64", "base64",
"bmp", "bmp",
"hex",
"once_cell", "once_cell",
"paris", "paris",
"scrap", "scrap",

View File

@ -27,6 +27,7 @@ paris = { version = "1.5", features = ["timestamps", "macros"] }
tokio = { version = "1.22.0", features = ["full"] } tokio = { version = "1.22.0", features = ["full"] }
tracing = "0.1.37" tracing = "0.1.37"
tracing-subscriber = "0.3.16" tracing-subscriber = "0.3.16"
hex = "0.4.3"
[features] [features]
# by default Tauri runs in production mode # by default Tauri runs in production mode

View File

@ -0,0 +1,50 @@
use paris::info;
use serde::Serialize;
#[derive(Clone, Copy)]
pub struct LedColor {
bits: [u8; 3],
}
impl LedColor {
pub fn default() -> Self {
Self { bits: [0, 0, 0] }
}
pub fn new(r: u8, g: u8, b: u8) -> Self {
Self { bits: [r, g, b] }
}
pub fn get_rgb(&self) -> [u8; 3] {
self.bits
}
pub fn is_empty(&self) -> bool {
self.bits.iter().any(|bit| *bit == 0)
}
pub fn set_rgb(&mut self, r: u8, g: u8, b: u8) -> &Self {
self.bits = [r, g, b];
self
}
pub fn merge(&mut self, r: u8, g: u8, b: u8) -> &Self {
self.bits = [
(self.bits[0] / 2 + r / 2),
(self.bits[1] / 2 + g / 2),
(self.bits[2] / 2 + b / 2),
];
self
}
}
impl Serialize for LedColor {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
info!("{:?}", self.bits);
let hex = format!("#{}", hex::encode(self.bits));
serializer.serialize_str(hex.as_str())
}
}

View File

@ -3,8 +3,11 @@
windows_subsystem = "windows" windows_subsystem = "windows"
)] )]
mod led_color;
mod screen;
mod screen_color_picker; mod screen_color_picker;
use led_color::LedColor;
use paris::*; use paris::*;
use screen_color_picker::ScreenColorPicker; use screen_color_picker::ScreenColorPicker;
use std::{ use std::{
@ -30,11 +33,11 @@ async fn take_snapshot() -> Arc<Mutex<Vec<String>>> {
Ok(bitmaps) => { Ok(bitmaps) => {
info!("bitmaps len: {}", bitmaps.len()); info!("bitmaps len: {}", bitmaps.len());
match ScreenColorPicker::global().screens.lock() { match ScreenColorPicker::global().screens.lock() {
Ok(screens) => { Ok(screens) => Some(Vec::from_iter(screens.iter().enumerate().map(
Some(Vec::from_iter(screens.iter().enumerate().map( |(index, screen)| {
|(index, screen)| bitmap_to_webp_base64(screen.width, screen.height, bitmaps[index].to_vec()), bitmap_to_webp_base64(screen.width, screen.height, bitmaps[index].to_vec())
))) },
} ))),
Err(error) => { Err(error) => {
error!("can not lock screens. {}", error); error!("can not lock screens. {}", error);
None None
@ -65,6 +68,27 @@ async fn take_snapshot() -> Arc<Mutex<Vec<String>>> {
} }
} }
#[tauri::command]
async fn get_led_strip_colors() -> Result<Vec<LedColor>, String> {
let screens = ScreenColorPicker::global()
.screens
.lock()
.map_err(|error| {
error!("can not lock ScreenColorPick.screens. {}", error);
"failed to lock."
})?;
let mut colors = Vec::new();
for screen in screens.iter() {
let result = screen.get_top_colors();
if let Ok(result) = result {
colors.extend_from_slice(&result);
} else if let Err(result) = result {
return Err(format!("can not get led strip colors. {}", result));
}
}
Ok(colors)
}
async fn bitmap_to_webp_base64(width: usize, height: usize, bitmap: Vec<u8>) -> String { async fn bitmap_to_webp_base64(width: usize, height: usize, bitmap: Vec<u8>) -> String {
let mut bitflipped = Vec::with_capacity(width * height * 3); let mut bitflipped = Vec::with_capacity(width * height * 3);
let stride = bitmap.len() / height; let stride = bitmap.len() / height;
@ -89,7 +113,11 @@ async fn bitmap_to_webp_base64(width: usize, height: usize, bitmap: Vec<u8>) ->
fn main() { fn main() {
tauri::Builder::default() tauri::Builder::default()
.invoke_handler(tauri::generate_handler![take_snapshot, refresh_displays]) .invoke_handler(tauri::generate_handler![
take_snapshot,
refresh_displays,
get_led_strip_colors
])
.run(tauri::generate_context!()) .run(tauri::generate_context!())
.expect("error while running tauri application"); .expect("error while running tauri application");
} }

124
src-tauri/src/screen.rs Normal file
View File

@ -0,0 +1,124 @@
use std::sync::{Arc, Mutex};
use anyhow::Ok;
use scrap::Capturer;
use crate::led_color::LedColor;
pub struct Screen {
bitmap: Arc<Mutex<Option<Vec<u8>>>>,
capturer: Option<Capturer>,
init_error: Option<anyhow::Error>,
pub width: usize,
pub height: usize,
pub led_number_of_x: usize,
pub led_number_of_y: usize,
}
impl Screen {
pub fn new(capturer: Capturer, width: usize, height: usize) -> Self {
Self {
bitmap: Arc::new(Mutex::new(None)),
capturer: Some(capturer),
init_error: None,
width,
height,
led_number_of_x: 0,
led_number_of_y: 0,
}
}
pub fn new_failed(init_error: anyhow::Error, width: usize, height: usize) -> Self {
Self {
bitmap: Arc::new(Mutex::new(None)),
capturer: None,
init_error: Some(init_error),
width,
height,
led_number_of_x: 0,
led_number_of_y: 0,
}
}
pub fn set_number_of_leds(&mut self, led_number_of_x: usize, led_number_of_y: usize) -> &Self {
self.led_number_of_x = led_number_of_x;
self.led_number_of_y = led_number_of_y;
self
}
pub fn take(&mut self) -> anyhow::Result<Vec<u8>> {
match self.capturer.as_mut() {
Some(capturer) => {
let buffer = capturer
.frame()
.map_err(|error| anyhow::anyhow!("failed to frame of display. {}", error))?;
self.bitmap = Arc::new(Mutex::new(Some(buffer.to_vec())));
anyhow::Ok(buffer.to_vec())
}
None => anyhow::bail!("Do not initialized"),
}
}
pub fn get_top_colors(&self) -> anyhow::Result<Vec<LedColor>> {
self.get_x_colors(XPosition::Top)
}
pub fn get_bottom_colors(&self) -> anyhow::Result<Vec<LedColor>> {
self.get_x_colors(XPosition::Bottom)
}
fn get_x_colors(&self, position: XPosition) -> anyhow::Result<Vec<LedColor>> {
if self.led_number_of_x == 0 {
return Ok(vec![]);
}
let bitmap = self
.bitmap
.lock()
.map_err(|error| anyhow::anyhow!("can not lock Screen#bitmap. {}", error))?;
match bitmap.as_ref() {
Some(bitmap) => {
let cell_size_x = self.width / self.led_number_of_x;
let cell_size_y = self.height / 5;
let cell_size = cell_size_x * cell_size_y;
let y_range = match position {
XPosition::Top => 0..cell_size_y,
XPosition::Bottom => self.height - cell_size_y..self.height,
};
let mut colors = vec![LedColor::default(); self.led_number_of_x];
let stride = bitmap.len() / self.height;
for pos in 0..self.led_number_of_x {
let mut y_range = y_range.to_owned();
let mut r = 0u32;
let mut g = 0u32;
let mut b = 0u32;
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 u32;
g += bitmap[i + 1] as u32;
b += bitmap[i] as u32;
}
}
colors[pos] = LedColor::new(
(r / cell_size as u32) as u8,
(g / cell_size as u32) as u8,
(b / cell_size as u32) as u8,
);
}
return Ok(colors);
}
None => Ok(vec![]),
}
}
}
unsafe impl Send for Screen {}
enum XPosition {
Top,
Bottom,
}

View File

@ -1,21 +1,13 @@
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use paris::*;
use scrap::{Capturer, Display}; use scrap::{Capturer, Display};
use tracing::field::display;
use std::{ use std::{
io, borrow::BorrowMut,
sync::{Arc, Mutex}, sync::{Arc, Mutex},
}; };
use paris::*;
pub struct Screen {
bitmap: Option<Vec<u8>>,
capturer: Option<Capturer>,
init_error: Option<io::Error>,
pub height: usize,
pub width: usize,
}
unsafe impl Send for Screen {}
use crate::screen::Screen;
pub struct ScreenColorPicker { pub struct ScreenColorPicker {
pub screens: Arc<Mutex<Vec<Screen>>>, pub screens: Arc<Mutex<Vec<Screen>>>,
@ -43,22 +35,19 @@ impl ScreenColorPicker {
let height = display.height(); let height = display.height();
let width = display.width(); let width = display.width();
match Capturer::new(display) { match Capturer::new(display) {
Ok(capturer) => screens.push(Screen { Ok(capturer) => screens.push(Screen::new(capturer, width, height)),
bitmap: None, Err(error) => screens.push(Screen::new_failed(
capturer: Some(capturer), anyhow::anyhow!("{}", error),
init_error: None,
height,
width, width,
}),
Err(error) => screens.push(Screen {
bitmap: None,
capturer: None,
init_error: Some(error),
height, height,
width, )),
}),
} }
} }
screens.reverse();
screens[0].set_number_of_leds(22, 0);
screens[1].set_number_of_leds(38, 0);
Ok(()) Ok(())
} }
@ -78,19 +67,3 @@ impl ScreenColorPicker {
anyhow::Ok(screenshots) anyhow::Ok(screenshots)
} }
} }
impl Screen {
fn take(&mut self) -> anyhow::Result<Vec<u8>> {
match self.capturer.as_mut() {
Some(capturer) => {
let buffer = capturer
.frame()
.map_err(|error| anyhow::anyhow!("failed to frame of display. {}", error))?;
self.bitmap = Some(buffer.to_vec());
anyhow::Ok(buffer.to_vec())
}
None => anyhow::bail!("Do not initialized"),
}
}
}

View File

@ -7,6 +7,7 @@ function App() {
const [greetMsg, setGreetMsg] = useState(''); const [greetMsg, setGreetMsg] = useState('');
const [name, setName] = useState(''); const [name, setName] = useState('');
const [screenshots, setScreenshots] = useState<string[]>([]); const [screenshots, setScreenshots] = useState<string[]>([]);
const [ledStripColors, setLedStripColors] = useState<string[]>([]);
async function greet() { async function greet() {
// Learn more about Tauri commands at https://tauri.app/v1/guides/features/command // Learn more about Tauri commands at https://tauri.app/v1/guides/features/command
@ -23,20 +24,16 @@ function App() {
await invoke('refresh_displays'); await invoke('refresh_displays');
}, []); }, []);
return ( const getLedStripColors = useCallback(async () => {
<div className="container"> setLedStripColors(await invoke('get_led_strip_colors'));
<h1>Welcome to Tauri!</h1> }, []);
<div className="flex gap-5 justify-center"> return (
<a href="https://vitejs.dev" target="_blank"> <div>
<img src="/vite.svg" className="logo vite" alt="Vite logo" /> <div className="flex justify-between h-1">
</a> {ledStripColors.map((it) => (
<a href="https://tauri.app" target="_blank"> <span className="h-1 flex-auto" style={{ backgroundColor: it }}></span>
<img src="/tauri.svg" className="logo tauri" alt="Tauri logo" /> ))}
</a>
<a href="https://reactjs.org" target="_blank">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
</div> </div>
<div className="flex gap-1 justify-center w-screen overflow-hidden"> <div className="flex gap-1 justify-center w-screen overflow-hidden">
@ -47,8 +44,6 @@ function App() {
))} ))}
</div> </div>
<p>Click on the Tauri, Vite, and React logos to learn more.</p>
<div className="flex gap-5 justify-center "> <div className="flex gap-5 justify-center ">
<button <button
className="bg-black bg-opacity-20" className="bg-black bg-opacity-20"
@ -64,8 +59,26 @@ function App() {
> >
Take Snapshot Take Snapshot
</button> </button>
<button
className="bg-black bg-opacity-20"
type="button"
onClick={() => getLedStripColors()}
>
Get Colors
</button>
</div>
<div className="flex gap-5 justify-center">
<a href="https://vitejs.dev" target="_blank">
<img src="/vite.svg" className="logo vite" alt="Vite logo" />
</a>
<a href="https://tauri.app" target="_blank">
<img src="/tauri.svg" className="logo tauri" alt="Tauri logo" />
</a>
<a href="https://reactjs.org" target="_blank">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
</div> </div>
<p>{greetMsg}</p>
</div> </div>
); );
} }