#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
}