Files
PrinceOfGlory/Assets/Editor/LWGUI-1.x/Runtime/LwguiGradient/LwguiGradient.cs
kridoo 6e91a0c7f0 111
2025-09-15 17:32:08 +08:00

500 lines
16 KiB
C#

// Copyright (c) Jason Ma
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace LWGUI.Runtime.LwguiGradient
{
[Serializable]
public class LwguiGradient : IDisposable
{
#region Channel Enum
public enum Channel
{
Red = 0,
Green = 1,
Blue = 2,
Alpha = 3,
Num = 4
}
[Flags]
public enum ChannelMask
{
None = 0,
Red = 1 << 0,
Green = 1 << 1,
Blue = 1 << 2,
Alpha = 1 << 3,
RGB = Red | Green | Blue,
All = RGB | Alpha
}
public enum GradientTimeRange
{
One = 1,
TwentyFour = 24,
TwentyFourHundred = 2400
}
public static bool HasChannelMask(ChannelMask channelMaskA, ChannelMask channelMaskB) => ((uint)channelMaskA & (uint)channelMaskB) > 0;
public static bool IsChannelIndexInMask(int channelIndex, ChannelMask channelMask) => ((uint)channelMask & (uint)(1 << channelIndex)) > 0;
public static ChannelMask ChannelIndexToMask(int channelIndex) => (ChannelMask)(1 << channelIndex);
#endregion
#region Const
public static readonly Color[] channelColors = new[] { Color.red, Color.green, Color.blue, Color.white };
public static readonly char[] channelNames = new[] { 'r', 'g', 'b', 'a' };
public static AnimationCurve defaultCurve => new (new Keyframe(0, 1).SetLinearTangentMode(), new Keyframe(1, 1).SetLinearTangentMode());
#endregion
#region Data
// The complete data is stored by RGBA Curves and can be converted into Texture
[SerializeField] private List<AnimationCurve> _curves;
#endregion
#region Constructor
public LwguiGradient()
{
_curves = new List<AnimationCurve>();
for (int c = 0; c < (int)Channel.Num; c++)
_curves.Add(defaultCurve);
}
public LwguiGradient(LwguiGradient src)
{
DeepCopyFrom(src);
}
public LwguiGradient(params Keyframe[] keys)
{
_curves = new List<AnimationCurve>();
for (int c = 0; c < (int)Channel.Num; c++)
_curves.Add(new AnimationCurve());
if (keys?.Length > 0)
{
AddKeys(keys, ChannelMask.All);
}
}
public LwguiGradient(Color[] colors, float[] times)
{
_curves = new List<AnimationCurve>();
for (int c = 0; c < (int)Channel.Num; c++)
_curves.Add(new AnimationCurve());
if (colors == null || times == null)
return;
for (int i = 0; i < Mathf.Min(colors.Length, times.Length); i++)
{
for (int c = 0; c < (int)Channel.Num; c++)
{
_curves[c].AddKey(new Keyframe(times[i], colors[i][c]).SetLinearTangentMode());
}
}
SetLinearTangentMode();
}
public LwguiGradient(List<AnimationCurve> inRgbaCurves) => SetRgbaCurves(inRgbaCurves);
public static LwguiGradient white
{
get => new ();
}
public static LwguiGradient gray
{
get => new (new []{Color.gray, Color.gray}, new []{0.0f, 1.0f});
}
public static LwguiGradient black
{
get => new (new []{Color.black, Color.black}, new []{0.0f, 1.0f});
}
public static LwguiGradient red
{
get => new (new []{Color.red, Color.red}, new []{0.0f, 1.0f});
}
public static LwguiGradient green
{
get => new (new []{Color.green, Color.green}, new []{0.0f, 1.0f});
}
public static LwguiGradient blue
{
get => new (new []{Color.blue, Color.blue}, new []{0.0f, 1.0f});
}
public static LwguiGradient cyan
{
get => new (new []{Color.cyan, Color.cyan}, new []{0.0f, 1.0f});
}
public static LwguiGradient magenta
{
get => new (new []{Color.magenta, Color.magenta}, new []{0.0f, 1.0f});
}
public static LwguiGradient yellow
{
get => new (new []{Color.yellow, Color.yellow}, new []{0.0f, 1.0f});
}
#endregion
public int GetValueBasedHashCode()
{
var hash = 17;
if (_curves != null)
{
foreach (var curve in _curves)
{
if (curve != null)
{
hash = hash * 23 + curve.GetHashCode();
}
}
}
return hash;
}
public void Dispose()
{
_curves?.Clear();
}
public void Clear(ChannelMask channelMask = ChannelMask.All)
{
_curves ??= new List<AnimationCurve>();
for (int c = 0; c < (int)Channel.Num; c++)
{
if (!IsChannelIndexInMask(c, channelMask)) continue;
if (_curves.Count > c) _curves[c].keys = Array.Empty<Keyframe>();
else _curves.Add(new AnimationCurve());
}
}
public void DeepCopyFrom(LwguiGradient src)
{
_curves ??= new List<AnimationCurve>();
for (int c = 0; c < (int)Channel.Num; c++)
{
if (_curves.Count == c)
_curves.Add(new AnimationCurve());
_curves[c].keys = Array.Empty<Keyframe>();
}
for (int c = 0; c < src._curves.Count; c++)
{
foreach (var key in src._curves[c].keys)
{
_curves[c].AddKey(key);
}
}
}
public void SetCurve(AnimationCurve curve, ChannelMask channelMask)
{
curve ??= defaultCurve;
for (int c = 0; c < (int)Channel.Num; c++)
{
if (!IsChannelIndexInMask(c, channelMask)) continue;
_curves[c] = curve;
}
}
public void SetRgbaCurves(List<AnimationCurve> inRgbaCurves)
{
_curves = new List<AnimationCurve>();
for (int c = 0; c < (int)Channel.Num; c++)
{
if (inRgbaCurves?.Count > c && inRgbaCurves[c]?.keys.Length > 0)
{
_curves.Add(inRgbaCurves[c]);
}
else
{
_curves.Add(defaultCurve);
}
}
}
public void AddKey(Keyframe key, ChannelMask channelMask)
{
for (int c = 0; c < (int)Channel.Num; c++)
{
if (!IsChannelIndexInMask(c, channelMask))
continue;
_curves[c].AddKey(key);
}
}
public void AddKeys(Keyframe[] keys, ChannelMask channelMask)
{
for (int i = 0; i < keys?.Length; i++)
{
AddKey(keys[i], channelMask);
}
}
public List<AnimationCurve> rawCurves
{
get => _curves;
set => SetRgbaCurves(value);
}
public AnimationCurve redCurve
{
get => _curves[(int)Channel.Red] ?? defaultCurve;
set => SetCurve(value, ChannelMask.Red);
}
public AnimationCurve greenCurve
{
get => _curves[(int)Channel.Green] ?? defaultCurve;
set => SetCurve(value, ChannelMask.Green);
}
public AnimationCurve blueCurve
{
get => _curves[(int)Channel.Blue] ?? defaultCurve;
set => SetCurve(value, ChannelMask.Blue);
}
public AnimationCurve alphaCurve
{
get => _curves[(int)Channel.Alpha] ?? defaultCurve;
set => SetCurve(value, ChannelMask.Alpha);
}
public Color Evaluate(float time, ChannelMask channelMask = ChannelMask.All, GradientTimeRange timeRange = GradientTimeRange.One)
{
time /= (int)timeRange;
if (channelMask == ChannelMask.Alpha)
{
var alpha = _curves[(int)Channel.Alpha].Evaluate(time);
return new Color(alpha, alpha, alpha, 1);
}
return new Color(
IsChannelIndexInMask((int)Channel.Red, channelMask) ? _curves[(int)Channel.Red].Evaluate(time) : 0,
IsChannelIndexInMask((int)Channel.Green, channelMask) ? _curves[(int)Channel.Green].Evaluate(time) : 0,
IsChannelIndexInMask((int)Channel.Blue, channelMask) ? _curves[(int)Channel.Blue].Evaluate(time) : 0,
IsChannelIndexInMask((int)Channel.Alpha, channelMask) ? _curves[(int)Channel.Alpha].Evaluate(time) : 1);
}
public void SetLinearTangentMode()
{
for (int c = 0; c < (int)Channel.Num; c++)
{
_curves[c].SetLinearTangents();
}
}
#region LwguiGradient <=> Ramp Texture
public Color[] GetPixels(int width, int height, ChannelMask channelMask = ChannelMask.All)
{
var pixels = new Color[width * height];
for (var x = 0; x < width; x++)
{
var u = x / (float)width;
var col = Evaluate(u, channelMask);
for (int i = 0; i < height; i++)
{
pixels[x + i * width] = col;
}
}
return pixels;
}
public Texture2D GetPreviewRampTexture(int width = 256, int height = 1, ColorSpace colorSpace = ColorSpace.Gamma, ChannelMask channelMask = ChannelMask.All)
{
if (LwguiGradientHelper.TryGetRampPreview(this, width, height, colorSpace, channelMask, out var cachedPreview))
return cachedPreview;
var rampPreview = new Texture2D(width, height, TextureFormat.RGBA32, false, colorSpace == ColorSpace.Linear);
var pixels = GetPixels(width, height, channelMask);
rampPreview.SetPixels(pixels);
rampPreview.wrapMode = TextureWrapMode.Clamp;
rampPreview.name = "LWGUI Gradient Preview";
rampPreview.Apply();
LwguiGradientHelper.SetRampPreview(this, width, height, colorSpace, channelMask, rampPreview);
return rampPreview;
}
#endregion
#region LwguiGradient <=> Gradient
public struct LwguiKeyframe
{
public float time;
public float value;
public int index;
public LwguiKeyframe(float time, float value, int index)
{
this.time = time;
this.value = value;
this.index = index;
}
}
public class LwguiMergedColorCurves : IDisposable
{
public List<List<LwguiKeyframe>> curves = new ();
public LwguiMergedColorCurves()
{
for (int c = 0; c < (int)Channel.Num; c++)
curves.Add(new List<LwguiKeyframe>());
}
public LwguiMergedColorCurves(List<AnimationCurve> rgbaCurves)
{
for (int c = 0; c < (int)Channel.Num; c++)
curves.Add(new List<LwguiKeyframe>());
// Get color keys
{
var timeColorDic = new Dictionary<float, List<(float value, int index)>>();
for (int c = 0; c < (int)Channel.Num - 1; c++)
{
var keys = rgbaCurves[c].keys;
for (int j = 0; j < keys.Length; j++)
{
var keyframe = keys[j];
if (timeColorDic.ContainsKey(keyframe.time))
{
timeColorDic[keyframe.time].Add((keyframe.value, j));
}
else
{
timeColorDic.Add(keyframe.time, new List<(float value, int index)> { (keyframe.value, j) });
}
}
}
foreach (var kwPair in timeColorDic)
{
if (kwPair.Value.Count == (int)Channel.Num - 1)
{
for (int c = 0; c < (int)Channel.Num - 1; c++)
{
curves[c].Add(new LwguiKeyframe(kwPair.Key, kwPair.Value[c].value, kwPair.Value[c].index));
}
}
}
}
// Get alpha keys
for (int i = 0; i < rgbaCurves[(int)Channel.Alpha].keys.Length; i++)
{
var alphaKey = rgbaCurves[(int)Channel.Alpha].keys[i];
curves[(int)Channel.Alpha].Add(new LwguiKeyframe(alphaKey.time, alphaKey.value, i));
}
}
public LwguiMergedColorCurves(Gradient gradient)
{
for (int c = 0; c < (int)Channel.Num; c++)
curves.Add(new List<LwguiKeyframe>());
foreach (var colorKey in gradient.colorKeys)
{
for (int c = 0; c < (int)Channel.Num - 1; c++)
{
curves[c].Add(new LwguiKeyframe(colorKey.time, colorKey.color[c], 0));
}
}
foreach (var alphaKey in gradient.alphaKeys)
{
curves[(int)Channel.Alpha].Add(new LwguiKeyframe(alphaKey.time, alphaKey.alpha, 0));
}
}
public Gradient ToGradient(int maxGradientKeyCount = 8) => new Gradient
{
colorKeys = curves[(int)Channel.Red].Select((keyframe, i) => new GradientColorKey(
new Color(
curves[(int)Channel.Red][i].value,
curves[(int)Channel.Green][i].value,
curves[(int)Channel.Blue][i].value),
curves[(int)Channel.Red][i].time))
.Where((key, i) => i < maxGradientKeyCount).ToArray(),
alphaKeys = curves[(int)Channel.Alpha].Select(alphaKey => new GradientAlphaKey(alphaKey.value, alphaKey.time))
.Where((key, i) => i < maxGradientKeyCount).ToArray()
};
public List<AnimationCurve> ToAnimationCurves()
{
var outCurves = new List<AnimationCurve>();
for (int c = 0; c < (int)Channel.Num; c++)
{
var curve = new AnimationCurve();
foreach (var key in curves[c])
{
curve.AddKey(new Keyframe(key.time, key.value).SetLinearTangentMode());
}
curve.SetLinearTangents();
outCurves.Add(curve);
}
return outCurves;
}
public LwguiGradient ToLwguiGradient()
{
return new LwguiGradient(ToAnimationCurves());
}
public void Dispose()
{
curves?.Clear();
}
}
public static LwguiGradient FromGradient(Gradient gradient)
{
return new LwguiMergedColorCurves(gradient).ToLwguiGradient();
}
public Gradient ToGradient(int maxGradientKeyCount = 8)
{
return new LwguiMergedColorCurves(_curves).ToGradient(maxGradientKeyCount);
}
#endregion
}
}