I wrote the two methods below to automatically select N distinct colors. It works by defining a piecewise linear function on the RGB cube. The benefit of this is you can also get a progressive scale if that's what you want, but when N gets large the colors can start to look similar. I can also imagine evenly subdividing the RGB cube into a lattice and then drawing points. Does anyone know any other methods? I'm ruling out defining a list and then just cycling through it. I should also say I don't generally care if they clash or don't look nice, they just have to be visually distinct.
public static List<Color> pick(int num) {
List<Color> colors = new ArrayList<Color>();
if (num < 2)
return colors;
float dx = 1.0f / (float) (num - 1);
for (int i = 0; i < num; i++) {
colors.add(get(i * dx));
}
return colors;
}
public static Color get(float x) {
float r = 0.0f;
float g = 0.0f;
float b = 1.0f;
if (x >= 0.0f && x < 0.2f) {
x = x / 0.2f;
r = 0.0f;
g = x;
b = 1.0f;
} else if (x >= 0.2f && x < 0.4f) {
x = (x - 0.2f) / 0.2f;
r = 0.0f;
g = 1.0f;
b = 1.0f - x;
} else if (x >= 0.4f && x < 0.6f) {
x = (x - 0.4f) / 0.2f;
r = x;
g = 1.0f;
b = 0.0f;
} else if (x >= 0.6f && x < 0.8f) {
x = (x - 0.6f) / 0.2f;
r = 1.0f;
g = 1.0f - x;
b = 0.0f;
} else if (x >= 0.8f && x <= 1.0f) {
x = (x - 0.8f) / 0.2f;
r = 1.0f;
g = 0.0f;
b = x;
}
return new Color(r, g, b);
}
我会尽量把饱和度和亮度调到最大,只关注色调。在我看来,H可以从0到255,然后绕圈。现在如果你想要两种对比色,你可以取这个环的对边,即0和128。如果你想要4种颜色,你需要取一些以圆周长度256的1/4为间隔的颜色,即0,64,128,192。当然,正如其他人建议的那样,当你需要N种颜色时,你可以用256/N将它们分开。
我想补充的是,用二进制数的反向表示来形成这个序列。看看这个:
0 = 00000000 after reversal is 00000000 = 0
1 = 00000001 after reversal is 10000000 = 128
2 = 00000010 after reversal is 01000000 = 64
3 = 00000011 after reversal is 11000000 = 192
...
这样,如果你需要N种不同的颜色,你只需要取前N个数字,把它们倒过来,你就能得到尽可能多的距离点(因为N是2的幂),同时保持序列的每个前缀都有很大不同。
在我的用例中,这是一个重要的目标,因为我有一个图表,其中颜色是根据这种颜色所覆盖的区域进行排序的。我希望图表中最大的区域具有较大的对比度,并且我对一些小区域使用与前十名相似的颜色也没有问题,因为读者通过观察区域就可以很明显地看出哪个是哪个。
这里有一个解决你的“独特”问题的解决方案,这完全是夸大的:
创建一个单位球体,并在其上放置带有排斥电荷的点。运行一个粒子系统,直到它们不再移动(或者delta“足够小”)。在这一点上,每个点之间的距离都尽可能远。将(x, y, z)转换为rgb。
我提到它是因为对于某些类型的问题,这种类型的解决方案比暴力解决方案更好。
我一开始看到这种方法是用来镶嵌球面的。
同样,遍历HSL空间或RGB空间的最明显的解决方案可能工作得很好。
HSL颜色模型可能非常适合“排序”颜色,但如果您正在寻找视觉上独特的颜色,您肯定需要Lab颜色模型。
CIELAB被设计成相对于人类色觉而言在感知上是一致的,这意味着这些数值中相同数量的数值变化对应着大约相同数量的视觉感知变化。
一旦你知道了这一点,从广泛的颜色范围中找到N种颜色的最优子集仍然是一个(NP)困难问题,有点类似于旅行推销员问题,所有使用k-mean算法或其他方法的解决方案都不会有真正的帮助。
也就是说,如果N不是太大,如果你从一个有限的颜色集开始,你会很容易找到一个非常好的不同颜色的子集,根据一个简单的随机函数的Lab距离。
我编写了这样一个工具供我自己使用(你可以在这里找到:https://mokole.com/palette.html),下面是我在N=7时得到的:
它都是javascript,所以请随意查看页面的源代码,并根据自己的需要进行调整。
对于Python用户来说,seaborn非常简洁:
>>> import seaborn as sns
>>> sns.color_palette(n_colors=4)
它返回RGB元组列表:
[(0.12156862745098039, 0.4666666666666667, 0.7058823529411765),
(1.0, 0.4980392156862745, 0.054901960784313725),
(0.17254901960784313, 0.6274509803921569, 0.17254901960784313),
(0.8392156862745098, 0.15294117647058825, 0.1568627450980392)]
我认为这个简单的递归算法补充了公认的答案,以产生不同的色调值。我为hsv做了它,但也可以用于其他颜色空间。
它在循环中产生色调,在每个循环中尽可能彼此分离。
/**
* 1st cycle: 0, 120, 240
* 2nd cycle (+60): 60, 180, 300
* 3th cycle (+30): 30, 150, 270, 90, 210, 330
* 4th cycle (+15): 15, 135, 255, 75, 195, 315, 45, 165, 285, 105, 225, 345
*/
public static float recursiveHue(int n) {
// if 3: alternates red, green, blue variations
float firstCycle = 3;
// First cycle
if (n < firstCycle) {
return n * 360f / firstCycle;
}
// Each cycle has as much values as all previous cycles summed (powers of 2)
else {
// floor of log base 2
int numCycles = (int)Math.floor(Math.log(n / firstCycle) / Math.log(2));
// divDown stores the larger power of 2 that is still lower than n
int divDown = (int)(firstCycle * Math.pow(2, numCycles));
// same hues than previous cycle, but summing an offset (half than previous cycle)
return recursiveHue(n % divDown) + 180f / divDown;
}
}
我在这里找不到这种算法。我希望这对你有所帮助,这是我在这里的第一篇文章。