ColorQuantizer.cs 3.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. #nullable disable
  2. using System.Collections.Concurrent;
  3. namespace Terminal.Gui.Drawing;
  4. /// <summary>
  5. /// Translates colors in an image into a Palette of up to <see cref="MaxColors"/> colors (typically 256).
  6. /// </summary>
  7. public class ColorQuantizer
  8. {
  9. /// <summary>
  10. /// Gets the current colors in the palette based on the last call to
  11. /// <see cref="BuildPalette"/>.
  12. /// </summary>
  13. public IReadOnlyCollection<Color> Palette { get; private set; } = new List<Color> ();
  14. /// <summary>
  15. /// Gets or sets the maximum number of colors to put into the <see cref="Palette"/>.
  16. /// Defaults to 256 (the maximum for sixel images).
  17. /// </summary>
  18. public int MaxColors { get; set; } = 256;
  19. /// <summary>
  20. /// Gets or sets the algorithm used to map novel colors into existing
  21. /// palette colors (closest match). Defaults to <see cref="EuclideanColorDistance"/>
  22. /// </summary>
  23. public IColorDistance DistanceAlgorithm { get; set; } = new EuclideanColorDistance ();
  24. /// <summary>
  25. /// Gets or sets the algorithm used to build the <see cref="Palette"/>.
  26. /// </summary>
  27. public IPaletteBuilder PaletteBuildingAlgorithm { get; set; } = new PopularityPaletteWithThreshold (new EuclideanColorDistance (), 8);
  28. private readonly ConcurrentDictionary<Color, int> _nearestColorCache = new ();
  29. /// <summary>
  30. /// Builds a <see cref="Palette"/> of colors that most represent the colors used in <paramref name="pixels"/> image.
  31. /// This is based on the currently configured <see cref="PaletteBuildingAlgorithm"/>.
  32. /// </summary>
  33. /// <param name="pixels"></param>
  34. public void BuildPalette (Color [,] pixels)
  35. {
  36. List<Color> allColors = new ();
  37. int width = pixels.GetLength (0);
  38. int height = pixels.GetLength (1);
  39. for (var x = 0; x < width; x++)
  40. {
  41. for (var y = 0; y < height; y++)
  42. {
  43. allColors.Add (pixels [x, y]);
  44. }
  45. }
  46. _nearestColorCache.Clear ();
  47. Palette = PaletteBuildingAlgorithm.BuildPalette (allColors, MaxColors);
  48. }
  49. /// <summary>
  50. /// Returns the closest color in <see cref="Palette"/> that matches <paramref name="toTranslate"/>
  51. /// based on the color comparison algorithm defined by <see cref="DistanceAlgorithm"/>
  52. /// </summary>
  53. /// <param name="toTranslate"></param>
  54. /// <returns></returns>
  55. public int GetNearestColor (Color toTranslate)
  56. {
  57. if (_nearestColorCache.TryGetValue (toTranslate, out int cachedAnswer))
  58. {
  59. return cachedAnswer;
  60. }
  61. // Simple nearest color matching based on DistanceAlgorithm
  62. var minDistance = double.MaxValue;
  63. var nearestIndex = 0;
  64. for (var index = 0; index < Palette.Count; index++)
  65. {
  66. Color color = Palette.ElementAt (index);
  67. double distance = DistanceAlgorithm.CalculateDistance (color, toTranslate);
  68. if (distance < minDistance)
  69. {
  70. minDistance = distance;
  71. nearestIndex = index;
  72. }
  73. }
  74. _nearestColorCache.TryAdd (toTranslate, nearestIndex);
  75. return nearestIndex;
  76. }
  77. }