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;
