1011d126d8
Co-authored-by: Cursor <cursoragent@cursor.com>
326 lines
16 KiB
C#
326 lines
16 KiB
C#
using Colourful;
|
|
using Dalamud.Game.ClientState.Objects.Enums;
|
|
using Dalamud.Game.ClientState.Objects.SubKinds;
|
|
using Dalamud.Game.ClientState.Objects.Types;
|
|
using HSUI.Config;
|
|
using HSUI.Enums;
|
|
using HSUI.Interface.GeneralElements;
|
|
using System;
|
|
using System.Numerics;
|
|
|
|
namespace HSUI.Helpers
|
|
{
|
|
public static class ColorUtils
|
|
{
|
|
|
|
//Build our converter objects and store them in a field. This will be used to convert our PluginConfigColors into different color spaces to be used for interpolation
|
|
private static readonly IColorConverter<RGBColor, LabColor> _rgbToLab = new ConverterBuilder().FromRGB().ToLab().Build();
|
|
private static readonly IColorConverter<LabColor, RGBColor> _labToRgb = new ConverterBuilder().FromLab().ToRGB().Build();
|
|
|
|
private static readonly IColorConverter<RGBColor, LChabColor> _rgbToLChab = new ConverterBuilder().FromRGB().ToLChab().Build();
|
|
private static readonly IColorConverter<LChabColor, RGBColor> _lchabToRgb = new ConverterBuilder().FromLChab().ToRGB().Build();
|
|
|
|
private static readonly IColorConverter<RGBColor, XYZColor> _rgbToXyz = new ConverterBuilder().FromRGB(RGBWorkingSpaces.sRGB).ToXYZ(Illuminants.D65).Build();
|
|
private static readonly IColorConverter<XYZColor, RGBColor> _xyzToRgb = new ConverterBuilder().FromXYZ(Illuminants.D65).ToRGB(RGBWorkingSpaces.sRGB).Build();
|
|
|
|
private static readonly IColorConverter<RGBColor, LChuvColor> _rgbToLChuv = new ConverterBuilder().FromRGB().ToLChuv().Build();
|
|
private static readonly IColorConverter<LChuvColor, RGBColor> _lchuvToRgb = new ConverterBuilder().FromLChuv().ToRGB().Build();
|
|
|
|
private static readonly IColorConverter<RGBColor, LuvColor> _rgbToLuv = new ConverterBuilder().FromRGB().ToLuv().Build();
|
|
private static readonly IColorConverter<LuvColor, RGBColor> _luvToRgb = new ConverterBuilder().FromLuv().ToRGB().Build();
|
|
|
|
private static readonly IColorConverter<RGBColor, JzazbzColor> _rgbToJzazbz = new ConverterBuilder().FromRGB().ToJzazbz().Build();
|
|
private static readonly IColorConverter<JzazbzColor, RGBColor> _jzazbzToRgb = new ConverterBuilder().FromJzazbz().ToRGB().Build();
|
|
|
|
private static readonly IColorConverter<RGBColor, JzCzhzColor> _rgbToJzCzhz = new ConverterBuilder().FromRGB().ToJzCzhz().Build();
|
|
private static readonly IColorConverter<JzCzhzColor, RGBColor> _jzCzhzToRgb = new ConverterBuilder().FromJzCzhz().ToRGB().Build();
|
|
|
|
//Simple LinearInterpolation method. T = [0 , 1]
|
|
private static float LinearInterpolation(float left, float right, float t)
|
|
=> left + ((right - left) * t);
|
|
|
|
public static PluginConfigColor GetColorByScale(float i, ColorByHealthValueConfig config) =>
|
|
GetColorByScale(i, config.LowHealthColorThreshold / 100f, config.FullHealthColorThreshold / 100f, config.LowHealthColor, config.FullHealthColor, config.MaxHealthColor, config.UseMaxHealthColor, config.BlendMode);
|
|
|
|
//Method used to interpolate two PluginConfigColors
|
|
//i is scale [0 , 1]
|
|
//min and max are used for color thresholds. for instance return colorLeft if i < min or return ColorRight if i > max
|
|
public static PluginConfigColor GetColorByScale(float i, float min, float max, PluginConfigColor colorLeft, PluginConfigColor colorRight, PluginConfigColor colorMax, bool useMaxColor, BlendMode blendMode)
|
|
{
|
|
try
|
|
{
|
|
// Guard against null/invalid config (can happen with corrupted or migrated config)
|
|
if (colorLeft == null || colorRight == null || colorMax == null)
|
|
return PluginConfigColor.FromHex(0xFF808080);
|
|
|
|
//Set our thresholds where the ratio is the range of values we will use for interpolation.
|
|
//Values outside this range will either return colorLeft or colorRight
|
|
float ratio = i;
|
|
if (min > 0 || max < 1)
|
|
{
|
|
if (i < min)
|
|
{
|
|
ratio = 0;
|
|
}
|
|
else if (i > max)
|
|
{
|
|
ratio = 1;
|
|
}
|
|
else
|
|
{
|
|
float range = max - min;
|
|
if (MathF.Abs(range) < 0.0001f) ratio = 1; // avoid divide-by-zero when min==max
|
|
else ratio = (i - min) / range;
|
|
}
|
|
}
|
|
|
|
//Convert our PluginConfigColor to RGBColor
|
|
RGBColor rgbColorLeft = new RGBColor(colorLeft.Vector.X, colorLeft.Vector.Y, colorLeft.Vector.Z);
|
|
RGBColor rgbColorRight = new RGBColor(colorRight.Vector.X, colorRight.Vector.Y, colorRight.Vector.Z);
|
|
|
|
//Interpolate our Alpha now
|
|
float alpha = LinearInterpolation(colorLeft.Vector.W, colorRight.Vector.W, ratio);
|
|
|
|
if (ratio >= 1 && useMaxColor)
|
|
{
|
|
return new PluginConfigColor(new Vector4((float)colorMax.Vector.X, (float)colorMax.Vector.Y, (float)colorMax.Vector.Z, colorMax.Vector.W));
|
|
}
|
|
|
|
//Allow the users to select different blend modes since interpolating between two colors can result in different blending depending on the color space
|
|
//We convert our RGBColor values into different color spaces. We then interpolate each channel before converting the color back into RGBColor space
|
|
switch (blendMode)
|
|
{
|
|
case BlendMode.LAB:
|
|
{
|
|
//convert RGB to LAB
|
|
LabColor LabLeft = _rgbToLab.Convert(rgbColorLeft);
|
|
LabColor LabRight = _rgbToLab.Convert(rgbColorRight);
|
|
|
|
RGBColor Lab2RGB = _labToRgb.Convert(
|
|
new LabColor(
|
|
LinearInterpolation((float)LabLeft.L, (float)LabRight.L, ratio),
|
|
LinearInterpolation((float)LabLeft.a, (float)LabRight.a, ratio),
|
|
LinearInterpolation((float)LabLeft.b, (float)LabRight.b, ratio)
|
|
)
|
|
);
|
|
|
|
Lab2RGB.NormalizeIntensity();
|
|
return new PluginConfigColor(new Vector4((float)Lab2RGB.R, (float)Lab2RGB.G, (float)Lab2RGB.B, alpha));
|
|
}
|
|
|
|
case BlendMode.LChab:
|
|
{
|
|
//convert RGB to LChab
|
|
LChabColor LChabLeft = _rgbToLChab.Convert(rgbColorLeft);
|
|
LChabColor LChabRight = _rgbToLChab.Convert(rgbColorRight);
|
|
|
|
RGBColor LChab2RGB = _lchabToRgb.Convert(
|
|
new LChabColor(
|
|
LinearInterpolation((float)LChabLeft.L, (float)LChabRight.L, ratio),
|
|
LinearInterpolation((float)LChabLeft.C, (float)LChabRight.C, ratio),
|
|
LinearInterpolation((float)LChabLeft.h, (float)LChabRight.h, ratio)
|
|
)
|
|
);
|
|
|
|
LChab2RGB.NormalizeIntensity();
|
|
|
|
return new PluginConfigColor(new Vector4((float)LChab2RGB.R, (float)LChab2RGB.G, (float)LChab2RGB.B, alpha));
|
|
}
|
|
case BlendMode.XYZ:
|
|
{
|
|
//convert RGB to XYZ
|
|
XYZColor XYZLeft = _rgbToXyz.Convert(rgbColorLeft);
|
|
XYZColor XYZRight = _rgbToXyz.Convert(rgbColorRight);
|
|
|
|
RGBColor XYZ2RGB = _xyzToRgb.Convert(
|
|
new XYZColor(
|
|
LinearInterpolation((float)XYZLeft.X, (float)XYZRight.X, ratio),
|
|
LinearInterpolation((float)XYZLeft.Y, (float)XYZRight.Y, ratio),
|
|
LinearInterpolation((float)XYZLeft.Z, (float)XYZRight.Z, ratio)
|
|
)
|
|
);
|
|
|
|
XYZ2RGB.NormalizeIntensity();
|
|
|
|
return new PluginConfigColor(new Vector4((float)XYZ2RGB.R, (float)XYZ2RGB.G, (float)XYZ2RGB.B, alpha));
|
|
}
|
|
case BlendMode.RGB:
|
|
{
|
|
//No conversion needed here because we are already working in RGB space
|
|
RGBColor newRGB = new RGBColor(
|
|
LinearInterpolation((float)rgbColorLeft.R, (float)rgbColorRight.R, ratio),
|
|
LinearInterpolation((float)rgbColorLeft.G, (float)rgbColorRight.G, ratio),
|
|
LinearInterpolation((float)rgbColorLeft.B, (float)rgbColorRight.B, ratio)
|
|
);
|
|
|
|
return new PluginConfigColor(new Vector4((float)newRGB.R, (float)newRGB.G, (float)newRGB.B, alpha));
|
|
}
|
|
case BlendMode.LChuv:
|
|
{
|
|
//convert RGB to LChuv
|
|
LChuvColor LChuvLeft = _rgbToLChuv.Convert(rgbColorLeft);
|
|
LChuvColor LChuvRight = _rgbToLChuv.Convert(rgbColorRight);
|
|
|
|
RGBColor LChuv2RGB = _lchuvToRgb.Convert(
|
|
new LChuvColor(
|
|
LinearInterpolation((float)LChuvLeft.L, (float)LChuvRight.L, ratio),
|
|
LinearInterpolation((float)LChuvLeft.C, (float)LChuvRight.C, ratio),
|
|
LinearInterpolation((float)LChuvLeft.h, (float)LChuvRight.h, ratio)
|
|
)
|
|
);
|
|
|
|
LChuv2RGB.NormalizeIntensity();
|
|
|
|
return new PluginConfigColor(new Vector4((float)LChuv2RGB.R, (float)LChuv2RGB.G, (float)LChuv2RGB.B, alpha));
|
|
}
|
|
|
|
case BlendMode.Luv:
|
|
{
|
|
//convert RGB to Luv
|
|
LuvColor LuvLeft = _rgbToLuv.Convert(rgbColorLeft);
|
|
LuvColor LuvRight = _rgbToLuv.Convert(rgbColorRight);
|
|
|
|
RGBColor Luv2RGB = _luvToRgb.Convert(
|
|
new LuvColor(
|
|
LinearInterpolation((float)LuvLeft.L, (float)LuvRight.L, ratio),
|
|
LinearInterpolation((float)LuvLeft.u, (float)LuvRight.u, ratio),
|
|
LinearInterpolation((float)LuvLeft.v, (float)LuvRight.v, ratio)
|
|
)
|
|
);
|
|
|
|
Luv2RGB.NormalizeIntensity();
|
|
|
|
return new PluginConfigColor(new Vector4((float)Luv2RGB.R, (float)Luv2RGB.G, (float)Luv2RGB.B, alpha));
|
|
|
|
}
|
|
case BlendMode.Jzazbz:
|
|
{
|
|
//convert RGB to Jzazbz
|
|
JzazbzColor JzazbzLeft = _rgbToJzazbz.Convert(rgbColorLeft);
|
|
JzazbzColor JzazbzRight = _rgbToJzazbz.Convert(rgbColorRight);
|
|
|
|
RGBColor Jzazbz2RGB = _jzazbzToRgb.Convert(
|
|
new JzazbzColor(
|
|
LinearInterpolation((float)JzazbzLeft.Jz, (float)JzazbzRight.Jz, ratio),
|
|
LinearInterpolation((float)JzazbzLeft.az, (float)JzazbzRight.az, ratio),
|
|
LinearInterpolation((float)JzazbzLeft.bz, (float)JzazbzRight.bz, ratio)
|
|
)
|
|
);
|
|
|
|
Jzazbz2RGB.NormalizeIntensity();
|
|
|
|
return new PluginConfigColor(new Vector4((float)Jzazbz2RGB.R, (float)Jzazbz2RGB.G, (float)Jzazbz2RGB.B, alpha));
|
|
}
|
|
case BlendMode.JzCzhz:
|
|
{
|
|
//convert RGB to JzCzhz
|
|
JzCzhzColor JzCzhzLeft = _rgbToJzCzhz.Convert(rgbColorLeft);
|
|
JzCzhzColor JzCzhzRight = _rgbToJzCzhz.Convert(rgbColorRight);
|
|
|
|
RGBColor JzCzhz2RGB = _jzCzhzToRgb.Convert(
|
|
new JzCzhzColor(
|
|
LinearInterpolation((float)JzCzhzLeft.Jz, (float)JzCzhzRight.Jz, ratio),
|
|
LinearInterpolation((float)JzCzhzLeft.Cz, (float)JzCzhzRight.Cz, ratio),
|
|
LinearInterpolation((float)JzCzhzLeft.hz, (float)JzCzhzRight.hz, ratio)
|
|
)
|
|
);
|
|
|
|
JzCzhz2RGB.NormalizeIntensity();
|
|
|
|
return new PluginConfigColor(new Vector4((float)JzCzhz2RGB.R, (float)JzCzhz2RGB.G, (float)JzCzhz2RGB.B, alpha));
|
|
}
|
|
}
|
|
return new(Vector4.One);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Plugin.Logger.Warning($"[HSUI] GetColorByScale failed (config may be corrupted): {ex.Message}");
|
|
return PluginConfigColor.FromHex(0xFF808080);
|
|
}
|
|
}
|
|
|
|
public static PluginConfigColor ColorForActor(IGameObject? actor)
|
|
{
|
|
if (actor == null || actor is not ICharacter character)
|
|
{
|
|
return GlobalColors.Instance.NPCNeutralColor;
|
|
}
|
|
|
|
if (character.ObjectKind == ObjectKind.Player ||
|
|
character.SubKind == 9 && character.ClassJob.RowId > 0)
|
|
{
|
|
return GlobalColors.Instance.SafeColorForJobId(character.ClassJob.RowId);
|
|
}
|
|
|
|
bool isHostile = Utils.IsHostile(character);
|
|
|
|
if (character is IBattleNpc npc)
|
|
{
|
|
if ((npc.BattleNpcKind == BattleNpcSubKind.Enemy || npc.BattleNpcKind == BattleNpcSubKind.BattleNpcPart) && isHostile)
|
|
{
|
|
return GlobalColors.Instance.NPCHostileColor;
|
|
}
|
|
else
|
|
{
|
|
return GlobalColors.Instance.NPCFriendlyColor;
|
|
}
|
|
}
|
|
|
|
return isHostile ? GlobalColors.Instance.NPCNeutralColor : GlobalColors.Instance.NPCFriendlyColor;
|
|
}
|
|
|
|
public static PluginConfigColor? ColorForCharacter(
|
|
IGameObject? gameObject,
|
|
uint currentHp = 0,
|
|
uint maxHp = 0,
|
|
bool useJobColor = false,
|
|
bool useRoleColor = false,
|
|
ColorByHealthValueConfig? colorByHealthConfig = null)
|
|
{
|
|
ICharacter? character = gameObject as ICharacter;
|
|
|
|
if (useJobColor && character != null)
|
|
{
|
|
return ColorForActor(character);
|
|
}
|
|
else if (useRoleColor)
|
|
{
|
|
return character is IPlayerCharacter ?
|
|
GlobalColors.Instance.SafeRoleColorForJobId(character.ClassJob.RowId) :
|
|
ColorForActor(character);
|
|
}
|
|
else if (colorByHealthConfig != null && colorByHealthConfig.Enabled && character != null)
|
|
{
|
|
var scale = (float)currentHp / Math.Max(1, maxHp);
|
|
if (colorByHealthConfig.UseJobColorAsMaxHealth)
|
|
{
|
|
return GetColorByScale(
|
|
scale,
|
|
colorByHealthConfig.LowHealthColorThreshold / 100f,
|
|
colorByHealthConfig.FullHealthColorThreshold / 100f,
|
|
colorByHealthConfig.LowHealthColor,
|
|
colorByHealthConfig.FullHealthColor,
|
|
ColorForActor(character),
|
|
colorByHealthConfig.UseMaxHealthColor,
|
|
colorByHealthConfig.BlendMode
|
|
);
|
|
}
|
|
else if (colorByHealthConfig.UseRoleColorAsMaxHealth)
|
|
{
|
|
return GetColorByScale(scale,
|
|
colorByHealthConfig.LowHealthColorThreshold / 100f,
|
|
colorByHealthConfig.FullHealthColorThreshold / 100f,
|
|
colorByHealthConfig.LowHealthColor, colorByHealthConfig.FullHealthColor,
|
|
character is IPlayerCharacter ? GlobalColors.Instance.SafeRoleColorForJobId(character.ClassJob.RowId) : ColorForActor(character),
|
|
colorByHealthConfig.UseMaxHealthColor,
|
|
colorByHealthConfig.BlendMode
|
|
);
|
|
}
|
|
return GetColorByScale(scale, colorByHealthConfig);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|
|
}
|