Notes on Color Spaces and X Window System Jim Carter , 950511 Reference: X Window System ICCCM section 7, "Device Color Characterization" Color names are sought by the server in its RGB database; default is /usr/X11R6/lib/X11/rgb.txt. Client side databases are also possible (specified how?) A proper color is represented as: space:value/value/value Spaces: #rrggbb (Deprecated) red, green, blue in hex, in 0..2^N-1, no gamma correction. Give 1, 2, 3 or 4 digits for each, same for all components. rgb Values are red, green, blue in hex; 0..2^N-1 maps to 0..1 where N = 4*number of digits (possibly different for each component). No gamma correction. rgbi Values are floating point 0..1 for red, green, blue. Xlibwill map rgbi nonlinearly (by interpolation in a table) from rgbi to rgb. CIEXYZ Y in 0..1, X and Z are 0..infinity (?). This is the primary color space. Xlib can map colors linearly from CIEXYZ to rgbi. CIEuvY (see below for more details) CIExyY CIELab CIELuv TekHVC The server's property XDCCC_LINEAR_RGB_MATRICES contains a matrix to map CIEXYZ to rgbi, and XDCCC_LINEAR_RGB_CORRECTION contains something for gamma correction so as to map to rgb. These are loaded by "xcmsdb filename". The file content is not well described in the man page, but the xcmsdb source includes two sample files which have the following format. Blank lines and lines beginning with # are ignored. Fields probably are whitespace separated with newlines probably irrelevant. The file contains: SCREENDATA_BEGIN 1.1 (Format of file; xcmsdb demands this number.) NAME Name of monitor, quotes not needed PART_NUMBER Vendor's part number MODEL Vendor's model number, may have blanks SCREEN_CLASS VIDEO_RGB (not sure what this means) REVISION 1.0 (not sure who cares) #xcmsdb -query does not return the above info so it's probably junked. COLORIMETRIC_BEGIN XYZtoRGB_MATRIX_BEGIN mtx[r,x] mtx[g,x] mtx[b,x] (Data is floating, e.g. -1.521763) mtx[r,y] mtx[g,y] mtx[b,y] mtx[r,z] mtx[g,z] mtx[b,z] XYZtoRGB_MATRIX_END RGBtoXYZ_MATRIX_BEGIN mtx[x,r] mtx[y,r] mtx[z,r] (Inverse of the above matrix) mtx[x,g] mtx[y,g] mtx[z,g] mtx[x,b] mtx[y,b] mtx[z,b] RGBtoXYZ_MATRIX_END #The above matrices vary +-20% between Panasonic and Sony monitors. #The 2nd one appears to have this interpretation: the top row has xyz #components of white (maybe yellow, not red), middle green, bottom blue. COLORIMETRIC_END #In the next line, most likely 0 is the visual ID, where 0 means it pertains #to all visuals without their own table, and 1 means that one table follows #which pertains equally to all three colors. You could have three tables, #one for each color, specifying RED, GREEN, BLUE in that order in place of RGB. #The server interpolates linearly in the table and so it's stupid to have lots #of entries just for a linear ramp. Though xcmsdb reports rgb values in hex, #it can accept them in decimal also. INTENSITY_PROFILE_BEGIN 0 1 INTENSITY_TBL_BEGIN RGB 256 (256 = number of entries. RGB = color) 0x0000 0.000000 0x1212 0.008368 (etc.) 0xffff 1.000000 INTENSITY_TBL_END INTENSITY_PROFILE_END SCREENDATA_END The following is from: Rea, Mark S (ed.), "Lighting Handbook, Reference & Application", Illuminating Society of North America (N.Y.), 1993, ISBN 0-87995-102-8 (with annotations by jimc) CIEXYZ: XYZ are called "tristimulus values". To compute them, multiply the illuminant power spectrum, the reflectance of the paint, and the observer response function (see below), and integrate over wavelength. There is a separate response function for X, Y, Z. Y appears to be a brightness measure whereas X seems to be a yellow component while Z is magenta. More or less, based on the behavior of buggy code (see below). The observer response functions are far from orthogonal. A lot of very careful work was done in the 1930's to define these curves, but the results are not exactly intuitive particularly for people who deal with RGB monitors. XYZ are unnormalized, and conventionally are in 0 to 100, although Xlib wants to see Y in 0.0 to 1.0. Ranges: For what follows I'll assume X, Y, Z in 0 to 100. X and Z can theoretically be greater than Y but for reasonable colors Y is the largest, so I'll calculate ranges "in practice" with X <= Y and Z <= Y. CIExyY is normalized XYZ. Use Y as above, which (if I read this right) is the "value" (for subtractive color) or brightness (for luminous sources). Then compute x = X/(X+Y+Z) y = Y/(X+Y+Z) z = Z/(X+Y+Z) Ranges: 0 to 1 in theory; for subsequent range calculations I will use x in 0 to .5, y in 0 to 1. Reading off a small graph, I get these approx coordinates: x y Color .22 .35 Gray (colorless) (.33, .33 probably better) .62 .35 600 nm red (pure spectral color) .10 .82 520 nm green .06 .12 470 nm blue CIEUVW: These intermediate coordinates are used several places below. The formulae with XYZ args are *not* identically equal to the xyY formulae, though close. I don't know why; that's what was in the book. I will use the XYZ formulae. u = 4X/(X+15Y+3Z) = 4x/(-2x+12y+3) where x = X/(X+Y+Z) etc. v = 6Y/(X+15Y+3Z) = 6y/(-2x+12y+3) Ranges: u in 0 to 4.0 in theory, 0 to 0.25 in practice. v in 0 to 0.4 in theory, 0.3 to 0.4 in practice (ranging from X=Y=Z to X=Z=0). Below, un and vn refer to the coords. of the white reference color, i.e. the light under which a paint is viewed. Typical white points have close to x = y = 0.33, so un = 0.211, vn = 0.316. This un,vn will be used for practical ranges below. The CIEUVW output coordinates are: U* = 13W* (u-un) V* = 13W* (v-vn) W* = 25(Y^.33) - 17 (if Y in 1..100) Ranges: W* in 8 to 99; U* and V* in +-5200 in theory; U* in -270 to +50 and V* in -21 to +108 in practice. CIEUVW is said to be not widely used; CIELuv and CIELab are both used more. CIELuv: (u' and v' refer to the intermediate coords above in CIEUVW.) L* = 116(Y/Yn)^.33 - 16 if Y/Yn > .008856 or L* > 8 L* = 903.29 Y/Yn if Y/Yn < .008856 or L* < 8 u* = 13L* (u'-un') v* = 13L* (v'-vn') Ranges: L* in 0 to 100. u* in +-5200 and v* in +-520 in theory; u* in -275 to +50 and v* in -21 to +109 in practice. CIELab: Let f(q) = q^.333 if q > .008856 or 7.787q + 0.1379 if less. L* = 116 f(Y/Yn) - 16 (same answer as CIELuv) a* = 500(f(X/Xn) - f(Y/Yn)) b* = 200(f(Y/Yn) - f(Z/Zn)) where the n quantities refer to the source color with Yn = 100. Ranges: L* in 0 to 100; a* in +-500 and b* in +-200 in theory; a* in -500 to 0 and b* in 0 to +200 in practice. When you compare colors, the perceptual difference is more or less proportional to abs of the CIELuv or CIELab coordinate differences (Euclidean metric). CIELSH from CIELuv or CIELab: C* (chroma) = sqrt(u^2+v^2) = sqrt(a^2+b^2), book says that these are equal but I don't believe it. s (saturation = C*/13L* h (hue) = atan(u/v) or atan(b/a) -- different origin. L* (lightness) copied directly from input coordinates. Ranges: L in 0 to 100; C* in 0 to 5200 (CIELuv theory) or 0 to 296 (CIELuv practice) or 0 to 538 (CIELab theory and practice); s in 0 to 4, 0.23, or 0.41 (same order). These saturations seem kind of small, probably due to my "practical" limitation that X < Y and Z < Y which may be too stringent. Hue is an angle (units not obvious, degrees assumed) in 0 to 360 degrees, with the origin at u=0 or b=0 as the case may be. These origins are not the same. CIELSH is not recognized by Xlib for color specifications. Colorimetry, summarized from: Weast, Robert C. (ed.), "Handbook of Chemistry and Physics" (63rd ed), Chem. Rubber Publ. Co, 1982. Page E-403. Which in turn is summarizing Judd, Jour. Optical Society of America, v. 23 p. 359 (1933). Who apparently is summarizing the CIE 1931 standard. The following colors are primary standards: x y z 700.0 nm red 0.73467 0.26533 0.00000 546.1 nm green 0.27376 0.71741 0.08833 435.8 nm blue 0.16658 0.00886 0.82456 Noon Sun white 0.34842 0.35161 0.29997 The standard, and the Handbook of Chemistry and Physics, gives a table of the spectral response of a standard observer in the X, Y, Z channels. Here is a qualitative description: Trichromatic component X Y Z Color name Magenta Yellow Blue Peak of response (nm) 600 555 445 Range at 30% max (nm) 545-645+ 500-625 420-485 435-455 From [reference here]: The light output from a CRT is proportional to the grid voltage to the 2.35 power, pretty closely, provided the origin is set very carefully (with the brightness control). The grid voltage is proportional to the raw RGB value (proportionality adjusted with the contrast control). The three guns in the tube can have quite different light per grid volts due to differences in the phosphor (and gun geometry). They should be adjusted at the factory to give close to equal response per input volt (RGB numbers). Reference for Pantone: Eiseman, L and L. Herbert, The Pantone Book of Color, Abrams (NY), 1990, ISBN 0-8109-3711-5. Pantone is a registered trademark of Pantone Inc. What the Xlib is REALLY doing to your colors... Reference: X11R6 sources, xc/lib/X11/*.c Xcms.h: RGB in intensity profile is scaled 0 to 2^16-1. rgbi is 0.0 to 1.0. CIEXYZ is unnormalized (so it says). Experimentally, Y must be in 0 to 1 but X and Z can be arbitrary and have some effect over 1.00, but around 1.33 Z shows clear signs of overflow, but X can go up to 10. CIEuvY each component is in 0.0 to 1.0. CIExyY each component is in 0.0 to 1.0. (.3, .3, Y) gives a pretty neutral black-gray-white axis, as it should. Set up (.08, .36, .24); Y can be any relatively low value. Color is aqua (L=.24, s=.50?). Decrease both x and y by .01 and the color changes suddenly to bright magenta (L=.6, s=.8). .08 is a break point in the CIELuv/CIELab gamma correction function. Significance of .36 is not obvious. When Y is higher the transition does not occur. CIELab, L is in 0 to 100, others unspecified. L in 0 to 7 gives a linear ramp black to white; L in 8 to 100 also gives a ramp (maybe not linear) dark gray to white. "a" seems to be in 0 to 1; above 1 it's constant bright magenta. "b" may be in 0 to 1 or 0 to 2. Seems to saturate at 1 and effects over 1 probably from overflow. When L < 8, a and b have no effect; it's only gray. CIELuv: LRGB.c: The default intensity profile is the same as in xcmsdb's sample1.dcc provided by Tektronix for the Tek 19" Sony CRT inthe Tek4300 (p/n 119-2451-00). The RGBtoXYZ_MATRIX and XYZtoRGB_MATRIX are also the same as sample1.dcc. Subroutine LINEAR_RGB_InitSCCData: 1. Allocate memory, filling it with a copy of the default matrices and intensity profile (or pointers thereto?). 2. Load the Matrix atom (if it exists); store the content scaling by 2^27 signed. 3. Screen white point is set to RGBtoXYZ * (RGBi){1 1 1}. Error if Y differs from 1. 4. Load the Correction atom (if it exists). Format: {VisualID (0 means this table applies to all unspecified visuals); cType (0, 1; see below for formats) Number of tables (1 for all colors, or 3 for red, green, blue) The data }. This repeats for arbitrarily many visuals. Subroutine _XcmsGetTableType0: Format: Number of elements; N (RGB, RGBi pairs)} For format 8 (bytes), RGB is multiplied by 257 and RGBi is divided by 255.0. For format 16 (short), RGB is unchanged and RGBi is divided by 2^16-1. For format 32 (long), RGB is unchanged and RGBi is divided by 2^32-1. Subroutine _XcmsGetTableType1: Format: Number of elements -1; N RGBi values. RGB is the index (0..N-1) * 2^16 / (N-1); RGBi is scaled as for type 0. Note, special case, RGBi = 0 is mapped to RGB = 0 whatever the table says. Matrix * Vector: product[i] = sum( matrix[3i+j]*vec[j] ). Subroutine XcmsCIEXYZToRGBi: 1. compute XYZtoRGB * input. 2. If any component is outside 0..1, do gamut compression, whatever that is. 3. Clip to 0..1 in case of epsilon-size errors. Subroutine XcmsRGBiToCIEXYZ: Return RGBtoXYZ * input. Subroutine XcmsRGBiToRGB and vice versa: Interpolate each component in the table. No impromptu gamma correction. BUG: For reasons unknown, when the Nokia intensity profile is loaded on the server, it is ignored by Xlib and the default intensity profile is used instead. The loaded matrix is used, though. This is true even for the sample2.dcc file that comes with xcmsdb. BUG: CIExyY on the server: set up CIExyY:.24/.0/.75, and run the y coordinate up and down. All colors are about 30% saturated and the hue is cyan at .00; pink at .01-.04; cyan at .05; pink at .06-.08, back to cyan at .09, etc. This is pathetic. BUG: CIEXYZ, set up anything with Y = 0; the whole color is black. In general, the color is proportional to Y whatever X and Z are. This is reminiscent of CIExyY. How the code as written can give this effect is not obvious -- maybe what should be a pointer to the XYZ converter actually points to the xyY converter. This is pathetic.