I've elaborated on the response provided by Thomas to discuss the #RGBA/#RRGGBBAA formats.
If there are errors during parsing, I personally opt to simply return Color.Empty instead of handling them differently.
(Exploring some C# code after adding unit tests in Typescript led me to realize that ColorTranslator does not consider alpha values - it's crucial to test thoroughly instead of assuming anything!)
public static ColorUtils
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "ColorTranslator throws System.Exception")]
public static Color HtmlToColor(string htmlColor)
{
Guard.ArgumentIsNotNull(htmlColor, nameof(htmlColor));
var htmlLowerCase = htmlColor.ToLower().Trim();
try
{
if (htmlLowerCase.StartsWith("rgb"))
{
return ArgbToColor(htmlLowerCase);
}
else if (htmlLowerCase.StartsWith("#"))
{
return HexToColor(htmlLowerCase);
}
else
{
// Fallback to ColorTranslator for named colors, e.g. "Black", "White" etc.
return ColorTranslator.FromHtml(htmlLowerCase);
}
}
catch
{
// ColorTranslator throws System.Exception, don't really care what the actual error is.
}
return Color.Empty;
}
private static Color HexToColor(string htmlLowerCase)
{
var len = htmlLowerCase.Length;
// #RGB
if (len == 4)
{
var r = Convert.ToInt32(htmlLowerCase.Substring(1, 1), 16);
var g = Convert.ToInt32(htmlLowerCase.Substring(2, 1), 16);
var b = Convert.ToInt32(htmlLowerCase.Substring(3, 1), 16);
return Color.FromArgb(r + (r * 16), g + (g * 16), b + (b * 16));
}
// #RGBA
else if (len == 5)
{
var r = Convert.ToInt32(htmlLowerCase.Substring(1, 1), 16);
var g = Convert.ToInt32(htmlLowerCase.Substring(2, 1), 16);
var b = Convert.ToInt32(htmlLowerCase.Substring(3, 1), 16);
var a = Convert.ToInt32(htmlLowerCase.Substring(4, 1), 16);
return Color.FromArgb(a + (a * 16), r + (r * 16), g + (g * 16), b + (b * 16));
}
// #RRGGBB
else if (len == 7)
{
return Color.FromArgb(
Convert.ToInt32(htmlLowerCase.Substring(1, 2), 16),
Convert.ToInt32(htmlLowerCase.Substring(3, 2), 16),
Convert.ToInt32(htmlLowerCase.Substring(5, 2), 16));
}
// #RRGGBBAA
else if (len == 9)
{
return Color.FromArgb(
Convert.ToInt32(htmlLowerCase.Substring(7, 2), 16),
Convert.ToInt32(htmlLowerCase.Substring(1, 2), 16),
Convert.ToInt32(htmlLowerCase.substring(3, 2), 16),
Convert.ToInt32(htmlLowerCase.substring(5, 2), 16));
}
return Color.Empty;
}
private static Color ArgbToColor(string htmlLowerCase)
{
int left = htmlLowerCase.IndexOf('(');
int right = htmlLowerCase.IndexOf(')');
if (left < 0 || right < 0)
{
return Color.Empty;
}
string noBrackets = htmlLowerCase.Substring(left + 1, right - left - 1);
string[] parts = noBrackets.Split(',');
int r = int.Parse(parts[0], CultureInfo.InvariantCulture);
int g = int.Parse(parts[1], CultureInfo.InvariantCulture);
int b = int.Parse(parts[2], CultureInfo.InvariantCulture);
if (parts.Length == 3)
{
return Color.FromArgb(r, g, b);
}
else if (parts.Length == 4)
{
float a = float.Parse(parts[3], CultureInfo.InvariantCulture);
return Color.FromArgb((int)(a * 255), r, g, b);
}
return Color.Empty;
}
}
Tests:
[TestMethod]
public void ColorUtils_HtmlToColor()
{
Assert.AreEqual(Color.FromArgb(0x11, 0x22, 0x33), ColorUtils.HtmlToColor("#123"), "#123 (without alpha channel)");
Assert.AreEqual(Color.FromArgb(0x11, 0x22, 0x33), ColorUtils.HtmlToColor("#112233"), "#112233 (without alpha channel)");
Assert.AreEqual(Color.FromArgb(0x44, 0x11, 0x22, 0x33), ColorUtils.HtmlToColor("#1234"), "#1234 (alpha channel)");
Assert.AreEqual(Color.FromArgb(0x44, 0x11, 0x22, 0x33), ColorUtils.HtmlToColor("#11223344"), "#11223344 (alpha channel)");
Assert.AreEqual(Color.FromArgb(127, 11, 22, 33), ColorUtils.HtmlToColor("rgba(11,22,33,0.5)"), "rgba(11,22,33,0.5) (alpha channel)");
Assert.AreEqual(Color.FromArgb(11, 22, 33), ColorUtils.HtmlToColor("rgb(11,22,33)"), "rgb(11,22,33) (alpha channel)");
Assert.AreEqual(Color.Red, ColorUtils.HtmlToColor("red"), "red (named color)");
Assert.AreEqual(Color.White, ColorUtils.HtmlToColor("white"), "white (named color)");
Assert.AreEqual(Color.Blue, ColorUtils.HtmlToColor("blue"), "blue (named color)");
Assert.AreEqual(Color.Empty, ColorUtils.HtmlToColor("invalid"), "invalid");
Assert.AreEqual(Color.Empty, ColorUtils.HtmlToColor("#invalid"), "#invalid");
Assert.AreEqual(Color.Empty, ColorUtils.HtmlToColor("rgb(invalid)"), "rgb(invalid)");
Assert.AreEqual(Color.Empty, ColorUtils.HtmlToColor("rgba(invalid)"), "rgba(invalid)");
Assert.AreEqual(Color.Empty, ColorUtils.HtmlToColor("rgba(invalid,invalid)"), "rgba(invalid,invalid)");
Assert.AreEqual(Color.Empty, ColorUtils.HtmlToColor("rgb(11,22,333)"), "rgb(11,22,333) (value out of range)");
Assert.AreEqual(Color.Empty, ColorUtils.HtmlToColor("rgba(11,22,333,0.5)"), "rgb(11,22,333,0.5) (value out of range)");
}