using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using Microsoft.Xna.Framework; namespace MonoGame.Extended { /// /// Provides utility methods for working with values. /// public static class ColorHelper { private static readonly Dictionary s_colorsByName = typeof(Color) .GetRuntimeProperties() .Where(p => p.PropertyType == typeof(Color)) .ToDictionary(p => p.Name, p => (Color)p.GetValue(null), StringComparer.OrdinalIgnoreCase); /// /// Converts a hexadecimal color string to a value. /// /// /// The hexadecimal color string to convert. Supports multiple formats: /// /// 3 characters: RGB shorthand (e.g., "F0A" becomes "FF00AA") /// 4 characters: RGBA shorthand (e.g., "F0A8" becomes "FF00AA88") /// 6 characters: Full RGB format (e.g., "FF00AA") /// 8 characters: Full RGBA format (e.g., "FF00AA88") /// /// Optional '#' prefix is automatically handled and removed. /// /// /// A value representing the parsed hexadecimal color, or /// if the input is or an empty string. /// /// /// The length of is not 3, 4, 6, or 8 (excluding a '#' prefix) /// /// /// contains invalid hexadecimal characters. /// /// /// represents a value too large for a 32-bit unsigned integer. /// public static Color FromHex(string value) { if (string.IsNullOrEmpty(value)) { return Color.Transparent; } return FromHex(value.AsSpan()); } /// /// Converts a hexadecimal color span to a value. /// /// /// 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. /// /// /// A read-only span of characters representing the hexadecimal color. Supports multiple formats: /// /// 3 characters: RGB shorthand (e.g., "F0A" becomes "FF00AA") /// 4 characters: RGBA shorthand (e.g., "F0A8" becomes "FF00AA88") /// 6 characters: Full RGB format (e.g., "FF00AA") /// 8 characters: Full RGBA format (e.g., "FF00AA88") /// /// Optional '#' prefix is automatically handled and removed. /// /// /// A value representing the parsed hexadecimal color, or /// if the input is is empty. /// /// /// The length of is not 3, 4, 6, or 8 (excluding a '#' prefix) /// /// /// contains invalid hexadecimal characters. /// /// /// represents a value too large for a 32-bit unsigned integer. /// public static Color FromHex(ReadOnlySpan 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); } /// /// Creates a value from the specified name of a predefined color. /// Gets a value from a p /// /// The name of the predefined color. /// /// The value this method creates. /// /// is not a valid color. 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"); } /// /// Returns a new value based on a packed value in the ABGR format. /// /// /// 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 /// ColorHelper.FromAbgr(0x123456FF); /// /// The packed color value in ABGR format /// The value created 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}"; } } }