Well the question says it all. I am looking for an algorithm to convert HSI (not HSL and not HSV) to RGB, assuming that all H, S, and I are > 0 and < 1.
I was hoping I could produce something like: http://en.wikipedia.org/wiki/HSL_and_HSV#From_HSV but for HSI w/o using cosine function. I am not quite sure if it is possible. Is there a way to compute C and m (as referred to in Wikipedia article for HSV and HSL) for HSI?
Thanks.
FYI, I am using this function for RGB to HSI (so I am trying to make the inverse):
public static void toHSI(byte R, byte G, byte B, out double H, out double S, out double I)
{
byte MAX, MIN;
if (R > G) { if (R > B) { MAX = R; MIN = (G < B ? G : B); } else { MAX = B; MIN = (G < R ? G : R); } }
else { if (G > B) { MAX = G; MIN = (R < B ? R : B); } else { MAX = B; MIN = (G < R ? G : R); } }
I = (double)(R + G + B) / 765;
if (I == 0) { H = S = 0; } // achromatic
else
{
double DIF = (double)(MAX - MIN);
S = 1 - (double)MIN / (255 * I);
if (MAX == R) { H = (double)(G - B) / DIF + (G < B ? 6 : 0); }
else if (MAX == G) { H = (double)(B - R) / DIF + 2; }
else { H = (double)(R - G) / DIF + 4; }
H /= 6;
}
}
You could try this I am not sure if it is helpful but you need to treat your values in the functions as floats. Hue is a number between 0-360. Saturation is 0.00 -1.00 and anything in between. Intensity aka Value is also used and is also a value of 0.00 -1.00 and anything in between. The result of the function are three values of an array which can then be used for rgb colorspace of values 0-255.
///I use this
// the function result will be the values of the array rgb[3] and will be the rgb values 0-255
///float H is values 0-360 because there are 360 degrees of color in hsi colorspace
///float S is 0.00 - 1.00 and aything in between
///float I is 0.00 - 1.00 and aything in between
///The input to our function is going to be hsi_to_rgb (Hue, Saturation, Intensity(brightness))
int rgb[3]; ///number of channels rgb = 3
void hsi_to_rgb(float H, float S, float I) {
int r, g, b;
if (H > 360) {
H = H - 360;
}
H = fmod(H, 360); // cycle H around to 0-360 degrees
H = 3.14159 * H / (float)180; // Convert to radians.
S = S > 0 ? (S < 1 ? S : 1) : 0; // clamp S and I to interval [0,1]
I = I > 0 ? (I < 1 ? I : 1) : 0;
if (H < 2.09439) {
r = 255 * I / 3 * (1 + S * cos(H) / cos(1.047196667 - H));
g = 255 * I / 3 * (1 + S * (1 - cos(H) / cos(1.047196667 - H)));
b = 255 * I / 3 * (1 - S);
} else if (H < 4.188787) {
H = H - 2.09439;
g = 255 * I / 3 * (1 + S * cos(H) / cos(1.047196667 - H));
b = 255 * I / 3 * (1 + S * (1 - cos(H) / cos(1.047196667 - H)));
r = 255 * I / 3 * (1 - S);
} else {
H = H - 4.188787;
b = 255 * I / 3 * (1 + S * cos(H) / cos(1.047196667 - H));
r = 255 * I / 3 * (1 + S * (1 - cos(H) / cos(1.047196667 - H)));
g = 255 * I / 3 * (1 - S);
}
//set the output to the array
rgb[0] = r;
rgb[1] = g;
rgb[2] = b;
}
Related
I am trying to calculate the length of a quadratic Bezier curve. Found a solution and a formula to calculate the length of the entire curve in a "closed form" - without having to do numeric methods. The solution is covered at: quadratic Bezier curve length. The function is below:
function quadraticBezierLength(x1, y1, x2, y2, x3, y3) {
let a, b, c, d, e, u, a1, e1, c1, d1, u1, v1x, v1y;
v1x = x2 * 2;
v1y = y2 * 2;
d = x1 - v1x + x3;
d1 = y1 - v1y + y3;
e = v1x - 2 * x1;
e1 = v1y - 2 * y1;
c1 = a = 4 * (d * d + d1 * d1);
c1 += b = 4 * (d * e + d1 * e1);
c1 += c = e * e + e1 * e1;
c1 = 2 * Math.sqrt(c1);
a1 = 2 * a * (u = Math.sqrt(a));
u1 = b / u;
a = 4 * c * a - b * b;
c = 2 * Math.sqrt(c);
return (
(a1 * c1 + u * b * (c1 - c) + a * Math.log((2 * u + u1 + c1) / (u1 + c))) /
(4 * a1)
);
}
It seems to be working correctly. I actually need L(t) rather than the length of the complete curve. It is another task. However, fortunately, someone solved it before me, and shared at length of a quadratic curve segment. I wrote a function based on this solution:
function quadraticBezierArcLength(t, x1, y1, x2, y2, x3, y3) {
let a, b, c, d, e, e1, d1, v1x, v1y;
v1x = x2 * 2;
v1y = y2 * 2;
d = x1 - v1x + x3;
d1 = y1 - v1y + y3;
e = v1x - 2 * x1;
e1 = v1y - 2 * y1;
a = 4 * (d * d + d1 * d1);
b = 4 * (d * e + d1 * e1);
c = e * e + e1 * e1;
a = 4 * c * a - b * b;
c = 2 * Math.sqrt(c);
const bt = b / (2 * a),
ct = c / a,
ut = t + bt,
k = ct - bt ** 2;
return (
(Math.sqrt(a) / 2) *
(ut * Math.sqrt(ut ** 2 + k) -
bt * Math.sqrt(bt ** 2 + k) +
k *
Math.log((ut + Math.sqrt(ut ** 2 + k)) / (bt + Math.sqrt(bt ** 2 + k))))
);
}
If I am not mistaken, the implementation in code is correct. Yet, the length values are definitely wrong. The only time it returns what I expect is when t equals 0.
Is there an error in my quadraticBezierArcLength function, or the formula wrong? I want the length along the curve of a segment from the point with coordinates {x1, x2} to the point C(t). I would expect that when t === 1, the results of both functions are the same, at the very least.
You seem to have added two unnecessary lines to the calculation. Here's a fiddle where I have commented out those two lines, along with the function copied from the original answer.
//a = 4 * c * a - b * b;
//c = 2 * Math.sqrt(c);
The original answer also used the absolute function, which you have removed.
I am trying to place evenly-spaced markers/dots on a quadratic curve drawn with HTML Canvas API. Found a nice article explaining how the paths are calculated in the first place, at determining coordinates on canvas curve.
There is a formula, at the end, to calculate the angle:
function getQuadraticAngle(t, sx, sy, cp1x, cp1y, ex, ey) {
var dx = 2*(1-t)*(cp1x-sx) + 2*t*(ex-cp1x);
var dy = 2*(1-t)*(cp1y-sy) + 2*t*(ey-cp1y);
return Math.PI / 2 - Math.atan2(dx, dy);
}
The x/y pairs that we pass, are the current position, the control point and the end position of the curve - exactly what is needed to pass to the canvas context, and t is a value from 0 to 1. Unless I somehow misunderstood the referenced article.
I want to do something very similar - place my markers over the distance specified s, rather than use t. This means, unless I am mistaken, that I need to calculate the length of the "curved path" and from there, I could probably use the above formula.
I found a solution for the length in JavaScript at length of quadratic curve. The formula is similar to:
.
And added the below function:
function quadraticBezierLength(x1, y1, x2, y2, x3, y3) {
let a, b, c, d, e, u, a1, e1, c1, d1, u1, v1x, v1y;
v1x = x2 * 2;
v1y = y2 * 2;
d = x1 - v1x + x3;
d1 = y1 - v1y + y3;
e = v1x - 2 * x1;
e1 = v1y - 2 * y1;
c1 = a = 4 * (d * d + d1 * d1);
c1 += b = 4 * (d * e + d1 * e1);
c1 += c = e * e + e1 * e1;
c1 = 2 * Math.sqrt(c1);
a1 = 2 * a * (u = Math.sqrt(a));
u1 = b / u;
a = 4 * c * a - b * b;
c = 2 * Math.sqrt(c);
return (
(a1 * c1 + u * b * (c1 - c) + a * Math.log((2 * u + u1 + c1) / (u1 + c))) /
(4 * a1)
);
}
Now, I am trying to space markers evenly. I thought that making "entropy" smooth - dividing the total length by the step length would result in the n markers, so going using the 1/nth step over t would do the trick. However, this does not work. The correlation between t and distance on the curve is not linear.
How do I solve the equation "backwards" - knowing the control point, the start, and the length of the curved path, calculate the end-point?
Not sure I fully understand what you mean by "space markers evenly" but I do have some code that I did with curves and markers that maybe can help you ...
Run the code below, it should output a canvas like this:
function drawBezierCurve(p0, p1, p2, p3) {
distance = 0
last = null
for (let t = 0; t <= 1; t += 0.0001) {
const x = Math.pow(1 - t, 3) * p0[0] + 3 * Math.pow(1 - t, 2) * t * p1[0] + 3 * (1 - t) * Math.pow(t, 2) * p2[0] + Math.pow(t, 3) * p3[0];
const y = Math.pow(1 - t, 3) * p0[1] + 3 * Math.pow(1 - t, 2) * t * p1[1] + 3 * (1 - t) * Math.pow(t, 2) * p2[1] + Math.pow(t, 3) * p3[1];
ctx.lineTo(x, y);
if (last) {
distance += Math.sqrt((x - last[0]) ** 2 + (y - last[1]) ** 2)
if (distance >= 30) {
ctx.rect(x - 1, y - 1, 2, 2);
distance = 0
}
}
last = [x, y]
}
}
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
ctx.beginPath();
drawBezierCurve([0, 0], [40, 300], [200, -90], [300, 150]);
ctx.stroke();
<canvas id="canvas" width=300 height=150></canvas>
I created the drawBezierCurve function, there I'm using a parametric equation of a bezier curve and then I use lineTo to draw between points, and also we get a distance between points, the points are very close so my thinking is OK to use the Pythagorean theorem to calculate the distance, and the markers are just little rectangles.
I am writing a simple ray shader and I am trying to prodcue a dice with a cube and a number of spheres representing the dots. The spheres are correct, but the sides of the cube are on the x, y and z axes. The cube is centred around 0, 0, 0.
I have checked that the coordinate of the vertices are correct. I am assuming that my ray calculation is correct as the spheres are in the correct positions.
Here is the code for the ray calculation
Ray Image::RayThruPixel(float i, float j)
{
float alpha = m_tanFOVx * ((j - m_halfWidth) / m_halfWidth);
float beta = m_tanFOVy * ((m_halfHeight - i) / m_halfHeight);
vec3 *coordFrame = m_camera.CoordFrame();
vec3 p1 = (coordFrame[U_VEC] * alpha) + (coordFrame[V_VEC] * beta) - coordFrame[W_VEC];
return Ray(m_camera.Eye(), p1);
}
where m_tanFOVx is tan(FOVx / 2) and m_tanFOVy is tan(FOVy / 2) FOVx and FOVy are in radians.
To find the intersection of the ray and triangle my code is as follows:
bool Triangle::Intersection(Ray ray, float &fDistance)
{
static float epsilon = 0.000001;
bool bHit = false;
float fMinDist(10000000);
float divisor = glm::dot(ray.p1, normal);
// if divisor == 0 then the ray is parallel with the triangle
if(divisor > -epsilon && divisor < epsilon)
{
bHit = false;
}
else
{
float t = (glm::dot(v0, normal) - glm::dot(ray.p0, normal)) / divisor;
if(t > 0)
{
vec3 P = ray.p0 + (ray.p1 * t);
vec3 v2 = P - m_vertexA;
v0 = m_vertexB - m_vertexA;
v1 = m_vertexC - m_vertexA;
normal = glm::normalize(glm::cross(v0, v1));
d00 = glm::dot(v0, v0);
d01 = glm::dot(v0, v1);
d11 = glm::dot(v1, v1);
denom = d00 * d11 - d01 * d01;
float d20 = glm::dot(v2, v0);
float d21 = glm::dot(v2, v1);
float alpha = (d11 * d20 - d01 * d21) / denom;
float beta = (d00 * d21 - d01 * d20) / denom;
float gamma = 1.0 - alpha - beta;
vec3 testP = alpha * m_vertexA + beta * m_vertexB + gamma * m_vertexC;
if((alpha >= 0 ) &&
(beta >= 0) &&
(alpha + beta <= 1))
{
bHit = true;
fDistance = t;
}
}
}
return bHit;
}
I am creating a map, i.e. bitmap where each pixel have some real value - height. I want to fill each pixel according to height if this pixel. I want to get something like this, but in 2D.
What is the function that maps height to (R,G,B) that I have such kind of pictures?
Edit: I code in python.
It's just a hue, starting at 360° and going backwards to 0°. You haven't specified a language, but here's how to do the conversion from HSL to RGB in JavaScript, for example:
/**
* Converts an HSL color value to RGB. Conversion formula
* adapted from http://en.wikipedia.org/wiki/HSL_color_space.
* Assumes h, s, and l are contained in the set [0, 1] and
* returns r, g, and b in the set [0, 255].
*
* #param Number h The hue
* #param Number s The saturation
* #param Number l The lightness
* #return Array The RGB representation
*/
function hslToRgb(h, s, l) {
var r, g, b;
if(s == 0) {
r = g = b = l; // achromatic
} else {
function hue2rgb(p, q, t){
if(t < 0) t += 1;
if(t > 1) t -= 1;
if(t < 1/6) return p + (q - p) * 6 * t;
if(t < 1/2) return q;
if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
return p;
}
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
var p = 2 * l - q;
r = hue2rgb(p, q, h + 1/3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1/3);
}
return [r * 255, g * 255, b * 255];
}
You'll want a fixed saturation of 100% and a lightness of 50%.
Basically I want two mix two colours color1 and color2.
Since simple calculation's bring up stuff like blue+yellow = grey ((color1.r + color2.r)/2 etc) i did some research and found that apparently mixing colors in order for the mixed color to look like we expect it too (e.g. blue+yellow = green) isn't that straight forward.
What another stackoverflow post taught me was that in order two achieve the mixture correctly i'd have to use the Lab* space / CIELAB and linked to the wikipedia page about this topic.
I found it informative but i couldn't really understand how to convert RGB to (sRGB and than to) Lab* - how to mix the obtained colors and how to convert back
I hope somebody here can help me
Thanks,
Samuel
1) convert sRGB to RGB. From GEGL:
static inline double
linear_to_gamma_2_2 (double value)
{
if (value > 0.0030402477F)
return 1.055F * pow (value, (1.0F/2.4F)) - 0.055F;
return 12.92F * value;
}
static inline double
gamma_2_2_to_linear (double value)
{
if (value > 0.03928F)
return pow ((value + 0.055F) / 1.055F, 2.4F);
return value / 12.92F;
}
2) RGB to CIELAB. Look in OpenCV source [/src/cv/cvcolor.cpp]. There are functions for color space conversions [icvBGRx2Lab_32f_CnC3R]
3) mix color channels.
4) make all the color conversions back.
To interpolate between two RGB colours in the LAB colour space, you first need to convert each colour to LAB via XYZ (RGB -> XYZ -> LAB).
function RGBtoXYZ([R, G, B]) {
const [var_R, var_G, var_B] = [R, G, B]
.map(x => x / 255)
.map(x => x > 0.04045
? Math.pow(((x + 0.055) / 1.055), 2.4)
: x / 12.92)
.map(x => x * 100)
// Observer. = 2°, Illuminant = D65
X = var_R * 0.4124 + var_G * 0.3576 + var_B * 0.1805
Y = var_R * 0.2126 + var_G * 0.7152 + var_B * 0.0722
Z = var_R * 0.0193 + var_G * 0.1192 + var_B * 0.9505
return [X, Y, Z]
}
function XYZtoRGB([X, Y, Z]) {
//X, Y and Z input refer to a D65/2° standard illuminant.
//sR, sG and sB (standard RGB) output range = 0 ÷ 255
let var_X = X / 100
let var_Y = Y / 100
let var_Z = Z / 100
var_R = var_X * 3.2406 + var_Y * -1.5372 + var_Z * -0.4986
var_G = var_X * -0.9689 + var_Y * 1.8758 + var_Z * 0.0415
var_B = var_X * 0.0557 + var_Y * -0.2040 + var_Z * 1.0570
return [var_R, var_G, var_B]
.map(n => n > 0.0031308
? 1.055 * Math.pow(n, (1 / 2.4)) - 0.055
: 12.92 * n)
.map(n => n * 255)
}
You may use this function to interpolate between two LAB colours
const sum = (a, b) => a.map((_, i) => a[i] + b[i])
const interpolate = (a, b, p) => {
return sum(
a.map(x => x * p),
b.map(x => x * (1 - p)),
)
}
const colour1 = [...]
const colour2 = [...]
interpolate(colour1, colour2, 0.2) // take 20% of colour1 and 80% of colour2
Finally, convert the result back from LAB to RGB
function XYZtoLAB([x, y, z]) {
const [ var_X, var_Y, var_Z ] = [ x / ref_X, y / ref_Y, z / ref_Z ]
.map(a => a > 0.008856
? Math.pow(a, 1 / 3)
: (7.787 * a) + (16 / 116))
CIE_L = (116 * var_Y) - 16
CIE_a = 500 * (var_X - var_Y)
CIE_b = 200 * (var_Y - var_Z)
return [CIE_L, CIE_a, CIE_b]
}
function LABtoXYZ([l, a, b]) {
const var_Y = (l + 16) / 116
const var_X = a / 500 + var_Y
const var_Z = var_Y - b / 200
const [X, Y, Z] = [var_X, var_Y, var_Z]
.map(n => Math.pow(n, 3) > 0.008856
? Math.pow(n, 3)
: (n - 16 / 116) / 7.787)
return [X * ref_X, Y * ref_Y, Z * ref_Z]
}
Some other helpful functions
const parseRGB = s => s.substring(s.indexOf('(') + 1, s.length - 1).split(',')
.map(ss => parseInt(ss.trim()))
const RGBtoString = ([r, g, b]) => `rgb(${r}, ${g}, ${b})`
All together:
document.body.style.backgroundColor = RGBtoString(XYZtoRGB(LABtoXYZ(interpolate(
XYZtoLAB(RGBtoXYZ(parseRGB('rgb(255, 0, 0)'))),
XYZtoLAB(RGBtoXYZ(parseRGB('rgb(0, 255, 0)'))),
0.2,
))))
Reference for colour space conversions: http://www.easyrgb.com/en/math.php