天天看點

C#繪制CIE1931色度圖

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);
}
           

結果:

C#繪制CIE1931色度圖

項目源碼下載下傳位址