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

当前回答

Janus的回答,但更容易读懂。我还稍微调整了配色方案,并在你可以自己修改的地方做了标记

我已经把这个片段直接粘贴到一个jupyter笔记本。

import colorsys
import itertools
from fractions import Fraction
from IPython.display import HTML as html_print

def infinite_hues():
    yield Fraction(0)
    for k in itertools.count():
        i = 2**k # zenos_dichotomy
        for j in range(1,i,2):
            yield Fraction(j,i)

def hue_to_hsvs(h: Fraction):
    # tweak values to adjust scheme
    for s in [Fraction(6,10)]:
        for v in [Fraction(6,10), Fraction(9,10)]: 
            yield (h, s, v) 

def rgb_to_css(rgb) -> str:
    uint8tuple = map(lambda y: int(y*255), rgb)
    return "rgb({},{},{})".format(*uint8tuple)

def css_to_html(css):
    return f"<text style=background-color:{css}>&nbsp;&nbsp;&nbsp;&nbsp;</text>"

def show_colors(n=33):
    hues = infinite_hues()
    hsvs = itertools.chain.from_iterable(hue_to_hsvs(hue) for hue in hues)
    rgbs = (colorsys.hsv_to_rgb(*hsv) for hsv in hsvs)
    csss = (rgb_to_css(rgb) for rgb in rgbs)
    htmls = (css_to_html(css) for css in csss)

    myhtmls = itertools.islice(htmls, n)
    display(html_print("".join(myhtmls)))

show_colors()

其他回答

我为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)

对于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)]

上面有很多非常好的答案,但如果有人正在寻找一个快速的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)]

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

HSL颜色模型可能非常适合“排序”颜色,但如果您正在寻找视觉上独特的颜色,您肯定需要Lab颜色模型。

CIELAB被设计成相对于人类色觉而言在感知上是一致的,这意味着这些数值中相同数量的数值变化对应着大约相同数量的视觉感知变化。

一旦你知道了这一点,从广泛的颜色范围中找到N种颜色的最优子集仍然是一个(NP)困难问题,有点类似于旅行推销员问题,所有使用k-mean算法或其他方法的解决方案都不会有真正的帮助。

也就是说,如果N不是太大,如果你从一个有限的颜色集开始,你会很容易找到一个非常好的不同颜色的子集,根据一个简单的随机函数的Lab距离。

我编写了这样一个工具供我自己使用(你可以在这里找到:https://mokole.com/palette.html),下面是我在N=7时得到的:

它都是javascript,所以请随意查看页面的源代码,并根据自己的需要进行调整。

这产生了与Janus Troelsen的溶液相同的颜色。但是它使用的不是生成器,而是开始/停止语义。它也是完全向量化的。

import numpy as np
import numpy.typing as npt
import matplotlib.colors

def distinct_colors(start: int=0, stop: int=20) -> npt.NDArray[np.float64]:
    """Returns an array of distinct RGB colors, from an infinite sequence of colors
    """
    if stop <= start: # empty interval; return empty array
        return np.array([], dtype=np.float64)
    sat_values = [6/10]         # other tones could be added
    val_values = [8/10, 5/10]   # other tones could be added
    colors_per_hue_value = len(sat_values) * len(val_values)
    # Get the start and stop indices within the hue value stream that are needed
    # to achieve the requested range
    hstart = start // colors_per_hue_value
    hstop = (stop+colors_per_hue_value-1) // colors_per_hue_value
    # Zero will cause a singularity in the caluculation, so we will add the zero
    # afterwards
    prepend_zero = hstart==0 

    # Sequence (if hstart=1): 1,2,...,hstop-1
    i = np.arange(1 if prepend_zero else hstart, hstop) 
    # The following yields (if hstart is 1): 1/2,  1/4, 3/4,  1/8, 3/8, 5/8, 7/8,  
    # 1/16, 3/16, ... 
    hue_values = (2*i+1) / np.power(2,np.floor(np.log2(i*2))) - 1
    
    if prepend_zero:
        hue_values = np.concatenate(([0], hue_values))

    # Make all combinations of h, s and v values, as if done by a nested loop
    # in that order
    hsv = np.array(np.meshgrid(hue_values, sat_values, val_values, indexing='ij')
                    ).reshape((3,-1)).transpose()

    # Select the requested range (only the necessary values were computed but we
    # need to adjust the indices since start & stop are not necessarily multiples
    # of colors_per_hue_value)
    hsv = hsv[start % colors_per_hue_value : 
                start % colors_per_hue_value + stop - start]
    # Use the matplotlib vectorized function to convert hsv to rgb
    return matplotlib.colors.hsv_to_rgb(hsv)

样品:

from matplotlib.colors import ListedColormap
ListedColormap(distinct_colors(stop=20))

ListedColormap(distinct_colors(start=30, stop=50))