public Color[] GenerateKMean(int colorsCount, PaletteOptions options = null, ColorDistanceType distanceType = ColorDistanceType.Default) { options = options ?? PaletteOptions.GetDefault(); var kMeans = new List<LabColor>(); for (var i = 0; i < colorsCount; i++) { var lab = LabColor.GetRandom(); while (!lab.IsValidRgb()) { lab = LabColor.GetRandom(); } kMeans.Add(lab); } var colorSamples = GenerateColorSamples(options); // Steps var steps = options.Quality; var samplesCount = colorSamples.Count; var samplesClosest = new int?[samplesCount]; while (steps-- > 0) { // kMeans -> Samples Closest for (var i = 0; i < samplesCount; i++) { var lab = colorSamples[i]; var minDistance = double.MaxValue; for (var j = 0; j < kMeans.Count; j++) { var kMean = kMeans[j]; var distance = GetColorDistance(lab, kMean, distanceType); if (distance < minDistance) { minDistance = distance; samplesClosest[i] = j; } } } // Samples -> kMeans var freeColorSamples = colorSamples .Select((lab) => (LabColor)lab.Clone()) // todo do I really need clone each item? .ToList(); for (var j = 0; j < kMeans.Count; j++) { var count = 0; var candidateKMean = new LabColor(); for (var i = 0; i < colorSamples.Count; i++) { if (samplesClosest[i] == j) { count++; var color = colorSamples[i]; candidateKMean.L += color.L; candidateKMean.A += color.A; candidateKMean.B += color.B; } } if (count != 0) { candidateKMean.L /= count; candidateKMean.A /= count; candidateKMean.B /= count; } if (count != 0 && candidateKMean.IsValidRgb() && options.ValidateColor(candidateKMean)) { kMeans[j] = candidateKMean; } else { // The candidate kMean is out of the boundaries of the color space, or unfound. if (freeColorSamples.Count > 0) { // We just search for the closest FREE color of the candidate kMean var minDistance = double.MaxValue; var closest = -1; for (var i = 0; i < freeColorSamples.Count; i++) { var distance = GetColorDistance(freeColorSamples[i], candidateKMean, distanceType); if (distance < minDistance) { minDistance = distance; closest = i; } } kMeans[j] = colorSamples[closest]; } else { // Then we just search for the closest color of the candidate kMean var minDistance = double.MaxValue; var closest = -1; for (var i = 0; i < colorSamples.Count; i++) { var distance = GetColorDistance(colorSamples[i], candidateKMean, distanceType); if (distance < minDistance) { minDistance = distance; closest = i; } } kMeans[j] = colorSamples[closest]; } } var baseColor = kMeans[j]; freeColorSamples = freeColorSamples .Where((lab) => !lab.Equals(baseColor)) .ToList(); } } return kMeans.Select((lab) => lab.ToRgb()).ToArray(); }
public Color[] GenerateForce(int colorsCount, PaletteOptions options = null, ColorDistanceType distanceType = ColorDistanceType.Default) { var random = new Random(); var colors = new LabColor[colorsCount]; options = options ?? PaletteOptions.GetDefault(); // Init for (var i = 0; i < colorsCount; i++) { // Find a valid Lab color var color = LabColor.GetRandom(); while (color.IsValidRgb() || options.ValidateColor(color)) { color = LabColor.GetRandom(); } colors[i] = color; } // Force vector: repulsion var repulsion = 100; var speed = 100; var steps = options.Quality * 20; var vectors = new LabVector[colorsCount]; while (steps-- > 0) { // Init for (var i = 0; i < colors.Length; i++) { vectors[i] = new LabVector(); } // Compute Force for (var i = 0; i < colors.Length; i++) { var colorA = colors[i]; for (var j = 0; j < i; j++) { var colorB = colors[j]; // repulsion force var dl = colorA.L - colorB.L; var da = colorA.A - colorB.A; var db = colorA.B - colorB.B; var d = GetColorDistance(colorA, colorB, distanceType); if (d > 0) { var force = repulsion / Math.Pow(d, 2); vectors[i].dL += dl * force / d; vectors[i].dA += da * force / d; vectors[i].dB += db * force / d; vectors[j].dL -= dl * force / d; vectors[j].dA -= da * force / d; vectors[j].dB -= db * force / d; } else { // Jitter vectors[j].dL += 2 - 4 * random.NextDouble(); vectors[j].dA += 2 - 4 * random.NextDouble(); vectors[j].dB += 2 - 4 * random.NextDouble(); } } } // Apply Force for (var i = 0; i < colors.Length; i++) { var color = colors[i]; var displacement = speed * vectors[i].Magnitude; ; if (displacement > 0) { var ratio = speed * Math.Min(0.1, displacement) / displacement; var l = color.L + vectors[i].dL * ratio; var a = color.A + vectors[i].dA * ratio; var b = color.B + vectors[i].dB * ratio; var candidateLab = new LabColor(l, a, b); if (candidateLab.IsValidRgb() && options.ValidateColor(candidateLab)) { colors[i] = candidateLab; } } } } return colors.Select((lab) => lab.ToRgb()).ToArray(); }