-
-
Save mjackson/5311256 to your computer and use it in GitHub Desktop.
/** | |
* 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 ]; | |
} | |
/** | |
* Converts an HSL color value to RGB. Conversion formula | |
* adapted from http://en.wikipedia.org/wiki/HSL_color_space. | |
* Assumes h, s, and l are contained in the set [0, 1] and | |
* returns r, g, and b in the set [0, 255]. | |
* | |
* @param Number h The hue | |
* @param Number s The saturation | |
* @param Number l The lightness | |
* @return Array The RGB representation | |
*/ | |
function hslToRgb(h, s, l) { | |
var r, g, b; | |
if (s == 0) { | |
r = g = b = l; // achromatic | |
} else { | |
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 * 255, g * 255, b * 255 ]; | |
} | |
/** | |
* Converts an RGB color value to HSV. Conversion formula | |
* adapted from http://en.wikipedia.org/wiki/HSV_color_space. | |
* Assumes r, g, and b are contained in the set [0, 255] and | |
* returns h, s, and v 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 HSV representation | |
*/ | |
function rgbToHsv(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, v = max; | |
var d = max - min; | |
s = max == 0 ? 0 : d / max; | |
if (max == min) { | |
h = 0; // achromatic | |
} else { | |
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, v ]; | |
} | |
/** | |
* Converts an HSV color value to RGB. Conversion formula | |
* adapted from http://en.wikipedia.org/wiki/HSV_color_space. | |
* Assumes h, s, and v are contained in the set [0, 1] and | |
* returns r, g, and b in the set [0, 255]. | |
* | |
* @param Number h The hue | |
* @param Number s The saturation | |
* @param Number v The value | |
* @return Array The RGB representation | |
*/ | |
function hsvToRgb(h, s, v) { | |
var r, g, b; | |
var i = Math.floor(h * 6); | |
var f = h * 6 - i; | |
var p = v * (1 - s); | |
var q = v * (1 - f * s); | |
var t = v * (1 - (1 - f) * s); | |
switch (i % 6) { | |
case 0: r = v, g = t, b = p; break; | |
case 1: r = q, g = v, b = p; break; | |
case 2: r = p, g = v, b = t; break; | |
case 3: r = p, g = q, b = v; break; | |
case 4: r = t, g = p, b = v; break; | |
case 5: r = v, g = p, b = q; break; | |
} | |
return [ r * 255, g * 255, b * 255 ]; | |
} |
@isbyerley, came here to say the same thing. I think there's a mistake in the hsvToRgb. Here's my version:
function rgbFromHSV(h,s,v) { /** * I: An array of three elements hue (h) ∈ [0, 360], and saturation (s) and value (v) which are ∈ [0, 1] * O: An array of red (r), green (g), blue (b), all ∈ [0, 255] * Derived from https://en.wikipedia.org/wiki/HSL_and_HSV * This stackexchange was the clearest derivation I found to reimplement https://cs.stackexchange.com/questions/64549/convert-hsv-to-rgb-colors */ hprime = h / 60; const c = v * s; const x = c * (1 - Math.abs(hprime % 2 - 1)); const m = v - c; let r, g, b; if (!hprime) {r = 0; g = 0; b = 0; } if (hprime >= 0 && hprime < 1) { r = c; g = x; b = 0} if (hprime >= 1 && hprime < 2) { r = x; g = c; b = 0} if (hprime >= 2 && hprime < 3) { r = 0; g = c; b = x} if (hprime >= 3 && hprime < 4) { r = 0; g = x; b = c} if (hprime >= 4 && hprime < 5) { r = x; g = 0; b = c} if (hprime >= 5 && hprime < 6) { r = c; g = 0; b = x} r = Math.round( (r + m)* 255); g = Math.round( (g + m)* 255); b = Math.round( (b + m)* 255); return [r, g, b] }
if h is equal to 360 this code shows NaN because you are not checking hprime for value 6 (360/60 = 6)
if (hprime >= 5 && hprime <= 6) { r = c; g = 0; b = x} "hprime <= 6" instead of "hprime < 6" fixes it
It does work, but it is not good to do the comparison of floating point Number values as you do in your switch
. If it works in your case, this is only a potentially unreliable special case. You could figure out the three cases of your switch
before your /=
expressions. With minimal modifications of your code, it would look like
const rgbToHsl = (r, g, b, a) => {
let [r, g, b, a] = rgba;
let max = Math.max(r, g, b), min = Math.min(r, g, b);
const componentCase = max == r ? 0 : (max == g ? 1 : 2); // is max r g or b?
r /= 255, g /= 255, b /= 255, min /= 255, max /= 255;
let h, s, l = (max + min) / 2;
if (max == min) {
h = s = 0; // achromatic
} else {
let d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (componentCase) {
case 0: h = (g - b) / d + (g < b ? 6 : 0); break;
case 1: h = (b - r) / d + 2; break;
case 2: h = (r - g) / d + 4; break;
}
h /= 6;
}
return [h, s, l, a];
};
Also, note that you should use const
or let
, but not var
, to avoid the known risks related to the obsolete var
.
Then, it is not nice that you accept separate r
, g
, and b
on input but the function returns an array. Why RGB should be different from HSL? Note the line let [r, g, b, a] = rgba
.
Finally, it is good to have the alpha channel as well. This work is done for the use in CSS, where the alpha channel is generally used everywhere.
Here's a version of hslToRgb for Clojure (and ClojureScript and whatever Clojure implementation):
(defn hsl->rgb [h s l]
(let [hue->rgb (fn hue->rgb
[p q t]
(let [t (if (< t 0) (inc t) t)
t (if (> t 1) (dec t) t)]
(cond
(< t (/ 1 6)) (+ p (* (* (- q p) 6) t))
(< t (/ 1 2)) q
(< t (/ 2 3)) (+ p (* (* (- q p) (- (/ 2 3) t)) 6))
:else p)))
[r g b] (if (= s 0)
[l l l]
(let [q (if (< l 0.5) (* l (+ 1 s)) (- (+ l s) (* l s)))
p (- (* 2 l) q)]
[(hue->rgb p q (+ h (/ 1 3))) (hue->rgb p q h) (hue->rgb p q (- h (/ 1 3)))]))]
[(* r 255) (* g 255) (* b 255)]))
(comment
(hsl->rgb (/ 120 256) 1.0 0.5)
;; => [0 255 207.18750000000003]
:rcf)
It's a straight port. There may be dragons such as boxed math and things? I haven't considered.
Thank you, Peter!
Here's a version of hslToRgb for Clojure (and ClojureScript and whatever Clojure implementation):
RGB, HSL, XYZ, LAB, LCH color conversion algorithms (ES6 Modules)
https://gist.github.com/avisek/eadfbe7a7a169b1001a2d3affc21052e