How are CIE xyY Luminance Values for Color Primaries Determined? - colors

In the sRGB color space, the luminance values for the red, green, and blue primaries are specified as 0.21216, 0.7152, and 0.0722, respectively. The white point is defined to have luminance 1. In other words, the sRGB values <1,0,0>, <0,1,0>, <0,0,1>, and <1,1,1> map to xyY values <0.64, 0.33, 21.216>, <0.3, 0.6, 71.52>, <0.15, 0.06, 7.217>, and <0.31273, 0.32902, 100> (with Y scaled by 100 by convention).
How are the luminance values for the primaries determined? Are they purely a function of the xy primaries, or a combination of the primaries and the illuminant (e.g. D65)? If so, what is the relationship? More generally, how can I determine the luminance values for an arbitrary set of primaries?

Finding the RGB-to-XYZ matrix is determined by the chromaticities (xy values) of the red, green, and blue primaries and by the chromaticies of the white point. The white point, in turn, is determined, at least in part, by the light source and by the color matching functions in use (for example, the D65 illuminant and the CIE 1931 standard observer, respectively).
The conversion is explained in further detail on Bruce Lindbloom's Web site:
http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
After generating the matrix, the luminances (Y values) of the three primaries are given in the second row of that matrix (see the pregenerated matrices further down on that page). Note that the formula given there takes the xy form of the primaries and the XYZ form of the white point, which can be converted from xy form by [x/y, 1, (1-(y+x))/y].

Related

how to convert CIE color in the following format x = 0.615, y = 0.346, 5.6 cd/m2; to HEX or RGB

Iv'e found converters from xyz, and posts that talk about that posts . but couldnt find a good reference for the 'cd/m2' component of the color
The coordinates you are giving in the question are CIE xyY (small x, small y, BIG Y), they are not CIE XYZ Tristimulus Values (BIG X, BIG Y, BIG Z). The commonality between the two is the Luminance Y. CIE xyY is a perspective projection from CIE XYZ along the Luminance Y-axis to separate Luminance and Chrominance information.
To convert from CIE xyY to HEX or RGB (sRGB) you need to follow the following path:
CIE xyY --> CIE XYZ --> RGB --> HEX
Using Colour, this transformation would be expressed as follows:
import colour
xyY = [0.615, 0.346, 5.6]
XYZ = colour.xyY_to_XYZ(xyY)
RGB = colour.XYZ_to_sRGB(XYZ, apply_cctf_encoding=False)
print(RGB)
RGB_n = colour.utilities.normalise_maximum(RGB)
print(RGB_n)
print(colour.notation.RGB_to_HEX(colour.cctf_encoding(RGB_n)))
[ 23.33310243 0.88648 0.07921734]
[ 1. 0.03799238 0.00339506]
#fe360b
or using the Automatic Colour Conversion Graph as of latest develop branch:
import colour
xyY = [0.615, 0.346, 5.6]
print(colour.convert(xyY, 'CIE xyY', 'Hexadecimal'))
.../colour/colour/utilities/verbose.py:237: ColourUsageWarning: "RGB" array contains values over 1 and will be normalised, unpredictable results may occur!
warn(*args, **kwargs)
#fe360b
Note that because your colour is High Dynamic Range (HDR), i.e. Y = 5.6, it cannot be converted to Hexadecimal representation without a normalisation process before converting from RGB to Hexadecimal. Here the colours are normalised so that the maximum RGB value is 1 but it is also worth considering that you are using sRGB colours and divide Y by 80 which is the typical sRGB display peak luminance.

Why are the colors in the 1931 CIE xyY chromaticity diagram white?

