Problems converting HSL to RGB - algorithm

I am trying to convert HSL values to RGB but I am getting strange results, if the hue value is about 180 then the resulting RGB is negative dependent on the lightness value.
My implementation:
public class HSLtoRGB {
private static float hueToRGB(float m1, float m2, float h) {
if(h < 0) {
h += 1.0f;
} else if(h > 1.0f) {
h -= 1.0f;
}
if((h * 6) < 1) {
return m1 + (m2 - m1) * 6 * h;
} else if((h * 2) < 1) {
return m2;
} else if((h * 3) < 2) {
return m1 + (m2 - m1) * ((2 / 3) - h) * 6;
} else {
return m1;
}
}
public static void main(String[] args) {
float h = 180.0f / 360.0f;
float s = 100.0f / 100.0f;
float l = 38.0f / 100.0f;
float r = 0;
float g = 0;
float b = 0;
if(s == 0.0) {
r = g = b = l;
} else {
float m2 = l < 0.5 ? l * (1 + s) : (l + s) - (l * s);
float m1 = (l * 2) - m2;
r = hueToRGB(m1, m2, h + (1.0f / 3.0f));
g = hueToRGB(m1, m2, h);
b = hueToRGB(m1, m2, h - (1.0f / 3.0f));
}
System.out.printf("%.2f %.2f %.2f -> %.2f %.2f %.2f",
h, s, l,
r, g, b);
}
}
and the output from the above:
0.50 1.00 0.38 -> 0.00 -2.28 0.76
I followed this algorithm and checked many others to get the above formula, including the one in the CSS3 docs:
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

It looks like the culprit is this line:
return m1 + (m2 - m1) * ((2 / 3) - h) * 6;
You're doing integer division 2/3, which will equal 0. A simple change to:
return m1 + (m2 - m1) * ((2.0f / 3.0f) - h) * 6;
seems to fix it for me. It comes back with equal blue/green 0.76, which makes sense for hue 180(cyan).

Related

NURBS derivative using de Boor's algorithm

