一,简介
1.1 计算机的颜色通常有两种表示方式:
光源模式RGB(Red=红, Green=绿, Blue=蓝),数值0-255
印刷模式CMYK(Cyan=青, Magenta=品红, Yellow=黄, Black=黑),数值1-100
任何颜色都是由RGB或CMYK混合出来的,再加上透明度的变化,就是我们所能看到的全部颜色
1.2 HSV 模式(色相,饱和度,明度)
H(hue)代表色相:在0~360°的标准色轮上,色相是按位置度量的。在通常的使用中,色相是由颜色名称标识的,比如红、绿或橙色。黑色和白色无色相。
S(saturation)表示饱和度:表示色彩的纯度,为0时为灰色。白、黑和其他灰色色彩都没有饱和度的。在最大饱和度时,每一色相具有最纯的色光。取值范围0~100%。数值越大,颜色中的灰色越少,颜色越鲜艳,呈现一种从灰度到纯色的变化。
V(value)表示明度度:其作用是控制色彩的明暗变化。它同样使用了 0% 至 100% 的取值范围。数值越小,色彩越暗,越接近于黑色;数值越大,色彩越亮,越接近于白色。
1.3 颜色的表示方式十进制
ARGB(透明度,红,绿,蓝)
白色:rgb(255,255,255)
黑色:rgb(0,0,0)
红色:rgb(255,0,0)
绿色:rgb(0,255,0)
蓝色:rgb(0,0,255)
青色:rgb(0,255,255)
紫色:rgb(255,0,255)
1.4 颜色的表示方式十六进制
0 1 2 3 4 5 6 7 8 9 a b c d e f
00表示没有,相当于rgb中的0
ff表示最大,相当于rgb中255
白色:rgb(FFFFFF)
黑色:rgb(00000)
红色:rgb(FF0000)
绿色:rgb(00FF00)
蓝色:rgb(0000FF)
青色:rgb(00FFFF)
紫色:rgb(FF00FF)
二 常用的选择器
2.1 方块选择器,RGB选择器也是最常用选择器
x轴方向是色相(0~360°),y轴方向是亮度(0-100%)
这里面有几个概念:
RGB(0-255),我们最终赋给控件的颜色
HSV(色相,饱和度,明度),一张图片的颜色范围,下面的其实也是一张图片
XY坐标(颜色在图片的坐标位置),比如我们点击一个位置,要先获取坐标。
转换关系:
RGB转坐标,比如我们需要定位颜色在选择器上的位置
第一步,RGB转HSV
//RGB颜色
int color = Color.rgb(RGBArray[1], RGBArray[2], RGBArray[3]);
//HSV颜色数组
float[] hsv = new float[3];
Color.colorToHSV(color, hsv);
//色相
float hue = hsv[0];
//饱和度
float saturation = hsv[1];
//明度
float value= hsv[2];
第二步,HSV转坐标
//x坐标表示色相(范围0-360),把选择器宽度平分360份,也就是360个轴,x=选择器宽度/360*当前的色相
int x = (int) colorPickerWidth / 360f * hue;
//y坐标表示饱和度,范围0-100),把选择器高度平分100份,也就是100个轴,y=选择器高度/100*当前的饱和度
int y = (int) colorPickerHeight - colorPickerHeight / 100f * saturation;
//设置选择器标点的坐标位置
colorPickerView.setCoordinate(x, y);
坐标转RGB,比如点击色盘上一个位置,需要获取整个位置的颜色值
第一步坐标转HSV,这里需要一个三角函数反正切公式,
//获取xy中心点坐标
x = x - getWidth() * 0.5f;
y = y - getHeight() * 0.5f;
//勾股定理,求斜边边长,求根公式Math.sqrt(x的平方+y的平方)
double r = Math.sqrt(x * x + y * y);
//半径
float radius = Math.min(getWidth(), getHeight()) * 0.5f;
//定义hsv数组
float[] hsv = {0, 0, 1};
//求角度
hsv[0] = (float) (Math.atan2(y, -x) / Math.PI * 180f) + 180;
//求明度
hsv[1] = Math.max(0f, Math.min(1f, (float) (r / radius)));
第二步,hsv转RGB
//HSV转颜色
Color.HSVToColor(hsv);
/** 颜色转十六进制 比如#FFFFFF*/
public static String getHexCode(@ColorInt int color) {
int a = Color.alpha(color);
int r = Color.red(color);
int g = Color.green(color);
int b = Color.blue(color);
return String.format(Locale.getDefault(), "%02X%02X%02X%02X", a, r, g, b);
}
/** 颜色转RGB数组,比如(255,255,255) */
public static int[] getColorARGB(@ColorInt int color) {
int[] argb = new int[4];
argb[0] = Color.alpha(color);
argb[1] = Color.red(color);
argb[2] = Color.green(color);
argb[3] = Color.blue(color);
return argb;
}
2.2 圆盘选择器
跟方块选择器差不多
色相0-360°,0从顶部开始
明度0-100%,0从中心开始
颜色转hsv就不多说了,下面是hsv转坐标
//圆形选择器的宽高
colorPickerWidth = colorPickerView.getMeasuredWidth();
colorPickerHeight = colorPickerView.getMeasuredHeight();
//圆形的便半径
float centerX = colorPickerWidth * 0.5f;
float centerY = colorPickerHeight * 0.5f;
//当前明度距离圆形的长度=明度(50%)*选择器圆形半径
float radius = hsvArray[1] * Math.min(centerX, centerY);
//根据明度的半径和色相的角度,计算出颜色所在的坐标
//即三角函数,已知边长和圆角,计算坐标
int pointX = (int) (radius * Math.cos(Math.toRadians(90 - hsvArray[0])) + centerX);
int pointY = (int) (-radius * Math.sin(Math.toRadians(90 - hsvArray[0])) + centerY);
坐标转颜色
当然可以把上面步骤反向一下,也有个快捷方式,我们知道计算中的图片其实都是有像素组成的,每个像素就是一种颜色,所以图片可以说本来就是有颜色构成的。
我们只要获取坐标点所在的像素就能或者这个像素的颜色,比如获取第一个像素
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.iv_200x200);
int color_0_0 = bitmap.getPixel(0, 0);//获取第1行,第1个像素颜色
2.3 XY选择器,cie-xy色度图,也称舌形图
坐标范围
X: 0.1600 – 0.6800
Y: 0.0700 – 0.6900
这种是最复杂的一个,需要把选择范围限制在黑色三角形之内,这就需要用到高数种的一个公式,判断任意一点是否在三角形内。
面积算法法:如果一个点在三角形内,其与三角形的三个点构成的三个子三角形的面积等于大三角形的面积。否则,大于大三角形的面积。所以,这个问题就转化成如何在知道三角形的三个点的情况下,求这个三角形的面积的问题了。
方式一,首先可以计算出每条边的长度及周长,我们就可以利用海伦公式计算面积,然后进行比较。
方式二,向量法,先求出这个三角形的对应的平行四边形的面积。然后这个面积的1/2就是三角形的面积了。
先随意选择两个点,如B、C通过其坐标相减得向量(B,C)。记得谁减另一个就是指向谁。然后求出其中一个点和剩下一个点的向量。这两个向量的叉乘的便是平行四边形的面积。除以2就是三角形的面积。(注意这里是叉乘 (cross product),而非点乘(dot product))。
向量之间的积分为两种:叉乘和点乘。叉乘求面积,点乘求投影。这是两者的意义。而且,叉乘理论得到的是一个向量,而点乘得到的是一个标量。
实践源码:
//更新x坐标色温位置
private void updateColorXPick() {
if (!isColorXListener) {
colorPickerX = colorPickerWidth / 8000f * seekbarAxisX.getProgress();
int selectX = (int) colorPickerX;
int selectY = (int) colorPickerY;
//计算坐标是否超出三角形范围,如果超出需要沿着边线运动
float xValue = Float.parseFloat(tvAxisX.getText().toString());
float yValue = Float.parseFloat(tvAxisY.getText().toString());
POINT p = new POINT(xValue, yValue);
if (isOutGB(p)) {
//y=kx+b
float k = GB_KB()[0];
float b = GB_KB()[1];
float y = k * xValue + b;
colorPickerY = colorPickerHeight - (colorPickerHeight / 9000f * (y * 10000f));
selectY = (int) colorPickerY;
seekbarAxisY.setProgress(y * 10000f);
}
if (isOutGR(p)) {
//y=kx+b
float k = GR_KB()[0];
float b = GR_KB()[1];
float y = k * xValue + b;
colorPickerY = colorPickerHeight - (colorPickerHeight / 9000f * (y * 10000f));
selectY = (int) colorPickerY;
seekbarAxisY.setProgress(y * 10000f);
}
if (isOutBR(p)) {
//y=kx+b
float k = BR_KB()[0];
float b = BR_KB()[1];
float y = k * xValue + b;
colorPickerY = colorPickerHeight - (colorPickerHeight / 9000f * (y * 10000f));
selectY = (int) colorPickerY;
seekbarAxisY.setProgress(y * 10000f);
}
colorPickerView.setCoordinate(selectX, selectY);
Point snapPoint = PointMapper.getColorPoint(colorPickerView, new Point(selectX, selectY));
int pixelColor = colorPickerView.getColorFromBitmap(snapPoint.x, snapPoint.y);
ColorEnvelope envelope = new ColorEnvelope(pixelColor);
GradientDrawable myShape = (GradientDrawable) viewColorBg.getBackground();
selectedColor = envelope.getColor();
myShape.setColor(selectedColor);
}
}
//更新y坐标色温位置
private void updateColorYPick() {
if (!isColorYListener) {
colorPickerY = colorPickerHeight - (colorPickerHeight / 9000f * seekbarAxisY.getProgress());
int selectX = (int) colorPickerX;
int selectY = (int) colorPickerY;
//计算坐标是否超出三角形范围,如果超出需要沿着边线运动
float xValue = Float.parseFloat(tvAxisX.getText().toString());
float yValue = Float.parseFloat(tvAxisY.getText().toString());
POINT p = new POINT(xValue, yValue);
if (isOutGB(p)) {
//y=kx+b
//x=(y-b)/k
float k = GB_KB()[0];
float b = GB_KB()[1];
float x = (yValue - b) / k;
colorPickerX = colorPickerWidth / 8000f * (x * 10000f);
selectX = (int) colorPickerX;
seekbarAxisX.setProgress(x * 10000f);
}
if (isOutGR(p)) {
//y=kx+b
//x=(y-b)/k
float k = GR_KB()[0];
float b = GR_KB()[1];
float x = (yValue - b) / k;
colorPickerX = colorPickerWidth / 8000f * (x * 10000f);
selectX = (int) colorPickerX;
seekbarAxisX.setProgress(x * 10000f);
}
if (isOutBR(p)) {
//y=kx+b
//x=(y-b)/k
float k = BR_KB()[0];
float b = BR_KB()[1];
float x = (yValue - b) / k;
colorPickerX = colorPickerWidth / 8000f * (x * 10000f);
selectX = (int) colorPickerX;
seekbarAxisX.setProgress(x * 10000f);
}
colorPickerView.setCoordinate(selectX, selectY);
Point snapPoint = PointMapper.getColorPoint(colorPickerView, new Point(selectX, selectY));
int pixelColor = colorPickerView.getColorFromBitmap(snapPoint.x, snapPoint.y);
ColorEnvelope envelope = new ColorEnvelope(pixelColor);
GradientDrawable myShape = (GradientDrawable) viewColorBg.getBackground();
selectedColor = envelope.getColor();
myShape.setColor(selectedColor);
}
}
//判断是否在GB线的外面
private boolean isOutGB(POINT p) {
POINT a = new POINT(pointRect1[0], pointRect1[1]);
POINT b = new POINT(pointB[0], pointB[1]);
POINT c = new POINT(pointG[0], pointG[1]);
return isInTriangle(a, b, c, p);
}
//判断是否在BG线的外面
private boolean isOutGR(POINT p) {
POINT a = new POINT(pointRect2[0], pointRect2[1]);
POINT c = new POINT(pointG[0], pointG[1]);
POINT b = new POINT(pointR[0], pointR[1]);
return isInTriangle(a, b, c, p);
}
//判断是否在BR线的外面
private boolean isOutBR(POINT p) {
POINT a = new POINT(pointRect3[0], pointRect3[1]);
POINT b = new POINT(pointB[0], pointB[1]);
POINT c = new POINT(pointR[0], pointR[1]);
return isInTriangle(a, b, c, p);
}
/**
* 中心法判断点p是否在一个三角形内
*
* @param p
* @param a
* @param b
* @param c
* @return
*/
private static boolean isInTriangle(POINT a, POINT b, POINT c, POINT p) {
POINT AB, AC, AP;
AB = new POINT(b.x - a.x, b.y - a.y);
AC = new POINT(c.x - a.x, c.y - a.y);
AP = new POINT(p.x - a.x, p.y - a.y);
float dot00 = dotProduct(AC, AC);
float dot01 = dotProduct(AC, AB);
float dot02 = dotProduct(AC, AP);
float dot11 = dotProduct(AB, AB);
float dot12 = dotProduct(AB, AP);
float inverDeno = 1 / (dot00 * dot11 - dot01 * dot01);
// 计算重心坐标
float u = (dot11 * dot02 - dot01 * dot12) * inverDeno;
float v = (dot00 * dot12 - dot01 * dot02) * inverDeno;
return (u >= 0) && (v >= 0) && (u + v < 1);
}
//求GB坐标线的KB
private float[] GB_KB() {
return calculationKB(pointG[0], pointG[1], pointB[0], pointB[1]);
}
//求GR坐标线的KB
private float[] GR_KB() {
return calculationKB(pointG[0], pointG[1], pointR[0], pointR[1]);
}
//求BR坐标线的KB
private float[] BR_KB() {
return calculationKB(pointB[0], pointB[1], pointR[0], pointR[1]);
}
/**
* 线性代数求k和b
* 已知两点坐标
* y=kx+b
* y1=kx1+b
*
* @param point1X
* @param point1Y
* @param point2X
* @param point2Y
*/
private float[] calculationKB(float point1X, float point1Y, float point2X, float point2Y) {
float k = (point2Y - point1Y) / (point2X - point1X);
float b = point1Y - point1X * k;
float[] kb = new float[]{k, b};
return kb;
}
private static float dotProduct(POINT p1, POINT p2) {
return p1.x * p2.x + p1.y * p2.y;
}
2.4 图片颜色选择器
任何一张图片,点击任何位置都可以提取当前位置颜色,类似于ps中的吸管工具
这个其实最简单的,上面有讲过,任何图片都是由像素颜色组成的,获取像素点就能获取颜色,可以参考上面,传入像素坐标
bitmap.getPixel(0, 0);//获取第1行,第1个像素颜色
2.5 色温选择器
色温介绍
色温是一种衡量温度
方法,使用这种方法标定的色温与普通大众所认为的“暖”和“冷”正好相反,例如,通常人们会感觉红色.橙色和黄色较暖,白色和蓝色较冷,而实际上红色的色温最低,然后逐步增加的是橙色、黄色、白色和蓝色,蓝色是最高的色温
光源色温不同,带来的感觉也不相同。高色温光源照射下.如亮度不高就会给人们一种阴冷的感觉;低色温光源照射下,亮度过高则会给人们一种闷热的感觉。色温越低,色调越暖(偏红);色温越高,色调越冷(偏蓝)
<3000K | 温暖(带红的白色) | 稳重、温暖 |
3000-5000K | 中间(白色) | 爽快 |
>5000K | 清凉型(带蓝的白色) | 冷 |
色纸与色温的关系
色纸按两种类提供数据
一种是HSI数值,然后根据HUE和SAT计算出对应的数值
另一种是提供坐标点,根据坐标点在坐标体系中提取对应的数值
HSI数值,可以参考RGB颜色
色纸名称 5600K数值 3200K数值
Red 90 HUE 11 SAT 92 - HUE 6 SAT 85
Red 60 HUE 17 SAT 86 - HUE 15 SAT 70
Red 30 HUE 17 SAT 58 - HUE 13 SAT 50
Red 15 HUE 17 SAT 20 - HUE 13 SAT 30
Green 90 HUE 104 SAT 93 - HUE 85 SAT 100
Green 60 HUE 95 SAT 80 - HUE 100 SAT 70
Green 30 HUE 95 SAT 70 - HUE 90 SAT 40
Green 15 HUE 95 SAT 40 - HUE 95 SAT 14
Blue 90 HUE 230 SAT 87 - HUE 250 SAT 60
Blue 60 HUE 215 SAT 60 - HUE 240 SAT 35
Blue 30 HUE 206 SAT 33 - HUE 240 SAT 10
Blue 15 HUE 195 SAT 15 - HUE 240 SAT 7
Cyan 90 HUE 150 SAT 75 - HUE 140 SAT 80
Cyan 60 HUE 145 SAT 65 - HUE 140 SAT 55
Cyan 30 HUE 145 SAT 35 - HUE 150 SAT 25
Cyan 15 HUE 153 SAT 20 - HUE 160 SAT 15
Cyan 7 HUE 155 SAT 15 - HUE 160 SAT 7
Magenta 90 HUE 330 SAT 80 - HUE 345 SAT 85
Magenta 60 HUE 330 SAT 70 - HUE 345 SAT 70
Magenta 30 HUE 330 SAT 43 - HUE 340 SAT 45
Magenta 15 HUE 330 SAT 18 - HUE 340 SAT 23
Yellow 90 HUE 60 SAT 98 - HUE 45 SAT 100
Yellow 60 HUE 60 SAT 92 - HUE 45 SAT 90
Yellow 30 HUE 63 SAT 75 - HUE 42 SAT 65
Yellow 15 HUE 63 SAT 50 - HUE 35 SAT 45
Pink 90 HUE 0 SAT 80 - HUE 355 SAT 75
Pink 60 HUE 350 SAT 60 - HUE 355 SAT 60
Pink 30 HUE 345 SAT 35 - HUE 355 SAT 45
Pink 15 HUE 345 SAT 15 - HUE 355 SAT 25
Lavender 90 HUE 285 SAT 85 - HUE 325 SAT 70
Lavender 60 HUE 285 SAT 65 - HUE 328 SAT 45
Lavender 30 HUE 285 SAT 35 - HUE 332 SAT 30
Lavender 15 HUE 285 SAT 10 - HUE 332 SAT 30
坐标数值:可以参考XY色盘模式,这个坐标其实也就是xy色盘坐标
色纸名称 5600K数据 3200K数据
DOUBLE CTB - 0.2785,0.2858 - 0.2952,0.3048
FULL CTB - 0.2952,0.3048 - 0.3302,0.3391
1/2 CTB - 0.2972,0.3069 - 0.3644,0.3661
1/4 CTB - 0.3105,0.3207 - 0.4053,0.3907
1/8 CTB - 0.3221,0.3318 - 0.3221,0.3318
DOUBLE CTO - 0.5780,0.4058 - 0.5834,0.4100
FULL CTO - 0.4234,0.3990 - 0.5712,0.4001
1/2 CTO - 0.3644,0.3661 - 0.5267,0.4133
1/4 CTO - 0.3451,0.3516 - 0.4683,0.4123
1/8 CTO - 0.3372,0.3451 - 0.4443,0.4065
2.6 色温线
XY模式色盘的弓形线”就是色温线,标有色温
最左边的直线,是色温“无穷大”等温线,再左边(兰色区)就无色温了。
1000K左右偏红色,1500K到3000K偏黄色,以后黄色越来越淡,4500K起蓝色逐步增加,10000K后颜色带青
三 色盘选择库
推荐一个选择器库:https://github.com/skydoves/ColorPickerView
支持方形,圆形,和图片选择颜色,有兴趣可以参考