using System.Collections.Frozen;
using System.Globalization;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text.Json.Serialization;
using ColorHelper;
using ColorConverter = ColorHelper.ColorConverter;
namespace Terminal.Gui.Drawing;
///
/// Represents a 24-bit color encoded in ARGB32 format.
///
/// The RGB components define the color identity (what color it is), while the alpha channel defines
/// rendering intent (how transparent it should be when drawn).
///
///
///
///
/// When matching colors to standard color names (e.g., via ),
/// the alpha channel is ignored. This means colors with the same RGB values but different alpha values
/// will resolve to the same color name. This design supports transparency features while maintaining
/// semantic color identity.
///
///
/// While Terminal.Gui does not currently support alpha blending during rendering, the alpha channel
/// is used to indicate rendering intent:
///
/// - Alpha = 0: Fully transparent (don't render)
/// - Alpha = 255: Fully opaque (normal rendering)
/// - Other values: Reserved for future alpha blending support
///
///
///
///
///
///
[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 represents rendering intent (transparency) rather than color identity.
/// Terminal.Gui does not currently perform alpha blending, but uses this value to determine
/// whether to render the color at all (alpha = 0 means don't render).
///
///
/// When matching colors to standard color names, the alpha channel is ignored. For example,
/// new Color(255, 0, 0, 255) and new Color(255, 0, 0, 128) will both be
/// identified as "Red".
///
///
[JsonIgnore]
[field: FieldOffset (3)]
public readonly byte A;
/// The value of this as a in ARGB32 format.
///
/// The alpha channel in the ARGB value represents rendering intent (transparency), not color identity.
/// When matching to standard color names, only the RGB components are considered.
///
[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 a value in the enum.
/// The 16-color value.
public Color (in StandardColor colorName) : this (StandardColors.GetArgb (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.
[ConfigurationProperty (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 (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; }
/// 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); }
///
/// Returns a color with the same hue and saturation as this color, but with a significantly different lightness,
/// making it suitable for use as a highlight or contrast color in UI elements.
///
///
///
/// This method brightens the color if it is dark, or darkens it if it is light, ensuring the result is visually
/// distinct
/// from the original. The algorithm works in HSL color space and adjusts the lightness channel:
///
/// -
/// If the color is dark (lightness < 0.5), the lightness is increased (brightened).
///
/// -
/// If the color is light (lightness >= 0.5), the lightness is decreased (darkened).
///
/// -
///
/// If the adjustment resulted in a color too close to the original, a larger adjustment is
/// made.
///
///
///
/// This ensures the returned color is always visually distinct and suitable for highlighting or selection states.
///
///
/// The returned color will always have the same hue and saturation as the original, but a different lightness.
///
///
/// The percent amount to brighten the color by. The default is 20%.
///
/// A instance with the same hue and saturation as this color, but with a contrasting lightness.
///
///
///
/// var baseColor = new Color(100, 100, 100);
/// var highlight = baseColor.GetHighlightColor();
/// // highlight will be a lighter or darker version of baseColor, depending on its original lightness.
///
///
public Color GetBrighterColor (double brightenAmount = 0.2)
{
HSL? hsl = ColorConverter.RgbToHsl (new (R, G, B));
double lNorm = hsl.L / 255.0;
double newL = lNorm < 0.5 ? Math.Min (1.0, lNorm + brightenAmount) : Math.Max (0.0, lNorm - brightenAmount);
if (Math.Abs (newL - lNorm) < 0.1)
{
newL = lNorm < 0.5 ? Math.Min (1.0, lNorm + 2 * brightenAmount) : Math.Max (0.0, lNorm - 2 * brightenAmount);
}
var newHsl = new HSL (hsl.H, hsl.S, (byte)(newL * 255));
RGB? rgb = ColorConverter.HslToRgb (newHsl);
return new (rgb.R, rgb.G, rgb.B);
}
///
/// Returns a color with the same hue and saturation as this color, but with a significantly lower lightness,
/// making it suitable for use as a shadow or background contrast color in UI elements.
///
///
///
/// This method darkens the color by reducing its lightness in HSL color space:
///
/// -
/// If the color is already very dark, returns .
///
/// -
/// Otherwise, reduces the lightness by a fixed amount (default 30%).
///
/// -
///
/// If the adjustment resulted in a color too close to the original, a larger adjustment is
/// made.
///
///
///
/// This ensures the returned color is always visually distinct and suitable for shadowing or de-emphasis.
///
///
/// The percent amount to dim the color by. The default is 20%.
///
/// A instance with the same hue and saturation as this color, but with a much lower lightness.
///
public Color GetDimColor (double dimAmount = 0.2)
{
HSL hsl = ColorConverter.RgbToHsl (new (R, G, B));
double lNorm = hsl.L / 255.0;
double newL = Math.Max (0.0, lNorm - dimAmount);
// If the color is already very dark, return a standard dark gray for visibility
if (lNorm <= 0.1)
{
return new (ColorName16.DarkGray);
}
// If the new lightness is too close to the original, force a bigger change
if (Math.Abs (newL - lNorm) < 0.1)
{
newL = Math.Max (0.0, lNorm - 2 * dimAmount);
}
var newHsl = new HSL (hsl.H, hsl.S, (byte)(newL * 255));
RGB rgb = ColorConverter.HslToRgb (newHsl);
return new (rgb.R, rgb.G, rgb.B);
}
#region Legacy Color Names
// ReSharper disable InconsistentNaming
/// 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
}