| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231 |
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Reflection;
- using Microsoft.Xna.Framework;
- namespace MonoGame.Extended
- {
- /// <summary>
- /// Provides utility methods for working with <see cref="Color"/> values.
- /// </summary>
- public static class ColorHelper
- {
- private static readonly Dictionary<string, Color> s_colorsByName = typeof(Color)
- .GetRuntimeProperties()
- .Where(p => p.PropertyType == typeof(Color))
- .ToDictionary(p => p.Name, p => (Color)p.GetValue(null), StringComparer.OrdinalIgnoreCase);
- /// <summary>
- /// Converts a hexadecimal color string to a <see cref="Color"/> value.
- /// </summary>
- /// <param name="value">
- /// The hexadecimal color string to convert. Supports multiple formats:
- /// <list type="bullet">
- /// <item>3 characters: RGB shorthand (e.g., "F0A" becomes "FF00AA")</item>
- /// <item>4 characters: RGBA shorthand (e.g., "F0A8" becomes "FF00AA88")</item>
- /// <item>6 characters: Full RGB format (e.g., "FF00AA")</item>
- /// <item>8 characters: Full RGBA format (e.g., "FF00AA88")</item>
- /// </list>
- /// Optional '#' prefix is automatically handled and removed.
- /// </param>
- /// <returns>
- /// A <see cref="Color"/> value representing the parsed hexadecimal color, or <see cref="Color.Transparent"/>
- /// if the input is <see langword="null"/> or an empty string.
- /// </returns>
- /// <exception cref="ArgumentException">
- /// The length of <paramref name="value"/> is not 3, 4, 6, or 8 (excluding a '#' prefix)
- /// </exception>
- /// <exception cref="FormatException">
- /// <paramref name="value"/> contains invalid hexadecimal characters.
- /// </exception>
- /// <exception cref="OverflowException">
- /// <paramref name="value"/> represents a value too large for a 32-bit unsigned integer.
- /// </exception>
- public static Color FromHex(string value)
- {
- if (string.IsNullOrEmpty(value))
- {
- return Color.Transparent;
- }
- return FromHex(value.AsSpan());
- }
- /// <summary>
- /// Converts a hexadecimal color span to a <see cref="Color"/> value.
- /// </summary>
- /// <remarks>
- /// This overload provides better performance than the string version by avoiding string allocations
- /// when removing the '#' prefix and during parsing operations. Particularly beneficial when
- /// processing large numbers of hex colors or when called frequently.
- /// </remarks>
- /// <param name="value">
- /// A read-only span of characters representing the hexadecimal color. Supports multiple formats:
- /// <list type="bullet">
- /// <item>3 characters: RGB shorthand (e.g., "F0A" becomes "FF00AA")</item>
- /// <item>4 characters: RGBA shorthand (e.g., "F0A8" becomes "FF00AA88")</item>
- /// <item>6 characters: Full RGB format (e.g., "FF00AA")</item>
- /// <item>8 characters: Full RGBA format (e.g., "FF00AA88")</item>
- /// </list>
- /// Optional '#' prefix is automatically handled and removed.
- /// </param>
- /// <returns>
- /// A <see cref="Color"/> value representing the parsed hexadecimal color, or <see cref="Color.Transparent"/>
- /// if the input is is empty.
- /// </returns>
- /// <exception cref="ArgumentException">
- /// The length of <paramref name="value"/> is not 3, 4, 6, or 8 (excluding a '#' prefix)
- /// </exception>
- /// <exception cref="FormatException">
- /// <paramref name="value"/> contains invalid hexadecimal characters.
- /// </exception>
- /// <exception cref="OverflowException">
- /// <paramref name="value"/> represents a value too large for a 32-bit unsigned integer.
- /// </exception>
- public static Color FromHex(ReadOnlySpan<char> value)
- {
- if (value.IsEmpty)
- {
- return Color.Transparent;
- }
- if (value[0] == '#')
- {
- value = value.Slice(1);
- }
- int r, g, b, a;
- uint hexInt = uint.Parse(value, System.Globalization.NumberStyles.HexNumber);
- switch (value.Length)
- {
- case 6:
- r = (byte)((hexInt & 0x00FF0000) >> 16);
- g = (byte)((hexInt & 0x0000FF00) >> 8);
- b = (byte)(hexInt & 0x000000FF);
- a = 255;
- break;
- case 8:
- r = (byte)((hexInt & 0xFF000000) >> 24);
- g = (byte)((hexInt & 0x00FF0000) >> 16);
- b = (byte)((hexInt & 0x0000FF00) >> 8);
- a = (byte)(hexInt & 0x000000FF);
- break;
- case 3:
- r = (byte)(((hexInt & 0x00000F00) | (hexInt & 0x00000F00) << 4) >> 8);
- g = (byte)(((hexInt & 0x000000F0) | (hexInt & 0x000000F0) << 4) >> 4);
- b = (byte)((hexInt & 0x0000000F) | (hexInt & 0x0000000F) << 4);
- a = 255;
- break;
- case 4:
- r = (byte)(((hexInt & 0x0000F000) | (hexInt & 0x0000F000) << 4) >> 12);
- g = (byte)(((hexInt & 0x00000F00) | (hexInt & 0x00000F00) << 4) >> 8);
- b = (byte)(((hexInt & 0x000000F0) | (hexInt & 0x000000F0) << 4) >> 4);
- a = (byte)((hexInt & 0x0000000F) | (hexInt & 0x0000000F) << 4);
- break;
- default:
- throw new ArgumentException($"Malformed hexadecimal color: {value}");
- }
- return new Color(r, g, b, a);
- }
- /// <summary>
- /// Creates a <see cref="Color"/> value from the specified name of a predefined color.
- /// Gets a <see cref="Color"/> value from a p
- /// </summary>
- /// <param name="name">The name of the predefined color.</param>
- /// <returns>
- /// The <see cref="Color"/> value this method creates.
- /// </returns>
- /// <exception cref="InvalidOperationException"><paramref name="name"/> is not a valid color.</exception>
- public static Color FromName(string name)
- {
- if (s_colorsByName.TryGetValue(name, out Color color))
- {
- return color;
- }
- throw new InvalidOperationException($"{name} is not a valid color");
- }
- /// <summary>
- /// Returns a new <see cref="Color"/> value based on a packed value in the ABGR format.
- /// </summary>
- /// <remarks>
- /// This is useful for when you have HTML hex style values such as #123456 and want to use it in hex format for
- /// the parameter. Since Color's standard format is RGBA, you would have to do new Color(0xFF563212) since R
- /// is the LSB. With this method, you can write it the same way it is written in HTML hex by doing
- /// <c>ColorHelper.FromAbgr(0x123456FF);</c>
- /// </remarks>
- /// <param name="abgr">The packed color value in ABGR format</param>
- /// <returns>The <see cref="Color"/> value created</returns>
- public static Color FromAbgr(uint abgr)
- {
- uint rgba = (abgr & 0x000000FF) << 24 | // Alpha
- (abgr & 0x0000FF00) << 8 | // Blue
- (abgr & 0x00FF0000) >> 8 | // Green
- (abgr & 0xFF000000) >> 24; // Red
- Color result;
- #if FNA
- result = default;
- result.PackedValue = rgba;
- #else
- result = new Color(rgba);
- #endif
- return result;
- }
- [Obsolete("Use HslColor.ToRgb instead. This will be removed in the next major SemVer release.")]
- //http://stackoverflow.com/questions/2353211/hsl-to-rgb-color-conversion
- public static Color FromHsl(float hue, float saturation, float lightness)
- {
- var hsl = new Vector4(hue, saturation, lightness, 1);
- var color = new Vector4(0, 0, 0, hsl.W);
- // ReSharper disable once CompareOfFloatsByEqualityOperator
- if (hsl.Y == 0.0f)
- color.X = color.Y = color.Z = hsl.Z;
- else
- {
- var q = hsl.Z < 0.5f ? hsl.Z * (1.0f + hsl.Y) : hsl.Z + hsl.Y - hsl.Z * hsl.Y;
- var p = 2.0f * hsl.Z - q;
- color.X = HueToRgb(p, q, hsl.X + 1.0f / 3.0f);
- color.Y = HueToRgb(p, q, hsl.X);
- color.Z = HueToRgb(p, q, hsl.X - 1.0f / 3.0f);
- }
- return new Color(color);
- }
- [Obsolete("This will be removed in the next major SemVer release")]
- private static float HueToRgb(float p, float q, float t)
- {
- if (t < 0.0f) t += 1.0f;
- if (t > 1.0f) t -= 1.0f;
- if (t < 1.0f / 6.0f) return p + (q - p) * 6.0f * t;
- if (t < 1.0f / 2.0f) return q;
- if (t < 2.0f / 3.0f) return p + (q - p) * (2.0f / 3.0f - t) * 6.0f;
- return p;
- }
- [Obsolete("Use ColorExtensions.ToHex instead. This will be removed in the next major SemVer release.")]
- public static string ToHex(Color color)
- {
- var rx = $"{color.R:x2}";
- var gx = $"{color.G:x2}";
- var bx = $"{color.B:x2}";
- var ax = $"{color.A:x2}";
- return $"#{rx}{gx}{bx}{ax}";
- }
- }
- }
|