Jens Kohl
Jens Kohl

Reputation: 5969

Hash string into RGB color

Is there a best practice on how to hash an arbitrary string into a RGB color value? Or to be more general: to 3 bytes.

You're asking: When will I ever need this? It doesn't matter to me, but imagine those tube graphs on any GitHub network page. There you can see something like this:

git branches

Where every colored line means a distinct git branch. The low tech approach to color these branches would be a CLUT (color lookup table). The more sophisticated version would be:

$branchColor = hashStringToColor(concat($username,$branchname));

Because you want a static color every time you see the branches representation. And for bonus points: How do you ensure an even color distribution of that hash function?

So the answer to my question boils down to the implementation of hashStringToColor().

Upvotes: 58

Views: 26910

Answers (8)

ZenCodr
ZenCodr

Reputation: 1175

If you are using python, you can take the last 3 bytes from a hash.digest object to get 3x8-bit values, which can be used as an RGB tuple:

import hashlib
data_to_hash = "My Custom String"
h = hashlib.new('sha256')
h.update(data_to_hash.encode())
rgb = tuple(h.digest()[:3])
print(rgb) # (238, 164, 104)

Upvotes: 0

assylias
assylias

Reputation: 328568

The hashcode returns an int that is calculated with a formula that is defined in the javadoc. You can then calculate the modulo of that int with 16,777,216 (2^24 = 3 bytes) to get an "RGB-compatible" number.

It is a deterministic calculation so the same word(s) will always have the same colour. The likelihood of hash collision (2 strings having the same colour) is small. Not sure about the colour distribution, but probably fairly random.

Upvotes: 2

Walter Stabosz
Walter Stabosz

Reputation: 7735

If you are using C# and want HSL colors

Here is a class I wrote based on the library written by Zeno Zeng and mentioned in this answer .

The HSLColor class was written by Rich Newman, it is defined here


    public class ColorHash
    {        
        
        // you can pass in a string hashing function of you choice
        public ColorHash(Func<string, int> hashFunction)
        {
            this.hashFunction = hashFunction;
        }

        // or use the default string.GetHashCode()
        public ColorHash()
        {
            this.hashFunction = (string s) => { return s.GetHashCode(); };
        }
        
        Func<string, int> hashFunction = null;

        static float[] defaultValues = new float[] { 0.35F, 0.5F, 0.65F };

        // HSLColor class is defined at https://richnewman.wordpress.com/about/code-listings-and-diagrams/hslcolor-class/
        public HSLColor HSL(string s) {
            return HSL(s, defaultValues, defaultValues);
        }

        // HSL function ported from https://github.com/zenozeng/color-hash/       
        public HSLColor HSL(string s, float[] saturationValues, float[] lightnessValues)
        {
            
            double hue;
            double saturation;
            double luminosity;

            int hash = Math.Abs(this.hashFunction(s));

            hue = hash % 359;

            hash = (int)Math.Ceiling((double)hash / 360);
            saturation = saturationValues[hash % saturationValues.Length];

            hash = (int)Math.Ceiling((double)hash / saturationValues.Length);
            luminosity = lightnessValues[hash % lightnessValues.Length];

            return new HSLColor(hue, saturation, luminosity);
        }
        
    }

Upvotes: 1

Terry Riegel
Terry Riegel

Reputation: 179

Interstingly enough a common representation of hashing functions like sha256 or md5 is as a hex string. Here is a fiddle that uses the 10 6 byte segemnts to create a coloful representaion of and sha256 hash. It uses the last 4 byte segment to determine the angle of the colors.

https://jsfiddle.net/ypx4mtnr/4/

