From d235795efd9f0fdfef7090ca94d451ea36d4646c Mon Sep 17 00:00:00 2001 From: bgkillas Date: Wed, 30 Oct 2024 09:39:23 -0400 Subject: [PATCH] use oklch instead of hsv for hue shifts --- noita-proxy/Cargo.lock | 25 +++---- noita-proxy/src/lib.rs | 26 +++---- noita-proxy/src/player_cosmetics.rs | 107 +++++++++++++++++++--------- 3 files changed, 100 insertions(+), 58 deletions(-) diff --git a/noita-proxy/Cargo.lock b/noita-proxy/Cargo.lock index 3fa6be17..7f49f14b 100644 --- a/noita-proxy/Cargo.lock +++ b/noita-proxy/Cargo.lock @@ -1271,9 +1271,9 @@ dependencies = [ [[package]] name = "glam" -version = "0.29.0" +version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28091a37a5d09b555cb6628fd954da299b536433834f5b8e59eba78e0cbbf8a" +checksum = "480c9417a5dc586fc0c0cb67891170e59cc11e9dc79ba1c11ddd2c56ca3f3b90" [[package]] name = "globset" @@ -1507,9 +1507,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" dependencies = [ "bytes", "futures-channel", @@ -2530,10 +2530,11 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fe68c2e9e1a1234e218683dbdf9f9dfcb094113c5ac2b938dfcb9bab4c4140b" +checksum = "e346e016eacfff12233c243718197ca12f148c84e1e84268a896699b41c71780" dependencies = [ + "cfg_aliases", "libc", "once_cell", "socket2", @@ -2680,9 +2681,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" -version = "0.12.8" +version = "0.12.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f713147fbe92361e52392c73b8c9e48c04c6625bce969ef54dc901e58e042a7b" +checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f" dependencies = [ "base64 0.22.1", "bytes", @@ -2992,18 +2993,18 @@ checksum = "d369a96f978623eb3dc28807c4852d6cc617fed53da5d3c400feff1ef34a714a" [[package]] name = "serde" -version = "1.0.213" +version = "1.0.214" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ea7893ff5e2466df8d720bb615088341b295f849602c6956047f8f80f0e9bc1" +checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.213" +version = "1.0.214" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e85ad2009c50b58e87caa8cd6dac16bdf511bbfb7af6c33df902396aa480fa5" +checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" dependencies = [ "proc-macro2", "quote", diff --git a/noita-proxy/src/lib.rs b/noita-proxy/src/lib.rs index 6f20e549..bf78025b 100644 --- a/noita-proxy/src/lib.rs +++ b/noita-proxy/src/lib.rs @@ -156,7 +156,7 @@ enum AppState { struct PlayerAppearance { player_color: PlayerColor, player_picker: PlayerPicker, - hue: f32, + hue: f64, cosmetics: (bool, bool, bool), } @@ -204,23 +204,23 @@ impl Default for AppSavedState { #[derive(Debug, Serialize, Deserialize, Decode, Encode, Copy, Clone)] pub struct PlayerColor { - player_main: [u8; 4], - player_alt: [u8; 4], - player_arm: [u8; 4], - player_cape: [u8; 4], - player_cape_edge: [u8; 4], - player_forearm: [u8; 4], + player_main: [f64; 4], + player_alt: [f64; 4], + player_arm: [f64; 4], + player_cape: [f64; 4], + player_cape_edge: [f64; 4], + player_forearm: [f64; 4], } impl Default for PlayerColor { fn default() -> Self { Self { - player_main: [155, 111, 154, 255], - player_alt: [127, 84, 118, 255], - player_arm: [89, 67, 84, 255], - player_cape: [118, 84, 127, 255], - player_cape_edge: [154, 111, 155, 255], - player_forearm: [158, 115, 154, 255], + player_main: [155.0, 111.0, 154.0, 255.0], + player_alt: [127.0, 84.0, 118.0, 255.0], + player_arm: [89.0, 67.0, 84.0, 255.0], + player_cape: [118.0, 84.0, 127.0, 255.0], + player_cape_edge: [154.0, 111.0, 155.0, 255.0], + player_forearm: [158.0, 115.0, 154.0, 255.0], } } } diff --git a/noita-proxy/src/player_cosmetics.rs b/noita-proxy/src/player_cosmetics.rs index 175e467b..9be67727 100644 --- a/noita-proxy/src/player_cosmetics.rs +++ b/noita-proxy/src/player_cosmetics.rs @@ -5,7 +5,6 @@ use bitcode::{Decode, Encode}; use eframe::egui; use eframe::egui::color_picker::{color_picker_color32, Alpha}; use eframe::egui::{Color32, TextureHandle, TextureOptions, Ui}; -use eframe::epaint::Hsva; use image::{Rgba, RgbaImage}; use std::ffi::OsString; use std::fs::{self, File}; @@ -51,19 +50,23 @@ pub fn replace_color(image: &mut RgbaImage, main: Rgba, alt: Rgba, arm: } } +fn to_u8(c: [f64; 4]) -> [u8; 4] { + [c[0] as u8, c[1] as u8, c[2] as u8, c[3] as u8] +} + pub fn make_player_image(image: &mut RgbaImage, colors: PlayerColor) { let target_main = Rgba::from([155, 111, 154, 255]); let target_alt = Rgba::from([127, 84, 118, 255]); let target_arm = Rgba::from([89, 67, 84, 255]); - let main = Rgba::from(colors.player_main); - let alt = Rgba::from(colors.player_alt); - let arm = Rgba::from(colors.player_arm); - let cape = Rgba::from(colors.player_cape); - let cape_edge = Rgba::from(colors.player_cape_edge); - let forearm = Rgba::from(colors.player_forearm); + let main = Rgba::from(to_u8(colors.player_main)); + let alt = Rgba::from(to_u8(colors.player_alt)); + let arm = Rgba::from(to_u8(colors.player_arm)); + let cape = Rgba::from(to_u8(colors.player_cape)); + let cape_edge = Rgba::from(to_u8(colors.player_cape_edge)); + let forearm = Rgba::from(to_u8(colors.player_forearm)); for (i, pixel) in image.pixels_mut().enumerate() { if *pixel == target_main { - *pixel = main; + *pixel = main } else if *pixel == target_alt { *pixel = alt } else if *pixel == target_arm { @@ -112,13 +115,51 @@ pub fn add_cosmetics( } } -pub fn shift_hue(diff: f32, color: &mut [u8; 4]) { - let rgb = Color32::from_rgb(color[0], color[1], color[2]); - let mut hsv = Hsva::from(rgb); - hsv.h += diff / 360.0; - hsv.h = hsv.h.fract(); - let rgb = hsv.to_srgb(); - *color = [rgb[0], rgb[1], rgb[2], 255]; +fn rgb_to_oklch(color: &mut [f64; 4]) { + let mut l = 0.4122214708 * color[0] + 0.5363325363 * color[1] + 0.0514459929 * color[2]; + let mut m = 0.2119034982 * color[0] + 0.6806995451 * color[1] + 0.1073969566 * color[2]; + let mut s = 0.0883024619 * color[0] + 0.2817188376 * color[1] + 0.6299787005 * color[2]; + + l = l.cbrt(); + m = m.cbrt(); + s = s.cbrt(); + + color[0] = 0.2104542553 * l + 0.7936177850 * m - 0.0040720468 * s; + color[1] = 1.9779984951 * l - 2.4285922050 * m + 0.4505937099 * s; + color[2] = 0.0259040371 * l + 0.7827717662 * m - 0.8086757660 * s; +} + +fn oklch_to_rgb(color: &mut [f64; 4]) { + let mut l = color[0] + 0.3963377774 * color[1] + 0.2158037573 * color[2]; + let mut m = color[0] - 0.1055613458 * color[1] - 0.0638541728 * color[2]; + let mut s = color[0] - 0.0894841775 * color[1] - 1.2914855480 * color[2]; + + l = l.powi(3); + m = m.powi(3); + s = s.powi(3); + + color[0] = 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s; + color[1] = -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s; + color[2] = -0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s; +} + +fn shift_hue_by(color: &mut [f64; 4], diff: f64) { + let tau = std::f64::consts::TAU; + let diff = tau * diff / 360.0; + let c = (color[1].powi(2) + color[2].powi(2)).sqrt(); + let hue = color[2].atan2(color[1]); + let mut new_hue = (hue + diff) % tau; + if new_hue.is_sign_negative() { + new_hue += tau; + } + color[1] = c * new_hue.cos(); + color[2] = c * new_hue.sin(); +} + +pub fn shift_hue(diff: f64, color: &mut [f64; 4]) { + rgb_to_oklch(color); + shift_hue_by(color, diff); + oklch_to_rgb(color); } pub fn player_skin_display_color_picker( @@ -149,10 +190,10 @@ pub fn player_skin_display_color_picker( } } -pub fn color_picker(ui: &mut Ui, color: &mut [u8; 4]) { - let mut rgb = Color32::from_rgb(color[0], color[1], color[2]); +pub fn color_picker(ui: &mut Ui, color: &mut [f64; 4]) { + let mut rgb = Color32::from_rgb(color[0] as u8, color[1] as u8, color[2] as u8); color_picker_color32(ui, &mut rgb, Alpha::Opaque); - *color = [rgb.r(), rgb.g(), rgb.b(), 255] + *color = [rgb.r() as f64, rgb.g() as f64, rgb.b() as f64, 255.0] } pub fn player_select_current_color_slot(ui: &mut Ui, app: &mut App) { @@ -258,30 +299,30 @@ pub fn create_player_png( let mut img = image::open(player_path).unwrap().into_rgba8(); replace_color( &mut img, - Rgba::from(rgb.player_main), - Rgba::from(rgb.player_alt), - Rgba::from(rgb.player_arm), + Rgba::from(to_u8(rgb.player_main)), + Rgba::from(to_u8(rgb.player_alt)), + Rgba::from(to_u8(rgb.player_arm)), ); let mut img_arrow = image::open(arrows_path).unwrap().into_rgba8(); replace_color( &mut img_arrow, - Rgba::from(rgb.player_main), - Rgba::from(rgb.player_alt), - Rgba::from(rgb.player_arm), + Rgba::from(to_u8(rgb.player_main)), + Rgba::from(to_u8(rgb.player_alt)), + Rgba::from(to_u8(rgb.player_arm)), ); let mut img_ping = image::open(ping_path).unwrap().into_rgba8(); replace_color( &mut img_ping, - Rgba::from(rgb.player_main), - Rgba::from(rgb.player_alt), - Rgba::from(rgb.player_arm), + Rgba::from(to_u8(rgb.player_main)), + Rgba::from(to_u8(rgb.player_alt)), + Rgba::from(to_u8(rgb.player_arm)), ); let mut img_cursor = image::open(cursor_path).unwrap().into_rgba8(); replace_color( &mut img_cursor, - Rgba::from(rgb.player_main), - Rgba::from(rgb.player_alt), - Rgba::from(rgb.player_arm), + Rgba::from(to_u8(rgb.player_main)), + Rgba::from(to_u8(rgb.player_alt)), + Rgba::from(to_u8(rgb.player_arm)), ); let path = tmp_path.join(format!("tmp/{}.png", id)); img.save(path).unwrap(); @@ -291,7 +332,7 @@ pub fn create_player_png( img_ping.save(path).unwrap(); let path = tmp_path.join(format!("tmp/{}_cursor.png", id)); img_cursor.save(path).unwrap(); - let img = create_arm(Rgba::from(rgb.player_forearm)); + let img = create_arm(Rgba::from(to_u8(rgb.player_forearm))); let path = tmp_path.join(format!("tmp/{}_arm.png", id)); img.save(path).unwrap(); edit_nth_line( @@ -301,10 +342,10 @@ pub fn create_player_png( .into_os_string(), vec![16, 16], vec![ - format!("cloth_color=\"0xFF{}\"", rgb_to_hex(rgb.player_cape)), + format!("cloth_color=\"0xFF{}\"", rgb_to_hex(to_u8(rgb.player_cape))), format!( "cloth_color_edge=\"0xFF{}\"", - rgb_to_hex(rgb.player_cape_edge) + rgb_to_hex(to_u8(rgb.player_cape_edge)) ), ], );