From c4dcf5203736c3d4e1ff4c20e3a7da9d97eabcd1 Mon Sep 17 00:00:00 2001 From: IhateTrains Date: Sat, 16 Dec 2023 22:52:20 +0000 Subject: [PATCH 1/2] More graceful handling of undefined named colors --- commonItems.UnitTests/Colors/ColorTests.cs | 50 ++++++++++++++++---- commonItems/Colors/Color.cs | 7 +++ commonItems/Colors/ColorFactory.cs | 55 +++++++++++++++++++--- 3 files changed, 96 insertions(+), 16 deletions(-) diff --git a/commonItems.UnitTests/Colors/ColorTests.cs b/commonItems.UnitTests/Colors/ColorTests.cs index 661a93ee..88d0564a 100644 --- a/commonItems.UnitTests/Colors/ColorTests.cs +++ b/commonItems.UnitTests/Colors/ColorTests.cs @@ -76,6 +76,21 @@ public void ColorCanBeInitializedWithHsvaComponents() { Assert.Equal(0.5, testColor.V, decimalPlaces); } + [Theory] + [InlineData(0, 0, 0)] + [InlineData(255, 0, 0)] + [InlineData(0, 255, 0)] + [InlineData(0, 0, 255)] + [InlineData(255, 255, 255)] + [InlineData(128, 128, 128)] + public void ColorCanBeConstructedFromSystemDrawingColor(int r, int g, int b) { + var systemDrawingColor = System.Drawing.Color.FromArgb(r, g, b); + var color = new Color(systemDrawingColor); + Assert.Equal(r, color.R); + Assert.Equal(g, color.G); + Assert.Equal(b, color.B); + } + [Fact] public void HsvConversion_GreyHasZeroHue() { var testColor = new Color(128, 128, 128); @@ -469,13 +484,6 @@ public void ColorCanBeInitializedFromQuotedStreamWithName() { Assert.Equal(0.5, color.V, decimalPlaces); } - [Fact] - public void ColorInitializingRequiresCachedColorWhenUsingName() { - var colorFactory = new ColorFactory(); - var reader = new BufferedReader("= dark_moderate_cyan"); - Assert.Throws(() => colorFactory.GetColor(reader)); - } - [Fact] public void ColorCanBeCachedFromStream() { var colorFactory = new ColorFactory(); @@ -499,7 +507,7 @@ public void ColorCanBeInitializedWithName() { var colorFactory = new ColorFactory(); colorFactory.AddNamedColor("dark_moderate_cyan", new Color(64, 128, 128)); - var color = colorFactory.GetColor("dark_moderate_cyan"); + var color = colorFactory.GetColorByName("dark_moderate_cyan"); Assert.Equal(64, color.R); Assert.Equal(128, color.G); @@ -510,6 +518,28 @@ public void ColorCanBeInitializedWithName() { Assert.Equal(0.5, color.V, decimalPlaces); } + [Theory] + [InlineData("white", "white")] + [InlineData("ck2_purple", "purple")] + [InlineData("purple", "purple")] + [InlineData("green_light", "green")] + [InlineData("yellow_light", "yellow")] + [InlineData("gray", "gray")] + [InlineData("grey", "gray")] // the classic + [InlineData("red_green_blue", "red")] // first matching word is used + [InlineData("snow_white", "snow")] + [InlineData("random_bullshit", "black")] // black is the final fallback + public void ColorCanBeReturnedEvenForUncachedName(string colorName, string expectedReturnedColorName) { + var colorFactory = new ColorFactory(); + + var color = colorFactory.GetColorByName(colorName); + var expectedColor = System.Drawing.Color.FromName(expectedReturnedColorName); + + Assert.Equal(expectedColor.R, color.R); + Assert.Equal(expectedColor.G, color.G); + Assert.Equal(expectedColor.B, color.B); + } + private class Foo : Parser { public Foo(BufferedReader reader) { RegisterKeyword("color", colorReader => color = new ColorFactory().GetColor(colorReader)); @@ -668,7 +698,7 @@ public void ColorPaletteCanBeAlteredByStream() { var reader2 = new BufferedReader("= hex { FFD700 }"); colorFactory.AddNamedColor("gold", reader2); - var gold = colorFactory.GetColor("gold"); + var gold = colorFactory.GetColorByName("gold"); Assert.Equal(255, gold.R); Assert.Equal(215, gold.G); @@ -685,7 +715,7 @@ public void ColorPaletteCanBeAlteredDirectly() { colorFactory.AddNamedColor("gold", new Color(255, 0, 0)); colorFactory.AddNamedColor("gold", new Color(255, 215, 0)); - var gold = colorFactory.GetColor("gold"); + var gold = colorFactory.GetColorByName("gold"); Assert.Equal(255, gold.R); Assert.Equal(215, gold.G); diff --git a/commonItems/Colors/Color.cs b/commonItems/Colors/Color.cs index 70be90a5..8252fa49 100644 --- a/commonItems/Colors/Color.cs +++ b/commonItems/Colors/Color.cs @@ -60,6 +60,13 @@ public Color(double h, double s, double v) { public Color(double h, double s, double v, double alpha) : this(h, s, v) { A = alpha; } + + public Color(System.Drawing.Color color) { + RgbComponents[0] = color.R; + RgbComponents[1] = color.G; + RgbComponents[2] = color.B; + DeriveHsvFromRgb(); + } public override bool Equals(object? obj) { return obj is Color color && RgbComponents.SequenceEqual(color.RgbComponents); diff --git a/commonItems/Colors/ColorFactory.cs b/commonItems/Colors/ColorFactory.cs index a633b53c..512f79a1 100644 --- a/commonItems/Colors/ColorFactory.cs +++ b/commonItems/Colors/ColorFactory.cs @@ -109,10 +109,7 @@ public Color GetColor(BufferedReader reader) { } default: { if (CommonRegexes.Catchall.IsMatch(token)) { - if (NamedColors.TryGetValue(token, out var value)) { - return value; - } - throw new ArgumentException($"{token} was not a cached color"); + return GetColorByName(token); } foreach (var ch in token.ToCharArray().Reverse()) { @@ -124,11 +121,57 @@ public Color GetColor(BufferedReader reader) { } } - public Color GetColor(string colorName) { + private static System.Drawing.Color? GetSystemDrawingColorByName(string colorName) { + try { + var color = System.Drawing.Color.FromName(colorName); + if (!color.IsKnownColor) { + return null; + } + if (color == System.Drawing.Color.Empty) { + return null; + } + + return color; + } catch { + return null; + } + } + + public Color GetColorByName(string colorName) { if (NamedColors.TryGetValue(colorName, out var value)) { return value; } - throw new ArgumentException($"{colorName} was not a cached color"); + + Logger.Warn($"{colorName} was not a cached color"); + + // Try to find a color that matches the name. + var systemDrawingColor = GetSystemDrawingColorByName(colorName); + if (systemDrawingColor is not null) { + var color = new Color(systemDrawingColor.Value); + NamedColors[colorName] = color; + return color; + } + + // Split the color name into words and try to find a color that matches each word. + foreach (string word in colorName.Split('_')) { + string wordToUse = word; + if (wordToUse == "grey") { + wordToUse = "gray"; + } + + var colorFromWord = GetSystemDrawingColorByName(wordToUse); + if (colorFromWord is null) { + continue; + } + + var color = new Color(colorFromWord.Value); + NamedColors[colorName] = color; + return color; + } + + // If all else fails, return black. + Logger.Warn($"No matching fallback color found for {colorName}, using black"); + return new Color(System.Drawing.Color.Black); } public void AddNamedColor(string name, Color color) { From 65444438560122c4c5e735cb7235fd270bb0de28 Mon Sep 17 00:00:00 2001 From: IhateTrains Date: Sun, 17 Dec 2023 19:26:10 +0000 Subject: [PATCH 2/2] Bump version --- commonItems/commonItems.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commonItems/commonItems.csproj b/commonItems/commonItems.csproj index 03ce0e5f..e232f206 100644 --- a/commonItems/commonItems.csproj +++ b/commonItems/commonItems.csproj @@ -6,7 +6,7 @@ False PGCG.$(AssemblyName) - 9.2.0 + 10.0.0 PGCG https://github.com/ParadoxGameConverters/commonItems.NET https://github.com/ParadoxGameConverters/commonItems.NET