When we look at the 1931 CIE chromaticity diagram, represented within the x y plane of xyY space, it renders white colors (or close to white) at points of luminance like the D65 point highlighted here with E.
But why is this the case? The point for D65 is supposed to be represented at x = 0.33, y = 0.33. Given the formula Y = 1 - x - y, wouldn't that mean Y is 0.34?
The sRGB correlate or xyY at 0.33,0.33,0.34 is 158.4182, 155.5676, 176.8565 according to every converter I found. This is a light brown and not the near-white seen in every 1931 chromaticity diagram.
It seems like I need to scale the Y to get the proper luminance value for every channel.
Using the Y = 1 - x - y formula, my diagram looks like this, a muted diagram:
What don't I understand?
Edit
Setting Y = 1 and the diagram looks like the below, better.
Edit
Now looks like the below.
There is some imprecision on the interpretation of chromacity diagrams.
CIE xyY is a 3D figures. Often we see only a projections (often not a intersecting plane, just a projection).
One common projection is the "additive" xy chromacity diagram. You may notice it because it has yellow at border, and the white somewhere near the center. In such projection you show the maximum Y given a chromacity x,y.
Common is also the "subtractive" diagram, like your second one. No yellow, no white. This diagram has just the subtractive mix of the primaries, so the brighter colour are the primaries, and you get darken between them.
Note: usually the chromacity diagram are also extended also out of gamut, so the primaries are no more the real primaries, and white could not be white, and the yellow could be cut off, as your diagrams. You may try at first just the triangle between primaries, then expand. It is easier to debug.
The white will be just on top of 3D figure. In the first case, you take the outer surface of gamut, so you get the white. In the second case, you get a plane inside the figure, so you will never get white. But it is still a xy chromacity diagram.
On your case, I think you clipped the colour values (Note 1), which it is wrong: by clipping you will not get the correct chromacities (by clipping, one remove a certain value of a colour, so the ratio between channel is not maintained). One should use float or larger numbers for calculations, before to normalize (channel values in range 0 to 255). [Normalize (in this case): keep chromacity, but adapt Y so that final colour is in gamut]. In practice: you get the maximum value between R, G, B, and you multiply every channels by 255/max(R,G,B).
Note: this is not fully correct/precise. The above normalization should be done in linear space (light mix linearly), and only after normalization, the gamma funtion should be applied. On the other hand, on above figures, we do not have the correct colour for every point x,y. We can do it correctly only on a triangle (of gamut). By expanding the available colour on screen to full xz chromacity, we create errors/imprecisions. So normalization before or after gamma correction is not more so relevant (and it just change slightly the colours).
Note 1: From comment: this (clipping) it is not true, OTOH the very tiny part of blue (dark blue), and too much magenta and cyan, make me thinking about some numerical prolem)
The white point of CIE 1931 is not in x=1/3, y=1/3, and white color is not x=1/3, y=1/3, Y = 1/3.
According to Wikipedia:
The CIE 1931 color space chromaticity coordinates of D65 are
x=0.31271
y=0.32902
Since D65 represents white light, its co-ordinates are also a white point, corresponding to a correlated color temperature of 6504 K. Rec. 709, used in HDTV systems, truncates the CIE 1931 coordinates to x=0.3127, y=0.329.
The meaning of x=1/3, y=1/3 is different:
Light with a flat power spectrum in terms of wavelength (equal power in every 1 nm interval) corresponds to the point (x, y) = (1/3, 1/3).
Important: D65 is not a "flat power spectrum".
Computer systems (PCs) uses sRGB color format.
In sRGB the color components are after gamma (in contrast to CIE 1931 which applies linear curve).
In xyY color space, x,y are the chromaticity and Y is the luminance.
x=0.31271, y=0.32902 is the chromaticity without luminance and applies gray chromaticity.
For white color use Y = 1
Rec. 709, used in HDTV systems, truncates the CIE 1931 coordinates to x=0.3127, y=0.329
Lets compute sRGB of x=0.3127, y=0.329, Y = 1:
X = (Y/y)*x = 0.95046
Y = 1
Z = Y/y*(1-x-y) = 1.0891
Rlinear 3.240600 -1.537200 -0.498600 X 0.99984
Glinear = -0.968900 1.875800 0.041500 * Y = 1.00010
Blinear 0.055700 -0.204000 1.057000 Z 1.00007
Assume result is 1, 1, 1.
Last stage is applying gamma for converting "Linear sRGB" to sRGB.
Since all values are 1, the result is sRGB = 1, 1, 1.
We can repeat the computation for Y = 0.2, and the result is Linear sRGB = 0.2, 0.2, 0.2.
Apply gamma:
gamma(u) = 1.055*u^(1/2.4) - 0.055 for u > 0.0031308
1.055*0.2^(1/2.4) - 0.055 = 0.48453
So sRGB = 0.48453, 0.48453, 0.48453.
For converting to the standard range of [0, 255] (one byte per color channel), we need to scale by 255 and round the result: RGB888 = 124, 124, 124.

How to define BGR color range? Map color code to color name

