diff --git a/OpenRA.Game/Graphics/PlayerColorRemap.cs b/OpenRA.Game/Graphics/PlayerColorRemap.cs index 714ff77314..1450e1ddea 100644 --- a/OpenRA.Game/Graphics/PlayerColorRemap.cs +++ b/OpenRA.Game/Graphics/PlayerColorRemap.cs @@ -18,43 +18,34 @@ namespace OpenRA.Graphics { public class PlayerColorRemap : IPaletteRemap { - Dictionary remapColors; + readonly int[] remapIndices; + readonly float hue; + readonly float saturation; - public static int GetRemapIndex(int[] ramp, int i) + public PlayerColorRemap(int[] remapIndices, float hue, float saturation) { - return ramp[i]; - } - - public PlayerColorRemap(int[] ramp, Color c, float rampFraction) - { - var h = c.GetHue() / 360.0f; - var s = c.GetSaturation(); - var l = c.GetBrightness(); - - // Increase luminosity if required to represent the full ramp - var rampRange = (byte)((1 - rampFraction) * l); - var c1 = Color.FromAhsl(h, s, Math.Max(rampRange, l)); - var c2 = Color.FromAhsl(h, s, (byte)Math.Max(0, l - rampRange)); - var baseIndex = ramp[0]; - var remapRamp = ramp.Select(r => r - ramp[0]); - var rampMaxIndex = ramp.Length - 1; - - // reversed remapping - if (ramp[0] > ramp[rampMaxIndex]) - { - baseIndex = ramp[rampMaxIndex]; - for (var i = rampMaxIndex; i > 0; i--) - remapRamp = ramp.Select(r => r - ramp[rampMaxIndex]); - } - - remapColors = remapRamp.Select((x, i) => (baseIndex + i, Exts.ColorLerp(x / (float)ramp.Length, c1, c2))) - .ToDictionary(u => u.Item1, u => u.Item2); + this.remapIndices = remapIndices; + this.hue = hue; + this.saturation = saturation; } public Color GetRemappedColor(Color original, int index) { - return remapColors.TryGetValue(index, out var c) - ? c : original; + if (!remapIndices.Contains(index)) + return original; + + // Color remapping is applied in a linear color space, so start + // by undoing the pre-multiplied alpha and gamma corrections + var (r, g, b) = original.ToLinear(); + + // Calculate the brightness (i.e HSV value) of the original colour + var value = Math.Max(Math.Max(r, g), b); + + // Construct the new RGB color + (r, g, b) = Color.HsvToRgb(hue, saturation, value); + + // Convert linear back to SRGB and pre-multiply by the alpha + return Color.FromLinear(original.A, r, g, b); } } } diff --git a/OpenRA.Game/Traits/Player/PlayerColorPalette.cs b/OpenRA.Game/Traits/Player/PlayerColorPalette.cs index bd7eb9bf4d..d3d0809553 100644 --- a/OpenRA.Game/Traits/Player/PlayerColorPalette.cs +++ b/OpenRA.Game/Traits/Player/PlayerColorPalette.cs @@ -29,9 +29,6 @@ namespace OpenRA.Traits [Desc("Remap these indices to player colors.")] public readonly int[] RemapIndex = { }; - [Desc("Luminosity range to span.")] - public readonly float Ramp = 0.05f; - [Desc("Allow palette modifiers to change the palette.")] public readonly bool AllowModifiers = true; @@ -49,7 +46,9 @@ namespace OpenRA.Traits public void LoadPlayerPalettes(WorldRenderer wr, string playerName, Color color, bool replaceExisting) { - var remap = new PlayerColorRemap(info.RemapIndex, color, info.Ramp); + color.ToAhsv(out _, out var h, out var s, out _); + + var remap = new PlayerColorRemap(info.RemapIndex, h, s); var pal = new ImmutablePalette(wr.Palette(info.BasePalette).Palette, remap); wr.AddPalette(info.BaseName + playerName, pal, info.AllowModifiers, replaceExisting); } diff --git a/OpenRA.Game/Traits/World/FixedColorPalette.cs b/OpenRA.Game/Traits/World/FixedColorPalette.cs index 5ead965d7c..25865a3908 100644 --- a/OpenRA.Game/Traits/World/FixedColorPalette.cs +++ b/OpenRA.Game/Traits/World/FixedColorPalette.cs @@ -31,9 +31,6 @@ namespace OpenRA.Traits [Desc("The fixed color to remap.")] public readonly Color Color; - [Desc("Luminosity range to span.")] - public readonly float Ramp = 0.05f; - [Desc("Allow palette modifiers to change the palette.")] public readonly bool AllowModifiers = true; @@ -51,7 +48,9 @@ namespace OpenRA.Traits public void LoadPalettes(WorldRenderer wr) { - var remap = new PlayerColorRemap(info.RemapIndex, info.Color, info.Ramp); + info.Color.ToAhsv(out _, out var h, out var s, out _); + + var remap = new PlayerColorRemap(info.RemapIndex, h, s); wr.AddPalette(info.Name, new ImmutablePalette(wr.Palette(info.Base).Palette, remap), info.AllowModifiers); } } diff --git a/OpenRA.Mods.Cnc/UtilityCommands/RemapShpCommand.cs b/OpenRA.Mods.Cnc/UtilityCommands/RemapShpCommand.cs index 5788aa0b87..8bdb7df3c5 100644 --- a/OpenRA.Mods.Cnc/UtilityCommands/RemapShpCommand.cs +++ b/OpenRA.Mods.Cnc/UtilityCommands/RemapShpCommand.cs @@ -54,8 +54,7 @@ namespace OpenRA.Mods.Cnc.UtilityCommands // the remap range is always 16 entries, but their location and order changes for (var i = 0; i < 16; i++) - remap[PlayerColorRemap.GetRemapIndex(srcRemapIndex, i)] - = PlayerColorRemap.GetRemapIndex(destRemapIndex, i); + remap[srcRemapIndex[i]] = destRemapIndex[i]; // map everything else to the best match based on channel-wise distance var srcPalette = new ImmutablePalette(args[1].Split(':')[1], new[] { 0 }, shadowIndex); diff --git a/OpenRA.Mods.Common/Traits/World/ColorPickerManager.cs b/OpenRA.Mods.Common/Traits/World/ColorPickerManager.cs index 44a6e4d3ef..aba7012da6 100644 --- a/OpenRA.Mods.Common/Traits/World/ColorPickerManager.cs +++ b/OpenRA.Mods.Common/Traits/World/ColorPickerManager.cs @@ -40,16 +40,19 @@ namespace OpenRA.Mods.Common.Traits "A dictionary of [faction name]: [actor name].")] public readonly Dictionary FactionPreviewActors = new Dictionary(); + [Desc("Remap these indices to player colors.")] public readonly int[] RemapIndices = { }; - public readonly float Ramp = 0.05f; + public Color Color { get; private set; } public void Update(WorldRenderer worldRenderer, Color color) { Color = color; + Color.ToAhsv(out _, out var h, out var s, out _); + var newPalette = new MutablePalette(worldRenderer.Palette(PaletteName).Palette); - newPalette.ApplyRemap(new PlayerColorRemap(RemapIndices, Color, Ramp)); + newPalette.ApplyRemap(new PlayerColorRemap(RemapIndices, h, s)); worldRenderer.ReplacePalette(PaletteName, newPalette); }