Skip to content

Commit f4374c5

Browse files
committed
Add ToHex method to Argb32
1 parent 9e29af2 commit f4374c5

File tree

2 files changed

+209
-0
lines changed

2 files changed

+209
-0
lines changed

src/ImageSharp/PixelFormats/PixelImplementations/Argb32.cs

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Copyright (c) Six Labors.
22
// Licensed under the Six Labors Split License.
33

4+
using System.Buffers.Binary;
5+
using System.Globalization;
46
using System.Numerics;
57
using System.Runtime.CompilerServices;
68
using System.Runtime.InteropServices;
@@ -165,6 +167,64 @@ public uint PackedValue
165167
[MethodImpl(MethodImplOptions.AggressiveInlining)]
166168
public static bool operator !=(Argb32 left, Argb32 right) => !left.Equals(right);
167169

170+
/// <summary>
171+
/// Creates a new instance of the <see cref="Argb32"/> struct
172+
/// from the given hexadecimal string.
173+
/// </summary>
174+
/// <param name="hex">
175+
/// The hexadecimal representation of the combined color components arranged
176+
/// in rgb, argb, rrggbb, or aarrggbb format.
177+
/// </param>
178+
/// <returns>
179+
/// The <see cref="Argb32"/>.
180+
/// </returns>
181+
/// <exception cref="ArgumentException">Hexadecimal string is not in the correct format.</exception>
182+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
183+
public static Argb32 ParseHex(string hex)
184+
{
185+
Guard.NotNull(hex, nameof(hex));
186+
187+
if (!TryParseHex(hex, out Argb32 rgba))
188+
{
189+
throw new ArgumentException("Hexadecimal string is not in the correct format.", nameof(hex));
190+
}
191+
192+
return rgba;
193+
}
194+
195+
/// <summary>
196+
/// Attempts to create a new instance of the <see cref="Argb32"/> struct
197+
/// from the given hexadecimal string.
198+
/// </summary>
199+
/// <param name="hex">
200+
/// The hexadecimal representation of the combined color components arranged
201+
/// in rgb, argb, rrggbb, or aarrggbb format.
202+
/// </param>
203+
/// <param name="result">When this method returns, contains the <see cref="Argb32"/> equivalent of the hexadecimal input.</param>
204+
/// <returns>
205+
/// The <see cref="bool"/>.
206+
/// </returns>
207+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
208+
public static bool TryParseHex(string? hex, out Argb32 result)
209+
{
210+
result = default;
211+
if (string.IsNullOrWhiteSpace(hex))
212+
{
213+
return false;
214+
}
215+
216+
ReadOnlySpan<char> hexSpan = ToArgbHex(hex);
217+
218+
if (!uint.TryParse(hexSpan, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out uint packedValue))
219+
{
220+
return false;
221+
}
222+
223+
packedValue = BinaryPrimitives.ReverseEndianness(packedValue);
224+
result = Unsafe.As<uint, Argb32>(ref packedValue);
225+
return true;
226+
}
227+
168228
/// <inheritdoc />
169229
[MethodImpl(MethodImplOptions.AggressiveInlining)]
170230
public readonly Rgba32 ToRgba32() => Rgba32.FromArgb32(this);
@@ -269,6 +329,16 @@ public static Argb32 FromRgba64(Rgba64 source)
269329
A = ColorNumerics.From16BitTo8Bit(source.A)
270330
};
271331

332+
/// <summary>
333+
/// Converts the value of this instance to a hexadecimal string. The format is AARRGGBB.
334+
/// </summary>
335+
/// <returns>A hexadecimal string representation of the value.</returns>
336+
public readonly string ToHex()
337+
{
338+
uint hexOrder = (uint)((this.B << 0) | (this.G << 8) | (this.R << 16) | (this.A << 24));
339+
return hexOrder.ToString("X8", CultureInfo.InvariantCulture);
340+
}
341+
272342
/// <inheritdoc/>
273343
public override readonly bool Equals(object? obj) => obj is Argb32 argb32 && this.Equals(argb32);
274344

@@ -298,4 +368,46 @@ private static Argb32 Pack(Vector4 vector)
298368
Vector128<byte> result = Vector128.ConvertToInt32(vector.AsVector128()).AsByte();
299369
return new Argb32(result.GetElement(0), result.GetElement(4), result.GetElement(8), result.GetElement(12));
300370
}
371+
372+
/// <summary>
373+
/// Converts the specified hex value to an aarrggbb hex value.
374+
/// </summary>
375+
/// <param name="hexText">The hex value to convert.</param>
376+
/// <returns>
377+
/// A aarrggbb hex value.
378+
/// </returns>
379+
private static ReadOnlySpan<char> ToArgbHex(string hexText)
380+
{
381+
if (hexText.Length == 8)
382+
{
383+
return hexText;
384+
}
385+
386+
ReadOnlySpan<char> hex = hexText.AsSpan();
387+
if (hex[0] == '#')
388+
{
389+
hex = hex[1..];
390+
}
391+
392+
if (hex.Length == 8)
393+
{
394+
return hex;
395+
}
396+
397+
if (hex.Length == 6)
398+
{
399+
return $"FF{hex}";
400+
}
401+
402+
if (hex.Length is < 3 or > 4)
403+
{
404+
return null;
405+
}
406+
407+
var (a, r, g, b) = hex.Length == 3
408+
? ('F', hex[0], hex[1], hex[2])
409+
: (hex[0], hex[1], hex[2], hex[3]);
410+
411+
return new string(new[] { a, a, r, r, g, g, b, b });
412+
}
301413
}

