Merge pull request #20736 from smoogipoo/combo-colour-brightness-limit

Normalise combo colour brightness
This commit is contained in:
Dean Herbert 2022-11-11 23:18:41 +09:00 committed by GitHub
commit daae560ff7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 302 additions and 8 deletions

View File

@ -31,6 +31,8 @@ namespace osu.Game.Rulesets.Osu.Tests
{
config.BindWith(OsuSetting.BeatmapSkins, BeatmapSkins);
config.BindWith(OsuSetting.BeatmapColours, BeatmapColours);
config.SetValue(OsuSetting.ComboColourNormalisationAmount, 0f);
}
[TestCase(true, true)]

View File

@ -6,6 +6,7 @@
using System;
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
@ -13,6 +14,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Textures;
using osu.Framework.Testing;
using osu.Game.Audio;
using osu.Game.Configuration;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Legacy;
using osu.Game.Rulesets.Objects.Types;
@ -25,10 +27,20 @@ namespace osu.Game.Tests.Gameplay
[HeadlessTest]
public class TestSceneHitObjectAccentColour : OsuTestScene
{
[Resolved]
private OsuConfigManager config { get; set; }
private Container skinContainer;
[SetUp]
public void Setup() => Schedule(() => Child = skinContainer = new SkinProvidingContainer(new TestSkin()));
public void Setup()
{
Schedule(() =>
{
config.SetValue(OsuSetting.ComboColourNormalisationAmount, 0f);
Child = skinContainer = new SkinProvidingContainer(new TestSkin());
});
}
[Test]
public void TestChangeComboIndexBeforeLoad()

View File

@ -0,0 +1,23 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Bindables;
namespace osu.Game.Configuration
{
/// <summary>
/// A settings provider which generally sources from <see cref="OsuConfigManager"/> (global user settings)
/// but can allow overriding settings by caching more locally. For instance, in the editor.
/// </summary>
/// <remarks>
/// More settings can be moved into this interface as required.
/// </remarks>
[Cached]
public interface IGameplaySettings
{
IBindable<float> ComboColourNormalisationAmount { get; }
IBindable<float> PositionalHitsoundsLevel { get; }
}
}

View File

@ -5,6 +5,7 @@
using System;
using System.Diagnostics;
using osu.Framework.Bindables;
using osu.Framework.Configuration;
using osu.Framework.Configuration.Tracking;
using osu.Framework.Extensions;
@ -26,7 +27,7 @@ using osu.Game.Skinning;
namespace osu.Game.Configuration
{
[ExcludeFromDynamicCompile]
public class OsuConfigManager : IniConfigManager<OsuSetting>
public class OsuConfigManager : IniConfigManager<OsuSetting>, IGameplaySettings
{
public OsuConfigManager(Storage storage)
: base(storage)
@ -175,6 +176,8 @@ namespace osu.Game.Configuration
SetDefault(OsuSetting.EditorShowHitMarkers, true);
SetDefault(OsuSetting.LastProcessedMetadataId, -1);
SetDefault(OsuSetting.ComboColourNormalisationAmount, 0.2f, 0f, 1f, 0.01f);
}
protected override bool CheckLookupContainsPrivateInformation(OsuSetting lookup)
@ -275,6 +278,9 @@ namespace osu.Game.Configuration
public Func<Guid, string> LookupSkinName { private get; set; } = _ => @"unknown";
public Func<GlobalAction, LocalisableString> LookupKeyBindings { get; set; } = _ => @"unknown";
IBindable<float> IGameplaySettings.ComboColourNormalisationAmount => GetOriginalBindable<float>(OsuSetting.ComboColourNormalisationAmount);
IBindable<float> IGameplaySettings.PositionalHitsoundsLevel => GetOriginalBindable<float>(OsuSetting.PositionalHitsoundsLevel);
}
// IMPORTANT: These are used in user configuration files.
@ -362,11 +368,12 @@ namespace osu.Game.Configuration
GameplayDisableWinKey,
SeasonalBackgroundMode,
EditorWaveformOpacity,
EditorShowHitMarkers,
DiscordRichPresence,
AutomaticallyDownloadWhenSpectating,
ShowOnlineExplicitContent,
LastProcessedMetadataId,
SafeAreaConsiderations,
EditorShowHitMarkers
ComboColourNormalisationAmount,
}
}

View File

