顏色的規律,個人理解 純屬扯淡 見諒。有些網上抄的 炒剩飯。
知識的海洋很深奧 就像一個神奇的盒子 貌似理解了 可能有些東西再過幾年都沒能理解
顏色模式
色彩是由物體所反射光的波長來決定的
RGB加色模式
RGB為三個能量值 能量值的強弱直觀的反映了明與暗 能量值越強越亮
而CMY減色模式是跟RGB互逆的一種方式
介質是白的 然后要把顏料塗上去 展現各種色彩。
物體是什么 物體可以反射光 但同時也要吸收一定的能量值,
所以從本質上看把顏料塗上去是一個變暗的過程。CMY正符合了這種原理。
你看RGB三原色交叉部分不正是CMY嗎 自然界是多么的神奇。
HSB(HSL) 色調飽和度亮度模式
以另外一種不同的理念進行色彩的調配
H色調 0~360 圓環形式 以角度表示
S 飽和度 0~1 之間的小數
L 亮度 0~1 之間的小數
什么是純色
先來看一下windows的拾色器
最上頭黑色框里面那些顏色 最最最頂端的部分。
是不是感覺他們最鮮艷 最惹眼,嗯 因為他們RGB之間的差異化最大
RGB產生顏色的原理就是RGB三個能量值產生差異化 組合。
所以我們才能看到這些花花綠綠的顏色。
純色一個特點那就是最大值跟最小值的差是255 ,差異達到最大。
也就是說RGB一定有一個顏色是0 一個是255,否則不能稱之為純色
純亮度表示就是只能量值沒有差異 也指灰度圖
在液晶屏上以仰視的角度看上面的那幅圖 你會看到底部飽和度比較低的部分。TFT屏幕都是這樣 可視角度比較差
就像這樣的效果:
這就是為什么確定RGB為三基色,其實我也不懂 但是看了上圖估計也不需要過多的技術解釋了吧。
純色的計算
通過觀察windows畫圖板拾色器 就是上面中間那個破圖。我們知道 如果飽和度固定為1 亮度固定為0.5 ,那么色調即純色 純色即色調。純色的定義參見上面。
從RGB值的變化規律可以看出色調的變化是連續平緩 首尾相接的 可以看成是一個360度的圓 紅色是0度。他的變化規律是:
又是看圖
這鬼的規律啊 哪有什么規律 花花綠綠好看吧 有點像披薩,說錯了 有點像燒餅。
三個規律
1至始至終都有一個值是0 一個值是255。
2整個過程中同一時間總是只有一個值在變
3三個數的全排列 那么總共有6種組合
形象點來說
有一個值在增加 增滿過后另一個值再增加 本值再減小 就像這樣從左往右 以長江后浪推前浪 前仆后繼的方式達到原點 有點像合金彈頭游戲里面那種循環的無縫銜接背景 哥們兒你聽懂我在說什么了嗎。其實我也沒搞懂自己在說什么。
通過以上我們寫段代碼枚舉一些個純色
但是首先有一點要明確: 就是那塊披薩 就是60度 值是255
我們把色調分成1~360的表現形式,但是RGB是以(255,255,255)的表現形式 那么他們之間怎么對應呢?那就是60度=255
每前進一度scale+=(255*6)*(1/360)
RGB可以表達255*255*255=16581375種顏色 但是他可以表達的純色只有255*6=1530種
public void getHue() { Graphics gph = Graphics.FromHwnd(this.Handle); int indx = 0; //狀態是Up 顏色是綠色 這已經很明顯了吧,這正是0度時候推進的趨勢 bool high = true;//Up int scale = 1;//0紅色 1綠色 2藍色 int[] cor = { 255, 0, 0 };//0度角 紅色 Color[] cors = new Color[36]; Color result = Color.FromArgb(cor[0], cor[1], cor[2]); int curScale = 0; //推進以便 生成所有色調的顏色表 //這里的30指前進30步 與其他並無關系 //具體前進的范圍為: 步數*(255D * 6 * (每步的大小 / 360D)) //注意每步大小的值不能超過60 因為60=255 scale的值域是[0,255] while (indx < 30) { if (high == true) { curScale = scale % 3; cor[curScale] += (int)(255D * 6 * (10 / 360D)); } else if (high == false) { curScale = (scale - 1) % 3;//< 0 ? 0 : (scale - 1) % 3; cor[curScale] -= (int)(255D * 6 * (10 / 360D)); } gph.DrawString(indx.ToString(), new Font(new FontFamily("宋體"), 9), new SolidBrush(Color.Black), 15 * (indx - 1), 50); if (cor[curScale] < 0) { cor[curScale] = 0; scale++; high = !high; gph.DrawString("▓", new Font(new FontFamily("宋體"), 9), new SolidBrush(Color.Black), 15 * (indx - 1), 90); continue; } else if (cor[curScale] > 255) { cor[curScale] = 255; //scale++; high = !high; gph.DrawString("▇ ", new Font(new FontFamily("宋體"), 9), new SolidBrush(Color.Black), 15 * (indx - 1), 90); continue; } cors[indx] = Color.FromArgb(cor[0], cor[1], cor[2]); gph.FillRectangle(new SolidBrush(Color.FromArgb(cor[0], cor[1], cor[2])), 15 * indx++, 0, 15, 20); gph.DrawString(curScale.ToString(), new Font(new FontFamily("宋體"), 9), new SolidBrush(Color.Black), 15 * (indx - 1), 30); gph.DrawString(scale.ToString(), new Font(new FontFamily("宋體"), 9), new SolidBrush(Color.Black), 15 * (indx - 1), 70); } }
增加他們之間的密度 越密過度越平滑
最平滑的狀況是 scale+=1
越稀 則越粗糙 他的極限是 scale+=(255*6)*(60/360)
即 scale+=255 超過255是無意義的 因為scale的取值是0~255
別看幾句破代碼 調試的時候可是費了我好大勁兒。
我們已經能枚舉所有色調,實際上他是一個從0度(255,0,0)偏移的過程。但是更改色調有沒有一種快速的方式計算呢。難道每確定一個顏色都進行255*6=1530次循環?不會吧。
自然不會 但是最多進行6次循環是必須的 因為網上的公式也要進行6個switch 囧 說明磚家該研究的早就研究過了 咱不可能超過磚家 對吧。
快速計算的方式就是~~~ 加速每次偏轉的值 更改偏轉的次數 嗯 條件是不少於6次。怎么樣換湯不換葯吧~~~ 囧
public Color getHue(int theta)//角度0~360 { int times = theta / 60, Mod = theta % 60; bool high = true; int scale = 1, indx = 0; int[] cor = { 255, 0, 0 }; int curScale = 0; while (indx <times ) { if (high == true) { curScale = scale % 3; cor[curScale] += 255 ; } else if (high == false) { curScale = (scale - 1) % 3; cor[curScale] -= 255 ; } if (cor[curScale] <= 0) { cor[curScale] = 0; scale++; high = !high; } else if (cor[curScale] >= 255) { cor[curScale] = 255; high = !high; } indx++; } if (high == true) cor[scale % 3] += (int)(255D * 6 * (Mod / 360D)) > 255 ? 255 : (int)(255D * 6 * (Mod / 360D)); else if (high == false) cor[(scale - 1) % 3] -= (int)(255D * 6 * (Mod / 360D)) < 0 ? 0 : (int)(255D * 6 * (Mod / 360D)); return Color.FromArgb(cor[0], cor[1], cor[2]); }
純色 飽和度 HSL RGB 他們之間到底有什么奧秘
我們來做個windows畫圖板那樣的拾色器順便 測試下上面函數的正確性
色調有了 然后就是處理飽和度
這個規律比色調那個簡單多了
還是通過觀察拾色器
R2為處理后的值 R1為處理前的值 :R2=R1+(127-R1)*S
public void drawPalette() { Graphics gph = Graphics.FromHwnd(this.Handle); //生成顏色表 Color[] cors = new Color[(int)(360D / 11)]; for (int i = 0; i < 360 - 11; i += 11) cors[(i / 11)] = getHue(i); //飽和度處理 float s = 0;//飽和度 for (int i = 0; i < 20; i++)//10行 10個等級的飽和度 { for (int j = 0; j < cors.Length; j++)//顏色數 { //計算方式 r'=r+(127-r)s Color corTmp = Color.FromArgb((int)(cors[j].R + (127 - cors[j].R) * s), (int)(cors[j].G + (127 - cors[j].G) * s), (int)(cors[j].B + (127 - cors[j].B) * s)); gph.FillRectangle(new SolidBrush(corTmp), 15 * j, 15 * i, 15, 15); } s += (1f / 20); } //this.BackColor = getHue(59);//10 * i++ }
但是有一點要說明下 如果一幅圖像的色調降得很低了接近灰度了 說明他的顏色信息已經損失了 再去還原他的色調是有困難的
上圖雖然已經達到我們要的目的了,但是我們就是愛折騰 就是要讓他不一樣 ~~
把for循環里面的顏色計算代碼改成這樣:
Color corTmp = Color.FromArgb((int)(cors[j].R + (255 - cors[j].R) * s), (int)(cors[j].G + (0 - cors[j].G) * s), (int)(cors[j].B + (0 - cors[j].B) * s));
這反映了一個現象 雖然某些顏色看上去不是紅色
但是他三基色的組成里紅色成分越多被侵蝕就越嚴重 紅色成分越少越抗侵蝕,看 多么的完美 這與photoshop里的調色常識不謀而合。
說道他們的奧秘嘛 ,咳咳咳 。其實也沒什么奧秘
總結下:
飽和度度/彩度 指顏色信息 降低容易 降低到一定程度 還原可就難了。
色調其實是指飽和度為1 亮度為0.5的 這種特例情況 也指純色。純色定義見前面
亮度、飽和度的調整 都是以純色為初始的 所以找出純色很關鍵。
從純色算法以及能量值差異原理可以知道 色調主要跟RGB里的最大值最小值相關 他們之間的差異反映了色調 他們跟另外一個值進行調配 反映了飽和度。最大值的強弱反映了可見度 即亮度。
何以見得呢。
調用.net自帶的函數得到飽和度
Color c = Color.FromArgb(210, 23, 232);
MessageBox.Show(c.GetSaturation().ToString());
輸出結果是0.8196079
你可以試驗一下把210改到(23,232)范圍內的任意值 最后輸出的結果都是一樣的。
網上寫的那些公式
public Color getRGBfromHSV(double _h, double _s, double _v)//色相 飽和度 亮度 { int h1 = (int)(_h / 60 % 6);//確定色調在色帶中的位置 其實就是確定色調 double f = _h / 60 - h1; //確定最大值最小值 亮度 double p = (_v * (1 - _s)); double q = (_v * (1 - f * _s)); double t = (_v * (1 - (1 - f) * _s)); Color cor; //按色調對這些值進行分配 switch (h1) { case 0: cor = Color.FromArgb((int)(_v * 255), (int)(t * 255), (int)(p * 255)); break; case 1: cor = Color.FromArgb((int)(q * 255), (int)(_v * 255), (int)(p * 255)); break; case 2: cor = Color.FromArgb((int)(p * 255), (int)(_v * 255), (int)(t * 255)); break; case 3: cor = Color.FromArgb((int)(p * 255), (int)(q * 255), (int)(_v * 255)); break; case 4: cor = Color.FromArgb((int)(t * 255), (int)(p * 255), (int)(_v * 255)); break; default: cor = Color.FromArgb((int)(_v * 255), (int)(p * 255), (int)(q * 255)); break; } return cor; }
我照着公式的原理寫了段代碼 神了 確實可以用 但是說實話我真沒搞太懂。看問題要研究他的本質 不能老抄別人的。
另一段改自C++的轉換代碼:
public Color GetRGBColorFromHSV(double _h, double _s, double _v)//色相 飽和度 亮度 { double r, g, b; if (_s == 0)//如果飽和度為0則是灰度圖 rgb三個值都將等於亮度 { r = _v; g = _v; b = _v; } else { double[] dp = new double[3]; double xh = _h * 6;//色相x6 ? if (xh == 6) xh = 0;//色環是一個360度的圓環 如果超過最大值則變為0 //int i = (int)(Math.Floor(xh) + 0.1);//色相x6取整數+0.1 ? int i = (int)xh; dp[0] = 1; dp[2] = 0; xh -= i; if (i % 2 != 0)// (i&1) //如果i是奇數 dp[1] = 1 - xh; else dp[1] = xh; //處理色調 for (int n = 0; n < 3; ++n) { dp[n] = (((dp[n]) - 0.5) * (_s) + 0.5); //SATFORMAT(dp[n], _s); } //處理亮度 if (_v == 0.5) { } else if (_v < 0.5) { if (_v < 0) _v = 0; for (int n = 0; n < 3; ++n) dp[n] = ((dp[n]) * (_v) * 2); //BLACKFORMAT(dp[n], _v) } else { if (_v > 1) _v = 1; for (int n = 0; n < 3; ++n) dp[n] = (1 - (1 - (dp[n])) * (1 - (_v)) * 2); //WHITEFORMAT(dp[n], _v); } //三個元素的全排列 ? switch (i) { case 0: r = dp[0]; g = dp[1]; b = dp[2]; break; case 1: r = dp[1]; g = dp[0]; b = dp[2]; break; case 2: r = dp[2]; g = dp[0]; b = dp[1]; break; case 3: r = dp[2]; g = dp[1]; b = dp[0]; break; case 4: r = dp[1]; g = dp[2]; b = dp[0]; break; default: r = dp[0]; g = dp[2]; b = dp[1]; break; } } //return ((int)(b * 255 + .5) << 16) + ((int)(g * 255 + .5) << 8) + (int)(r * 255 + .5); Color cor = Color.FromArgb((int)(r * 255 + 0.5), ((int)(g * 255 + 0.5)), ((int)(b * 255 + 0.5))); return cor; }
調用上面的函數來整點花哨的效果 一個圓環形的色帶:
Class1 c2 = new Class1(); Graphics gph = Graphics.FromHwnd(this.Handle); Color[] cors = new Color[360]; gph.TranslateTransform(200, 200); System.Drawing.Drawing2D.GraphicsPath ph = new System.Drawing.Drawing2D.GraphicsPath(); ph.AddEllipse(-100, -100, 200, 200); ph.AddEllipse(-80, -80, 160, 160); gph.SetClip(ph, System.Drawing.Drawing2D.CombineMode.Intersect); for (int k = 0; k < 360; k++) { cors[k] = c2.GetRGBColorFromHSV(1D/360 * k, 1D, 0.5D); gph.RotateTransform(1); gph.DrawLine(new Pen(new SolidBrush(cors[k]), 2), 0, 0, 100, 0); } gph.FillEllipse(new SolidBrush(Color.Transparent), -80, -80, 160, 160);
亮度部分我暫時不想寫了 累了 下次補上。
亮度0.5是個分水嶺 高於0.5的時候RGB三個值都是增的趨勢 低於0.5時是減的趨勢