tests/ImageSharp.Tests/PixelFormats/Argb32Tests.cs

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,18 @@ public void AreEqual()
2020
Argb32 color2 = new(new Vector4(0.0f));
2121
Argb32 color3 = new(new Vector4(1f, 0.0f, 1f, 1f));
2222
Argb32 color4 = new(1f, 0.0f, 1f, 1f);
23+
Argb32 color5 = new(0, 0, 0, 1F);
24+
Argb32 color6 = Argb32.ParseHex("#000");
25+
Argb32 color7 = Argb32.ParseHex("#F000");
26+
Argb32 color8 = Argb32.ParseHex("#000000");
27+
Argb32 color9 = Argb32.ParseHex("#FF000000");
2328

2429
Assert.Equal(color1, color2);
2530
Assert.Equal(color3, color4);
31+
Assert.Equal(color5, color6);
32+
Assert.Equal(color5, color7);
33+
Assert.Equal(color5, color8);
34+
Assert.Equal(color5, color9);
2635
}
2736

2837
/// <summary>
@@ -71,6 +80,94 @@ public void ConstructorAssignsProperties()
7180
Assert.Equal(Math.Round(.5f * 255), color5.A);
7281
}
7382

83+
/// <summary>
84+
/// Tests whether FromHex and ToHex work correctly.
85+
/// </summary>
86+
[Fact]
87+
public void FromAndToHex()
88+
{
89+
// 8 digit hex. AARRGGBB
90+
Argb32 color = Argb32.ParseHex("#AABBCCDD");
91+
Assert.Equal(0xAA, color.A);
92+
Assert.Equal(0xBB, color.R);
93+
Assert.Equal(0xCC, color.G);
94+
Assert.Equal(0xDD, color.B);
95+
96+
Assert.Equal("AABBCCDD", color.ToHex());
97+
98+
color.R = 0;
99+
100+
Assert.Equal("AA00CCDD", color.ToHex());
101+
102+
color.A = 255;
103+
104+
Assert.Equal("FF00CCDD", color.ToHex());
105+
}
106+
107+
[Fact]
108+
public void Argb32_TryParseHex()
109+
{
110+
Assert.True(Argb32.TryParseHex("000", out Argb32 color));
111+
Assert.Equal("FF000000", color.ToHex());
112+
113+
Assert.True(Argb32.TryParseHex("#000", out color));
114+
Assert.Equal("FF000000", color.ToHex());
115+
116+
Assert.True(Argb32.TryParseHex("0000", out color));
117+
Assert.Equal("00000000", color.ToHex());
118+
119+
Assert.True(Argb32.TryParseHex("#0000", out color));
120+
Assert.Equal("00000000", color.ToHex());
121+
122+
Assert.True(Argb32.TryParseHex("000000", out color));
123+
Assert.Equal("FF000000", color.ToHex());
124+
125+
Assert.True(Argb32.TryParseHex("#000000", out color));
126+
Assert.Equal("FF000000", color.ToHex());
127+
128+
Assert.True(Argb32.TryParseHex("abc", out color));
129+
Assert.Equal("FFAABBCC", color.ToHex());
130+
131+
Assert.True(Argb32.TryParseHex("ABC", out color));
132+
Assert.Equal("FFAABBCC", color.ToHex());
133+
134+
Assert.True(Argb32.TryParseHex("#ABC", out color));
135+
Assert.Equal("FFAABBCC", color.ToHex());
136+
137+
Assert.True(Argb32.TryParseHex("ABCD", out color));
138+
Assert.Equal("AABBCCDD", color.ToHex());
139+
140+
Assert.True(Argb32.TryParseHex("#ABCD", out color));
141+
Assert.Equal("AABBCCDD", color.ToHex());
142+
143+
Assert.True(Argb32.TryParseHex("AABBCC", out color));
144+
Assert.Equal("FFAABBCC", color.ToHex());
145+
146+
Assert.True(Argb32.TryParseHex("#AABBCC", out color));
147+
Assert.Equal("FFAABBCC", color.ToHex());
148+
149+
Assert.True(Argb32.TryParseHex("AABBCCDD", out color));
150+
Assert.Equal("AABBCCDD", color.ToHex());
151+
152+
Assert.True(Argb32.TryParseHex("#AABBCCDD", out color));
153+
Assert.Equal("AABBCCDD", color.ToHex());
154+
155+
Assert.False(Argb32.TryParseHex(string.Empty, out _));
156+
Assert.False(Argb32.TryParseHex(null, out _));
157+
Assert.False(Argb32.TryParseHex("A", out _));
158+
Assert.False(Argb32.TryParseHex("#A", out _));
159+
Assert.False(Argb32.TryParseHex("AB", out _));
160+
Assert.False(Argb32.TryParseHex("#AB", out _));
161+
Assert.False(Argb32.TryParseHex("ABCDE", out _));
162+
Assert.False(Argb32.TryParseHex("#ABCDE", out _));
163+
Assert.False(Argb32.TryParseHex("ABCDEFG", out _));
164+
Assert.False(Argb32.TryParseHex("#ABCDEFG", out _));
165+
Assert.False(Argb32.TryParseHex("ABCD123", out _));
166+
Assert.False(Argb32.TryParseHex("#ABCD123", out _));
167+
Assert.False(Argb32.TryParseHex("ABCD12345", out _));
168+
Assert.False(Argb32.TryParseHex("#ABCD12345", out _));
169+
}
170+
74171
[Fact]
75172
public void Argb32_PackedValue()
76173
{

0 commit comments

Comments
 (0)