ColorHelper.cs 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Reflection;
  5. using Microsoft.Xna.Framework;
  6. namespace MonoGame.Extended
  7. {
  8. /// <summary>
  9. /// Provides utility methods for working with <see cref="Color"/> values.
  10. /// </summary>
  11. public static class ColorHelper
  12. {
  13. private static readonly Dictionary<string, Color> s_colorsByName = typeof(Color)
  14. .GetRuntimeProperties()
  15. .Where(p => p.PropertyType == typeof(Color))
  16. .ToDictionary(p => p.Name, p => (Color)p.GetValue(null), StringComparer.OrdinalIgnoreCase);
  17. /// <summary>
  18. /// Converts a hexadecimal color string to a <see cref="Color"/> value.
  19. /// </summary>
  20. /// <param name="value">
  21. /// The hexadecimal color string to convert. Supports multiple formats:
  22. /// <list type="bullet">
  23. /// <item>3 characters: RGB shorthand (e.g., "F0A" becomes "FF00AA")</item>
  24. /// <item>4 characters: RGBA shorthand (e.g., "F0A8" becomes "FF00AA88")</item>
  25. /// <item>6 characters: Full RGB format (e.g., "FF00AA")</item>
  26. /// <item>8 characters: Full RGBA format (e.g., "FF00AA88")</item>
  27. /// </list>
  28. /// Optional '#' prefix is automatically handled and removed.
  29. /// </param>
  30. /// <returns>
  31. /// A <see cref="Color"/> value representing the parsed hexadecimal color, or <see cref="Color.Transparent"/>
  32. /// if the input is <see langword="null"/> or an empty string.
  33. /// </returns>
  34. /// <exception cref="ArgumentException">
  35. /// The length of <paramref name="value"/> is not 3, 4, 6, or 8 (excluding a '#' prefix)
  36. /// </exception>
  37. /// <exception cref="FormatException">
  38. /// <paramref name="value"/> contains invalid hexadecimal characters.
  39. /// </exception>
  40. /// <exception cref="OverflowException">
  41. /// <paramref name="value"/> represents a value too large for a 32-bit unsigned integer.
  42. /// </exception>
  43. public static Color FromHex(string value)
  44. {
  45. if (string.IsNullOrEmpty(value))
  46. {
  47. return Color.Transparent;
  48. }
  49. return FromHex(value.AsSpan());
  50. }
  51. /// <summary>
  52. /// Converts a hexadecimal color span to a <see cref="Color"/> value.
  53. /// </summary>
  54. /// <remarks>
  55. /// This overload provides better performance than the string version by avoiding string allocations
  56. /// when removing the '#' prefix and during parsing operations. Particularly beneficial when
  57. /// processing large numbers of hex colors or when called frequently.
  58. /// </remarks>
  59. /// <param name="value">
  60. /// A read-only span of characters representing the hexadecimal color. Supports multiple formats:
  61. /// <list type="bullet">
  62. /// <item>3 characters: RGB shorthand (e.g., "F0A" becomes "FF00AA")</item>
  63. /// <item>4 characters: RGBA shorthand (e.g., "F0A8" becomes "FF00AA88")</item>
  64. /// <item>6 characters: Full RGB format (e.g., "FF00AA")</item>
  65. /// <item>8 characters: Full RGBA format (e.g., "FF00AA88")</item>
  66. /// </list>
  67. /// Optional '#' prefix is automatically handled and removed.
  68. /// </param>
  69. /// <returns>
  70. /// A <see cref="Color"/> value representing the parsed hexadecimal color, or <see cref="Color.Transparent"/>
  71. /// if the input is is empty.
  72. /// </returns>
  73. /// <exception cref="ArgumentException">
  74. /// The length of <paramref name="value"/> is not 3, 4, 6, or 8 (excluding a '#' prefix)
  75. /// </exception>
  76. /// <exception cref="FormatException">
  77. /// <paramref name="value"/> contains invalid hexadecimal characters.
  78. /// </exception>
  79. /// <exception cref="OverflowException">
  80. /// <paramref name="value"/> represents a value too large for a 32-bit unsigned integer.
  81. /// </exception>
  82. public static Color FromHex(ReadOnlySpan<char> value)
  83. {
  84. if (value.IsEmpty)
  85. {
  86. return Color.Transparent;
  87. }
  88. if (value[0] == '#')
  89. {
  90. value = value.Slice(1);
  91. }
  92. int r, g, b, a;
  93. uint hexInt = uint.Parse(value, System.Globalization.NumberStyles.HexNumber);
  94. switch (value.Length)
  95. {
  96. case 6:
  97. r = (byte)((hexInt & 0x00FF0000) >> 16);
  98. g = (byte)((hexInt & 0x0000FF00) >> 8);
  99. b = (byte)(hexInt & 0x000000FF);
  100. a = 255;
  101. break;
  102. case 8:
  103. r = (byte)((hexInt & 0xFF000000) >> 24);
  104. g = (byte)((hexInt & 0x00FF0000) >> 16);
  105. b = (byte)((hexInt & 0x0000FF00) >> 8);
  106. a = (byte)(hexInt & 0x000000FF);
  107. break;
  108. case 3:
  109. r = (byte)(((hexInt & 0x00000F00) | (hexInt & 0x00000F00) << 4) >> 8);
  110. g = (byte)(((hexInt & 0x000000F0) | (hexInt & 0x000000F0) << 4) >> 4);
  111. b = (byte)((hexInt & 0x0000000F) | (hexInt & 0x0000000F) << 4);
  112. a = 255;
  113. break;
  114. case 4:
  115. r = (byte)(((hexInt & 0x0000F000) | (hexInt & 0x0000F000) << 4) >> 12);
  116. g = (byte)(((hexInt & 0x00000F00) | (hexInt & 0x00000F00) << 4) >> 8);
  117. b = (byte)(((hexInt & 0x000000F0) | (hexInt & 0x000000F0) << 4) >> 4);
  118. a = (byte)((hexInt & 0x0000000F) | (hexInt & 0x0000000F) << 4);
  119. break;
  120. default:
  121. throw new ArgumentException($"Malformed hexadecimal color: {value}");
  122. }
  123. return new Color(r, g, b, a);
  124. }
  125. /// <summary>
  126. /// Creates a <see cref="Color"/> value from the specified name of a predefined color.
  127. /// Gets a <see cref="Color"/> value from a p
  128. /// </summary>
  129. /// <param name="name">The name of the predefined color.</param>
  130. /// <returns>
  131. /// The <see cref="Color"/> value this method creates.
  132. /// </returns>
  133. /// <exception cref="InvalidOperationException"><paramref name="name"/> is not a valid color.</exception>
  134. public static Color FromName(string name)
  135. {
  136. if (s_colorsByName.TryGetValue(name, out Color color))
  137. {
  138. return color;
  139. }
  140. throw new InvalidOperationException($"{name} is not a valid color");
  141. }
  142. /// <summary>
  143. /// Returns a new <see cref="Color"/> value based on a packed value in the ABGR format.
  144. /// </summary>
  145. /// <remarks>
  146. /// This is useful for when you have HTML hex style values such as #123456 and want to use it in hex format for
  147. /// the parameter. Since Color's standard format is RGBA, you would have to do new Color(0xFF563212) since R
  148. /// is the LSB. With this method, you can write it the same way it is written in HTML hex by doing
  149. /// <c>ColorHelper.FromAbgr(0x123456FF);</c>
  150. /// </remarks>
  151. /// <param name="abgr">The packed color value in ABGR format</param>
  152. /// <returns>The <see cref="Color"/> value created</returns>
  153. public static Color FromAbgr(uint abgr)
  154. {
  155. uint rgba = (abgr & 0x000000FF) << 24 | // Alpha
  156. (abgr & 0x0000FF00) << 8 | // Blue
  157. (abgr & 0x00FF0000) >> 8 | // Green
  158. (abgr & 0xFF000000) >> 24; // Red
  159. Color result;
  160. #if FNA
  161. result = default;
  162. result.PackedValue = rgba;
  163. #else
  164. result = new Color(rgba);
  165. #endif
  166. return result;
  167. }
  168. [Obsolete("Use HslColor.ToRgb instead. This will be removed in the next major SemVer release.")]
  169. //http://stackoverflow.com/questions/2353211/hsl-to-rgb-color-conversion
  170. public static Color FromHsl(float hue, float saturation, float lightness)
  171. {
  172. var hsl = new Vector4(hue, saturation, lightness, 1);
  173. var color = new Vector4(0, 0, 0, hsl.W);
  174. // ReSharper disable once CompareOfFloatsByEqualityOperator
  175. if (hsl.Y == 0.0f)
  176. color.X = color.Y = color.Z = hsl.Z;
  177. else
  178. {
  179. var q = hsl.Z < 0.5f ? hsl.Z * (1.0f + hsl.Y) : hsl.Z + hsl.Y - hsl.Z * hsl.Y;
  180. var p = 2.0f * hsl.Z - q;
  181. color.X = HueToRgb(p, q, hsl.X + 1.0f / 3.0f);
  182. color.Y = HueToRgb(p, q, hsl.X);
  183. color.Z = HueToRgb(p, q, hsl.X - 1.0f / 3.0f);
  184. }
  185. return new Color(color);
  186. }
  187. [Obsolete("This will be removed in the next major SemVer release")]
  188. private static float HueToRgb(float p, float q, float t)
  189. {
  190. if (t < 0.0f) t += 1.0f;
  191. if (t > 1.0f) t -= 1.0f;
  192. if (t < 1.0f / 6.0f) return p + (q - p) * 6.0f * t;
  193. if (t < 1.0f / 2.0f) return q;
  194. if (t < 2.0f / 3.0f) return p + (q - p) * (2.0f / 3.0f - t) * 6.0f;
  195. return p;
  196. }
  197. [Obsolete("Use ColorExtensions.ToHex instead. This will be removed in the next major SemVer release.")]
  198. public static string ToHex(Color color)
  199. {
  200. var rx = $"{color.R:x2}";
  201. var gx = $"{color.G:x2}";
  202. var bx = $"{color.B:x2}";
  203. var ax = $"{color.A:x2}";
  204. return $"#{rx}{gx}{bx}{ax}";
  205. }
  206. }
  207. }