Skip to content

Commit c523cfa

Browse files
committed
Make parsing to be closer to the original implementation
1 parent d59d836 commit c523cfa

File tree

3 files changed

+128
-197
lines changed

3 files changed

+128
-197
lines changed

src/Graphics/src/Graphics/Color.cs

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -339,35 +339,27 @@ public static Color FromRgba(double r, double g, double b, double a)
339339

340340
static Color FromRgba(ReadOnlySpan<char> colorAsHex)
341341
{
342-
if (ColorUtils.TryParseRgbaHexFormat(colorAsHex, out float red, out float green, out float blue, out float alpha))
343-
{
344-
return new Color(red, green, blue, alpha);
345-
}
346-
347-
return FromRgba(0f, 0f, 0f, 1f);
342+
var (r, g, b, a) = ColorUtils.FromRgba(colorAsHex);
343+
return new Color(r, g, b, a);
348344
}
349345

350346
public static Color FromArgb(string colorAsHex) => FromArgb(colorAsHex != null ? colorAsHex.AsSpan() : default);
351347

352348
static Color FromArgb(ReadOnlySpan<char> colorAsHex)
353349
{
354-
if (ColorUtils.TryParseHex(colorAsHex, out float red, out float green, out float blue, out float alpha))
355-
{
356-
return new Color(red, green, blue, alpha);
357-
}
358-
359-
return FromRgba(0f, 0f, 0f, 1f);
350+
var (r, g, b, a) = ColorUtils.FromArgb(colorAsHex);
351+
return new Color(r, g, b, a);
360352
}
361353

362354
public static Color FromHsla(float h, float s, float l, float a = 1)
363355
{
364-
(float r, float g, float b) = ColorUtils.ConvertHslToRgb(h, s, l);
356+
var (r, g, b) = ColorUtils.ConvertHslToRgb(h, s, l);
365357
return new Color(r, g, b, a);
366358
}
367359

368360
public static Color FromHsla(double h, double s, double l, double a = 1)
369361
{
370-
(float r, float g, float b) = ColorUtils.ConvertHslToRgb((float)h, (float)s, (float)l);
362+
var (r, g, b) = ColorUtils.ConvertHslToRgb((float)h, (float)s, (float)l);
371363
return new Color(r, g, b, (float)a);
372364
}
373365

src/Graphics/src/Graphics/ColorUtils.cs