At the bottom of De Boor's Algorithm, it is said that
De Boor's algorithm also works for NURBS curves. We just multiply every control point by its weight converting the NURBS curve to a 4D B-spline curve, perform de Boor's algorithm on this 4D B-spline curve, and then project the resulting curve back by dividing the first three components with the fourth and keeping the fourth component as its new weight.
Then modifying the code from B-Spline derivative using de Boor's algorithm, I came up with the following.
import numpy as np
import math as m
weights = [0.3, 1, 1, 2, 1, 1, 0.5, 1, 1, 3, 1]
def deBoor(k, x, t, c_, p):
c = []
for point, w in zip(c_, weights):
c.append([point[0]*w, point[1]*w, point[2]*w, w])
c = np.array(c)
d = [c[j + k - p] for j in range(0, p+1)]
for r in range(1, p+1):
for j in range(p, r-1, -1):
alpha = (x - t[j+k-p]) / (t[j+1+k-r] - t[j+k-p])
d[j] = (1.0 - alpha) * d[j-1] + alpha * d[j]
return np.array([
d[p][0] / d[p][3],
d[p][1] / d[p][3],
d[p][2] / d[p][3]
])
def deBoorDerivative(k, x, t, c_, p):
c = []
for point, w in zip(c_, weights):
c.append([point[0]*w, point[1]*w, point[2]*w, w])
c = np.array(c)
q = [p * (c[j+k-p+1] - c[j+k-p]) / (t[j+k+1] - t[j+k-p+1]) for j in range(0, p)]
for r in range(1, p):
for j in range(p-1, r-1, -1):
right = j+1+k-r
left = j+k-(p-1)
alpha = (x - t[left]) / (t[right] - t[left])
q[j] = (1.0 - alpha) * q[j-1] + alpha * q[j]
return np.array([
q[p-1][0] / q[p-1][3],
q[p-1][1] / q[p-1][3],
q[p-1][2] / q[p-1][3]
])
def finiteDifferenceDerivative(k, x, t, c, p):
f = lambda xx : deBoor(k, xx, t, c, p)
dx = 1e-7
return (- f(x + 2 * dx) \
+ 8 * f(x + dx) \
- 8 * f(x - dx) \
+ f(x - 2 * dx)) / ( 12 * dx )
points = np.array([[i, m.sin(i / 3.0), m.cos(i / 2)] for i in range(0, 11)])
knots = np.array([0, 0, 0, 0, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 1.0, 1.0, 1.0, 1.0])
a = deBoorDerivative(7, 0.44, knots, points, 3)
b = finiteDifferenceDerivative(7, 0.44, knots, points, 3)
print(a)
print(b)
Although the derivative calculated from finite difference is not the same as the one when using deboors algorithm.
[ 9.125 1.02221755 -2.22839545]
[16.85238398 0.14138772 -5.90135073]
Solved it. This computes the derivative (velocity) and the position (point) at t at once using deboors algorithm, (written in C).
typedef struct vec3 { double x, y, z; } vec3_t;
typedef struct vec4 { double x, y, z, w; } vec4_t;
vec4_t vec4homo (vec3_t u, double w) { return (vec4_t){u.x * w, u.y * w, u.z * w, w }; }
vec4_t vec4add (vec4_t u, vec4_t v) { return (vec4_t){u.x + v.x, u.y + v.y, u.z + v.z, u.w + v.w}; }
vec4_t vec4sub (vec4_t u, vec4_t v) { return (vec4_t){u.x - v.x, u.y - v.y, u.z - v.z, u.w - v.w}; }
vec4_t vec4mul (vec4_t u, double s) { return (vec4_t){u.x * s, u.y * s, u.z * s, u.w * s }; }
vec4_t vec4div (vec4_t u, double s) { return (vec4_t){u.x / s, u.y / s, u.z / s, u.w / s }; }
vec3_t vec4trunc (vec4_t u) { return (vec3_t){u.x, u.y, u.z }; }
vec3_t vecadd (vec3_t u, vec3_t v) { return (vec3_t){u.x + v.x, u.y + v.y, u.z + v.z}; }
vec3_t vecsub (vec3_t u, vec3_t v) { return (vec3_t){u.x - v.x, u.y - v.y, u.z - v.z}; }
vec3_t vecmul (vec3_t u, double s) { return (vec3_t){u.x * s, u.y * s, u.z * s }; }
vec3_t vecdiv (vec3_t u, double s) { return (vec3_t){u.x / s, u.y / s, u.z / s }; }
typedef struct pv {
vec3_t position;
vec3_t velocity;
} pv_t;
typedef struct nurbs {
vec3_t P[100];
double w[100];
double U[100];
int p;
int m;
int n;
} nurbs_t;
int findspan(double* U, double t, int n, int p) {
if(t >= U[n]) { return n - 1; }
if(t <= U[p]) { return p; }
int low = p;
int high = n;
int mid = (low + high) / 2;
while(t < U[mid] || t >= U[mid+1]) {
if(t < U[mid]) { high = mid; }
else { low = mid; }
mid = (low + high) / 2;
}
return mid;
}
pv_t nurbs_deboor(double t, nurbs_t* func) {
vec3_t* P = func->P;
double* U = func->U;
double* w = func->w;
int p = func->p;
int m = func->m;
int n = func->n;
int k = findspan(U, t, n, p);
vec4_t d[30];
vec4_t q[30];
for(int i = 0; i < p + 1; i++) {
d[i] = vec4homo(P[i+k-p], w[i+k-p]);
if(!(i < p)) { continue; }
q[i] = vec4mul(vec4sub(vec4homo(P[i+k-p+1], w[i+k-p+1]), vec4homo(P[i+k-p], w[i+k-p])), p);
q[i] = vec4div(q[i], U[i+k+1] - U[i+k-p+1]);
}
for(int r = 1; r < p + 1; r++) {
for(int j = p; j > r - 1; j--) {
double alpha = (t - U[j+k-p]) / (U[j+1+k-r] - U[j+k-p]);
d[j] = vec4add(vec4mul(d[j-1], 1.0-alpha), vec4mul(d[j], alpha));
if(!(r < p && j < p)) { continue; }
alpha = (t - U[j+k-p+1]) / (U[j+1+k-r] - U[j+k-p+1]);
q[j] = vec4add(vec4mul(q[j-1], 1.0-alpha), vec4mul(q[j], alpha));
}
}
pv_t pv;
pv.position = vecdiv(vec4trunc(d[p]), d[p].w);
pv.velocity = vecdiv(vecsub(vec4trunc(q[p-1]), vecmul(pv.position, q[p-1].w)), d[p].w);
return pv;
}

The Two Water Jug Puzzle

