diff --git a/Robust.Shared.Tests/Utility/ColorExtensionsTest.cs b/Robust.Shared.Tests/Utility/ColorExtensionsTest.cs
new file mode 100644
index 00000000000..2ae74f3157b
--- /dev/null
+++ b/Robust.Shared.Tests/Utility/ColorExtensionsTest.cs
@@ -0,0 +1,74 @@
+using NUnit.Framework;
+using Robust.Shared.Maths;
+using Robust.Shared.Utility;
+
+namespace Robust.Shared.Tests.Utility;
+
+[Parallelizable(ParallelScope.All)]
+[TestOf(typeof(ColorExtensions))]
+internal sealed class ColorExtensionsTest
+{
+ [Test]
+ public void TestAnalogousPalette()
+ {
+ var palette = ColorExtensions.GetAnalogousComplementaries(Color.Red);
+
+ using (Assert.EnterMultipleScope())
+ {
+ Assert.That(palette, Has.Length.EqualTo(3));
+ Assert.That(MathHelper.CloseToPercent(palette[0], Color.Red));
+
+ Assert.That(Color.ToHsl(palette[0]).X, Is.Zero);
+ Assert.That(MathHelper.CloseToPercent(Color.ToHsl(palette[1]).X, 30f / 360f));
+ Assert.That(MathHelper.CloseToPercent(Color.ToHsl(palette[2]).X, 1f - 30f / 360f));
+ }
+ }
+
+ [Test]
+ public void TestTriadicPalette()
+ {
+ var palette = ColorExtensions.GetTriadicComplementaries(Color.Red);
+
+ using (Assert.EnterMultipleScope())
+ {
+ Assert.That(palette, Has.Length.EqualTo(3));
+ Assert.That(MathHelper.CloseToPercent(palette[0], Color.Red));
+
+ Assert.That(Color.ToHsl(palette[0]).X, Is.Zero);
+ Assert.That(MathHelper.CloseToPercent(Color.ToHsl(palette[1]).X, 120f / 360f));
+ Assert.That(MathHelper.CloseToPercent(Color.ToHsl(palette[2]).X, 1f - 120f / 360f));
+ }
+ }
+
+ [Test]
+ public void TestSplitComplementaryPalette()
+ {
+ var palette = ColorExtensions.GetSplitComplementaries(Color.Red);
+
+ using (Assert.EnterMultipleScope())
+ {
+ Assert.That(palette, Has.Length.EqualTo(3));
+ Assert.That(MathHelper.CloseToPercent(palette[0], Color.Red));
+
+ Assert.That(Color.ToHsl(palette[0]).X, Is.Zero);
+ Assert.That(MathHelper.CloseToPercent(Color.ToHsl(palette[1]).X, 150f / 360f));
+ Assert.That(MathHelper.CloseToPercent(Color.ToHsl(palette[2]).X, 1f - 150f / 360f));
+ }
+ }
+
+ [Test]
+ public void TestComplementaryPalette()
+ {
+ var palette = ColorExtensions.GetOneComplementary(Color.Red);
+
+ using (Assert.EnterMultipleScope())
+ {
+ Assert.That(palette, Has.Length.EqualTo(3));
+ Assert.That(MathHelper.CloseToPercent(palette[0], Color.Red));
+
+ Assert.That(Color.ToHsl(palette[0]).X, Is.Zero);
+ Assert.That(MathHelper.CloseToPercent(Color.ToHsl(palette[1]).X, 180f / 360f));
+ Assert.That(MathHelper.CloseToPercent(Color.ToHsl(palette[2]).X, 1f - 180f / 360f));
+ }
+ }
+}
diff --git a/Robust.Shared/Utility/ColorExtensions.cs b/Robust.Shared/Utility/ColorExtensions.cs
new file mode 100644
index 00000000000..007914540c9
--- /dev/null
+++ b/Robust.Shared/Utility/ColorExtensions.cs
@@ -0,0 +1,91 @@
+using System;
+using System.Numerics;
+using Robust.Shared.Random;
+using Robust.Shared.Maths;
+
+namespace Robust.Shared.Utility;
+
+public static class ColorExtensions
+{
+ public static readonly float AnalogousHueDelta = 30f / 360f; // +/- 1/12. 30 degrees, 0.08333... over hue
+ public static readonly float TriadicHueDelta = 120f / 360f; // +/- 1/3. 120 degrees, 0.333 over hue
+ public static readonly float SplitComplementaryHueDelta = 150f / 360f; // +/- 5/12. 150 degrees, 0.4166... over hue
+ public static readonly float ComplementaryHueDelta = 180f / 360f; // +/- 1/2. 180 degrees
+
+ ///
+ /// Generates a list of analogous complementary colors
+ ///
+ public static Color[] GetAnalogousComplementaries(this Color color)
+ {
+ return GetComplementaryColors(color, AnalogousHueDelta);
+ }
+
+ ///
+ /// Generates a list of triadic complementary colors
+ ///
+ public static Color[] GetTriadicComplementaries(this Color color)
+ {
+ return GetComplementaryColors(color, TriadicHueDelta);
+ }
+
+ ///
+ /// Generates a list of split complementary colors
+ ///
+ public static Color[] GetSplitComplementaries(this Color color)
+ {
+ return GetComplementaryColors(color, SplitComplementaryHueDelta);
+ }
+
+ ///
+ /// Generates a list containing the base color and two copies of a single complementary color
+ ///
+ public static Color[] GetOneComplementary(this Color color)
+ {
+ return GetComplementaryColors(color, ComplementaryHueDelta);
+ }
+
+ ///
+ /// Generates a complementary color palette for a provided
+ /// color by rotating a set amount of degrees around the
+ /// color wheel, and then varying the value and saturation
+ /// slightly.
+ ///
+ ///
+ /// A list of 3 colors.
+ ///
+ public static Color[] GetComplementaryColors(Color color, float hueDelta)
+ {
+ var hsl = Color.ToHsl(color);
+ var random = new RobustRandom();
+
+ // sorry about how messy these are, but to get all random values we need to reroll for positive and negative HSL.
+ // since we want to rotate x degrees around the colour wheel, we need to do so in both directions- doing x + x degrees will give us the wrong hue!
+
+ // also varying the saturation and lightness just a little to add some contrast.
+ // since 'color' is our main color, we are desaturating our secondary colors.
+ // this means our main color will always stand out the most.
+
+ var hVal = hsl.X + hueDelta;
+ hVal -= MathF.Floor(hVal);
+ var positiveHSL = new Vector4(
+ hVal,
+ MathHelper.Clamp01(hsl.Y + random.Next(-20 / 100, 0)),
+ MathHelper.Clamp01(hsl.Z + random.Next(-15 / 100, 16 / 100)),
+ hsl.W);
+
+ var hVal1 = hsl.X - hueDelta;
+ if (hVal1 < 0f)
+ hVal1 += 1f;
+ var negativeHSL = new Vector4(
+ hVal1,
+ MathHelper.Clamp01(hsl.Y + random.Next(-20 / 100, 0)),
+ MathHelper.Clamp01(hsl.Z + random.Next(-15 / 100, 16 / 100)),
+ hsl.W);
+
+ var c0 = Color.FromHsl(positiveHSL);
+ var c1 = Color.FromHsl(negativeHSL);
+
+ var palette = new Color[] { color, c0, c1 };
+ return palette;
+ }
+}