This question already has answers here:
Programmatically Lighten or Darken a hex color (or rgb, and blend colors)
(21 answers)
Closed 9 years ago.
I want to have the user be able to enter a random hex color and my javascript code would print out a lighter version of that color (some sort of algorithm so to speak)
A quick example of how I want the colors to change.
What the user inputs: #2AC0A3
What it spits out: #C6EEE6
Thanks so much to anyone who can help!
An easy way to lighten up a color is linear interpolation with white. In the same way, a color can be darkened by interpolating with black.
Here's a function that takes a color string and changes the brightness indicated by light:
function hex2(c) {
c = Math.round(c);
if (c < 0) c = 0;
if (c > 255) c = 255;
var s = c.toString(16);
if (s.length < 2) s = "0" + s;
return s;
}
function color(r, g, b) {
return "#" + hex2(r) + hex2(g) + hex2(b);
}
function shade(col, light) {
// TODO: Assert that col is good and that -1 < light < 1
var r = parseInt(col.substr(1, 2), 16);
var g = parseInt(col.substr(3, 2), 16);
var b = parseInt(col.substr(5, 2), 16);
if (light < 0) {
r = (1 + light) * r;
g = (1 + light) * g;
b = (1 + light) * b;
} else {
r = (1 - light) * r + light * 255;
g = (1 - light) * g + light * 255;
b = (1 - light) * b + light * 255;
}
return color(r, g, b);
}
When light is negative, the color is darkened; -1 always yields black. When light is positive, the color is lightened, 1always yields white. Finally, 0 always yields the original color:
alert(shade("#2ac0a3", 0.731));
Related
I'm trying to add a gradient to every element to each page, for now, I've achieved it but I've run into a problem.
If the gradient is too light my text is not visible, is there any way to check if the gradient color is too light and then change the text to black?
Here is my code
This is the gradients element = https://github.com/ghosh/uiGradients/blob/master/gradients.json
const cards = document.querySelectorAll('.card');
cards.forEach((card)=>{
let i = Math.floor(Math.random() * gradients.length);
const color = gradients[i].colors;
color.forEach((color)=> {
let finalGradient = `background: linear-gradient(to right, ${color}, ${color}, ${color}) !important;`;
card.style.cssText = finalGradient;
})
})
Here a function that I use to detect if a color is light or dark. You can use it and adapt to your code:
function isLightOrDark(color) {
// Variables for red, green, blue values
var r, g, b, hsp;
// Check the format of the color, HEX or RGB?
if (color.match(/^rgb/)) {
// If HEX --> store the red, green, blue values in separate variables
color = color.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+(?:\.\d+)?))?\)$/);
r = color[1];
g = color[2];
b = color[3];
}
else {
// If RGB --> Convert it to HEX: http://gist.github.com/983661
color = +("0x" + color.slice(1).replace(
color.length < 5 && /./g, '$&$&'));
r = color >> 16;
g = color >> 8 & 255;
b = color & 255;
}
// HSP (Highly Sensitive Poo) equation from http://alienryderflex.com/hsp.html
hsp = Math.sqrt(
0.299 * (r * r) +
0.587 * (g * g) +
0.114 * (b * b)
);
// Using the HSP value, determine whether the color is light or dark
if (hsp>127.5) {
return 'light';
}
else {
return 'dark';
}
}
I'm making a color game via a course which generates 6 squares with random colours. When you click on a square which corresponds with the target color, you win the game.
Here's a link to how the game should work using RGB:
https://jsfiddle.net/jdwrgbh0/
I'm using HSL values instead.
Here's my code using HSL:
https://jsfiddle.net/fh7boykd/
(The only difference is this code for generate random colors)
function randomColor() {
var h = Math.floor(Math.random() * 361);
var s = Math.floor(Math.random() * 101);
var l = Math.floor(Math.random() * 101);
return "hsl(" + h + ', ' + s + '%' + ', ' + l + '%' + ")";
}
Even though I used the function above to generate HSL values, the background-color of the squares still shows RGB values instead of HSL values and as such, I can't win the game because the target color is never shown. I want the color of the squares to display background-color in HSL and not RGB. The above randomColor function seems fine and testing it in the console, it does seem to generate a random color after it's invoked each time.
Here's an image of the console when I run the code. The background-color is in RGB and not HSL.
I think the problem may be related to this function:
function changeColors(color){
//loop through all squares
for(var i = 0; i < squares.length; i++){
//change each color to match given color
squares[i].style.background = color;
}
}
This code changes the color of each square. When I look at the browser console, it shows RGB values instead of HSL values. How do I force squares[i].style.background = color; to use HSL instead of RGB?
The browser will convert your HSL back to RGB colors. From what I see in your question, you are using HSL just so you can generate random colors. You can also use this piece of code to generate random hex color code
var generateRandomHexColor = () => {
var allPossibleLetters = '0123456789ABCDEF';
var HexCode = '';
for (var i = 0; i < 6; i++) {
HexCode += allPossibleLetters[Math.floor(Math.random() * 16)];
}
return '#' + HexCode;
}
console.log(generateRandomHexColor())
I believe your question is that you want to be able to convert RGB to HSL and then understand that HSL value. This is entirely possible. I have found some js script off of github, from user mjackson.
/**
* Converts an RGB color value to HSL. Conversion formula
* adapted from http://en.wikipedia.org/wiki/HSL_color_space.
* Assumes r, g, and b are contained in the set [0, 255] and
* returns h, s, and l in the set [0, 1].
*
* #param Number r The red color value
* #param Number g The green color value
* #param Number b The blue color value
* #return Array The HSL representation
*/
function rgbToHsl(r, g, b) {
r /= 255, g /= 255, b /= 255;
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 ];
}
You may be able to adapt this to the way that you wish. I hope this helped.
Cheers!
if you want to set as hsl, just like this format:
divElement.style.backgroundColor = "hsl(155,100%,30%)";
but you want to got the style, that must be rgb or rgba string. so you must change it to hsl by your self.
I am trying to display each color channel for different color models, similar to this site.
That site displays HSB and CMYK but I would also like to show HWB.
For displaying RGB and HSL I obviously just used rgb() and hsl().
For example, to display the full range of s(aturation) in hsl I would do something like this:
hsl(hue, 0, luminosity);
hsl(hue, 50, luminosity);
hsl(hue, 100, luminosity);
Apply that to a linear gradient and then display on canvas. It seems addColorStop only supports keywords, hex, rgb/a and hsl/a. So how can I visually represent the other color models using the ones available?
I've looked everywhere and can't find anything, hopefully this isn't a duplicate question.
HWB is not supported in the browser or canvas at the moment. It's included in CSS module level 4, but not supported anywhere yet.
It's easy to convert to and from the color model though.
I converted these JavaScript functions to convert between RGB and HWB from this code (with some adjustments to normalize H and "de-normalize" RGB). They both return an object:
// RGB [0, 255]
// HWB [0, 1]
function rgb2hwb(r, g, b) {
r /= 255;
g /= 255;
b /= 255;
var f, i,
w = Math.min(r, g, b);
v = Math.max(r, g, b);
black = 1 - v;
if (v === w) return {h: 0, w: w, b: black};
f = r === w ? g - b : (g === w ? b - r : r - g);
i = r === w ? 3 : (g === w ? 5 : 1);
return {h: (i - f / (v - w)) / 6, w: w, b: black}
}
// HWB [0, 1]
// RGB [0, 255]
function hwb2rgb(h, w, b) {
h *= 6;
var v = 1 - b, n, f, i;
if (!h) return {r:v, g:v, b:v};
i = h|0;
f = h - i;
if (i & 1) f = 1 - f;
n = w + f * (v - w);
v = (v * 255)|0;
n = (n * 255)|0;
w = (w * 255)|0;
switch(i) {
case 6:
case 0: return {r:v, g:n, b: w};
case 1: return {r:n, g:v, b: w};
case 2: return {r:w, g:v, b: n};
case 3: return {r:w, g:n, b: v};
case 4: return {r:n, g:w, b: v};
case 5: return {r:v, g:w, b: n};
}
}
// ------ TEST -------
var hwb = rgb2hwb(250,100,10);
var rgb = hwb2rgb(hwb.h, hwb.w, hwb.b);
document.getElementById("hwb").innerHTML = hwb.h.toFixed(2) + "," + hwb.w.toFixed(2) + "," + hwb.w.toFixed(2);
document.getElementById("rgb").innerHTML = rgb.r + "," + rgb.g + "," + rgb.b;
Original RGB: <span>250,100,10</span><br>
rgb2hwb: <span id="hwb"></span><br>
hwb2rgb: <span id="rgb"></span><br>
To use it as a input for a color-stop etc., do something like this:
gr.addColorStop(n, fromHWB(360, 50, 50));
...
function fromHWB(H, W, B) {
var c = hwb2rgb(H/360, W/100, B/100);
return "rgb(" + c.r + "," + c.g + "," + c.b + ")"
}
Support for HWB(hue whiteness blackness) was added in Chrome 101 in 2022.
The specifications are listed in the CSS Color Module Level 4:
Implementation:
hwb() = hwb( [<hue> | none] [<percentage> | none] [<percentage> | none] [ / [<alpha-value> | none] ]? )
function hwbToRgb(hue, white, black) {
white /= 100;
black /= 100;
if (white + black >= 1) {
let gray = white / (white + black);
return [gray, gray, gray];
}
let rgb = hslToRgb(hue, 100, 50);
for (let i = 0; i < 3; i++) {
rgb[i] *= (1 - white - black);
rgb[i] += white;
}
return rgb;
}
function rgbToHwb(red, green, blue) {
var hsl = rgbToHsl(red, green, blue);
var white = Math.min(red, green, blue);
var black = 1 - Math.max(red, green, blue);
return([hsl[0], white*100, black*100]);
}
Is it possible to calculate a color in a middle of a gradient?
var color1 = 'FF0000';
var color2 = '00FF00';
// 50% between the two colors, should return '808000'
var middle = gradient(color1, color2, 0.5);
I only have two hex strings, and I want one in return.
This should work:
It basically involves converting them to decimal, finding the halves, converting the results back to hex and then concatenating them.
var color1 = 'FF0000';
var color2 = '00FF00';
var ratio = 0.5;
var hex = function(x) {
x = x.toString(16);
return (x.length == 1) ? '0' + x : x;
};
var r = Math.ceil(parseInt(color1.substring(0,2), 16) * ratio + parseInt(color2.substring(0,2), 16) * (1-ratio));
var g = Math.ceil(parseInt(color1.substring(2,4), 16) * ratio + parseInt(color2.substring(2,4), 16) * (1-ratio));
var b = Math.ceil(parseInt(color1.substring(4,6), 16) * ratio + parseInt(color2.substring(4,6), 16) * (1-ratio));
var middle = hex(r) + hex(g) + hex(b);
An ES6 version with comprehensions:
function interpolateColor(c0, c1, f){
c0 = c0.match(/.{1,2}/g).map((oct)=>parseInt(oct, 16) * (1-f))
c1 = c1.match(/.{1,2}/g).map((oct)=>parseInt(oct, 16) * f)
let ci = [0,1,2].map(i => Math.min(Math.round(c0[i]+c1[i]), 255))
return ci.reduce((a,v) => ((a << 8) + v), 0).toString(16).padStart(6, "0")
}
As in the accepted answer, c0,c1 are color codes (without the leading #) and f is "progress" between the two values. (At f=0 this ends up returning c0, at f=1 this returns c1).
The first two lines convert the color codes into arrays of scaled integers
The third line:
"zips" the two integer arrays
sums the corresponding values
rounds the sum and clamps it to 0-255
The fourth line:
converts the integer array into a single integer (reduce and bitshifting)
converts the integer into its hexadecimal string form
ensures the resulting string is 6 characters long and returns it
I can't comment on the answer above, so I write it here:
I found out that in the Javascript substring method the to parameter index is not included in the returned string. That means:
var string = "test";
//index: 0123
alert(string.substring(1,3));
//will alert es and NOT est
Edit: So it should be:
parseInt(color1.substring(0,2), 16);
parseInt(color1.substring(2,4), 16);
and
parseInt(color1.substring(4,6), 16);
You can use this ready function (ES6):
const calculateMiddleColor = ({
color1 = 'FF0000',
color2 = '00FF00',
ratio,
}) => {
const hex = (color) => {
const colorString = color.toString(16);
return colorString.length === 1 ? `0${colorString}` : colorString;
};
const r = Math.ceil(
parseInt(color2.substring(0, 2), 16) * ratio
+ parseInt(color1.substring(0, 2), 16) * (1 - ratio),
);
const g = Math.ceil(
parseInt(color2.substring(2, 4), 16) * ratio
+ parseInt(color1.substring(2, 4), 16) * (1 - ratio),
);
const b = Math.ceil(
parseInt(color2.substring(4, 6), 16) * ratio
+ parseInt(color1.substring(4, 6), 16) * (1 - ratio),
);
return hex(r) + hex(g) + hex(b);
};
//////////////////////////////////////////////////////////////////////
console.log(calculateMiddleColor({ ratio: 0 / 5 })); // ff0000
console.log(calculateMiddleColor({ ratio: 5 / 5 })); // 00ff00
console.log(calculateMiddleColor({ ratio: 2.5 / 5 })); // 808000
console.log(calculateMiddleColor({ ratio: 4.2 / 5 })); // 29d700
I'm trying to interpolate between two colours in HSV colour space to produce a smooth colour gradient.
I'm using a linear interpolation, eg:
h = (1 - p) * h1 + p * h2
s = (1 - p) * s1 + p * s2
v = (1 - p) * v1 + p * v2
(where p is the percentage, and h1, h2, s1, s2, v1, v2 are the hue, saturation and value components of the two colours)
This produces a good result for s and v but not for h. As the hue component is an angle, the calculation needs to work out the shortest distance between h1 and h2 and then do the interpolation in the right direction (either clockwise or anti-clockwise).
What formula or algorithm should I use?
EDIT: By following Jack's suggestions I modified my JavaScript gradient function and it works well. For anyone interested, here's what I ended up with:
// create gradient from yellow to red to black with 100 steps
var gradient = hsbGradient(100, [{h:0.14, s:0.5, b:1}, {h:0, s:1, b:1}, {h:0, s:1, b:0}]);
function hsbGradient(steps, colours) {
var parts = colours.length - 1;
var gradient = new Array(steps);
var gradientIndex = 0;
var partSteps = Math.floor(steps / parts);
var remainder = steps - (partSteps * parts);
for (var col = 0; col < parts; col++) {
// get colours
var c1 = colours[col],
c2 = colours[col + 1];
// determine clockwise and counter-clockwise distance between hues
var distCCW = (c1.h >= c2.h) ? c1.h - c2.h : 1 + c1.h - c2.h;
distCW = (c1.h >= c2.h) ? 1 + c2.h - c1.h : c2.h - c1.h;
// ensure we get the right number of steps by adding remainder to final part
if (col == parts - 1) partSteps += remainder;
// make gradient for this part
for (var step = 0; step < partSteps; step ++) {
var p = step / partSteps;
// interpolate h, s, b
var h = (distCW <= distCCW) ? c1.h + (distCW * p) : c1.h - (distCCW * p);
if (h < 0) h = 1 + h;
if (h > 1) h = h - 1;
var s = (1 - p) * c1.s + p * c2.s;
var b = (1 - p) * c1.b + p * c2.b;
// add to gradient array
gradient[gradientIndex] = {h:h, s:s, b:b};
gradientIndex ++;
}
}
return gradient;
}
You should just need to find out which is the shortest path from starting hue to ending hue. This can be done easily since hue values range from 0 to 255.
You can first subtract the lower hue from the higher one, then add 256 to the lower one to check again the difference with swapped operands.
int maxCCW = higherHue - lowerHue;
int maxCW = (lowerHue+256) - higherHue;
So you'll obtain two values, the greater one decides if you should go clockwise or counterclockwise. Then you'll have to find a way to make the interpolation operate on modulo 256 of the hue, so if you are interpolating from 246 to 20 if the coefficient is >= 0.5f you should reset hue to 0 (since it reaches 256 and hue = hue%256 in any case).
Actually if you don't care about hue while interpolating over the 0 but just apply modulo operator after calculating the new hue it should work anyway.
Although this answer is late, the accepted one is incorrect in stating that hue should be within [0, 255]; also more justice can be done with clearer explanation and code.
Hue is an angular value in the interval [0, 360); a full circle where 0 = 360. The HSV colour space is easier to visualize and is more intuitive to humans then RGB. HSV forms a cylinder from which a slice is shown in many colour pickers, while RGB is really a cube and isn't really a good choice for a colour picker; most ones which do use it would have to employ more sliders than required for a HSV picker.
The requirement when interpolating hue is that the smaller arc is chosen to reach from one hue to another. So given two hue values, there are four possibilities, given with example angles below:
Δ | ≤ 180 | > 180
--|---------|---------
+ | 40, 60 | 310, 10
− | 60, 40 | 10, 310
if Δ = 180 then both +/− rotation are valid options
Lets take + as counter-clockwise and − as clockwise rotation. If the difference in absolute value exceeds 180 then normalize it by ± 360 to make sure the magnitude is within 180; this also reverses the direction, rightly.
var d = h2 - h1;
var delta = d + ((Math.abs(d) > 180) ? ((d < 0) ? 360 : -360) : 0);
Now just divide delta by the required number of steps to get the weight of each loop iteration to add to the start angle during interpolating.
var new_angle = start + (i * delta);
Relevant function excerpted from the complete code that follows:
function interpolate(h1, h2, steps) {
var d = h2 - h1;
var delta = (d + ((Math.abs(d) > 180) ? ((d < 0) ? 360 : -360) : 0)) / (steps + 1.0);
var turns = [];
for (var i = 1; d && i <= steps; ++i)
turns.push(((h1 + (delta * i)) + 360) % 360);
return turns;
}
"use strict";
function interpolate(h1, h2, steps) {
var d = h2 - h1;
var delta = (d + ((Math.abs(d) > 180) ? ((d < 0) ? 360 : -360) : 0)) / (steps + 1.0);
var turns = [];
for (var i = 1; d && i <= steps; ++i)
turns.push(((h1 + (delta * i)) + 360) % 360);
return turns;
}
function get_results(h1, h2, steps) {
h1 = norm_angle(h1);
h2 = norm_angle(h2);
var r = "Start: " + h1 + "<br />";
var turns = interpolate(h1, h2, steps);
r += turns.length ? "Turn: " : "";
r += turns.join("<br />Turn: ");
r += (turns.length ? "<br />" : "") + "Stop: " + h2;
return r;
}
function run() {
var h1 = get_angle(document.getElementById('h1').value);
var h2 = get_angle(document.getElementById('h2').value);
var steps = get_num(document.getElementById('steps').value);
var result = get_results(h1, h2, steps);
document.getElementById('res').innerHTML = result;
}
function get_num(s) {
var n = parseFloat(s);
return (isNaN(n) || !isFinite(n)) ? 0 : n;
}
function get_angle(s) {
return get_num(s) % 360;
}
function norm_angle(a) {
a %= 360;
a += (a < 0) ? 360 : 0;
return a;
}
<h1 id="title">Hue Interpolation</h1>
Angle 1
<input type="text" id="h1" />
<br />Angle 2
<input type="text" id="h2" />
<br />
<br />Intermediate steps
<input type="text" id="steps" value="5" />
<br />
<br/>
<input type="submit" value="Run" onclick="run()" />
<p id="res"></p>