Spanify FromRgba.
Fix Color.TryParse to not throw exceptions.
Add Grey colors to the named table.
This commit is contained in:
Eric Erhardt 2022-03-09 18:24:56 -06:00
Родитель 9cace0d81c
Коммит 2fc1a9e6db
2 изменённых файлов: 232 добавлений и 75 удалений

Просмотреть файл

@ -337,46 +337,51 @@ namespace Microsoft.Maui.Graphics
return new Color((float)r, (float)g, (float)b, (float)a); return new Color((float)r, (float)g, (float)b, (float)a);
} }
public static Color FromRgba(string colorAsHex) public static Color FromRgba(string colorAsHex) => FromRgba(colorAsHex != null ? colorAsHex.AsSpan() : default);
{
//Remove # if present
if (colorAsHex.IndexOf('#') != -1)
colorAsHex = colorAsHex.Replace("#", "");
static Color FromRgba(ReadOnlySpan<char> colorAsHex)
{
int red = 0; int red = 0;
int green = 0; int green = 0;
int blue = 0; int blue = 0;
int alpha = 255; int alpha = 255;
if (colorAsHex.Length == 6) if (!colorAsHex.IsEmpty)
{ {
//#RRGGBB //Skip # if present
red = int.Parse(colorAsHex.Substring(0, 2), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture); if (colorAsHex[0] == '#')
green = int.Parse(colorAsHex.Substring(2, 2), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture); colorAsHex = colorAsHex.Slice(1);
blue = int.Parse(colorAsHex.Substring(4, 2), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture);
} if (colorAsHex.Length == 6 || colorAsHex.Length == 3)
else if (colorAsHex.Length == 3) {
{ //#RRGGBB or #RGB - since there is no A, use FromArgb
//#RGB
red = int.Parse($"{colorAsHex[0]}{colorAsHex[0]}", NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture); return FromArgb(colorAsHex);
green = int.Parse($"{colorAsHex[1]}{colorAsHex[1]}", NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture); }
blue = int.Parse($"{colorAsHex[2]}{colorAsHex[2]}", NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture); else if (colorAsHex.Length == 4)
} {
else if (colorAsHex.Length == 4) //#RGBA
{ Span<char> temp = stackalloc char[2];
//#RGBA temp[0] = temp[1] = colorAsHex[0];
red = int.Parse($"{colorAsHex[0]}{colorAsHex[0]}", NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture); red = ParseInt(temp);
green = int.Parse($"{colorAsHex[1]}{colorAsHex[1]}", NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture);
blue = int.Parse($"{colorAsHex[2]}{colorAsHex[2]}", NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture); temp[0] = temp[1] = colorAsHex[1];
alpha = int.Parse($"{colorAsHex[3]}{colorAsHex[3]}", NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture); green = ParseInt(temp);
}
else if (colorAsHex.Length == 8) temp[0] = temp[1] = colorAsHex[2];
{ blue = ParseInt(temp);
//#RRGGBBAA
red = int.Parse(colorAsHex.Substring(0, 2), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture); temp[0] = temp[1] = colorAsHex[3];
green = int.Parse(colorAsHex.Substring(2, 2), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture); alpha = ParseInt(temp);
blue = int.Parse(colorAsHex.Substring(4, 2), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture); }
alpha = int.Parse(colorAsHex.Substring(6, 2), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture); else if (colorAsHex.Length == 8)
{
//#RRGGBBAA
red = ParseInt(colorAsHex.Slice(0, 2));
green = ParseInt(colorAsHex.Slice(2, 2));
blue = ParseInt(colorAsHex.Slice(4, 2));
alpha = ParseInt(colorAsHex.Slice(6, 2));
}
} }
return FromRgba(red / 255f, green / 255f, blue / 255f, alpha / 255f); return FromRgba(red / 255f, green / 255f, blue / 255f, alpha / 255f);
@ -600,8 +605,7 @@ namespace Microsoft.Maui.Graphics
} }
catch catch
{ {
color = default; goto ReturnFalse;
return false;
} }
} }
@ -613,14 +617,16 @@ namespace Microsoft.Maui.Graphics
out ReadOnlySpan<char> quad2, out ReadOnlySpan<char> quad2,
out ReadOnlySpan<char> quad3)) out ReadOnlySpan<char> quad3))
{ {
color = default; goto ReturnFalse;
return false;
} }
var r = ParseColorValue(quad0, 255, acceptPercent: true); bool valid = TryParseColorValue(quad0, 255, acceptPercent: true, out double r);
var g = ParseColorValue(quad1, 255, acceptPercent: true); valid &= TryParseColorValue(quad1, 255, acceptPercent: true, out double g);
var b = ParseColorValue(quad2, 255, acceptPercent: true); valid &= TryParseColorValue(quad2, 255, acceptPercent: true, out double b);
var a = ParseOpacity(quad3); valid &= TryParseOpacity(quad3, out double a);
if (!valid)
goto ReturnFalse;
color = new Color((float)r, (float)g, (float)b, (float)a); color = new Color((float)r, (float)g, (float)b, (float)a);
return true; return true;
@ -633,13 +639,15 @@ namespace Microsoft.Maui.Graphics
out ReadOnlySpan<char> triplet1, out ReadOnlySpan<char> triplet1,
out ReadOnlySpan<char> triplet2)) out ReadOnlySpan<char> triplet2))
{ {
color = default; goto ReturnFalse;
return false;
} }
var r = ParseColorValue(triplet0, 255, acceptPercent: true); bool valid = TryParseColorValue(triplet0, 255, acceptPercent: true, out double r);
var g = ParseColorValue(triplet1, 255, acceptPercent: true); valid &= TryParseColorValue(triplet1, 255, acceptPercent: true, out double g);
var b = ParseColorValue(triplet2, 255, acceptPercent: true); valid &= TryParseColorValue(triplet2, 255, acceptPercent: true, out double b);
if (!valid)
goto ReturnFalse;
color = new Color((float)r, (float)g, (float)b); color = new Color((float)r, (float)g, (float)b);
return true; return true;
@ -653,14 +661,16 @@ namespace Microsoft.Maui.Graphics
out ReadOnlySpan<char> quad2, out ReadOnlySpan<char> quad2,
out ReadOnlySpan<char> quad3)) out ReadOnlySpan<char> quad3))
{ {
color = default; goto ReturnFalse;
return false;
} }
var h = ParseColorValue(quad0, 360, acceptPercent: false); bool valid = TryParseColorValue(quad0, 360, acceptPercent: false, out double h);
var s = ParseColorValue(quad1, 100, acceptPercent: true); valid &= TryParseColorValue(quad1, 100, acceptPercent: true, out double s);
var l = ParseColorValue(quad2, 100, acceptPercent: true); valid &= TryParseColorValue(quad2, 100, acceptPercent: true, out double l);
var a = ParseOpacity(quad3); valid &= TryParseOpacity(quad3, out double a);
if (!valid)
goto ReturnFalse;
color = Color.FromHsla(h, s, l, a); color = Color.FromHsla(h, s, l, a);
return true; return true;
@ -673,13 +683,15 @@ namespace Microsoft.Maui.Graphics
out ReadOnlySpan<char> triplet1, out ReadOnlySpan<char> triplet1,
out ReadOnlySpan<char> triplet2)) out ReadOnlySpan<char> triplet2))
{ {
color = default; goto ReturnFalse;
return false;
} }
var h = ParseColorValue(triplet0, 360, acceptPercent: false); bool valid = TryParseColorValue(triplet0, 360, acceptPercent: false, out double h);
var s = ParseColorValue(triplet1, 100, acceptPercent: true); valid &= TryParseColorValue(triplet1, 100, acceptPercent: true, out double s);
var l = ParseColorValue(triplet2, 100, acceptPercent: true); valid &= TryParseColorValue(triplet2, 100, acceptPercent: true, out double l);
if (!valid)
goto ReturnFalse;
color = Color.FromHsla(h, s, l); color = Color.FromHsla(h, s, l);
return true; return true;
@ -693,14 +705,16 @@ namespace Microsoft.Maui.Graphics
out ReadOnlySpan<char> quad2, out ReadOnlySpan<char> quad2,
out ReadOnlySpan<char> quad3)) out ReadOnlySpan<char> quad3))
{ {
color = default; goto ReturnFalse;
return false;
} }
var h = ParseColorValue(quad0, 360, acceptPercent: false); bool valid = TryParseColorValue(quad0, 360, acceptPercent: false, out double h);
var s = ParseColorValue(quad1, 100, acceptPercent: true); valid &= TryParseColorValue(quad1, 100, acceptPercent: true, out double s);
var v = ParseColorValue(quad2, 100, acceptPercent: true); valid &= TryParseColorValue(quad2, 100, acceptPercent: true, out double v);
var a = ParseOpacity(quad3); valid &= TryParseOpacity(quad3, out double a);
if (!valid)
goto ReturnFalse;
color = Color.FromHsva((float)h, (float)s, (float)v, (float)a); color = Color.FromHsva((float)h, (float)s, (float)v, (float)a);
return true; return true;
@ -713,13 +727,15 @@ namespace Microsoft.Maui.Graphics
out ReadOnlySpan<char> triplet1, out ReadOnlySpan<char> triplet1,
out ReadOnlySpan<char> triplet2)) out ReadOnlySpan<char> triplet2))
{ {
color = default; goto ReturnFalse;
return false;
} }
var h = ParseColorValue(triplet0, 360, acceptPercent: false); bool valid = TryParseColorValue(triplet0, 360, acceptPercent: false, out double h);
var s = ParseColorValue(triplet1, 100, acceptPercent: true); valid &= TryParseColorValue(triplet1, 100, acceptPercent: true, out double s);
var v = ParseColorValue(triplet2, 100, acceptPercent: true); valid &= TryParseColorValue(triplet2, 100, acceptPercent: true, out double v);
if (!valid)
goto ReturnFalse;
color = Color.FromHsv((float)h, (float)s, (float)v); color = Color.FromHsv((float)h, (float)s, (float)v);
return true; return true;
@ -733,6 +749,7 @@ namespace Microsoft.Maui.Graphics
} }
} }
ReturnFalse:
color = default; color = default;
return false; return false;
} }
@ -791,6 +808,7 @@ namespace Microsoft.Maui.Graphics
AddColor("darkgoldenrod", Colors.DarkGoldenrod); AddColor("darkgoldenrod", Colors.DarkGoldenrod);
AddColor("darkgray", Colors.DarkGray); AddColor("darkgray", Colors.DarkGray);
AddColor("darkgreen", Colors.DarkGreen); AddColor("darkgreen", Colors.DarkGreen);
AddColor("darkgrey", Colors.DarkGrey);
AddColor("darkkhaki", Colors.DarkKhaki); AddColor("darkkhaki", Colors.DarkKhaki);
AddColor("darkmagenta", Colors.DarkMagenta); AddColor("darkmagenta", Colors.DarkMagenta);
AddColor("darkolivegreen", Colors.DarkOliveGreen); AddColor("darkolivegreen", Colors.DarkOliveGreen);
@ -801,11 +819,13 @@ namespace Microsoft.Maui.Graphics
AddColor("darkseagreen", Colors.DarkSeaGreen); AddColor("darkseagreen", Colors.DarkSeaGreen);
AddColor("darkslateblue", Colors.DarkSlateBlue); AddColor("darkslateblue", Colors.DarkSlateBlue);
AddColor("darkslategray", Colors.DarkSlateGray); AddColor("darkslategray", Colors.DarkSlateGray);
AddColor("darkslategrey", Colors.DarkSlateGrey);
AddColor("darkturquoise", Colors.DarkTurquoise); AddColor("darkturquoise", Colors.DarkTurquoise);
AddColor("darkviolet", Colors.DarkViolet); AddColor("darkviolet", Colors.DarkViolet);
AddColor("deeppink", Colors.DeepPink); AddColor("deeppink", Colors.DeepPink);
AddColor("deepskyblue", Colors.DeepSkyBlue); AddColor("deepskyblue", Colors.DeepSkyBlue);
AddColor("dimgray", Colors.DimGray); AddColor("dimgray", Colors.DimGray);
AddColor("dimgrey", Colors.DimGrey);
AddColor("dodgerblue", Colors.DodgerBlue); AddColor("dodgerblue", Colors.DodgerBlue);
AddColor("firebrick", Colors.Firebrick); AddColor("firebrick", Colors.Firebrick);
AddColor("floralwhite", Colors.FloralWhite); AddColor("floralwhite", Colors.FloralWhite);
@ -817,6 +837,7 @@ namespace Microsoft.Maui.Graphics
AddColor("goldenrod", Colors.Goldenrod); AddColor("goldenrod", Colors.Goldenrod);
AddColor("gray", Colors.Gray); AddColor("gray", Colors.Gray);
AddColor("green", Colors.Green); AddColor("green", Colors.Green);
AddColor("grey", Colors.Grey);
AddColor("greenyellow", Colors.GreenYellow); AddColor("greenyellow", Colors.GreenYellow);
AddColor("honeydew", Colors.Honeydew); AddColor("honeydew", Colors.Honeydew);
AddColor("hotpink", Colors.HotPink); AddColor("hotpink", Colors.HotPink);
@ -840,6 +861,7 @@ namespace Microsoft.Maui.Graphics
AddColor("lightseagreen", Colors.LightSeaGreen); AddColor("lightseagreen", Colors.LightSeaGreen);
AddColor("lightskyblue", Colors.LightSkyBlue); AddColor("lightskyblue", Colors.LightSkyBlue);
AddColor("lightslategray", Colors.LightSlateGray); AddColor("lightslategray", Colors.LightSlateGray);
AddColor("lightslategrey", Colors.LightSlateGrey);
AddColor("lightsteelblue", Colors.LightSteelBlue); AddColor("lightsteelblue", Colors.LightSteelBlue);
AddColor("lightyellow", Colors.LightYellow); AddColor("lightyellow", Colors.LightYellow);
AddColor("lime", Colors.Lime); AddColor("lime", Colors.Lime);
@ -892,6 +914,7 @@ namespace Microsoft.Maui.Graphics
AddColor("skyblue", Colors.SkyBlue); AddColor("skyblue", Colors.SkyBlue);
AddColor("slateblue", Colors.SlateBlue); AddColor("slateblue", Colors.SlateBlue);
AddColor("slategray", Colors.SlateGray); AddColor("slategray", Colors.SlateGray);
AddColor("slategrey", Colors.SlateGrey);
AddColor("snow", Colors.Snow); AddColor("snow", Colors.Snow);
AddColor("springgreen", Colors.SpringGreen); AddColor("springgreen", Colors.SpringGreen);
AddColor("steelblue", Colors.SteelBlue); AddColor("steelblue", Colors.SteelBlue);
@ -1002,7 +1025,7 @@ ReturnFalse:
return false; return false;
} }
static double ParseColorValue(ReadOnlySpan<char> elem, int maxValue, bool acceptPercent) static bool TryParseColorValue(ReadOnlySpan<char> elem, int maxValue, bool acceptPercent, out double value)
{ {
elem = elem.Trim(); elem = elem.Trim();
if (!elem.IsEmpty && elem[elem.Length - 1] == '%' && acceptPercent) if (!elem.IsEmpty && elem[elem.Length - 1] == '%' && acceptPercent)
@ -1010,20 +1033,33 @@ ReturnFalse:
maxValue = 100; maxValue = 100;
elem = elem.Slice(0, elem.Length - 1); elem = elem.Slice(0, elem.Length - 1);
} }
return ParseDouble(elem).Clamp(0, maxValue) / maxValue;
if (TryParseDouble(elem, out value))
{
value = value.Clamp(0, maxValue) / maxValue;
return true;
}
return false;
} }
static double ParseOpacity(ReadOnlySpan<char> elem) static bool TryParseOpacity(ReadOnlySpan<char> elem, out double value)
=> ParseDouble(elem).Clamp(0, 1); {
if (TryParseDouble(elem, out value))
{
value = value.Clamp(0, 1);
return true;
}
return false;
}
static double ParseDouble(ReadOnlySpan<char> s) => static bool TryParseDouble(ReadOnlySpan<char> s, out double value) =>
double.Parse( double.TryParse(
#if NETSTANDARD2_0 #if NETSTANDARD2_0
s.ToString(), s.ToString(),
#else #else
s, s,
#endif #endif
NumberStyles.Number, CultureInfo.InvariantCulture); NumberStyles.Number, CultureInfo.InvariantCulture, out value);
static int ParseInt(ReadOnlySpan<char> s) => static int ParseInt(ReadOnlySpan<char> s) =>
int.Parse( int.Parse(

Просмотреть файл

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection;
using Microsoft.Maui.Graphics; using Microsoft.Maui.Graphics;
using Xunit; using Xunit;
@ -344,5 +345,125 @@ namespace Microsoft.Maui.Graphics.Tests
Assert.Equal(expectedComplement.Green, comp.Green, 3); Assert.Equal(expectedComplement.Green, comp.Green, 3);
Assert.Equal(expectedComplement.Blue, comp.Blue, 3); Assert.Equal(expectedComplement.Blue, comp.Blue, 3);
} }
public static IEnumerable<object[]> TestFromRgbaValues()
{
yield return new object[] { "#111", Color.FromRgb(0x11, 0x11, 0x11) };
yield return new object[] { "#a222", Color.FromRgba(0xaa, 0x22, 0x22, 0x22) };
yield return new object[] { "#F2E2D2", Color.FromRgb(0xF2, 0xE2, 0xD2) };
yield return new object[] { "#C2F2E2D2", Color.FromRgba(0xC2, 0xF2, 0xE2, 0xD2) };
yield return new object[] { "111", Color.FromRgb(0x11, 0x11, 0x11) };
yield return new object[] { "a222", Color.FromRgba(0xaa, 0x22, 0x22, 0x22) };
yield return new object[] { "F2E2D2", Color.FromRgb(0xF2, 0xE2, 0xD2) };
yield return new object[] { "C2F2E2D2", Color.FromRgba(0xC2, 0xF2, 0xE2, 0xD2) };
}
[Theory]
[MemberData(nameof(TestFromRgbaValues))]
public void TestFromRgba(string value, Color expected)
{
Color actual = Color.FromRgba(value);
Assert.Equal(expected, actual);
}
public static IEnumerable<object[]> TestFromArgbValuesHash()
{
yield return new object[] { "#111", Color.FromRgb(0x11, 0x11, 0x11) };
yield return new object[] { "#a222", Color.FromRgba(0x22, 0x22, 0x22, 0xaa) };
yield return new object[] { "#F2E2D2", Color.FromRgb(0xF2, 0xE2, 0xD2) };
yield return new object[] { "#C2F2E2D2", Color.FromRgba(0xF2, 0xE2, 0xD2, 0xC2) };
}
public static IEnumerable<object[]> TestFromArgbValuesNoHash()
{
yield return new object[] { "111", Color.FromRgb(0x11, 0x11, 0x11) };
yield return new object[] { "a222", Color.FromRgba(0x22, 0x22, 0x22, 0xaa) };
yield return new object[] { "F2E2D2", Color.FromRgb(0xF2, 0xE2, 0xD2) };
yield return new object[] { "C2F2E2D2", Color.FromRgba(0xF2, 0xE2, 0xD2, 0xC2) };
}
[Theory]
[MemberData(nameof(TestFromArgbValuesHash))]
[MemberData(nameof(TestFromArgbValuesNoHash))]
public void TestFromArgb(string value, Color expected)
{
Color actual = Color.FromArgb(value);
Assert.Equal(expected, actual);
}
public static IEnumerable<object[]> TestParseValidValues()
{
foreach (object[] argb in TestFromArgbValuesHash())
{
yield return argb;
}
yield return new object[] { "rgb(255,0,0)", Color.FromRgb(255, 0, 0) };
yield return new object[] { "rgb(100%, 0%, 0%)", Color.FromRgb(255, 0, 0) };
yield return new object[] { "rgba(0, 255, 0, 0.7)", Color.FromRgba(0, 255, 0, 0.7f) };
yield return new object[] { "rgba(0%, 100%, 0%, 0.7)", Color.FromRgba(0, 255, 0, 0.7f) };
yield return new object[] { "hsl(120, 100%, 50%)", Color.FromHsla(120f / 360f, 1.0f, .5f) };
yield return new object[] { "hsl(120, 75, 20%)", Color.FromHsla(120f / 360f, .75f, .2f) };
yield return new object[] { "hsla(160, 100%, 50%, .4)", Color.FromHsla(160f / 360f, 1.0f, .5f, .4f) };
yield return new object[] { "hsla(160,100%,50%,.6)", Color.FromHsla(160f / 360f, 1.0f, .5f, .6f) };
yield return new object[] { "hsv(120, 85%, 35%)", Color.FromHsv(120f / 360f, .85f, .35f) };
yield return new object[] { "hsv(120, 85, 35)", Color.FromHsv(120f / 360f, .85f, .35f) };
yield return new object[] { "hsva(120, 100%, 50%, .8)", Color.FromHsva(120f / 360f, 1.0f, .5f, .8f) };
yield return new object[] { "hsva(120, 100, 50, .8)", Color.FromHsva(120f / 360f, 1.0f, .5f, .8f) };
}
[Theory]
[MemberData(nameof(TestParseValidValues))]
public void TestParseValid(string value, Color expected)
{
Assert.True(Color.TryParse(value, out Color actual));
Assert.Equal(expected, actual);
actual = Color.Parse(value);
Assert.Equal(expected, actual);
}
[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData("default")]
[InlineData("notAColor")]
[InlineData("#ZZZ")]
[InlineData("#12g")]
[InlineData("#1g3")]
[InlineData("#zyxv")]
[InlineData("222")]
[InlineData("rgb)255,0,0(")]
[InlineData("rgb255,0,0")]
[InlineData("rgba(255, 0, 0, 0.8")]
[InlineData("hsv(120, 100#, 50#)")]
[InlineData("hsv(120%, 100%, 50%)")]
[InlineData("hsva(120, 120%, 50%, a)")]
public void TestParseBad(string badValue)
{
Assert.False(Color.TryParse(badValue, out Color actual));
Assert.Throws<InvalidOperationException>(() => Color.Parse(badValue));
}
[Fact]
public void TestParseAllBuiltInColors()
{
var fields = typeof(Colors).GetFields(BindingFlags.Public | BindingFlags.Static);
Assert.True(fields.Length > 100, "we should have some Color fields");
foreach (FieldInfo field in fields)
{
string colorName = field.Name;
Color actual = Color.Parse(colorName);
Color expected = (Color)field.GetValue(null);
Assert.Equal(expected, actual);
}
}
} }
} }