Lines changed: 119 additions & 183 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,15 @@ public static bool TryParse(ReadOnlySpan<char> value, out float red, out float g
1515

1616
if (value[0] == '#')
1717
{
18-
return TryParseHex(value, out red, out green, out blue, out alpha);
18+
try
19+
{
20+
(red, green, blue, alpha) = FromArgb(value);
21+
return true;
22+
}
23+
catch
24+
{
25+
return false;
26+
}
1927
}
2028

2129
if (value.StartsWith("rgba".AsSpan(), StringComparison.OrdinalIgnoreCase))
@@ -163,187 +171,6 @@ public static bool TryParse(ReadOnlySpan<char> value, out float red, out float g
163171
return false;
164172
}
165173

166-
/// <summary>
167-
/// Attempts to parse a hex color string (with or without #).
168-
/// Supports formats: #RGB, #ARGB, #RRGGBB, #AARRGGBB
169-
/// Uses ARGB format for 4-digit and 8-digit hex values.
170-
/// </summary>
171-
/// <param name="colorAsHex">The hex color string</param>
172-
/// <param name="red">The red component (0.0-1.0)</param>
173-
/// <param name="green">The green component (0.0-1.0)</param>
174-
/// <param name="blue">The blue component (0.0-1.0)</param>
175-
/// <param name="alpha">The alpha component (0.0-1.0)</param>
176-
/// <returns>True if parsing succeeded, false otherwise</returns>
177-
public static bool TryParseHex(ReadOnlySpan<char> colorAsHex, out float red, out float green, out float blue, out float alpha)
178-
{
179-
red = green = blue = 0;
180-
alpha = 1;
181-
182-
if (colorAsHex.IsEmpty)
183-
return false;
184-
185-
//Skip # if present
186-
if (colorAsHex[0] == '#')
187-
colorAsHex = colorAsHex.Slice(1);
188-
189-
try
190-
{
191-
return TryParseArgbHex(colorAsHex, out red, out green, out blue, out alpha);
192-
}
193-
catch
194-
{
195-
return false;
196-
}
197-
}
198-
199-
/// <summary>
200-
/// Attempts to parse a hex color string in RGBA format (with or without #).
201-
/// Supports formats: #RGB, #RGBA, #RRGGBB, #RRGGBBAA
202-
/// Uses RGBA format for 4-digit and 8-digit hex values.
203-
/// </summary>
204-
/// <param name="colorAsHex">The hex color string</param>
205-
/// <param name="red">The red component (0.0-1.0)</param>
206-
/// <param name="green">The green component (0.0-1.0)</param>
207-
/// <param name="blue">The blue component (0.0-1.0)</param>
208-
/// <param name="alpha">The alpha component (0.0-1.0)</param>
209-
/// <returns>True if parsing succeeded, false otherwise</returns>
210-
public static bool TryParseRgbaHexFormat(ReadOnlySpan<char> colorAsHex, out float red, out float green, out float blue, out float alpha)
211-
{
212-
red = green = blue = 0;
213-
alpha = 1;
214-
215-
if (colorAsHex.IsEmpty)
216-
return false;
217-
218-
//Skip # if present
219-
if (colorAsHex[0] == '#')
220-
colorAsHex = colorAsHex.Slice(1);
221-
222-
try
223-
{
224-
return TryParseRgbaHex(colorAsHex, out red, out green, out blue, out alpha);
225-
}
226-
catch
227-
{
228-
return false;
229-
}
230-
}
231-
232-
/// <summary>
233-
/// Parses hex color in ARGB format (#ARGB, #AARRGGBB)
234-
/// </summary>
235-
private static bool TryParseArgbHex(ReadOnlySpan<char> colorAsHex, out float red, out float green, out float blue, out float alpha)
236-
{
237-
int r = 0, g = 0, b = 0, a = 255;
238-
239-
if (colorAsHex.Length == 6)
240-
{
241-
//#RRGGBB
242-
r = ParseInt(colorAsHex.Slice(0, 2));
243-
g = ParseInt(colorAsHex.Slice(2, 2));
244-
b = ParseInt(colorAsHex.Slice(4, 2));
245-
}
246-
else if (colorAsHex.Length == 3)
247-
{
248-
//#RGB
249-
Span<char> temp = stackalloc char[2];
250-
temp[0] = temp[1] = colorAsHex[0];
251-
r = ParseInt(temp);
252-
253-
temp[0] = temp[1] = colorAsHex[1];
254-
g = ParseInt(temp);
255-
256-
temp[0] = temp[1] = colorAsHex[2];
257-
b = ParseInt(temp);
258-
}
259-
else if (colorAsHex.Length == 4)
260-
{
261-
//#ARGB
262-
Span<char> temp = stackalloc char[2];
263-
temp[0] = temp[1] = colorAsHex[0];
264-
a = ParseInt(temp);
265-
266-
temp[0] = temp[1] = colorAsHex[1];
267-
r = ParseInt(temp);
268-
269-
temp[0] = temp[1] = colorAsHex[2];
270-
g = ParseInt(temp);
271-
272-
temp[0] = temp[1] = colorAsHex[3];
273-
b = ParseInt(temp);
274-
}
275-
else if (colorAsHex.Length == 8)
276-
{
277-
//#AARRGGBB
278-
a = ParseInt(colorAsHex.Slice(0, 2));
279-
r = ParseInt(colorAsHex.Slice(2, 2));
280-
g = ParseInt(colorAsHex.Slice(4, 2));
281-
b = ParseInt(colorAsHex.Slice(6, 2));
282-
}
283-
else
284-
{
285-
red = green = blue = alpha = 0;
286-
return false;
287-
}
288-
289-
red = (r / 255f).Clamp(0, 1);
290-
green = (g / 255f).Clamp(0, 1);
291-
blue = (b / 255f).Clamp(0, 1);
292-
alpha = (a / 255f).Clamp(0, 1);
293-
return true;
294-
}
295-
296-
/// <summary>
297-
/// Parses hex color in RGBA format (#RGBA, #RRGGBBAA)
298-
/// For 3-digit and 6-digit (no alpha), delegates to ARGB parsing since they're identical
299-
/// </summary>
300-
private static bool TryParseRgbaHex(ReadOnlySpan<char> colorAsHex, out float red, out float green, out float blue, out float alpha)
301-
{
302-
if (colorAsHex.Length == 3 || colorAsHex.Length == 6)
303-
{
304-
// For 3-digit and 6-digit hex, RGBA and ARGB are the same since there's no alpha
305-
return TryParseArgbHex(colorAsHex, out red, out green, out blue, out alpha);
306-
}
307-
308-
int r = 0, g = 0, b = 0, a = 255;
309-
310-
if (colorAsHex.Length == 4)
311-
{
312-
//#RGBA
313-
Span<char> temp = stackalloc char[2];
314-
temp[0] = temp[1] = colorAsHex[0];
315-
r = ParseInt(temp);
316-
317-
temp[0] = temp[1] = colorAsHex[1];
318-
g = ParseInt(temp);
319-
320-
temp[0] = temp[1] = colorAsHex[2];
321-
b = ParseInt(temp);
322-
323-
temp[0] = temp[1] = colorAsHex[3];
324-
a = ParseInt(temp);
325-
}
326-
else if (colorAsHex.Length == 8)
327-
{
328-
//#RRGGBBAA
329-
r = ParseInt(colorAsHex.Slice(0, 2));
330-
g = ParseInt(colorAsHex.Slice(2, 2));
331-
b = ParseInt(colorAsHex.Slice(4, 2));
332-
a = ParseInt(colorAsHex.Slice(6, 2));
333-
}
334-
else
335-
{
336-
red = green = blue = alpha = 0;
337-
return false;
338-
}
339-
340-
red = (r / 255f).Clamp(0, 1);
341-
green = (g / 255f).Clamp(0, 1);
342-
blue = (b / 255f).Clamp(0, 1);
343-
alpha = (a / 255f).Clamp(0, 1);
344-
return true;
345-
}
346-
347174
/// <summary>
348175
/// Converts HSL values to RGB.
349176
/// </summary>
@@ -362,7 +189,7 @@ public static (float red, float green, float blue) ConvertHslToRgb(float hue, fl
362189
{
363190
return (luminosity, luminosity, luminosity);
364191
}
365-
192+
366193
float temp2 = luminosity <= 0.5f ? luminosity * (1.0f + saturation) : luminosity + saturation - luminosity * saturation;
367194
float temp1 = 2.0f * luminosity - temp2;
368195

@@ -387,6 +214,115 @@ public static (float red, float green, float blue) ConvertHslToRgb(float hue, fl
387214
return (clr[0], clr[1], clr[2]);
388215
}
389216

217+
public static (float red, float green, float blue, float alpha) FromRgba(ReadOnlySpan<char> colorAsHex)
218+
{
219+
int red = 0;
220+
int green = 0;
221+
int blue = 0;
222+
int alpha = 255;
223+
224+
if (!colorAsHex.IsEmpty)
225+
{
226+
//Skip # if present
227+
if (colorAsHex[0] == '#')
228+
colorAsHex = colorAsHex.Slice(1);
229+
230+
if (colorAsHex.Length == 6 || colorAsHex.Length == 3)
231+
{
232+
//#RRGGBB or #RGB - since there is no A, use FromArgb
233+
234+
return FromArgb(colorAsHex);
235+
}
236+
else if (colorAsHex.Length == 4)
237+
{
238+
//#RGBA
239+
Span<char> temp = stackalloc char[2];
240+
temp[0] = temp[1] = colorAsHex[0];
241+
red = ParseInt(temp);
242+
243+
temp[0] = temp[1] = colorAsHex[1];
244+
green = ParseInt(temp);
245+
246+
temp[0] = temp[1] = colorAsHex[2];
247+
blue = ParseInt(temp);
248+
249+
temp[0] = temp[1] = colorAsHex[3];
250+
alpha = ParseInt(temp);
251+
}
252+
else if (colorAsHex.Length == 8)
253+
{
254+
//#RRGGBBAA
255+
red = ParseInt(colorAsHex.Slice(0, 2));
256+
green = ParseInt(colorAsHex.Slice(2, 2));
257+
blue = ParseInt(colorAsHex.Slice(4, 2));
258+
alpha = ParseInt(colorAsHex.Slice(6, 2));
259+
}
260+
}
261+
262+
return (red / 255f, green / 255f, blue / 255f, alpha / 255f);
263+
}
264+
265+
public static (float red, float green, float blue, float alpha) FromArgb(ReadOnlySpan<char> colorAsHex)
266+
{
267+
int red = 0;
268+
int green = 0;
269+
int blue = 0;
270+
int alpha = 255;
271+
272+
if (!colorAsHex.IsEmpty)
273+
{
274+
//Skip # if present
275+
if (colorAsHex[0] == '#')
276+
colorAsHex = colorAsHex.Slice(1);
277+
278+
if (colorAsHex.Length == 6)
279+
{
280+
//#RRGGBB
281+
red = ParseInt(colorAsHex.Slice(0, 2));
282+
green = ParseInt(colorAsHex.Slice(2, 2));
283+
blue = ParseInt(colorAsHex.Slice(4, 2));
284+
}
285+
else if (colorAsHex.Length == 3)
286+
{
287+
//#RGB
288+
Span<char> temp = stackalloc char[2];
289+
temp[0] = temp[1] = colorAsHex[0];
290+
red = ParseInt(temp);
291+
292+
temp[0] = temp[1] = colorAsHex[1];
293+
green = ParseInt(temp);
294+
295+
temp[0] = temp[1] = colorAsHex[2];
296+
blue = ParseInt(temp);
297+
}
298+
else if (colorAsHex.Length == 4)
299+
{
300+
//#ARGB
301+
Span<char> temp = stackalloc char[2];
302+
temp[0] = temp[1] = colorAsHex[0];
303+
alpha = ParseInt(temp);
304+
305+
temp[0] = temp[1] = colorAsHex[1];
306+
red = ParseInt(temp);
307+
308+
temp[0] = temp[1] = colorAsHex[2];
309+
green = ParseInt(temp);
310+
311+
temp[0] = temp[1] = colorAsHex[3];
312+
blue = ParseInt(temp);
313+
}
314+
else if (colorAsHex.Length == 8)
315+
{
316+
//#AARRGGBB
317+
alpha = ParseInt(colorAsHex.Slice(0, 2));
318+
red = ParseInt(colorAsHex.Slice(2, 2));
319+
green = ParseInt(colorAsHex.Slice(4, 2));
320+
blue = ParseInt(colorAsHex.Slice(6, 2));
321+
}
322+
}
323+
324+
return (red / 255f, green / 255f, blue / 255f, alpha / 255f);
325+
}
390326
/// <summary>
391327
/// Converts HSV values to RGB.
392328
/// </summary>

src/Graphics/tests/Graphics.Tests/ColorUnitTests.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,9 @@ public static IEnumerable<object[]> TestFromArgbValuesHash()
372372
yield return new object[] { "#a222", Color.FromRgba(0x22, 0x22, 0x22, 0xaa) };
373373
yield return new object[] { "#F2E2D2", Color.FromRgb(0xF2, 0xE2, 0xD2) };
374374
yield return new object[] { "#C2F2E2D2", Color.FromRgba(0xF2, 0xE2, 0xD2, 0xC2) };
375+
yield return new object[] { "#000000", Color.FromRgba(0x00, 0x00, 0x00, 0xFF) };
376+
yield return new object[] { "#000", Color.FromRgba(0x00, 0x00, 0x00, 0xFF) };
377+
yield return new object[] { "#00FFff 40%", Color.FromRgba(0f, 0f, 0f, 1f) }; // unsupported syntax, but should not throw and fall back to the default black
375378
}
376379

377380
public static IEnumerable<object[]> TestFromArgbValuesNoHash()

0 commit comments

Comments
 (0)