I'm trying to implement the method of improving fingerprint images by Anil Jain. As a starter, I encountered some difficulties while extracting the orientation image, and am strictly following those steps described in Section 2.4 of that paper.
So, this is the input image:
And this is after normalization using exactly the same method as in that paper:
I'm expecting to see something like this (an example from the internet):
However, this is what I got for displaying obtained orientation matrix:
Obviously this is wrong, and it also gives non-zero values for those zero points in the original input image.
This is the code I wrote:
cv::Mat orientation(cv::Mat inputImage)
{
cv::Mat orientationMat = cv::Mat::zeros(inputImage.size(), CV_8UC1);
// compute gradients at each pixel
cv::Mat grad_x, grad_y;
cv::Sobel(inputImage, grad_x, CV_16SC1, 1, 0, 3, 1, 0, cv::BORDER_DEFAULT);
cv::Sobel(inputImage, grad_y, CV_16SC1, 0, 1, 3, 1, 0, cv::BORDER_DEFAULT);
cv::Mat Vx, Vy, theta, lowPassX, lowPassY;
cv::Mat lowPassX2, lowPassY2;
Vx = cv::Mat::zeros(inputImage.size(), inputImage.type());
Vx.copyTo(Vy);
Vx.copyTo(theta);
Vx.copyTo(lowPassX);
Vx.copyTo(lowPassY);
Vx.copyTo(lowPassX2);
Vx.copyTo(lowPassY2);
// estimate the local orientation of each block
int blockSize = 16;
for(int i = blockSize/2; i < inputImage.rows - blockSize/2; i+=blockSize)
{
for(int j = blockSize / 2; j < inputImage.cols - blockSize/2; j+= blockSize)
{
float sum1 = 0.0;
float sum2 = 0.0;
for ( int u = i - blockSize/2; u < i + blockSize/2; u++)
{
for( int v = j - blockSize/2; v < j+blockSize/2; v++)
{
sum1 += grad_x.at<float>(u,v) * grad_y.at<float>(u,v);
sum2 += (grad_x.at<float>(u,v)*grad_x.at<float>(u,v)) * (grad_y.at<float>(u,v)*grad_y.at<float>(u,v));
}
}
Vx.at<float>(i,j) = sum1;
Vy.at<float>(i,j) = sum2;
double calc = 0.0;
if(sum1 != 0 && sum2 != 0)
{
calc = 0.5 * atan(Vy.at<float>(i,j) / Vx.at<float>(i,j));
}
theta.at<float>(i,j) = calc;
// Perform low-pass filtering
float angle = 2 * calc;
lowPassX.at<float>(i,j) = cos(angle * pi / 180);
lowPassY.at<float>(i,j) = sin(angle * pi / 180);
float sum3 = 0.0;
float sum4 = 0.0;
for(int u = -lowPassSize / 2; u < lowPassSize / 2; u++)
{
for(int v = -lowPassSize / 2; v < lowPassSize / 2; v++)
{
sum3 += inputImage.at<float>(u,v) * lowPassX.at<float>(i - u*lowPassSize, j - v * lowPassSize);
sum4 += inputImage.at<float>(u, v) * lowPassY.at<float>(i - u*lowPassSize, j - v * lowPassSize);
}
}
lowPassX2.at<float>(i,j) = sum3;
lowPassY2.at<float>(i,j) = sum4;
float calc2 = 0.0;
if(sum3 != 0 && sum4 != 0)
{
calc2 = 0.5 * atan(lowPassY2.at<float>(i, j) / lowPassX2.at<float>(i, j)) * 180 / pi;
}
orientationMat.at<float>(i,j) = calc2;
}
}
return orientationMat;
}
I've already searched a lot on the web, but almost all of them are in Matlab. And there exist very few ones using OpenCV, but they didn't help me either. I sincerely hope someone could go through my code and point out any error to help. Thank you in advance.
Update
Here are the steps that I followed according to the paper:
Obtain normalized image G.
Divide G into blocks of size wxw (16x16).
Compute the x and y gradients at each pixel (i,j).
Estimate the local orientation of each block centered at pixel (i,j) using equations:
Perform low-pass filtering to remove noise. For that, convert the orientation image into a continuous vector field defined as:
where W is a two-dimensional low-pass filter, and w(phi) x w(phi) is its size, which equals to 5.
Finally, compute the local ridge orientation at (i,j) using:
Update2
This is the output of orientationMat after changing the mat type to CV_16SC1 in Sobel operation as Micka suggested:
Maybe it's too late for me to answer, but anyway somebody could read this later and solve the same problem.
I've been working for a while in the same algorithm, same method you posted... But there's some writting errors when the papper was redacted (I guess). After fighting a lot with the equations I found this errors by looking other similar works.
Here is what worked for me...
Vy(i, j) = 2*dx(u,v)*dy(u,v)
Vx(i,j) = dx(u,v)^2 - dy(u,v)^2
O(i,j) = 0.5*arctan(Vy(i,j)/Vx(i,j)
(Excuse me I wasn't able to post images, so I wrote the modified ecuations. Remeber "u" and "v" are positions of the summation across the BlockSize by BlockSize window)
The first thing and most important (obviously) are the equations, I saw that in different works this expressions were really different and in every one they talked about the same algorithm of Hong et al.
The Key is finding the Least Mean Square (First 3 equations) of the gradients (Vx and Vy), I provided the corrected formulas above for this ation. Then you can compute angle theta for the non overlapping window (16x16 size recommended in the papper), after that the algorithm says you must calculate the magnitud of the doubled angle in "x" and "y" directions (Phi_x and Phi_y).
Phi_x(i,j) = V(i,j) * cos(2*O(i,j))
Phi_y(i,j) = V(i,j) * sin(2*O(i,j))
Magnitud is just:
V = sqrt(Vx(i,j)^2 + Vy(i,j)^2)
Note that in the related work doesn't mention that you have to use the gradient magnitud, but it make sense (for me) in doing it. After all this corrections you can apply the low pass filter to Phi_x and Phi_y, I used a simple Mask of size 5x5 to average this magnitudes (something like medianblur() of opencv).
Last thing is to calculate new angle, that is the average of the 25ith neighbors in the O(i,j) image, for this you just have to:
O'(i,j) = 0.5*arctan(Phi_y/Phi_x)
We're just there... All this just for calculating the angle of the NORMAL VECTOR TO THE RIDGES DIRECTIONS (O'(i,j)) in the BlockSize by BlockSize non overlapping window, what does it mean? it means that the angle we just calculated is perpendicular to the ridges, in simple words we just calculated the angle of the riges plus 90 degrees... To get the angle we need, we just have to substract to the obtained angle 90°.
To draw the lines we need to have an initial point (X0, Y0) and a final point(X1, Y1). For that imagine a circle centered on (X0, Y0) with a radious of "r":
x0 = i + blocksize/2
y0 = j + blocksize/2
r = blocksize/2
Note we add i and j to the first coordinates becouse the window is moving and we are gonna draw the line starting from the center of the non overlaping window, so we can't use just the center of the non overlaping window.
Then to calculate the end coordinates to draw a line we can just have to use a right triangle so...
X1 = r*cos(O'(i,j)-90°)+X0
Y1 = r*sin(O'(i,j)-90°)+Y0
X2 = X0-r*cos(O'(i,j)-90°)
Y2 = Y0-r*cos(O'(i,j)-90°)
Then just use opencv line function, where initial Point is (X0,Y0) and final Point is (X1, Y1). Additional to it, I drawed the windows of 16x16 and computed the oposite points of X1 and Y1 (X2 and Y2) to draw a line of the entire window.
Hope this help somebody.
My results...
Main function:
Mat mat = imread("nwmPa.png",0);
mat.convertTo(mat, CV_32F, 1.0/255, 0);
Normalize(mat);
int blockSize = 6;
int height = mat.rows;
int width = mat.cols;
Mat orientationMap;
orientation(mat, orientationMap, blockSize);
Normalize:
void Normalize(Mat & image)
{
Scalar mean, dev;
meanStdDev(image, mean, dev);
double M = mean.val[0];
double D = dev.val[0];
for(int i(0) ; i<image.rows ; i++)
{
for(int j(0) ; j<image.cols ; j++)
{
if(image.at<float>(i,j) > M)
image.at<float>(i,j) = 100.0/255 + sqrt( 100.0/255*pow(image.at<float>(i,j)-M,2)/D );
else
image.at<float>(i,j) = 100.0/255 - sqrt( 100.0/255*pow(image.at<float>(i,j)-M,2)/D );
}
}
}
Orientation map:
void orientation(const Mat &inputImage, Mat &orientationMap, int blockSize)
{
Mat fprintWithDirectionsSmoo = inputImage.clone();
Mat tmp(inputImage.size(), inputImage.type());
Mat coherence(inputImage.size(), inputImage.type());
orientationMap = tmp.clone();
//Gradiants x and y
Mat grad_x, grad_y;
// Sobel(inputImage, grad_x, CV_32F, 1, 0, 3, 1, 0, BORDER_DEFAULT);
// Sobel(inputImage, grad_y, CV_32F, 0, 1, 3, 1, 0, BORDER_DEFAULT);
Scharr(inputImage, grad_x, CV_32F, 1, 0, 1, 0);
Scharr(inputImage, grad_y, CV_32F, 0, 1, 1, 0);
//Vector vield
Mat Fx(inputImage.size(), inputImage.type()),
Fy(inputImage.size(), inputImage.type()),
Fx_gauss,
Fy_gauss;
Mat smoothed(inputImage.size(), inputImage.type());
// Local orientation for each block
int width = inputImage.cols;
int height = inputImage.rows;
int blockH;
int blockW;
//select block
for(int i = 0; i < height; i+=blockSize)
{
for(int j = 0; j < width; j+=blockSize)
{
float Gsx = 0.0;
float Gsy = 0.0;
float Gxx = 0.0;
float Gyy = 0.0;
//for check bounds of img
blockH = ((height-i)<blockSize)?(height-i):blockSize;
blockW = ((width-j)<blockSize)?(width-j):blockSize;
//average at block WхW
for ( int u = i ; u < i + blockH; u++)
{
for( int v = j ; v < j + blockW ; v++)
{
Gsx += (grad_x.at<float>(u,v)*grad_x.at<float>(u,v)) - (grad_y.at<float>(u,v)*grad_y.at<float>(u,v));
Gsy += 2*grad_x.at<float>(u,v) * grad_y.at<float>(u,v);
Gxx += grad_x.at<float>(u,v)*grad_x.at<float>(u,v);
Gyy += grad_y.at<float>(u,v)*grad_y.at<float>(u,v);
}
}
float coh = sqrt(pow(Gsx,2) + pow(Gsy,2)) / (Gxx + Gyy);
//smoothed
float fi = 0.5*fastAtan2(Gsy, Gsx)*CV_PI/180;
Fx.at<float>(i,j) = cos(2*fi);
Fy.at<float>(i,j) = sin(2*fi);
//fill blocks
for ( int u = i ; u < i + blockH; u++)
{
for( int v = j ; v < j + blockW ; v++)
{
orientationMap.at<float>(u,v) = fi;
Fx.at<float>(u,v) = Fx.at<float>(i,j);
Fy.at<float>(u,v) = Fy.at<float>(i,j);
coherence.at<float>(u,v) = (coh<0.85)?1:0;
}
}
}
} ///for
GaussConvolveWithStep(Fx, Fx_gauss, 5, blockSize);
GaussConvolveWithStep(Fy, Fy_gauss, 5, blockSize);
for(int m = 0; m < height; m++)
{
for(int n = 0; n < width; n++)
{
smoothed.at<float>(m,n) = 0.5*fastAtan2(Fy_gauss.at<float>(m,n), Fx_gauss.at<float>(m,n))*CV_PI/180;
if((m%blockSize)==0 && (n%blockSize)==0){
int x = n;
int y = m;
int ln = sqrt(2*pow(blockSize,2))/2;
float dx = ln*cos( smoothed.at<float>(m,n) - CV_PI/2);
float dy = ln*sin( smoothed.at<float>(m,n) - CV_PI/2);
arrowedLine(fprintWithDirectionsSmoo, Point(x, y+blockH), Point(x + dx, y + blockW + dy), Scalar::all(255), 1, CV_AA, 0, 0.06*blockSize);
// qDebug () << Fx_gauss.at<float>(m,n) << Fy_gauss.at<float>(m,n) << smoothed.at<float>(m,n);
// imshow("Orientation", fprintWithDirectionsSmoo);
// waitKey(0);
}
}
}///for2
normalize(orientationMap, orientationMap,0,1,NORM_MINMAX);
imshow("Orientation field", orientationMap);
orientationMap = smoothed.clone();
normalize(smoothed, smoothed, 0, 1, NORM_MINMAX);
imshow("Smoothed orientation field", smoothed);
imshow("Coherence", coherence);
imshow("Orientation", fprintWithDirectionsSmoo);
}
seems nothing forgot )
I have read your code thoroughly and found that you have made a mistake while calculating sum3 and sum4:
sum3 += inputImage.at<float>(u,v) * lowPassX.at<float>(i - u*lowPassSize, j - v * lowPassSize);
sum4 += inputImage.at<float>(u, v) * lowPassY.at<float>(i - u*lowPassSize, j - v * lowPassSize);
instead of inputImage you should use a low pass filter.
I am looking for an algorithm to convert between HSL color to RGB.
It seems to me that HSL is not very widely used so I am not having much luck searching for a converter.
Garry Tan posted a Javascript solution on his blog (which he attributes to a now defunct mjijackson.com, but is archived here and the original author has a gist - thanks to user2441511).
The code is re-posted below:
HSL to RGB:
/**
* 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{
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 [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
}
RGB to HSL:
/**
* 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];
}
Found the easiest way, python to the rescue :D
colorsys.hls_to_rgb(h, l, s)
Convert the color from HLS coordinates to RGB coordinates.
Java implementation of Mohsen's code
Note that all integer are declared as float (i.e 1f) and must be float, else you will optain grey colors.
HSL to RGB
/**
* 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 h The hue
* #param s The saturation
* #param l The lightness
* #return int array, the RGB representation
*/
public static int[] hslToRgb(float h, float s, float l){
float r, g, b;
if (s == 0f) {
r = g = b = l; // achromatic
} else {
float q = l < 0.5f ? l * (1 + s) : l + s - l * s;
float p = 2 * l - q;
r = hueToRgb(p, q, h + 1f/3f);
g = hueToRgb(p, q, h);
b = hueToRgb(p, q, h - 1f/3f);
}
int[] rgb = {to255(r), to255(g), to255(b)};
return rgb;
}
public static int to255(float v) { return (int)Math.min(255,256*v); }
/** Helper method that converts hue to rgb */
public static float hueToRgb(float p, float q, float t) {
if (t < 0f)
t += 1f;
if (t > 1f)
t -= 1f;
if (t < 1f/6f)
return p + (q - p) * 6f * t;
if (t < 1f/2f)
return q;
if (t < 2f/3f)
return p + (q - p) * (2f/3f - t) * 6f;
return p;
}
RGB to HSL
/**
* Converts an RGB color value to HSL. Conversion formula
* adapted from http://en.wikipedia.org/wiki/HSL_color_space.
* Assumes pR, pG, and bpBare contained in the set [0, 255] and
* returns h, s, and l in the set [0, 1].
*
* #param pR The red color value
* #param pG The green color value
* #param pB The blue color value
* #return float array, the HSL representation
*/
public static float[] rgbToHsl(int pR, int pG, int pB) {
float r = pR / 255f;
float g = pG / 255f;
float b = pB / 255f;
float max = (r > g && r > b) ? r : (g > b) ? g : b;
float min = (r < g && r < b) ? r : (g < b) ? g : b;
float h, s, l;
l = (max + min) / 2.0f;
if (max == min) {
h = s = 0.0f;
} else {
float d = max - min;
s = (l > 0.5f) ? d / (2.0f - max - min) : d / (max + min);
if (r > g && r > b)
h = (g - b) / d + (g < b ? 6.0f : 0.0f);
else if (g > b)
h = (b - r) / d + 2.0f;
else
h = (r - g) / d + 4.0f;
h /= 6.0f;
}
float[] hsl = {h, s, l};
return hsl;
}
Short but precise - JS
Use this JS code (more: rgb2hsl, hsv2rgb rgb2hsv and hsl2hsv) - php version here
// input: h as an angle in [0,360] and s,l in [0,1] - output: r,g,b in [0,1]
function hsl2rgb(h,s,l)
{
let a=s*Math.min(l,1-l);
let f= (n,k=(n+h/30)%12) => l - a*Math.max(Math.min(k-3,9-k,1),-1);
return [f(0),f(8),f(4)];
}
// oneliner version
let hsl2rgb = (h,s,l, a=s*Math.min(l,1-l), f= (n,k=(n+h/30)%12) => l - a*Math.max(Math.min(k-3,9-k,1),-1)) => [f(0),f(8),f(4)];
// r,g,b are in [0-1], result e.g. #0812fa.
let rgb2hex = (r,g,b) => "#" + [r,g,b].map(x=>Math.round(x*255).toString(16).padStart(2,0) ).join('');
console.log(`hsl: (30,0.2,0.3) --> rgb: (${hsl2rgb(30,0.2,0.3)}) --> hex: ${rgb2hex(...hsl2rgb(30,0.2,0.3))}`);
// ---------------
// UX
// ---------------
rgb= [0,0,0];
hs= [0,0,0];
let $ = x => document.querySelector(x);
function changeRGB(i,e) {
rgb[i]=e.target.value/255;
hs = rgb2hsl(...rgb);
refresh();
}
function changeHS(i,e) {
hs[i]=e.target.value/(i?255:1);
rgb= hsl2rgb(...hs);
refresh();
}
function refresh() {
rr = rgb.map(x=>x*255|0).join(', ')
hh = rgb2hex(...rgb);
tr = `RGB: ${rr}`
th = `HSL: ${hs.map((x,i)=>i? (x*100).toFixed(2)+'%':x|0).join(', ')}`
thh= `HEX: ${hh}`
$('.box').style.backgroundColor=`rgb(${rr})`;
$('.infoRGB').innerHTML=`${tr}`;
$('.infoHS').innerHTML =`${th}\n${thh}`;
$('#r').value=rgb[0]*255;
$('#g').value=rgb[1]*255;
$('#b').value=rgb[2]*255;
$('#h').value=hs[0];
$('#s').value=hs[1]*255;
$('#l').value=hs[2]*255;
}
function rgb2hsl(r,g,b) {
let a=Math.max(r,g,b), n=a-Math.min(r,g,b), f=(1-Math.abs(a+a-n-1));
let h= n && ((a==r) ? (g-b)/n : ((a==g) ? 2+(b-r)/n : 4+(r-g)/n));
return [60*(h<0?h+6:h), f ? n/f : 0, (a+a-n)/2];
}
refresh();
.box {
width: 50px;
height: 50px;
margin: 20px;
}
body {
display: flex;
}
<div>
<input id="r" type="range" min="0" max="255" oninput="changeRGB(0,event)">R<br>
<input id="g" type="range" min="0" max="255" oninput="changeRGB(1,event)">G<br>
<input id="b" type="range" min="0" max="255" oninput="changeRGB(2,event)">B<br>
<pre class="infoRGB"></pre>
</div>
<div>
<div class="box hsl"></div>
</div>
<div>
<input id="h" type="range" min="0" max="360" oninput="changeHS(0,event)">H<br>
<input id="s" type="range" min="0" max="255" oninput="changeHS(1,event)">S<br>
<input id="l" type="range" min="0" max="255" oninput="changeHS(2,event)">L<br>
<pre class="infoHS"></pre><br>
</div>
Here is formula which I discover and precisely describe in wiki + error analysis,
The article for HSL and HSV on wikipedia contains some formulas. The calculations are a bit tricky, so it might be useful to take a look at existing implementations.
If you're looking for something that definitely conforms with the CSS semantics for HSL and RGB, you could use the algorithm specified in the CSS 3 specification, which reads:
HOW TO RETURN hsl.to.rgb(h, s, l):
SELECT:
l<=0.5: PUT l*(s+1) IN m2
ELSE: PUT l+s-l*s IN m2
PUT l*2-m2 IN m1
PUT hue.to.rgb(m1, m2, h+1/3) IN r
PUT hue.to.rgb(m1, m2, h ) IN g
PUT hue.to.rgb(m1, m2, h-1/3) IN b
RETURN (r, g, b)
HOW TO RETURN hue.to.rgb(m1, m2, h):
IF h<0: PUT h+1 IN h
IF h>1: PUT h-1 IN h
IF h*6<1: RETURN m1+(m2-m1)*h*6
IF h*2<1: RETURN m2
IF h*3<2: RETURN m1+(m2-m1)*(2/3-h)*6
RETURN m1
I believe this is the source for some of the other answers here.
C# Code from Mohsen's answer.
Here is the code from Mohsen's answer in C# if anyone else wants it. Note: Color is a custom class and Vector4 is from OpenTK. Both are easy to replace with something else of your choosing.
Hsl To Rgba
/// <summary>
/// Converts an HSL color value to RGB.
/// Input: Vector4 ( X: [0.0, 1.0], Y: [0.0, 1.0], Z: [0.0, 1.0], W: [0.0, 1.0] )
/// Output: Color ( R: [0, 255], G: [0, 255], B: [0, 255], A: [0, 255] )
/// </summary>
/// <param name="hsl">Vector4 defining X = h, Y = s, Z = l, W = a. Ranges [0, 1.0]</param>
/// <returns>RGBA Color. Ranges [0, 255]</returns>
public static Color HslToRgba(Vector4 hsl)
{
float r, g, b;
if (hsl.Y == 0.0f)
r = g = b = hsl.Z;
else
{
var q = hsl.Z < 0.5f ? hsl.Z * (1.0f + hsl.Y) : hsl.Z + hsl.Y - hsl.Z * hsl.Y;
var p = 2.0f * hsl.Z - q;
r = HueToRgb(p, q, hsl.X + 1.0f / 3.0f);
g = HueToRgb(p, q, hsl.X);
b = HueToRgb(p, q, hsl.X - 1.0f / 3.0f);
}
return new Color((int)(r * 255), (int)(g * 255), (int)(b * 255), (int)(hsl.W * 255));
}
// Helper for HslToRgba
private static float HueToRgb(float p, float q, float t)
{
if (t < 0.0f) t += 1.0f;
if (t > 1.0f) t -= 1.0f;
if (t < 1.0f / 6.0f) return p + (q - p) * 6.0f * t;
if (t < 1.0f / 2.0f) return q;
if (t < 2.0f / 3.0f) return p + (q - p) * (2.0f / 3.0f - t) * 6.0f;
return p;
}
Rgba To Hsl
/// <summary>
/// Converts an RGB color value to HSL.
/// Input: Color ( R: [0, 255], G: [0, 255], B: [0, 255], A: [0, 255] )
/// Output: Vector4 ( X: [0.0, 1.0], Y: [0.0, 1.0], Z: [0.0, 1.0], W: [0.0, 1.0] )
/// </summary>
/// <param name="rgba"></param>
/// <returns></returns>
public static Vector4 RgbaToHsl(Color rgba)
{
float r = rgba.R / 255.0f;
float g = rgba.G / 255.0f;
float b = rgba.B / 255.0f;
float max = (r > g && r > b) ? r : (g > b) ? g : b;
float min = (r < g && r < b) ? r : (g < b) ? g : b;
float h, s, l;
h = s = l = (max + min) / 2.0f;
if (max == min)
h = s = 0.0f;
else
{
float d = max - min;
s = (l > 0.5f) ? d / (2.0f - max - min) : d / (max + min);
if (r > g && r > b)
h = (g - b) / d + (g < b ? 6.0f : 0.0f);
else if (g > b)
h = (b - r) / d + 2.0f;
else
h = (r - g) / d + 4.0f;
h /= 6.0f;
}
return new Vector4(h, s, l, rgba.A / 255.0f);
}
This is how I do it which is easy to remember is to think of RGB as three spokes on a wheel, 120 degrees apart.
H = hue (0-360)
S = saturation (0-1)
L = luminance (0-1)
R1 = SIN( H ) * L
G1 = SIN( H + 120 ) * L
B1 = SIN( H + 240 ) * L
The tricky part is saturation, which is to a scale down to the average of those three.
AVERAGE = (R1 + G1 + B1) / 3
R2 = ((R1 - AVERAGE) * S) + AVERAGE
G2 = ((G1 - AVERAGE) * S) + AVERAGE
B2 = ((B1 - AVERAGE) * S) + AVERAGE
RED = R2 * 255
GREEN = G2 * 255
BLUE = B2 * 255
#Php Implementation of Chris's C# Code
Also from here, which explains the math of it very well.
This is basically a bunch of functions to convert to and from HSL (Hue Saturation Lightness)
Tested and working on PHP 5.6.15
TL;DR: The full code can be found here on Pastebin.
##Hex to HSL
Input: Hex color in format: [#]0f4 or [#]00ff44 (pound sign optional)
Output: HSL in Degrees, Percent, Percent
/**
* Input: hex color
* Output: hsl(in ranges from 0-1)
*
* Takes the hex, converts it to RGB, and sends
* it to RGBToHsl. Returns the output.
*
*/
function hexToHsl($hex) {
$r = "";
$g = "";
$b = "";
$hex = str_replace('#', '', $hex);
if (strlen($hex) == 3) {
$r = substr($hex, 0, 1);
$r = $r . $r;
$g = substr($hex, 1, 1);
$g = $g . $g;
$b = substr($hex, 2, 1);
$b = $b . $b;
} elseif (strlen($hex) == 6) {
$r = substr($hex, 0, 2);
$g = substr($hex, 2, 2);
$b = substr($hex, 4, 2);
} else {
return false;
}
$r = hexdec($r);
$g = hexdec($g);
$b = hexdec($b);
$hsl = rgbToHsl($r,$g,$b);
return $hsl;
}
RGB to HSL
Input: RGB in range 0-255
Output: HSL in Degrees, Percent, Percent.
/**
*
*Credits:
* https://stackoverflow.com/questions/4793729/rgb-to-hsl-and-back-calculation-problems
* http://www.niwa.nu/2013/05/math-behind-colorspace-conversions-rgb-hsl/
*
* Called by hexToHsl by default.
*
* Converts an RGB color value to HSL. Conversion formula
* adapted from http://www.niwa.nu/2013/05/math-behind-colorspace-conversions-rgb-hsl/.
* Assumes r, g, and b are contained in the range [0 - 255] and
* returns h, s, and l in the format Degrees, Percent, Percent.
*
* #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){
//For the calculation, rgb needs to be in the range from 0 to 1. To convert, divide by 255 (ff).
$r /= 255;
$g /= 255;
$b /= 255;
$myMax = max($r, $g, $b);
$myMin = min($r, $g, $b);
$maxAdd = ($myMax + $myMin);
$maxSub = ($myMax - $myMin);
//luminence is (max + min)/2
$h = 0;
$s = 0;
$l = ($maxAdd / 2.0);
//if all the numbers are equal, there is no saturation (greyscale).
if($myMin != $myMax){
if ($l < 0.5) {
$s = ($maxSub / $maxAdd);
} else {
$s = (2.0 - $myMax - $myMin); //note order of opperations - can't use $maxSub here
$s = ($maxSub / $s);
}
//find hue
switch($myMax){
case $r:
$h = ($g - $b);
$h = ($h / $maxSub);
break;
case $g:
$h = ($b - $r);
$h = ($h / $maxSub);
$h = ($h + 2.0);
break;
case $b:
$h = ($r - $g);
$h = ($h / $maxSub);
$h = ($h + 4.0);
break;
}
}
$hsl = hslToDegPercPerc($h, $s, $l);
return $hsl;
}
##HSL (0-1 range) to Degrees, Percent, Percent format
For the math calculations, HSL is easier to deal with in the 0-1 range, but for human readability, it's easier in Degrees, Percent, Percent. This function takes HSL in the ranges 0-1, and returns HSL in Degrees, Percent, Percent.
/**
* Input: HSL in ranges 0-1.
* Output: HSL in format Deg, Perc, Perc.
*
* Note: rgbToHsl calls this function by default.
*
* Multiplies $h by 60, and $s and $l by 100.
*/
function hslToDegPercPerc($h, $s, $l) {
//convert h to degrees
$h *= 60;
if ($h < 0) {
$h += 360;
}
//convert s and l to percentage
$s *= 100;
$l *= 100;
$hsl['h'] = $h;
$hsl['s'] = $s;
$hsl['l'] = $l;
return $hsl;
}
##HSL (Degrees, Percent, Percent format) to HSL in range 0-1
This function converts HSL in the format Degrees, Percent, Percent, to the ranges 0-1 for easier computing.
/**
* Input: HSL in format Deg, Perc, Perc
* Output: An array containing HSL in ranges 0-1
*
* Divides $h by 60, and $s and $l by 100.
*
* hslToRgb calls this by default.
*/
function degPercPercToHsl($h, $s, $l) {
//convert h, s, and l back to the 0-1 range
//convert the hue's 360 degrees in a circle to 1
$h /= 360;
//convert the saturation and lightness to the 0-1
//range by multiplying by 100
$s /= 100;
$l /= 100;
$hsl['h'] = $h;
$hsl['s'] = $s;
$hsl['l'] = $l;
return $hsl;
}
##HSL to RGB
Input: HSL in the format Degrees, Percent, Percent
Output: RGB in the format 255, 255, 255.
/**
* Converts an HSL color value to RGB. Conversion formula
* adapted from http://www.niwa.nu/2013/05/math-behind-colorspace-conversions-rgb-hsl/.
* Assumes h, s, and l are in the format Degrees,
* Percent, Percent, and returns r, g, and b in
* the range [0 - 255].
*
* Called by hslToHex by default.
*
* Calls:
* degPercPercToHsl
* hueToRgb
*
* #param Number h The hue value
* #param Number s The saturation level
* #param Number l The luminence
* #return Array The RGB representation
*/
function hslToRgb($h, $s, $l){
$hsl = degPercPercToHsl($h, $s, $l);
$h = $hsl['h'];
$s = $hsl['s'];
$l = $hsl['l'];
//If there's no saturation, the color is a greyscale,
//so all three RGB values can be set to the lightness.
//(Hue doesn't matter, because it's grey, not color)
if ($s == 0) {
$r = $l * 255;
$g = $l * 255;
$b = $l * 255;
}
else {
//calculate some temperary variables to make the
//calculation eaisier.
if ($l < 0.5) {
$temp2 = $l * (1 + $s);
} else {
$temp2 = ($l + $s) - ($s * $l);
}
$temp1 = 2 * $l - $temp2;
//run the calculated vars through hueToRgb to
//calculate the RGB value. Note that for the Red
//value, we add a third (120 degrees), to adjust
//the hue to the correct section of the circle for
//red. Simalarly, for blue, we subtract 1/3.
$r = 255 * hueToRgb($temp1, $temp2, $h + (1 / 3));
$g = 255 * hueToRgb($temp1, $temp2, $h);
$b = 255 * hueToRgb($temp1, $temp2, $h - (1 / 3));
}
$rgb['r'] = $r;
$rgb['g'] = $g;
$rgb['b'] = $b;
return $rgb;
}
###Hue to RGB
This function is called by hslToRgb to convert the hue into the separate RGB values.
/**
* Converts an HSL hue to it's RGB value.
*
* Input: $temp1 and $temp2 - temperary vars based on
* whether the lumanence is less than 0.5, and
* calculated using the saturation and luminence
* values.
* $hue - the hue (to be converted to an RGB
* value) For red, add 1/3 to the hue, green
* leave it alone, and blue you subtract 1/3
* from the hue.
*
* Output: One RGB value.
*
* Thanks to Easy RGB for this function (Hue_2_RGB).
* http://www.easyrgb.com/index.php?X=MATH&$h=19#text19
*
*/
function hueToRgb($temp1, $temp2, $hue) {
if ($hue < 0) {
$hue += 1;
}
if ($hue > 1) {
$hue -= 1;
}
if ((6 * $hue) < 1 ) {
return ($temp1 + ($temp2 - $temp1) * 6 * $hue);
} elseif ((2 * $hue) < 1 ) {
return $temp2;
} elseif ((3 * $hue) < 2 ) {
return ($temp1 + ($temp2 - $temp1) * ((2 / 3) - $hue) * 6);
}
return $temp1;
}
##HSL to Hex
Input: HSL in format Degrees, Percent, Percent
Output: Hex in format 00ff22 (no pound sign).
Converts to RGB, then converts separately to hex.
/**
* Converts HSL to Hex by converting it to
* RGB, then converting that to hex.
*
* string hslToHex($h, $s, $l[, $prependPound = true]
*
* $h is the Degrees value of the Hue
* $s is the Percentage value of the Saturation
* $l is the Percentage value of the Lightness
* $prependPound is a bool, whether you want a pound
* sign prepended. (optional - default=true)
*
* Calls:
* hslToRgb
*
* Output: Hex in the format: #00ff88 (with
* pound sign). Rounded to the nearest whole
* number.
*/
function hslToHex($h, $s, $l, $prependPound = true) {
//convert hsl to rgb
$rgb = hslToRgb($h,$s,$l);
//convert rgb to hex
$hexR = $rgb['r'];
$hexG = $rgb['g'];
$hexB = $rgb['b'];
//round to the nearest whole number
$hexR = round($hexR);
$hexG = round($hexG);
$hexB = round($hexB);
//convert to hex
$hexR = dechex($hexR);
$hexG = dechex($hexG);
$hexB = dechex($hexB);
//check for a non-two string length
//if it's 1, we can just prepend a
//0, but if it is anything else non-2,
//it must return false, as we don't
//know what format it is in.
if (strlen($hexR) != 2) {
if (strlen($hexR) == 1) {
//probably in format #0f4, etc.
$hexR = "0" . $hexR;
} else {
//unknown format
return false;
}
}
if (strlen($hexG) != 2) {
if (strlen($hexG) == 1) {
$hexG = "0" . $hexG;
} else {
return false;
}
}
if (strlen($hexB) != 2) {
if (strlen($hexB) == 1) {
$hexB = "0" . $hexB;
} else {
return false;
}
}
//if prependPound is set, will prepend a
//# sign to the beginning of the hex code.
//(default = true)
$hex = "";
if ($prependPound) {
$hex = "#";
}
$hex = $hex . $hexR . $hexG . $hexB;
return $hex;
}
Here's a fast, super-simple, branchless version in GLSL:
vec3 hsl2rgb( vec3 c ) {
vec3 rgb = clamp(abs(mod(c.x*6.0 + vec3(0.0, 4.0, 2.0), 6.0)-3.0)-1.0, 0.0, 1.0);
return c.z + c.y * (rgb-0.5)*(1.0-abs(2.0*c.z-1.0));
}
Doesn't get much shorter than that ~
Link to the original proof-of-concept: https://www.shadertoy.com/view/XljGzV
(Disclaimer: not my code!)
Here is the modified javascript function, it outputs Hue in set 0-360 degrees.
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 ; break;
case g: h = 2 + ( (b - r) / d); break;
case b: h = 4 + ( (r - g) / d); break;
}
h*=60;
if (h < 0) h +=360;
}
return([h, s, l]);
}
alert(rgbToHsl(125,115,145));
I got this from Brandon Mathis' HSL Picker source code.
It was originally written in CoffeeScript. I converted it to JavaScript using an online converter, and took out the mechanism to verify the user input was a valid RGB value. This answer worked for my usecase, as the most up-voted answer on this post I found to not produce a valid HSL value.
Note that it returns an hsla value, with a representing opacity/transparency. 0 is completely transparent, and 1 fully opaque.
function rgbToHsl(rgb) {
var a, add, b, diff, g, h, hue, l, lum, max, min, r, s, sat;
r = parseFloat(rgb[0]) / 255;
g = parseFloat(rgb[1]) / 255;
b = parseFloat(rgb[2]) / 255;
max = Math.max(r, g, b);
min = Math.min(r, g, b);
diff = max - min;
add = max + min;
hue = min === max ? 0 : r === max ? ((60 * (g - b) / diff) + 360) % 360 : g === max ? (60 * (b - r) / diff) + 120 : (60 * (r - g) / diff) + 240;
lum = 0.5 * add;
sat = lum === 0 ? 0 : lum === 1 ? 1 : lum <= 0.5 ? diff / add : diff / (2 - add);
h = Math.round(hue);
s = Math.round(sat * 100);
l = Math.round(lum * 100);
a = parseFloat(rgb[3]) || 1;
return [h, s, l, a];
}
An hsl|a color value, set in javascript, will be instantly
converted to rgb|a All you need to do then is access the
computed style value
document.body.style.color = 'hsla(44, 100%, 50%, 0.8)';
console.log(window.getComputedStyle(document.body).color);
// displays: rgba(255, 187, 0, 0.8)
Technically, I guess, this isn't even any lines of code - it's
just done automatically. So, depending on your environment, you
might be able to get away with just this. Not that there aren't
a lot of very thoughtful responses here. I don't know what your
goal is.
Now, what if you want to convert from rbg|a to hsl|a?
HSL to RGB in Typescript
All the options above didn't work on my code in TS.
I tweak one of those and now it works as a charm:
type HslType = { h: number; s: number; l: number }
const hslToRgb = (hsl: HslType): RgbType => {
let { h, s, l } = hsl
// IMPORTANT if s and l between 0,1 remove the next two lines:
s /= 100
l /= 100
const k = (n: number) => (n + h / 30) % 12
const a = s * Math.min(l, 1 - l)
const f = (n: number) =>
l - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1)))
return {
r: Math.round(255 * f(0)),
g: Math.round(255 * f(8)),
b: Math.round(255 * f(4)),
}
}
With H, S,and L in [0,1] range:
ConvertHslToRgb: function (iHsl)
{
var min, sv, sextant, fract, vsf;
var v = (iHsl.l <= 0.5) ? (iHsl.l * (1 + iHsl.s)) : (iHsl.l + iHsl.s - iHsl.l * iHsl.s);
if (v === 0)
return { Red: 0, Green: 0, Blue: 0 };
min = 2 * iHsl.l - v;
sv = (v - min) / v;
var h = (6 * iHsl.h) % 6;
sextant = Math.floor(h);
fract = h - sextant;
vsf = v * sv * fract;
switch (sextant)
{
case 0: return { r: v, g: min + vsf, b: min };
case 1: return { r: v - vsf, g: v, b: min };
case 2: return { r: min, g: v, b: min + vsf };
case 3: return { r: min, g: v - vsf, b: v };
case 4: return { r: min + vsf, g: min, b: v };
case 5: return { r: v, g: min, b: v - vsf };
}
}
For when you need RGB to HSV and vice versa instead:
function rgbToHsv(r, g, b)
{
r /= 255, g /= 255, b /= 255;
var min = Math.min(r, g, b),
max = Math.max(r, g, b),
delta = max - min,
h = 0, s = 0, v = max;
if (min != max)
{
s = (delta / max);
switch (max)
{
case r: h = (g - b) / delta + (g < b ? 6 : 0); break;
case g: h = (b - r) / delta + 2; break;
case b: h = (r - g) / delta + 4; break;
}
h /= 6;
}
return [h, s, v];
}
function hsvToRgb(h, s, v)
{
var step = h / (1 / 6),
pos = step - Math.floor(step), // the hue position within the current step
m = (Math.floor(step) % 2) ? (1 - pos) * v : pos * v, // mix color value adjusted to the brightness(v)
max = 1 * v,
min = (1 - s) * v,
med = m + ((1 - s) * (v - m)),
r, g, b;
switch (Math.floor(step))
{
case 0:
r = max;
g = med;
b = min;
break;
case 1:
r = med;
g = max;
b = min;
break;
case 2:
r = min;
g = max;
b = med;
break;
case 3:
r = min;
g = med;
b = max;
break;
case 4:
r = med;
g = min;
b = max;
break;
case 5:
r = max;
g = min;
b = med;
break;
}
return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
}
Unity3D C# Code from Mohsen's answer.
Here is the code from Mohsen's answer in C# targeted specifically for Unity3D. It was adapted from the C# answer given by Alec Thilenius above.
using UnityEngine;
using System.Collections;
public class ColorTools {
/// <summary>
/// Converts an HSL color value to RGB.
/// Input: Vector4 ( X: [0.0, 1.0], Y: [0.0, 1.0], Z: [0.0, 1.0], W: [0.0, 1.0] )**strong text**
/// Output: Color ( R: [0.0, 1.0], G: [0.0, 1.0], B: [0.0, 1.0], A: [0.0, 1.0] )
/// </summary>
/// <param name="hsl">Vector4 defining X = h, Y = s, Z = l, W = a. Ranges [0, 1.0]</param>
/// <returns>RGBA Color. Ranges [0.0, 1.0]</returns>
public static Color HslToRgba(Vector4 hsl)
{
float r, g, b;
if (hsl.y == 0.0f)
r = g = b = hsl.z;
else
{
var q = hsl.z < 0.5f ? hsl.z * (1.0f + hsl.y) : hsl.z + hsl.y - hsl.z * hsl.y;
var p = 2.0f * hsl.z - q;
r = HueToRgb(p, q, hsl.x + 1.0f / 3.0f);
g = HueToRgb(p, q, hsl.x);
b = HueToRgb(p, q, hsl.x - 1.0f / 3.0f);
}
return new Color(r, g, b, hsl.w);
}
// Helper for HslToRgba
private static float HueToRgb(float p, float q, float t)
{
if (t < 0.0f) t += 1.0f;
if (t > 1.0f) t -= 1.0f;
if (t < 1.0f / 6.0f) return p + (q - p) * 6.0f * t;
if (t < 1.0f / 2.0f) return q;
if (t < 2.0f / 3.0f) return p + (q - p) * (2.0f / 3.0f - t) * 6.0f;
return p;
}
/// <summary>
/// Converts an RGB color value to HSL.
/// Input: Color ( R: [0.0, 1.0], G: [0.0, 1.0], B: [0.0, 1.0], A: [0.0, 1.0] )
/// Output: Vector4 ( X: [0.0, 1.0], Y: [0.0, 1.0], Z: [0.0, 1.0], W: [0.0, 1.0] )
/// </summary>
/// <param name="rgba"></param>
/// <returns></returns>
public static Vector4 RgbaToHsl(Color rgba)
{
float max = (rgba.r > rgba.g && rgba.r > rgba.b) ? rgba.r :
(rgba.g > rgba.b) ? rgba.g : rgba.b;
float min = (rgba.r < rgba.g && rgba.r < rgba.b) ? rgba.r :
(rgba.g < rgba.b) ? rgba.g : rgba.b;
float h, s, l;
h = s = l = (max + min) / 2.0f;
if (max == min)
h = s = 0.0f;
else
{
float d = max - min;
s = (l > 0.5f) ? d / (2.0f - max - min) : d / (max + min);
if (rgba.r > rgba.g && rgba.r > rgba.b)
h = (rgba.g - rgba.b) / d + (rgba.g < rgba.b ? 6.0f : 0.0f);
else if (rgba.g > rgba.b)
h = (rgba.b - rgba.r) / d + 2.0f;
else
h = (rgba.r - rgba.g) / d + 4.0f;
h /= 6.0f;
}
return new Vector4(h, s, l, rgba.a);
}
}
For all who said that Garry Tan solution converting incorrect from RGB to HSL and back. It because he left out fraction part of number in his code.
I corrected his code (javascript).
Sorry for link on russian languadge, but on english absent - HSL-wiki
function toHsl(r, g, b)
{
r /= 255.0;
g /= 255.0;
b /= 255.0;
var max = Math.max(r, g, b);
var min = Math.min(r, g, b);
var h, s, l = (max + min) / 2.0;
if(max == min)
{
h = s = 0;
}
else
{
var d = max - min;
s = (l > 0.5 ? d / (2.0 - max - min) : d / (max + min));
if(max == r && g >= b)
{
h = 1.0472 * (g - b) / d ;
}
else if(max == r && g < b)
{
h = 1.0472 * (g - b) / d + 6.2832;
}
else if(max == g)
{
h = 1.0472 * (b - r) / d + 2.0944;
}
else if(max == b)
{
h = 1.0472 * (r - g) / d + 4.1888;
}
}
return {
str: 'hsl(' + parseInt(h / 6.2832 * 360.0 + 0.5) + ',' + parseInt(s * 100.0 + 0.5) + '%,' + parseInt(l * 100.0 + 0.5) + '%)',
obj: { h: parseInt(h / 6.2832 * 360.0 + 0.5), s: parseInt(s * 100.0 + 0.5), l: parseInt(l * 100.0 + 0.5) }
};
};
PHP implementation of #Mohsen's code (including Test!)
Sorry to re-post this. But I really haven't seen any other implementation that gives the quality I needed.
/**
* 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 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;
}
function hslToRgb($h, $s, $l){
if($s == 0){
$r = $l;
$g = $l;
$b = $l; // achromatic
}else{
$q = $l < 0.5 ? $l * (1 + $s) : $l + $s - $l * $s;
$p = 2 * $l - $q;
$r = hue2rgb($p, $q, $h + 1/3);
$g = hue2rgb($p, $q, $h);
$b = hue2rgb($p, $q, $h - 1/3);
}
return array(round($r * 255), round($g * 255), round($b * 255));
}
/* Uncomment to test * /
for ($i=0;$i<360;$i++) {
$rgb=hslToRgb($i/360, 1, .9);
echo '<div style="background-color:rgb(' .$rgb[0] . ', ' . $rgb[1] . ', ' . $rgb[2] . ');padding:2px;"></div>';
}
/* End Test */
C++ implementation with probably better performance than #Mohsen code. It uses a [0-6] range for the hue, avoiding the division and multiplication by 6. S and L range is [0,1]
void fromRGBtoHSL(float rgb[], float hsl[])
{
const float maxRGB = max(rgb[0], max(rgb[1], rgb[2]));
const float minRGB = min(rgb[0], min(rgb[1], rgb[2]));
const float delta2 = maxRGB + minRGB;
hsl[2] = delta2 * 0.5f;
const float delta = maxRGB - minRGB;
if (delta < FLT_MIN)
hsl[0] = hsl[1] = 0.0f;
else
{
hsl[1] = delta / (hsl[2] > 0.5f ? 2.0f - delta2 : delta2);
if (rgb[0] >= maxRGB)
{
hsl[0] = (rgb[1] - rgb[2]) / delta;
if (hsl[0] < 0.0f)
hsl[0] += 6.0f;
}
else if (rgb[1] >= maxRGB)
hsl[0] = 2.0f + (rgb[2] - rgb[0]) / delta;
else
hsl[0] = 4.0f + (rgb[0] - rgb[1]) / delta;
}
}
void fromHSLtoRGB(const float hsl[], float rgb[])
{
if(hsl[1] < FLT_MIN)
rgb[0] = rgb[1] = rgb[2] = hsl[2];
else if(hsl[2] < FLT_MIN)
rgb[0] = rgb[1] = rgb[2] = 0.0f;
else
{
const float q = hsl[2] < 0.5f ? hsl[2] * (1.0f + hsl[1]) : hsl[2] + hsl[1] - hsl[2] * hsl[1];
const float p = 2.0f * hsl[2] - q;
float t[] = {hsl[0] + 2.0f, hsl[0], hsl[0] - 2.0f};
for(int i=0; i<3; ++i)
{
if(t[i] < 0.0f)
t[i] += 6.0f;
else if(t[i] > 6.0f)
t[i] -= 6.0f;
if(t[i] < 1.0f)
rgb[i] = p + (q - p) * t[i];
else if(t[i] < 3.0f)
rgb[i] = q;
else if(t[i] < 4.0f)
rgb[i] = p + (q - p) * (4.0f - t[i]);
else
rgb[i] = p;
}
}
}
Java version:
/*
Converts color from HSL/A format to RGB/A format
0 <= h <= 360
0 <= s, l, a <= 1
Based on: https://en.wikipedia.org/wiki/HSL_and_HSV#:~:text=%5Bedit%5D-,HSL%20to%20RGB%5Bedit%5D,-Given%20a%20color
*/
public RGB toRGB(double h, double s, double l, Double a) {
double c = (1 - Math.abs(2 * l - 1)) * s;
double x = c * (1 - Math.abs((h / 60) % 2 - 1));
double m = l - c / 2;
int[] RGBTag = Arrays.stream(getRGBTag(c, x)).mapToInt(e -> (int)Math.round((e + m) * 255)).toArray();
return RGB(RGBTag[0], RGBTag[1], RGBTag[2], a);
}
private double[] getRGBTag(double c, double x) {
if (h < 60) {
return new double[] {c, x, 0};
} else if (h < 120) {
return new double[] {x, c, 0};
} else if (h < 180) {
return new double[] {0, c, x};
} else if (h < 240) {
return new double[] {0, x, c};
} else if (h < 300) {
return new double[] {x, 0, c};
}
return new double[] {c, 0, x};
}
I needed a really light weight one, Its not 100%, but it gets close enough for some usecases.
float3 Hue(float h, float s, float l)
{
float r = max(cos(h * 2 * UNITY_PI) * 0.5 + 0.5, 0);
float g = max(cos((h + 0.666666) * 2 * UNITY_PI) * 0.5 + 0.5, 0);
float b = max(cos((h + 0.333333) * 2 * UNITY_PI) * 0.5 + 0.5, 0);
float gray = 0.2989 * r + 0.5870 * g + 0.1140 * b;
return lerp(gray, float3(r, g, b), s) * smoothstep(0, 0.5, l) + 1 * smoothstep(0.5, 1, l);
}
PHP - shortest but precise
Here I rewrite my JS answer (math details are there) to PHP - you can run it here
function hsl2rgb($h,$s,$l)
{
$a = $s * min($l, 1-$l);
$k = function($n,$h) { return ($n+$h/30)%12;};
$f = function($n) use ($h,$s,$l,$a,$k) {
return $l - $a * max( min($k($n,$h)-3, 9-$k($n,$h), 1),-1);
};
return [ $f(0), $f(8), $f(4) ];
}
Since colorsys isn't supported in (or currently ported to) circuitpython, if you're trying to handle this conversion on Raspberry Pi, the following works (subject to the rounding limitation to accuracy mentioned elsewhere in this thread):
def hslToRgb (h, s, l): #in range 0-1 for h,s,l
if s == 0:
r = g = b = l #achromatic
else:
def hue2rgb(p, q, t):
if t < 0: t += 1
if t > 1: t -= 1
if t < 1.0 / 6.0: return p + (q - p) * 6 * t
if t < 1.0 / 2.0: return q
if t < 2.0 / 3.0: return p + (q - p) * ((2.0 / 3.0) - t) * 6
return p
if l < 0.5:
q = l * (1 + s)
else:
q = l + s - l * s
p = 2 * l - q
r = hue2rgb(p, q, h + 1.0/3.0)
g = hue2rgb(p, q, h)
b = hue2rgb(p, q, h - 1.0/3.0)
return [round(r * 255), round(g * 255), round(b * 255)]