#nullable enable using System.Collections.Frozen; using System.Diagnostics.Contracts; using System.Globalization; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text.Json.Serialization; using ColorHelper; namespace Terminal.Gui; /// /// Represents a 24-bit color encoded in ARGB32 format. /// /// /// /// /// [JsonConverter (typeof (ColorJsonConverter))] [StructLayout (LayoutKind.Explicit)] public readonly partial record struct Color : ISpanParsable, IUtf8SpanParsable, ISpanFormattable, IUtf8SpanFormattable, IMinMaxValue { /// The value of the alpha channel component /// /// The alpha channel is not currently supported, so the value of the alpha channel bits will not affect /// rendering. /// [JsonIgnore] [field: FieldOffset (3)] public readonly byte A; /// The value of this as a in ARGB32 format. /// /// The alpha channel is not currently supported, so the value of the alpha channel bits will not affect /// rendering. /// [JsonIgnore] [field: FieldOffset (0)] public readonly uint Argb; /// The value of the blue color component. [JsonIgnore] [field: FieldOffset (0)] public readonly byte B; /// The value of the green color component. [JsonIgnore] [field: FieldOffset (1)] public readonly byte G; /// The value of the red color component. [JsonIgnore] [field: FieldOffset (2)] public readonly byte R; /// The value of this encoded as a signed 32-bit integer in ARGB32 format. [JsonIgnore] [field: FieldOffset (0)] public readonly int Rgba; /// /// Initializes a new instance of the using the supplied component /// values. /// /// The red 8-bits. /// The green 8-bits. /// The blue 8-bits. /// Optional; defaults to 0xFF. The Alpha channel is not supported by Terminal.Gui. /// Alpha channel is not currently supported by Terminal.Gui. /// If the value of any parameter is greater than . /// If the value of any parameter is negative. public Color (int red = 0, int green = 0, int blue = 0, int alpha = byte.MaxValue) { ArgumentOutOfRangeException.ThrowIfNegative (red, nameof (red)); ArgumentOutOfRangeException.ThrowIfNegative (green, nameof (green)); ArgumentOutOfRangeException.ThrowIfNegative (blue, nameof (blue)); ArgumentOutOfRangeException.ThrowIfNegative (alpha, nameof (alpha)); A = Convert.ToByte (alpha); R = Convert.ToByte (red); G = Convert.ToByte (green); B = Convert.ToByte (blue); } /// /// Initializes a new instance of the class with an encoded signed 32-bit color value in /// ARGB32 format. /// /// The encoded 32-bit color value (see ). /// /// The alpha channel is not currently supported, so the value of the alpha channel bits will not affect /// rendering. /// public Color (int rgba) { Rgba = rgba; } /// /// Initializes a new instance of the class with an encoded unsigned 32-bit color value in /// ARGB32 format. /// /// The encoded unsigned 32-bit color value (see ). /// /// The alpha channel is not currently supported, so the value of the alpha channel bits will not affect /// rendering. /// public Color (uint argb) { Argb = argb; } /// Initializes a new instance of the color from a legacy 16-color named value. /// The 16-color value. public Color (in ColorName colorName) { this = ColorExtensions.ColorNameToColorMap [colorName]; } /// /// Initializes a new instance of the color from string. See /// for details. /// /// /// If is . /// /// If is an empty string or consists of only whitespace /// characters. /// /// If thrown by public Color (string colorString) { ArgumentException.ThrowIfNullOrWhiteSpace (colorString, nameof (colorString)); this = Parse (colorString, CultureInfo.InvariantCulture); } /// Initializes a new instance of the with all channels set to 0. public Color () { Argb = 0u; } /// Gets or sets the 3-byte/6-character hexadecimal value for each of the legacy 16-color values. [SerializableConfigurationProperty (Scope = typeof (SettingsScope), OmitClassName = true)] public static Dictionary Colors { get => // Transform _colorToNameMap into a Dictionary ColorExtensions.ColorToNameMap.ToDictionary (static kvp => kvp.Value, static kvp => kvp.Key.ToString ("g")); set { // Transform Dictionary into _colorToNameMap ColorExtensions.ColorToNameMap = value.ToFrozenDictionary (GetColorToNameMapKey, GetColorToNameMapValue); return; static Color GetColorToNameMapKey (KeyValuePair kvp) { return new Color (kvp.Value); } static ColorName GetColorToNameMapValue (KeyValuePair kvp) { return Enum.TryParse (kvp.Key.ToString (), true, out ColorName colorName) ? colorName : throw new ArgumentException ($"Invalid color name: {kvp.Key}"); } } } /// /// Gets the using a legacy 16-color value. will /// return the closest 16 color match to the true color when no exact value is found. /// /// /// Get returns the of the closest 24-bit color value. Set sets the RGB /// value using a hard-coded map. /// public AnsiColorCode GetAnsiColorCode () { return ColorExtensions.ColorNameToAnsiColorMap [GetClosestNamedColor ()]; } /// /// Gets the using a legacy 16-color value. /// will return the closest 16 color match to the true color when no exact value is found. /// /// /// Get returns the of the closest 24-bit color value. Set sets the RGB /// value using a hard-coded map. /// public ColorName GetClosestNamedColor () { return GetClosestNamedColor (this); } /// /// Determines if the closest named to is the provided /// . /// /// /// The to check if this is closer /// to than any other configured named color. /// /// /// if the closest named color is the provided value.
if any /// other named color is closer to this than . ///
/// /// If is equidistant from two named colors, the result of this method is not guaranteed to /// be determinate. /// [Pure] [MethodImpl (MethodImplOptions.AggressiveInlining)] public bool IsClosestToNamedColor (in ColorName namedColor) { return GetClosestNamedColor () == namedColor; } /// /// Determines if the closest named to /> is the provided /// . /// /// /// The color to test against the value in /// . /// /// /// The to check if this is closer /// to than any other configured named color. /// /// /// if the closest named color to is the provided value.
/// if any other named color is closer to than /// . ///
/// /// If is equidistant from two named colors, the result of this method is not guaranteed /// to be determinate. /// [Pure] [MethodImpl (MethodImplOptions.AggressiveInlining)] public static bool IsColorClosestToNamedColor (in Color color, in ColorName namedColor) { return color.IsClosestToNamedColor (in namedColor); } /// Gets the "closest" named color to this value. /// /// /// Distance is defined here as the Euclidean distance between each color interpreted as a . /// /// The order of the values in the passed Vector3 must be /// /// [SkipLocalsInit] internal static ColorName GetClosestNamedColor (Color inputColor) { return ColorExtensions.ColorToNameMap.MinBy (pair => CalculateColorDistance (inputColor, pair.Key)).Value; } [SkipLocalsInit] private static float CalculateColorDistance (in Vector4 color1, in Vector4 color2) { return Vector4.Distance (color1, color2); } /// /// Gets a color that is the same hue as the current color, but with a different lightness. /// /// public Color GetHighlightColor () { // TODO: This is a temporary implementation; just enough to show how it could work. var hsl = ColorHelper.ColorConverter.RgbToHsl(new RGB (R, G, B)); var amount = .7; if (hsl.L <= 5) { return DarkGray; } hsl.L = (byte)(hsl.L * amount); var rgb = ColorHelper.ColorConverter.HslToRgb (hsl); return new (rgb.R, rgb.G, rgb.B); } /// /// Gets a color that is the same hue as the current color, but with a different lightness. /// /// public Color GetDarkerColor () { // TODO: This is a temporary implementation; just enough to show how it could work. var hsl = ColorHelper.ColorConverter.RgbToHsl (new RGB (R, G, B)); var amount = .3; if (hsl.L <= 5) { return DarkGray; } hsl.L = (byte)(hsl.L * amount); var rgb = ColorHelper.ColorConverter.HslToRgb (hsl); return new (rgb.R, rgb.G, rgb.B); } #region Legacy Color Names /// The black color. public const ColorName Black = ColorName.Black; /// The blue color. public const ColorName Blue = ColorName.Blue; /// The green color. public const ColorName Green = ColorName.Green; /// The cyan color. public const ColorName Cyan = ColorName.Cyan; /// The red color. public const ColorName Red = ColorName.Red; /// The magenta color. public const ColorName Magenta = ColorName.Magenta; /// The yellow color. public const ColorName Yellow = ColorName.Yellow; /// The gray color. public const ColorName Gray = ColorName.Gray; /// The dark gray color. public const ColorName DarkGray = ColorName.DarkGray; /// The bright bBlue color. public const ColorName BrightBlue = ColorName.BrightBlue; /// The bright green color. public const ColorName BrightGreen = ColorName.BrightGreen; /// The bright cyan color. public const ColorName BrightCyan = ColorName.BrightCyan; /// The bright red color. public const ColorName BrightRed = ColorName.BrightRed; /// The bright magenta color. public const ColorName BrightMagenta = ColorName.BrightMagenta; /// The bright yellow color. public const ColorName BrightYellow = ColorName.BrightYellow; /// The White color. public const ColorName White = ColorName.White; #endregion }