using System.Collections.Concurrent; namespace Terminal.Gui; /// /// Translates colors in an image into a Palette of up to colors (typically 256). /// public class ColorQuantizer { /// /// Gets the current colors in the palette based on the last call to /// . /// public IReadOnlyCollection Palette { get; private set; } = new List (); /// /// Gets or sets the maximum number of colors to put into the . /// Defaults to 256 (the maximum for sixel images). /// public int MaxColors { get; set; } = 256; /// /// Gets or sets the algorithm used to map novel colors into existing /// palette colors (closest match). Defaults to /// public IColorDistance DistanceAlgorithm { get; set; } = new EuclideanColorDistance (); /// /// Gets or sets the algorithm used to build the . /// public IPaletteBuilder PaletteBuildingAlgorithm { get; set; } = new PopularityPaletteWithThreshold (new EuclideanColorDistance (), 8); private readonly ConcurrentDictionary _nearestColorCache = new (); /// /// Builds a of colors that most represent the colors used in image. /// This is based on the currently configured . /// /// public void BuildPalette (Color [,] pixels) { List allColors = new (); int width = pixels.GetLength (0); int height = pixels.GetLength (1); for (var x = 0; x < width; x++) { for (var y = 0; y < height; y++) { allColors.Add (pixels [x, y]); } } _nearestColorCache.Clear (); Palette = PaletteBuildingAlgorithm.BuildPalette (allColors, MaxColors); } public int GetNearestColor (Color toTranslate) { if (_nearestColorCache.TryGetValue (toTranslate, out int cachedAnswer)) { return cachedAnswer; } // Simple nearest color matching based on DistanceAlgorithm var minDistance = double.MaxValue; var nearestIndex = 0; for (var index = 0; index < Palette.Count; index++) { Color color = Palette.ElementAt (index); double distance = DistanceAlgorithm.CalculateDistance (color, toTranslate); if (distance < minDistance) { minDistance = distance; nearestIndex = index; } } _nearestColorCache.TryAdd (toTranslate, nearestIndex); return nearestIndex; } }