@ -0,0 +1,201 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using osuTK.Graphics;
namespace osu.Game.Graphics
{
public struct HSPAColour
{
private const float p_r = 0.299f;
private const float p_g = 0.587f;
private const float p_b = 0.114f;
/// <summary>
/// The hue.
/// </summary>
public float H;
/// <summary>
/// The saturation.
/// </summary>
public float S;
/// <summary>
/// The perceived brightness of this colour.
/// </summary>
public float P;
/// <summary>
/// The alpha.
/// </summary>
public float A;
public HSPAColour(float h, float s, float p, float a)
{
H = h;
S = s;
P = p;
A = a;
}
public HSPAColour(Color4 colour)
{
H = 0;
S = 0;
P = MathF.Sqrt(colour.R * colour.R * p_r + colour.G * colour.G * p_g + colour.B + colour.B * p_b);
A = colour.A;
if (colour.R == colour.G && colour.R == colour.B)
return;
if (colour.R >= colour.G && colour.R >= colour.B)
{
if (colour.B >= colour.G)
{
H = 6f / 6f - 1f / 6f * (colour.B - colour.G) / (colour.R - colour.G);
S = 1f - colour.G / colour.R;
}
else
{
H = 0f / 6f + 1f / 6f * (colour.G - colour.B) / (colour.R - colour.B);
S = 1f - colour.B / colour.R;
}
}
else if (colour.G >= colour.R && colour.G >= colour.B)
{
if (colour.R >= colour.B)
{
H = 2f / 6f - 1f / 6f * (colour.R - colour.B) / (colour.G - colour.B);
S = 1f - colour.B / colour.G;
}
else
{
H = 2f / 6f + 1f / 6f * (colour.B - colour.R) / (colour.G - colour.R);
S = 1f - colour.R / colour.G;
}
}
else
{
if (colour.G >= colour.R)
{
H = 4f / 6f - 1f / 6f * (colour.G - colour.R) / (colour.B - colour.R);
S = 1f - colour.R / colour.B;
}
else
{
H = 4f / 6f + 1f / 6f * (colour.R - colour.G) / (colour.B - colour.G);
S = 1f - colour.G / colour.B;
}
}
}
public Color4 ToColor4()
{
float minOverMax = 1f - S;
Color4 result = new Color4 { A = A };
float h = H;
if (minOverMax > 0f)
{
if (h < 1f / 6f)
{
h = 6f * (h - 0f / 6f);
float part = 1f + h * (1f / minOverMax - 1f);
result.B = P / MathF.Sqrt(p_r / minOverMax / minOverMax + p_g * part * part + p_b);
result.R = result.B / minOverMax;
result.G = result.B + h * (result.R - result.B);
}
else if (h < 2f / 6f)
{
h = 6f * (-h + 2f / 6f);
float part = 1f + h * (1f / minOverMax - 1f);
result.B = P / MathF.Sqrt(p_g / minOverMax / minOverMax + p_r * part * part + p_b);
result.G = result.B / minOverMax;
result.R = result.B + h * (result.G - result.B);
}
else if (h < 3f / 6f)
{
h = 6f * (h - 2f / 6f);
float part = 1f + h * (1f / minOverMax - 1f);
result.R = P / MathF.Sqrt(p_g / minOverMax / minOverMax + p_b * part * part + p_r);
result.G = result.R / minOverMax;
result.B = result.R + h * (result.G - result.R);
}
else if (h < 4f / 6f)
{
h = 6f * (-h + 4f / 6f);
float part = 1f + h * (1f / minOverMax - 1f);
result.R = P / MathF.Sqrt(p_b / minOverMax / minOverMax + p_g * part * part + p_r);
result.B = result.R / minOverMax;
result.G = result.R + h * (result.B - result.R);
}
else if (h < 5f / 6f)
{
h = 6f * (h - 4f / 6f);
float part = 1f + h * (1f / minOverMax - 1f);
result.G = P / MathF.Sqrt(p_b / minOverMax / minOverMax + p_r * part * part + p_g);
result.B = result.G / minOverMax;
result.R = result.G + h * (result.B - result.G);
}
else
{
h = 6f * (-h + 6f / 6f);
float part = 1f + h * (1f / minOverMax - 1f);
result.G = P / MathF.Sqrt(p_r / minOverMax / minOverMax + p_b * part * part + p_g);
result.R = result.G / minOverMax;
result.B = result.G + h * (result.R - result.G);
}
}
else
{
if (h < 1f / 6f)
{
h = 6f * (h - 0f / 6f);
result.R = MathF.Sqrt(P * P / (p_r + p_g * h * h));
result.G = result.R * h;
result.B = 0f;
}
else if (h < 2f / 6f)
{
h = 6f * (-h + 2f / 6f);
result.G = MathF.Sqrt(P * P / (p_g + p_r * h * h));
result.R = result.G * h;
result.B = 0f;
}
else if (h < 3f / 6f)
{
h = 6f * (h - 2f / 6f);
result.G = MathF.Sqrt(P * P / (p_g + p_b * h * h));
result.B = result.G * h;
result.R = 0f;
}
else if (h < 4f / 6f)
{
h = 6f * (-h + 4f / 6f);
result.B = MathF.Sqrt(P * P / (p_b + p_g * h * h));
result.G = result.B * h;
result.R = 0f;
}
else if (h < 5f / 6f)
{
h = 6f * (h - 4f / 6f);
result.B = MathF.Sqrt(P * P / (p_b + p_r * h * h));
result.R = result.B * h;
result.G = 0f;
}
else
{
h = 6f * (-h + 6f / 6f);
result.R = MathF.Sqrt(P * P / (p_r + p_b * h * h));
result.B = result.R * h;
result.G = 0f;
}
}
return result;
}
}
}

