实用型ColorPicker的设计与实现

一、前言

ColorPicker,颜色选取器,简称拾色器。
说到拾色器,大家可能就会想到Photoshop, 使用得最多的应该是设计, 对于开发而言,平常要用到拾色器的机会不多。
如果有一天,项目中需要一个拾色器(多用于自定义颜色),该如何入手?
今天且来给大家分享一下拾色器的设计和实现。

二、颜色空间

要实现实用的拾色器,了解下颜色空间是必要的。
颜色空间也称彩色模型(又称彩色空间或彩色系统),它的用途是在某些标准下用通常可接受的方式对彩色加以说明。
关于颜色空间,文末附有一些链接,都是前人的精华总结。
尤其是《色彩空间中的 HSL、HSV、HSB 有什么区别》中的讨论(知乎大神的作答),读完立即醍醐灌顶,茅塞顿开。

本节主要摘录各大神的一些表述,并加以整理。
为了阅读体验,就不逐一附上作者了,具体见文后链接。

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(饱和度)控制纯色中混入白色的量,越往右,值越大,白色越少,颜色纯度越高。


2.5 色相

色相指的是色彩的外相,是在不同波长的光照射下,人眼所感觉不同的颜色,如红色、黄色、蓝色等。
在HSL和HSV色彩空间中,H指的就是色相,是以红色为0°(360°);黄色为60°;绿色为120°;青色为180°;蓝色为240°;品红色为300°。

从上图可以看出,从0°到360°,是一个分段函数,其中,每一段都有一个颜色分量是0,一个分量是1,另一个分量或从0到1,或从1到0。

2.6 HSB转RGB

对用户而言,HSB空间更容易调节,但是对于计算机,用RGB空间渲染会更加方便。
以下是HSB到RGB的转换公式:

图中, 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.1 条形拾色器

需要用到自定义颜色的APP不多,网易APP是其中一个。
点击“个性换肤->自选颜色”,会弹出这样的界面:

页面中间是预览,底部是预定义的调色板;
调色板的最后,是一张多彩的图片,点击后切换成两个颜色条,如下图:

第一个颜色条很眼熟了,就是前面提到的色相;
底下这条,应该是以选取的色相为基础,饱和度为1,明度从0到1渐变。
这两个颜色条组合起来,颜色的取值范围为2.2节中的圆柱的侧面。
取值范围虽然只占颜色空间的一部分,但是也是很有价值的一部分。
可能网易的设计师只想让用户选取鲜明的颜色,所以舍弃了饱和度的调节,同时换来了极大的简洁性。

3.2 环形拾色器

开源的Android拾色器有不少,其中HoloColorPicker是star比较多的一个项目。

该项目把色相做成一个环,底下辅以饱和度和明度的调节,可以说是一个完整的拾色器了(可以选取整个颜色空间的颜色)。
把色相做成色相环,看起来比色相条要更加炫酷一些,但是占用面积变大了。
像网易云音乐的颜色选取,因为要给预览图足够的空间,所以只能用空间占用少的拾色器;
像这种一下占用大半个屏幕的设计,不适合网易云音乐这种选色场景。

3.3 PS拾色器

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, 使得我们可以轻松的绘制各种坐标。
接下来我们结合实例看一下。

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颜色,同时限制编辑框只能输入十六进制,限制输入长度。

五、总结

两类拾色器中,通过条形坐标来选取颜色比较节约空间,通过环形和矩形则相对直观。
具体使用哪一种,要视情况而定:
如果要实时预览效果,那拾色器就不能占太多空间,这时候第一种方案会比较适合;
如果要制作调色板之类的,用第二种方案就比较高效。

限于篇幅,在实现方面没有讲的很细,读者可以具体看项目代码。
代码链接:
https://github.com/No89757/ColorPicker

参考资料:
色彩空间中的 HSL、HSV、HSB 有什么区别
从 RGB 到 HSV 的转换详细介绍
RGB与HSB之间的转换公式
维基百科 - 色相

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,128评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,316评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,737评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,283评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,384评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,458评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,467评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,251评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,688评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,980评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,155评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,818评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,492评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,142评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,382评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,020评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,044评论 2 352

推荐阅读更多精彩内容