Repository: spectreconsole/spectre.console (csharp)

Issue:
Ability to instantiate a new instance of the Color class with a HEX color as well as RGB.
**Is your feature request related to a problem? Please describe.**
It's not a big problem, but as a web developer and designer, I work with hex color values much more often than RGB values. 

**Describe the solution you'd like**
I really like that you can use hex in _markup_ - but you can't actually create an instance of the `Color` class with a hex value. I have a helper function that automatically converts hex to RGB to fill this void, but it would be great to avoid that step entirely.

**Describe alternatives you've considered**
Just using a helper function to convert to HEX on the fly.

**Additional context**
Example (Note: I'm using a [Powershell wrapper](https://pwshspectreconsole.com/) here, but the implementation is more or less the same)

This first example doesn't work, but would be awesome if it did.

```powershell
using namespace Spectre.Console
$data = @(
    @{ Label = "Oranges"; Value = 10; Color = [Color]::new("#3E85ED")},
    @{ Label = "Apples"; Value = 7; Color = [Color]::new("#3EEDBF")},
    @{ Label = "Blueberries"; Value = 3; Color = [Color]::new("#ED6E3E")}
)
Format-SpectreBarChart -Data $data -Title "[white]Fruit Sales[/]" -Width 80
```
This, of course, works:

```powershell
using namespace Spectre.Console
$data = @(
    @{ Label = "Oranges"; Value = 10; Color = [Color]::new(245,137,57)},
    @{ Label = "Apples"; Value = 7; Color = [Color]::new(255,75,138)},
    @{ Label = "Blueberries"; Value = 3; Color = [Color]::new(91,82,238)}
)
Format-SpectreBarChart -Data $data -Title "[white]Fruit Sales[/]" -Width 80
```
![Code_T5qn7J1NlF](https://github.com/spectreconsole/spectre.console/assets/33441569/da68e572-b177-4251-a7f2-098f263b4993)

The way the BarChart is instantiated / created doesn't involve markup - so I can't use Hex. Hopefully all of this is clear.

Really appreciate the hard work, and any consideration on this improvement. 

---
Please upvote :+1: this issue if you are interested in it.

Pull request description:
Solves issue https://github.com/spectreconsole/spectre.console/issues/1354

I'm not sure if this is desired but seemed like an easy quality of life feature to allow hex to rgb for creating colors.

---
Please upvote :+1: this pull request if you are interested in it.

Patch:
diff --git a/src/Spectre.Console/Color.cs b/src/Spectre.Console/Color.cs
index 38825a991..d3fc25396 100644
--- a/src/Spectre.Console/Color.cs
+++ b/src/Spectre.Console/Color.cs
@@ -213,6 +213,50 @@ public static Color FromInt32(int number)
         return ColorTable.GetColor(number);
     }
 
+    /// <summary>
+    /// Creates a color from a hexadecimal string representation.
+    /// </summary>
+    /// <param name="hex">The hexadecimal string representation of the color.</param>
+    /// <returns>The color created from the hexadecimal string.</returns>
+    public static Color FromHex(string hex)
+    {
+        if (hex is null)
+        {
+            throw new ArgumentNullException(nameof(hex));
+        }
+
+        if (hex.StartsWith("#"))
+        {
+            hex = hex.Substring(1);
+        }
+
+        var r = byte.Parse(hex.Substring(0, 2), NumberStyles.HexNumber);
+        var g = byte.Parse(hex.Substring(2, 2), NumberStyles.HexNumber);
+        var b = byte.Parse(hex.Substring(4, 2), NumberStyles.HexNumber);
+
+        return new Color(r, g, b);
+    }
+
+    /// <summary>
+    /// Tries to convert a hexadecimal color code to a <see cref="Color"/> object.
+    /// </summary>
+    /// <param name="hex">The hexadecimal color code.</param>
+    /// <param name="color">When this method returns, contains the <see cref="Color"/> equivalent of the hexadecimal color code, if the conversion succeeded, or <see cref="Color.Default"/> if the conversion failed.</param>
+    /// <returns><c>true</c> if the conversion succeeded; otherwise, <c>false</c>.</returns>
+    public static bool TryFromHex(string hex, out Color color)
+    {
+        try
+        {
+            color = FromHex(hex);
+            return true;
+        }
+        catch
+        {
+            color = Color.Default;
+            return false;
+        }
+    }
+
     /// <summary>
     /// Converts a <see cref="ConsoleColor"/> to a <see cref="Color"/>.
     /// </summary>


Test patch:
diff --git a/src/Tests/Spectre.Console.Tests/Unit/ColorTests.cs b/src/Tests/Spectre.Console.Tests/Unit/ColorTests.cs
index 5c07195be..e2ea9dde1 100644
--- a/src/Tests/Spectre.Console.Tests/Unit/ColorTests.cs
+++ b/src/Tests/Spectre.Console.Tests/Unit/ColorTests.cs
@@ -1,9 +1,73 @@
+using System.Drawing;
+
 namespace Spectre.Console.Tests.Unit;
 
 public sealed class ColorTests
 {
     public sealed class TheEqualsMethod
     {
+        [Theory]
+        [InlineData("800080")]
+        [InlineData("#800080")]
+        public void Should_Consider_Color_And_Color_From_Hex_Equal(string color)
+        {
+            // Given
+            var color1 = new Color(128, 0, 128);
+
+            // When
+            var color2 = Color.FromHex(color);
+
+            // Then
+            color2.ShouldBe(color1);
+        }
+
+        [Theory]
+        [InlineData("800080")]
+        [InlineData("#800080")]
+        public void Should_Consider_Color_And_Color_Try_From_Hex_Equal(string color)
+        {
+            // Given
+            var color1 = new Color(128, 0, 128);
+
+            // When
+            var result = Color.TryFromHex(color, out var color2);
+
+            // Then
+            result.ShouldBeTrue();
+            color2.ShouldBe(color1);
+        }
+
+        [Theory]
+        [InlineData(null)]
+        [InlineData("")]
+        [InlineData("#")]
+        [InlineData("#80")]
+        [InlineData("FOO")]
+        public void Should_Not_Parse_Non_Color_From_Hex(string noncolor)
+        {
+            // Given, When
+            var result = Record.Exception(() => Color.FromHex(noncolor));
+
+            // Then
+            result.ShouldBeAssignableTo<Exception>();
+        }
+
+        [Theory]
+        [InlineData(null)]
+        [InlineData("")]
+        [InlineData("#")]
+        [InlineData("#80")]
+        [InlineData("FOO")]
+        public void Should_Not_Parse_Non_Color_Try_From_Hex(string noncolor)
+        {
+            // Given, When
+            var result = Color.TryFromHex(noncolor, out var color);
+
+            // Then
+            result.ShouldBeFalse();
+            color.ShouldBe(Color.Default);
+        }
+
         [Fact]
         public void Should_Consider_Color_And_Non_Color_Equal()
         {