ColorQuantizer.cs 3.2 KB

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