#nullable enable using System.Collections.Frozen; using System.Diagnostics.Contracts; using System.Drawing; 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 ColorName16 colorName) { this = ColorExtensions.ColorName16ToColorMap [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; } // TODO: ColorName and AnsiColorCode are only needed when a driver is in Force16Color mode and we // TODO: should be able to remove these from any non-Driver-specific usages. /// 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 Colors16 { get => // Transform _colorToNameMap into a Dictionary ColorExtensions.ColorToName16Map.ToDictionary (static kvp => kvp.Value, static kvp => kvp.Key.ToString ("g")); set { // Transform Dictionary into _colorToNameMap ColorExtensions.ColorToName16Map = value.ToFrozenDictionary (GetColorToNameMapKey, GetColorToNameMapValue); return; static Color GetColorToNameMapKey (KeyValuePair kvp) { return new Color (kvp.Value); } static ColorName16 GetColorToNameMapValue (KeyValuePair kvp) { return Enum.TryParse (kvp.Key.ToString (), true, out ColorName16 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.ColorName16ToAnsiColorMap [GetClosestNamedColor16 ()]; } /// /// 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 ColorName16 GetClosestNamedColor16 () { return GetClosestNamedColor16 (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 IsClosestToNamedColor16 (in ColorName16 namedColor) { return GetClosestNamedColor16 () == 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 IsColorClosestToNamedColor16 (in Color color, in ColorName16 namedColor) { return color.IsClosestToNamedColor16 (in namedColor); } /// Gets the "closest" named color to this value. /// /// /// Distance is defined here as the Euclidean distance between each color interpreted as a . /// /// [SkipLocalsInit] internal static ColorName16 GetClosestNamedColor16 (Color inputColor) { return ColorExtensions.ColorToName16Map.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 ColorName16 Black = ColorName16.Black; /// The blue color. public const ColorName16 Blue = ColorName16.Blue; /// The green color. public const ColorName16 Green = ColorName16.Green; /// The cyan color. public const ColorName16 Cyan = ColorName16.Cyan; /// The red color. public const ColorName16 Red = ColorName16.Red; /// The magenta color. public const ColorName16 Magenta = ColorName16.Magenta; /// The yellow color. public const ColorName16 Yellow = ColorName16.Yellow; /// The gray color. public const ColorName16 Gray = ColorName16.Gray; /// The dark gray color. public const ColorName16 DarkGray = ColorName16.DarkGray; /// The bright bBlue color. public const ColorName16 BrightBlue = ColorName16.BrightBlue; /// The bright green color. public const ColorName16 BrightGreen = ColorName16.BrightGreen; /// The bright cyan color. public const ColorName16 BrightCyan = ColorName16.BrightCyan; /// The bright red color. public const ColorName16 BrightRed = ColorName16.BrightRed; /// The bright magenta color. public const ColorName16 BrightMagenta = ColorName16.BrightMagenta; /// The bright yellow color. public const ColorName16 BrightYellow = ColorName16.BrightYellow; /// The White color. public const ColorName16 White = ColorName16.White; #endregion }