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);
}

当前回答

如果N足够大,你会得到一些相似的颜色。世界上只有这么多。

为什么不把它们均匀地分布在光谱中,像这样:

IEnumerable<Color> CreateUniqueColors(int nColors)
{
    int subdivision = (int)Math.Floor(Math.Pow(nColors, 1/3d));
    for(int r = 0; r < 255; r += subdivision)
        for(int g = 0; g < 255; g += subdivision)
            for(int b = 0; b < 255; b += subdivision)
                yield return Color.FromArgb(r, g, b);
}

如果您想混合序列,以便相似的颜色不在彼此旁边,您可能会打乱结果列表。

是我想得不够周全吗?

其他回答

上面有很多非常好的答案,但如果有人正在寻找一个快速的python解决方案,那么提到python包distinctify可能会很有用。它是pypi提供的一个轻量级包,使用起来非常简单:

from distinctipy import distinctipy

colors = distinctipy.get_colors(12)

print(colors)

# display the colours
distinctipy.color_swatch(colors)

它返回一个rgb元组列表

[(0, 1, 0), (1, 0, 1), (0, 0.5, 1), (1, 0.5, 0), (0.5, 0.75, 0.5), (0.4552518132842178, 0.12660764790179446, 0.5467915225460569), (1, 0, 0), (0.12076092516775849, 0.9942188027771208, 0.9239958090462229), (0.254747094970068, 0.4768020779917903, 0.02444859177890535), (0.7854526395841417, 0.48630704929211144, 0.9902480906347156), (0, 0, 1), (1, 1, 0)]

此外,它还有一些额外的功能,比如生成不同于现有颜色列表的颜色。

我认为这个简单的递归算法补充了公认的答案,以产生不同的色调值。我为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;
    }
}

我在这里找不到这种算法。我希望这对你有所帮助,这是我在这里的第一篇文章。

我会尽量把饱和度和亮度调到最大,只关注色调。在我看来,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空间的最明显的解决方案可能工作得很好。

我为R写了一个名为qualpalr的包,它是专门为此目的设计的。我建议你看看小插图,看看它是如何工作的,但我会尽量总结要点。

qualpalr在HSL颜色空间(前面在这个线程中描述过)中获取一个颜色规范,将其投射到DIN99d颜色空间(感知上是均匀的),并找到使它们之间的最小距离最大化的n。

# Create a palette of 4 colors of hues from 0 to 360, saturations between
# 0.1 and 0.5, and lightness from 0.6 to 0.85
pal <- qualpal(n = 4, list(h = c(0, 360), s = c(0.1, 0.5), l = c(0.6, 0.85)))

# Look at the colors in hex format
pal$hex
#> [1] "#6F75CE" "#CC6B76" "#CAC16A" "#76D0D0"

# Create a palette using one of the predefined color subspaces
pal2 <- qualpal(n = 4, colorspace = "pretty")

# Distance matrix of the DIN99d color differences
pal2$de_DIN99d
#>        #69A3CC #6ECC6E #CA6BC4
#> 6ECC6E      22                
#> CA6BC4      21      30        
#> CD976B      24      21      21

plot(pal2)