// ReSharper disable UnusedMember.Global using System.Collections.Generic; using System.ComponentModel; using System.Linq; using RGB.NET.Core; namespace RGB.NET.Presets.Textures.Gradients; /// /// /// Represents a linear interpolated gradient with n stops. /// public sealed class LinearGradient : AbstractGradient { #region Properties & Fields private bool _isOrderedGradientListDirty = true; private readonly List _orderedGradientStops = new(); #endregion #region Constructors /// /// /// Initializes a new instance of the class. /// public LinearGradient() { Initialize(); } /// /// /// Initializes a new instance of the class. /// /// The stops with which the gradient should be initialized. public LinearGradient(params GradientStop[] gradientStops) : base(gradientStops) { Initialize(); } /// /// /// Initializes a new instance of the class. /// /// Specifies whether the gradient should wrapp or not (see for an example of what this means). /// The stops with which the gradient should be initialized. public LinearGradient(bool wrapGradient, params GradientStop[] gradientStops) : base(wrapGradient, gradientStops) { Initialize(); } #endregion #region Methods private void Initialize() { void OnGradientStopOnPropertyChanged(object? sender, PropertyChangedEventArgs args) => _isOrderedGradientListDirty = true; foreach (GradientStop gradientStop in GradientStops) gradientStop.PropertyChanged += OnGradientStopOnPropertyChanged; GradientStops.CollectionChanged += (_, args) => { if (args.OldItems != null) foreach (GradientStop gradientStop in args.OldItems) gradientStop.PropertyChanged -= OnGradientStopOnPropertyChanged; if (args.NewItems != null) foreach (GradientStop gradientStop in args.NewItems) gradientStop.PropertyChanged += OnGradientStopOnPropertyChanged; }; } /// /// /// Gets the linear interpolated at the specified offset. /// /// The percentage offset to take the color from. /// The at the specific offset. public override Color GetColor(float offset) { if (GradientStops.Count == 0) return Color.Transparent; if (GradientStops.Count == 1) return GradientStops[0].Color; if (_isOrderedGradientListDirty) { _orderedGradientStops.Clear(); _orderedGradientStops.AddRange(GradientStops.OrderBy(x => x.Offset)); } (GradientStop gsBefore, GradientStop gsAfter) = GetEnclosingGradientStops(offset, _orderedGradientStops, WrapGradient); float blendFactor = 0; if (!gsBefore.Offset.Equals(gsAfter.Offset)) blendFactor = ((offset - gsBefore.Offset) / (gsAfter.Offset - gsBefore.Offset)); float colA = ((gsAfter.Color.A - gsBefore.Color.A) * blendFactor) + gsBefore.Color.A; float colR = ((gsAfter.Color.R - gsBefore.Color.R) * blendFactor) + gsBefore.Color.R; float colG = ((gsAfter.Color.G - gsBefore.Color.G) * blendFactor) + gsBefore.Color.G; float colB = ((gsAfter.Color.B - gsBefore.Color.B) * blendFactor) + gsBefore.Color.B; return new Color(colA, colR, colG, colB); } /// /// Get the two s encapsulating the specified offset. /// /// The reference offset. /// The ordered list of to choose from. /// Bool indicating if the gradient should be wrapped or not. /// The two s encapsulating the specified offset. private (GradientStop gsBefore, GradientStop gsAfter) GetEnclosingGradientStops(float offset, IEnumerable orderedStops, bool wrap) { LinkedList gradientStops = new(orderedStops); if (wrap) { GradientStop? gsBefore, gsAfter; do { gsBefore = gradientStops.LastOrDefault(n => n.Offset <= offset); if (gsBefore == null) { GradientStop lastStop = gradientStops.Last!.Value; gradientStops.AddFirst(new GradientStop(lastStop.Offset - 1, lastStop.Color)); gradientStops.RemoveLast(); } gsAfter = gradientStops.FirstOrDefault(n => n.Offset >= offset); if (gsAfter == null) { GradientStop firstStop = gradientStops.First!.Value; gradientStops.AddLast(new GradientStop(firstStop.Offset + 1, firstStop.Color)); gradientStops.RemoveFirst(); } } while ((gsBefore == null) || (gsAfter == null)); return (gsBefore, gsAfter); } offset = ClipOffset(offset); return (gradientStops.Last(n => n.Offset <= offset), gradientStops.First(n => n.Offset >= offset)); } #endregion }