diff --git a/Cargo.lock b/Cargo.lock index 80cb8cb..9b9daf8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -33,12 +33,12 @@ dependencies = [ "anyhow", "config", "ctrlc", + "embed-resource", "futures", "grim-rs", - "image", - "image-compare", "libc", - "okmain", + "oklab", + "png 0.17.16", "reqwest", "rgb", "serde", @@ -426,7 +426,7 @@ dependencies = [ "serde-untagged", "serde_core", "serde_json", - "toml", + "toml 1.0.6+spec-1.1.0", "winnow", "yaml-rust2", ] @@ -680,6 +680,20 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "embed-resource" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d506610004cfc74a6f5ee7e8c632b355de5eca1f03ee5e5e0ec11b77d4eb3d61" +dependencies = [ + "cc", + "memchr", + "rustc_version", + "toml 0.8.23", + "vswhom", + "winreg", +] + [[package]] name = "encoding_rs" version = "0.8.35" @@ -1076,12 +1090,6 @@ dependencies = [ "hashbrown 0.15.5", ] -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - [[package]] name = "hermit-abi" version = "0.1.19" @@ -1343,18 +1351,6 @@ dependencies = [ "zune-jpeg", ] -[[package]] -name = "image-compare" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "176623a137baf75908084aff53578d6336cbd932bffaf12fabe26b343eb13338" -dependencies = [ - "image", - "itertools", - "rayon", - "thiserror 2.0.18", -] - [[package]] name = "image-webp" version = "0.2.4" @@ -1775,20 +1771,6 @@ dependencies = [ "rgb", ] -[[package]] -name = "okmain" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49e2519388a310524c60205a1321707d0e9790fa7da2d0a41cc627922cfd6933" -dependencies = [ - "fast-srgb8", - "oklab", - "rand 0.10.0", - "rand_xoshiro", - "rgb", - "snafu", -] - [[package]] name = "once_cell" version = "1.21.4" @@ -2057,7 +2039,7 @@ dependencies = [ "bytes", "getrandom 0.3.4", "lru-slab", - "rand 0.9.2", + "rand", "ring", "rustc-hash", "rustls", @@ -2105,16 +2087,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha", - "rand_core 0.9.5", -] - -[[package]] -name = "rand" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8" -dependencies = [ - "rand_core 0.10.0", + "rand_core", ] [[package]] @@ -2124,7 +2097,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.5", + "rand_core", ] [[package]] @@ -2136,21 +2109,6 @@ dependencies = [ "getrandom 0.3.4", ] -[[package]] -name = "rand_core" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" - -[[package]] -name = "rand_xoshiro" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f0b2cc7bfeef8f0320ca45f88b00157a03c67137022d59393614352d6bf4312" -dependencies = [ - "rand_core 0.10.0", -] - [[package]] name = "rav1e" version = "0.8.1" @@ -2178,7 +2136,7 @@ dependencies = [ "num-traits", "paste", "profiling", - "rand 0.9.2", + "rand", "rand_chacha", "simd_helpers", "thiserror 2.0.18", @@ -2323,6 +2281,15 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "1.1.4" @@ -2464,6 +2431,12 @@ dependencies = [ "libc", ] +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + [[package]] name = "serde" version = "1.0.228" @@ -2519,6 +2492,15 @@ dependencies = [ "zmij", ] +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + [[package]] name = "serde_spanned" version = "1.0.4" @@ -2582,27 +2564,6 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" -[[package]] -name = "snafu" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e84b3f4eacbf3a1ce05eac6763b4d629d60cbc94d632e4092c54ade71f1e1a2" -dependencies = [ - "snafu-derive", -] - -[[package]] -name = "snafu-derive" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1c97747dbf44bb1ca44a561ece23508e99cb592e862f22222dcf42f51d1e451" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "socket2" version = "0.6.3" @@ -2844,6 +2805,18 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_edit", +] + [[package]] name = "toml" version = "1.0.6+spec-1.1.0" @@ -2851,12 +2824,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "399b1124a3c9e16766831c6bba21e50192572cdd98706ea114f9502509686ffc" dependencies = [ "serde_core", - "serde_spanned", - "toml_datetime", + "serde_spanned 1.0.4", + "toml_datetime 1.0.0+spec-1.1.0", "toml_parser", "winnow", ] +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + [[package]] name = "toml_datetime" version = "1.0.0+spec-1.1.0" @@ -2866,6 +2848,20 @@ dependencies = [ "serde_core", ] +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_write", + "winnow", +] + [[package]] name = "toml_parser" version = "1.0.9+spec-1.1.0" @@ -2875,6 +2871,12 @@ dependencies = [ "winnow", ] +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + [[package]] name = "tower" version = "0.5.3" @@ -3045,6 +3047,26 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "vswhom" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" +dependencies = [ + "libc", + "vswhom-sys", +] + +[[package]] +name = "vswhom-sys" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb067e4cbd1ff067d1df46c9194b5de0e98efd2810bbc95c5d5e5f25a3231150" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "walkdir" version = "2.5.0" @@ -3353,6 +3375,15 @@ dependencies = [ "windows-targets 0.42.2", ] +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + [[package]] name = "windows-sys" version = "0.52.0" @@ -3404,6 +3435,21 @@ dependencies = [ "windows_x86_64_msvc 0.42.2", ] +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -3443,6 +3489,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -3461,6 +3513,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -3479,6 +3537,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -3509,6 +3573,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -3527,6 +3597,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -3545,6 +3621,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -3563,6 +3645,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -3584,6 +3672,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "winreg" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + [[package]] name = "wit-bindgen" version = "0.51.0" diff --git a/Cargo.toml b/Cargo.toml index 929730e..40fd3b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,21 +2,24 @@ name = "ambiligth" version = "0.1.0" edition = "2021" +build = "build.rs" [dependencies] anyhow = "1.0" config = "0.15.21" ctrlc = "3.5.2" futures = "0.3" -image = "0.25.10" -image-compare = "0.5.0" -okmain = "0.2.0" +oklab = "1.1.2" +png = "0.17" reqwest = { version = "0.13.2", features = ["json"] } rgb = "0.8" serde = { version = "1.0.228", features = ["derive"] } serde_json = "1.0.149" tokio = { version = "1.50.0", features = ["full"] } +[build-dependencies] +embed-resource = "2.3" + [target.'cfg(unix)'.dependencies] grim-rs = "0.1.6" libc = "0.2.183" diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..033bb63 --- /dev/null +++ b/build.rs @@ -0,0 +1,7 @@ +#[cfg(target_os = "windows")] +fn main() { + embed_resource::compile("resources.rc", embed_resource::NONE); +} + +#[cfg(not(target_os = "windows"))] +fn main() {} diff --git a/config.toml b/config.toml index 6e7dece..3a0a549 100644 --- a/config.toml +++ b/config.toml @@ -2,30 +2,30 @@ # The base URL of your Home Assistant instance. # Example: "http://192.168.1.100:8123" or "https://ha.yourdomain.com" -ha_url = "https://homeassistant.pvv.ntnu.no:8123" - +#ha_url = "https://homeassistant.pvv.ntnu.no:8123" +ha_url = "http://192.168.1.238:8123" # A Long-Lived Access Token from Home Assistant. # You can generate this in Home Assistant by going to your user profile. -ha_token = "" +ha_token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJiMWU1ZmU2MWNlMjM0YzNkYjg2OWY1NTMzZGY5NGFjOCIsImlhdCI6MTc3MzY4ODMzOSwiZXhwIjoyMDg5MDQ4MzM5fQ.5dRwDrWakUeQk_3tCwurnqnsv3GLuukUEE3IyWnbK1g" # Screenshot capture rate (FPS). How often to capture and analyze the screen. -screenshot_fps = 15 +screenshot_fps = 10 # Maximum light update rate (FPS). Lights only update when color actually changes. # This acts as a rate limiter to prevent spamming Home Assistant. -target_fps = 3 +target_fps = 2 # Color smoothing factor (0.0 to 1.0) # Higher values = slower, smoother transitions (0.0 = instant, 1.0 = no movement) -smoothing = 0.6 +smoothing = 0.0 -# Specific lights to control. If empty, all supported color lights are used. +# Specific lights to control. If empty, all supported color lights are used.https://docs.rs/oklab/1.1.2/oklab/ # Example: ["light.living_room", "light.bedroom"] -lights = ["light.fargelys"] +#lights = ["light.fargelys"] # Restore lights to their original state/color when the program exits. restore_on_exit = true # Minimum percentage of difference required between frames to trigger an update. # Helps prevent flickering on static screens due to non-deterministic color algorithm. -min_diff_percent = 10.0 +min_diff_percent = 0.1 diff --git a/flake.nix b/flake.nix index 6c6a903..96e4e48 100644 --- a/flake.nix +++ b/flake.nix @@ -57,7 +57,6 @@ rustfmt clippy rustPlatform.bindgenHook - pkgs.pkgsCross.mingwW64.windows.mingw_w64_headers dbus gtk3 glib diff --git a/icon.png b/icon.png new file mode 100644 index 0000000..d945206 Binary files /dev/null and b/icon.png differ diff --git a/src/eventloop.rs b/src/eventloop.rs index 34b7a39..01074fc 100644 --- a/src/eventloop.rs +++ b/src/eventloop.rs @@ -50,8 +50,20 @@ pub fn run_loop( match active.as_ref() { None => true, Some(active_screenshot) => { - let diff = current_arc.diff_percent(active_screenshot); - diff >= settings_screenshot.min_diff_percent + let active_color = active_screenshot.average_color( + settings_screenshot.brightness_boost, + settings_screenshot.saturation_boost, + ); + let _current_color = current_arc.average_color( + settings_screenshot.brightness_boost, + settings_screenshot.saturation_boost, + ); + let color_diff = current_arc.color_diff( + &active_color, + settings_screenshot.brightness_boost, + settings_screenshot.saturation_boost, + ); + color_diff >= settings_screenshot.min_diff_percent } } }; @@ -104,7 +116,10 @@ pub fn run_loop( }; if let Some(screenshot) = active_screenshot { - let color = screenshot.dominant_color(); + let color = screenshot.average_color( + settings.brightness_boost, + settings.saturation_boost, + ); let new_target = RGB::new(color.r as f32, color.g as f32, color.b as f32); { diff --git a/src/main.rs b/src/main.rs index a5daf13..7327b0a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,11 +4,28 @@ mod screenshot; mod settings; mod state; +use std::io::Cursor; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use tray_item::{IconSource, TrayItem}; +fn get_tray_icon() -> IconSource { + let icon_data = include_bytes!("../icon.png"); + let cursor = Cursor::new(&icon_data[..]); + let mut decoder = png::Decoder::new(cursor); + decoder.set_transformations(png::Transformations::normalize_to_color8()); + let mut reader = decoder.read_info().expect("Failed to read icon PNG"); + let mut buf: Vec = vec![0; reader.output_buffer_size()]; + let info = reader.next_frame(&mut buf).expect("Failed to decode icon PNG"); + + IconSource::Data { + data: buf, + width: info.width as i32, + height: info.height as i32, + } +} + fn main() { let rt = tokio::runtime::Runtime::new().unwrap(); let rt_handle = rt.handle().clone(); @@ -71,16 +88,10 @@ fn main() { ); }); - // Create the tray application - // On Linux: empty string works as a default/no icon - // On Windows: use IDI_APPLICATION (default app icon) to avoid resource issues - #[cfg(unix)] - let icon_source = IconSource::Resource(""); - #[cfg(windows)] - let icon_source = IconSource::Resource("#32512"); // IDI_APPLICATION + let icon_source = get_tray_icon(); let mut tray = TrayItem::new("Ambiligth", icon_source) - .expect("Failed to create tray icon. On Windows, this may require a valid icon resource."); + .expect("Failed to create tray icon"); let is_running_toggle = is_running.clone(); let restore_on_toggle = settings_arc.restore_on_exit; diff --git a/src/screenshot.rs b/src/screenshot.rs index 4018940..b49b7de 100644 --- a/src/screenshot.rs +++ b/src/screenshot.rs @@ -17,62 +17,78 @@ pub struct Screenshot { } impl Screenshot { - pub fn dominant_color(&self) -> rgb::Rgb { - let mut rgb_data = vec![0u8; self.data.len() * 3]; + pub fn average_color(&self, brightness_boost: f32, saturation_boost: f32) -> rgb::Rgb { + let mut sum_l = 0.0f32; + let mut sum_a = 0.0f32; + let mut sum_b = 0.0f32; - for (i, p) in self.data.iter().enumerate() { - let offset = i * 3; - rgb_data[offset] = p.r; - rgb_data[offset + 1] = p.g; - rgb_data[offset + 2] = p.b; + for pixel in &self.data { + if pixel.a == 0 { + continue; + } + + let r = pixel.r as f32 / 255.0; + let g = pixel.g as f32 / 255.0; + let b = pixel.b as f32 / 255.0; + + let linear_rgb = oklab::LinearRgb::new(r, g, b); + let oklab_color = oklab::linear_srgb_to_oklab(linear_rgb); + sum_l += oklab_color.l; + sum_a += oklab_color.a; + sum_b += oklab_color.b; } - let input = - okmain::InputImage::from_bytes(self.width as u16, self.height as u16, &rgb_data) - .expect("Failed to create okmain InputImage"); - let palette = okmain::colors(input); - palette - .into_iter() - .next() - .map(|c| rgb::Rgb { - r: c.r, - g: c.g, - b: c.b, - }) - .unwrap_or(rgb::Rgb { r: 0, g: 0, b: 0 }) + let count = self.data.len() as f32; + let avg_l = sum_l / count; + let avg_a = sum_a / count; + let avg_b = sum_b / count; + + let target_l = brightness_boost; + let boosted_l = (avg_l + target_l) / 2.0; + + let boosted_a = avg_a * saturation_boost; + let boosted_b = avg_b * saturation_boost; + + let oklab_avg = oklab::Oklab { + l: boosted_l, + a: boosted_a, + b: boosted_b, + }; + let linear_rgb = oklab::oklab_to_linear_srgb(oklab_avg); + + rgb::Rgb { + r: (linear_rgb.r * 255.0).clamp(0.0, 255.0) as u8, + g: (linear_rgb.g * 255.0).clamp(0.0, 255.0) as u8, + b: (linear_rgb.b * 255.0).clamp(0.0, 255.0) as u8, + } } - /// Compares this screenshot against another and returns the percentage of difference (0.0 to 100.0) - pub fn diff_percent(&self, other: &Screenshot) -> f32 { - if self.width != other.width || self.height != other.height { - return 100.0; - } + pub fn color_diff( + &self, + color: &rgb::Rgb, + brightness_boost: f32, + saturation_boost: f32, + ) -> f32 { + let avg_color = self.average_color(brightness_boost, saturation_boost); - let mut rgb_data1 = vec![0u8; self.data.len() * 3]; - let mut rgb_data2 = vec![0u8; other.data.len() * 3]; + let r1 = avg_color.r as f32 / 255.0; + let g1 = avg_color.g as f32 / 255.0; + let b1 = avg_color.b as f32 / 255.0; - for (i, (p1, p2)) in self.data.iter().zip(other.data.iter()).enumerate() { - let offset = i * 3; - rgb_data1[offset] = p1.r; - rgb_data1[offset + 1] = p1.g; - rgb_data1[offset + 2] = p1.b; + let r2 = color.r as f32 / 255.0; + let g2 = color.g as f32 / 255.0; + let b2 = color.b as f32 / 255.0; - rgb_data2[offset] = p2.r; - rgb_data2[offset + 1] = p2.g; - rgb_data2[offset + 2] = p2.b; - } + let oklab1 = oklab::linear_srgb_to_oklab(oklab::LinearRgb::new(r1, g1, b1)); + let oklab2 = oklab::linear_srgb_to_oklab(oklab::LinearRgb::new(r2, g2, b2)); - let img1 = image::RgbImage::from_vec(self.width as u32, self.height as u32, rgb_data1) - .expect("Failed to create image1 buffer"); - let img2 = image::RgbImage::from_vec(other.width as u32, other.height as u32, rgb_data2) - .expect("Failed to create image2 buffer"); + let delta_l = (oklab1.l - oklab2.l).abs(); + let delta_a = (oklab1.a - oklab2.a).abs(); + let delta_b = (oklab1.b - oklab2.b).abs(); - let algo = image_compare::Algorithm::MSSIMSimple; - if let Ok(res) = image_compare::rgb_similarity_structure(&algo, &img1, &img2) { - ((1.0 - res.score) * 100.0) as f32 - } else { - 100.0 - } + let delta_e = (delta_l * delta_l + delta_a * delta_a + delta_b * delta_b).sqrt(); + + (delta_e * 1000.0).clamp(0.0, 100.0) as f32 } } @@ -212,8 +228,8 @@ mod ffi { #![allow(non_snake_case, dead_code)] use super::{Pixel, ScreenResult, Screenshot}; use std::ffi::c_void; - use std::os::raw::{c_int, c_long, c_uint}; use std::mem::size_of; + use std::os::raw::{c_int, c_long, c_uint}; type PVOID = *mut c_void; type LPVOID = *mut c_void; diff --git a/src/settings.rs b/src/settings.rs index 9a1a4a3..cc66ceb 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -26,12 +26,26 @@ pub struct Settings { #[serde(default = "default_min_diff_percent")] pub min_diff_percent: f32, + + #[serde(default = "default_brightness_boost")] + pub brightness_boost: f32, + + #[serde(default = "default_saturation_boost")] + pub saturation_boost: f32, } fn default_min_diff_percent() -> f32 { 1.0 } +fn default_brightness_boost() -> f32 { + 0.5 +} + +fn default_saturation_boost() -> f32 { + 2.0 +} + fn default_restore_on_exit() -> bool { true }