private static List <LabColor> GenerateSamples(PaletteOptions options) { var samples = new List <LabColor>(); var rangeDivider = Math.Pow(options.Samples, 1.0 / 3.0) * 1.001; var hStep = (options.HueMax - options.HueMin) / rangeDivider; var cStep = (options.ChromaMax - options.ChromaMin) / rangeDivider; var lStep = (options.LightMax - options.LightMin) / rangeDivider; for (double h = options.HueMin; h <= options.HueMax; h += hStep) { for (double c = options.ChromaMin; c <= options.ChromaMax; c += cStep) { for (double l = options.LightMin; l <= options.LightMax; l += lStep) { var color = new HclColor(h, c, l).ToLab(); if (CheckColor(color, options)) { samples.Add(color); } } } } return(samples.Distinct().ToList()); }
public static List <Color> GetPalette(int colorCount, PaletteOptions options = null) { if (colorCount < 1) { return(new List <Color>()); } if (options == null) { options = new PaletteOptions(); } if (options.Samples < colorCount * 5) { options.Samples = colorCount * 5; } ; var samples = GenerateSamples(options); if (samples.Count < colorCount) { throw new Exception("Not enough samples to generate palette, increase sample count."); } var sliceSize = samples.Count / colorCount; var colors = new List <LabColor>(); for (var i = 0; i < samples.Count; i += sliceSize) { colors.Add(samples[i]); if (colors.Count >= colorCount) { break; } } for (var step = 1; step <= options.Quality; step++) { var zones = GenerateZones(samples, colors); var lastColors = colors.Select(x => x).ToList(); for (var i = 0; i < zones.Count; i++) { var zone = zones[i]; var total = zone.Count; var lAvg = zone.Sum(x => x.L) / total; var aAvg = zone.Sum(x => x.A) / total; var bAvg = zone.Sum(x => x.B) / total; colors[i] = new LabColor(lAvg, aAvg, bAvg); } if (!AreEqualPalettes(lastColors, colors)) { break; } } colors = SortByContrast(colors); return(colors.Select((lab) => lab.ToRgb()).ToList()); }
private static bool CheckColor(LabColor color, PaletteOptions options) { var rgb = color.ToRgb(); var hcl = color.ToHcl(); var compLab = rgb.ToLab(); var labTolerance = 7; return( hcl.H >= options.HueMin && hcl.H <= options.HueMax && hcl.C >= options.ChromaMin && hcl.C <= options.ChromaMax && hcl.L >= options.LightMin && hcl.L <= options.LightMax && compLab.L >= (color.L - labTolerance) && compLab.L <= (color.L + labTolerance) && compLab.A >= (color.A - labTolerance) && compLab.A <= (color.A + labTolerance) && compLab.B >= (color.B - labTolerance) && compLab.B <= (color.B + labTolerance) ); }
private List<LabColor> GenerateColorSamples(PaletteOptions options) { var samples = new List<LabColor>(); var rangeDivider = Math.Pow(options.Samples, 1.0 / 3.0) * 1.001; var hStep = (options.HueMax - options.HueMin) / rangeDivider; var cStep = (options.ChromaMax - options.ChromaMin) / rangeDivider; var lStep = (options.LightMax - options.LightMin) / rangeDivider; for (double h = options.HueMin; h <= options.HueMax; h += hStep) { for (double c = options.ChromaMin; c <= options.ChromaMax; c += cStep) { for (double l = options.LightMin; l <= options.LightMax; l += lStep) { var color = new HclColor(h, c, l).ToLab(); if (color.IsValidRgb() && options.ValidateColor(color)) { samples.Add(color); } } } } return samples; }
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(); }