WPF 控件庫系列博文地址:html
WPF 控件庫——仿製Chrome的ColorPickergit
WPF 控件庫——仿製Windows10的進度條github
1、觀察google
項目中的一個新需求,須要往控件庫中添加顏色拾取器控件,由於公司暫時尚未UI設計大佬入住,因此就從網上開始找各類模樣的ColorPicker,找來找去我就看上了谷歌瀏覽器自帶的,它長這個樣:spa
看上去不錯,能夠搞!搞以前得觀察一下這裏面可能的一些坑。對WPF而言,圓角陰影等效果都是基本操做,這裏就不說了。.net
首先咱們注意到上圖中有兩個拖動條,一個背景是可見光譜,另外一個背景是顏色漸變和方塊平鋪的疊加。由於需求裏沒有屏幕取色的功能,因此在拖動條左側的拾取圖標能夠去掉,只保留當前顏色預覽,這樣會多出來一大塊空間,能夠考慮將圓形的顏色預覽區域改爲有圓角的矩形。而最上方的顏色拾取區域就比較複雜了,它實際上是三層畫刷的疊加,第一層是洋紅色主色調,第二層是白色到透明的左右漸變,第三層是透明到黑色的上下漸變。因爲WPF的帶透明通道的顏色漸變並不是是標準的,舉個例子,假設有一個從透明到黑色的上下漸變層,在漸變層下方是純紅色背景,那麼理論上漸變開始的顏色是#FFFF0000,漸變結束的顏色是#FF000000,那麼在上下一半處的顏色應該是#FF7F0000(或者是#FF800000,就是簡單的相加除以2),可是在WPF中卻不是這個值(在專業的圖像處理軟件中好比PS中的確是#FF7F0000),若是你不信,咱們如今就作個實驗。設計
2、實驗
打開Blend,新建個WPF項目,設置窗口尺寸爲400*300,爲了方便定位中心點,咱們須要設置 AllowsTransparency="True" , WindowStyle="None" ,接着把主窗口背景改爲純紅色,再添加一層從透明到黑色的上下漸變層,用Border實現,以下圖:
咱們使用標尺,定位中心點(200,150),以下圖:
咱們看看在中心點處的顏色是什麼,用Blend取色獲得的顏色以下:
下面咱們在PS中重現以上操做,看看最後的顏色會是什麼,打開PS新建400*300,分辨率爲72的畫布:
新建純紅色填充圖層和透明到黑色的上下漸變層,再用標尺定位一下中心點:
最後獲得的顏色以下:
咱們該相信誰?固然是PS,畢竟人家是圖像處理科班出身,因此咱們只要用PS作一張從透明到黑色的漸變png就ok了。
3、拖動條背景
我有個強迫症,那就是能不用png就不用png,除非是萬不得已,好比上一節中顏色偏差問題。因此咱們這裏談談那兩個拖動條的背景該怎麼實現。第一個是光譜,簡單觀察其實就是顏色漸變,只不過裏面的 GradientStop 比較多罷了,光譜的XAML代碼以下:
1 <LinearGradientBrush x:Key="ColorPickerRainbowBrush" StartPoint="0,1"> 2 <GradientStop Color="#ff0000"/> 3 <GradientStop Color="#ff00ff" Offset="0.167"/> 4 <GradientStop Color="#0000ff" Offset="0.334"/> 5 <GradientStop Color="#00ffff" Offset="0.501"/> 6 <GradientStop Color="#00ff00" Offset="0.668"/> 7 <GradientStop Color="#ffff00" Offset="0.835"/> 8 <GradientStop Color="#ff0000" Offset="1"/> 9 </LinearGradientBrush>
第二個背景也很簡單,就是普通的 DrawingBrush ,不過可能接觸過它的人很少,簡單的來講當設置屬性 TileMode="Tile" 時,它會使用咱們提供的單位畫筆來平鋪整個畫布,經過觀察google的ColorPicker,咱們發現,這裏的單位畫筆是一深一淺的兩個方塊,和一條不太明顯的分割線組成的,因此最後的代碼以下:
1 <DrawingBrush x:Key="ColorPickerOpacityBrush" Viewport="0,0,12,11" ViewportUnits="Absolute" Stretch="None" TileMode="Tile"> 2 <DrawingBrush.Drawing> 3 <DrawingGroup> 4 <GeometryDrawing Brush="#d0cec7"> 5 <GeometryDrawing.Geometry> 6 <GeometryGroup> 7 <RectangleGeometry Rect="0,0,6,5" /> 8 <RectangleGeometry Rect="6,6,6,5" /> 9 </GeometryGroup> 10 </GeometryDrawing.Geometry> 11 </GeometryDrawing> 12 <GeometryDrawing Brush="#e7e7e2"> 13 <GeometryDrawing.Geometry> 14 <RectangleGeometry Rect="0,5,12,1" /> 15 </GeometryDrawing.Geometry> 16 </GeometryDrawing> 17 </DrawingGroup> 18 </DrawingBrush.Drawing> 19 </DrawingBrush>
至於拖動條的樣式因爲篇幅有限我就不貼出來了。
3、算法
一、顏色的進制轉換
由於涉及到顏色的16進制和10進制的相互轉換,因此須要寫一個簡單的算法加以處理。顏色的16進制轉10進制.net已經給咱們封裝在類型 ColorConverter 中了,只要給靜態方法 ConvertFromString 傳入一個顏色字符串,再將返回值轉換爲 Color 就能實現咱們想要的功能。而從10進制到16進制就太簡單了,微軟都不屑去作,那隻能咱們去實現了,只要一行代碼: $"#{color.A:X2}{color.R:X2}{color.G:X2}{color.B:X2}" 。要注意的是,在WPF中最好將涉及到UI的數據轉換作成轉換器,以便在XAML中使用。
二、根據拖動條在光譜上的位置,改變頂部顏色拾取區域的主色調
該算法用一張gif能簡單的說明:
爲了實現該算法咱們須要先搞清楚光譜的顏色分佈,由於以前已經貼過光譜的畫刷,因此咱們能夠給它加個註釋:
如上圖,我把光譜分紅了6塊,數一數一共是7條豎線,它們分別對應光譜畫刷中的7個 GradientStop ,如今咱們已知拖動條的位置和7處節點處對應的顏色,求拖動條所處位置的顏色就很是簡單了,由於拖動條是個 Slider 控件,咱們能夠把它的最大值設爲6 Maximum="6" ,並從它的 OnValueChanged 事件中獲知它此時的位置,假設此時的值爲1.75,那麼就至關因而落在了編號爲1的方塊中,並且是3/4位置處。這時該怎麼計算此處的顏色呢?因爲編號0和編號1的分割線(左起第二根)處的顏色剛好是第二個 GradientStop 的值#ff00ff(咱們用color1代替),又由於第三個 GradientStop 值爲#0000ff(咱們用color2代替),因此3/4位置處的顏色應該是(color1 -(color1 - color2)* 3 / 4),至此該算法看似完成了,可是谷歌在這基礎上多了一個步驟,詳細請看最後一小節。
三、根據主色調來改變拖動條在光譜上的位置
對,這個算法就是2的逆過程。什麼狀況下會用到呢?仍是看一下gif吧:
既然是逆過程,咱們就要反過來思考,把重點放在顏色上。此次咱們要把光譜的10進制代碼拿來分析,咱們已經知道光譜被7個節點拆分紅6塊顏色漸變區域,用代碼來表示的話就是這樣的:
稍加觀察便可發現,每一塊顏色漸變都只改變三色通道中的一個,好比從(0,0,255)到(0,255,255)改變的是G通道,它從0增長到了255。這說明了什麼?這說明光譜上的顏色都是強迫症,它們的三色通道一定有一個值爲255,也一定有一個值爲0,只有一個通道的值在不停地改變。
假設咱們如今選中了一個顏色#4caf50,接下來該怎麼分析它呢?16進制不適合觀察,咱們先把它轉換成10進制:(76,175,80),能夠發現,G通道175的值最大,而R通道76的值最小,這說明這個顏色比較喜歡G通道,而討厭R通道,對B通道則無所謂,那麼它在光譜上的表現就是處於R通道值最小,G通道值最大,B通道值無所謂的顏色漸變區域,在哪裏呢?經過上圖的代碼能夠判斷應該在(0,255,255)到(0,255,0)這塊,也就是編號3的這塊。至於在塊內的相對位置在上一小節中已經給出了計算方法,這裏再也不贅述。
這裏須要注意的是,有可能咱們選取的顏色是形如(0,0,255)或(0,255,255)這種極值數量不惟一的狀況,針對這種特殊樣本,作好充足的驗證便可,也再也不贅述。
四、根據鼠標位置來改變選取顏色
按照慣例,給張gif:
獲取鼠標位置很簡單,我就不說明了,如今又已知主色調,那麼咱們能夠作出以下示意圖:
如圖,此時主色調爲(255,0,0),假設鼠標位置爲中心點,那麼選取的顏色是什麼?若是不能一步算出,就分而算之。咱們先計算左右兩邊中點的顏色,很簡單,利用以前貼出的算法計算後得出左側中點的顏色爲(127,127,127),右側的爲(127,0,0),故中心點的顏色爲(127,63,63),或者是(127,64,64),主要看你舍入的規則。
五、根據主色調來改變拾取點位置
這裏的gif和小節3中的同樣:
能夠看到,選取一個預置的顏色後,不只僅是光譜位置變了,顏色選取點的位置也變了。假設咱們選取了一個預置顏色#4caf50,它的10進製爲:(76,175,80),再假設此時咱們也知道主色調(也就是顏色拾取區域右上角的顏色),如此一來就和小節3同樣了,只不過從原來的一維變成了二維而已。
六、不太明白谷歌的邏輯
假如給定一個顏色(76,175,80),經過上面5小節的內容,你可能算出來右上角主色調爲(0,255,80),但google的ColorPicker倒是(0,255,10),這不是個特殊狀況,例如再點擊一個預置顏色(244,67,54),根據咱們的算法主色調應該是(255,67,0),但google的結果是(255,17,0),有興趣你能夠多試試一些預置值。
因此google的答案究竟是如何計算而成的?只要嘗試幾組數據,你會發現谷歌是這麼計算非極值通道的值的255*(min-common)/(min-max)。至於爲何要這麼計算,但願瞭解的園友不吝賜教。
4、截圖
5、源碼
本文所討論的顏色拾取器源碼已經在github開源:https://github.com/NaBian/HandyControl