WPF 控件庫——仿製Chrome的ColorPicker

WPF 控件庫系列博文地址:html

WPF 控件庫——仿製Chrome的ColorPickergit

WPF 控件庫——仿製Windows10的進度條github

WPF 控件庫——輪播控件算法

WPF 控件庫——帶有慣性的ScrollViewer瀏覽器

WPF 控件庫——可拖動選項卡的TabControlide

 

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

相關文章
相關標籤/搜索