From c7e0a68f2779361ac23bf13003860ed5634c8278 Mon Sep 17 00:00:00 2001 From: robloo Date: Fri, 15 Apr 2022 19:07:01 -0400 Subject: [PATCH] Support RGB component percentages in CSS format parsing --- src/Avalonia.Base/Media/Color.cs | 43 +++++++++++++++---- .../Media/ColorTests.cs | 24 ++++++++--- 2 files changed, 53 insertions(+), 14 deletions(-) diff --git a/src/Avalonia.Base/Media/Color.cs b/src/Avalonia.Base/Media/Color.cs index 9ad75b3290..2f0def58a4 100644 --- a/src/Avalonia.Base/Media/Color.cs +++ b/src/Avalonia.Base/Media/Color.cs @@ -360,9 +360,9 @@ namespace Avalonia.Media if (components.Length == 3) // RGB { - if (byte.TryParse(components[0], NumberStyles.Number, CultureInfo.InvariantCulture, out byte red) && - byte.TryParse(components[1], NumberStyles.Number, CultureInfo.InvariantCulture, out byte green) && - byte.TryParse(components[2], NumberStyles.Number, CultureInfo.InvariantCulture, out byte blue)) + if (InternalTryParseByte(components[0], out byte red) && + InternalTryParseByte(components[1], out byte green) && + InternalTryParseByte(components[2], out byte blue)) { color = new Color(0xFF, red, green, blue); return true; @@ -370,18 +370,45 @@ namespace Avalonia.Media } else if (components.Length == 4) // RGBA { - if (byte.TryParse(components[0], NumberStyles.Number, CultureInfo.InvariantCulture, out byte red) && - byte.TryParse(components[1], NumberStyles.Number, CultureInfo.InvariantCulture, out byte green) && - byte.TryParse(components[2], NumberStyles.Number, CultureInfo.InvariantCulture, out byte blue) && - TryInternalParse(components[3], out double alpha)) + if (InternalTryParseByte(components[0], out byte red) && + InternalTryParseByte(components[1], out byte green) && + InternalTryParseByte(components[2], out byte blue) && + InternalTryParseDouble(components[3], out double alpha)) { color = new Color((byte)Math.Round(alpha * 255.0), red, green, blue); return true; } } + // Local function to specially parse a byte value with an optional percentage sign + bool InternalTryParseByte(string inString, out byte outByte) + { + // The percent sign, if it exists, must be at the end of the number + int percentIndex = inString.IndexOf("%", StringComparison.Ordinal); + + if (percentIndex >= 0) + { + var result = double.TryParse( + inString.Substring(0, percentIndex), + NumberStyles.Number, + CultureInfo.InvariantCulture, + out double percentage); + + outByte = (byte)Math.Round((percentage / 100.0) * 255.0); + return result; + } + else + { + return byte.TryParse( + inString, + NumberStyles.Number, + CultureInfo.InvariantCulture, + out outByte); + } + } + // Local function to specially parse a double value with an optional percentage sign - bool TryInternalParse(string inString, out double outDouble) + bool InternalTryParseDouble(string inString, out double outDouble) { // The percent sign, if it exists, must be at the end of the number int percentIndex = inString.IndexOf("%", StringComparison.Ordinal); diff --git a/tests/Avalonia.Base.UnitTests/Media/ColorTests.cs b/tests/Avalonia.Base.UnitTests/Media/ColorTests.cs index 1392635b32..36929d5e95 100644 --- a/tests/Avalonia.Base.UnitTests/Media/ColorTests.cs +++ b/tests/Avalonia.Base.UnitTests/Media/ColorTests.cs @@ -216,8 +216,8 @@ namespace Avalonia.Base.UnitTests.Media Tuple.Create("hsl(-1000, -1000, -1000)", new HslColor(1, 0, 0, 0)), // Clamps to min Tuple.Create("hsl(-1000, -1000%, -1000%)", new HslColor(1, 0, 0, 0)), // Clamps to min - Tuple.Create("hsl(1000, 1000, 1000)", new HslColor(1, 0, 1, 1)), // Clamps to max - Tuple.Create("hsl(1000, 1000%, 1000%)", new HslColor(1, 0, 1, 1)), // Clamps to max + Tuple.Create("hsl(1000, 1000, 1000)", new HslColor(1, 0, 1, 1)), // Clamps to max (Hue wraps to zero) + Tuple.Create("hsl(1000, 1000%, 1000%)", new HslColor(1, 0, 1, 1)), // Clamps to max (Hue wraps to zero) Tuple.Create("hsl(300, 0.8, 0.2)", new HslColor(1.0, 300, 0.8, 0.2)), Tuple.Create("hsl(300, 80%, 20%)", new HslColor(1.0, 300, 0.8, 0.2)), @@ -262,8 +262,8 @@ namespace Avalonia.Base.UnitTests.Media Tuple.Create("hsv(-1000, -1000, -1000)", new HsvColor(1, 0, 0, 0)), // Clamps to min Tuple.Create("hsv(-1000, -1000%, -1000%)", new HsvColor(1, 0, 0, 0)), // Clamps to min - Tuple.Create("hsv(1000, 1000, 1000)", new HsvColor(1, 0, 1, 1)), // Clamps to max - Tuple.Create("hsv(1000, 1000%, 1000%)", new HsvColor(1, 0, 1, 1)), // Clamps to max + Tuple.Create("hsv(1000, 1000, 1000)", new HsvColor(1, 0, 1, 1)), // Clamps to max (Hue wraps to zero) + Tuple.Create("hsv(1000, 1000%, 1000%)", new HsvColor(1, 0, 1, 1)), // Clamps to max (Hue wraps to zero) Tuple.Create("hsv(300, 0.8, 0.2)", new HsvColor(1.0, 300, 0.8, 0.2)), Tuple.Create("hsv(300, 80%, 20%)", new HsvColor(1.0, 300, 0.8, 0.2)), @@ -303,8 +303,20 @@ namespace Avalonia.Base.UnitTests.Media Tuple.Create("#123456", new Color(0xff, 0x12, 0x34, 0x56)), Tuple.Create("rgb(100, 30, 45)", new Color(255, 100, 30, 45)), - Tuple.Create("rgba(100, 30, 45, 0.9)", new Color(229, 100, 30, 45)), - Tuple.Create("rgba(100, 30, 45, 90%)", new Color(229, 100, 30, 45)), + Tuple.Create("rgba(100, 30, 45, 0.9)", new Color(230, 100, 30, 45)), + Tuple.Create("rgba(100, 30, 45, 90%)", new Color(230, 100, 30, 45)), + + Tuple.Create("rgb(255,0,0)", new Color(255, 255, 0, 0)), + Tuple.Create("rgb(0,255,0)", new Color(255, 0, 255, 0)), + Tuple.Create("rgb(0,0,255)", new Color(255, 0, 0, 255)), + + Tuple.Create("rgb(100%, 0, 0)", new Color(255, 255, 0, 0)), + Tuple.Create("rgb(0, 100%, 0)", new Color(255, 0, 255, 0)), + Tuple.Create("rgb(0, 0, 100%)", new Color(255, 0, 0, 255)), + + Tuple.Create("rgba(0, 0, 100%, 50%)", new Color(128, 0, 0, 255)), + Tuple.Create("rgba(50%, 10%, 80%, 50%)", new Color(128, 128, 26, 204)), + Tuple.Create("rgba(50%, 10%, 80%, 0.5)", new Color(128, 128, 26, 204)), // HSL Tuple.Create("hsl(296, 85%, 12%)", new Color(255, 53, 5, 57)),