CIE 1931 色度圖
CIE 1931 色度圖是一個理想的圖形,如圖 2.所示。理論上的馬蹄形曲線内區域包括了一切實體上能實
現的顔色。在此二維色度圖中,X 軸色度坐标相當于紅原色的比例,Y 軸色度坐标相當于綠原色的比例,并
且有 X+Y+Z=1,由 X、Y 值可得出 Z 值。圖中沒有 Z 軸色度坐标,但 Z 相當于藍原色的比例。
描繪馬蹄形曲線
描繪馬蹄形曲線的方法:選取 46 個光譜軌迹色度坐标。
由于馬蹄形曲線并非随波長變化而均勻變化,是以選取坐标時并未按照等波長間隔選取,而是根據觀
察馬蹄形曲線,得出馬蹄形曲線上長度大概相同的點,對照 CIE 1931 光譜軌迹色度坐标表選取。
将波長 380nm 點作為純藍色點。實際上該點應該為藍色與紅色的混合色:紫色點,但由于紫色是混合
色,繪制時不好控制計算機上 R、B 的比例,是以繪制馬蹄形曲線的時候将該點作為純藍色點。設色度圖中
純藍色點為 B 點、純綠色點(520nm)為 G 點、純紅色點(780nm)為 R 點;則 B 點在計算機上的顔色設為
(0,0,255)、G 點為(0,255,0)、R 點為(255,0,0)。
在波長 380nm 到 520nm 之間,選取 25 個點,然後,用 B 樣條曲線算法用圓滑連續曲線連接配接這 25 個點。
連接配接的時候,顔色變化規律為:B 點到 498nm(由光譜軌迹色度坐标表得到此處為 B、G 比例近似相同點)
顔色從(0,0,255)變化到(0,255,255);498nm 到 G 點顔色從(0,255,255)變化到(0,255,0)。
同理,在波長 520nm 到 780nm 之間,選取 20 個點,然後,用 B 樣條曲線算法用圓滑連續曲線連接配接這 20
個點。連接配接的時候,顔色變化規律為: G 點到 575nm(由光譜軌迹色度坐标表得到此處為 G、R 比例近似相
同點)顔色從(0,255,0)變化到(255,255,0);575nm 到 R 點顔色從(255,255,0)變化到(255,0,
0)。
由于連接配接波長 780 到 380nm 兩點的是直線,是以在這兩點間設 511 個象素點,然後以畫點的方式畫從
紅到藍的直線。其變化規律相似,顔色從(255,0,0)變化到(255,0,255);再從(255,0,255)變
化到(0,0,255)。
為了表現盡可能多的顔色, 在顔色漸變過程中采用了一種“準”線性變換的辦法。即顔色在 B 點到 498nm、
498nm 到 G 點、G 點到 575nm、575nm 到 R 點以及 R 點到 B 點變化時,均為均勻變化。
這樣,便繪出了馬蹄形曲線。有了曲線後,以等能白點 C 點(255,255,255)(色度坐标為 X=0.3333,
Y=0.3333)為中心,向色度圖邊緣上的各點作射線,射線上的點的顔色則在白色與這條射線和馬蹄形曲
線邊界交點的顔色之間連續變化。此時的變化仍采用線性均勻變化。為了去除中間不連續部分,射線掃描
先由綠→藍、綠→紅→藍,再由紅→藍、紅→綠做了兩次掃描,基本做到了連續。
private void XYZ2RGB(float x, float y, float z, ref int r, ref int g, ref int b)
{
double dr, dg, db;
dr = 0.4185 * x - 0.1587 * y - 0.0828 * z;
dg = -0.0912 * x + 0.2524 * y + 0.0157 * z;
db = 0.0009 * x - 0.0025 * y + 0.1786 * z;
double max = 0;
max = dr > dg ? dr : dg;
max = max > db ? max : db;
dr = dr / max * 255;
dg = dg / max * 255;
db = db / max * 255;
dr = dr > 0 ? dr : 0;
dg = dg > 0 ? dg : 0;
db = db > 0 ? db : 0;
if (dr > 255)
{
dr = 255;
}
if (dg > 255)
{
dg = 255;
}
if (db > 255)
{
db = 255;
}
r = (int)(dr + 0.5);
g = (int)(dg + 0.5);
b = (int)(db + 0.5);
}
private void DrawCIE()
{
List<float> mySmallx = new List<float>();
List<float> mySmally = new List<float>();
List<float> mySmallz = new List<float>();
using (FileStream fs = new FileStream("輪廓坐标.txt", FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
lock (fs)
{
StreamReader sr = new StreamReader(fs, Encoding.UTF8);
while (!sr.EndOfStream)
{
string str = sr.ReadLine();
string[] temp = str.Split(new char[] { }, StringSplitOptions.RemoveEmptyEntries);
if (temp.Length < 3) continue;
mySmallx.Add(float.Parse(temp[1]));
mySmally.Add(float.Parse(temp[2]));
}
}
}
//1:選擇3個頂點
PointF p1 = new PointF(); // 代表最下面的點
PointF p2 = new PointF(); // 代表最上面的點
PointF p3 = new PointF(); // 代表最右邊的點
//代表最下面的點
p1.X = 0.173101f;
p1.Y = 0.004774f;
//代表最上面的點
p2.X = 0.082053f;
p2.Y = 0.83409f;
//代表最右邊的點
p3.X = 0.73469f;
p3.Y = 0.26531f;
float k3 = 0, b3 = 0;
k3 = (p3.Y - p1.Y) / (p3.X - p1.X);
b3 = p3.Y - p3.X * k3;
//左邊數組
List<PointF> leftArray = new List<PointF>();
List<PointF> rightArray = new List<PointF>();
PointF tempPonit = new PointF();
for (int i = 0; i < (int)mySmallx.Count(); i++)
{
tempPonit.X = mySmallx[i];
tempPonit.Y = mySmally[i];
if (i < 161)
{
leftArray.Add(tempPonit);
if (i == 160)
{
rightArray.Add(tempPonit);
}
}
else if (i >= 161 && i < 339)
{
rightArray.Add(tempPonit);
}
}
List<PointF> downArray = new List<PointF>();
for (float y = 0.001f; y < 0.265; y += 0.001f)
{
if (y > p1.Y && y < p3.Y)
{
tempPonit.X = (y - b3) / k3;
tempPonit.Y = y;
downArray.Add(tempPonit);
}
}
Bitmap bitmap = new Bitmap(2000, 2000, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
Rectangle dimension = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
BitmapData picData = bitmap.LockBits(dimension, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
float tempx = 0, tempy = 0;
float miny = 0, maxy = 0;
float minx = 0, maxx = 0;
float middlex = 0;
double minvalue1 = 999999;
double minvalue2 = 999999;
int maxindex = 0, minindex = 0;
for (int i = 0; i < picData.Height; i++)
{
tempy = (float)i / (float)picData.Height;
if (tempy < 0.004774 || tempy > 0.83409)
{
continue;
}
for (int j = 0; j < picData.Width; j++)
{
tempx = (float)j / (float)picData.Width;
if (tempx < 0.003636 || tempx > 0.73469)
{
continue;
}
//1、判斷左邊的數組
maxindex = 0;
minindex = 0;
minvalue2 = 999999;
minvalue1 = 999999;
for (int k = 0; k < (int)leftArray.Count(); k++)
{
double value1 = tempy - leftArray[k].Y;
double value2 = leftArray[k].Y - tempy;
if (value1 < minvalue1 && value1 > 0)
{
minvalue1 = value1;
minindex = k;
}
if (value2 < minvalue2 && value2 > 0)
{
minvalue2 = value2;
maxindex = k;
}
}
miny = leftArray[minindex].Y;
minx = leftArray[minindex].X;
maxy = leftArray[maxindex].Y;
maxx = leftArray[maxindex].X;
middlex = minx - (tempy - miny) / (maxy - miny) * (minx - maxx);
if (tempx < middlex || maxindex == minindex)
{
continue;
}
//2、判斷右邊的數組
maxindex = 0;
minindex = 0;
minvalue2 = 999999;
minvalue1 = 999999;
for (int k = 0; k < (int)rightArray.Count(); k++)
{
double value1 = tempy - rightArray[k].Y;
double value2 = rightArray[k].Y - tempy;
if (value1 < minvalue1 && value1 > 0)
{
minvalue1 = value1;
minindex = k;
}
if (value2 < minvalue2 && value2 > 0)
{
minvalue2 = value2;
maxindex = k;
}
}
miny = rightArray[minindex].Y;
minx = rightArray[minindex].X;
maxy = rightArray[maxindex].Y;
maxx = rightArray[maxindex].X;
middlex = minx - (tempy - miny) / (maxy - miny) * (minx - maxx);
if (tempx > middlex || maxindex==minindex)
{
continue;
}
//3、判斷下邊的數組
maxindex = 0;
minindex = 0;
minvalue2 = 999999;
minvalue1 = 999999;
for (int k = 0; k < (int)downArray.Count(); k++)
{
double value1 = tempy - downArray[k].Y;
double value2 = downArray[k].Y - tempy;
if (value1 < minvalue1 && value1 > 0)
{
minvalue1 = value1;
minindex = k;
}
if (value2 < minvalue2 && value2 > 0)
{
minvalue2 = value2;
maxindex = k;
}
}
miny = downArray[minindex].Y;
minx = downArray[minindex].X;
maxy = downArray[maxindex].Y;
maxx = downArray[maxindex].X;
middlex = minx - (tempy - miny) / (maxy - miny) * (minx - maxx);
if (tempx > middlex || maxindex == minindex)
{
continue;
}
float x = 0, y = 0, z = 0;
x = tempx;
y = tempy;
z = 1 - x - y;
int R = 0, G = 0, B = 0;
XYZ2RGB(x, y, z, ref R, ref G, ref B);
unsafe
{
byte* target = (byte*)picData.Scan0.ToPointer();
target[(picData.Width - i) * picData.Stride + 3 * j] = (byte)B;
target[(picData.Width - i) * picData.Stride + 3 * j + 1] = (byte)G;
target[(picData.Width - i) * picData.Stride + 3 * j + 2] = (byte)R;
}
}
}
bitmap.UnlockBits(picData);
bitmap = bitmap.Clone(new Rectangle(0, 200, 1600, 1800), bitmap.PixelFormat);
bitmap.Save("Cie1931.bmp", ImageFormat.Bmp);
}
結果:
項目源碼下載下傳位址