async function sha256(message) ... see fiddle ...

   async function sha256(message) {
    // encode as UTF-8
    const msgBuffer = new TextEncoder().encode(message);                    

    // hash the message
    const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);

    // convert ArrayBuffer to Array
    const hashArray = Array.from(new Uint8Array(hashBuffer));

    // convert bytes to hex string                  
    const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
    return hashHex;
   }  
   function sha256tohtml(x,b){
var y=chunkSubstr(x,6);
return '<div style="'+
    'display: inline-block;'+
    'margin:2em;'+
    'width:128px;'+
    'height:128px;'+
    'border-radius: 512px;'+
    'background: conic-gradient('+
    '   from '+((360/65536)*parseInt(y[10],16))+'deg at 50% 50%,'+
    '   #'+y[0]+' '+(0+b)+'%,'+
    '   #'+y[0]+' 10%,'+
    '   #'+y[1]+' '+(10+b)+'%,'+
    '   #'+y[1]+' 20%,'+
    '   #'+y[2]+' '+(20+b)+'%,'+
    '   #'+y[2]+' 30%,'+
    '   #'+y[3]+' '+(30+b)+'%,'+
    '   #'+y[3]+' 40%,'+
    '   #'+y[4]+' '+(40+b)+'%,'+
    '   #'+y[4]+' 50%,'+
    '   #'+y[5]+' '+(50+b)+'%,'+
    '   #'+y[5]+' 60%,'+
    '   #'+y[6]+' '+(60+b)+'%,'+
    '   #'+y[6]+' 70%,'+
    '   #'+y[7]+' '+(70+b)+'%,'+
    '   #'+y[7]+' 80%,'+
    '   #'+y[8]+' '+(80+b)+'%,'+
    '   #'+y[8]+' 90%,'+
    '   #'+y[9]+' '+(90+b)+'%,'+
    '   #'+y[9]+' 100%);"></div>';
  }
   function chunkSubstr(str, size) {
  const numChunks = Math.ceil(str.length / size)
  const chunks = new Array(numChunks)

  for (let i = 0, o = 0; i < numChunks; ++i, o += size) {
    chunks[i] = str.substr(o, size)
  }

  return chunks
}
   function draw(x){
    sha256(x).then((x)=>{
        var html=sha256tohtml(x,0);
            document.getElementById('addhere').innerHTML=html;
        });
   }
   window.onload=function(){
    document.getElementById('sha256string').oninput=function(){draw(this.value);}
        draw(document.getElementById('sha256string').value)
}
<html>
<input id="sha256string" value="Hello World">
 <div id="addhere"></div>
</html>

Upvotes: 1

R.J.
R.J.

Reputation: 683

I tried all the solutions others provided but found that similar strings (string1 vs string2) produce colors that are too similar for my liking. Therefore, I built my own influenced by the input and ideas of others.

This one will compute the MD5 checksum of the string, and take the first 6 hex digits to define the RGB 24-bit code.

The MD5 functionality is an open-source JQuery plug in. The JS function goes as follows:

function getRGB(str) {
    return '#' + $.md5(str).substring(0, 6);
}

A link to this working example is on jsFiddle. Just input a string into the input field and press enter, and do so over and over again to compare your findings.

Upvotes: 10

Zeno Zeng
Zeno Zeng

Reputation: 331

I just build a JavaScript library named color-hash, which can generate color based on the given string (using HSL color space and BKDRHash).

Repo: https://github.com/zenozeng/color-hash
Demo: https://zenozeng.github.io/color-hash/demo/

Upvotes: 17

clayzermk1
clayzermk1

Reputation: 1048

For any Javascript users out there, I combined the accepted answer from @jeff-foster with the djb2 hash function from erlycoder.

The result per the question:

function djb2(str){
  var hash = 5381;
  for (var i = 0; i < str.length; i++) {
    hash = ((hash << 5) + hash) + str.charCodeAt(i); /* hash * 33 + c */
  }
  return hash;
}

function hashStringToColor(str) {
  var hash = djb2(str);
  var r = (hash & 0xFF0000) >> 16;
  var g = (hash & 0x00FF00) >> 8;
  var b = hash & 0x0000FF;
  return "#" + ("0" + r.toString(16)).substr(-2) + ("0" + g.toString(16)).substr(-2) + ("0" + b.toString(16)).substr(-2);
}

UPDATE: Fixed the return string to always return a #000000 format hex string based on an edit by @alexc (thanks!).

Upvotes: 29

Jeff Foster
Jeff Foster

Reputation: 44696

A good hash function will provide a near uniform distribution over the key space. This reduces the question to how do I convert a random 32 bit number to a 3 byte RGB space. I see nothing wrong with just taking the low 3 bytes.

int hash = string.getHashCode();
int r = (hash & 0xFF0000) >> 16;
int g = (hash & 0x00FF00) >> 8;
int b = hash & 0x0000FF;

Upvotes: 42

Related Questions