View File

@ -99,6 +99,11 @@ namespace osu.Game.Localisation
/// </summary>
public static LocalisableString StoryboardVideo => new TranslatableString(getKey(@"storyboard_video"), @"Storyboard / video");
/// <summary>
/// "Combo colour normalisation"
/// </summary>
public static LocalisableString ComboColourNormalisation => new TranslatableString(getKey(@"combo_colour_normalisation"), @"Combo colour normalisation");
/// <summary>
/// "Hit lighting"
/// </summary>

View File

@ -262,6 +262,7 @@ namespace osu.Game
dependencies.Cache(largeStore);
dependencies.CacheAs(LocalConfig);
dependencies.CacheAs<IGameplaySettings>(LocalConfig);
InitialiseFonts();

View File

@ -4,6 +4,7 @@
#nullable disable
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Game.Configuration;
@ -15,9 +16,13 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
{
protected override LocalisableString Header => GameplaySettingsStrings.BeatmapHeader;
private readonly BindableFloat comboColourNormalisation = new BindableFloat();
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
config.BindWith(OsuSetting.ComboColourNormalisationAmount, comboColourNormalisation);
Children = new Drawable[]
{
new SettingsCheckbox
@ -40,6 +45,12 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
LabelText = GraphicsSettingsStrings.StoryboardVideo,
Current = config.GetBindable<bool>(OsuSetting.ShowStoryboard)
},
new SettingsSlider<float>
{
LabelText = GraphicsSettingsStrings.ComboColourNormalisation,
Current = comboColourNormalisation,
DisplayAsPercentage = true,
}
};
}
}

View File

@ -15,8 +15,10 @@ using osu.Framework.Extensions.TypeExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Threading;
using osu.Framework.Utils;
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;
@ -127,7 +129,8 @@ namespace osu.Game.Rulesets.Objects.Drawables
private readonly BindableList<HitSampleInfo> samplesBindable = new BindableList<HitSampleInfo>();
private readonly Bindable<int> comboIndexBindable = new Bindable<int>();
private readonly Bindable<float> positionalHitsoundsLevel = new Bindable<float>();
private readonly IBindable<float> positionalHitsoundsLevel = new Bindable<float>();
private readonly IBindable<float> comboColourBrightness = new Bindable<float>();
private readonly Bindable<int> comboIndexWithOffsetsBindable = new Bindable<int>();
protected override bool RequiresChildrenUpdate => true;
@ -168,9 +171,10 @@ namespace osu.Game.Rulesets.Objects.Drawables
}
[BackgroundDependencyLoader]
private void load(OsuConfigManager config, ISkinSource skinSource)
private void load(IGameplaySettings gameplaySettings, ISkinSource skinSource)
{
config.BindWith(OsuSetting.PositionalHitsoundsLevel, positionalHitsoundsLevel);
positionalHitsoundsLevel.BindTo(gameplaySettings.PositionalHitsoundsLevel);
comboColourBrightness.BindTo(gameplaySettings.ComboColourNormalisationAmount);
// Explicit non-virtual function call in case a DrawableHitObject overrides AddInternal.
base.AddInternal(Samples = new PausableSkinnableSound());
@ -192,6 +196,8 @@ namespace osu.Game.Rulesets.Objects.Drawables
comboIndexBindable.BindValueChanged(_ => UpdateComboColour());
comboIndexWithOffsetsBindable.BindValueChanged(_ => UpdateComboColour(), true);
comboColourBrightness.BindValueChanged(_ => UpdateComboColour());
// Apply transforms
updateState(State.Value, true);
}
@ -511,7 +517,15 @@ 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.
if (comboColourBrightness.Value != 0)
{
colour = Interpolation.ValueAt(Math.Abs(comboColourBrightness.Value), colour, new HSPAColour(colour) { P = 0.6f }.ToColor4(), 0, 1);
}
AccentColour.Value = colour;
}
/// <summary>

