From 9dc981b909019cf85838c882df38637163f0baca Mon Sep 17 00:00:00 2001 From: Moritz Ruth Date: Sat, 6 Jan 2024 03:18:35 +0100 Subject: [PATCH] commit --- Cargo.lock | 419 +++++++++++++++++++++++- deckster/Cargo.toml | 3 + deckster/src/icons/mod.rs | 125 +++++++ deckster/src/main.rs | 1 + deckster/src/model/config.rs | 17 +- deckster/src/model/icon_descriptor.rs | 4 +- deckster/src/model/image_filter.rs | 11 + deckster/src/runner/mod.rs | 1 + loupedeck_serial/src/characteristics.rs | 4 + 9 files changed, 575 insertions(+), 10 deletions(-) create mode 100644 deckster/src/icons/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 6f29796..176f217 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -128,6 +128,12 @@ version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" +[[package]] +name = "bit_field" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" + [[package]] name = "bitflags" version = "1.3.2" @@ -152,6 +158,12 @@ version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.5.0" @@ -253,6 +265,12 @@ dependencies = [ "tracing-error", ] +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + [[package]] name = "colorchoice" version = "1.0.0" @@ -271,12 +289,12 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75acbfb314aeb4f5210d379af45ed1ec2c98c7f1790bf57b8a4c562ac0c51b71" dependencies = [ - "fontdb", + "fontdb 0.15.0", "libm", "log", "rangemap", "rustc-hash", - "rustybuzz", + "rustybuzz 0.11.0", "self_cell", "swash", "sys-locale", @@ -295,6 +313,43 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fca89a0e215bab21874660c67903c5f143333cab1da83d041c7ded6053774751" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e3681d554572a651dda4186cd47240627c3d0114d45a95f6ad27f2f22e7548d" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3a430a770ebd84726f584a90ee7f020d28db52c6d02138900f22341f866d39c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "darling" version = "0.20.3" @@ -330,10 +385,17 @@ dependencies = [ "syn", ] +[[package]] +name = "data-url" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a" + [[package]] name = "deckster" version = "0.1.0" dependencies = [ + "bytemuck", "bytes", "clap", "color-eyre", @@ -343,9 +405,11 @@ dependencies = [ "env_logger", "flume", "humantime-serde", + "image", "log", "loupedeck_serial", "regex", + "resvg", "rgb", "serde", "serde_regex", @@ -367,6 +431,12 @@ dependencies = [ "serde", ] +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + [[package]] name = "enum-map" version = "3.0.0-beta.2" @@ -457,6 +527,22 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "exr" +version = "1.71.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "832a761f35ab3e6664babfbdc6cef35a4860e816ec3916dcfd0882954e98a8a8" +dependencies = [ + "bit_field", + "flume", + "half", + "lebe", + "miniz_oxide", + "rayon-core", + "smallvec", + "zune-inflate", +] + [[package]] name = "eyre" version = "0.6.11" @@ -486,6 +572,12 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" + [[package]] name = "flume" version = "0.11.0" @@ -510,7 +602,7 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "674e258f4b5d2dcd63888c01c68413c51f565e8af99d2f7701c7b81d79ef41c4" dependencies = [ - "roxmltree", + "roxmltree 0.18.1", ] [[package]] @@ -521,12 +613,26 @@ checksum = "020e203f177c0fb250fb19455a252e838d2bbbce1f80f25ecc42402aafa8cd38" dependencies = [ "fontconfig-parser", "log", - "memmap2", + "memmap2 0.8.0", "slotmap", "tinyvec", "ttf-parser 0.19.2", ] +[[package]] +name = "fontdb" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98b88c54a38407f7352dd2c4238830115a6377741098ffd1f997c813d0e088a6" +dependencies = [ + "fontconfig-parser", + "log", + "memmap2 0.9.3", + "slotmap", + "tinyvec", + "ttf-parser 0.20.0", +] + [[package]] name = "futures-core" version = "0.3.30" @@ -552,12 +658,31 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "gif" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80792593675e051cf94a4b111980da2ba60d4a83e43e0048c5693baab3977045" +dependencies = [ + "color_quant", + "weezl", +] + [[package]] name = "gimli" version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +[[package]] +name = "half" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b4af3693f1b705df946e9fe5631932443781d0aabb423b62fcd4d73f6d2fd0" +dependencies = [ + "crunchy", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -633,6 +758,31 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "image" +version = "0.24.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f3dfdbdd72063086ff443e297b61695500514b1e41095b6fb9a5ab48a70a711" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "exr", + "gif", + "jpeg-decoder", + "num-rational", + "num-traits", + "png", + "qoi", + "tiff", +] + +[[package]] +name = "imagesize" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "029d73f573d8e8d63e6d5020011d3255b28c3ba85d6cf870a07184ed23de9284" + [[package]] name = "indenter" version = "0.3.3" @@ -688,6 +838,15 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +[[package]] +name = "jpeg-decoder" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e" +dependencies = [ + "rayon", +] + [[package]] name = "js-sys" version = "0.3.66" @@ -697,12 +856,27 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "kurbo" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd85a5776cd9500c2e2059c8c76c3b01528566b7fcbaf8098b55a33fc298849b" +dependencies = [ + "arrayvec", +] + [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "lebe" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" + [[package]] name = "libc" version = "0.2.151" @@ -794,6 +968,15 @@ dependencies = [ "libc", ] +[[package]] +name = "memmap2" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45fd3a57831bf88bc63f8cebc0cf956116276e97fef3966103e96416209f7c92" +dependencies = [ + "libc", +] + [[package]] name = "miniz_oxide" version = "0.7.1" @@ -824,6 +1007,27 @@ dependencies = [ "libc", ] +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.17" @@ -887,6 +1091,12 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "pico-args" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + [[package]] name = "pin-project-lite" version = "0.2.13" @@ -927,6 +1137,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + [[package]] name = "quote" version = "1.0.33" @@ -942,6 +1161,32 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "977b1e897f9d764566891689e642653e5ed90c6895106acd005eb4c1d0203991" +[[package]] +name = "rayon" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "rctree" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b42e27ef78c35d3998403c1d26f3efd9e135d3e5121b0a4845cc5cc27547f4f" + [[package]] name = "redox_syscall" version = "0.4.1" @@ -980,6 +1225,23 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +[[package]] +name = "resvg" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cadccb3d99a9efb8e5e00c16fbb732cbe400db2ec7fc004697ee7d97d86cf1f4" +dependencies = [ + "gif", + "jpeg-decoder", + "log", + "pico-args", + "png", + "rgb", + "svgtypes", + "tiny-skia", + "usvg", +] + [[package]] name = "rgb" version = "0.8.37" @@ -998,6 +1260,12 @@ dependencies = [ "xmlparser", ] +[[package]] +name = "roxmltree" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cd14fd5e3b777a7422cca79358c57a8f6e3a703d9ac187448d0daf220c2407f" + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -1040,6 +1308,22 @@ dependencies = [ "unicode-script", ] +[[package]] +name = "rustybuzz" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0ae5692c5beaad6a9e22830deeed7874eae8a4e3ba4076fb48e12c56856222c" +dependencies = [ + "bitflags 2.4.1", + "bytemuck", + "smallvec", + "ttf-parser 0.20.0", + "unicode-bidi-mirroring", + "unicode-ccc", + "unicode-properties", + "unicode-script", +] + [[package]] name = "ryu" version = "1.0.16" @@ -1180,6 +1464,21 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +[[package]] +name = "simplecss" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a11be7c62927d9427e9f40f3444d5499d868648e2edbc4e2116de69e7ec0e89d" +dependencies = [ + "log", +] + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + [[package]] name = "slotmap" version = "1.0.7" @@ -1209,6 +1508,9 @@ name = "strict-num" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" +dependencies = [ + "float-cmp", +] [[package]] name = "strsim" @@ -1216,6 +1518,16 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "svgtypes" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e44e288cd960318917cbd540340968b90becc8bc81f171345d706e7a89d9d70" +dependencies = [ + "kurbo", + "siphasher", +] + [[package]] name = "swash" version = "0.1.8" @@ -1285,6 +1597,17 @@ dependencies = [ "once_cell", ] +[[package]] +name = "tiff" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d172b0f4d3fba17ba89811858b9d3d97f928aece846475bbda076ca46736211" +dependencies = [ + "flate2", + "jpeg-decoder", + "weezl", +] + [[package]] name = "time" version = "0.3.31" @@ -1523,6 +1846,73 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" +[[package]] +name = "unicode-vo" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" + +[[package]] +name = "usvg" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b0a51b72ab80ca511d126b77feeeb4fb1e972764653e61feac30adc161a756" +dependencies = [ + "base64", + "log", + "pico-args", + "usvg-parser", + "usvg-text-layout", + "usvg-tree", + "xmlwriter", +] + +[[package]] +name = "usvg-parser" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bd4e3c291f45d152929a31f0f6c819245e2921bfd01e7bd91201a9af39a2bdc" +dependencies = [ + "data-url", + "flate2", + "imagesize", + "kurbo", + "log", + "roxmltree 0.19.0", + "simplecss", + "siphasher", + "svgtypes", + "usvg-tree", +] + +[[package]] +name = "usvg-text-layout" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d383a3965de199d7f96d4e11a44dd859f46e86de7f3dca9a39bf82605da0a37c" +dependencies = [ + "fontdb 0.16.0", + "kurbo", + "log", + "rustybuzz 0.12.1", + "unicode-bidi", + "unicode-script", + "unicode-vo", + "usvg-tree", +] + +[[package]] +name = "usvg-tree" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee3d202ebdb97a6215604b8f5b4d6ef9024efd623cf2e373a6416ba976ec7d3" +dependencies = [ + "rctree", + "strict-num", + "svgtypes", + "tiny-skia-path", +] + [[package]] name = "utf8parse" version = "0.2.1" @@ -1611,6 +2001,12 @@ version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" +[[package]] +name = "weezl" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb" + [[package]] name = "winapi" version = "0.3.9" @@ -1789,6 +2185,12 @@ version = "0.13.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" +[[package]] +name = "xmlwriter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" + [[package]] name = "yazi" version = "0.1.6" @@ -1800,3 +2202,12 @@ name = "zeno" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd15f8e0dbb966fd9245e7498c7e9e5055d9e5c8b676b95bd67091cd11a1e697" + +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] diff --git a/deckster/Cargo.toml b/deckster/Cargo.toml index a489e64..7e510ba 100644 --- a/deckster/Cargo.toml +++ b/deckster/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" [dependencies] bytes = "1.5.0" +bytemuck = "1.14.0" clap = { version = "4.4.12", features = ["derive"] } color-eyre = "0.6.2" cosmic-text = "0.10.0" @@ -13,9 +14,11 @@ enum-ordinalize = "4.3.0" env_logger = "0.10.1" flume = "0.11.0" humantime-serde = "1.1.1" +image = "0.24.7" log = "0.4.20" loupedeck_serial = { path = "../loupedeck_serial" } regex = "1.10.2" +resvg = "0.37.0" rgb = "0.8.37" serde = { version = "1.0.193", features = ["derive"] } serde_regex = "1.1.0" diff --git a/deckster/src/icons/mod.rs b/deckster/src/icons/mod.rs new file mode 100644 index 0000000..38e7676 --- /dev/null +++ b/deckster/src/icons/mod.rs @@ -0,0 +1,125 @@ +use std::collections::hash_map::Entry; +use std::collections::{HashMap, HashSet}; +use std::path::Path; + +use color_eyre::eyre::{eyre, ContextCompat, WrapErr}; +use color_eyre::Result; +use image::RgbaImage; +use resvg::usvg::{TextRendering, TreeParsing, TreeTextToPath}; +use tiny_skia::{Pixmap, PixmapMut, Transform}; + +use crate::model::config::{IconFormat, IconPack}; +use crate::model::icon_descriptor::{IconDescriptor, IconDescriptorSource}; +use crate::model::image_filter::ImageFilter; + +pub fn load_icons( + icon_packs_by_id: HashMap, + descriptors: HashSet, + key_size: (u16, u16), + dpi: f32, +) -> Result> { + let mut original_images_by_source: HashMap = HashMap::new(); + let mut icons_by_descriptor: HashMap = HashMap::new(); + let mut fonts_db = resvg::usvg::fontdb::Database::new(); + fonts_db.load_system_fonts(); + + for descriptor in descriptors { + if descriptor.source == IconDescriptorSource::None { + continue; + } + + let image = match original_images_by_source.entry(descriptor.source.clone()) { + Entry::Occupied(o) => o.into_mut(), + Entry::Vacant(v) => v.insert(read_image(&icon_packs_by_id, dpi, &fonts_db, descriptor.source)?), + }; + + dbg!(image.height()); + } + + Ok(icons_by_descriptor) +} + +fn read_image( + icon_packs_by_id: &HashMap, + dpi: f32, + fonts_db: &resvg::usvg::fontdb::Database, + source: IconDescriptorSource, +) -> Result { + let path = match source { + IconDescriptorSource::None => unreachable!(), + IconDescriptorSource::Path(path) => path, + IconDescriptorSource::IconPack { pack_id, icon_id } => { + let pack = icon_packs_by_id.get(&pack_id).wrap_err_with(|| format!("Unknown icon pack: @{}", pack_id))?; + + let extension = match pack.format { + IconFormat::PNG => "png", + IconFormat::SVG => "svg", + }; + + pack.path.join(icon_id + "." + extension) + } + }; + + Ok(match path.extension() { + None => return Err(eyre!("Invalid icon path: {:?}", path)), + Some(extension) => match extension.to_string_lossy().as_ref() { + "png" => image::open(path.clone()) + .wrap_err_with(|| format!("Failed to open or decode the PNG file at {}", path.to_string_lossy()))? + .into_rgba8(), + "svg" => { + read_image_from_svg(&path, dpi, fonts_db).wrap_err_with(|| format!("Failed to open or decode the SVG file at {}", path.to_string_lossy()))? + } + extension => return Err(eyre!("Invalid file extension, only *.png and *.svg are allowed: {}", extension)), + }, + }) +} + +fn read_image_from_svg(path: &Path, dpi: f32, font_db: &resvg::usvg::fontdb::Database) -> Result { + let data = std::fs::read(path)?; + let mut tree = resvg::usvg::Tree::from_data( + &data, + &resvg::usvg::Options { + dpi, + font_family: "Inter".to_owned(), + font_size: 11.0, + text_rendering: TextRendering::OptimizeLegibility, + ..resvg::usvg::Options::default() + }, + )?; + + tree.convert_text(font_db); + + let render_tree = resvg::Tree::from_usvg(&tree); + let width = render_tree.size.width().ceil() as u32; + let height = render_tree.size.height().ceil() as u32; + let mut pixmap = Pixmap::new(width, height).unwrap(); + let mut pixmap_mut: PixmapMut = pixmap.as_mut(); + + render_tree.render(Transform::identity(), &mut pixmap_mut); + + Ok(RgbaImage::from_vec( + width, + height, + pixmap + .pixels_mut() + .iter() + .flat_map(|p| { + let p = p.demultiply(); + [p.red(), p.green(), p.blue(), p.alpha()] + }) + .collect(), + ) + .unwrap()) +} + +fn apply_filter(original: &RgbaImage, filter: &ImageFilter) -> RgbaImage { + let mut result = original.clone(); + + if filter.alpha != 1.0 { + for pixel in result.pixels_mut() { + pixel.0[3] = (pixel.0[3] as f32 * filter.alpha).floor() as u8; + } + } + + result +} diff --git a/deckster/src/main.rs b/deckster/src/main.rs index 65e8e3b..7c48ffb 100644 --- a/deckster/src/main.rs +++ b/deckster/src/main.rs @@ -9,6 +9,7 @@ use walkdir::WalkDir; use crate::model::config::WithFallbackId; +mod icons; mod model; mod runner; diff --git a/deckster/src/model/config.rs b/deckster/src/model/config.rs index 7549d6e..7318e6c 100644 --- a/deckster/src/model/config.rs +++ b/deckster/src/model/config.rs @@ -1,9 +1,10 @@ use std::collections::HashMap; +use std::path::PathBuf; use color_eyre::{eyre::eyre, Result}; use enum_map::EnumMap; use rgb::RGB8; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use crate::model; use crate::model::image_filter::ImageFilter; @@ -12,7 +13,7 @@ use crate::model::ButtonPosition; #[derive(Debug, Deserialize)] pub struct File { - pub icon_packs: Vec, + pub icon_packs: HashMap, #[serde(default = "inactive_button_color_default")] pub inactive_button_color: SerializableRGB8, #[serde(default = "active_button_color_default")] @@ -31,7 +32,7 @@ pub struct WithFallbackId { pub struct Config { pub key_pages_by_id: HashMap, pub knob_pages_by_id: HashMap, - pub icon_packs: Vec, + pub icon_packs: HashMap, pub inactive_button_color: SerializableRGB8, pub active_button_color: SerializableRGB8, pub buttons: EnumMap, @@ -58,10 +59,18 @@ pub struct InitialConfig { pub knob_page: String, } +#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum IconFormat { + PNG, + SVG, +} + #[derive(Debug, Deserialize)] pub struct IconPack { pub id: String, - pub path: String, + pub path: PathBuf, + pub format: IconFormat, pub global_filter: Option, } diff --git a/deckster/src/model/icon_descriptor.rs b/deckster/src/model/icon_descriptor.rs index be16b73..62cc68d 100644 --- a/deckster/src/model/icon_descriptor.rs +++ b/deckster/src/model/icon_descriptor.rs @@ -10,7 +10,7 @@ use crate::model::image_filter::{ImageFilter, ImageFilterFromStringError}; #[derive(Debug, Default, PartialEq, Clone, DeserializeFromStr)] pub struct IconDescriptorString(pub IconDescriptor); -#[derive(Debug, Default, PartialEq, Clone, Serialize, Deserialize)] +#[derive(Debug, Default, Eq, PartialEq, Hash, Clone, Serialize, Deserialize)] pub struct IconDescriptor { pub source: IconDescriptorSource, pub filter: Option, @@ -61,7 +61,7 @@ impl FromStr for IconDescriptorString { } } -#[derive(Debug, Default, Eq, PartialEq, Clone, Serialize, Deserialize)] +#[derive(Debug, Default, Eq, PartialEq, Hash, Clone, Serialize, Deserialize)] pub enum IconDescriptorSource { #[default] None, diff --git a/deckster/src/model/image_filter.rs b/deckster/src/model/image_filter.rs index 7010227..036a127 100644 --- a/deckster/src/model/image_filter.rs +++ b/deckster/src/model/image_filter.rs @@ -1,4 +1,5 @@ use std::fmt::{Display, Formatter}; +use std::hash::{Hash, Hasher}; use std::str::FromStr; use rgb::RGB8; @@ -22,6 +23,16 @@ pub struct ImageFilter { pub invert: bool, } +impl Eq for ImageFilter { + fn assert_receiver_is_total_eq(&self) {} +} + +impl Hash for ImageFilter { + fn hash(&self, hasher: &mut H) { + self.to_string().hash(hasher) + } +} + const DEFAULT_IMAGE_FILTER: ImageFilter = ImageFilter { crop_original: None, scale: 1.0, diff --git a/deckster/src/runner/mod.rs b/deckster/src/runner/mod.rs index b84f54d..f2a6207 100644 --- a/deckster/src/runner/mod.rs +++ b/deckster/src/runner/mod.rs @@ -32,6 +32,7 @@ pub async fn start(config: model::config::Config) -> Result<()> { .connect() .wrap_err("Connecting to the device failed.")?; + device.set_brightness(0.0); device.vibrate(VibrationPattern::RiseFall); let events_receiver = device.events(); diff --git a/loupedeck_serial/src/characteristics.rs b/loupedeck_serial/src/characteristics.rs index 03a9ab4..8e9585a 100644 --- a/loupedeck_serial/src/characteristics.rs +++ b/loupedeck_serial/src/characteristics.rs @@ -32,6 +32,7 @@ pub enum LoupedeckButton { pub struct LoupedeckDeviceDisplayConfiguration { pub id: u8, pub name: &'static str, + pub dpi: f32, pub width: u16, pub height: u16, pub local_offset_x: u16, @@ -149,6 +150,7 @@ static LOUPEDECK_LIVE_CHARACTERISTIC: LoupedeckDeviceCharacteristics = Loupedeck display: LoupedeckDeviceDisplayConfiguration { id: 0x4d, name: "center", + dpi: 142.875, width: 360, height: 270, local_offset_x: 60, @@ -162,6 +164,7 @@ static LOUPEDECK_LIVE_CHARACTERISTIC: LoupedeckDeviceCharacteristics = Loupedeck LoupedeckDeviceDisplayConfiguration { id: 0x4d, name: "left", + dpi: 142.875, width: 60, height: 270, local_offset_x: 0, @@ -173,6 +176,7 @@ static LOUPEDECK_LIVE_CHARACTERISTIC: LoupedeckDeviceCharacteristics = Loupedeck LoupedeckDeviceDisplayConfiguration { id: 0x4d, name: "right", + dpi: 142.875, width: 60, height: 270, local_offset_x: 420,