diff --git a/Robust.Shared.Maths/Color.cs b/Robust.Shared.Maths/Color.cs index 7174b7e1896..097fcf468a5 100644 --- a/Robust.Shared.Maths/Color.cs +++ b/Robust.Shared.Maths/Color.cs @@ -161,19 +161,36 @@ public readonly int ToArgb() /// A new Color structure containing the converted components. public static implicit operator Color(System.Drawing.Color color) { - return new(color.R, color.G, color.B, color.A); + return new Color(color.R, color.G, color.B, color.A); } + /// + /// Converts the specified RGBA float tuple into a Color instance. + /// + /// The tuple to convert. + /// The converted Color instance with the tuple's values. public static implicit operator Color((float r, float g, float b, float a) tuple) { - return new(tuple.r, tuple.g, tuple.b, tuple.a); + return new Color(tuple.r, tuple.g, tuple.b, tuple.a); } + /// + /// Converts the specified RGB float tuple into a Color instance. + /// + /// The tuple to convert. + /// The converted Color instance with the tuple's values. public static implicit operator Color((float r, float g, float b) tuple) { - return new(tuple.r, tuple.g, tuple.b); + return new Color(tuple.r, tuple.g, tuple.b); } + /// + /// Deconstructs a Color instance into its float RGBA parts. + /// + /// The Color instance's red value. + /// The Color instance's green value. + /// The Color instance's blue value. + /// The Color instance's alpha value. public readonly void Deconstruct(out float r, out float g, out float b, out float a) { r = R; @@ -182,6 +199,12 @@ public readonly void Deconstruct(out float r, out float g, out float b, out floa a = A; } + /// + /// Deconstructs a Color instance into its float RGB parts. + /// + /// The Color instance's red value. + /// The Color instance's green value. + /// The Color instance's blue value. public readonly void Deconstruct(out float r, out float g, out float b) { r = R; @@ -203,16 +226,32 @@ public static explicit operator System.Drawing.Color(Color color) (int) (color.B * byte.MaxValue)); } - public static Color FromName(string colorname) + /// + /// Indexes the instance and returns the value. + /// + /// The key to index the instance with. + /// The Color instance returned from indexing the dictionary. + public static Color FromName(string colorName) { - return DefaultColors[colorname.ToLower()]; + return DefaultColors[colorName.ToLower()]; } + /// + /// Tries to output a specific Color instance from the dictionary + /// based on a colorName key. + /// + /// The key to index the with. + /// The Color instance value that was associated with the colorName key, if any. + /// Whether a Color instance was successfully found with the key. public static bool TryFromName(string colorName, out Color color) { return DefaultColors.TryGetValue(colorName.ToLower(), out color); } + /// + /// Gets the dictionary with all color names and their associated values. + /// + /// The dictionary. public static IEnumerable> GetAllDefaultColors() { return DefaultColors; @@ -249,11 +288,14 @@ public readonly override string ToString() return $"{{(R, G, B, A) = ({R}, {G}, {B}, {A})}}"; } + /// + /// Does not do anything different to ToString(). public readonly string ToString(string? format, IFormatProvider? formatProvider) { return ToString(); } + public readonly bool TryFormat( Span destination, out int charsWritten, @@ -266,44 +308,84 @@ public readonly bool TryFormat( $"{{(R, G, B, A) = ({R}, {G}, {B}, {A})}}"); } + /// + /// Creates a new Color instance with the provided red value. + /// + /// The new red value to give the Color instance. + /// A new Color instance with the provided red value. public readonly Color WithRed(float newR) { - return new(newR, G, B, A); + return new Color(newR, G, B, A); } + /// + /// Creates a new Color instance with the provided green value. + /// + /// The new green value to give the Color instance. + /// A new Color instance with the provided green value. public readonly Color WithGreen(float newG) { - return new(R, newG, B, A); + return new Color(R, newG, B, A); } + /// + /// Creates a new Color instance with the provided blue value. + /// + /// The new blue value to give the Color instance. + /// A new Color instance with the provided blue value. public readonly Color WithBlue(float newB) { - return new(R, G, newB, A); + return new Color(R, G, newB, A); } + /// + /// Creates a new Color instance with the provided alpha value. + /// + /// The new alpha value to give the Color instance. + /// A new Color instance with the provided alpha value. public readonly Color WithAlpha(float newA) { - return new(R, G, B, newA); + return new Color(R, G, B, newA); } + /// + /// Creates a new Color instance with a new byte-based red value. + /// + /// The byte to convert into the new red value. + /// A new Color instance with the provided red value. public readonly Color WithRed(byte newR) { - return new((float) newR / byte.MaxValue, G, B, A); + return new Color((float) newR / byte.MaxValue, G, B, A); } + /// + /// Creates a new Color instance with a new byte-based green value. + /// + /// The byte to convert into the new green value. + /// A new Color instance with the provided green value. public readonly Color WithGreen(byte newG) { - return new(R, (float) newG / byte.MaxValue, B, A); + return new Color(R, (float) newG / byte.MaxValue, B, A); } + /// + /// Creates a new Color instance with a new byte-based blue value. + /// + /// The byte to convert into the new blue value. + /// A new Color instance with the provided blue value. public readonly Color WithBlue(byte newB) { - return new(R, G, (float) newB / byte.MaxValue, A); + return new Color(R, G, (float) newB / byte.MaxValue, A); } + /// + /// Creates a new Color instance with a new byte-based alpha value. + /// + /// The byte to convert into the new alpha value. + /// A new Color instance with the provided alpha value. public readonly Color WithAlpha(byte newA) { - return new(R, G, B, (float) newA / byte.MaxValue); + return new Color(R, G, B, (float) newA / byte.MaxValue); } /// @@ -342,27 +424,27 @@ public static Color FromSrgb(Color srgb) /// /// Returns the converted color value. /// - /// Color value to convert. - public static Color ToSrgb(Color rgb) + /// Color value to convert. + public static Color ToSrgb(Color color) { float r, g, b; - if (rgb.R <= 0.0031308) - r = 12.92f * rgb.R; + if (color.R <= 0.0031308) + r = 12.92f * color.R; else - r = (1.0f + 0.055f) * MathF.Pow(rgb.R, 1.0f / 2.4f) - 0.055f; + r = (1.0f + 0.055f) * MathF.Pow(color.R, 1.0f / 2.4f) - 0.055f; - if (rgb.G <= 0.0031308) - g = 12.92f * rgb.G; + if (color.G <= 0.0031308) + g = 12.92f * color.G; else - g = (1.0f + 0.055f) * MathF.Pow(rgb.G, 1.0f / 2.4f) - 0.055f; + g = (1.0f + 0.055f) * MathF.Pow(color.G, 1.0f / 2.4f) - 0.055f; - if (rgb.B <= 0.0031308) - b = 12.92f * rgb.B; + if (color.B <= 0.0031308) + b = 12.92f * color.B; else - b = (1.0f + 0.055f) * MathF.Pow(rgb.B, 1.0f / 2.4f) - 0.055f; + b = (1.0f + 0.055f) * MathF.Pow(color.B, 1.0f / 2.4f) - 0.055f; - return new Color(r, g, b, rgb.A); + return new Color(r, g, b, color.A); } /// @@ -445,23 +527,23 @@ public static Color FromHsl(Vector4 hsl) /// Alpha (a copy of the input's Alpha value). /// Each has a range of 0.0 to 1.0. /// - /// Color value to convert. + /// Color value to convert. [SuppressMessage("ReSharper", "CompareOfFloatsByEqualityOperator")] - public static Vector4 ToHsl(Color rgb) + public static Vector4 ToHsl(Color color) { - var max = MathF.Max(rgb.R, MathF.Max(rgb.G, rgb.B)); - var min = MathF.Min(rgb.R, MathF.Min(rgb.G, rgb.B)); + var max = MathF.Max(color.R, MathF.Max(color.G, color.B)); + var min = MathF.Min(color.R, MathF.Min(color.G, color.B)); var c = max - min; var h = 0.0f; if (c != 0) { - if (max == rgb.R) - h = (rgb.G - rgb.B) / c; - else if (max == rgb.G) - h = (rgb.B - rgb.R) / c + 2.0f; - else if (max == rgb.B) - h = (rgb.R - rgb.G) / c + 4.0f; + if (max == color.R) + h = (color.G - color.B) / c; + else if (max == color.G) + h = (color.B - color.R) / c + 2.0f; + else if (max == color.B) + h = (color.R - color.G) / c + 4.0f; } var hue = h / 6.0f; @@ -474,7 +556,7 @@ public static Vector4 ToHsl(Color rgb) if (0.0f != lightness && lightness != 1.0f) saturation = c / (1.0f - MathF.Abs(2.0f * lightness - 1.0f)); - return new Vector4(hue, saturation, lightness, rgb.A); + return new Vector4(hue, saturation, lightness, color.A); } /// @@ -559,25 +641,25 @@ public static Color FromHsv(Vector4 hsv) /// /// Color value to convert. [SuppressMessage("ReSharper", "CompareOfFloatsByEqualityOperator")] - public static Vector4 ToHsv(Color rgb) + public static Vector4 ToHsv(Color color) { - var max = MathF.Max(rgb.R, MathF.Max(rgb.G, rgb.B)); - var min = MathF.Min(rgb.R, MathF.Min(rgb.G, rgb.B)); + var max = MathF.Max(color.R, MathF.Max(color.G, color.B)); + var min = MathF.Min(color.R, MathF.Min(color.G, color.B)); var c = max - min; var h = 0.0f; if (c != 0) { - if (max == rgb.R) + if (max == color.R) { - h = (rgb.G - rgb.B) / c % 6.0f; + h = (color.G - color.B) / c % 6.0f; if (h < 0f) h += 6.0f; } - else if (max == rgb.G) - h = (rgb.B - rgb.R) / c + 2.0f; - else if (max == rgb.B) - h = (rgb.R - rgb.G) / c + 4.0f; + else if (max == color.G) + h = (color.B - color.R) / c + 2.0f; + else if (max == color.B) + h = (color.R - color.G) / c + 4.0f; } var hue = h * 60.0f / 360.0f; @@ -586,7 +668,7 @@ public static Vector4 ToHsv(Color rgb) if (0.0f != max) saturation = c / max; - return new Vector4(hue, saturation, max, rgb.A); + return new Vector4(hue, saturation, max, color.A); } #region Oklab/Oklch @@ -736,14 +818,14 @@ public static Color FromXyz(Vector4 xyz) /// element with Alpha (a copy of the input's Alpha value). /// Each has a range of 0.0 to 1.0. /// - /// Color value to convert. + /// Color value to convert. /// Uses the CIE XYZ colorspace. - public static Vector4 ToXyz(Color rgb) + public static Vector4 ToXyz(Color color) { - var x = (0.49f * rgb.R + 0.31f * rgb.G + 0.20f * rgb.B) / 0.17697f; - var y = (0.17697f * rgb.R + 0.81240f * rgb.G + 0.01063f * rgb.B) / 0.17697f; - var z = (0.00f * rgb.R + 0.01f * rgb.G + 0.99f * rgb.B) / 0.17697f; - return new Vector4(x, y, z, rgb.A); + var x = (0.49f * color.R + 0.31f * color.G + 0.20f * color.B) / 0.17697f; + var y = (0.17697f * color.R + 0.81240f * color.G + 0.01063f * color.B) / 0.17697f; + var z = (0.00f * color.R + 0.01f * color.G + 0.99f * color.B) / 0.17697f; + return new Vector4(x, y, z, color.A); } /// @@ -777,14 +859,14 @@ public static Color FromYcbcr(Vector4 ycbcr) /// input's Alpha value). /// Each has a range of 0.0 to 1.0. /// - /// Color value to convert. + /// Color value to convert. /// Converts using ITU-R BT.601/CCIR 601 W(r) = 0.299 W(b) = 0.114 U(max) = 0.436 V(max) = 0.615. - public static Vector4 ToYcbcr(Color rgb) + public static Vector4 ToYcbcr(Color color) { - var y = 0.299f * rgb.R + 0.587f * rgb.G + 0.114f * rgb.B; - var u = -0.168736f * rgb.R + -0.331264f * rgb.G + 0.5f * rgb.B; - var v = 0.5f * rgb.R + -0.418688f * rgb.G + -0.081312f * rgb.B; - return new Vector4(y, u, v, rgb.A); + var y = 0.299f * color.R + 0.587f * color.G + 0.114f * color.B; + var u = -0.168736f * color.R + -0.331264f * color.G + 0.5f * color.B; + var v = 0.5f * color.R + -0.418688f * color.G + -0.081312f * color.B; + return new Vector4(y, u, v, color.A); } /// @@ -894,10 +976,19 @@ public static Vector4 ToHcy(Color rgb) return new Vector4(hue, c, luminance, rgb.A); } - - public static Vector4 ToCmyk(Color rgb) + /// + /// Converts a Color instance into CMYK color values. + /// + /// The color instance to convert. + /// + /// Returns the converted color value. + /// The X element is cyan (C), the Y element is magenta (M), the Z element is yellow (Y), + /// and the W element is the key (K). + /// Each has a range of 0.0 to 1.0. + /// + public static Vector4 ToCmyk(Color color) { - var (r, g, b) = rgb; + var (r, g, b) = color; var k = 1 - MathF.Max(r, MathF.Max(g, b)); var c = (1 - r - k) / (1 - k); var m = (1 - g - k) / (1 - k); @@ -906,6 +997,18 @@ public static Vector4 ToCmyk(Color rgb) return new Vector4(c, m, y, k); } + /// + /// Converts CMYK color values into a Color instance. + /// + /// + /// The CMYK color values. + /// The X element is cyan (C), the Y element is magenta (M), the Z element is yellow (Y), + /// and the W element is the key (K). + /// Each has a range of 0.0 to 1.0. + /// + /// + /// The converted color instance. + /// public static Color FromCmyk(Vector4 cmyk) { var (c, m, y, k) = cmyk; @@ -930,62 +1033,107 @@ public static Color FromCmyk(Vector4 cmyk) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Color InterpolateBetween(Color α, Color β, float λ) { - return new(Vector4.Lerp(α.RGBA, β.RGBA, λ)); + return new Color(Vector4.Lerp(α.RGBA, β.RGBA, λ)); } - public static Color? TryFromHex(ReadOnlySpan hexColor) + /// + /// Tries to convert a of characters into a instance. + /// + /// The to try to convert into a instance. + /// The created instance. + /// + /// Returns if it created a proper instance from the hex code. + /// Returns if no instance could be made. + /// + /// + /// must start with a #. + /// Works with the following hex code formats: + /// #00000000, #000000, #0000, #000 + /// + public static bool TryFromHex(ReadOnlySpan hexColor, out Color color) { - if (hexColor.Length <= 0 || hexColor[0] != '#') return null; + color = default; + + if (hexColor.Length <= 0 || hexColor[0] != '#') return false; if (hexColor.Length == 9) { - if (!byte.TryParse(hexColor[1..3], NumberStyles.HexNumber, null, out var r)) return null; - if (!byte.TryParse(hexColor[3..5], NumberStyles.HexNumber, null, out var g)) return null; - if (!byte.TryParse(hexColor[5..7], NumberStyles.HexNumber, null, out var b)) return null; - if (!byte.TryParse(hexColor[7..9], NumberStyles.HexNumber, null, out var a)) return null; - return new Color(r, g, b, a); + if (!byte.TryParse(hexColor[1..3], NumberStyles.HexNumber, null, out var r)) return false; + if (!byte.TryParse(hexColor[3..5], NumberStyles.HexNumber, null, out var g)) return false; + if (!byte.TryParse(hexColor[5..7], NumberStyles.HexNumber, null, out var b)) return false; + if (!byte.TryParse(hexColor[7..9], NumberStyles.HexNumber, null, out var a)) return false; + color = new Color(r, g, b, a); + return true; } if (hexColor.Length == 7) { - if (!byte.TryParse(hexColor[1..3], NumberStyles.HexNumber, null, out var r)) return null; - if (!byte.TryParse(hexColor[3..5], NumberStyles.HexNumber, null, out var g)) return null; - if (!byte.TryParse(hexColor[5..7], NumberStyles.HexNumber, null, out var b)) return null; - return new Color(r, g, b); - } - - static bool ParseDup(char chr, out byte value) - { - Span buf = stackalloc char[2]; - buf[0] = chr; - buf[1] = chr; - - return byte.TryParse(buf, NumberStyles.HexNumber, null, out value); + if (!byte.TryParse(hexColor[1..3], NumberStyles.HexNumber, null, out var r)) return false; + if (!byte.TryParse(hexColor[3..5], NumberStyles.HexNumber, null, out var g)) return false; + if (!byte.TryParse(hexColor[5..7], NumberStyles.HexNumber, null, out var b)) return false; + color = new Color(r, g, b); + return true; } if (hexColor.Length == 5) { - if (!ParseDup(hexColor[1], out var rByte)) return null; - if (!ParseDup(hexColor[2], out var gByte)) return null; - if (!ParseDup(hexColor[3], out var bByte)) return null; - if (!ParseDup(hexColor[4], out var aByte)) return null; + if (!ParseDup(hexColor[1], out var rByte)) return false; + if (!ParseDup(hexColor[2], out var gByte)) return false; + if (!ParseDup(hexColor[3], out var bByte)) return false; + if (!ParseDup(hexColor[4], out var aByte)) return false; - return new Color(rByte, gByte, bByte, aByte); + color = new Color(rByte, gByte, bByte, aByte); + return true; } if (hexColor.Length == 4) { - if (!ParseDup(hexColor[1], out var rByte)) return null; - if (!ParseDup(hexColor[2], out var gByte)) return null; - if (!ParseDup(hexColor[3], out var bByte)) return null; + if (!ParseDup(hexColor[1], out var rByte)) return false; + if (!ParseDup(hexColor[2], out var gByte)) return false; + if (!ParseDup(hexColor[3], out var bByte)) return false; - return new Color(rByte, gByte, bByte); + color = new Color(rByte, gByte, bByte); + return true; } + return false; + } + + /// + /// Tries to convert a of characters into a . + /// + /// The of characters to convert into a instance. + /// A instance if it succeeded, otherwise. + /// + /// must start with a #. + /// Works with the following hex code formats: + /// #00000000, #000000, #0000, #000 + /// + private static Color? TryFromHex(ReadOnlySpan hexColor) + { + if (TryFromHex(hexColor, out var color)) + return color; + return null; } + private static bool ParseDup(char chr, out byte value) + { + Span buf = stackalloc char[2]; + buf[0] = chr; + buf[1] = chr; + + return byte.TryParse(buf, NumberStyles.HexNumber, null, out value); + } + + /// + /// Converts a of characters into a instance + /// and lets you provide a fallback if it fails. + /// + /// Tries to + /// + /// + /// If no fallback is provided, throws an public static Color FromHex(ReadOnlySpan hexColor, Color? fallback = null) { - var color = TryFromHex(hexColor); - if (color.HasValue) - return color.Value; + if (TryFromHex(hexColor, out var color)) + return color; if (fallback.HasValue) return fallback.Value; throw new ArgumentException($"Invalid color code \"{new string(hexColor)}\" and no fallback provided.", nameof(hexColor)); @@ -993,9 +1141,8 @@ public static Color FromHex(ReadOnlySpan hexColor, Color? fallback = null) public static Color FromXaml(string name) { - var color = TryFromHex(name); - if (color != null) - return color.Value; + if (TryFromHex(name, out var color)) + return color; if (TryFromName(name, out var namedColor)) return namedColor; @@ -1089,6 +1236,10 @@ public static Color Blend(Color dstColor, Color srcColor, BlendFactor dstFactor, public static Color operator *(in Color a, in Color b) => new(a.RGBA * b.RGBA); + /// + /// Converts a instance into a hex color . + /// + /// The hex color code, formatted as #00000000. public readonly string ToHex() { var hexColor = 0; @@ -1100,6 +1251,8 @@ public readonly string ToHex() return $"#{hexColor:X8}"; } + /// + /// The hex color code, formatted as #000000. public readonly string ToHexNoAlpha() { var hexColor = 0; @@ -2015,22 +2168,30 @@ public enum BlendFactor : byte private static readonly FrozenDictionary DefaultColorsInverted = DefaultColors.ToLookup(pair => pair.Value).ToFrozenDictionary(i => i.Key, i => i.First().Key); + /// + /// Gets the associated color name with this color instance by using an inverted version of . + /// Returns if there is no possible name with this color instance. + /// + /// The associated color name if any, usable with . public readonly string? Name() { return DefaultColorsInverted.GetValueOrDefault(this); } + /// + /// Tries to return a instance by first calling , and then + /// . + /// + /// A that could either be a hex color code or a valid default color name. + /// The parsed color if it returned true. + /// Returns true if a color was returned successfully. Returns false otherwise. public static bool TryParse(string input, out Color color) { if (TryFromName(input, out color)) return true; - var nullableColor = TryFromHex(input); - if (nullableColor != null) - { - color = nullableColor.Value; + if (TryFromHex(input, out color)) return true; - } return false; } diff --git a/Robust.Shared/Serialization/TypeSerializers/Implementations/ColorSerializer.cs b/Robust.Shared/Serialization/TypeSerializers/Implementations/ColorSerializer.cs index 816e7115faf..9580ecd548a 100644 --- a/Robust.Shared/Serialization/TypeSerializers/Implementations/ColorSerializer.cs +++ b/Robust.Shared/Serialization/TypeSerializers/Implementations/ColorSerializer.cs @@ -30,7 +30,7 @@ public ValidationNode Validate(ISerializationManager serializationManager, Value IDependencyCollection dependencies, ISerializationContext? context = null) { - return Color.TryFromName(node.Value, out _) || Color.TryFromHex(node.Value) != null + return Color.TryFromName(node.Value, out _) || Color.TryFromHex(node.Value, out _) ? new ValidatedValueNode(node) : new ErrorNode(node, "Failed parsing Color."); } diff --git a/Robust.Shared/Utility/FormattedMessage.MarkupParser.cs b/Robust.Shared/Utility/FormattedMessage.MarkupParser.cs index 61507999405..fb639b3abf7 100644 --- a/Robust.Shared/Utility/FormattedMessage.MarkupParser.cs +++ b/Robust.Shared/Utility/FormattedMessage.MarkupParser.cs @@ -272,6 +272,10 @@ private static Color CreateColor(string nameOrHex) if (Color.TryFromName(nameOrHex, out var nameColor)) return nameColor; - return Color.TryFromHex(nameOrHex) ?? Color.Black; + // ReSharper disable once ConvertIfStatementToReturnStatement + if (Color.TryFromHex(nameOrHex, out var hexColor)) + return hexColor; + + return Color.Black; } }