예제 #1
0
        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());
        }
예제 #2
0
        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());
        }
예제 #3
0
        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)
                );
        }
예제 #4
0
        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;
        }
예제 #5
0
        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();
        }
예제 #6
0
        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();
        }