ColorPicker,顏色選取器,簡稱拾色器。 說到拾色器,你們可能就會想到Photoshop, 使用得最多的應該是設計, 對於開發而言,日常要用到拾色器的機會很少。
若是有一天,項目中須要一個拾色器(多用於自定義顏色),該如何入手?
今天且來給你們分享一下拾色器的設計和實現。
java
要實現實用的拾色器,瞭解下顏色空間是必要的。
顏色空間也稱彩色模型(又稱彩色空間或彩色系統),它的用途是在某些標準下用一般可接受的方式對彩色加以說明。
關於顏色空間,文末附有一些連接,都是前人的精華總結。
尤爲是《色彩空間中的 HSL、HSV、HSB 有什麼區別》中的討論(知乎大神的做答),讀完當即醍醐灌頂,茅塞頓開。git
本節主要摘錄各大神的一些表述,並加以整理。
爲了閱讀體驗,就不逐一附上做者了,具體見文後連接。github
RGB是從顏色發光的原理來設計定的,通俗點說它的顏色混合方式就好像有紅、綠、藍三盞燈,當它們的光相互疊合的時候,色彩相混,而亮度卻等於二者亮度之總和,越混合亮度越高,即加法混合。
紅、綠、藍三個顏色通道每種色各分爲256階亮度,在0時「燈」最弱——是關掉的,而在255時「燈」最亮。
當三色灰度數值相同時,產生不一樣灰度值的灰色調,即三色灰度都爲0時,是最暗的黑色調;三色灰度都爲255時,是最亮的白色調。數組
RGB 是對機器很友好的色彩模式,但並不夠人性化,由於咱們對色彩的認識每每是」什麼顏色?鮮豔不鮮豔?亮仍是暗?」。
例如,咱們平時描述顏色,「深紫」,「淺綠」,「明黃」,"暗紅」等,一是交代基礎顏色,二是對顏色自己加以描述。
HSB(HSV) 基於 RGB ,是一個更人性化的表示方法。
H(Hue) 爲色相, 取值範圍:0-360°,基礎顏色。
S(Saturation) 爲飽和度, 取值範圍:0 - 1(0% - 100%), 表示色彩的純度。
B(Brightness)爲明度, 取值範圍:0 - 1(0% - 100%),表示對光量的感知。
明度在某些地方也叫Value,因此就有了HSV,HSV和HSB是同樣的,只是關於明度的叫法不同而已。函數
先看 Photoshop 的 HSB 顏色模型拾色器,以下圖所示,HSB 的 B(明度)控制純色中混入黑色的量,越往上,值越大,黑色越少,顏色明度越高。字體
以下圖所示,HSB 的 S(飽和度)控制純色中混入白色的量,越往右,值越大,白色越少,顏色純度越高。 spa
色相指的是色彩的外相,是在不一樣波長的光照射下,人眼所感受不一樣的顏色,如紅色、黃色、藍色等。
在HSL和HSV色彩空間中,H指的就是色相,是以紅色爲0°(360°);黃色爲60°;綠色爲120°;青色爲180°;藍色爲240°;品紅色爲300°。.net
從上圖能夠看出,從0°到360°,是一個分段函數,其中,每一段都有一個顏色份量是0,一個份量是1,另外一個份量或從0到1,或從1到0。設計
對用戶而言,HSB空間更容易調節,可是對於計算機,用RGB空間渲染會更加方便。
如下是HSB到RGB的轉換公式:3d
圖中, h, s , v分別是色相,飽和度,明度。
有了這個組公式,咱們就能夠理解2.5節的色相條是怎麼來的了。
首先看到第一個公式,[]是取整符號,即 hi = (int) (h/60)% 6 。
令s=1, v=1, 則p=0, q=1-f, t=f。
當i=0(第一段),(r, g, b) = (v, t, p) = (1, f, 0), 而f=h/60-hi =h/60,
也就是,第一段中,r=1, g=h/60, b=0;
特例:當h=60, r=1,g=1,b=0, 混合出黃色。
以此類推。
另外咱們還注意到,當h選定以後,顏色和s,v成線性關係,這一點對後面拾色器的實現很重要。
須要用到自定義顏色的APP很少,網易APP是其中一個。
點擊「個性換膚->自選顏色」,會彈出這樣的界面:
頁面中間是預覽,底部是預約義的調色板;
調色板的最後,是一張多彩的圖片,點擊後切換成兩個顏色條,以下圖:
第一個顏色條很眼熟了,就是前面提到的色相;
底下這條,應該是以選取的色相爲基礎,飽和度爲1,明度從0到1漸變。
這兩個顏色條組合起來,顏色的取值範圍爲2.2節中的圓柱的側面。
取值範圍雖然只佔顏色空間的一部分,可是也是頗有價值的一部分。
可能網易的設計師只想讓用戶選取鮮明的顏色,因此捨棄了飽和度的調節,同時換來了極大的簡潔性。
開源的Android拾色器有很多,其中HoloColorPicker是star比較多的一個項目。
該項目把色相作成一個環,底下輔以飽和度和明度的調節,能夠說是一個完整的拾色器了(能夠選取整個顏色空間的顏色)。
把色相作成色相環,看起來比色相條要更加炫酷一些,可是佔用面積變大了。
像網易雲音樂的顏色選取,由於要給預覽圖足夠的空間,因此只能用空間佔用少的拾色器;
像這種一下佔用大半個屏幕的設計,不適合網易雲音樂這種選色場景。
Photoshop的常規拾色器是矩形構成的「飽和度-明度」選取面板,以及色相條(而後也能夠經過設置換成色相輪)。
Photoshop做爲專業的圖像編輯軟件,拾色器無疑是很強大的。
同時電腦顯示器的面積畢竟比手機要大不少,鼠標的選取精確度也比手指觸摸屏幕要精確,
因此Photoshop的拾色器能夠大開大合,提供各類面板,顯示全面的參數。
經過前面三個小節的分析,咱們能夠看出,拾色器的實現要點爲:
以HSB顏色空間爲基礎,經過條形,環形,矩形等座標來調節HSB各份量的值,達成顏色的選取。
首先第一個要解決的問題就是,顏色空間的轉換計算。
幸運的是,SDK 的 Color 類提供HSB(HSV)和RGB之間的轉換方法:
public static void colorToHSV(@ColorInt int color, @Size(3) float hsv[]) public static int HSVToColor(@Size(3) float hsv[]) 複製代碼
而後要解決的第二個問題是,座標的繪製。
一樣,SDK也提供了各類Shader, 使得咱們能夠輕鬆的繪製各類座標。
接下來咱們結合實例看一下。
也不賣關子了,先上效果圖吧:
和網易雲音樂同樣,都是在下方顯示面板,有預置的調色板,調色板最後的方塊能夠跳轉去自定義暗色;
不一樣之處在於,比之多了透明度和純度的調節,如此,能夠選擇整個顏色空間,以及alpha通道。
以上實現的關鍵點在於,色相、飽和度(爲了字體長度相同,以「純度」做爲title),以及明度的繪製。
前面咱們提到,色相是分段線性變化的,所以,咱們能夠利用 LinearGradient 來繪製。
public LinearGradient(float x0, float y0, float x1, float y1, @NonNull @ColorInt int colors[], @Nullable float positions[], @NonNull TileMode tile) 複製代碼
給LinearGradient的color數組福祉,只需列出色相在分段交界的顏色便可:
private static final int[] COLORS = new int[] {
0xFFFF0000,
0xFFFFFF00,
0xFF00FF00,
0xFF00FFFF,
0xFF0000FF,
0xFFFF00FF,
0xFFFF0000,
};
複製代碼
前面2.6節提到,h肯定以後, 顏色和s,v成線性關係,所以,也能夠經過LinearGradient來繪製飽和度和明度。
例如,繪製飽和度時,只需計算hsv第二份量(hsv[1], 也就是s)等於0和等於1時的顏色值,做爲LinearGradient的colors的參數,便可繪製在當前h, v值對應的s的變化(也就是飽和度對應的顏色條)。
hsv[1] = 0f;
colors[0] = Color.HSVToColor(hsv);
hsv[1] = 1f;
colors[1] = Color.HSVToColor(hsv);
複製代碼
明度的繪製以此類推。
色相在0°和360°對應的都是紅色,首位相接,因此不少時候會被作成色相環(Photoshop中叫色相輪)。
飽和度和明度,若是合成一個二維座標,會更加直觀,這樣也是Photoshop的方案。
要繪製色相環,須要用到另外一個Shader:
public SweepGradient(float cx, float cy, @NonNull @ColorInt int colors[], @Nullable float positions[]) 複製代碼
用法很簡單,把上一節給出的 COLORS 代入 SweepGradient 的 colors 便可。
而要合成飽和度和明度,能夠用ComposeShader:
private Shader getSVShader() {
if (mValShader == null) {
mValShader = new LinearGradient(
mSVRect.left, mSVRect.top,
mSVRect.left, mSVRect.bottom,
Color.WHITE, Color.BLACK, Shader.TileMode.CLAMP);
}
if (mShaderHSV[0] != mHSV[0] || mComposeShader == null) {
mShaderHSV[0] = mHSV[0];
Shader satShader = new LinearGradient(
mSVRect.left, mSVRect.top,
mSVRect.right, mSVRect.top,
Color.WHITE, Color.HSVToColor(mShaderHSV), Shader.TileMode.CLAMP);
mComposeShader = new ComposeShader(mValShader, satShader, PorterDuff.Mode.MULTIPLY);
}
return mComposeShader;
}
複製代碼
ComposeShader能夠組合兩個Shader, 由於顏色和s, v是線性關係,因此須要組合兩個LinearGradient。
第一個LinearGradient從左上角到左下角,從白到黑;
第二個LinearGradient從左上角到右上角,從白到色相的顏色。
效果以下:
既然拾色部分已經佔了這麼多空間了,因此乾脆把剩下的空間也用上,來作數據面板;
還加一個編輯框,能夠手動輸入RGB顏色,同時限制編輯框只能輸入十六進制,限制輸入長度。
兩類拾色器中,經過條形座標來選取顏色比較節約空間,經過環形和矩形則相對直觀。
具體使用哪種,要視狀況而定:
若是要實時預覽效果,那拾色器就不能佔太多空間,這時候第一種方案會比較適合;
若是要製做調色板之類的,用第二種方案就比較高效。
限於篇幅,在實現方面沒有講的很細,讀者能夠具體看項目代碼。
代碼連接: github.com/No89757/Col…
參考資料:
色彩空間中的 HSL、HSV、HSB 有什麼區別
從 RGB 到 HSV 的轉換詳細介紹
RGB與HSB之間的轉換公式
維基百科 - 色相