public static ConfusionLine Get(ColorDistanceType type) { switch (type) { case ColorDistanceType.Protanope: return(new ConfusionLine() { X = 0.7465, Y = 0.2535, M = 1.273463, Yint = -0.073894 }); case ColorDistanceType.Deuteranope: return(new ConfusionLine() { X = 1.4, Y = -0.4, M = 0.968437, Yint = 0.003331 }); case ColorDistanceType.Tritanope: return(new ConfusionLine() { X = 0.1748, Y = 0.0, M = 0.062921, Yint = 0.292119 }); default: return(new ConfusionLine()); } }
private double DistanceColorBlind(LabColor lab1, LabColor lab2, ColorDistanceType type) { var lab1Cb = Simulate(lab1, type); var lab2Cb = Simulate(lab2, type); return CmcDistance(lab1Cb, lab2Cb); }
private double GetColorDistance( LabColor lab1, LabColor lab2, ColorDistanceType type = ColorDistanceType.Default) { switch (type) { case ColorDistanceType.Default: case ColorDistanceType.Euclidian: return EuclidianDistance(lab1, lab2); case ColorDistanceType.CMC: return CmcDistance(lab1, lab2); case ColorDistanceType.Compromise: return CompromiseDistance(lab1, lab2); default: return DistanceColorBlind(lab1, lab2, type); } }
// WARNING: may return [NaN, NaN, NaN] private LabColor Simulate(LabColor lab, ColorDistanceType type, int amount = 1) { // Cache var key = string.Format("{0}-{1}{2}", lab, type, amount); if (this.simulateCache.ContainsKey(key)) { return this.simulateCache[key]; } // Get data from type var confuse = ConfusionLine.Get(type); // Code adapted from http://galacticmilk.com/labs/Color-Vision/Javascript/Color.Vision.Simulate.js var color = lab.ToRgb(); var sr = color.R; var sg = color.G; var sb = color.B; double dr = sr; // destination color double dg = sg; double db = sb; // Convert source color into XYZ color space var pow_r = Math.Pow(sr, 2.2); var pow_g = Math.Pow(sg, 2.2); var pow_b = Math.Pow(sb, 2.2); var X = pow_r * 0.412424 + pow_g * 0.357579 + pow_b * 0.180464; // RGB->XYZ (sRGB:D65) var Y = pow_r * 0.212656 + pow_g * 0.715158 + pow_b * 0.0721856; var Z = pow_r * 0.0193324 + pow_g * 0.119193 + pow_b * 0.950444; // Convert XYZ into xyY Chromacity Coordinates (xy) and Luminance (Y) var chroma_x = X / (X + Y + Z); var chroma_y = Y / (X + Y + Z); // Generate the "Confusion Line" between the source color and the Confusion Point var m = (chroma_y - confuse.Y) / (chroma_x - confuse.X); // slope of Confusion Line var yint = chroma_y - chroma_x * m; // y-intercept of confusion line (x-intercept = 0.0) // How far the xy coords deviate from the simulation var deviate_x = (confuse.Yint - yint) / (m - confuse.M); var deviate_y = (m * deviate_x) + yint; // Compute the simulated color's XYZ coords X = deviate_x * Y / deviate_y; Z = (1.0 - (deviate_x + deviate_y)) * Y / deviate_y; // Neutral grey calculated from luminance (in D65) var neutral_X = 0.312713 * Y / 0.329016; var neutral_Z = 0.358271 * Y / 0.329016; // Difference between simulated color and neutral grey var diff_X = neutral_X - X; var diff_Z = neutral_Z - Z; var diff_r = diff_X * 3.24071 + diff_Z * -0.498571; // XYZ->RGB (sRGB:D65) var diff_g = diff_X * -0.969258 + diff_Z * 0.0415557; var diff_b = diff_X * 0.0556352 + diff_Z * 1.05707; // Convert to RGB color space dr = X * 3.24071 + Y * -1.53726 + Z * -0.498571; // XYZ->RGB (sRGB:D65) dg = X * -0.969258 + Y * 1.87599 + Z * 0.0415557; db = X * 0.0556352 + Y * -0.203996 + Z * 1.05707; // Compensate simulated color towards a neutral fit in RGB space var fit_r = ((dr < 0.0 ? 0.0 : 1.0) - dr) / diff_r; var fit_g = ((dg < 0.0 ? 0.0 : 1.0) - dg) / diff_g; var fit_b = ((db < 0.0 ? 0.0 : 1.0) - db) / diff_b; var adjust = Math.Max( // highest value Math.Max( (fit_r > 1.0 || fit_r < 0.0) ? 0.0 : fit_r, (fit_g > 1.0 || fit_g < 0.0) ? 0.0 : fit_g), (fit_b > 1.0 || fit_b < 0.0) ? 0.0 : fit_b ); // Shift proportional to the greatest shift dr = dr + (adjust * diff_r); dg = dg + (adjust * diff_g); db = db + (adjust * diff_b); // Apply gamma correction dr = Math.Pow(dr, 1.0 / 2.2); dg = Math.Pow(dg, 1.0 / 2.2); db = Math.Pow(db, 1.0 / 2.2); // Anomylize colors dr = sr * (1.0 - amount) + dr * amount; dg = sg * (1.0 - amount) + dg * amount; db = sb * (1.0 - amount) + db * amount; var dcolor = Color.FromArgb( (int)Math.Round(dr), (int)Math.Round(dg), (int)Math.Round(db)); var result = dcolor.ToLab(); this.simulateCache[key] = result; return result; }
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(); }