mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
162 lines
6.6 KiB
C#
162 lines
6.6 KiB
C#
using System;
|
|
using System.Numerics;
|
|
using Robust.Shared.Localization;
|
|
using Robust.Shared.Maths;
|
|
using Robust.Shared.Utility;
|
|
|
|
namespace Robust.Shared.ColorNaming;
|
|
|
|
// color naming algorithim is inspired by https://react-spectrum.adobe.com/blog/accessible-color-descriptions.html
|
|
|
|
public static class ColorNaming
|
|
{
|
|
private static readonly (float Hue, string Loc)[] HueNames =
|
|
{
|
|
(float.DegreesToRadians(0f), "color-pink"),
|
|
(float.DegreesToRadians(15f), "color-red"),
|
|
(float.DegreesToRadians(45f), "color-orange"),
|
|
(float.DegreesToRadians(90f), "color-yellow"),
|
|
(float.DegreesToRadians(135f), "color-green"),
|
|
(float.DegreesToRadians(180f), "color-cyan"),
|
|
(float.DegreesToRadians(240f), "color-blue"),
|
|
(float.DegreesToRadians(285f), "color-purple"),
|
|
(float.DegreesToRadians(330f), "color-pink"),
|
|
};
|
|
// one past 360 because we're now inclusive on the upper for testing if we're out of bounds
|
|
private static readonly (float Hue, string Loc) HueFallback = (float.DegreesToRadians(361f), "color-pink");
|
|
|
|
private const float BrownLightnessThreshold = 0.675f;
|
|
private static readonly LocId OrangeString = "color-orange";
|
|
private static readonly LocId BrownString = "color-brown";
|
|
|
|
private const float VeryDarkLightnessThreshold = 0.25f;
|
|
private const float DarkLightnessThreshold = 0.5f;
|
|
private const float NeutralLightnessThreshold = 0.7f;
|
|
private const float LightLightnessThreshold = 0.85f;
|
|
|
|
private static readonly LocId VeryDarkString = "color-very-dark";
|
|
private static readonly LocId DarkString = "color-dark";
|
|
private static readonly LocId LightString = "color-light";
|
|
private static readonly LocId VeryLightString = "color-very-light";
|
|
|
|
private static readonly LocId MixedHueString = "color-mixed-hue";
|
|
private static readonly LocId LightLowChromaString = "color-pale";
|
|
private static readonly LocId DarkLowChromaString = "color-gray-adjective";
|
|
private static readonly LocId HighChromaString = "color-strong";
|
|
|
|
private static readonly LocId WhiteString = "color-white";
|
|
private static readonly LocId GrayString = "color-gray";
|
|
private static readonly LocId BlackString = "color-black";
|
|
|
|
private const float LowChromaThreshold = 0.07f;
|
|
private const float HighChromaThreshold = 0.16f;
|
|
private const float LightLowChromaThreshold = 0.6f;
|
|
|
|
private const float WhiteLightnessThreshold = 0.99f;
|
|
private const float BlackLightnessThreshold = 0.01f;
|
|
private const float GrayChromaThreshold = 0.01f;
|
|
|
|
private static (string Loc, float AdjustedLightness) DescribeHue(Vector4 oklch, ILocalizationManager localization)
|
|
{
|
|
var (lightness, _, hue, _) = oklch;
|
|
|
|
for (var i = 0; i < HueNames.Length; i++)
|
|
{
|
|
var prevData = HueNames[i];
|
|
var nextData = i+1 < HueNames.Length ? HueNames[i+1] : HueFallback;
|
|
|
|
if (prevData.Hue > hue || hue >= nextData.Hue)
|
|
continue;
|
|
|
|
var loc = prevData.Loc;
|
|
var adjustedLightness = lightness;
|
|
|
|
if (prevData.Loc == OrangeString && lightness <= BrownLightnessThreshold)
|
|
loc = BrownString;
|
|
else if (prevData.Loc == OrangeString)
|
|
adjustedLightness = lightness - BrownLightnessThreshold + DarkLightnessThreshold;
|
|
|
|
if (hue >= (prevData.Hue + nextData.Hue)/2f && prevData.Loc != nextData.Loc)
|
|
{
|
|
if (localization.TryGetString($"{loc}-{nextData.Loc}", out var hueName))
|
|
return (hueName!, adjustedLightness);
|
|
else
|
|
return (localization.GetString(MixedHueString, ("a", localization.GetString(loc)), ("b", localization.GetString(nextData.Loc))), adjustedLightness);
|
|
}
|
|
|
|
return (localization.GetString(loc), adjustedLightness);
|
|
}
|
|
|
|
DebugTools.Assert($"colour ({oklch}) hue {hue} is outside of expected bounds");
|
|
return (localization.GetString("color-unknown"), lightness);
|
|
}
|
|
|
|
private static string? DescribeChroma(Vector4 oklch, ILocalizationManager localization)
|
|
{
|
|
var (lightness, chroma, _, _) = oklch;
|
|
|
|
if (chroma <= LowChromaThreshold)
|
|
{
|
|
if (lightness >= LightLowChromaThreshold)
|
|
return localization.GetString(LightLowChromaString);
|
|
else
|
|
return localization.GetString(DarkLowChromaString);
|
|
}
|
|
else if (chroma >= HighChromaThreshold)
|
|
{
|
|
return localization.GetString(HighChromaString);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static string? DescribeLightness(Vector4 oklch, ILocalizationManager localization)
|
|
{
|
|
return oklch.X switch
|
|
{
|
|
< VeryDarkLightnessThreshold => localization.GetString(VeryDarkString),
|
|
< DarkLightnessThreshold => localization.GetString(DarkString),
|
|
< NeutralLightnessThreshold => null,
|
|
< LightLightnessThreshold => localization.GetString(LightString),
|
|
_ => localization.GetString(VeryLightString)
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Textually describes a color
|
|
/// </summary>
|
|
/// <returns>
|
|
/// Returns a localized textual description of the provided color
|
|
/// </returns>
|
|
/// <param name="srgb">A Color that is assumed to be in SRGB (the default for most cases)</param>
|
|
public static string Describe(Color srgb, ILocalizationManager localization)
|
|
{
|
|
var oklch = Color.ToLch(Color.ToLab(Color.FromSrgb(srgb)));
|
|
|
|
if (oklch.X >= WhiteLightnessThreshold)
|
|
return localization.GetString(WhiteString);
|
|
|
|
if (oklch.X <= BlackLightnessThreshold)
|
|
return localization.GetString(BlackString);
|
|
|
|
var (hueDescription, adjustedLightness) = DescribeHue(oklch, localization);
|
|
oklch.X = adjustedLightness;
|
|
var chromaDescription = DescribeChroma(oklch, localization);
|
|
var lightnessDescription = DescribeLightness(oklch, localization);
|
|
|
|
if (oklch.Y <= GrayChromaThreshold)
|
|
{
|
|
hueDescription = localization.GetString(GrayString);
|
|
chromaDescription = null;
|
|
}
|
|
|
|
return (hueDescription, chromaDescription, lightnessDescription) switch
|
|
{
|
|
({ } hue, { } chroma, { } lightness) => localization.GetString("color-hue-chroma-lightness", ("hue", hue), ("chroma", chroma), ("lightness", lightness)),
|
|
({ } hue, { } chroma, null) => localization.GetString("color-hue-chroma", ("hue", hue), ("chroma", chroma)),
|
|
({ } hue, null, { } lightness) => localization.GetString("color-hue-lightness", ("hue", hue), ("lightness", lightness)),
|
|
({ } hue, null, null) => hue,
|
|
};
|
|
}
|
|
}
|