I want to create color mapping, define few color names and boundaries in range of which those colors should fall. For example (BGR format),
colors = {
'red': ((0, 0, 255), (125, 125, 255)),
'blue': ((255, 0, 0), (255, 125, 125)),
'yellow' ....
}
So if I receive color, let's say (255, 50, 119) I can call it blue. I want to make such mapping for at least colors of rainbow plus gray, black, white. Using Python and openCV.
The problem is that I don't really understand where to get those values for boundaries, is there kind of lowest / highest value for blue, red and so on?
I would suggest using HSV colourspace for comparing colours because it is less sensitive to variable lighting than RGB, where green in the sunlight might be rgb(20,255,10), but green in a shadow might be rgb(3,45,2), whereas both will have a very similar Hue in HSV colourspace.
So, to get started...
Create a little 10x1 numpy array and make the first pixel red, the second orange, then yellow, green, blue, indigo, violet then black, mid-grey and white. There's a table here.
Then convert to HSV colourspace and note the Hue values.
I have started some code...
#!/usr/local/bin/python3
import numpy as np
import imageio
import cv2
# Create black image 10x1
im = np.zeros([1,10,3], dtype=np.uint8)
# Fill with colours of rainbow and greys
im[0,0,:]=[255,0,0] # red
im[0,1,:]=[255,165,0] # orange
im[0,2,:]=[255,255,0] # yellow
im[0,3,:]=[0,255,0] # green
im[0,4,:]=[0,0,255] # blue
im[0,5,:]=[75,0,130] # indigo
im[0,6,:]=[238,130,238] # violet
im[0,7,:]=[0,0,0] # black
im[0,8,:]=[127,127,127] # grey
im[0,9,:]=[255,255,255] # white
imageio.imwrite("result.png",im)
hsv=cv2.cvtColor(im,cv2.COLOR_RGB2HSV)
print(hsv)
Check image:
Check colours with Imagemagick too:
convert result.png txt:
# ImageMagick pixel enumeration: 10,1,65535,srgb
0,0: (65535,0,0) #FF0000 red
1,0: (65535,42405,0) #FFA500 orange
2,0: (65535,65535,0) #FFFF00 yellow
3,0: (0,65535,0) #00FF00 lime
4,0: (0,0,65535) #0000FF blue
5,0: (19275,0,33410) #4B0082 indigo
6,0: (61166,33410,61166) #EE82EE violet
7,0: (0,0,0) #000000 black
8,0: (32639,32639,32639) #7F7F7F grey50
9,0: (65535,65535,65535) #FFFFFF white
Now look at the HSV array below - specifically the first column (Hue). You can see Red has a Hue=0, Orange is 19, Yellow is 30 and so on. Note too that the Black, Grey and White all have zero Saturation and Black has a low Value, Grey has a medium Value and White has a high Value.
[[[ 0 255 255]
[ 19 255 255]
[ 30 255 255]
[ 60 255 255]
[120 255 255]
[137 255 130]
[150 116 238]
[ 0 0 0]
[ 0 0 127]
[ 0 0 255]]]
Now you can make a data-structure in Python that stores, for each colour:
Lowest included Hue
Highest included Hue
Name
So, you might use:
... see note at bottom for Red
14,23,"Orange"
25,35,"Yellow"
55,65,"Green"
115,125,"Blue"
...
and so on - omit Black, Grey and White from the table.
So, how do you use this?
Well, When you get a colour to check, first convert the R, G and B values to HSV and look at the resulting Saturation - which is a measure of vividness of the colour. Garish colours will have high saturation, whereas lacklustre, greyish colours will have low saturation.
So, see if the Saturation is more than say 10% of the max possible, e.g. more than 25 on a scale of 0-255.
If the Saturation is below the limit, check the Value and assign Black if Value low, Grey if middling and White if Value is high.
If the Saturation is above the limit, check if it is within the lower and upper limits of one of your recorded Hues and name it accordingly.
So the code is something like this:
def ColorNameFromRGB(R,G,B)
# Calculate HSV from R,G,B - something like this
# Make a single pixel from the parameters
onepx=np.reshape(np.array([R,G,B],dtype=np.uint8),(1,1,3))
# Convert it to HSV
onepxHSV=cv2.cvtColor(onepx,cv2.COLOR_RGB2HSV)
...
...
if S<25:
if V<85:
return "black"
elsif V<170:
return "grey"
return "white"
# This is a saturated colour
Iterate through colour names table and return name of entry with matching Hue
There are 2 things to be aware of:
There is a discontinuity in the Hue values for Red, because the HSV colour wheel is a circular wheel and the Hue value for Red is at an angle of 0, so values above 350 and below 10 are all Reds. It so happens that OpenCV scales the 0-360 range by dividing by 2, meaning it comes out as 0-180... which neatly fits in a single unsigned byte. So, for Red, you need to check for Hue greater than 175 and less than 5, say.
Be careful to always generate an 8-bit image when looking up colours, as the Hue values are scaled differently on 16-bit and float images.
Define a distance between two colors. Then find the "closest" color name for the given color. Which definition of distance you will choose has to be guided by your requirements, because there is no "best" definition, as far as I know.
One possibility is distance in RGB space. The distance between two colors can be defined, for example, as the euclidean (L2) distance between the colors as represented by vectors in three dimensional space - distance(a,b) = (a-b).length() Alternatively, try the Manhattan (L1) metric if the result makes sense, because the euclidean distance in RGB space is more of a heuristic than a valid measurement.
Another possibility is to first convert to HSV space. Then the closest color will be the one that has the closest hue to the given color. Unless the given color has insufficient saturation, then the color is either white, gray or black, depending on the color's lightness.

