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}";
}
}
}