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