I am trying to solve the two WATER JUG PUZZLE using euclidean algorithm and Diophantine equation.
let gcd(m,n) = g
using euclidean aldortihm we get X' and Y' such that mX' + nY' = g
for mX + nY = d
if d%g!= 0 no solution exists
else i made X' as X' / g * d and Y' as Y' / g * d
this is one solution for mX + nY = d
now multiple solutions by m ( X' + ( k * n / g ) ) + n ( Y' - ( m * k / g ) ) = d
i just needed to output the SUM OF NO. OPERATIONS
so, i think of the solution as X' + Y' + k * ( n - m ) / g and i want to minimise this
my code below for the same (its giving wrong answers...)
int X, Y;
int gcd(int a, int b)
{
if (b == 0)
{
X = 1;
Y = 0;
return a;
}
int g = gcd(b, a % b);
int X1 = Y;
int Y1 = X - (a / b) * Y;
X = X1;
Y = Y1;
return g;
}
cin >> m >> n >> d;
int g = gcd(n, m);
if (d % g)
cout << -1 << endl;
else
{
X = X / g * d;
Y = Y / g * d;
int ans = X + Y;
int mn = ans;
while (ans > 0)
{
ans += ((m - n) / g);
mn = min(ans, mn);
}
while (ans < 10000)
{
ans += ((n - m) / g);
mn = min(ans, mn);
}
cout << mn << endl;
}

HSL to RGB conversion help needed

This may be a very stupid question but just wanted to clarify my issue.
Having seen a few different algorithms for conversion between RGB & HSL, I have seen var_1 and var_2. I was just wondering what these variables actually are?
I knew they'd show up as errors just wanted to fully understand why and where I need to make changes for the conversion.
void HSLToRGB( int H, int S, int L, int& R, int& G, int& B )
{
H = 240;
S = 47;
L = 58;
if (S == 0)
{
R = L * 255
G = L * 255
B = L * 255
}
else
{
if (L < 0.5) { var_2 = L * (1 + S); }
else
{
var_2 = (L + S) - (S * L);
var_1 = 2 * L - var_2;
}
var_1 = 2 * L- var_2;
R = 255 * Hue_2_RGB(var_1, var_2, H + (1 / 3));
G = 255 * Hue_2_RGB(var_1, var_2, H);
B = 255 * Hue_2_RGB(var_1, var_2, H - (1 / 3));
}
static float Hue_2_RGB(float v1, float v2, float vH)
{
if (vH < 0) { vH += 1; }
if (vH > 1) { vH -= 1; }
if ((6 * vH) < 1) { return (v1 + (v2 - v1) * 6 * vH); }
if ((2 * vH) < 1) {return (v2); }
if ((3 * vH) < 2)
{
return (v1 + (v2 - v1) * ((2 / 3) - vH) * 6);
return (v1);
}
}
Sorry if this is a dumb question just wanted to 100% understand in laymans term.
Thank you.
I also have not included the code for RGB to HSL conversion.
There is no way to know since they are not defined in the code you show. We can guess, based on what the other values are that are defined, but that is no guarantee either. I presume this is C which would mean they are possibly int.
I would make them int if you just want to try it and see if it compiles. If it compiles, then you can dig more into the algorithm to see what works and how far it goes.

HSL to RGB color conversion

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)]

An algorithm to find bounding box of closed bezier curves?