Getting alpha from normal and darken color

Given
darkColor = darken(normalColor, alpha)
darkColor and normalColor are known, alpha is unknown.
How can I calculate alpha?
How should I interpolate alpha if multiple color tuples (normalColor, darkColor) are presented?
As per Less docs, the below is how darken() function is defined:
Decrease the lightness of a color in the HSL color space by an absolute amount.
So given the normal color and its darkened version, the logic to find the percentage is to find out the lightness of both the normal color, the dark color and then subtract the latter from the former. Less has a built-in function to calculate the lightness() of a given color also and so it can be used directly.
#normalColor: #AAAAAA;
#darkColor: #6A6A6A; /* this is darken(#normalColor, 25%) */
#dummy{
percentage: lightness(#normalColor) - lightness(#darkColor);
}
Notes:
Calculation is lightness of normal color - lightness of dark color as darken decreases lightness.
The output is an approximate value and not accurate. For example, in the above case the output is 25.09803922% and not 25%. We cannot round down the output value also because deviation can be positive or negative. For example, if the dark color is #919191 (= darken(#normalColor, 10%)), the calculated output is 9.80392157%.
This method works only when the dark color is actually a darkened version of the normal color. That is, the hue and saturation of the two colors should be the same as the darken function modifies only the lightness.

How do I calculate a four colour gradient?

If I have four colours (A, B, C & D) on four corners of a square and I want to fill that square with a gradient that blends nicely between the four colours how would I calculate the colour of the point E?
The closer E is to any of the other points, the strong that colour should affect the result.
Any idea how to do that? Speed and simplicity is preferred to accuracy.
colours http://rabien.com/image/colours.png
The best solution when a gradient is required between two colors, is to use the HSV representation (Hue Saturation Value).
If you have the HSV values for your two colors, you just make linear interpolation for H, S and V, and you have nice colors (interpolation in RGB space always lead to "bad" results).
You also find here the formulae to go from RGB to HSV and from HSV to RGB, respectively.
Now, for your problem with the four corner, you can make a linear combination of the four H/S/V values, weighted by the distance from E to that four points A,B,C and D.
EDIT: same method than tekBlues, but in HSV space (it is quite easy to test it in RGB and in HSV spaces. And you will see the differences. In HSV, you just turn around the chromatic cylinder, and this is why it gives nice result)
EDIT2: if you prefer "speed and simplicity", you may use a L1-norm, instead of a L2-norm (euclidian norm)
So, if a is the size of your square and the coordinate of your points are A(0,0), B(0,a), C(a,0), D(a,a), then the Hue of a point E(x,y) can be computed with:
Hue(E) = ( Hue(B)*y/a + Hue(A)*(1-y/a) ) * (x/a) + ( Hue(D)*y/a + Hue(C)*(1-y/a) ) * (1-x/a)
where Hue(A) is the Hue of point A, Hue(B) the Hue of B, etc...
You apply the same formulae for the Saturation and Value.
Once you have the Hue/Saturation/Value for your point E, you can transform it in RGB space.
Check out this site, which gives a visual demo of #ThibThib's comment that "gradients in HSV will be more satifying":
http://www.perbang.dk/rgbgradient/
It is a gradient creator that will create and show BOTH an RGB gradient and an HSV gradient.
If you try 9 steps from FFAAAA to AAFFAA (light red to green), you’ll get a nice transition through light yellow, and the HSV and RGB ones look similar.
But try 9 steps from FF0000 to 00FF00 (bold red to green), and you’ll see the RGB one transition through a yucky greenish brown. The HSV gradient, however, transitions through bold yellow.
Determine the distance of point E to each point A,B,C,D
The color for point E will be the combination of Red / Green / Blue. Calculate each color axis as the average of the same color axis for A,B,C,D, ponderating by distance.
distance_a = sqrt((xa-xe)^2+(ya-ye)^2)
distance_b = ....
sum_distances = distance_a + distance_b ...
red = (red_adistance_a + red_bdistance_b ... ) / sum_distances
color_E = ColorFromARgb(red,green,blue)

Resources