1.前言:
在許多應用中,例如LED照明就需要利用色度圖來觀察發光的顏色,色溫等趨勢,本文討論如何利用C#程式繪製色度圖,為簡化程式,僅討論CIE 1931 標準色度2度視角觀察者的色度圖,色彩系統採用NTSC。
2.說明:
RGB色彩空間系統參考wiki的說明
http://en.wikipedia.org/wiki/RGB_color_space
http://en.wikipedia.org/wiki/Standard_illuminant#Illuminants_B_and_C
CIE1931色彩空間
http://en.wikipedia.org/wiki/CIE_1931_color_space
http://www.fourmilab.ch/documents/specrend/
CIE 1931 標準色度2度視角觀察者XYZ函數的資料集
http://cvrl.ioo.ucl.ac.uk/
CIE色度圖Delphi程式碼可參考
http://www.efg2.com/Lab/Graphics/Colors/Chromaticity.htm
CIE 1931 2° Standard Observer Color Matching Functions
private void DrawColorMatchingFunctions() { //使用SteemaTeeChart繪製Color Matching Functions Curve tChart1.Chart.Aspect.View3D = false; Steema.TeeChart.Styles.Line line1 = new Steema.TeeChart.Styles.Line(); Steema.TeeChart.Styles.Line line2 = new Steema.TeeChart.Styles.Line(); Steema.TeeChart.Styles.Line line3 = new Steema.TeeChart.Styles.Line(); line1.LinePen.Width = 3; line2.LinePen.Width = 3; line3.LinePen.Width = 3; line1.Color = Color.Red; line2.Color = Color.Green; line3.Color = Color.Blue; tChart1.Chart.Series.Add(line1); tChart1.Chart.Series.Add(line2); tChart1.Chart.Series.Add(line3); //讀取資料 DataTable dt = TxtConvertToDataTable(@"d:\tmp\da.csv", "tmp", ","); for (int i = 0; i < dt.Rows.Count; i++) { line1.Add(double.Parse(dt.Rows[i]["Lamda"].ToString()), double.Parse(dt.Rows[i]["Xt"].ToString())); line2.Add(double.Parse(dt.Rows[i]["Lamda"].ToString()), double.Parse(dt.Rows[i]["Yt"].ToString())); line3.Add(double.Parse(dt.Rows[i]["Lamda"].ToString()), double.Parse(dt.Rows[i]["Zt"].ToString())); } //設定繪圖區顯示設定 tChart1.Chart.Series[0].Title = "CIE-X(Red)"; tChart1.Chart.Series[1].Title = "CIE-Y(Green)"; tChart1.Chart.Series[2].Title = "CIE-Z(Blue)"; tChart1.Chart.Axes.Bottom.Title.Text = "Wavelength [nm]"; tChart1.Chart.Axes.Left.Title.Text = "Tristimulus values"; tChart1.Header.Text = "CIE 1931 2° Standard Observer Color Matching Functions"; tChart1.Header.TextAlign = StringAlignment.Center; tChart1.Legend.Alignment = Steema.TeeChart.LegendAlignments.Top; tChart1.Legend.TopLeftPos = -16; tChart1.Chart.Axes.Left.SetMinMax(0, 2); }
Color Matching Functions繪圖:
The CIE 1931 Color Space Chromaticity Diagram
NTSC系統類別
public class NTSCsystem { public static double xRed = 0.67; public static double yRed = 0.33; public static double xGreen = 0.21; public static double yGreen = 0.71; public static double xBlue = 0.14; public static double yBlue = 0.08; public static double xWhite = 0.310; public static double yWhite = 0.316; }
繪製馬蹄形圖
private void DrawHorseArea(DataTable dt, Graphics gg, Image img) { double x = 0, y = 0, z = 0; double[] xy; int[] rgb = new Int32[3]; for (int i = 0; i <= img.Width; i+=1) { for (int j = 0; j <= img.Height; j+=1) { xy = PixelToCoordinate(i, j, img.Width, img.Height); x = xy[0]; y = xy[1]; z = 1 - x - y; if (IsInsideHorseShape(dt, x, y) == true) { rgb = rgbData(x, y, z); SolidBrush s = new SolidBrush(Color.FromArgb(rgb[0], rgb[1], rgb[2])); RectangleF rec = new RectangleF(i, img.Height - j, 1, 1); gg.FillRectangle(s, rec); } } } }
繪製馬蹄形邊界圖
private void DrawBoundary(DataTable dt, Graphics gg, Image img) { int lamda = 0; double StartPointX = 0, StartPointY = 0, StartPointZ = 0; double EndPointX = 0, EndPointY = 0, EndPointZ = 0; double PositionX = 0, PositionY = 0; int[] rgb = new Int32[3]; //繪製馬蹄形邊界 for (int i = 0; i < dt.Rows.Count - 1; i++) { StartPointX = double.Parse(dt.Rows[i]["x"].ToString()); StartPointY = double.Parse(dt.Rows[i]["y"].ToString()); StartPointZ = double.Parse(dt.Rows[i]["z"].ToString()); EndPointX = double.Parse(dt.Rows[i + 1]["x"].ToString()); EndPointY = double.Parse(dt.Rows[i + 1]["y"].ToString()); EndPointZ = double.Parse(dt.Rows[i + 1]["z"].ToString()); rgb = rgbData(StartPointX, StartPointY, StartPointZ); SolidBrush s = new SolidBrush(Color.FromArgb(rgb[0], rgb[1], rgb[2])); gg.DrawLine(new Pen(s, 1), (float)StartPointX * img.Width, img.Height - (float)StartPointY * img.Height, (float)EndPointX * img.Width, img.Height - (float)EndPointY * img.Height); } //波長從420nm到680nm,繪製mark for (int i = 60; i <= 320; i++) { lamda = int.Parse(dt.Rows[i]["Lamda"].ToString()); if (lamda % 10 == 0)//每10nm繪製mark { if (lamda >= 460 && lamda <= 630)//430nm到450nm以及640nm到670nm不繪製,避免重疊 { PositionX = double.Parse(dt.Rows[i]["x"].ToString()); PositionY = double.Parse(dt.Rows[i]["y"].ToString()); SolidBrush s = new SolidBrush(Color.White); gg.DrawLine(new Pen(s), (float)PositionX * img.Width, img.Height - (float)PositionY * img.Height, (float)PositionX * img.Width, img.Height - (float)PositionY * img.Height - 10); gg.DrawString(lamda.ToString(), new Font("Arial", 7), s, (float)PositionX * img.Width - 5, img.Height - (float)PositionY * img.Height - 20); } if (lamda == 420 || lamda == 680) { PositionX = double.Parse(dt.Rows[i]["x"].ToString()); PositionY = double.Parse(dt.Rows[i]["y"].ToString()); SolidBrush s = new SolidBrush(Color.White); gg.DrawLine(new Pen(s), (float)PositionX * img.Width, img.Height - (float)PositionY * img.Height, (float)PositionX * img.Width, img.Height - (float)PositionY * img.Height - 10); gg.DrawString(lamda.ToString(), new Font("Arial", 7), s, (float)PositionX * img.Width - 5, img.Height - (float)PositionY * img.Height - 20); } } } }
繪製馬蹄形邊界底線圖
private void DrawBottomLine(DataTable dt, Graphics gg, Image img) { double x = 0, y = 0, z = 0; double x1 = 0, y1 = 0, z1 = 0; int[] rgb = new Int32[3]; double minWavelengthXCoordinate = 0; double minWavelengthYCoordinate = 0; double maxWavelengthXCoordinate = 0; double maxWavelengthYCoordinate = 0; //360nm (x,y) minWavelengthXCoordinate = double.Parse(dt.Rows[0]["x"].ToString()); minWavelengthYCoordinate = double.Parse(dt.Rows[0]["y"].ToString()); //830nm (x,y) maxWavelengthXCoordinate = double.Parse(dt.Rows[dt.Rows.Count - 1]["x"].ToString()); maxWavelengthYCoordinate = double.Parse(dt.Rows[dt.Rows.Count - 1]["y"].ToString()); double XCoordinateDistance = maxWavelengthXCoordinate - minWavelengthXCoordinate; double YCoordinateDistance = maxWavelengthYCoordinate - minWavelengthYCoordinate; double Slope = YCoordinateDistance / XCoordinateDistance; for (double i = minWavelengthXCoordinate; i < maxWavelengthXCoordinate; i += 0.002) { //直線方程式: y = (x - xmin) * Slope + ymin x = i; y = (x - minWavelengthXCoordinate) * Slope + minWavelengthYCoordinate; z = 1 - x - y; x1 = i + 0.002; y1 = (x1 - minWavelengthXCoordinate) * Slope + minWavelengthYCoordinate; z1 = 1 - x1 - y1; rgb = rgbData(x, y, z); SolidBrush s = new SolidBrush(Color.FromArgb(rgb[0], rgb[1], rgb[2])); gg.DrawLine(new Pen(s, 1), (float)x * img.Width, img.Height - (float)y * img.Height, (float)x1 * img.Width, img.Height - (float)y1 * img.Height); } }
繪製色溫曲線
private void DrawColorTemperatureCurve(DataTable dt, Graphics gg, Image img) { string temp = ""; double StartPointX = 0, StartPointY = 0; double EndPointX = 0, EndPointY = 0; double PositionX = 0, PositionY = 0; for (int i = 0; i < dt.Rows.Count - 1; i++) { temp = dt.Rows[i]["Temp"].ToString(); StartPointX = double.Parse(dt.Rows[i]["x"].ToString()); StartPointY = double.Parse(dt.Rows[i]["y"].ToString()); EndPointX = double.Parse(dt.Rows[i + 1]["x"].ToString()); EndPointY = double.Parse(dt.Rows[i + 1]["y"].ToString()); PositionX = double.Parse(dt.Rows[i]["x"].ToString()); PositionY = double.Parse(dt.Rows[i]["y"].ToString()); SolidBrush s = new SolidBrush(Color.FromArgb(255, 255, 255)); gg.DrawLine(new Pen(s, 1), (float)StartPointX * img.Width, img.Height - (float)StartPointY * img.Height, (float)EndPointX * img.Width, img.Height - (float)EndPointY * img.Height); if (i != 4 && i != 8 && i != 10 && i != 12) { gg.DrawString(temp.ToString(), new Font("Arial", 5), s, (float)PositionX * img.Width - 5, img.Height - (float)PositionY * img.Height + 10); SolidBrush ss = new SolidBrush(Color.FromArgb(255, 0, 0)); RectangleF rec = new RectangleF((float)PositionX * img.Width - 2, img.Height - (float)PositionY * img.Height - 2, 4, 4); gg.FillEllipse(ss, rec); } else//將重疊的文字置於色溫線上方 { gg.DrawString(temp.ToString(), new Font("Arial", 5), s, (float)PositionX * img.Width - 10, img.Height - (float)PositionY * img.Height - 15); SolidBrush ss = new SolidBrush(Color.FromArgb(255, 0, 0)); RectangleF rec = new RectangleF((float)PositionX * img.Width - 2, img.Height - (float)PositionY * img.Height - 2, 4, 4); gg.FillEllipse(ss, rec); } } }
繪圖:
3.應用:
//讀取檔案 DataTable da = TxtConvertToDataTable(@"D:\TMP\da.csv", "tmp", ","); DataTable db = TxtConvertToDataTable(@"D:\TMP\db.csv", "tmp", ","); //定義影像檔 Image img = new Bitmap(500, 500); //在image上繪圖 Graphics gg = Graphics.FromImage(img); //繪圖品質設定 gg.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias; gg.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality; gg.CompositingQuality = CompositingQuality.HighQuality; gg.SmoothingMode = SmoothingMode.AntiAlias; gg.InterpolationMode = InterpolationMode.High; //先將背景色設定為黑色 gg.FillRectangle(new SolidBrush(Color.Black), 0, 0, 500, 500); //繪製馬蹄形圖 DrawHorseArea(da, gg, img); //繪製馬蹄形邊界圖 DrawBoundary(da, gg, img); //繪製馬蹄形邊界底線圖 DrawBottomLine(da, gg, img); //繪製色溫曲線 DrawColorTemperatureCurve(db, gg, img); //顯示影像 pictureBox1.Image = img;