Gradient.cs 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. // This code is a C# port from python library Terminal Text Effects https://github.com/ChrisBuilds/terminaltexteffects/
  2. namespace Terminal.Gui;
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Linq;
  6. /// <summary>
  7. /// Describes the pattern that a <see cref="Gradient"/> results in e.g. <see cref="Vertical"/>, <see cref="Horizontal"/> etc
  8. /// </summary>
  9. public enum GradientDirection
  10. {
  11. /// <summary>
  12. /// Color varies along Y axis but is constant on X axis.
  13. /// </summary>
  14. Vertical,
  15. /// <summary>
  16. /// Color varies along X axis but is constant on Y axis.
  17. /// </summary>
  18. Horizontal,
  19. /// <summary>
  20. /// Color varies by distance from center (i.e. in circular ripples)
  21. /// </summary>
  22. Radial,
  23. /// <summary>
  24. /// Color varies by X and Y axis (i.e. a slanted gradient)
  25. /// </summary>
  26. Diagonal
  27. }
  28. /// <summary>
  29. /// Describes
  30. /// </summary>
  31. public class Gradient
  32. {
  33. public List<Color> Spectrum { get; private set; }
  34. private readonly bool _loop;
  35. private readonly List<Color> _stops;
  36. private readonly List<int> _steps;
  37. public Gradient (IEnumerable<Color> stops, IEnumerable<int> steps, bool loop = false)
  38. {
  39. _stops = stops.ToList ();
  40. if (_stops.Count < 1)
  41. throw new ArgumentException ("At least one color stop must be provided.");
  42. _steps = steps.ToList ();
  43. if (_steps.Any (step => step < 1))
  44. throw new ArgumentException ("Steps must be greater than 0.");
  45. _loop = loop;
  46. Spectrum = GenerateGradient (_steps);
  47. }
  48. /// <summary>
  49. /// Returns the color to use at the given part of the spectrum
  50. /// </summary>
  51. /// <param name="fraction">Proportion of the way through the spectrum, must be between
  52. /// 0 and 1 (inclusive). Returns the last color if <paramref name="fraction"/> is
  53. /// <see cref="double.NaN"/>.</param>
  54. /// <returns></returns>
  55. /// <exception cref="ArgumentOutOfRangeException"></exception>
  56. public Color GetColorAtFraction (double fraction)
  57. {
  58. if (double.IsNaN (fraction))
  59. {
  60. return Spectrum.Last ();
  61. }
  62. if (fraction < 0 || fraction > 1)
  63. {
  64. throw new ArgumentOutOfRangeException (nameof (fraction), "Fraction must be between 0 and 1.");
  65. }
  66. int index = (int)(fraction * (Spectrum.Count - 1));
  67. return Spectrum [index];
  68. }
  69. private List<Color> GenerateGradient (IEnumerable<int> steps)
  70. {
  71. List<Color> gradient = new List<Color> ();
  72. if (_stops.Count == 1)
  73. {
  74. for (int i = 0; i < steps.Sum (); i++)
  75. {
  76. gradient.Add (_stops [0]);
  77. }
  78. return gradient;
  79. }
  80. if (_loop)
  81. {
  82. _stops.Add (_stops [0]);
  83. }
  84. var colorPairs = _stops.Zip (_stops.Skip (1), (start, end) => new { start, end });
  85. var stepsList = _steps.ToList ();
  86. foreach (var (colorPair, thesteps) in colorPairs.Zip (stepsList, (pair, step) => (pair, step)))
  87. {
  88. gradient.AddRange (InterpolateColors (colorPair.start, colorPair.end, thesteps));
  89. }
  90. return gradient;
  91. }
  92. private IEnumerable<Color> InterpolateColors (Color start, Color end, int steps)
  93. {
  94. for (int step = 0; step <= steps; step++)
  95. {
  96. double fraction = (double)step / steps;
  97. int r = (int)(start.R + fraction * (end.R - start.R));
  98. int g = (int)(start.G + fraction * (end.G - start.G));
  99. int b = (int)(start.B + fraction * (end.B - start.B));
  100. yield return new Color (r, g, b);
  101. }
  102. }
  103. /// <summary>
  104. /// <para>
  105. /// Creates a mapping starting at 0,0 and going to <paramref name="maxRow"/> and <paramref name="maxColumn"/>
  106. /// (inclusively) using the supplied <paramref name="direction"/>.
  107. /// </para>
  108. /// <para>
  109. /// Note that this method is inclusive i.e. passing 1/1 results in 4 mapped coordinates.
  110. /// </para>
  111. /// </summary>
  112. /// <param name="maxRow"></param>
  113. /// <param name="maxColumn"></param>
  114. /// <param name="direction"></param>
  115. /// <returns></returns>
  116. public Dictionary<Point, Color> BuildCoordinateColorMapping (int maxRow, int maxColumn, GradientDirection direction)
  117. {
  118. var gradientMapping = new Dictionary<Point, Color> ();
  119. switch (direction)
  120. {
  121. case GradientDirection.Vertical:
  122. for (int row = 0; row <= maxRow; row++)
  123. {
  124. double fraction = maxRow == 0 ? 1.0 : (double)row / maxRow;
  125. Color color = GetColorAtFraction (fraction);
  126. for (int col = 0; col <= maxColumn; col++)
  127. {
  128. gradientMapping [new Point (col, row)] = color;
  129. }
  130. }
  131. break;
  132. case GradientDirection.Horizontal:
  133. for (int col = 0; col <= maxColumn; col++)
  134. {
  135. double fraction = maxColumn == 0 ? 1.0 : (double)col / maxColumn;
  136. Color color = GetColorAtFraction (fraction);
  137. for (int row = 0; row <= maxRow; row++)
  138. {
  139. gradientMapping [new Point (col, row)] = color;
  140. }
  141. }
  142. break;
  143. case GradientDirection.Radial:
  144. for (int row = 0; row <= maxRow; row++)
  145. {
  146. for (int col = 0; col <= maxColumn; col++)
  147. {
  148. double distanceFromCenter = FindNormalizedDistanceFromCenter (maxRow, maxColumn, new Point (col, row));
  149. Color color = GetColorAtFraction (distanceFromCenter);
  150. gradientMapping [new Point (col, row)] = color;
  151. }
  152. }
  153. break;
  154. case GradientDirection.Diagonal:
  155. for (int row = 0; row <= maxRow; row++)
  156. {
  157. for (int col = 0; col <= maxColumn; col++)
  158. {
  159. double fraction = ((double)row * 2 + col) / (maxRow * 2 + maxColumn);
  160. Color color = GetColorAtFraction (fraction);
  161. gradientMapping [new Point (col, row)] = color;
  162. }
  163. }
  164. break;
  165. }
  166. return gradientMapping;
  167. }
  168. private double FindNormalizedDistanceFromCenter (int maxRow, int maxColumn, Point coord)
  169. {
  170. double centerX = maxColumn / 2.0;
  171. double centerY = maxRow / 2.0;
  172. double dx = coord.X - centerX;
  173. double dy = coord.Y - centerY;
  174. double distance = Math.Sqrt (dx * dx + dy * dy);
  175. double maxDistance = Math.Sqrt (centerX * centerX + centerY * centerY);
  176. return distance / maxDistance;
  177. }
  178. }