누구든지 광 주파수를 RGB 값으로 변환하는 공식을 알고 있습니까?
누구든지 광 주파수를 RGB 값으로 변환하는 공식을 알고 있습니까?
답변:
다음은 전체 변환 프로세스에 대한 자세한 설명입니다. http://www.fourmilab.ch/documents/specrend/ . 소스 코드 포함!
게으른 사람들 (나와 같은)의 경우 @ user151323의 답변에서 찾은 코드의 Java 구현이 있습니다 (즉 Spectra Lab Report 에서 찾은 파스칼 코드의 간단한 번역 ).
static private final double Gamma = 0.80;
static private final double IntensityMax = 255;
/**
* Taken from Earl F. Glynn's web page:
* <a href="http://www.efg2.com/Lab/ScienceAndEngineering/Spectra.htm">Spectra Lab Report</a>
*/
public static int[] waveLengthToRGB(double Wavelength) {
double factor;
double Red, Green, Blue;
if((Wavelength >= 380) && (Wavelength < 440)) {
Red = -(Wavelength - 440) / (440 - 380);
Green = 0.0;
Blue = 1.0;
} else if((Wavelength >= 440) && (Wavelength < 490)) {
Red = 0.0;
Green = (Wavelength - 440) / (490 - 440);
Blue = 1.0;
} else if((Wavelength >= 490) && (Wavelength < 510)) {
Red = 0.0;
Green = 1.0;
Blue = -(Wavelength - 510) / (510 - 490);
} else if((Wavelength >= 510) && (Wavelength < 580)) {
Red = (Wavelength - 510) / (580 - 510);
Green = 1.0;
Blue = 0.0;
} else if((Wavelength >= 580) && (Wavelength < 645)) {
Red = 1.0;
Green = -(Wavelength - 645) / (645 - 580);
Blue = 0.0;
} else if((Wavelength >= 645) && (Wavelength < 781)) {
Red = 1.0;
Green = 0.0;
Blue = 0.0;
} else {
Red = 0.0;
Green = 0.0;
Blue = 0.0;
}
// Let the intensity fall off near the vision limits
if((Wavelength >= 380) && (Wavelength < 420)) {
factor = 0.3 + 0.7 * (Wavelength - 380) / (420 - 380);
} else if((Wavelength >= 420) && (Wavelength < 701)) {
factor = 1.0;
} else if((Wavelength >= 701) && (Wavelength < 781)) {
factor = 0.3 + 0.7 * (780 - Wavelength) / (780 - 700);
} else {
factor = 0.0;
}
int[] rgb = new int[3];
// Don't want 0^x = 1 for x <> 0
rgb[0] = Red == 0.0 ? 0 : (int)Math.round(IntensityMax * Math.pow(Red * factor, Gamma));
rgb[1] = Green == 0.0 ? 0 : (int)Math.round(IntensityMax * Math.pow(Green * factor, Gamma));
rgb[2] = Blue == 0.0 ? 0 : (int)Math.round(IntensityMax * Math.pow(Blue * factor, Gamma));
return rgb;
}
Wavelength<=439
하는 것이 좋습니다 Wavelength<440
.
일반적인 아이디어:
1 단계와 2 단계는 다를 수 있습니다.
표 또는 분석적 근사치 (@Tarc 및 @Haochen Xie에서 제안)로 사용할 수있는 몇 가지 색상 일치 함수가 있습니다 . 부드러운 정확한 결과가 필요한 경우 테이블이 가장 좋습니다.
단일 RGB 색 공간이 없습니다. 여러 변환 행렬 과 다른 종류의 감마 보정을 사용할 수 있습니다.
아래는 내가 최근에 생각 해낸 C # 코드입니다. "CIE 1964 표준 관찰자"테이블 및 sRGB 매트릭스 + 감마 보정에 대한 선형 보간을 사용합니다 .
static class RgbCalculator {
const int
LEN_MIN = 380,
LEN_MAX = 780,
LEN_STEP = 5;
static readonly double[]
X = {
0.000160, 0.000662, 0.002362, 0.007242, 0.019110, 0.043400, 0.084736, 0.140638, 0.204492, 0.264737,
0.314679, 0.357719, 0.383734, 0.386726, 0.370702, 0.342957, 0.302273, 0.254085, 0.195618, 0.132349,
0.080507, 0.041072, 0.016172, 0.005132, 0.003816, 0.015444, 0.037465, 0.071358, 0.117749, 0.172953,
0.236491, 0.304213, 0.376772, 0.451584, 0.529826, 0.616053, 0.705224, 0.793832, 0.878655, 0.951162,
1.014160, 1.074300, 1.118520, 1.134300, 1.123990, 1.089100, 1.030480, 0.950740, 0.856297, 0.754930,
0.647467, 0.535110, 0.431567, 0.343690, 0.268329, 0.204300, 0.152568, 0.112210, 0.081261, 0.057930,
0.040851, 0.028623, 0.019941, 0.013842, 0.009577, 0.006605, 0.004553, 0.003145, 0.002175, 0.001506,
0.001045, 0.000727, 0.000508, 0.000356, 0.000251, 0.000178, 0.000126, 0.000090, 0.000065, 0.000046,
0.000033
},
Y = {
0.000017, 0.000072, 0.000253, 0.000769, 0.002004, 0.004509, 0.008756, 0.014456, 0.021391, 0.029497,
0.038676, 0.049602, 0.062077, 0.074704, 0.089456, 0.106256, 0.128201, 0.152761, 0.185190, 0.219940,
0.253589, 0.297665, 0.339133, 0.395379, 0.460777, 0.531360, 0.606741, 0.685660, 0.761757, 0.823330,
0.875211, 0.923810, 0.961988, 0.982200, 0.991761, 0.999110, 0.997340, 0.982380, 0.955552, 0.915175,
0.868934, 0.825623, 0.777405, 0.720353, 0.658341, 0.593878, 0.527963, 0.461834, 0.398057, 0.339554,
0.283493, 0.228254, 0.179828, 0.140211, 0.107633, 0.081187, 0.060281, 0.044096, 0.031800, 0.022602,
0.015905, 0.011130, 0.007749, 0.005375, 0.003718, 0.002565, 0.001768, 0.001222, 0.000846, 0.000586,
0.000407, 0.000284, 0.000199, 0.000140, 0.000098, 0.000070, 0.000050, 0.000036, 0.000025, 0.000018,
0.000013
},
Z = {
0.000705, 0.002928, 0.010482, 0.032344, 0.086011, 0.197120, 0.389366, 0.656760, 0.972542, 1.282500,
1.553480, 1.798500, 1.967280, 2.027300, 1.994800, 1.900700, 1.745370, 1.554900, 1.317560, 1.030200,
0.772125, 0.570060, 0.415254, 0.302356, 0.218502, 0.159249, 0.112044, 0.082248, 0.060709, 0.043050,
0.030451, 0.020584, 0.013676, 0.007918, 0.003988, 0.001091, 0.000000, 0.000000, 0.000000, 0.000000,
0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000,
0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000,
0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000,
0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000,
0.000000
};
static readonly double[]
MATRIX_SRGB_D65 = {
3.2404542, -1.5371385, -0.4985314,
-0.9692660, 1.8760108, 0.0415560,
0.0556434, -0.2040259, 1.0572252
};
public static byte[] Calc(double len) {
if(len < LEN_MIN || len > LEN_MAX)
return new byte[3];
len -= LEN_MIN;
var index = (int)Math.Floor(len / LEN_STEP);
var offset = len - LEN_STEP * index;
var x = Interpolate(X, index, offset);
var y = Interpolate(Y, index, offset);
var z = Interpolate(Z, index, offset);
var m = MATRIX_SRGB_D65;
var r = m[0] * x + m[1] * y + m[2] * z;
var g = m[3] * x + m[4] * y + m[5] * z;
var b = m[6] * x + m[7] * y + m[8] * z;
r = Clip(GammaCorrect_sRGB(r));
g = Clip(GammaCorrect_sRGB(g));
b = Clip(GammaCorrect_sRGB(b));
return new[] {
(byte)(255 * r),
(byte)(255 * g),
(byte)(255 * b)
};
}
static double Interpolate(double[] values, int index, double offset) {
if(offset == 0)
return values[index];
var x0 = index * LEN_STEP;
var x1 = x0 + LEN_STEP;
var y0 = values[index];
var y1 = values[1 + index];
return y0 + offset * (y1 - y0) / (x1 - x0);
}
static double GammaCorrect_sRGB(double c) {
if(c <= 0.0031308)
return 12.92 * c;
var a = 0.055;
return (1 + a) * Math.Pow(c, 1 / 2.4) - a;
}
static double Clip(double c) {
if(c < 0)
return 0;
if(c > 1)
return 1;
return c;
}
}
400-700 nm 범위의 결과 :
이것은 오래된 질문이고 이미 몇 가지 좋은 대답을 얻었지만 내 응용 프로그램에서 이러한 변환 기능을 구현하려고 할 때 이미 여기에 나열된 알고리즘에 만족하지 않았고 자체 조사를 수행하여 좋은 결과를 얻었습니다. 그래서 저는 새로운 답변을 게시 할 것입니다.
몇 가지 연구 끝에 CIE XYZ Color Matching Functions에 대한 Simple Analytic Approximations라는 논문 을 접하고 도입 된 다중 로브 조각 별 가우스 피팅 알고리즘을 애플리케이션에 채택하려고했습니다. 이 논문에서는 파장을 해당 XYZ 값 으로 변환하는 기능 만 설명 했기 때문에 sRGB 색 공간에서 XYZ를 RGB로 변환하는 기능을 구현하여 결합했습니다. 결과는 환상적이고 공유 할 가치가 있습니다.
/**
* Convert a wavelength in the visible light spectrum to a RGB color value that is suitable to be displayed on a
* monitor
*
* @param wavelength wavelength in nm
* @return RGB color encoded in int. each color is represented with 8 bits and has a layout of
* 00000000RRRRRRRRGGGGGGGGBBBBBBBB where MSB is at the leftmost
*/
public static int wavelengthToRGB(double wavelength){
double[] xyz = cie1931WavelengthToXYZFit(wavelength);
double[] rgb = srgbXYZ2RGB(xyz);
int c = 0;
c |= (((int) (rgb[0] * 0xFF)) & 0xFF) << 16;
c |= (((int) (rgb[1] * 0xFF)) & 0xFF) << 8;
c |= (((int) (rgb[2] * 0xFF)) & 0xFF) << 0;
return c;
}
/**
* Convert XYZ to RGB in the sRGB color space
* <p>
* The conversion matrix and color component transfer function is taken from http://www.color.org/srgb.pdf, which
* follows the International Electrotechnical Commission standard IEC 61966-2-1 "Multimedia systems and equipment -
* Colour measurement and management - Part 2-1: Colour management - Default RGB colour space - sRGB"
*
* @param xyz XYZ values in a double array in the order of X, Y, Z. each value in the range of [0.0, 1.0]
* @return RGB values in a double array, in the order of R, G, B. each value in the range of [0.0, 1.0]
*/
public static double[] srgbXYZ2RGB(double[] xyz) {
double x = xyz[0];
double y = xyz[1];
double z = xyz[2];
double rl = 3.2406255 * x + -1.537208 * y + -0.4986286 * z;
double gl = -0.9689307 * x + 1.8757561 * y + 0.0415175 * z;
double bl = 0.0557101 * x + -0.2040211 * y + 1.0569959 * z;
return new double[] {
srgbXYZ2RGBPostprocess(rl),
srgbXYZ2RGBPostprocess(gl),
srgbXYZ2RGBPostprocess(bl)
};
}
/**
* helper function for {@link #srgbXYZ2RGB(double[])}
*/
private static double srgbXYZ2RGBPostprocess(double c) {
// clip if c is out of range
c = c > 1 ? 1 : (c < 0 ? 0 : c);
// apply the color component transfer function
c = c <= 0.0031308 ? c * 12.92 : 1.055 * Math.pow(c, 1. / 2.4) - 0.055;
return c;
}
/**
* A multi-lobe, piecewise Gaussian fit of CIE 1931 XYZ Color Matching Functions by Wyman el al. from Nvidia. The
* code here is adopted from the Listing 1 of the paper authored by Wyman et al.
* <p>
* Reference: Chris Wyman, Peter-Pike Sloan, and Peter Shirley, Simple Analytic Approximations to the CIE XYZ Color
* Matching Functions, Journal of Computer Graphics Techniques (JCGT), vol. 2, no. 2, 1-11, 2013.
*
* @param wavelength wavelength in nm
* @return XYZ in a double array in the order of X, Y, Z. each value in the range of [0.0, 1.0]
*/
public static double[] cie1931WavelengthToXYZFit(double wavelength) {
double wave = wavelength;
double x;
{
double t1 = (wave - 442.0) * ((wave < 442.0) ? 0.0624 : 0.0374);
double t2 = (wave - 599.8) * ((wave < 599.8) ? 0.0264 : 0.0323);
double t3 = (wave - 501.1) * ((wave < 501.1) ? 0.0490 : 0.0382);
x = 0.362 * Math.exp(-0.5 * t1 * t1)
+ 1.056 * Math.exp(-0.5 * t2 * t2)
- 0.065 * Math.exp(-0.5 * t3 * t3);
}
double y;
{
double t1 = (wave - 568.8) * ((wave < 568.8) ? 0.0213 : 0.0247);
double t2 = (wave - 530.9) * ((wave < 530.9) ? 0.0613 : 0.0322);
y = 0.821 * Math.exp(-0.5 * t1 * t1)
+ 0.286 * Math.exp(-0.5 * t2 * t2);
}
double z;
{
double t1 = (wave - 437.0) * ((wave < 437.0) ? 0.0845 : 0.0278);
double t2 = (wave - 459.0) * ((wave < 459.0) ? 0.0385 : 0.0725);
z = 1.217 * Math.exp(-0.5 * t1 * t1)
+ 0.681 * Math.exp(-0.5 * t2 * t2);
}
return new double[] { x, y, z };
}
내 코드는 Java 8로 작성되었지만 Java 및 기타 언어의 하위 버전으로 이식하는 것은 어렵지 않습니다.
transfer
DoubleUnaryOperator가하는 일 에 대해 실제로 잘못 기억 했으므로 이전 의견의 설명이 올바르지 않으므로 새 코드를 확인하십시오.
1.
단지 1 만 종류가 될 것 double
대신에int
파장 을 RGB 값으로 변환하는 것에 대해 이야기하고 있습니다.
여기를보세요, 아마도 귀하의 질문에 답할 것입니다. 소스 코드와 몇 가지 설명으로이 작업을 수행 할 수있는 유틸리티가 있습니다.
방법 1
이것은 약간 정리되고 @ haochen-xie의 C ++ 11 버전을 테스트했습니다. 이 방법으로 사용할 수있는 가시 광선 파장으로 0을 1로 변환하는 기능도 추가했습니다. 아래에 하나의 헤더 파일에 넣고 종속성없이 사용할 수 있습니다. 이 버전은 여기에서 유지 됩니다 .
#ifndef common_utils_OnlineStats_hpp
#define common_utils_OnlineStats_hpp
namespace common_utils {
class ColorUtils {
public:
static void valToRGB(double val0To1, unsigned char& r, unsigned char& g, unsigned char& b)
{
//actual visible spectrum is 375 to 725 but outside of 400-700 things become too dark
wavelengthToRGB(val0To1 * (700 - 400) + 400, r, g, b);
}
/**
* Convert a wavelength in the visible light spectrum to a RGB color value that is suitable to be displayed on a
* monitor
*
* @param wavelength wavelength in nm
* @return RGB color encoded in int. each color is represented with 8 bits and has a layout of
* 00000000RRRRRRRRGGGGGGGGBBBBBBBB where MSB is at the leftmost
*/
static void wavelengthToRGB(double wavelength, unsigned char& r, unsigned char& g, unsigned char& b) {
double x, y, z;
cie1931WavelengthToXYZFit(wavelength, x, y, z);
double dr, dg, db;
srgbXYZ2RGB(x, y, z, dr, dg, db);
r = static_cast<unsigned char>(static_cast<int>(dr * 0xFF) & 0xFF);
g = static_cast<unsigned char>(static_cast<int>(dg * 0xFF) & 0xFF);
b = static_cast<unsigned char>(static_cast<int>(db * 0xFF) & 0xFF);
}
/**
* Convert XYZ to RGB in the sRGB color space
* <p>
* The conversion matrix and color component transfer function is taken from http://www.color.org/srgb.pdf, which
* follows the International Electrotechnical Commission standard IEC 61966-2-1 "Multimedia systems and equipment -
* Colour measurement and management - Part 2-1: Colour management - Default RGB colour space - sRGB"
*
* @param xyz XYZ values in a double array in the order of X, Y, Z. each value in the range of [0.0, 1.0]
* @return RGB values in a double array, in the order of R, G, B. each value in the range of [0.0, 1.0]
*/
static void srgbXYZ2RGB(double x, double y, double z, double& r, double& g, double& b) {
double rl = 3.2406255 * x + -1.537208 * y + -0.4986286 * z;
double gl = -0.9689307 * x + 1.8757561 * y + 0.0415175 * z;
double bl = 0.0557101 * x + -0.2040211 * y + 1.0569959 * z;
r = srgbXYZ2RGBPostprocess(rl);
g = srgbXYZ2RGBPostprocess(gl);
b = srgbXYZ2RGBPostprocess(bl);
}
/**
* helper function for {@link #srgbXYZ2RGB(double[])}
*/
static double srgbXYZ2RGBPostprocess(double c) {
// clip if c is out of range
c = c > 1 ? 1 : (c < 0 ? 0 : c);
// apply the color component transfer function
c = c <= 0.0031308 ? c * 12.92 : 1.055 * std::pow(c, 1. / 2.4) - 0.055;
return c;
}
/**
* A multi-lobe, piecewise Gaussian fit of CIE 1931 XYZ Color Matching Functions by Wyman el al. from Nvidia. The
* code here is adopted from the Listing 1 of the paper authored by Wyman et al.
* <p>
* Reference: Chris Wyman, Peter-Pike Sloan, and Peter Shirley, Simple Analytic Approximations to the CIE XYZ Color
* Matching Functions, Journal of Computer Graphics Techniques (JCGT), vol. 2, no. 2, 1-11, 2013.
*
* @param wavelength wavelength in nm
* @return XYZ in a double array in the order of X, Y, Z. each value in the range of [0.0, 1.0]
*/
static void cie1931WavelengthToXYZFit(double wavelength, double& x, double& y, double& z) {
double wave = wavelength;
{
double t1 = (wave - 442.0) * ((wave < 442.0) ? 0.0624 : 0.0374);
double t2 = (wave - 599.8) * ((wave < 599.8) ? 0.0264 : 0.0323);
double t3 = (wave - 501.1) * ((wave < 501.1) ? 0.0490 : 0.0382);
x = 0.362 * std::exp(-0.5 * t1 * t1)
+ 1.056 * std::exp(-0.5 * t2 * t2)
- 0.065 * std::exp(-0.5 * t3 * t3);
}
{
double t1 = (wave - 568.8) * ((wave < 568.8) ? 0.0213 : 0.0247);
double t2 = (wave - 530.9) * ((wave < 530.9) ? 0.0613 : 0.0322);
y = 0.821 * std::exp(-0.5 * t1 * t1)
+ 0.286 * std::exp(-0.5 * t2 * t2);
}
{
double t1 = (wave - 437.0) * ((wave < 437.0) ? 0.0845 : 0.0278);
double t2 = (wave - 459.0) * ((wave < 459.0) ? 0.0385 : 0.0725);
z = 1.217 * std::exp(-0.5 * t1 * t1)
+ 0.681 * std::exp(-0.5 * t2 * t2);
}
}
};
} //namespace
#endif
375nm에서 725nm까지의 색상 플롯은 다음과 같습니다.
이 방법의 한 가지 문제는 400-700nm 사이에서만 작동하고 그 밖에서는 급격히 검은 색으로 떨어 졌다는 사실입니다. 또 다른 문제는 더 좁은 파란색입니다.
비교를 위해 아래는 maxmax.com에서 Vision FAQ의 색상입니다.
나는 이것을 사용하여 각 픽셀이 깊이 값을 미터 단위로 나타내는 깊이 맵을 시각화했습니다.
방법 2
이것은 Aeash Partow 의 bitmap_image 단일 파일 헤더 전용 라이브러리의 일부로 구현됩니다 .
inline rgb_t convert_wave_length_nm_to_rgb(const double wave_length_nm)
{
// Credits: Dan Bruton http://www.physics.sfasu.edu/astro/color.html
double red = 0.0;
double green = 0.0;
double blue = 0.0;
if ((380.0 <= wave_length_nm) && (wave_length_nm <= 439.0))
{
red = -(wave_length_nm - 440.0) / (440.0 - 380.0);
green = 0.0;
blue = 1.0;
}
else if ((440.0 <= wave_length_nm) && (wave_length_nm <= 489.0))
{
red = 0.0;
green = (wave_length_nm - 440.0) / (490.0 - 440.0);
blue = 1.0;
}
else if ((490.0 <= wave_length_nm) && (wave_length_nm <= 509.0))
{
red = 0.0;
green = 1.0;
blue = -(wave_length_nm - 510.0) / (510.0 - 490.0);
}
else if ((510.0 <= wave_length_nm) && (wave_length_nm <= 579.0))
{
red = (wave_length_nm - 510.0) / (580.0 - 510.0);
green = 1.0;
blue = 0.0;
}
else if ((580.0 <= wave_length_nm) && (wave_length_nm <= 644.0))
{
red = 1.0;
green = -(wave_length_nm - 645.0) / (645.0 - 580.0);
blue = 0.0;
}
else if ((645.0 <= wave_length_nm) && (wave_length_nm <= 780.0))
{
red = 1.0;
green = 0.0;
blue = 0.0;
}
double factor = 0.0;
if ((380.0 <= wave_length_nm) && (wave_length_nm <= 419.0))
factor = 0.3 + 0.7 * (wave_length_nm - 380.0) / (420.0 - 380.0);
else if ((420.0 <= wave_length_nm) && (wave_length_nm <= 700.0))
factor = 1.0;
else if ((701.0 <= wave_length_nm) && (wave_length_nm <= 780.0))
factor = 0.3 + 0.7 * (780.0 - wave_length_nm) / (780.0 - 700.0);
else
factor = 0.0;
rgb_t result;
const double gamma = 0.8;
const double intensity_max = 255.0;
#define round(d) std::floor(d + 0.5)
result.red = static_cast<unsigned char>((red == 0.0) ? red : round(intensity_max * std::pow(red * factor, gamma)));
result.green = static_cast<unsigned char>((green == 0.0) ? green : round(intensity_max * std::pow(green * factor, gamma)));
result.blue = static_cast<unsigned char>((blue == 0.0) ? blue : round(intensity_max * std::pow(blue * factor, gamma)));
#undef round
return result;
}
375-725nm의 파장 플롯은 다음과 같습니다.
따라서 이것은 400-725nm에서 더 유용합니다. 방법 1과 동일한 깊이 맵을 시각화하면 다음과 같습니다. 이 코드에서 더 깊이 보지 않은 사소한 버그를 나타내는 검은 선에 대한 명백한 문제가 있습니다. 또한 제비꽃은이 방법에서 조금 더 좁아 져 멀리있는 물체의 대비가 낮아집니다.
#!/usr/bin/ghci
ångstrømsfromTHz terahertz = 2997924.58 / terahertz
tristimulusXYZfromÅngstrøms å=map(sum.map(stimulus))[
[[1056,5998,379,310],[362,4420,160,267],[-65,5011,204,262]],
[[821,5688,469,405],[286,5309,163,311]],
[[1217,4370,118,360],[681,4590,260,138]]]
where stimulus[ω,μ,ς,σ]=ω/1000*exp(-((å-μ)/if å<μ then ς else σ)^2/2)
standardRGBfromTristimulusXYZ xyz=
map(gamma.sum.zipWith(*)(gamutConfine xyz))[
[3.2406,-1.5372,-0.4986],[-0.9689,1.8758,0.0415],[0.0557,-0.2040,1.057]]
gamma u=if u<=0.0031308 then 12.92*u else (u**(5/12)*211-11)/200
[red,green,blue,black]=
[[0.64,0.33],[0.3,0.6],[0.15,0.06],[0.3127,0.3290,0]]
ciexyYfromXYZ xyz=if xyz!!1==0 then black else map(/sum xyz)xyz
cieXYZfromxyY[x,y,l]=if y==0 then black else[x*l/y,l,(1-x-y)*l/y]
gamutConfine xyz=last$xyz:[cieXYZfromxyY[x0+t*(x1-x0),y0+t*(y1-y0),xyz!!1]|
x0:y0:_<-[black],x1:y1:_<-[ciexyYfromXYZ xyz],i<-[0..2],
[x2,y2]:[x3,y3]:_<-[drop i[red,green,blue,red]],
det<-[(x0-x1)*(y2-y3)-(y0-y1)*(x2-x3)],
t <-[((x0-x2)*(y2-y3)-(y0-y2)*(x2-x3))/det|det/=0],0<=t,t<=1]
sRGBfromÅ=standardRGBfromTristimulusXYZ.tristimulusXYZfromÅngstrøms
x s rgb=concat["\ESC[48;2;",
intercalate";"$map(show.(17*).round.(15*).max 0.min 1)rgb,
"m",s,"\ESC[49m"]
spectrum=concatMap(x" ".sRGBfromÅ)$takeWhile(<7000)$iterate(+60)4000
main=putStrLn spectrum