Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion Semantics.Color/Color.Conversions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
/// <param name="hex">The hex color string.</param>
/// <returns>The linear-RGB color.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="hex"/> is null.</exception>
/// <exception cref="ArgumentException">Thrown when <paramref name="hex"/> is not a recognised hex length.</exception>
/// <exception cref="ArgumentException">Thrown when <paramref name="hex"/> is not a recognised hex length, or contains a character that is not a hex digit.</exception>
public static Color FromHex(string hex)
{
Ensure.NotNull(hex);
Expand All @@ -62,6 +62,16 @@
throw new ArgumentException("Hex color must be #RGB, #RRGGBB, or #RRGGBBAA.", nameof(hex));
}

// Convert.ToByte(s, 16) accepts a leading '+' and throws FormatException for other bad
// characters, so every character is checked here and only hex digits reach it.
foreach (char c in h)

Check warning on line 67 in Semantics.Color/Color.Conversions.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Loops should be simplified using the "Where" LINQ method

See more on https://sonarcloud.io/project/issues?id=ktsu-dev_Semantics&issues=AaEcLJJ5Q8RC0l5igvfB&open=AaEcLJJ5Q8RC0l5igvfB&pullRequest=399
{
if (!IsHexDigit(c))
{
throw new ArgumentException($"Hex color contains '{c}', which is not a hex digit.", nameof(hex));
}
}

byte r = ParseByte(h, 0);
byte g = ParseByte(h, 2);
byte b = ParseByte(h, 4);
Expand Down Expand Up @@ -99,6 +109,9 @@
private static byte ParseByte(string hex, int index) =>
Convert.ToByte(hex.Substring(index, 2), 16);

private static bool IsHexDigit(char c) =>
c is (>= '0' and <= '9') or (>= 'A' and <= 'F') or (>= 'a' and <= 'f');

/// <summary>Converts this linear color to <see cref="Oklab"/>.</summary>
/// <returns>The Oklab equivalent.</returns>
public Oklab ToOklab() => Oklab.FromColor(this);
Expand Down
23 changes: 23 additions & 0 deletions Semantics.Test/Colors/HexConversionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,27 @@ public void ToHex_EmitsAlphaWhenNotOpaque()
[TestMethod]
public void FromHex_InvalidLength_Throws() =>
Assert.ThrowsExactly<ArgumentException>(() => Color.FromHex("#12345"));

[TestMethod]
[DataRow("#+F+F+F")]
[DataRow("+F+F+F")]
[DataRow("#00+F00")]
[DataRow("#GGGGGG")]
[DataRow("#0x0x0x")]
[DataRow("#-1-1-1")]
[DataRow("#12345G")]
[DataRow("#FFFFFF+F")]
[DataRow("#F F")]
[DataRow("#G00")]
public void FromHex_NonHexCharacter_ThrowsArgumentException(string hex) =>
Assert.ThrowsExactly<ArgumentException>(() => Color.FromHex(hex));

[TestMethod]
[DataRow("#abc", "#AABBCC")]
[DataRow("#ABC", "#AABBCC")]
[DataRow("#3a7bd5", "#3A7BD5")]
[DataRow("3A7BD5", "#3A7BD5")]
[DataRow("#3a7bd580", "#3A7BD580")]
public void FromHex_ValidDigitsInEitherCase_RoundTrip(string hex, string expected) =>
Assert.AreEqual(expected, Color.FromHex(hex).ToHex());
}
Loading