[Intro]
HSP color model is a made-up color model created in 2006. It uses the same values as HSV for Hue and Saturation but, for calculating the P (perceived brightness), it uses Weighted Euclidean norm of the [R, G, B] vector.
More info: https://alienryderflex.com/hsp.html
As you can see, at the bottom of the website, there are formulas for calculating between RGB and HSP that I've taken and re-formatted for Python.
[Issues]
In some places, I found that for calculating the Perceived brightness, you need to first linearize the RGB channels (assuming it's sRGB) but if you do so, then the formulas no longer work. For that reason, I'm not doing that and applying the formulas directly on the input RGB color. Also, I found in a js library someone made it so the perceived brightness is in range 0-255. I don't know where they got that idea, but it should be in range 0-100 (percentage).
[Where it all goes wrong]
I don't have any issues with calculating from RGB to HSP. The problem is when calculating RGB from HSP. I won't bother you with the full code since you can take it from the link above but I'm giving you a snippet of the part that doesn't work correctly (or I have a mistake that I can't find).
P.S: After further investigation, it turns out that more than just this snippet gives false results!
elif H < 4 / 6: # B > G > R
H = 6 * (-H + 4 / 6)
B = (P ** 2 / (Pb + Pg * H ** 2)) ** 0.5
G = B * H
R = 0
This is the part where Saturation is 100%. The problem is that when you pass it these values HSP(253, 100, 50), or any similar ones, the resulting blue is beyond the acceptable range (in this case 356). I tried clamping the values to 255 but then when doing the RGB to HSV conversion, the values don't match so the problem isn't there.
Any ideas?
So, I found a mistake in my code which brought down the out-of-range values from 300+ to a maximum of 261 which is acceptable to be clamped at 255 (for 8-bit colors) without needing to do anything to the other values. No values need to be clamped on the black side.
Here's my simplified version of the calculation with comments:
def hsp_to_rgb(HSP: tuple | list, depth: int = 8, normalized: bool = False):
"""### Takes an HSP color and returns R, G, B values.
#### N/B: All examples below are given for 8-bit color depth that has range 0-255. \
If you want to use this function with a different depth the actual range is 0-(max value for bit depth).
### Args:
`color` (tuple | list): Either int in range 0-255 or float in range 0-1
`depth` (int): The bit depth of the input RGB values. Defaults to 8-bit (range 0-255)
`normalized` (bool, optional): Returns the values in range 0-1. Defaults to False.
Reference: http://alienryderflex.com/hsp.html
### Returns:
list[int, int, int] | list[float, float, float]: (H, S, P)
"""
H, S, P = HSP[0]/360, HSP[1]/100, HSP[2]/100
max_value = 2 ** depth - 1
def wrap(HSP: tuple | list, c1: float, c2: float, c3: float, S1: bool):
"""### This is an internal helper function for the hsp_to_rgb function to lift off some of the calculations.
c1, c2, c3 - Pr, Pg, Pb in different order
### Args:
`HSP` (tuple | list): Hue, Saturation, Perceived brightness in range 0-1
`c1` (float): Constant. Either 0.299, 0.587 or 0.114
`c2` (float): Constant. Either 0.299, 0.587 or 0.114
`c3` (float): Constant. Either 0.299, 0.587 or 0.114
`S1` (bool): Whether S (Saturation) is 1 (100%). Defaults to False
### Returns:
tuple[float, float, float]: R, G, B values in different order depending on the constants.
"""
if S1:
ch1 = (HSP[2] ** 2 / (c1 + c2 * HSP[0] ** 2)) ** 0.5
ch2 = ch1 * HSP[0]
ch3 = 0
return ch3, ch1, ch2
min_over_max = 1 - HSP[1]
part = 1 + HSP[0] * (1 / min_over_max - 1)
ch1 = HSP[2] / (c1 / min_over_max ** 2 + c2 * part ** 2 + c3) ** 0.5
ch2 = ch1 / min_over_max
ch3 = ch1 + HSP[0] * (ch2 - ch1)
return ch1, ch2, ch3
# Get weights constants
Pr, Pg, Pb = 0.299, 0.587, 0.114
# Calculate R, G, B based on the Hue
if H < 1 / 6: # R > G > B
H = 6 * H
B, R, G = wrap((H, S, P), Pr, Pg, Pb, S >= 1)
elif H < 2 / 6: # G > R > B
H = 6 * (-H + 2 / 6)
B, G, R = wrap((H, S, P), Pg, Pr, Pb, S >= 1)
elif H < 3 / 6: # G > B > R
H = 6 * (H - 2 / 6)
R, G, B = wrap((H, S, P), Pg, Pb, Pr, S >= 1)
elif H < 4 / 6: # B > G > R
H = 6 * (-H + 4 / 6)
R, B, G = wrap((H, S, P), Pb, Pg, Pr, S >= 1)
elif H < 5 / 6: # B > R > G
H = 6 * (H - 4 / 6)
G, B, R = wrap((H, S, P), Pb, Pr, Pg, S >= 1)
else: # R > B > G
H = 6 * (-H + 1)
G, R, B = wrap((H, S, P), Pr, Pb, Pg, S >= 1)
return [min(i, 1.0) for i in (R, G, B)] if normalized else [min(i*max_value, 255) for i in (R, G, B)]
This works pretty well and the conversions are really accurate. Note that in order to get perfect conversions, you'll need to use an exact floating-point number for the calculations. Otherwise, you'll get a number of overlapping values due to limitations of the system. Ex. RGB = 256 * 256 * 256 = 16 777 216 colors, whereas HSP = 360 * 100 * 100 = 3 600 000 unique colors.
Related
Problem
i got a similar problem like this one:
How to convert CIE color space into RGB or HEX color code in PHP
how to convert xy color to sRGB?
I can't get the formular working xyY. What should i enter for Y?
Setup of the environment
i got an ikea light bulb which gives me a XY color value in the (CIE 1931 colorspace)
I would like to convert it into RGB,(sRGB) or HEX.
The Phosconn app is sending the following xy values when setting the colors by full brighness and saturation.
RED [0.735, 0.265]
GREEN [0.115, 0.826]
BLUE [0.157, 0.018]
i figured out that the lamp shows deeper colors when i send following values:
RED [1.0, 0.0]
GREEN [0.0, 1.0]
BLUE [0.0, 0.0]
To be more precise here is an illustration what i try to achieve:
Retrieve imformation from the bulb (xy color) via zigbee, convert it with javascript to RGB or HEX for the dashboard's color picker
The other way around does already work. Retrieving information from dashboard's color picker (RGB,brightness,saturation) convert it with JS into XY color, brightness and saturation and send it with zigbee to the bulb.
Current Implementation
It's based on the suggested cie-rgb-color-converter NPM module.
function xyBriToRgb(x, y, bri){
// bri = bri/254*100
node.warn("XYBRI: "+x+ " | "+y+" | "+bri)
function getReversedGammaCorrectedValue(value) {
return value <= 0.0031308 ? 12.92 * value : (1.0 + 0.055) * Math.pow(value, (1.0 / 2.4)) - 0.055;
}
let xy = {
x: x,
y: y
};
let z = 1.0 - xy.x - xy.y;
let Y = bri / 255;
let X = (Y / xy.y) * xy.x;
let Z = (Y / xy.y) * z;
let r = X * 1.656492 - Y * 0.354851 - Z * 0.255038;
let g = -X * 0.707196 + Y * 1.655397 + Z * 0.036152;
let b = X * 0.051713 - Y * 0.121364 + Z * 1.011530;
r = getReversedGammaCorrectedValue(r);
g = getReversedGammaCorrectedValue(g);
b = getReversedGammaCorrectedValue(b);
// Bring all negative components to zero
r = Math.max(r, 0);
g = Math.max(g, 0);
b = Math.max(b, 0);
// If one component is greater than 1, weight components by that value
let max = Math.max(r, g, b);
if (max > 1) {
r = r / max;
g = g / max;
b = b / max;
}
return {
r: Math.floor(r * 255),
g: Math.floor(g * 255),
b: Math.floor(b * 255),
};
}
msg.payload = xyBriToRgb(msg.payload.xy[0], msg.payload.xy[1], msg.payload.bri);
node.warn("RGB: "+ JSON.stringify(msg.payload))
return msg;
Results
let rgb = ColorConverter.xyBriToRgb(0.157 ,0.018, 6);
// return {r: 64, g: 0, b: 255}
Research Material
With the help of the fantastic guys here i found some explanations in this Phillips HUE docs
which was leading me to a Review of RGB color spaces
Meanwhile i discovered some bugs inside the phosconn api or its the firmware of the bulb, that the saturation can not be set via api.
I found zigbee2mqtt page which could fix all my problems with a page fitting 100% to the model of the ikea bulb Zigbee2MQTT IKEA LED1624G9
Im trying to setup zigbee2mqtt for this, because i got some problems with phosconn and the api not setting correctly brightness and stuff.
Also the brightness is just the luminosity of the bulb and has here nothing to do with the color so i assume it's phosconn specific or bulb specific?
Short Answer
Okay, so what I am seeing there are the xy coordinates of the 1931 chromaticity diagram? From that we can generate a matrix, and thru the matrixes we can generate RGB or XYZ (and xyY in a simple further transform.)
Longer Answer
Those coordinates are "xy" not to be confused with XYZ (because what would color science be if it wasn't completely confusing?)
XYZ is a color space, and xyY is a derivative, with the xy being coordinates on the 1931 chromaticity diagram:
The Y, luminance (spectrally weighted light) is missing, but let's solve for all three primaries at 255, and let's assume that's white.
One thing I don't have is the "white point"... when you set the RGB values to 255,255,255 what is the color temperature of the white light? In order to create the matrix I made the assumption of D50, reasonable for lighting like this... but it could be lower or even higher.
The math for the matrixes can be seen at Bruce Lindbloom's site: http://brucelindbloom.com
Assuming that 255,255,255 creates a white light close to D50 then the matrix to go from XYZ to RGB is:
MATRIX
XYZ TO RGB
X
1.4628522474616900
-0.1840680708796900
-0.2743691849401830
R
Y
-0.5217863795540330
1.4472188263102400
0.0677218457168470
=
G
Z
0.0349375228787856
-0.0969021860977637
1.2885320438253000
B
But wait...
Where are you getting these xy values? It seems your controller is sending 255 to each of the lights... so, I'm a little confused as to the application?
Assuming white at a given color temperature when all three are at 255, then the relative Y is 1.0 (0.0 to 1.0 scale, though you can also use a 0 to 100 scale instead).
The next question is ... linearity. On a computer monitor, typically sRGB has an output gamma, a power curve of ^2.2 ... an LED bulb probably does not. Are you trying to match a computer monitor?
Assuming not, and assuming that your controller is also a gamma of 1, then 128,128,128 would be a Y of 0.5
If you want to get the whole XYZ from your RGB, that matrix is here:
For reference the RGB to XYZ and the reference whitepoint
MATRIX
RGB TO XYZ
R
0.7160822783599350
0.1009309426243870
0.1471718784550240
X
G
0.2581793248508610
0.7249474661542950
0.0168732089948435
=
Y
B
0.0000000000000000
0.0517819618681639
0.7733554122636590
Z
CALCULATED
REF WHITE POINT
WP
0.964185099439346
1.00
0.825137374131823
Matrix Sans Neo
Once the matrix is generated, it's fairly simple to process.
You multiply each number of each row with each row of the input channels, and then sum and that gives you the corresponding output.
so for the output to RGB, you go
X * 1.46285 + Y * -0.184068 + Z * -0.274369 = R
X * 0.258179 + Y * 0.724947 + Z * 0.01687 = G
X * 0.0 + Y * 0.05178 + Z * 0.773355 = B
If I understood better what you were attempting to acheive, I might be able to be more helpful.
Recalcualted Matrixes
with the white point you gave:
FINAL RGB TO XYZ MATRIX
R 0.6491852651246980 0.1034883891428110 0.1973263457324920 X
G 0.2340599935483600 0.7433166037561910 0.0226234026954449 Y
B 0.0000000000000000 0.0530940431254422 1.0369059568745600 Z
CALCULATED REF WHITE POINT
WP 0.950000000000001 1.00 1.090000000000000
MATRIX INVERTED — XYZ TO RGB
X 1.6135957276619700 -0.2030358522440090 -0.3026422835182280 R
Y -0.5088917855729640 1.4114545750797300 0.0660482763436608 G
Z 0.0260574473801223 -0.0722725427335469 0.9610256584609440 B
You can use cie-rgb-color-converter NPM module.
let xy = ColorConverter.rgbToXy(255, 0, 0);
// It returns {x: 0.7350000508904126, y: 0.26499994910958735}
The number of xy is same as question example.
But if you want to convert these numbers back to RGB. You need to Brightness parameter.
let rgb = ColorConverter.xyBriToRgb(0.7350000508904126 ,0.26499994910958735 , Brightness);
If you set Brightness to 0, it is darkest light (no light, no color), and all number of RGB comes back as zero, because in zero light human eyes can not see anything.
Following example is nearest numbers:
let rgb = ColorConverter.xyBriToRgb(0.7350000508904126 ,0.26499994910958735, 70);
// return {r: 255, g: 0, b: 16}
let rgb = ColorConverter.xyBriToRgb(0.11500021676131911 ,0.8259995753701338, 200);
// return {r: 0, g: 255, b: 0}
let rgb = ColorConverter.xyBriToRgb(0.15700016726803506 ,0.01799963360335173, 6);
// return {r: 64, g: 0, b: 255}
Note: cie-rgb-color-converter has a simple problem, after install go to ColorConverter.js and change these lines:
let red = parseInt(r * 255) > 255 ? 255: parseInt(r * 255);
let green = parseInt(g * 255) > 255 ? 255: parseInt(g * 255);
let blue = parseInt(b * 255) > 255 ? 255: parseInt(b * 255);
red = Math.abs(red);
green = Math.abs(green);
blue = Math.abs(blue);
return {r: red, g: green, b: blue};
to
// Bring all negative components to zero
r = Math.max(r, 0);
g = Math.max(g, 0);
b = Math.max(b, 0);
// If one component is greater than 1, weight components by that value
let max = Math.max(r, g, b);
if (max > 1) {
r = r / max;
g = g / max;
b = b / max;
}
return {
r: Math.floor(r * 255),
g: Math.floor(g * 255),
b: Math.floor(b * 255),
};
#bokup PR it on GitHub.
I have a trapezoid where I know the length of all 4 lines, and I know the coordinates of two of the corners. How do I find the coordinates of the remaining two corners in JavaScript?
NOTE: All 4 lines are of different length, ie this is not an isosceles trapezoid
Here's a little diagram in case it helps:
L3
D ____ C
/ \
L4 / \ L2
--------
A L1 B
I know the coordinates of A and B, and the length of L1, L2, L3, and L4 (Which are all different). I just need to get all of the possible sets of coordinates for D and C!
Drop vertical lines from points C and D to the line AB to find their projections on E and F on AB:
Now AED and BFC are right triangles with the same height. Let's call the height h. From Pythagoras:
a² + h² = L4² and b² + h² = L2²
Subtracting one equation from the other you get: a² - b² = L4² - L2²
Also you can divide L1 into segments AE, EF, and FB, so the length of L1 must be:
L1 = a + L3 + b => a + b = L1 - L3
Therefore we have a system of equations in a and b:
a² - b² = L4² - L2²
a + b = L3 - L1
Using the fact that a² - b² = (a+b)(a-b) and the above equation, you get:
(L3 - L1)(a - b) = L4² - L2² => a - b = (L4² - L2²)/(L3 - L1)
(Note that L1 and L3 can not be equal. If L1 = L3, there is an infinite number of solutions.)
So the equations simplify to:
a + b = L3 - L1
a - b = (L4² - L2²)/(L3 - L1)
The solution is:
a = (L3 - L1 + (L4² - L2²)/(L3 - L1)) / 2
b = (L3 - L1 - (L4² - L2²)/(L3 - L1)) / 2
The height of the trapezoid is:
h = sqrt(L4² - a²) = sqrt(L2² - b²)
You could now use a to solve for the angle at point A and b to solve the angle at point B, and use them to calculate the coordinates for C and D. Or you can use a, b, and h directly.
For example: suppose A is at the origin (0,0) and B is at (L1, 0). Then the two solutions are:
C is at (a, h) and D is at (L1 - b, h)
C is at (a, -h) and D is at (L1 - b, -h)
I have two lines defined by two points each(P1, P2, P3, P4).
I'd like to find the 2 points(X0, Y0) that are distanced r units from both lines.
I found a way but I think it's too long to solve(too difficult for me) and maybe there's one shorter(https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line).
my way:
Without solving it it's impossible tp create the function
I'd like to have the solution not the sistem.
Example(I'd like to find the yellow points knowing r):
Thanks.
(Sorry for my terrible english)
Variant A:
represent your lines in general form
a * x + b * y + c = 0
d * x + e * y + f = 0
with normalized coefficients (divide equation by Sqrt(a^2+b^2))
For this form point lies at distance r if
|a * x + b * y + c| = r
|d * x + e * y + f| = r
Open absolute value brackets with all possible +/- sign combinations and get 4 linear systems for 4 possible points
Variant B:
1) Find unit direction vectors of both lines da and db
2) Find intersection point C of two lines
3) Calculate angle between lines as
Fi = atan2(da.cross.db, da.dot.db)
4) Find unit bisector vector
b = (da + db).Normalized
5) Find perpendicular bisector
pb = (-b.y, b.x)
6) Get needed points as
C + b * r / Sin(Fi/2)
C - b * r / Sin(Fi/2)
C + pb * r / Cos(Fi/2)
C - pb * r / Cos(Fi/2)
Consider the given image of the soccer field
As you can see in the image the various ball movements, some of them are curved(i.e. in case of (1), (2), (3) in the image)) and some may not(i.e a line(4)),
so I need to find the intersection points of ball path with goalline and sideline. Sometimes the input may not be a curve(i.e a line) like in case of (4) given image
I have written a program, I have no clue what is wrong - is this right way to solve this kind of program.
if yes then, how to convert bezier curve into an equation for better solving
considering the given as
beizer curve eqaution -> a(x*x) + b*x + c
and line segment equation -> y = y1 + m(x-x1)
//maxCurvedPoint is the topmost of the curve
var getIntersectionPoint = function (room, ballFromPosition, ballToPosition, maxCurvePoint)
{
var linepoints = [[m1,n1], [m2, n2], [m3, n3], [m4, n4]];
//converting three points(ballFromPosition, maxCurvePoint, ballToPosition) into the quadratic equation (Bezier curve) --(1)
//getting the equation of the line segment using the linepoints --(2)
//equating (1) and (2) and getting a quadratic equation and solving and finding intersection points
return intersectionPoint;
}
// solves //(-b(+-)sqrt(b*b - 4ac)/2ac)
function solve(a, b, c)
{
//check curve intersects line or not
if((Math.pow(b, 2) - (4 * a * c)) >= 0)
{
result1 = (-1 * b + Math.sqrt(Math.pow(b, 2) - (4 * a * c))) / (2 * a);
result2 = (-1 * b - Math.sqrt(Math.pow(b, 2) - (4 * a * c))) / (2 * a);
return [result1, result2];
}
return [];
}
Can anyone help me with this? Also is the most curve point can be called vertex of the curve?
I find it easier to work with vector equations since the algebra will be rotation-invariant (hence you don't have to re-write the code to deal with e.g. a "horizontal" parabola).
1. Curve representation + Intersection test
Consider a quadratic Bezier curve with endpoints A, C, control point B and parameter t:
And an infinite line with source O, direction D and parameter s:
Equating P and R give a pair of quadratic simultaneous equations, which can be re-arranged to eliminate s and find the parabolic parameter t:
Solve this quadratic equation for t, and only accept real roots in the range [0, 1]. This ensures that any intersection point is always on the segment itself.
2. Dealing with line segments
You can also restrict the intersection point to a line segment, by computing s from t using the equations above, and limiting its value - which equals the distance along the line from O if D is normalized.
3. Computing the control point B
Note that a general value of the control point B will not give a symmetrical parabola. To compute B for a general symmetric curve:
Defining the variables:
M: midpoint of AB
n: clockwise normal to the direction AC
q: signed bulge distance - absolute value is the distance from M to the midpoint of the curve
k: signed distance from M to B
A surprisingly simple result.
4. Sample C# (-style) code
public static Vector2[] computeIntersection
(
Vector2 A, Vector2 C, double q, // parabola segment
Vector2 O, Vector2 P // line segment
)
{
// quadratic solve
double[] solve(double a, double b, double c)
{
double d = b * b - 4.0 * a * c;
if (d < 0.0) // imaginary roots - no intersection at all
return null;
if (d > 0.0) // two distinct real roots
{
double sd = Math.Sqrt(d);
return new double[2] { (-b + sd) / (2.0 * a),
(-b - sd) / (2.0 * a) };
}
else // only one (line segment is tangent to curve)
{
return new double[1] { -b / (2.0 * a) };
}
}
// cross product (in-case undefined)
double cross(Vector2 i, Vector2 j)
{
return i.x * j.y - i.y * j.x;
}
// validity check for t and s
bool valid(double v)
{
return (v >= 0.0) && (v <= 1.0);
}
// compute control point B
Vector2 E = C - A;
Vector2 M = 0.5 * (A + C);
Vector2 N = (new Vector2(E.y, -E.x)).normalize();
Vector2 B = M + (2.0 * q) * N;
// solving for t
Vector2 D = P - O;
bool useX = Math.Abs(D.X) > Math.Abs(D.Y);
double[] T = solve(cross(A + C - 2.0 * B, D),
cross(B - A, D) * 2.0,
cross(A - O, D));
if (T == null) return null;
Vector2[] I = new Vector2[2];
int c = 0;
for (int i = 0; i < T.Length; i++)
{
// check if t is within curve range
double t = T[i];
if (!valid(t)) continue;
// solve for s and check if is within line range
double u = (1 - t) * (1 - t);
double v = 2 * t * (1 - t);
double w = t * t;
double s = useX ? ((u * A.X + v * B.X + w * C.X - O.X) / D.X)
: ((u * A.Y + v * B.Y + w * C.Y - O.Y) / D.Y);
if (!valid(s)) continue;
// compute the corresponding intersection point
I[c++] = O + s * D;
}
// only return valid solutions
if (c == 0) return null;
Array.Resize(ref I, c);
return I;
}
If you translate and rotate all the endpoints in such a way that the line segment becomes (0, 0)-(d, 0), the problem simplifies.
Let the control points be (Xk, Yk), k= 0, 1, 2. The intersections with the axis X are obtained by solving for t the quadratic equation
Y0 (1-t)² + 2 Y1 t(1-t) + Y2 t² = 0.
The corresponding abscissas are given by
X0 (1-t)² + 2 X1 t(1-t) + X2 t² = 0.
You can check if these belong to the interval [0, d]. Then apply the reverse rotation and translation.
Addendum: intersection of two quadratic Beziers
The vector equation of one of the curves can be written
P = P0 (1 - t)² + 2 P1 t (1 - t) + P2 t²
= P0 + 2 (P1 - P0) t + (P0 - 2 P1 + P2) t².
If you apply the affine change of coordinates such that the reference frame becomes (P0, P1 - P0, P0 - 2 P1 + P2), the equation simplifies to
X = 2t
Y = t²
which is the implicit equation
X² = 4Y.
Now by applying the same transform to the control points of the second curve and plugging the parametric equations in the above, you get a quartic equation in t (square of a quadratic polynomial on one side, quadratic polynomial on the other).
There are closed-form formulas for the roots of a quartic equation, and there can be four of them. After selecting the real roots in [0, 1], you evaluate the t parameter of the first curve and also check membership in [0, 1].
Don't forget to restore the original coordinates.
I am storing id (which is a value comprised in 24bit-range) into an Float32Array(3) for latter retrieval in WebGL:
var r = 0,
g = 0,
b = id;
if (b >= 65536) {
r = ~~(b / 65536);
b -= r * 65536;
}
if (b >= 256) {
g = ~~(b / 256);
b -= g * 256;
}
var fa = new Float32Array([r/255, g/255, b/255]);
For the sake of completeness, here is how i am using that value:
gl.uniform3fv(uniforms['u_id'], fa);
...and this is how i get my id back from WebGLRenderingContext.readPixels():
var result = new Uint8Array(4);
gl.readPixels(x, y, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, result);
var id = result[0] << 16 | result[1] << 8 | result[2];
Which is the correct way to split that value into my Float32Array? I strongly believe that such task could be accomplished in a more efficient and elegant way (actually, what i am doing is working but is really hurting my eyes).
id has a form like this:
0000 0000 rrrr rrrr gggg gggg bbbb bbbb
A part (r, g or b) can be extracted by putting it in the lowest byte and masking the rest away. Sometimes one of those steps is unnecessary. So:
b = id & 255 // b is already in the low byte, so no shift
g = (id >> 8) & 255
r = id >> 16 // no mask because there is nothing "above" r
This can be put together with the division and putting it in an array:
[(id >> 16) / 255, ((id >> 8) & 255) / 255, (id & 255) / 255]