View File

@ -58,7 +58,8 @@ namespace osu.Game.Screens.Edit
{
[Cached(typeof(IBeatSnapProvider))]
[Cached]
public class Editor : ScreenWithBeatmapBackground, IKeyBindingHandler<GlobalAction>, IKeyBindingHandler<PlatformAction>, IBeatSnapProvider, ISamplePlaybackDisabler, IBeatSyncProvider
public class Editor : ScreenWithBeatmapBackground, IKeyBindingHandler<GlobalAction>, IKeyBindingHandler<PlatformAction>, IBeatSnapProvider, ISamplePlaybackDisabler, IBeatSyncProvider,
IGameplaySettings
{
public override float BackgroundParallaxAmount => 0.1f;
@ -98,6 +99,9 @@ namespace osu.Game.Screens.Edit
[Resolved(canBeNull: true)]
private INotificationOverlay notifications { get; set; }
[Resolved]
private IGameplaySettings globalGameplaySettings { get; set; }
public readonly Bindable<EditorScreenMode> Mode = new Bindable<EditorScreenMode>();
public IBindable<bool> SamplePlaybackDisabled => samplePlaybackDisabled;
@ -1041,5 +1045,11 @@ namespace osu.Game.Screens.Edit
{
}
}
// Combo colour normalisation should not be applied in the editor.
IBindable<float> IGameplaySettings.ComboColourNormalisationAmount => new Bindable<float>();
// Arguable.
IBindable<float> IGameplaySettings.PositionalHitsoundsLevel => globalGameplaySettings.PositionalHitsoundsLevel;
}
}

View File

@ -12,6 +12,7 @@ namespace osu.Game.Screens.Play.PlayerSettings
{
private readonly PlayerSliderBar<double> dimSliderBar;
private readonly PlayerSliderBar<double> blurSliderBar;
private readonly PlayerSliderBar<float> comboColourNormalisationSliderBar;
private readonly PlayerCheckbox showStoryboardToggle;
private readonly PlayerCheckbox beatmapSkinsToggle;
private readonly PlayerCheckbox beatmapColorsToggle;
@ -34,6 +35,11 @@ namespace osu.Game.Screens.Play.PlayerSettings
showStoryboardToggle = new PlayerCheckbox { LabelText = GraphicsSettingsStrings.StoryboardVideo },
beatmapSkinsToggle = new PlayerCheckbox { LabelText = SkinSettingsStrings.BeatmapSkins },
beatmapColorsToggle = new PlayerCheckbox { LabelText = SkinSettingsStrings.BeatmapColours },
comboColourNormalisationSliderBar = new PlayerSliderBar<float>
{
LabelText = GraphicsSettingsStrings.ComboColourNormalisation,
DisplayAsPercentage = true,
},
};
}
@ -45,6 +51,7 @@ namespace osu.Game.Screens.Play.PlayerSettings
showStoryboardToggle.Current = config.GetBindable<bool>(OsuSetting.ShowStoryboard);
beatmapSkinsToggle.Current = config.GetBindable<bool>(OsuSetting.BeatmapSkins);
beatmapColorsToggle.Current = config.GetBindable<bool>(OsuSetting.BeatmapColours);
comboColourNormalisationSliderBar.Current = config.GetBindable<float>(OsuSetting.ComboColourNormalisationAmount);
}
}
}

View File

@ -334,6 +334,7 @@
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=GL/@EntryIndexedValue">GL</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=GLSL/@EntryIndexedValue">GLSL</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=HID/@EntryIndexedValue">HID</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=HSPA/@EntryIndexedValue">HSPA</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=HSV/@EntryIndexedValue">HSV</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=HTML/@EntryIndexedValue">HTML</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=HUD/@EntryIndexedValue">HUD</s:String>