Skip to content

Instantly share code, notes, and snippets.

@matzipan
Last active April 7, 2016 19:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save matzipan/d0199db1706426a8f4436d707b3288fd to your computer and use it in GitHub Desktop.
Save matzipan/d0199db1706426a8f4436d707b3288fd to your computer and use it in GitHub Desktop.
Generate a hash color for an arbitrary input string with Vala

As elementary OS is going to add letter tiles (similar to Android's) to act as default avatars for contacts, there was discussion about the color allocation algorithm. As some of the proposals were about using a boring lookup tables, I decided to try my hand at some hashing and color format conversion.

The algorithm generates a 32 bit hash and squashes it in the [0, 1] interval by dividing it with uint32.MAX. This hash value then gets multiplied with 2*PI and used as the hue component of HSL. This gets converted back to RGB for further manipulation.

The hashing function

The first hashing function used was a simple 8 bit XOR. Ignoring the fact that that only gave 256 possible colors, empirically, the hashing functions proved to be quite prone to collisions, especially with short strings and with short Hamming distances. Since the whole idea of the coloring is to help distinguish between strings that might be similar, I decided to change it to Bob Jenkins' one-at-a-time hashing function.

Picking saturation and lightness

As one of the requirements was to use colors similar to the elementary OS brand colors, I have adjusted the saturation and lightness values to match the mean of those components over the range of brand colors.

Since the foreground of the tile will contain a letter styled white, I used a contrast checker to evaluate the colors and adjust them for an acceptable contrast level. However, since the letters will also have a dark drop-shadow behind them, I did not pursue full test coverage.

HSL to RGB

In order to convert to RGB I used this algorithm, which is written in Javascript. After converting it to Vala, I decided to changed it to accept hue values expressed in radians.

Build and run

valac main.vala && ./main.vala
class HSL {
public double h;
public double s;
public double l;
}
class RGB {
public uint8 r;
public uint8 g;
public uint8 b;
}
const double pi = 3.14159265359;
double hue_to_primary_component (double p, double q, double t) {
if(t < 0) t += 2*pi;
if(t > 2*pi) t -= 2*pi;
if(t < 1/6f * 2*pi) return p + (q - p) * 6 * t / (2*pi);
if(t < pi) return q;
if(t < 2/3f * 2*pi) return p + (q - p) * (2/3f - t / (2*pi)) * 6;
return p;
}
RGB hsl_to_rgb (HSL hsl_color) {
var rgb_color = new RGB();
if(hsl_color.s == 0){
rgb_color.r = rgb_color.g = rgb_color.b = (uint8) (hsl_color.l*255); // achromatic
} else {
var q = hsl_color.l < 0.5 ?
hsl_color.l * (1 + hsl_color.s) :
hsl_color.l + hsl_color.s - hsl_color.l * hsl_color.s;
var p = 2 * hsl_color.l - q;
rgb_color.r = (uint8) (hue_to_primary_component(p, q, hsl_color.h + 2*pi/3f)*255);
rgb_color.g = (uint8) (hue_to_primary_component(p, q, hsl_color.h)*255);
rgb_color.b = (uint8) (hue_to_primary_component(p, q, hsl_color.h - 2*pi/3f)*255);
}
return rgb_color;
}
// http://www.burtleburtle.net/bob/hash/doobs.html
uint32 jenkins_one_at_a_time_hash (string original) {
uint32 hash = 0;
for(uint i = 0; i < original.length; i++) {
hash += original[i];
hash += (hash << 10);
hash ^= (hash >> 6);
}
hash += (hash << 3);
hash ^= (hash >> 11);
hash += (hash << 15);
return hash;
}
RGB squashed_hash_to_rgb(double squashed_hash) {
var hsl_color = new HSL ();
hsl_color.h = squashed_hash*2*pi;
// adjusted to pass the contrast checker at
// http://snook.ca/technical/colour_contrast/colour.html
// there are some issues with the blues, but they're not worth pursuing, as
// the text covering it will have a dark shadow
hsl_color.s = 0.65;
hsl_color.l = 0.48;
return hsl_to_rgb (hsl_color);
}
void print_rgb_for_string_with_jenkins (string source) {
var rgb_color = squashed_hash_to_rgb (jenkins_one_at_a_time_hash (source)/(double) uint32.MAX);
message("%s: #%02x%02x%02x", source, rgb_color.r, rgb_color.g, rgb_color.b);
}
void print_rgb_for_steps () {
for(double i=0; i<=1; i+=0.05) {
var rgb_color = squashed_hash_to_rgb(i);
message("%f: #%02x%02x%02x", i, rgb_color.r, rgb_color.g, rgb_color.b);
}
}
void main() {
message("-------------");
message("Jenkins hash");
message("-------------");
print_rgb_for_string_with_jenkins("asd");
print_rgb_for_string_with_jenkins("ard");
print_rgb_for_string_with_jenkins("blllasadsa");
print_rgb_for_string_with_jenkins("b2llasadsa");
print_rgb_for_string_with_jenkins("matzipan@gmail.com");
print_rgb_for_string_with_jenkins("matzipan@gmail!com");
print_rgb_for_string_with_jenkins("daniel@elementary.io");
print_rgb_for_string_with_jenkins("dbniel@elementary.io");
message("-------------");
message("Steps preview");
message("-------------");
print_rgb_for_steps();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment