namespace Spectre.Console; /// /// Represents a color. /// public partial struct Color : IEquatable { /// /// Gets the default color. /// public static Color Default { get; } static Color() { Default = new Color(0, 0, 0, 0, true); } /// /// Gets the red component. /// public byte R { get; } /// /// Gets the green component. /// public byte G { get; } /// /// Gets the blue component. /// public byte B { get; } /// /// Gets the number of the color, if any. /// internal byte? Number { get; } /// /// Gets a value indicating whether or not this is the default color. /// internal bool IsDefault { get; } /// /// Initializes a new instance of the struct. /// /// The red component. /// The green component. /// The blue component. public Color(byte red, byte green, byte blue) { R = red; G = green; B = blue; IsDefault = false; Number = null; } /// /// Blends two colors. /// /// The other color. /// The blend factor. /// The resulting color. public Color Blend(Color other, float factor) { // https://github.com/willmcgugan/rich/blob/f092b1d04252e6f6812021c0f415dd1d7be6a16a/rich/color.py#L494 return new Color( (byte)(R + ((other.R - R) * factor)), (byte)(G + ((other.G - G) * factor)), (byte)(B + ((other.B - B) * factor))); } /// /// Gets the hexadecimal representation of the color. /// /// The hexadecimal representation of the color. public string ToHex() { return string.Format( CultureInfo.InvariantCulture, "{0}{1}{2}", R.ToString("X2", CultureInfo.InvariantCulture), G.ToString("X2", CultureInfo.InvariantCulture), B.ToString("X2", CultureInfo.InvariantCulture)); } /// public override int GetHashCode() { unchecked { var hash = (int)2166136261; hash = (hash * 16777619) ^ R.GetHashCode(); hash = (hash * 16777619) ^ G.GetHashCode(); hash = (hash * 16777619) ^ B.GetHashCode(); return hash; } } /// public override bool Equals(object? obj) { return obj is Color color && Equals(color); } /// public bool Equals(Color other) { return (IsDefault && other.IsDefault) || (IsDefault == other.IsDefault && R == other.R && G == other.G && B == other.B); } /// /// Checks if two instances are equal. /// /// The first color instance to compare. /// The second color instance to compare. /// true if the two colors are equal, otherwise false. public static bool operator ==(Color left, Color right) { return left.Equals(right); } /// /// Checks if two instances are not equal. /// /// The first color instance to compare. /// The second color instance to compare. /// true if the two colors are not equal, otherwise false. public static bool operator !=(Color left, Color right) { return !(left == right); } /// /// Converts a to a . /// /// The color number to convert. public static implicit operator Color(int number) { return FromInt32(number); } /// /// Converts a to a . /// /// The color to convert. public static implicit operator Color(ConsoleColor color) { return FromConsoleColor(color); } /// /// Converts a to a . /// /// The console color to convert. public static implicit operator ConsoleColor(Color color) { return ToConsoleColor(color); } /// /// Converts a to a . /// /// The color to convert. /// A representing the . public static ConsoleColor ToConsoleColor(Color color) { if (color.IsDefault) { return (ConsoleColor)(-1); } if (color.Number == null || color.Number.Value >= 16) { color = ColorPalette.ExactOrClosest(ColorSystem.Standard, color); } // Should not happen, but this will make things easier if we mess things up... Debug.Assert( color.Number >= 0 && color.Number < 16, "Color does not fall inside the standard palette range."); return color.Number.Value switch { 0 => ConsoleColor.Black, 1 => ConsoleColor.DarkRed, 2 => ConsoleColor.DarkGreen, 3 => ConsoleColor.DarkYellow, 4 => ConsoleColor.DarkBlue, 5 => ConsoleColor.DarkMagenta, 6 => ConsoleColor.DarkCyan, 7 => ConsoleColor.Gray, 8 => ConsoleColor.DarkGray, 9 => ConsoleColor.Red, 10 => ConsoleColor.Green, 11 => ConsoleColor.Yellow, 12 => ConsoleColor.Blue, 13 => ConsoleColor.Magenta, 14 => ConsoleColor.Cyan, 15 => ConsoleColor.White, _ => throw new InvalidOperationException("Cannot convert color to console color."), }; } /// /// Converts a color number into a . /// /// The color number. /// The color representing the specified color number. public static Color FromInt32(int number) { return ColorTable.GetColor(number); } /// /// Creates a color from a hexadecimal string representation. /// /// The hexadecimal string representation of the color. /// The color created from the hexadecimal string. public static Color FromHex(string hex) { if (hex is null) { throw new ArgumentNullException(nameof(hex)); } if (hex.StartsWith("#")) { hex = hex.Substring(1); } var r = byte.Parse(hex.Substring(0, 2), NumberStyles.HexNumber); var g = byte.Parse(hex.Substring(2, 2), NumberStyles.HexNumber); var b = byte.Parse(hex.Substring(4, 2), NumberStyles.HexNumber); return new Color(r, g, b); } /// /// Tries to convert a hexadecimal color code to a object. /// /// The hexadecimal color code. /// When this method returns, contains the equivalent of the hexadecimal color code, if the conversion succeeded, or if the conversion failed. /// true if the conversion succeeded; otherwise, false. public static bool TryFromHex(string hex, out Color color) { try { color = FromHex(hex); return true; } catch { color = Color.Default; return false; } } /// /// Converts a to a . /// /// The color to convert. /// A representing the . public static Color FromConsoleColor(ConsoleColor color) { return color switch { ConsoleColor.Black => Black, ConsoleColor.Blue => Blue, ConsoleColor.Cyan => Aqua, ConsoleColor.DarkBlue => Navy, ConsoleColor.DarkCyan => Teal, ConsoleColor.DarkGray => Grey, ConsoleColor.DarkGreen => Green, ConsoleColor.DarkMagenta => Purple, ConsoleColor.DarkRed => Maroon, ConsoleColor.DarkYellow => Olive, ConsoleColor.Gray => Silver, ConsoleColor.Green => Lime, ConsoleColor.Magenta => Fuchsia, ConsoleColor.Red => Red, ConsoleColor.White => White, ConsoleColor.Yellow => Yellow, _ => Default, }; } /// /// Converts the color to a markup string. /// /// A representing the color as markup. public string ToMarkup() { if (IsDefault) { return "default"; } if (Number != null) { var name = ColorTable.GetName(Number.Value); if (!string.IsNullOrWhiteSpace(name)) { return name; } } return string.Format(CultureInfo.InvariantCulture, "#{0:X2}{1:X2}{2:X2}", R, G, B); } /// public override string ToString() { if (IsDefault) { return "default"; } if (Number != null) { var name = ColorTable.GetName(Number.Value); if (!string.IsNullOrWhiteSpace(name)) { return name; } } return string.Format(CultureInfo.InvariantCulture, "#{0:X2}{1:X2}{2:X2} (RGB={0},{1},{2})", R, G, B); } }