This commit is contained in:
Moritz Ruth 2024-01-07 01:08:55 +01:00
parent d97bf24168
commit a57347bd78
Signed by: moritzruth
GPG key ID: C9BBAB79405EE56D
7 changed files with 122 additions and 77 deletions

70
Cargo.lock generated
View file

@ -188,9 +188,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.4.12"
version = "4.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcfab8ba68f3668e89f6ff60f5b205cea56aa7b769451a59f34b8682f51c056d"
checksum = "52bdc885e4cacc7f7c9eedc1ef6da641603180c783c41a15c264944deeaab642"
dependencies = [
"clap_builder",
"clap_derive",
@ -217,7 +217,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.43",
"syn 2.0.48",
]
[[package]]
@ -328,7 +328,7 @@ dependencies = [
"proc-macro2",
"quote",
"strsim",
"syn 2.0.43",
"syn 2.0.48",
]
[[package]]
@ -339,7 +339,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5"
dependencies = [
"darling_core",
"quote",
"syn 2.0.43",
"syn 2.0.48",
]
[[package]]
@ -379,9 +379,9 @@ dependencies = [
[[package]]
name = "deranged"
version = "0.3.10"
version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8eb30d70a07a3b04884d2677f06bec33509dc67ca60d92949e5535352d3191dc"
checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
dependencies = [
"powerfmt",
"serde",
@ -417,7 +417,7 @@ checksum = "44600091ce205df4f8b661e98617d49c37b2dd609e449ec82b0fb5d7b33e2eeb"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.43",
"syn 2.0.48",
]
[[package]]
@ -437,7 +437,7 @@ checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.43",
"syn 2.0.48",
]
[[package]]
@ -458,7 +458,7 @@ dependencies = [
"darling",
"proc-macro2",
"quote",
"syn 2.0.43",
"syn 2.0.48",
]
[[package]]
@ -669,9 +669,9 @@ dependencies = [
[[package]]
name = "iana-time-zone"
version = "0.1.58"
version = "0.1.59"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20"
checksum = "b6a67363e2aa4443928ce15e57ebae94fd8949958fd1223c4cfc0cd473ad7539"
dependencies = [
"android_system_properties",
"core-foundation-sys",
@ -1019,18 +1019,18 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]]
name = "proc-macro2"
version = "1.0.71"
version = "1.0.76"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75cb1540fadbd5b8fbccc4dddad2734eba435053f725621c070711a14bb5f4b8"
checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.33"
version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
dependencies = [
"proc-macro2",
]
@ -1228,29 +1228,29 @@ checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0"
[[package]]
name = "serde"
version = "1.0.193"
version = "1.0.195"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89"
checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.193"
version = "1.0.195"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.43",
"syn 2.0.48",
]
[[package]]
name = "serde_json"
version = "1.0.108"
version = "1.0.111"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b"
checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4"
dependencies = [
"itoa",
"ryu",
@ -1302,7 +1302,7 @@ dependencies = [
"darling",
"proc-macro2",
"quote",
"syn 2.0.43",
"syn 2.0.48",
]
[[package]]
@ -1426,9 +1426,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.43"
version = "2.0.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee659fb5f3d355364e1f3e5bc10fb82068efbf824a1e9d1c9504244a6469ad53"
checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
dependencies = [
"proc-macro2",
"quote",
@ -1470,7 +1470,7 @@ checksum = "e7fbe9b594d6568a6a1443250a7e67d80b74e1e96f6d1715e1e21cc1888291d3"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.43",
"syn 2.0.48",
]
[[package]]
@ -1574,7 +1574,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.43",
"syn 2.0.48",
]
[[package]]
@ -1843,7 +1843,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.43",
"syn 2.0.48",
"wasm-bindgen-shared",
]
@ -1865,7 +1865,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.43",
"syn 2.0.48",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@ -1915,11 +1915,11 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-core"
version = "0.51.1"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64"
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
dependencies = [
"windows-targets 0.48.5",
"windows-targets 0.52.0",
]
[[package]]
@ -2047,9 +2047,9 @@ checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
[[package]]
name = "winnow"
version = "0.5.31"
version = "0.5.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97a4882e6b134d6c28953a387571f1acdd3496830d5e36c5e3a1075580ea641c"
checksum = "b7520bbdec7211caa7c4e682eb1fbe07abe20cee6756b6e00f537c82c11816aa"
dependencies = [
"memchr",
]

