PopularityPaletteWithThreshold.cs 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. using Terminal.Gui;
  2. using Color = Terminal.Gui.Color;
  3. /// <summary>
  4. /// Simple fast palette building algorithm which uses the frequency that a color is seen
  5. /// to determine whether it will appear in the final palette. Includes a threshold where
  6. /// by colors will be considered 'the same'. This reduces the chance of under represented
  7. /// colors being missed completely.
  8. /// </summary>
  9. public class PopularityPaletteWithThreshold : IPaletteBuilder
  10. {
  11. private readonly IColorDistance _colorDistance;
  12. private readonly double _mergeThreshold;
  13. /// <summary>
  14. /// Creates a new instance with the given color grouping parameters.
  15. /// </summary>
  16. /// <param name="colorDistance">Determines which different colors can be considered the same.</param>
  17. /// <param name="mergeThreshold">Threshold for merging two colors together.</param>
  18. public PopularityPaletteWithThreshold (IColorDistance colorDistance, double mergeThreshold)
  19. {
  20. _colorDistance = colorDistance;
  21. _mergeThreshold = mergeThreshold; // Set the threshold for merging similar colors
  22. }
  23. /// <inheritdoc/>
  24. public List<Color> BuildPalette (List<Color> colors, int maxColors)
  25. {
  26. if (colors == null || colors.Count == 0 || maxColors <= 0)
  27. {
  28. return new ();
  29. }
  30. // Step 1: Build the histogram of colors (count occurrences)
  31. Dictionary<Color, int> colorHistogram = new ();
  32. foreach (Color color in colors)
  33. {
  34. if (colorHistogram.ContainsKey (color))
  35. {
  36. colorHistogram [color]++;
  37. }
  38. else
  39. {
  40. colorHistogram [color] = 1;
  41. }
  42. }
  43. // If we already have fewer or equal colors than the limit, no need to merge
  44. if (colorHistogram.Count <= maxColors)
  45. {
  46. return colorHistogram.Keys.ToList ();
  47. }
  48. // Step 2: Merge similar colors using the color distance threshold
  49. Dictionary<Color, int> mergedHistogram = MergeSimilarColors (colorHistogram, maxColors);
  50. // Step 3: Sort the histogram by frequency (most frequent colors first)
  51. List<Color> sortedColors = mergedHistogram.OrderByDescending (c => c.Value)
  52. .Take (maxColors) // Keep only the top `maxColors` colors
  53. .Select (c => c.Key)
  54. .ToList ();
  55. return sortedColors;
  56. }
  57. /// <summary>
  58. /// Merge colors in the histogram if they are within the threshold distance
  59. /// </summary>
  60. /// <param name="colorHistogram"></param>
  61. /// <param name="maxColors"></param>
  62. /// <returns></returns>
  63. private Dictionary<Color, int> MergeSimilarColors (Dictionary<Color, int> colorHistogram, int maxColors)
  64. {
  65. Dictionary<Color, int> mergedHistogram = new ();
  66. foreach (KeyValuePair<Color, int> entry in colorHistogram)
  67. {
  68. Color currentColor = entry.Key;
  69. var merged = false;
  70. // Try to merge the current color with an existing entry in the merged histogram
  71. foreach (Color mergedEntry in mergedHistogram.Keys.ToList ())
  72. {
  73. double distance = _colorDistance.CalculateDistance (currentColor, mergedEntry);
  74. // If the colors are similar enough (within the threshold), merge them
  75. if (distance <= _mergeThreshold)
  76. {
  77. mergedHistogram [mergedEntry] += entry.Value; // Add the color frequency to the existing one
  78. merged = true;
  79. break;
  80. }
  81. }
  82. // If no similar color is found, add the current color as a new entry
  83. if (!merged)
  84. {
  85. mergedHistogram [currentColor] = entry.Value;
  86. }
  87. // Early exit if we've reduced the colors to the maxColors limit
  88. if (mergedHistogram.Count >= maxColors)
  89. {
  90. return mergedHistogram;
  91. }
  92. }
  93. return mergedHistogram;
  94. }
  95. }