// This code is a C# port from python library Terminal Text Effects https://github.com/ChrisBuilds/terminaltexteffects/ namespace Terminal.Gui; /// /// Describes the pattern that a results in e.g. , /// etc /// public enum GradientDirection { /// /// Color varies along Y axis but is constant on X axis. /// Vertical, /// /// Color varies along X axis but is constant on Y axis. /// Horizontal, /// /// Color varies by distance from center (i.e. in circular ripples) /// Radial, /// /// Color varies by X and Y axis (i.e. a slanted gradient) /// Diagonal } /// /// Describes a of colors that can be combined /// to make a color gradient. Use /// to create into gradient fill area maps. /// public class Gradient { /// /// The discrete colors that will make up the . /// public List Spectrum { get; } private readonly bool _loop; private readonly List _stops; private readonly List _steps; /// /// Creates a new instance of the class which hosts a /// of colors including all and interpolated colors /// between each corresponding pair. /// /// The colors to use in the spectrum (N) /// /// The number of colors to generate between each pair (must be N-1 numbers). /// If only one step is passed then it is assumed to be the same distance for all pairs. /// /// True to duplicate the first stop and step so that the gradient repeats itself /// public Gradient (IEnumerable stops, IEnumerable steps, bool loop = false) { _stops = stops.ToList (); if (_stops.Count < 1) { throw new ArgumentException ("At least one color stop must be provided."); } _steps = steps.ToList (); // If multiple colors and only 1 step assume same distance applies to all steps if (_stops.Count > 2 && _steps.Count == 1) { _steps = Enumerable.Repeat (_steps.Single (), _stops.Count () - 1).ToList (); } if (_steps.Any (step => step < 1)) { throw new ArgumentException ("Steps must be greater than 0."); } if (_steps.Count != _stops.Count - 1) { throw new ArgumentException ("Number of steps must be N-1"); } _loop = loop; Spectrum = GenerateGradient (_steps); } /// /// Returns the color to use at the given part of the spectrum /// /// /// Proportion of the way through the spectrum, must be between /// 0 and 1 (inclusive). Returns the last color if is /// . /// /// /// public Color GetColorAtFraction (double fraction) { if (double.IsNaN (fraction)) { return Spectrum.Last (); } if (fraction is < 0 or > 1) { throw new ArgumentOutOfRangeException (nameof (fraction), @"Fraction must be between 0 and 1."); } var index = (int)(fraction * (Spectrum.Count - 1)); return Spectrum [index]; } private List GenerateGradient (IEnumerable steps) { List gradient = new (); if (_stops.Count == 1) { for (var i = 0; i < steps.Sum (); i++) { gradient.Add (_stops [0]); } return gradient; } List stopsToUse = _stops.ToList (); List stepsToUse = _steps.ToList (); if (_loop) { stopsToUse.Add (_stops [0]); stepsToUse.Add (_steps.First ()); } var colorPairs = stopsToUse.Zip (stopsToUse.Skip (1), (start, end) => new { start, end }); List stepsList = stepsToUse; foreach ((var colorPair, int thesteps) in colorPairs.Zip (stepsList, (pair, step) => (pair, step))) { gradient.AddRange (InterpolateColors (colorPair.start, colorPair.end, thesteps)); } return gradient; } private static IEnumerable InterpolateColors (Color start, Color end, int steps) { for (var step = 0; step < steps; step++) { double fraction = (double)step / steps; var r = (int)(start.R + fraction * (end.R - start.R)); var g = (int)(start.G + fraction * (end.G - start.G)); var b = (int)(start.B + fraction * (end.B - start.B)); yield return new (r, g, b); } yield return end; // Ensure the last color is included } /// /// /// Creates a mapping starting at 0,0 and going to and /// (inclusively) using the supplied . /// /// /// Note that this method is inclusive i.e. passing 1/1 results in 4 mapped coordinates. /// /// /// /// /// /// public Dictionary BuildCoordinateColorMapping (int maxRow, int maxColumn, GradientDirection direction) { Dictionary gradientMapping = new (); switch (direction) { case GradientDirection.Vertical: for (var row = 0; row <= maxRow; row++) { double fraction = maxRow == 0 ? 1.0 : (double)row / maxRow; Color color = GetColorAtFraction (fraction); for (var col = 0; col <= maxColumn; col++) { gradientMapping [new (col, row)] = color; } } break; case GradientDirection.Horizontal: for (var col = 0; col <= maxColumn; col++) { double fraction = maxColumn == 0 ? 1.0 : (double)col / maxColumn; Color color = GetColorAtFraction (fraction); for (var row = 0; row <= maxRow; row++) { gradientMapping [new (col, row)] = color; } } break; case GradientDirection.Radial: for (var row = 0; row <= maxRow; row++) { for (var col = 0; col <= maxColumn; col++) { double distanceFromCenter = FindNormalizedDistanceFromCenter (maxRow, maxColumn, new (col, row)); Color color = GetColorAtFraction (distanceFromCenter); gradientMapping [new (col, row)] = color; } } break; case GradientDirection.Diagonal: for (var row = 0; row <= maxRow; row++) { for (var col = 0; col <= maxColumn; col++) { double fraction = ((double)row * 2 + col) / (maxRow * 2 + maxColumn); Color color = GetColorAtFraction (fraction); gradientMapping [new (col, row)] = color; } } break; } return gradientMapping; } private static double FindNormalizedDistanceFromCenter (int maxRow, int maxColumn, Point coord) { double centerX = maxColumn / 2.0; double centerY = maxRow / 2.0; double dx = coord.X - centerX; double dy = coord.Y - centerY; double distance = Math.Sqrt (dx * dx + dy * dy); double maxDistance = Math.Sqrt (centerX * centerX + centerY * centerY); return distance / maxDistance; } }