View file

@ -1,30 +1,30 @@
[keys.1x1]
icon = "@ph/play[alpha=0.8]"
icon = "@ph/play[invert|alpha=0.5|scale=2.0]"
mode.vibrate.pattern = "low"
mode.media__play_pause.icon.paused = "@ph/play"
mode.media__play_pause.icon.playing = "@ph/pause"
[keys.1x2]
icon = "@fad/shuffle"
icon = "@fad/shuffle[invert]"
mode.vibrate.pattern = "low"
mode.spotify__shuffle.icon.active = "@fad/shuffle[color=#58fc11]"
[keys.2x1]
icon = "@ph/timer"
icon = "@ph/timer[invert|scale=0.5]"
mode.vibrate.pattern = "low"
mode.timer.durations = ["60s", "5m", "10m", "15m", "30m"]
mode.timer.vibrate_when_finished = true
mode.timer.needy = true
[keys.3x3]
icon = "@fad/thunderbolt"
icon = "@fad/thunderbolt[invert]"
label = "Dock"
mode.vibrate.pattern = "low"
mode.home_assistant__switch.name = "switch.moritz_thunderbolt_dock"
mode.home_assistant__switch.icon.on = "@fad/thunderbolt[color=#58fc11]"
[keys.3x4]
icon = "@ph/computer-tower"
icon = "@ph/computer-tower[invert]"
label = "Tower PC"
mode.vibrate.pattern = "low"
mode.home_assistant__switch.name = "switch.mwin"

View file