I'm looking for an algorithm to find bounding box (max/min points) of a closed quadratic bezier curve in Cartesian axis:
input: C (a closed bezier curve)
output: A B C D points
Image http://www.imagechicken.com/uploads/1270586513022388700.jpg
Note: above image shows a smooth curve. it could be not smooth. (have corners)
Ivan Kuckir's DeCasteljau is a brute force, but works in many cases. The problem with it is the count of iterations. The actual shape and the distance between coordinates affect to the precision of the result. And to find a precise enough answer, you have to iterate tens of times, may be more. And it may fail if there are sharp turns in curve.
Better solution is to find first derivative roots, as is described on the excellent site http://processingjs.nihongoresources.com/bezierinfo/. Please read the section Finding the extremities of the curves.
The link above has the algorithm for both quadratic and cubic curves.
The asker of question is interested in quadratic curves, so the rest of this answer may be irrelevant, because I provide codes for calculating extremities of Cubic curves.
Below are three Javascript codes of which the first (CODE 1) is the one I suggest to use.
** CODE 1 **
After testing processingjs and Raphael's solutions I find they had some restrictions and/or bugs. Then more search and found Bonsai and it's bounding box function, which is based on NISHIO Hirokazu's Python script. Both have a downside where double equality is tested using ==. When I changed these to numerically robust comparisons, then script succeeds 100% right in all cases. I tested the script with thousands of random paths and also with all collinear cases and all succeeded:
Various cubic curves
Random cubic curves
Collinear cubic curves
The code is as follows. Usually left, right, top and bottom values are the all needed, but in some cases it's fine to know the coordinates of local extreme points and corresponding t values. So I added there two variables: tvalues and points. Remove code regarding them and you have fast and stable bounding box calculation function.
// Source: http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html
// Original version: NISHIO Hirokazu
// Modifications: Timo
var pow = Math.pow,
sqrt = Math.sqrt,
min = Math.min,
max = Math.max;
abs = Math.abs;
function getBoundsOfCurve(x0, y0, x1, y1, x2, y2, x3, y3)
{
var tvalues = new Array();
var bounds = [new Array(), new Array()];
var points = new Array();
var a, b, c, t, t1, t2, b2ac, sqrtb2ac;
for (var i = 0; i < 2; ++i)
{
if (i == 0)
{
b = 6 * x0 - 12 * x1 + 6 * x2;
a = -3 * x0 + 9 * x1 - 9 * x2 + 3 * x3;
c = 3 * x1 - 3 * x0;
}
else
{
b = 6 * y0 - 12 * y1 + 6 * y2;
a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3;
c = 3 * y1 - 3 * y0;
}
if (abs(a) < 1e-12) // Numerical robustness
{
if (abs(b) < 1e-12) // Numerical robustness
{
continue;
}
t = -c / b;
if (0 < t && t < 1)
{
tvalues.push(t);
}
continue;
}
b2ac = b * b - 4 * c * a;
sqrtb2ac = sqrt(b2ac);
if (b2ac < 0)
{
continue;
}
t1 = (-b + sqrtb2ac) / (2 * a);
if (0 < t1 && t1 < 1)
{
tvalues.push(t1);
}
t2 = (-b - sqrtb2ac) / (2 * a);
if (0 < t2 && t2 < 1)
{
tvalues.push(t2);
}
}
var x, y, j = tvalues.length,
jlen = j,
mt;
while (j--)
{
t = tvalues[j];
mt = 1 - t;
x = (mt * mt * mt * x0) + (3 * mt * mt * t * x1) + (3 * mt * t * t * x2) + (t * t * t * x3);
bounds[0][j] = x;
y = (mt * mt * mt * y0) + (3 * mt * mt * t * y1) + (3 * mt * t * t * y2) + (t * t * t * y3);
bounds[1][j] = y;
points[j] = {
X: x,
Y: y
};
}
tvalues[jlen] = 0;
tvalues[jlen + 1] = 1;
points[jlen] = {
X: x0,
Y: y0
};
points[jlen + 1] = {
X: x3,
Y: y3
};
bounds[0][jlen] = x0;
bounds[1][jlen] = y0;
bounds[0][jlen + 1] = x3;
bounds[1][jlen + 1] = y3;
tvalues.length = bounds[0].length = bounds[1].length = points.length = jlen + 2;
return {
left: min.apply(null, bounds[0]),
top: min.apply(null, bounds[1]),
right: max.apply(null, bounds[0]),
bottom: max.apply(null, bounds[1]),
points: points, // local extremes
tvalues: tvalues // t values of local extremes
};
};
// Usage:
var bounds = getBoundsOfCurve(532,333,117,305,28,93,265,42);
console.log(JSON.stringify(bounds));
// Prints: {"left":135.77684049079755,"top":42,"right":532,"bottom":333,"points":[{"X":135.77684049079755,"Y":144.86387466397255},{"X":532,"Y":333},{"X":265,"Y":42}],"tvalues":[0.6365030674846626,0,1]}
CODE 2 (which fails in collinear cases):
I translated the code from http://processingjs.nihongoresources.com/bezierinfo/sketchsource.php?sketch=tightBoundsCubicBezier to Javascript. The code works fine in normal cases, but not in collinear cases where all points lie on the same line.
For reference, here is the Javascript code.
function computeCubicBaseValue(a,b,c,d,t) {
var mt = 1-t;
return mt*mt*mt*a + 3*mt*mt*t*b + 3*mt*t*t*c + t*t*t*d;
}
function computeCubicFirstDerivativeRoots(a,b,c,d) {
var ret = [-1,-1];
var tl = -a+2*b-c;
var tr = -Math.sqrt(-a*(c-d) + b*b - b*(c+d) +c*c);
var dn = -a+3*b-3*c+d;
if(dn!=0) { ret[0] = (tl+tr)/dn; ret[1] = (tl-tr)/dn; }
return ret;
}
function computeCubicBoundingBox(xa,ya,xb,yb,xc,yc,xd,yd)
{
// find the zero point for x and y in the derivatives
var minx = 9999;
var maxx = -9999;
if(xa<minx) { minx=xa; }
if(xa>maxx) { maxx=xa; }
if(xd<minx) { minx=xd; }
if(xd>maxx) { maxx=xd; }
var ts = computeCubicFirstDerivativeRoots(xa, xb, xc, xd);
for(var i=0; i<ts.length;i++) {
var t = ts[i];
if(t>=0 && t<=1) {
var x = computeCubicBaseValue(t, xa, xb, xc, xd);
var y = computeCubicBaseValue(t, ya, yb, yc, yd);
if(x<minx) { minx=x; }
if(x>maxx) { maxx=x; }}}
var miny = 9999;
var maxy = -9999;
if(ya<miny) { miny=ya; }
if(ya>maxy) { maxy=ya; }
if(yd<miny) { miny=yd; }
if(yd>maxy) { maxy=yd; }
ts = computeCubicFirstDerivativeRoots(ya, yb, yc, yd);
for(i=0; i<ts.length;i++) {
var t = ts[i];
if(t>=0 && t<=1) {
var x = computeCubicBaseValue(t, xa, xb, xc, xd);
var y = computeCubicBaseValue(t, ya, yb, yc, yd);
if(y<miny) { miny=y; }
if(y>maxy) { maxy=y; }}}
// bounding box corner coordinates
var bbox = [minx,miny, maxx,miny, maxx,maxy, minx,maxy ];
return bbox;
}
CODE 3 (works in most cases):
To handle also collinear cases, I found Raphael's solution, which is based on the same first derivative method as the CODE 2. I added also a return value dots, which has the extrema points, because always it's not enough to know bounding boxes min and max coordinates, but we want to know the exact extrema coordinates.
EDIT: found another bug. Fails eg. in 532,333,117,305,28,93,265,42 and also many other cases.
The code is here:
Array.max = function( array ){
return Math.max.apply( Math, array );
};
Array.min = function( array ){
return Math.min.apply( Math, array );
};
var findDotAtSegment = function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
var t1 = 1 - t;
return {
x: t1*t1*t1*p1x + t1*t1*3*t*c1x + t1*3*t*t * c2x + t*t*t * p2x,
y: t1*t1*t1*p1y + t1*t1*3*t*c1y + t1*3*t*t * c2y + t*t*t * p2y
};
};
var cubicBBox = function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
var a = (c2x - 2 * c1x + p1x) - (p2x - 2 * c2x + c1x),
b = 2 * (c1x - p1x) - 2 * (c2x - c1x),
c = p1x - c1x,
t1 = (-b + Math.sqrt(b * b - 4 * a * c)) / 2 / a,
t2 = (-b - Math.sqrt(b * b - 4 * a * c)) / 2 / a,
y = [p1y, p2y],
x = [p1x, p2x],
dot, dots=[];
Math.abs(t1) > "1e12" && (t1 = 0.5);
Math.abs(t2) > "1e12" && (t2 = 0.5);
if (t1 >= 0 && t1 <= 1) {
dot = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1);
x.push(dot.x);
y.push(dot.y);
dots.push({X:dot.x, Y:dot.y});
}
if (t2 >= 0 && t2 <= 1) {
dot = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2);
x.push(dot.x);
y.push(dot.y);
dots.push({X:dot.x, Y:dot.y});
}
a = (c2y - 2 * c1y + p1y) - (p2y - 2 * c2y + c1y);
b = 2 * (c1y - p1y) - 2 * (c2y - c1y);
c = p1y - c1y;
t1 = (-b + Math.sqrt(b * b - 4 * a * c)) / 2 / a;
t2 = (-b - Math.sqrt(b * b - 4 * a * c)) / 2 / a;
Math.abs(t1) > "1e12" && (t1 = 0.5);
Math.abs(t2) > "1e12" && (t2 = 0.5);
if (t1 >= 0 && t1 <= 1) {
dot = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1);
x.push(dot.x);
y.push(dot.y);
dots.push({X:dot.x, Y:dot.y});
}
if (t2 >= 0 && t2 <= 1) {
dot = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2);
x.push(dot.x);
y.push(dot.y);
dots.push({X:dot.x, Y:dot.y});
}
// remove duplicate dots
var dots2 = [];
var l = dots.length;
for(var i=0; i<l; i++) {
for(var j=i+1; j<l; j++) {
if (dots[i].X === dots[j].X && dots[i].Y === dots[j].Y)
j = ++i;
}
dots2.push({X: dots[i].X, Y: dots[i].Y});
}
return {
min: {x: Array.min(x), y: Array.min(y)},
max: {x: Array.max(x), y: Array.max(y)},
dots: dots2 // these are the extrema points
};
};
Well, I would say you start by adding all endpoints to your bounding box. Then, you go through all the bezier elements. I assume the formula in question is this one:
From this, extract two formulas for X and Y, respectively. Test both for extrema by taking the derivative (zero crossings). Then add the corresponding points to your bounding box as well.
Use De Casteljau algorithm to approximate the curve of higher orders. Here is how it works for cubic curve
http://jsfiddle.net/4VCVX/25/
function getCurveBounds(ax, ay, bx, by, cx, cy, dx, dy)
{
var px, py, qx, qy, rx, ry, sx, sy, tx, ty,
tobx, toby, tocx, tocy, todx, tody, toqx, toqy,
torx, tory, totx, toty;
var x, y, minx, miny, maxx, maxy;
minx = miny = Number.POSITIVE_INFINITY;
maxx = maxy = Number.NEGATIVE_INFINITY;
tobx = bx - ax; toby = by - ay; // directions
tocx = cx - bx; tocy = cy - by;
todx = dx - cx; tody = dy - cy;
var step = 1/40; // precision
for(var d=0; d<1.001; d+=step)
{
px = ax +d*tobx; py = ay +d*toby;
qx = bx +d*tocx; qy = by +d*tocy;
rx = cx +d*todx; ry = cy +d*tody;
toqx = qx - px; toqy = qy - py;
torx = rx - qx; tory = ry - qy;
sx = px +d*toqx; sy = py +d*toqy;
tx = qx +d*torx; ty = qy +d*tory;
totx = tx - sx; toty = ty - sy;
x = sx + d*totx; y = sy + d*toty;
minx = Math.min(minx, x); miny = Math.min(miny, y);
maxx = Math.max(maxx, x); maxy = Math.max(maxy, y);
}
return {x:minx, y:miny, width:maxx-minx, height:maxy-miny};
}
I believe that the control points of a Bezier curve form a convex hull that encloses the curve. If you just want a axis-aligned bounding box, I think you need to find the min and max of each (x, y) for each control point of all the segments.
I suppose that might not be a tight box. That is, the box might be slightly larger than it needs to be, but it's simple and fast to compute. I guess it depends on your requirements.
I think the accepted answer is fine, but just wanted to offer a little more explanation for anyone else trying to do this.
Consider a quadratic Bezier with starting point p1, ending point p2 and "control point" pc. This curve has three parametric equations:
pa(t) = p1 + t(pc-p1)
pb(t) = pc + t(p2-pc)
p(t) = pa(t) + t*(pb(t) - pa(t))
In all cases, t runs from 0 to 1, inclusive.
The first two are linear, defining line segments from p1 to pc and from pc to p2, respectively. The third is quadratic once you substitute in the expressions for pa(t) and pb(t); this is the one that actually defines points on the curve.
Actually, each of these equations is a pair of equations, one for the horizontal dimension, and one for the vertical. The nice thing about parametric curves is that the x and y can be handled independently of one another. The equations are exactly the same, just substitute x or y for p in the above equations.
The important point is that the line segment defined in equation 3, that runs from pa(t) to pb(t) for a specific value of t is tangent to the curve at the corresponding point p(t). To find the local extrema of the curve, you need to find the parameter value where the tangent is flat (i.e., a critical point). For the vertical dimension, you want to find the value of t such that ya(t) = yb(t), which gives the tangent a slope of 0. For the horizontal dimension, find t such that xa(t) = xb(t), which gives the tangent an infinite slope (i.e., a vertical line). In each case, you can just plug the value of t back into equation 1 (or 2, or even 3) to get the location of that extrema.
In other words, to find the vertical extrema of the curve, take just the y-component of equations 1 and 2, set them equal to each other and solve for t; plug this back into the y-component of equation 1, to get the y-value of that extrema. To get the complete y-range of the curve, find the minimum of this extreme y value and the y-components of the two end points, and likewise find the maximum of all three. Repeat for x to get the horizontal limits.
Remember that t only runs in [0, 1], so if you get a value outside of this range, it means there is no local extrema on the curve (at least not between your two endpoints). This includes the case where you end up dividing by zero when solving for t, which you will probably need to check for before you do it.
The same idea can be applied to higher-order Beziers, there are just more equations of higher degree, which also means there are potentially more local extrema per curve. For instance, on a cubic Bezier (two control points), solving for t to find the local extrema is a quadratic equation, so you could get 0, 1, or 2 values (remember to check for 0-denominators, and for negative square-roots, both of which indicate that there are no local extrema for that dimension). To find the range, you just need to find the min/max of all the local extrema, and the two end points.
I answered this question in Calculating the bounding box of cubic bezier curve
this article explain the details and also has a live html5 demo:
Calculating / Computing the Bounding Box of Cubic Bezier
I found a javascript in Snap.svg to calculate that: here
see the bezierBBox and curveDim functions.
I rewrite a javascript function.
//(x0,y0) is start point; (x1,y1),(x2,y2) is control points; (x3,y3) is end point.
function bezierMinMax(x0, y0, x1, y1, x2, y2, x3, y3) {
var tvalues = [], xvalues = [], yvalues = [],
a, b, c, t, t1, t2, b2ac, sqrtb2ac;
for (var i = 0; i < 2; ++i) {
if (i == 0) {
b = 6 * x0 - 12 * x1 + 6 * x2;
a = -3 * x0 + 9 * x1 - 9 * x2 + 3 * x3;
c = 3 * x1 - 3 * x0;
} else {
b = 6 * y0 - 12 * y1 + 6 * y2;
a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3;
c = 3 * y1 - 3 * y0;
}
if (Math.abs(a) < 1e-12) {
if (Math.abs(b) < 1e-12) {
continue;
}
t = -c / b;
if (0 < t && t < 1) {
tvalues.push(t);
}
continue;
}
b2ac = b * b - 4 * c * a;
if (b2ac < 0) {
continue;
}
sqrtb2ac = Math.sqrt(b2ac);
t1 = (-b + sqrtb2ac) / (2 * a);
if (0 < t1 && t1 < 1) {
tvalues.push(t1);
}
t2 = (-b - sqrtb2ac) / (2 * a);
if (0 < t2 && t2 < 1) {
tvalues.push(t2);
}
}
var j = tvalues.length, mt;
while (j--) {
t = tvalues[j];
mt = 1 - t;
xvalues[j] = (mt * mt * mt * x0) + (3 * mt * mt * t * x1) + (3 * mt * t * t * x2) + (t * t * t * x3);
yvalues[j] = (mt * mt * mt * y0) + (3 * mt * mt * t * y1) + (3 * mt * t * t * y2) + (t * t * t * y3);
}
xvalues.push(x0,x3);
yvalues.push(y0,y3);
return {
min: {x: Math.min.apply(0, xvalues), y: Math.min.apply(0, yvalues)},
max: {x: Math.max.apply(0, xvalues), y: Math.max.apply(0, yvalues)}
};
}
Timo-s first variant adapted to Objective-C
CGPoint CubicBezierPointAt(CGPoint p1, CGPoint p2, CGPoint p3, CGPoint p4, CGFloat t) {
CGFloat x = CubicBezier(p1.x, p2.x, p3.x, p4.x, t);
CGFloat y = CubicBezier(p1.y, p2.y, p3.y, p4.y, t);
return CGPointMake(x, y);
}
// array containing TopLeft and BottomRight points for curve`s enclosing bounds
NSArray* CubicBezierExtremums(CGPoint p1, CGPoint p2, CGPoint p3, CGPoint p4) {
CGFloat a, b, c, t, t1, t2, b2ac, sqrtb2ac;
NSMutableArray *tValues = [NSMutableArray new];
for (int i = 0; i < 2; i++) {
if (i == 0) {
a = 3 * (-p1.x + 3 * p2.x - 3 * p3.x + p4.x);
b = 6 * (p1.x - 2 * p2.x + p3.x);
c = 3 * (p2.x - p1.x);
}
else {
a = 3 * (-p1.y + 3 * p2.y - 3 * p3.y + p4.y);
b = 6 * (p1.y - 2 * p2.y + p3.y);
c = 3 * (p2.y - p1.y);
}
if(ABS(a) < CGFLOAT_MIN) {// Numerical robustness
if (ABS(b) < CGFLOAT_MIN) {// Numerical robustness
continue;
}
t = -c / b;
if (t > 0 && t < 1) {
[tValues addObject:[NSNumber numberWithDouble:t]];
}
continue;
}
b2ac = pow(b, 2) - 4 * c * a;
if (b2ac < 0) {
continue;
}
sqrtb2ac = sqrt(b2ac);
t1 = (-b + sqrtb2ac) / (2 * a);
if (t1 > 0.0 && t1 < 1.0) {
[tValues addObject:[NSNumber numberWithDouble:t1]];
}
t2 = (-b - sqrtb2ac) / (2 * a);
if (t2 > 0.0 && t2 < 1.0) {
[tValues addObject:[NSNumber numberWithDouble:t2]];
}
}
int j = (int)tValues.count;
CGFloat x = 0;
CGFloat y = 0;
NSMutableArray *xValues = [NSMutableArray new];
NSMutableArray *yValues = [NSMutableArray new];
while (j--) {
t = [[tValues objectAtIndex:j] doubleValue];
x = CubicBezier(p1.x, p2.x, p3.x, p4.x, t);
y = CubicBezier(p1.y, p2.y, p3.y, p4.y, t);
[xValues addObject:[NSNumber numberWithDouble:x]];
[yValues addObject:[NSNumber numberWithDouble:y]];
}
[xValues addObject:[NSNumber numberWithDouble:p1.x]];
[xValues addObject:[NSNumber numberWithDouble:p4.x]];
[yValues addObject:[NSNumber numberWithDouble:p1.y]];
[yValues addObject:[NSNumber numberWithDouble:p4.y]];
//find minX, minY, maxX, maxY
CGFloat minX = [[xValues valueForKeyPath:#"#min.self"] doubleValue];
CGFloat minY = [[yValues valueForKeyPath:#"#min.self"] doubleValue];
CGFloat maxX = [[xValues valueForKeyPath:#"#max.self"] doubleValue];
CGFloat maxY = [[yValues valueForKeyPath:#"#max.self"] doubleValue];
CGPoint origin = CGPointMake(minX, minY);
CGPoint bottomRight = CGPointMake(maxX, maxY);
NSArray *toReturn = [NSArray arrayWithObjects:
[NSValue valueWithCGPoint:origin],
[NSValue valueWithCGPoint:bottomRight],
nil];
return toReturn;
}
Timo's CODE 2 answer has a small bug: the t parameter in computeCubicBaseValue function should be last. Nevertheless good job, works like a charm ;)
Solution in C# :
double computeCubicBaseValue(double a, double b, double c, double d, double t)
{
var mt = 1 - t;
return mt * mt * mt * a + 3 * mt * mt * t * b + 3 * mt * t * t * c + t * t * t * d;
}
double[] computeCubicFirstDerivativeRoots(double a, double b, double c, double d)
{
var ret = new double[2] { -1, -1 };
var tl = -a + 2 * b - c;
var tr = -Math.Sqrt(-a * (c - d) + b * b - b * (c + d) + c * c);
var dn = -a + 3 * b - 3 * c + d;
if (dn != 0) { ret[0] = (tl + tr) / dn; ret[1] = (tl - tr) / dn; }
return ret;
}
public double[] ComputeCubicBoundingBox(Point start, Point firstControl, Point secondControl, Point end)
{
double xa, ya, xb, yb, xc, yc, xd, yd;
xa = start.X;
ya = start.Y;
xb = firstControl.X;
yb = firstControl.Y;
xc = secondControl.X;
yc = secondControl.Y;
xd = end.X;
yd = end.Y;
// find the zero point for x and y in the derivatives
double minx = Double.MaxValue;
double maxx = Double.MinValue;
if (xa < minx) { minx = xa; }
if (xa > maxx) { maxx = xa; }
if (xd < minx) { minx = xd; }
if (xd > maxx) { maxx = xd; }
var ts = computeCubicFirstDerivativeRoots(xa, xb, xc, xd);
for (var i = 0; i < ts.Length; i++)
{
var t = ts[i];
if (t >= 0 && t <= 1)
{
var x = computeCubicBaseValue(xa, xb, xc, xd,t);
var y = computeCubicBaseValue(ya, yb, yc, yd,t);
if (x < minx) { minx = x; }
if (x > maxx) { maxx = x; }
}
}
double miny = Double.MaxValue;
double maxy = Double.MinValue;
if (ya < miny) { miny = ya; }
if (ya > maxy) { maxy = ya; }
if (yd < miny) { miny = yd; }
if (yd > maxy) { maxy = yd; }
ts = computeCubicFirstDerivativeRoots(ya, yb, yc, yd);
for (var i = 0; i < ts.Length; i++)
{
var t = ts[i];
if (t >= 0 && t <= 1)
{
var x = computeCubicBaseValue(xa, xb, xc, xd,t);
var y = computeCubicBaseValue(ya, yb, yc, yd,t);
if (y < miny) { miny = y; }
if (y > maxy) { maxy = y; }
}
}
// bounding box corner coordinates
var bbox = new double[] { minx, miny, maxx, maxy};
return bbox;
}

Resources