實用型ColorPicker的設計與實現

1、前言

ColorPicker,顏色選取器,簡稱拾色器。 說到拾色器,你們可能就會想到Photoshop, 使用得最多的應該是設計, 對於開發而言,日常要用到拾色器的機會很少。
若是有一天,項目中須要一個拾色器(多用於自定義顏色),該如何入手?
今天且來給你們分享一下拾色器的設計和實現。
java

2、顏色空間

要實現實用的拾色器,瞭解下顏色空間是必要的。
顏色空間也稱彩色模型(又稱彩色空間或彩色系統),它的用途是在某些標準下用一般可接受的方式對彩色加以說明。
關於顏色空間,文末附有一些連接,都是前人的精華總結。
尤爲是《色彩空間中的 HSL、HSV、HSB 有什麼區別》中的討論(知乎大神的做答),讀完當即醍醐灌頂,茅塞頓開。git

本節主要摘錄各大神的一些表述,並加以整理。
爲了閱讀體驗,就不逐一附上做者了,具體見文後連接。github

2.1 RGB空間

RGB是從顏色發光的原理來設計定的,通俗點說它的顏色混合方式就好像有紅、綠、藍三盞燈,當它們的光相互疊合的時候,色彩相混,而亮度卻等於二者亮度之總和,越混合亮度越高,即加法混合。
紅、綠、藍三個顏色通道每種色各分爲256階亮度,在0時「燈」最弱——是關掉的,而在255時「燈」最亮。
當三色灰度數值相同時,產生不一樣灰度值的灰色調,即三色灰度都爲0時,是最暗的黑色調;三色灰度都爲255時,是最亮的白色調。數組

2.2 HSB空間

RGB 是對機器很友好的色彩模式,但並不夠人性化,由於咱們對色彩的認識每每是」什麼顏色?鮮豔不鮮豔?亮仍是暗?」。
例如,咱們平時描述顏色,「深紫」,「淺綠」,「明黃」,"暗紅」等,一是交代基礎顏色,二是對顏色自己加以描述。
HSB(HSV) 基於 RGB ,是一個更人性化的表示方法。
H(Hue) 爲色相, 取值範圍:0-360°,基礎顏色。
S(Saturation) 爲飽和度, 取值範圍:0 - 1(0% - 100%), 表示色彩的純度。
B(Brightness)爲明度, 取值範圍:0 - 1(0% - 100%),表示對光量的感知。
明度在某些地方也叫Value,因此就有了HSV,HSV和HSB是同樣的,只是關於明度的叫法不同而已。函數

  • 色相環的每一種顏色,在RGB空間中,最多隻有兩個顏色通道(r, g, b)大於0,因此色相環的顏色是最純淨的
  • 明度決定了RBG三個份量的大小,也就是決定了光亮的大小,在感知層面,就是明暗的區別。
  • 飽和度爲0的顏色,r,g,b相等, 當明度爲0時爲黑色,明度爲1時爲白色,大於0小於1時爲灰色;
    飽和度爲1時,顏色值僅取決於色相和明度,而明度只控制RGB份量的大小,因此顏色仍是純淨的;
    飽和度在0到1之間時,爲飽和度爲0的顏色(黑灰白)和飽和度爲1的顏色的線性插值,越靠近1顏色越純淨。

2.3 明度

先看 Photoshop 的 HSB 顏色模型拾色器,以下圖所示,HSB 的 B(明度)控制純色中混入黑色的量,越往上,值越大,黑色越少,顏色明度越高。字體

2.4 飽和度

以下圖所示,HSB 的 S(飽和度)控制純色中混入白色的量,越往右,值越大,白色越少,顏色純度越高。 spa

2.5 色相

色相指的是色彩的外相,是在不一樣波長的光照射下,人眼所感受不一樣的顏色,如紅色、黃色、藍色等。
在HSL和HSV色彩空間中,H指的就是色相,是以紅色爲0°(360°);黃色爲60°;綠色爲120°;青色爲180°;藍色爲240°;品紅色爲300°。.net

從上圖能夠看出,從0°到360°,是一個分段函數,其中,每一段都有一個顏色份量是0,一個份量是1,另外一個份量或從0到1,或從1到0。設計

2.6 HSB轉RGB

對用戶而言,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成線性關係,這一點對後面拾色器的實現很重要。

3、拾色器的設計

3.1 條形拾色器

須要用到自定義顏色的APP很少,網易APP是其中一個。
點擊「個性換膚->自選顏色」,會彈出這樣的界面:

頁面中間是預覽,底部是預約義的調色板;
調色板的最後,是一張多彩的圖片,點擊後切換成兩個顏色條,以下圖:

第一個顏色條很眼熟了,就是前面提到的色相;
底下這條,應該是以選取的色相爲基礎,飽和度爲1,明度從0到1漸變。
這兩個顏色條組合起來,顏色的取值範圍爲2.2節中的圓柱的側面。
取值範圍雖然只佔顏色空間的一部分,可是也是頗有價值的一部分。
可能網易的設計師只想讓用戶選取鮮明的顏色,因此捨棄了飽和度的調節,同時換來了極大的簡潔性。

3.2 環形拾色器

開源的Android拾色器有很多,其中HoloColorPicker是star比較多的一個項目。

該項目把色相作成一個環,底下輔以飽和度和明度的調節,能夠說是一個完整的拾色器了(能夠選取整個顏色空間的顏色)。
把色相作成色相環,看起來比色相條要更加炫酷一些,可是佔用面積變大了。
像網易雲音樂的顏色選取,由於要給預覽圖足夠的空間,因此只能用空間佔用少的拾色器;
像這種一下佔用大半個屏幕的設計,不適合網易雲音樂這種選色場景。

3.3 PS拾色器

Photoshop的常規拾色器是矩形構成的「飽和度-明度」選取面板,以及色相條(而後也能夠經過設置換成色相輪)。

Photoshop做爲專業的圖像編輯軟件,拾色器無疑是很強大的。
同時電腦顯示器的面積畢竟比手機要大不少,鼠標的選取精確度也比手指觸摸屏幕要精確,
因此Photoshop的拾色器能夠大開大合,提供各類面板,顯示全面的參數。

4、技術實現

經過前面三個小節的分析,咱們能夠看出,拾色器的實現要點爲:
以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, 使得咱們能夠輕鬆的繪製各類座標。
接下來咱們結合實例看一下。

4.1 仿網易雲音樂

也不賣關子了,先上效果圖吧:

和網易雲音樂同樣,都是在下方顯示面板,有預置的調色板,調色板最後的方塊能夠跳轉去自定義暗色;
不一樣之處在於,比之多了透明度和純度的調節,如此,能夠選擇整個顏色空間,以及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);
複製代碼

明度的繪製以此類推。

4.2 仿Photoshop

色相在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顏色,同時限制編輯框只能輸入十六進制,限制輸入長度。

5、總結

兩類拾色器中,經過條形座標來選取顏色比較節約空間,經過環形和矩形則相對直觀。
具體使用哪種,要視狀況而定:
若是要實時預覽效果,那拾色器就不能佔太多空間,這時候第一種方案會比較適合;
若是要製做調色板之類的,用第二種方案就比較高效。

限於篇幅,在實現方面沒有講的很細,讀者能夠具體看項目代碼。
代碼連接: github.com/No89757/Col…

參考資料:
色彩空間中的 HSL、HSV、HSB 有什麼區別
從 RGB 到 HSV 的轉換詳細介紹
RGB與HSB之間的轉換公式
維基百科 - 色相

相關文章
相關標籤/搜索