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);
}
}