diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Argb32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Argb32.cs
index b74f5db718..0e8681c429 100644
--- a/src/ImageSharp/PixelFormats/PixelImplementations/Argb32.cs
+++ b/src/ImageSharp/PixelFormats/PixelImplementations/Argb32.cs
@@ -1,6 +1,8 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
+using System.Buffers.Binary;
+using System.Globalization;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -165,6 +167,64 @@ public uint PackedValue
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(Argb32 left, Argb32 right) => !left.Equals(right);
+ ///
+ /// Creates a new instance of the struct
+ /// from the given hexadecimal string.
+ ///
+ ///
+ /// The hexadecimal representation of the combined color components arranged
+ /// in rgb, argb, rrggbb, or aarrggbb format.
+ ///
+ ///
+ /// The .
+ ///
+ /// Hexadecimal string is not in the correct format.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Argb32 ParseHex(string hex)
+ {
+ Guard.NotNull(hex, nameof(hex));
+
+ if (!TryParseHex(hex, out Argb32 rgba))
+ {
+ throw new ArgumentException("Hexadecimal string is not in the correct format.", nameof(hex));
+ }
+
+ return rgba;
+ }
+
+ ///
+ /// Attempts to create a new instance of the struct
+ /// from the given hexadecimal string.
+ ///
+ ///
+ /// The hexadecimal representation of the combined color components arranged
+ /// in rgb, argb, rrggbb, or aarrggbb format.
+ ///
+ /// When this method returns, contains the equivalent of the hexadecimal input.
+ ///
+ /// The .
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool TryParseHex(string? hex, out Argb32 result)
+ {
+ result = default;
+ if (string.IsNullOrWhiteSpace(hex))
+ {
+ return false;
+ }
+
+ ReadOnlySpan hexSpan = ToArgbHex(hex);
+
+ if (!uint.TryParse(hexSpan, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out uint packedValue))
+ {
+ return false;
+ }
+
+ packedValue = BinaryPrimitives.ReverseEndianness(packedValue);
+ result = Unsafe.As(ref packedValue);
+ return true;
+ }
+
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly Rgba32 ToRgba32() => Rgba32.FromArgb32(this);
@@ -269,6 +329,16 @@ public static Argb32 FromRgba64(Rgba64 source)
A = ColorNumerics.From16BitTo8Bit(source.A)
};
+ ///
+ /// Converts the value of this instance to a hexadecimal string. The format is AARRGGBB.
+ ///
+ /// A hexadecimal string representation of the value.
+ public readonly string ToHex()
+ {
+ uint hexOrder = (uint)((this.B << 0) | (this.G << 8) | (this.R << 16) | (this.A << 24));
+ return hexOrder.ToString("X8", CultureInfo.InvariantCulture);
+ }
+
///
public override readonly bool Equals(object? obj) => obj is Argb32 argb32 && this.Equals(argb32);
@@ -298,4 +368,46 @@ private static Argb32 Pack(Vector4 vector)
Vector128 result = Vector128.ConvertToInt32(vector.AsVector128()).AsByte();
return new Argb32(result.GetElement(0), result.GetElement(4), result.GetElement(8), result.GetElement(12));
}
+
+ ///
+ /// Converts the specified hex value to an aarrggbb hex value.
+ ///
+ /// The hex value to convert.
+ ///
+ /// A aarrggbb hex value.
+ ///
+ private static ReadOnlySpan ToArgbHex(string hexText)
+ {
+ if (hexText.Length == 8)
+ {
+ return hexText;
+ }
+
+ ReadOnlySpan hex = hexText.AsSpan();
+ if (hex[0] == '#')
+ {
+ hex = hex[1..];
+ }
+
+ if (hex.Length == 8)
+ {
+ return hex;
+ }
+
+ if (hex.Length == 6)
+ {
+ return $"FF{hex}";
+ }
+
+ if (hex.Length is < 3 or > 4)
+ {
+ return null;
+ }
+
+ var (a, r, g, b) = hex.Length == 3
+ ? ('F', hex[0], hex[1], hex[2])
+ : (hex[0], hex[1], hex[2], hex[3]);
+
+ return new string(new[] { a, a, r, r, g, g, b, b });
+ }
}
diff --git a/tests/ImageSharp.Tests/PixelFormats/Argb32Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Argb32Tests.cs
index bcaf9265a3..967fca611b 100644
--- a/tests/ImageSharp.Tests/PixelFormats/Argb32Tests.cs
+++ b/tests/ImageSharp.Tests/PixelFormats/Argb32Tests.cs
@@ -20,9 +20,18 @@ public void AreEqual()
Argb32 color2 = new(new Vector4(0.0f));
Argb32 color3 = new(new Vector4(1f, 0.0f, 1f, 1f));
Argb32 color4 = new(1f, 0.0f, 1f, 1f);
+ Argb32 color5 = new(0, 0, 0, 1F);
+ Argb32 color6 = Argb32.ParseHex("#000");
+ Argb32 color7 = Argb32.ParseHex("#F000");
+ Argb32 color8 = Argb32.ParseHex("#000000");
+ Argb32 color9 = Argb32.ParseHex("#FF000000");
Assert.Equal(color1, color2);
Assert.Equal(color3, color4);
+ Assert.Equal(color5, color6);
+ Assert.Equal(color5, color7);
+ Assert.Equal(color5, color8);
+ Assert.Equal(color5, color9);
}
///
@@ -71,6 +80,94 @@ public void ConstructorAssignsProperties()
Assert.Equal(Math.Round(.5f * 255), color5.A);
}
+ ///
+ /// Tests whether FromHex and ToHex work correctly.
+ ///
+ [Fact]
+ public void FromAndToHex()
+ {
+ // 8 digit hex. AARRGGBB
+ Argb32 color = Argb32.ParseHex("#AABBCCDD");
+ Assert.Equal(0xAA, color.A);
+ Assert.Equal(0xBB, color.R);
+ Assert.Equal(0xCC, color.G);
+ Assert.Equal(0xDD, color.B);
+
+ Assert.Equal("AABBCCDD", color.ToHex());
+
+ color.R = 0;
+
+ Assert.Equal("AA00CCDD", color.ToHex());
+
+ color.A = 255;
+
+ Assert.Equal("FF00CCDD", color.ToHex());
+ }
+
+ [Fact]
+ public void Argb32_TryParseHex()
+ {
+ Assert.True(Argb32.TryParseHex("000", out Argb32 color));
+ Assert.Equal("FF000000", color.ToHex());
+
+ Assert.True(Argb32.TryParseHex("#000", out color));
+ Assert.Equal("FF000000", color.ToHex());
+
+ Assert.True(Argb32.TryParseHex("0000", out color));
+ Assert.Equal("00000000", color.ToHex());
+
+ Assert.True(Argb32.TryParseHex("#0000", out color));
+ Assert.Equal("00000000", color.ToHex());
+
+ Assert.True(Argb32.TryParseHex("000000", out color));
+ Assert.Equal("FF000000", color.ToHex());
+
+ Assert.True(Argb32.TryParseHex("#000000", out color));
+ Assert.Equal("FF000000", color.ToHex());
+
+ Assert.True(Argb32.TryParseHex("abc", out color));
+ Assert.Equal("FFAABBCC", color.ToHex());
+
+ Assert.True(Argb32.TryParseHex("ABC", out color));
+ Assert.Equal("FFAABBCC", color.ToHex());
+
+ Assert.True(Argb32.TryParseHex("#ABC", out color));
+ Assert.Equal("FFAABBCC", color.ToHex());
+
+ Assert.True(Argb32.TryParseHex("ABCD", out color));
+ Assert.Equal("AABBCCDD", color.ToHex());
+
+ Assert.True(Argb32.TryParseHex("#ABCD", out color));
+ Assert.Equal("AABBCCDD", color.ToHex());
+
+ Assert.True(Argb32.TryParseHex("AABBCC", out color));
+ Assert.Equal("FFAABBCC", color.ToHex());
+
+ Assert.True(Argb32.TryParseHex("#AABBCC", out color));
+ Assert.Equal("FFAABBCC", color.ToHex());
+
+ Assert.True(Argb32.TryParseHex("AABBCCDD", out color));
+ Assert.Equal("AABBCCDD", color.ToHex());
+
+ Assert.True(Argb32.TryParseHex("#AABBCCDD", out color));
+ Assert.Equal("AABBCCDD", color.ToHex());
+
+ Assert.False(Argb32.TryParseHex(string.Empty, out _));
+ Assert.False(Argb32.TryParseHex(null, out _));
+ Assert.False(Argb32.TryParseHex("A", out _));
+ Assert.False(Argb32.TryParseHex("#A", out _));
+ Assert.False(Argb32.TryParseHex("AB", out _));
+ Assert.False(Argb32.TryParseHex("#AB", out _));
+ Assert.False(Argb32.TryParseHex("ABCDE", out _));
+ Assert.False(Argb32.TryParseHex("#ABCDE", out _));
+ Assert.False(Argb32.TryParseHex("ABCDEFG", out _));
+ Assert.False(Argb32.TryParseHex("#ABCDEFG", out _));
+ Assert.False(Argb32.TryParseHex("ABCD123", out _));
+ Assert.False(Argb32.TryParseHex("#ABCD123", out _));
+ Assert.False(Argb32.TryParseHex("ABCD12345", out _));
+ Assert.False(Argb32.TryParseHex("#ABCD12345", out _));
+ }
+
[Fact]
public void Argb32_PackedValue()
{