@ -1,14 +1,55 @@
use color_eyre::{eyre::ContextCompat, Result};
use tiny_skia::Pixmap;
use tiny_skia::{ColorU8, Pixmap, PremultipliedColorU8};
use crate::model::image_filter::ImageFilter;
use crate::model::rgb::RGB8Wrapper;
pub fn apply_filter(original: &Pixmap, filter: &ImageFilter) -> Result<Pixmap> {
pub fn apply(original: &Pixmap, filter: &ImageFilter) -> Result<Pixmap> {
let mut result = if let Some(rect) = filter.crop {
original.clone_rect(*rect).wrap_err_with(|| format!("Invalid crop rect: {}", rect))?
} else {
original.clone()
};
// scale is handled in runner::graphics::render_key
// rotate
if let Some(color) = &filter.color {
apply_color(&mut result, color);
}
if filter.alpha != 1.0 {
apply_alpha(&mut result, filter.alpha);
}
// grayscale
if filter.invert {
apply_invert(&mut result);
}
Ok(result)
}
fn apply_alpha(pixmap: &mut Pixmap, alpha: f32) {
for p in pixmap.pixels_mut() {
let d = p.demultiply();
*p = ColorU8::from_rgba(d.red(), d.green(), d.blue(), (d.alpha() as f32 * alpha).round() as u8).premultiply();
}
}
fn apply_invert(pixmap: &mut Pixmap) {
for p in pixmap.pixels_mut() {
*p = PremultipliedColorU8::from_rgba(p.alpha() - p.red(), p.alpha() - p.green(), p.alpha() - p.blue(), p.alpha()).unwrap();
}
}
fn apply_color(pixmap: &mut Pixmap, color: &RGB8Wrapper) {
for p in pixmap.pixels_mut() {
let a = p.alpha();
if a > 0 {
*p = ColorU8::from_rgba(color.r, color.g, color.b, a).premultiply();
}
}
}

View file

@ -7,7 +7,6 @@ use color_eyre::Result;
use resvg::usvg::{TextRendering, TreeParsing, TreeTextToPath};
use tiny_skia::{Pixmap, Transform};
use crate::icons::filter::apply_filter;
use crate::model::config::{Config, IconFormat, IconPack};
use crate::model::icon_descriptor::{IconDescriptor, IconDescriptorSource};
use crate::model::IconMap;
@ -99,7 +98,7 @@ pub fn load_icons(
Entry::Vacant(v) => v.insert(read_image(config_directory, icon_packs_by_id, dpi, &fonts_db, descriptor.source.clone())?),
};
let pixmap = apply_filter(original_image, &descriptor.filter)?;
let pixmap = filter::apply(original_image, &descriptor.filter)?;
let scale = descriptor.filter.scale;
icons_by_descriptor.insert(descriptor, LoadedIcon { pixmap, scale });

View file

@ -15,7 +15,6 @@ pub struct ImageFilter {
pub clockwise_quarter_rotations: u8,
pub color: Option<RGB8Wrapper>,
pub alpha: f32,
pub blur: f32,
pub grayscale: bool,
pub invert: bool,
}
@ -36,7 +35,6 @@ const DEFAULT_IMAGE_FILTER: ImageFilter = ImageFilter {
crop: None,
color: None,
alpha: 1.0,
blur: 0.0,
grayscale: false,
invert: false,
};
@ -169,7 +167,6 @@ impl FromStr for ImageFilter {
)
}
"alpha" => result.alpha = parse_f32_filter_value(&filter_name, use_raw_value()?, "0..<1", Box::new(|v| (0.0..1.0).contains(v)))?,
"blur" => result.blur = parse_f32_filter_value(&filter_name, use_raw_value()?, "0..=10", Box::new(|v| (0.0..=10.0).contains(v)))?,
"grayscale" => result.grayscale = use_bool_value()?,
"invert" => result.invert = use_bool_value()?,
_ => return Err(ImageFilterFromStringError::UnknownFilter { name: filter_name }),
@ -226,14 +223,6 @@ impl Display for ImageFilter {
is_first = false;
}
if self.blur != DEFAULT_IMAGE_FILTER.blur {
if !is_first {
f.write_str("|")?
}
f.write_fmt(format_args!("blur={}", self.blur))?;
is_first = false;
}
if self.grayscale {
if !is_first {
f.write_str("|")?

View file

@ -1,7 +1,7 @@
use std::collections::HashMap;
use bytes::{BufMut, Bytes, BytesMut};
use tiny_skia::{Color, IntSize, Pixmap, PremultipliedColorU8};
use tiny_skia::{BlendMode, Color, FilterQuality, IntSize, Pixmap, PixmapPaint, PremultipliedColorU8, Transform};
use loupedeck_serial::util::Endianness;
@ -9,8 +9,8 @@ use crate::icons::LoadedIcon;
use crate::model::icon_descriptor::{IconDescriptor, IconDescriptorSource};
use crate::runner::state::Key;
pub fn render_key(key_size: (u16, u16), buffer_endianness: Endianness, icons: &HashMap<IconDescriptor, LoadedIcon>, state: Option<&Key>) -> Bytes {
let mut canvas = Pixmap::new(key_size.0 as u32, key_size.1 as u32).unwrap();
pub fn render_key(key_size: IntSize, buffer_endianness: Endianness, icons: &HashMap<IconDescriptor, LoadedIcon>, state: Option<&Key>) -> Bytes {
let mut canvas = Pixmap::new(key_size.width(), key_size.height()).unwrap();
if let Some(state) = state {
if state.icon.source != IconDescriptorSource::None {
@ -20,11 +20,24 @@ pub fn render_key(key_size: (u16, u16), buffer_endianness: Endianness, icons: &H
.scale_by(icon.scale)
.unwrap();
//canvas.draw_pixmap(0, 0, icon.pixmap.as_ref(), &PixmapPaint::default(), Transform::from_scale(icon.scale, icon.scale), None);
static PAINT: PixmapPaint = PixmapPaint {
opacity: 1.0,
blend_mode: BlendMode::SourceOver,
quality: FilterQuality::Bicubic,
};
canvas.draw_pixmap(
(((key_size.width() - scaled_size.width()) / 2) as f32 / icon.scale).round() as i32,
(((key_size.height() - scaled_size.height()) / 2) as f32 / icon.scale).round() as i32,
icon.pixmap.as_ref(),
&PAINT,
Transform::from_scale(icon.scale, icon.scale),
None,
);
}
} else {
canvas.fill(Color::BLACK);
}
canvas.fill(Color::WHITE);
convert_pixels_to_rgb565(canvas.pixels(), buffer_endianness).freeze()
}

View file

@ -2,14 +2,16 @@ use std::collections::HashMap;
use std::path::Path;
use std::sync::Arc;
use std::thread;
use std::time::Instant;
use color_eyre::eyre::{ContextCompat, WrapErr};
use color_eyre::Result;
use enum_map::EnumMap;
use enum_ordinalize::Ordinalize;
use flume::{Receiver, Sender};
use log::{debug, trace};
use log::{debug, info, trace};
use rgb::RGB8;
use tiny_skia::IntSize;
use loupedeck_serial::characteristics::{LoupedeckButton, LoupedeckDeviceKeyGridCharacteristics};
use loupedeck_serial::commands::VibrationPattern;
@ -28,18 +30,26 @@ mod state;
pub async fn start(config_directory: &Path, config: model::config::Config) -> Result<()> {
let config = Arc::new(config);
let device = LoupedeckDevice::discover()?
.first()
.wrap_err("No device connected.")?
.connect()
.wrap_err("Connecting to the device failed.")?;
info!("Discovering devices…");
let available_devices = LoupedeckDevice::discover()?;
let available_device = available_devices.first().wrap_err("No device connected.")?;
info!("Connecting to the device…");
let device = available_device.connect().wrap_err("Connecting to the device failed.")?;
info!("Connected");
let key_grid = &device.characteristics().key_grid;
let used_icon_descriptors = get_used_icon_descriptors(&config);
let icons = load_icons(config_directory, &config.icon_packs, used_icon_descriptors, key_grid.display.dpi)?;
device.set_brightness(0.0);
let start_time = Instant::now();
info!("Loading icons…");
let icons = load_icons(config_directory, &config.icon_packs, used_icon_descriptors, key_grid.display.dpi)?;
info!("Finished loading {} icon(s) in {}ms", icons.len(), start_time.elapsed().as_millis());
device.set_brightness(0.5);
device.vibrate(VibrationPattern::RiseFall);
let events_receiver = device.events();
@ -59,6 +69,7 @@ pub async fn start(config_directory: &Path, config: model::config::Config) -> Re
.spawn(move || do_io_work(cloned_config, icons, device, events_receiver, cloned_commands_sender, commands_receiver))
.wrap_err("Could not spawn the worker thread")?;
info!("Ready.");
io_worker_thread.join().unwrap();
Ok(())
@ -191,14 +202,6 @@ fn handle_event(context: &IoWorkerContext, commands_sender: &Sender<StateChangeC
page_id: context.state.active_key_page_id.clone(),
position,
};
let value = if context.state.get_key(&path).label.is_empty() {
"a".to_owned()
} else {
String::new()
};
commands_sender.send(StateChangeCommand::SetKeyLabel { path, value }).unwrap();
}
}
}
@ -303,7 +306,7 @@ fn draw_key(context: &IoWorkerContext, index: u8, key: Option<&Key>) {
let key_grid = &context.device.characteristics().key_grid;
let (x, y, w, h) = key_grid.get_local_key_rect_xywh(index).unwrap();
let p = render_key((w, h), key_grid.display.endianness, &context.icons, key);
let p = render_key(IntSize::from_wh(w as u32, h as u32).unwrap(), key_grid.display.endianness, &context.icons, key);
context.device.replace_framebuffer_area_raw(&key_grid.display, x, y, w, h, p).unwrap();
}