summaryrefslogtreecommitdiffstats
path: root/rgbxyz.c
blob: c8de1a07c87110df24786ca5e0a4c4b1060f0890 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
/*
 * Matrixes for conversion between CIE XYZ values to device linear RGB,
 * with values normalized so that Y = 1.0 and R = G = B = 255.0
 * represent the maximum possible values.
 *
 * If the device primaries are nonlinear, or if there are more then
 * three primaries, additional code could be added to these functions,
 * possibly replacing the type of "struct RGB".  Either way, such code
 * would involve more than a simple matrix multiplication;
 */

#include "color.h"
#include <math.h>
#include <stdio.h>

/*
 * Conversion matrices, calculated by RGBXYZ_init()
 */
static double RGBXYZ[3][3], XYZRGB[3][3];

/*
 * This is the inverse of the RGB-to-XYZ matrix
 */
struct RGB const_func XYZ_to_RGB(struct cie_XYZ XYZ)
{
	struct RGB RGB;

	color_mat_multiply((double *)&RGB, XYZRGB, (const double *)&XYZ);
	return RGB;
}

/*
 * These are the values from the XYZ table at the RGB primaries from the
 * data sheet, normalized by their brightness (both taken from the
 * center points of the data sheet):
 *
 * R = 622.5 nm @ 405 mcd
 * G = 523.5 nm @ 690 mcd
 * B = 466.0 nm @ 190 mcd
 * Total:        1285 mcd
 *
 * Normalized values are thus given by:
 * <value> = <table value> * <brightness>/1285 mcd / <table Y>
 */
struct cie_XYZ const_func RGB_to_XYZ(struct RGB RGB)
{
	struct cie_XYZ XYZ;

	color_mat_multiply((double *)&XYZ, RGBXYZ, (const double *)&RGB);
	return XYZ;
}

/*
 * Compute the RGB <-> XYZ transformation matrices.  For pure spectral
 * primaries (as one can expect from LEDs or lasers) this code should
 * be possible to use as-is with modified constants; for nonspectral
 * primaries the XYZ or xyY values will have to be obtained directly
 * some other way.
 */
void RGBXYZ_init(void)
{
	/*
	 * This code assumes the available data specifies a luminous
	 * (photometric) intensity, normally given in candela (cd).
	 * If what we have is a bolometric intensity (W/sr) then
	 * the values need to be scaled using the photometric_factor()
	 * for the given wavelength.
	 */
	struct primary {
		double nm;	/* Wavelength in nm */
		double cd;	/* Intensity in cd */
	};

	/* From the WS2812B data sheet (average of ranges given) */
	const struct primary primary[3] = {
		{ 622.5, 0.405 }, /* Red */
		{ 523.5, 0.690 }, /* Green */
		{ 466.0, 0.190 }  /* Blue */
	};
	double peakcd;		/* Peak (total) intensity in cd */
	double scale;
	int i;

	peakcd = 0.0;
	for (i = 0; i < 3; i++)
		peakcd += primary[i].cd;

	/*
	 * Compute the RGBXYZ matrix.  Note that we already have a
	 * photometric intensity (in cd).  If we have a *bolometric*
	 * intensity (in W/sr) then we need to convert it to a photometric
	 * intensity using the photometric_scale() conversion factor.
	 *
	 * This matrix represents the contribution to the total XYZ
	 * value for *each scale point* of each primary.
	 */
	scale = 1.0/(255.0*peakcd);

	printf("rgbxyz =\n\n");
	for (i = 0; i < 3; i++) {
		struct cie_xy xy = spectral(primary[i].nm);
		struct cie_XYZ XYZ = xyY_to_XYZ(xy, primary[i].cd*scale);

		RGBXYZ[0][i] = XYZ.X;
		RGBXYZ[1][i] = XYZ.Y;
		RGBXYZ[2][i] = XYZ.Z;
	}
	for (i = 0; i < 3; i++) {
		printf(" %22.14e %22.14e %22.14e\n",
		       RGBXYZ[i][0], RGBXYZ[i][1], RGBXYZ[i][2]);
	}

	/*
	 * The inverse matrix provides for XYZ -> RGB conversion
	 */
	color_mat_inverse(XYZRGB, RGBXYZ);
	printf("\nxyzrgb =\n\n");
	for (i = 0; i < 3; i++) {
		printf(" %22.14e %22.14e %22.14e\n",
		       XYZRGB[i][0], XYZRGB[i][1], XYZRGB[i][2]);
	}
	putchar('\n');
}