I’m not sure I’m entirely ready to get rid of color ramps, but I am very ready to explore programmatic color systems. This is a little helper script to generate a color value of a specific hue, saturation and desired contrast ratio. This method allows color pairings to be generated relative to each other as opposed to picking from a static ramp.
Note: I’m not a huge fan of how I’ve structured the parameters for the main function in this code snippet. I’ll likely adjust in the future.
Instructions
Copy the code below into a file on your machine and name it contrast.js
Edit BASE_COLOR, RATIO constants to fit your needs
Fire up a Terminal window and cd to the directory where you saved the contrast.js file
Run node contrast.js in your terminal
Read out the results returned in the terminal
/*
*
* Full disclosure on a few points:
* 1) I pulled functions from _numerous_ sources, so big thanks for folks for sharing code.
* I will try to retrace my history and provide credits where credit is due.
*
* 2) The code is still a mess and not fully tested. Some of the lines of code I still don't
* fully understand _why_ it works - which is always a "good" sign. This is only intended
* to be a proof of concept. A lot more work needs to happen to get this in a place that
* I'm confident with.
*
* 3) I'm still trying to work out how this kind of color develop flow would actually work.
* I am not a color expert - by a long shot - and so more vetting/thinking needs to occur.
* Some of the things I'm doing could very well be poor practices in relation to color
* theory. I'm learning as I go.
*/
const RED = 0.2126;
const GREEN = 0.7152;
const BLUE = 0.0722;
const GAMMA = 2.4;
function contrast(rgb1, rgb2) {
var lum1 = luminance(rgb1);
var lum2 = luminance(rgb2);
var brightest = Math.max(lum1, lum2);
var darkest = Math.min(lum1, lum2);
return (brightest + 0.05) / (darkest + 0.05);
}
function luminance(rgb) {
var a = rgb.map((v) => {
return v <= 0.03928
? v / 12.92
: Math.pow((v + 0.055) / 1.055, GAMMA);
});
return a[0] * RED + a[1] * GREEN + a[2] * BLUE;
}
function hslToRgb(hsl){
let h = hsl[0]
let s = hsl[1]
let l = hsl[2]
var r, g, b;
if(s == 0){
r = g = b = l;
} else{
var hue2rgb = function hue2rgb(p, q, t){
if(t < 0) t += 1;
if(t > 1) t -= 1;
if(t < 1/6) return p + (q - p) * 6 * t;
if(t < 1/2) return q;
if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
return p;
}
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
var p = 2 * l - q;
r = hue2rgb(p, q, h + 1/3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1/3);
}
return [r,g,b];
}
function rgbToHsl(rgb) {
let r = rgb[0];
let g = rgb[1];
let b = rgb[2];
var max = Math.max(r, g, b), min = Math.min(r, g, b);
var h, s, l = (max + min) / 2;
if(max == min){
h = s = 0; // achromatic
} else{
var d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch(max){
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
h /= 6;
}
return [h, s, l];
}
function getContrastingColor(h, s, rgb1, ratio, type = "auto") {
var inc = -0.001;
var l1 = luminance(rgb1);
let l2 = l1;
if(type === 'lighter' || (type === 'auto' && contrast(rgb1, [1,1,1]) > contrast(rgb1, [0,0,0]))) {
inc = -inc
}
while ((contrast(rgb1, hslToRgb([h, s, l2])) < ratio || Math.round( contrast(rgb1, hslToRgb([h, s, l2])) * 10) / 10 > ratio)) {
l2 += inc
if(l2 >= 1 || l2 <= 0) {
return null
}
}
return hslToRgb([h, s, l2]);
}
function rgbToHex(rgb) {
return "#" + ((1 << 24) + (Math.round(rgb[0]*255) << 16) + (Math.round(rgb[1]*255) << 8) + Math.round(rgb[2]*255)).toString(16).slice(1);
}
function hexToRgb(hex) {
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? [
parseInt(result[1], 16)/255,
parseInt(result[2], 16)/255,
parseInt(result[3], 16)/255
] : null;
}
const BASE_COLOR = '#F7D5B3';
const RATIO = 4.5
let baseRgb = hexToRgb(BASE_COLOR);
let baseHSL = rgbToHsl(baseRgb)
/*
* Generate a new color from `baseColor` to be lighter at a `RATIO` contrast ratio.
*/
let newRGB = getContrastingColor(baseHSL[0], baseHSL[1], baseRgb, RATIO, "auto")
/*
* Then convert to hex.
*/
if(newRGB) {
console.log(rgbToHex(newRGB))
} else {
console.error("No color available at this contrast")
}