PopularityPaletteWithThreshold.cs 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  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. public List<Color> BuildPalette (List<Color> colors, int maxColors)
  24. {
  25. if (colors == null || colors.Count == 0 || maxColors <= 0)
  26. {
  27. return new ();
  28. }
  29. // Step 1: Build the histogram of colors (count occurrences)
  30. Dictionary<Color, int> colorHistogram = new ();
  31. foreach (Color color in colors)
  32. {
  33. if (colorHistogram.ContainsKey (color))
  34. {
  35. colorHistogram [color]++;
  36. }
  37. else
  38. {
  39. colorHistogram [color] = 1;
  40. }
  41. }
  42. // If we already have fewer or equal colors than the limit, no need to merge
  43. if (colorHistogram.Count <= maxColors)
  44. {
  45. return colorHistogram.Keys.ToList ();
  46. }
  47. // Step 2: Merge similar colors using the color distance threshold
  48. Dictionary<Color, int> mergedHistogram = MergeSimilarColors (colorHistogram, maxColors);
  49. // Step 3: Sort the histogram by frequency (most frequent colors first)
  50. List<Color> sortedColors = mergedHistogram.OrderByDescending (c => c.Value)
  51. .Take (maxColors) // Keep only the top `maxColors` colors
  52. .Select (c => c.Key)
  53. .ToList ();
  54. return sortedColors;
  55. }
  56. /// <summary>
  57. /// Merge colors in the histogram if they are within the threshold distance
  58. /// </summary>
  59. /// <param name="colorHistogram"></param>
  60. /// <param name="maxColors"></param>
  61. /// <returns></returns>
  62. private Dictionary<Color, int> MergeSimilarColors (Dictionary<Color, int> colorHistogram, int maxColors)
  63. {
  64. Dictionary<Color, int> mergedHistogram = new ();
  65. foreach (KeyValuePair<Color, int> entry in colorHistogram)
  66. {
  67. Color currentColor = entry.Key;
  68. var merged = false;
  69. // Try to merge the current color with an existing entry in the merged histogram
  70. foreach (Color mergedEntry in mergedHistogram.Keys.ToList ())
  71. {
  72. double distance = _colorDistance.CalculateDistance (currentColor, mergedEntry);
  73. // If the colors are similar enough (within the threshold), merge them
  74. if (distance <= _mergeThreshold)
  75. {
  76. mergedHistogram [mergedEntry] += entry.Value; // Add the color frequency to the existing one
  77. merged = true;
  78. break;
  79. }
  80. }
  81. // If no similar color is found, add the current color as a new entry
  82. if (!merged)
  83. {
  84. mergedHistogram [currentColor] = entry.Value;
  85. }
  86. // Early exit if we've reduced the colors to the maxColors limit
  87. if (mergedHistogram.Count >= maxColors)
  88. {
  89. return mergedHistogram;
  90. }
  91. }
  92. return mergedHistogram;
  93. }
  94. }