feat: 生成 LED 灯条颜色。
This commit is contained in:
parent
f8a479f58b
commit
d4709b9404
7
src-tauri/Cargo.lock
generated
7
src-tauri/Cargo.lock
generated
@ -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",
|
||||||
|
@ -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
|
||||||
|
50
src-tauri/src/led_color.rs
Normal file
50
src-tauri/src/led_color.rs
Normal 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())
|
||||||
|
}
|
||||||
|
}
|
@ -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
124
src-tauri/src/screen.rs
Normal 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,
|
||||||
|
}
|
@ -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"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
45
src/App.tsx
45
src/App.tsx
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user