From 4149235e63173f374a80a7235411ef72588c455b Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 5 Oct 2022 17:50:36 +0900 Subject: [PATCH] Limit minimum brightness of combo colours --- osu.Game/Configuration/OsuConfigManager.cs | 4 +- osu.Game/Graphics/OsuColour.cs | 167 ++++++++++++++++++ .../Sections/Gameplay/BeatmapSettings.cs | 6 + .../Objects/Drawables/DrawableHitObject.cs | 14 +- 4 files changed, 189 insertions(+), 2 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index e3bfb6b1e9..b433d81f31 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -175,6 +175,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.EditorWaveformOpacity, 0.25f); SetDefault(OsuSetting.LastProcessedMetadataId, -1); + SetDefault(OsuSetting.ComboColourBrightness, 0.7f, 0f, 1f); } protected override bool CheckLookupContainsPrivateInformation(OsuSetting lookup) @@ -370,6 +371,7 @@ namespace osu.Game.Configuration DiscordRichPresence, AutomaticallyDownloadWhenSpectating, ShowOnlineExplicitContent, - LastProcessedMetadataId + LastProcessedMetadataId, + ComboColourBrightness } } diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs index 91161d5c71..bd0604bb76 100644 --- a/osu.Game/Graphics/OsuColour.cs +++ b/osu.Game/Graphics/OsuColour.cs @@ -229,6 +229,173 @@ namespace osu.Game.Graphics return Gray(brightness > 0.5f ? 0.2f : 0.9f); } + /// + /// Converts RGBA to HSPA. + /// + public static Color4 ToHSPA(Color4 colour) + { + const float p_r = 0.299f; + const float p_g = 0.587f; + const float p_b = 0.114f; + + Color4 result = new Color4 + { + A = colour.A, + B = MathF.Sqrt(colour.R * colour.R * p_r + colour.G * colour.G * p_g + colour.B + colour.B * p_b) + }; + + if (colour.R == colour.G && colour.R == colour.B) + return result; + + if (colour.R >= colour.G && colour.R >= colour.B) + { + if (colour.B >= colour.G) + { + result.R = 6f / 6f - 1f / 6f * (colour.B - colour.G) / (colour.R - colour.G); + result.G = 1f - colour.G / colour.R; + } + else + { + result.R = 0f / 6f + 1f / 6f * (colour.G - colour.B) / (colour.R - colour.B); + result.G = 1f - colour.B / colour.R; + } + } + else if (colour.G >= colour.R && colour.G >= colour.B) + { + if (colour.R >= colour.B) + { + result.R = 2f / 6f - 1f / 6f * (colour.R - colour.B) / (colour.G - colour.B); + result.G = 1f - colour.B / colour.G; + } + else + { + result.R = 2f / 6f + 1f / 6f * (colour.B - colour.R) / (colour.G - colour.R); + result.G = 1f - colour.R / colour.G; + } + } + else + { + if (colour.G >= colour.R) + { + result.R = 4f / 6f - 1f / 6f * (colour.G - colour.R) / (colour.B - colour.R); + result.G = 1f - colour.R / colour.B; + } + else + { + result.R = 4f / 6f + 1f / 6f * (colour.R - colour.G) / (colour.B - colour.G); + result.G = 1f - colour.G / colour.B; + } + } + + return result; + } + + public static Color4 FromHSPA(Color4 colour) + { + const float p_r = 0.299f; + const float p_g = 0.587f; + const float p_b = 0.114f; + + float minOverMax = 1f - colour.G; + + Color4 result = new Color4 { A = colour.A }; + + if (minOverMax > 0f) + { + float part = 1f + colour.R * (1f / minOverMax - 1f); + + if (colour.R < 1f / 6f) + { + colour.R = 6f * (colour.R - 0f / 6f); + result.B = colour.B / MathF.Sqrt(p_r / minOverMax / minOverMax + p_g * part * part + p_b); + result.R = result.B / minOverMax; + result.G = result.B + colour.R * (result.R - result.B); + } + else if (colour.R < 2f / 6f) + { + colour.R = 6f * (-colour.R + 2f / 6f); + result.B = colour.B / MathF.Sqrt(p_g / minOverMax / minOverMax + p_r * part * part + p_b); + result.G = result.B / minOverMax; + result.R = result.B + colour.R * (result.R - result.B); + } + else if (colour.R < 3f / 6f) + { + colour.R = 6f * (colour.R - 2f / 6f); + result.R = colour.B / MathF.Sqrt(p_g / minOverMax / minOverMax + p_b * part * part + p_r); + result.G = result.R / minOverMax; + result.B = result.R + colour.R * (result.G - result.R); + } + else if (colour.R < 4f / 6f) + { + colour.R = 6f * (-colour.R + 4f / 6f); + result.R = colour.B / MathF.Sqrt(p_b / minOverMax / minOverMax + p_g * part * part + p_r); + result.B = result.R / minOverMax; + result.G = result.R + colour.R * (result.B - result.R); + } + else if (colour.R < 5f / 6f) + { + colour.R = 6f * (colour.R - 4f / 6f); + result.G = colour.B / MathF.Sqrt(p_b / minOverMax / minOverMax + p_r * part * part + p_g); + result.B = result.G / minOverMax; + result.R = result.G + colour.R * (result.B - result.G); + } + else + { + colour.R = 6f * (-colour.R + 6f / 6f); + result.G = colour.B / MathF.Sqrt(p_r / minOverMax / minOverMax + p_b * part * part + p_g); + result.R = result.G / minOverMax; + result.B = result.G + colour.R * (result.R - result.G); + } + } + else + { + if (colour.R < 1f / 6f) + { + colour.R = 6f * (colour.R - 0f / 6f); + result.R = MathF.Sqrt(colour.B * colour.B / (p_r + p_g * colour.R * colour.R)); + result.G = result.R * colour.R; + result.B = 0f; + } + else if (colour.R < 2f / 6f) + { + colour.R = 6f * (-colour.R + 2f / 6f); + result.G = MathF.Sqrt(colour.B * colour.B / (p_g + p_r * colour.R * colour.R)); + result.R = result.G * colour.R; + result.B = 0f; + } + else if (colour.R < 3f / 6f) + { + colour.R = 6f * (colour.R - 2f / 6f); + result.G = MathF.Sqrt(colour.B * colour.B / (p_g + p_b * colour.R * colour.R)); + result.B = result.G * colour.R; + result.R = 0f; + } + else if (colour.R < 4f / 6f) + { + colour.R = 6f * (-colour.R + 4f / 6f); + result.B = MathF.Sqrt(colour.B * colour.B / (p_b + p_g * colour.R * colour.R)); + result.G = result.B * colour.R; + result.R = 0f; + } + else if (colour.R < 5f / 6f) + { + colour.R = 6f * (colour.R - 4f / 6f); + result.B = MathF.Sqrt(colour.B * colour.B / (p_b + p_r * colour.R * colour.R)); + result.R = result.B * colour.R; + result.G = 0f; + } + else + { + colour.R = 6f * (-colour.R + 6f / 6f); + result.R = MathF.Sqrt(colour.B * colour.B / (p_r + p_b * colour.R * colour.R)); + result.B = result.R * colour.R; + result.G = 0f; + } + } + + return result; + } + public readonly Color4 TeamColourRed = Color4Extensions.FromHex("#AA1414"); public readonly Color4 TeamColourBlue = Color4Extensions.FromHex("#1462AA"); diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/BeatmapSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/BeatmapSettings.cs index becb7aa80f..16b7b74f38 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/BeatmapSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/BeatmapSettings.cs @@ -40,6 +40,12 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay LabelText = GraphicsSettingsStrings.StoryboardVideo, Current = config.GetBindable(OsuSetting.ShowStoryboard) }, + new SettingsSlider + { + LabelText = "Combo colour brightness", + Current = config.GetBindable(OsuSetting.ComboColourBrightness), + DisplayAsPercentage = true + } }; } } diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index dec68a6c22..0ef15ed5c2 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -17,6 +17,7 @@ using osu.Framework.Graphics.Primitives; using osu.Framework.Threading; using osu.Game.Audio; using osu.Game.Configuration; +using osu.Game.Graphics; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Pooling; using osu.Game.Rulesets.Objects.Types; @@ -128,6 +129,7 @@ namespace osu.Game.Rulesets.Objects.Drawables private readonly Bindable comboIndexBindable = new Bindable(); private readonly Bindable positionalHitsoundsLevel = new Bindable(); + private readonly Bindable comboColourBrightness = new Bindable(); private readonly Bindable comboIndexWithOffsetsBindable = new Bindable(); protected override bool RequiresChildrenUpdate => true; @@ -171,6 +173,7 @@ namespace osu.Game.Rulesets.Objects.Drawables private void load(OsuConfigManager config, ISkinSource skinSource) { config.BindWith(OsuSetting.PositionalHitsoundsLevel, positionalHitsoundsLevel); + config.BindWith(OsuSetting.ComboColourBrightness, comboColourBrightness); // Explicit non-virtual function call in case a DrawableHitObject overrides AddInternal. base.AddInternal(Samples = new PausableSkinnableSound()); @@ -192,6 +195,8 @@ namespace osu.Game.Rulesets.Objects.Drawables comboIndexBindable.BindValueChanged(_ => UpdateComboColour()); comboIndexWithOffsetsBindable.BindValueChanged(_ => UpdateComboColour(), true); + comboColourBrightness.BindValueChanged(_ => UpdateComboColour()); + // Apply transforms updateState(State.Value, true); } @@ -519,7 +524,14 @@ namespace osu.Game.Rulesets.Objects.Drawables { if (!(HitObject is IHasComboInformation combo)) return; - AccentColour.Value = combo.GetComboColour(CurrentSkin); + Color4 colour = combo.GetComboColour(CurrentSkin); + + // Normalise the combo colour to the given brightness level. + colour = OsuColour.ToHSPA(colour); + colour.B = comboColourBrightness.Value; + colour = OsuColour.FromHSPA(colour); + + AccentColour.Value = colour; } ///