Search code examples
c#wpf

How can I generate a "random constant colour" for a given string?


how can I generate a "random constant colour" for a given string at runtime?

So a given string value will always have the same colour but different strings will have different colours.

Like how gmail assigns colours to the sender names.

Thanks

Responses to comments:

  1. Thinking to generate the colour from a hashcode.
  2. The colours won't be stored but generated from a hashcode.

Solution

  • Similar to what the other answers are suggesting (hash the string in some form then use that hash to pick the color), but instead of using the hash to directly calculate the color use it as the index to an array of "Acceptable" colors.

    class ColorPicker
    {
        public ColorPicker(int colorCount)
        {
            //The ".Skip(2)" makes it skip pure white and pure black.
            // If you want those two, take out the +2 and the skip.
            _colors = ColorGenerator.Generate(colorCount + 2).Skip(2).ToArray();
        }
        private readonly Color[] _colors;
        
        public Color StringToColor(string message)
        {
            int someHash = CalculateHashOfStringSomehow(message);
            return _colors[someHash % _colors.Length];
        }
    
        private int CalculateHashOfStringSomehow(string message)
        {
            //TODO: I would not use "message.GetHashCode()" as you are not
            // guaranteed the same value between runs of the program.
            // Make up your own algorithom or use a existing one that has a fixed 
            // output for a given input, like MD5.
        }
    }
    

    This prevents issues like getting a white color when you plan on showing the text with a white background and other similar problems.

    To populate your Color[] see this answer for the ColorGenerator class or just make your own pre-defined list of colors that look good on whatever background they will be used on.


    Appendix:
    In case the link goes down, here is a copy of the ColorGenerator class

    public static class ColorGenerator
    {
    
        // RYB color space
        private static class RYB
        {
            private static readonly double[] White = { 1, 1, 1 };
            private static readonly double[] Red = { 1, 0, 0 };
            private static readonly double[] Yellow = { 1, 1, 0 };
            private static readonly double[] Blue = { 0.163, 0.373, 0.6 };
            private static readonly double[] Violet = { 0.5, 0, 0.5 };
            private static readonly double[] Green = { 0, 0.66, 0.2 };
            private static readonly double[] Orange = { 1, 0.5, 0 };
            private static readonly double[] Black = { 0.2, 0.094, 0.0 };
    
            public static double[] ToRgb(double r, double y, double b)
            {
                var rgb = new double[3];
                for (int i = 0; i < 3; i++)
                {
                    rgb[i] = White[i]  * (1.0 - r) * (1.0 - b) * (1.0 - y) +
                             Red[i]    * r         * (1.0 - b) * (1.0 - y) +
                             Blue[i]   * (1.0 - r) * b         * (1.0 - y) +
                             Violet[i] * r         * b         * (1.0 - y) +
                             Yellow[i] * (1.0 - r) * (1.0 - b) *        y +
                             Orange[i] * r         * (1.0 - b) *        y +
                             Green[i]  * (1.0 - r) * b         *        y +
                             Black[i]  * r         * b         *        y;
                }
    
                return rgb;
            }
        }
    
        private class Points : IEnumerable<double[]>
        {
            private readonly int pointsCount;
            private double[] picked;
            private int pickedCount;
    
            private readonly List<double[]> points = new List<double[]>();
    
            public Points(int count)
            {
                pointsCount = count;
            }
    
            private void Generate()
            {
                points.Clear();
                var numBase = (int)Math.Ceiling(Math.Pow(pointsCount, 1.0 / 3.0));
                var ceil = (int)Math.Pow(numBase, 3.0);
                for (int i = 0; i < ceil; i++)
                {
                    points.Add(new[]
                    {
                        Math.Floor(i/(double)(numBase*numBase))/ (numBase - 1.0),
                        Math.Floor((i/(double)numBase) % numBase)/ (numBase - 1.0),
                        Math.Floor((double)(i % numBase))/ (numBase - 1.0),
                    });
                }
            }
    
            private double Distance(double[] p1)
            {
                double distance = 0;
                for (int i = 0; i < 3; i++)
                {
                    distance += Math.Pow(p1[i] - picked[i], 2.0);
                }
    
                return distance;
            }
    
            private double[] Pick()
            {
                if (picked == null)
                {
                    picked = points[0];
                    points.RemoveAt(0);
                    pickedCount = 1;
                    return picked;
                }
    
                var d1 = Distance(points[0]);
                int i1 = 0, i2 = 0;
                foreach (var point in points)
                {
                    var d2 = Distance(point);
                    if (d1 < d2)
                    {
                        i1 = i2;
                        d1 = d2;
                    }
    
                    i2 += 1;
                }
    
                var pick = points[i1];
                points.RemoveAt(i1);
    
                for (int i = 0; i < 3; i++)
                {
                    picked[i] = (pickedCount * picked[i] + pick[i]) / (pickedCount + 1.0);
                }
    
                pickedCount += 1;
                return pick;
            }
    
            public IEnumerator<double[]> GetEnumerator()
            {
                Generate();
                for (int i = 0; i < pointsCount; i++)
                {
                    yield return Pick();
                }
            }
    
            IEnumerator IEnumerable.GetEnumerator()
            {
                return GetEnumerator();
            }
        }
    
        public static IEnumerable<Color> Generate(int numOfColors)
        {
            var points = new Points(numOfColors);
    
            foreach (var point in points)
            {
                var rgb = RYB.ToRgb(point[0], point[1], point[2]);
                yield return Color.FromArgb(
                    (int)Math.Floor(255 * rgb[0]),
                    (int)Math.Floor(255 * rgb[1]),
                    (int)Math.Floor(255 * rgb[2]));
            }
        }
    }
    

    Wayback link to original paper: https://web.archive.org/web/20150318233155/http://threekings.tk:80/mirror/ryb_TR.pdf