ColorTests.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. using System;
  2. using Avalonia.Media;
  3. using Xunit;
  4. namespace Avalonia.Base.UnitTests.Media
  5. {
  6. public class ColorTests
  7. {
  8. [Fact]
  9. public void Parse_Parses_RGB_Hash_Color()
  10. {
  11. var result = Color.Parse("#ff8844");
  12. Assert.Equal(0xff, result.R);
  13. Assert.Equal(0x88, result.G);
  14. Assert.Equal(0x44, result.B);
  15. Assert.Equal(0xff, result.A);
  16. }
  17. [Fact]
  18. public void Try_Parse_Parses_RGB_Hash_Color()
  19. {
  20. var success = Color.TryParse("#ff8844", out Color result);
  21. Assert.True(success);
  22. Assert.Equal(0xff, result.R);
  23. Assert.Equal(0x88, result.G);
  24. Assert.Equal(0x44, result.B);
  25. Assert.Equal(0xff, result.A);
  26. }
  27. [Fact]
  28. public void Parse_Parses_RGB_Hash_Shorthand_Color()
  29. {
  30. var result = Color.Parse("#f84");
  31. Assert.Equal(0xff, result.R);
  32. Assert.Equal(0x88, result.G);
  33. Assert.Equal(0x44, result.B);
  34. Assert.Equal(0xff, result.A);
  35. }
  36. [Fact]
  37. public void Try_Parse_Parses_RGB_Hash_Shorthand_Color()
  38. {
  39. var success = Color.TryParse("#f84", out Color result);
  40. Assert.True(success);
  41. Assert.Equal(0xff, result.R);
  42. Assert.Equal(0x88, result.G);
  43. Assert.Equal(0x44, result.B);
  44. Assert.Equal(0xff, result.A);
  45. }
  46. [Fact]
  47. public void Parse_Parses_ARGB_Hash_Color()
  48. {
  49. var result = Color.Parse("#40ff8844");
  50. Assert.Equal(0xff, result.R);
  51. Assert.Equal(0x88, result.G);
  52. Assert.Equal(0x44, result.B);
  53. Assert.Equal(0x40, result.A);
  54. }
  55. [Fact]
  56. public void Try_Parse_Parses_ARGB_Hash_Color()
  57. {
  58. var success = Color.TryParse("#40ff8844", out Color result);
  59. Assert.True(success);
  60. Assert.Equal(0xff, result.R);
  61. Assert.Equal(0x88, result.G);
  62. Assert.Equal(0x44, result.B);
  63. Assert.Equal(0x40, result.A);
  64. }
  65. [Fact]
  66. public void Parse_Parses_ARGB_Hash_Shorthand_Color()
  67. {
  68. var result = Color.Parse("#4f84");
  69. Assert.Equal(0xff, result.R);
  70. Assert.Equal(0x88, result.G);
  71. Assert.Equal(0x44, result.B);
  72. Assert.Equal(0x44, result.A);
  73. }
  74. [Fact]
  75. public void Try_Parse_Parses_ARGB_Hash_Shorthand_Color()
  76. {
  77. var success = Color.TryParse("#4f84", out Color result);
  78. Assert.True(success);
  79. Assert.Equal(0xff, result.R);
  80. Assert.Equal(0x88, result.G);
  81. Assert.Equal(0x44, result.B);
  82. Assert.Equal(0x44, result.A);
  83. }
  84. [Fact]
  85. public void Parse_Parses_Named_Color_Lowercase()
  86. {
  87. var result = Color.Parse("red");
  88. Assert.Equal(0xff, result.R);
  89. Assert.Equal(0x00, result.G);
  90. Assert.Equal(0x00, result.B);
  91. Assert.Equal(0xff, result.A);
  92. }
  93. [Fact]
  94. public void TryParse_Parses_Named_Color_Lowercase()
  95. {
  96. var success = Color.TryParse("red", out Color result);
  97. Assert.True(success);
  98. Assert.Equal(0xff, result.R);
  99. Assert.Equal(0x00, result.G);
  100. Assert.Equal(0x00, result.B);
  101. Assert.Equal(0xff, result.A);
  102. }
  103. [Fact]
  104. public void Parse_Parses_Named_Color_Uppercase()
  105. {
  106. var result = Color.Parse("RED");
  107. Assert.Equal(0xff, result.R);
  108. Assert.Equal(0x00, result.G);
  109. Assert.Equal(0x00, result.B);
  110. Assert.Equal(0xff, result.A);
  111. }
  112. [Fact]
  113. public void TryParse_Parses_Named_Color_Uppercase()
  114. {
  115. var success = Color.TryParse("RED", out Color result);
  116. Assert.True(success);
  117. Assert.Equal(0xff, result.R);
  118. Assert.Equal(0x00, result.G);
  119. Assert.Equal(0x00, result.B);
  120. Assert.Equal(0xff, result.A);
  121. }
  122. [Fact]
  123. public void Parse_Hex_Value_Doesnt_Accept_Too_Few_Chars()
  124. {
  125. Assert.Throws<FormatException>(() => Color.Parse("#ff"));
  126. }
  127. [Fact]
  128. public void TryParse_Hex_Value_Doesnt_Accept_Too_Few_Chars()
  129. {
  130. Assert.False(Color.TryParse("#ff", out _));
  131. }
  132. [Fact]
  133. public void Parse_Hex_Value_Doesnt_Accept_Too_Many_Chars()
  134. {
  135. Assert.Throws<FormatException>(() => Color.Parse("#ff5555555"));
  136. }
  137. [Fact]
  138. public void TryParse_Hex_Value_Doesnt_Accept_Too_Many_Chars()
  139. {
  140. Assert.False(Color.TryParse("#ff5555555", out _));
  141. }
  142. [Fact]
  143. public void Parse_Hex_Value_Doesnt_Accept_Invalid_Number()
  144. {
  145. Assert.Throws<FormatException>(() => Color.Parse("#ff808g80"));
  146. }
  147. [Fact]
  148. public void TryParse_Hex_Value_Doesnt_Accept_Invalid_Number()
  149. {
  150. Assert.False(Color.TryParse("#ff808g80", out _));
  151. }
  152. [Fact]
  153. public void Parse_Throws_ArgumentNullException_For_Null_Input()
  154. {
  155. Assert.Throws<ArgumentNullException>(() => Color.Parse((string)null));
  156. }
  157. [Fact]
  158. public void Parse_Throws_FormatException_For_Invalid_Input()
  159. {
  160. Assert.Throws<FormatException>(() => Color.Parse(string.Empty));
  161. }
  162. [Theory]
  163. [InlineData("")]
  164. [InlineData(null)]
  165. public void TryParse_Returns_False_For_Invalid_Input(string input)
  166. {
  167. Assert.False(Color.TryParse(input, out _));
  168. }
  169. [Fact]
  170. public void Try_Parse_HslColor()
  171. {
  172. // Inline data requires constants, so the data is handled internally here
  173. var data = new Tuple<string, HslColor>[]
  174. {
  175. // HSV
  176. Tuple.Create("hsl(0, 0, 0)", new HslColor(1, 0, 0, 0)),
  177. Tuple.Create("hsl(0, 0%, 0%)", new HslColor(1, 0, 0, 0)),
  178. Tuple.Create("hsl(180, 0.5, 0.5)", new HslColor(1, 180, 0.5, 0.5)),
  179. Tuple.Create("hsl(180, 50%, 50%)", new HslColor(1, 180, 0.5, 0.5)),
  180. Tuple.Create("hsl(360, 1.0, 1.0)", new HslColor(1, 0, 1, 1)), // Wraps Hue to zero
  181. Tuple.Create("hsl(360, 100%, 100%)", new HslColor(1, 0, 1, 1)), // Wraps Hue to zero
  182. Tuple.Create("hsl(-1000, -1000, -1000)", new HslColor(1, 0, 0, 0)), // Clamps to min
  183. Tuple.Create("hsl(-1000, -1000%, -1000%)", new HslColor(1, 0, 0, 0)), // Clamps to min
  184. Tuple.Create("hsl(1000, 1000, 1000)", new HslColor(1, 0, 1, 1)), // Clamps to max (Hue wraps to zero)
  185. Tuple.Create("hsl(1000, 1000%, 1000%)", new HslColor(1, 0, 1, 1)), // Clamps to max (Hue wraps to zero)
  186. Tuple.Create("hsl(300, 0.8, 0.2)", new HslColor(1.0, 300, 0.8, 0.2)),
  187. Tuple.Create("hsl(300, 80%, 20%)", new HslColor(1.0, 300, 0.8, 0.2)),
  188. // HSVA
  189. Tuple.Create("hsla(0, 0, 0, 0)", new HslColor(0, 0, 0, 0)),
  190. Tuple.Create("hsla(0, 0%, 0%, 0%)", new HslColor(0, 0, 0, 0)),
  191. Tuple.Create("hsla(180, 0.5, 0.5, 0.5)", new HslColor(0.5, 180, 0.5, 0.5)),
  192. Tuple.Create("hsla(180, 50%, 50%, 50%)", new HslColor(0.5, 180, 0.5, 0.5)),
  193. Tuple.Create("hsla(360, 1.0, 1.0, 1.0)", new HslColor(1, 0, 1, 1)), // Wraps Hue to zero
  194. Tuple.Create("hsla(360, 100%, 100%, 100%)", new HslColor(1, 0, 1, 1)), // Wraps Hue to zero
  195. Tuple.Create("hsla(-1000, -1000, -1000, -1000)", new HslColor(0, 0, 0, 0)), // Clamps to min
  196. Tuple.Create("hsla(-1000, -1000%, -1000%, -1000%)", new HslColor(0, 0, 0, 0)), // Clamps to min
  197. Tuple.Create("hsla(1000, 1000, 1000, 1000)", new HslColor(1, 0, 1, 1)), // Clamps to max (Hue wraps to zero)
  198. Tuple.Create("hsla(1000, 1000%, 1000%, 1000%)", new HslColor(1, 0, 1, 1)), // Clamps to max (Hue wraps to zero)
  199. Tuple.Create("hsla(300, 0.9, 0.2, 0.8)", new HslColor(0.8, 300, 0.9, 0.2)),
  200. Tuple.Create("hsla(300, 90%, 20%, 0.8)", new HslColor(0.8, 300, 0.9, 0.2)),
  201. };
  202. foreach (var dataPoint in data)
  203. {
  204. Assert.True(HslColor.TryParse(dataPoint.Item1, out HslColor parsedHslColor));
  205. Assert.True(dataPoint.Item2 == parsedHslColor);
  206. }
  207. }
  208. [Fact]
  209. public void Try_Parse_HsvColor()
  210. {
  211. // Inline data requires constants, so the data is handled internally here
  212. var data = new Tuple<string, HsvColor>[]
  213. {
  214. // HSV
  215. Tuple.Create("hsv(0, 0, 0)", new HsvColor(1, 0, 0, 0)),
  216. Tuple.Create("hsv(0, 0%, 0%)", new HsvColor(1, 0, 0, 0)),
  217. Tuple.Create("hsv(180, 0.5, 0.5)", new HsvColor(1, 180, 0.5, 0.5)),
  218. Tuple.Create("hsv(180, 50%, 50%)", new HsvColor(1, 180, 0.5, 0.5)),
  219. Tuple.Create("hsv(360, 1.0, 1.0)", new HsvColor(1, 0, 1, 1)), // Wraps Hue to zero
  220. Tuple.Create("hsv(360, 100%, 100%)", new HsvColor(1, 0, 1, 1)), // Wraps Hue to zero
  221. Tuple.Create("hsv(-1000, -1000, -1000)", new HsvColor(1, 0, 0, 0)), // Clamps to min
  222. Tuple.Create("hsv(-1000, -1000%, -1000%)", new HsvColor(1, 0, 0, 0)), // Clamps to min
  223. Tuple.Create("hsv(1000, 1000, 1000)", new HsvColor(1, 0, 1, 1)), // Clamps to max (Hue wraps to zero)
  224. Tuple.Create("hsv(1000, 1000%, 1000%)", new HsvColor(1, 0, 1, 1)), // Clamps to max (Hue wraps to zero)
  225. Tuple.Create("hsv(300, 0.8, 0.2)", new HsvColor(1.0, 300, 0.8, 0.2)),
  226. Tuple.Create("hsv(300, 80%, 20%)", new HsvColor(1.0, 300, 0.8, 0.2)),
  227. // HSVA
  228. Tuple.Create("hsva(0, 0, 0, 0)", new HsvColor(0, 0, 0, 0)),
  229. Tuple.Create("hsva(0, 0%, 0%, 0%)", new HsvColor(0, 0, 0, 0)),
  230. Tuple.Create("hsva(180, 0.5, 0.5, 0.5)", new HsvColor(0.5, 180, 0.5, 0.5)),
  231. Tuple.Create("hsva(180, 50%, 50%, 50%)", new HsvColor(0.5, 180, 0.5, 0.5)),
  232. Tuple.Create("hsva(360, 1.0, 1.0, 1.0)", new HsvColor(1, 0, 1, 1)), // Wraps Hue to zero
  233. Tuple.Create("hsva(360, 100%, 100%, 100%)", new HsvColor(1, 0, 1, 1)), // Wraps Hue to zero
  234. Tuple.Create("hsva(-1000, -1000, -1000, -1000)", new HsvColor(0, 0, 0, 0)), // Clamps to min
  235. Tuple.Create("hsva(-1000, -1000%, -1000%, -1000%)", new HsvColor(0, 0, 0, 0)), // Clamps to min
  236. Tuple.Create("hsva(1000, 1000, 1000, 1000)", new HsvColor(1, 0, 1, 1)), // Clamps to max (Hue wraps to zero)
  237. Tuple.Create("hsva(1000, 1000%, 1000%, 1000%)", new HsvColor(1, 0, 1, 1)), // Clamps to max (Hue wraps to zero)
  238. Tuple.Create("hsva(300, 0.9, 0.2, 0.8)", new HsvColor(0.8, 300, 0.9, 0.2)),
  239. Tuple.Create("hsva(300, 90%, 20%, 0.8)", new HsvColor(0.8, 300, 0.9, 0.2)),
  240. };
  241. foreach (var dataPoint in data)
  242. {
  243. Assert.True(HsvColor.TryParse(dataPoint.Item1, out HsvColor parsedHsvColor));
  244. Assert.True(dataPoint.Item2 == parsedHsvColor);
  245. }
  246. }
  247. [Fact]
  248. public void Try_Parse_All_Formats_With_Conversion()
  249. {
  250. // Inline data requires constants, so the data is handled internally here
  251. var data = new Tuple<string, Color>[]
  252. {
  253. // RGB
  254. Tuple.Create("White", new Color(0xff, 0xff, 0xff, 0xff)),
  255. Tuple.Create("#123456", new Color(0xff, 0x12, 0x34, 0x56)),
  256. Tuple.Create("rgb(100, 30, 45)", new Color(255, 100, 30, 45)),
  257. Tuple.Create("rgba(100, 30, 45, 0.9)", new Color(230, 100, 30, 45)),
  258. Tuple.Create("rgba(100, 30, 45, 90%)", new Color(230, 100, 30, 45)),
  259. Tuple.Create("rgb(255,0,0)", new Color(255, 255, 0, 0)),
  260. Tuple.Create("rgb(0,255,0)", new Color(255, 0, 255, 0)),
  261. Tuple.Create("rgb(0,0,255)", new Color(255, 0, 0, 255)),
  262. Tuple.Create("rgb(100%, 0, 0)", new Color(255, 255, 0, 0)),
  263. Tuple.Create("rgb(0, 100%, 0)", new Color(255, 0, 255, 0)),
  264. Tuple.Create("rgb(0, 0, 100%)", new Color(255, 0, 0, 255)),
  265. Tuple.Create("rgba(0, 0, 100%, 50%)", new Color(128, 0, 0, 255)),
  266. Tuple.Create("rgba(50%, 10%, 80%, 50%)", new Color(128, 128, 26, 204)),
  267. Tuple.Create("rgba(50%, 10%, 80%, 0.5)", new Color(128, 128, 26, 204)),
  268. // HSL
  269. Tuple.Create("hsl(296, 85%, 12%)", new Color(255, 53, 5, 57)),
  270. Tuple.Create("hsla(296, 0.85, 0.12, 0.9)", new Color(230, 53, 5, 57)),
  271. Tuple.Create("hsla(296, 85%, 12%, 90%)", new Color(230, 53, 5, 57)),
  272. // HSV
  273. Tuple.Create("hsv(240, 83%, 78%)", new Color(255, 34, 34, 199)),
  274. Tuple.Create("hsva(240, 0.83, 0.78, 0.9)", new Color(230, 34, 34, 199)),
  275. Tuple.Create("hsva(240, 83%, 78%, 90%)", new Color(230, 34, 34, 199)),
  276. };
  277. foreach (var dataPoint in data)
  278. {
  279. Assert.True(Color.TryParse(dataPoint.Item1, out Color parsedColor));
  280. Assert.True(dataPoint.Item2 == parsedColor);
  281. }
  282. }
  283. [Fact]
  284. public void Hsv_To_From_Hsl_Conversion()
  285. {
  286. // Note that conversion of values more representative of actual colors is not done due to rounding error
  287. // It would be necessary to introduce a different equality comparison that accounts for rounding differences in values
  288. // This is a result of the math in the conversion itself
  289. // RGB doesn't have this problem because it uses whole numbers
  290. var data = new Tuple<HsvColor, HslColor>[]
  291. {
  292. Tuple.Create(new HsvColor(1.0, 0.0, 0.0, 0.0), new HslColor(1.0, 0.0, 0.0, 0.0)),
  293. Tuple.Create(new HsvColor(1.0, 359.0, 1.0, 1.0), new HslColor(1.0, 359.0, 1.0, 0.5)),
  294. Tuple.Create(new HsvColor(1.0, 128.0, 0.0, 0.0), new HslColor(1.0, 128.0, 0.0, 0.0)),
  295. Tuple.Create(new HsvColor(1.0, 128.0, 0.0, 1.0), new HslColor(1.0, 128.0, 0.0, 1.0)),
  296. Tuple.Create(new HsvColor(1.0, 128.0, 1.0, 1.0), new HslColor(1.0, 128.0, 1.0, 0.5)),
  297. Tuple.Create(new HsvColor(0.23, 0.5, 1.0, 1.0), new HslColor(0.23, 0.5, 1.0, 0.5)),
  298. };
  299. foreach (var dataPoint in data)
  300. {
  301. var convertedHsl = dataPoint.Item1.ToHsl();
  302. var convertedHsv = dataPoint.Item2.ToHsv();
  303. Assert.Equal(convertedHsv, dataPoint.Item1);
  304. Assert.Equal(convertedHsl, dataPoint.Item2);
  305. }
  306. }
  307. }
  308. }