A game framework written with osu! in mind.
at master 257 lines 13 kB view raw
1// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2// See the LICENCE file in the repository root for full licence text. 3 4using System; 5using System.Numerics; 6using NUnit.Framework; 7using osu.Framework.Graphics; 8 9namespace osu.Framework.Tests.Graphics 10{ 11 [TestFixture] 12 public class ColourTest 13 { 14 [Test] 15 public void TestFromHSL() 16 { 17 // test FromHSL that black and white are only affected by luminance 18 testConvertFromHSL(Colour4.White, (0f, 0.5f, 1f, 1f)); 19 testConvertFromHSL(Colour4.White, (1f, 1f, 1f, 1f)); 20 testConvertFromHSL(Colour4.White, (0.5f, 0.75f, 1f, 1f)); 21 testConvertFromHSL(Colour4.Black, (0f, 0.5f, 0f, 1f)); 22 testConvertFromHSL(Colour4.Black, (1f, 1f, 0f, 1f)); 23 testConvertFromHSL(Colour4.Black, (0.5f, 0.75f, 0f, 1f)); 24 25 // test FromHSL that grey is not affected by hue 26 testConvertFromHSL(Colour4.Gray, (0f, 0f, 0.5f, 1f)); 27 testConvertFromHSL(Colour4.Gray, (0.5f, 0f, 0.5f, 1f)); 28 testConvertFromHSL(Colour4.Gray, (1f, 0f, 0.5f, 1f)); 29 30 // test FromHSL that alpha is being passed through 31 testConvertFromHSL(Colour4.Black.Opacity(0.5f), (0f, 0f, 0f, 0.5f)); 32 33 // test FromHSL with primaries 34 testConvertFromHSL(Colour4.Red, (0, 1f, 0.5f, 1f)); 35 testConvertFromHSL(Colour4.Yellow, (1f / 6f, 1f, 0.5f, 1f)); 36 testConvertFromHSL(Colour4.Lime, (2f / 6f, 1f, 0.5f, 1f)); 37 testConvertFromHSL(Colour4.Cyan, (3f / 6f, 1f, 0.5f, 1f)); 38 testConvertFromHSL(Colour4.Blue, (4f / 6f, 1f, 0.5f, 1f)); 39 testConvertFromHSL(Colour4.Magenta, (5f / 6f, 1f, 0.5f, 1f)); 40 testConvertFromHSL(Colour4.Red, (1f, 1f, 0.5f, 1f)); 41 42 // test FromHSL with some other knowns 43 testConvertFromHSL(Colour4.CornflowerBlue, (219f / 360f, 0.792f, 0.661f, 1f)); 44 testConvertFromHSL(Colour4.Tan.Opacity(0.5f), (34f / 360f, 0.437f, 0.686f, 0.5f)); 45 } 46 47 [Test] 48 public void TestToHSL() 49 { 50 // test ToHSL that black, white, and grey always return constant 0f for hue and saturation 51 testConvertToHSL((0f, 0f, 1f, 1f), Colour4.White); 52 testConvertToHSL((0f, 0f, 0f, 1f), Colour4.Black); 53 testConvertToHSL((0f, 0f, 0.5f, 1f), Colour4.Gray); 54 55 // test ToHSL that alpha is being passed through 56 testConvertToHSL((0f, 0f, 0f, 0.5f), Colour4.Black.Opacity(0.5f)); 57 58 // test ToHSL with primaries 59 testConvertToHSL((0, 1f, 0.5f, 1f), Colour4.Red); 60 testConvertToHSL((1f / 6f, 1f, 0.5f, 1f), Colour4.Yellow); 61 testConvertToHSL((2f / 6f, 1f, 0.5f, 1f), Colour4.Lime); 62 testConvertToHSL((3f / 6f, 1f, 0.5f, 1f), Colour4.Cyan); 63 testConvertToHSL((4f / 6f, 1f, 0.5f, 1f), Colour4.Blue); 64 testConvertToHSL((5f / 6f, 1f, 0.5f, 1f), Colour4.Magenta); 65 66 // test ToHSL with some other knowns 67 testConvertToHSL((219f / 360f, 0.792f, 0.661f, 1f), Colour4.CornflowerBlue); 68 testConvertToHSL((34f / 360f, 0.437f, 0.686f, 0.5f), Colour4.Tan.Opacity(0.5f)); 69 } 70 71 private void testConvertFromHSL(Colour4 expected, (float, float, float, float) convert) => 72 assertAlmostEqual(expected.Vector, Colour4.FromHSL(convert.Item1, convert.Item2, convert.Item3, convert.Item4).Vector); 73 74 private void testConvertToHSL((float, float, float, float) expected, Colour4 convert) => 75 assertAlmostEqual(new Vector4(expected.Item1, expected.Item2, expected.Item3, expected.Item4), convert.ToHSL(), "HSLA"); 76 77 [Test] 78 public void TestFromHSV() 79 { 80 // test FromHSV that black is only affected by luminance 81 testConvertFromHSV(Colour4.Black, (0f, 0.5f, 0f, 1f)); 82 testConvertFromHSV(Colour4.Black, (1f, 1f, 0f, 1f)); 83 testConvertFromHSV(Colour4.Black, (0.5f, 0.75f, 0f, 1f)); 84 85 // test FromHSV that white and grey are not affected by hue 86 testConvertFromHSV(Colour4.White, (0f, 0f, 1f, 1f)); 87 testConvertFromHSV(Colour4.White, (1f, 0f, 1f, 1f)); 88 testConvertFromHSV(Colour4.White, (0.5f, 0f, 1f, 1f)); 89 testConvertFromHSV(Colour4.Gray, (0f, 0f, 0.5f, 1f)); 90 testConvertFromHSV(Colour4.Gray, (0.5f, 0f, 0.5f, 1f)); 91 testConvertFromHSV(Colour4.Gray, (1f, 0f, 0.5f, 1f)); 92 93 // test FromHSV that alpha is being passed through 94 testConvertFromHSV(Colour4.Black.Opacity(0.5f), (0f, 0f, 0f, 0.5f)); 95 96 // test FromHSV with primaries 97 testConvertFromHSV(Colour4.Red, (0, 1f, 1f, 1f)); 98 testConvertFromHSV(Colour4.Yellow, (1f / 6f, 1f, 1f, 1f)); 99 testConvertFromHSV(Colour4.Lime, (2f / 6f, 1f, 1f, 1f)); 100 testConvertFromHSV(Colour4.Cyan, (3f / 6f, 1f, 1f, 1f)); 101 testConvertFromHSV(Colour4.Blue, (4f / 6f, 1f, 1f, 1f)); 102 testConvertFromHSV(Colour4.Magenta, (5f / 6f, 1f, 1f, 1f)); 103 testConvertFromHSV(Colour4.Red, (1f, 1f, 1f, 1f)); 104 105 // test FromHSV with some other knowns 106 testConvertFromHSV(Colour4.CornflowerBlue, (219f / 360f, 0.578f, 0.929f, 1f)); 107 testConvertFromHSV(Colour4.Tan.Opacity(0.5f), (34f / 360f, 0.333f, 0.824f, 0.5f)); 108 } 109 110 [Test] 111 public void TestToHSV() 112 { 113 // test ToHSV that black, white, and grey always return constant 0f for hue and saturation 114 testConvertToHSV((0f, 0f, 1f, 1f), Colour4.White); 115 testConvertToHSV((0f, 0f, 0f, 1f), Colour4.Black); 116 testConvertToHSV((0f, 0f, 0.5f, 1f), Colour4.Gray); 117 118 // test ToHSV that alpha is being passed through 119 testConvertToHSV((0f, 0f, 1f, 0.5f), Colour4.White.Opacity(0.5f)); 120 121 // test ToHSV with primaries 122 testConvertToHSV((0, 1f, 1f, 1f), Colour4.Red); 123 testConvertToHSV((1f / 6f, 1f, 1f, 1f), Colour4.Yellow); 124 testConvertToHSV((2f / 6f, 1f, 1f, 1f), Colour4.Lime); 125 testConvertToHSV((3f / 6f, 1f, 1f, 1f), Colour4.Cyan); 126 testConvertToHSV((4f / 6f, 1f, 1f, 1f), Colour4.Blue); 127 testConvertToHSV((5f / 6f, 1f, 1f, 1f), Colour4.Magenta); 128 129 // test ToHSV with some other knowns 130 testConvertToHSV((219f / 360f, 0.578f, 0.929f, 1f), Colour4.CornflowerBlue); 131 testConvertToHSV((34f / 360f, 0.333f, 0.824f, 0.5f), Colour4.Tan.Opacity(0.5f)); 132 } 133 134 private void testConvertFromHSV(Colour4 expected, (float, float, float, float) convert) => 135 assertAlmostEqual(expected.Vector, Colour4.FromHSV(convert.Item1, convert.Item2, convert.Item3, convert.Item4).Vector); 136 137 private void testConvertToHSV((float, float, float, float) expected, Colour4 convert) => 138 assertAlmostEqual(new Vector4(expected.Item1, expected.Item2, expected.Item3, expected.Item4), convert.ToHSV(), "HSVA"); 139 140 [Test] 141 public void TestToHex() 142 { 143 Assert.AreEqual("#D2B48C", Colour4.Tan.ToHex()); 144 Assert.AreEqual("#D2B48CFF", Colour4.Tan.ToHex(true)); 145 Assert.AreEqual("#6495ED80", Colour4.CornflowerBlue.Opacity(half_alpha).ToHex()); 146 } 147 148 private static readonly object[][] valid_hex_colours = 149 { 150 new object[] { Colour4.White, "#fff" }, 151 new object[] { Colour4.Red, "#ff0000" }, 152 new object[] { Colour4.Yellow.Opacity(half_alpha), "ffff0080" }, 153 new object[] { Colour4.Lime.Opacity(half_alpha), "00ff0080" }, 154 new object[] { new Colour4(17, 34, 51, 255), "123" }, 155 new object[] { new Colour4(17, 34, 51, 255), "#123" }, 156 new object[] { new Colour4(17, 34, 51, 68), "1234" }, 157 new object[] { new Colour4(17, 34, 51, 68), "#1234" }, 158 new object[] { new Colour4(18, 52, 86, 255), "123456" }, 159 new object[] { new Colour4(18, 52, 86, 255), "#123456" }, 160 new object[] { new Colour4(18, 52, 86, 120), "12345678" }, 161 new object[] { new Colour4(18, 52, 86, 120), "#12345678" } 162 }; 163 164 [TestCaseSource(nameof(valid_hex_colours))] 165 public void TestFromHex(Colour4 expectedColour, string hexCode) 166 { 167 Assert.AreEqual(expectedColour, Colour4.FromHex(hexCode)); 168 169 Assert.True(Colour4.TryParseHex(hexCode, out var actualColour)); 170 Assert.AreEqual(expectedColour, actualColour); 171 } 172 173 [TestCase("1")] 174 [TestCase("#1")] 175 [TestCase("12")] 176 [TestCase("#12")] 177 [TestCase("12345")] 178 [TestCase("#12345")] 179 [TestCase("1234567")] 180 [TestCase("#1234567")] 181 [TestCase("123456789")] 182 [TestCase("#123456789")] 183 [TestCase("gg00zz")] 184 public void TestFromHexFailsOnInvalidColours(string invalidColour) 185 { 186 // Assert.Catch allows any exception type, contrary to .Throws<T>() (which expects exactly T) 187 Assert.Catch(() => Colour4.FromHex(invalidColour)); 188 189 Assert.False(Colour4.TryParseHex(invalidColour, out _)); 190 } 191 192 [Test] 193 public void TestChainingFunctions() 194 { 195 // test that Opacity replaces alpha channel rather than multiplying 196 var expected1 = new Colour4(1f, 0f, 0f, 0.5f); 197 Assert.AreEqual(expected1, Colour4.Red.Opacity(0.5f)); 198 Assert.AreEqual(expected1, expected1.Opacity(0.5f)); 199 200 // test that MultiplyAlpha multiplies existing alpha channel 201 var expected2 = new Colour4(1f, 0f, 0f, 0.25f); 202 Assert.AreEqual(expected2, expected1.MultiplyAlpha(0.5f)); 203 Assert.Throws<ArgumentOutOfRangeException>(() => Colour4.White.MultiplyAlpha(-1f)); 204 205 // test clamping all channels in either direction 206 Assert.AreEqual(Colour4.White, new Colour4(1.1f, 1.1f, 1.1f, 1.1f).Clamped()); 207 Assert.AreEqual(Colour4.Black.Opacity(0f), new Colour4(-1.1f, -1.1f, -1.1f, -1.1f).Clamped()); 208 209 // test lighten and darken 210 assertAlmostEqual(new Colour4(0.431f, 0.642f, 1f, 1f).Vector, Colour4.CornflowerBlue.Lighten(0.1f).Vector); 211 assertAlmostEqual(new Colour4(0.356f, 0.531f, 0.845f, 1f).Vector, Colour4.CornflowerBlue.Darken(0.1f).Vector); 212 } 213 214 [Test] 215 public void TestOperators() 216 { 217 var colour = new Colour4(0.5f, 0.5f, 0.5f, 0.5f); 218 assertAlmostEqual(new Vector4(0.6f, 0.7f, 0.8f, 0.9f), (colour + new Colour4(0.1f, 0.2f, 0.3f, 0.4f)).Vector); 219 assertAlmostEqual(new Vector4(0.4f, 0.3f, 0.2f, 0.1f), (colour - new Colour4(0.1f, 0.2f, 0.3f, 0.4f)).Vector); 220 assertAlmostEqual(new Vector4(0.25f, 0.25f, 0.25f, 0.25f), (colour * colour).Vector); 221 assertAlmostEqual(new Vector4(0.25f, 0.25f, 0.25f, 0.25f), (colour / 2f).Vector); 222 assertAlmostEqual(Colour4.White.Vector, (colour * 2f).Vector); 223 Assert.Throws<ArgumentOutOfRangeException>(() => _ = colour * -1f); 224 Assert.Throws<ArgumentOutOfRangeException>(() => _ = colour / -1f); 225 Assert.Throws<ArgumentOutOfRangeException>(() => _ = colour / 0f); 226 } 227 228 [Test] 229 public void TestOtherConversions() 230 { 231 // test uint conversions 232 Assert.AreEqual(0x6495ED80, Colour4.CornflowerBlue.Opacity(half_alpha).ToRGBA()); 233 Assert.AreEqual(0x806495ED, Colour4.CornflowerBlue.Opacity(half_alpha).ToARGB()); 234 Assert.AreEqual(Colour4.CornflowerBlue.Opacity(half_alpha), Colour4.FromRGBA(0x6495ED80)); 235 Assert.AreEqual(Colour4.CornflowerBlue.Opacity(half_alpha), Colour4.FromARGB(0x806495ED)); 236 237 // test SRGB 238 var srgb = new Vector4(0.659f, 0.788f, 0.968f, 1f); 239 assertAlmostEqual(srgb, Colour4.CornflowerBlue.ToSRGB().Vector); 240 assertAlmostEqual(Colour4.CornflowerBlue.Vector, new Colour4(srgb).ToLinear().Vector); 241 } 242 243 private void assertAlmostEqual(Vector4 expected, Vector4 actual, string type = "RGBA") 244 { 245 // note that we use a fairly high delta since the test constants are approximations 246 const float delta = 0.005f; 247 var message = $"({type}) Expected: {expected}, Actual: {actual}"; 248 Assert.AreEqual(expected.X, actual.X, delta, message); 249 Assert.AreEqual(expected.Y, actual.Y, delta, message); 250 Assert.AreEqual(expected.Z, actual.Z, delta, message); 251 Assert.AreEqual(expected.W, actual.W, delta, message); 252 } 253 254 // 0x80 alpha is slightly more than half 255 private const float half_alpha = 128f / 255f; 256 } 257}