【问题标题】:How to automatically generate N "distinct" colors?如何自动生成 N 个“不同”颜色?
【发布时间】:2010-10-03 00:15:27
【问题描述】:

我编写了以下两种方法来自动选择 N 种不同的颜色。它通过在 RGB 立方体上定义分段线性函数来工作。这样做的好处是,如果您想要的话,您也可以获得渐进式比例,但是当 N 变大时,颜色可能会开始看起来相似。我也可以想象将 RGB 立方体均匀细分为一个格子,然后绘制点。有人知道其他方法吗?我排除了定义一个列表,然后只是循环浏览它。我还应该说,我通常不在乎它们是否发生冲突或看起来不好看,它们只需要在视觉上不同即可。

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

【问题讨论】:

标签: java colors color-scheme color-picker


【解决方案1】:

这个问题出现在不少 SO 讨论中:

提出了不同的解决方案,但没有一个是最佳的。幸运的是,科学来拯救

任意 N

最后 2 个将通过大多数大学图书馆/代理免费提供。

N 是有限的并且相对较小

在这种情况下,可以选择列表解决方案。该主题的一篇非常有趣的文章是免费提供的:

有几个颜色列表需要考虑:

  • Boynton 列出的 11 种几乎不会混淆的颜色(可在上一节的第一篇论文中找到)
  • Kelly 的 22 种颜色的最大对比度(可在上面的论文中找到)

我还遇到了一位麻省理工学院学生this Palette。 最后,以下链接可能有助于在不同颜色系统/坐标之间进行转换(例如,文章中的某些颜色未在 RGB 中指定):

对于 Kelly 和 Boynton 的列表,我已经转换为 RGB(白色和黑色除外,这应该很明显)。一些 C# 代码:

public static ReadOnlyCollection<Color> KellysMaxContrastSet
{
    get { return _kellysMaxContrastSet.AsReadOnly(); }
}

private static readonly List<Color> _kellysMaxContrastSet = new List<Color>
{
    UIntToColor(0xFFFFB300), //Vivid Yellow
    UIntToColor(0xFF803E75), //Strong Purple
    UIntToColor(0xFFFF6800), //Vivid Orange
    UIntToColor(0xFFA6BDD7), //Very Light Blue
    UIntToColor(0xFFC10020), //Vivid Red
    UIntToColor(0xFFCEA262), //Grayish Yellow
    UIntToColor(0xFF817066), //Medium Gray

    //The following will not be good for people with defective color vision
    UIntToColor(0xFF007D34), //Vivid Green
    UIntToColor(0xFFF6768E), //Strong Purplish Pink
    UIntToColor(0xFF00538A), //Strong Blue
    UIntToColor(0xFFFF7A5C), //Strong Yellowish Pink
    UIntToColor(0xFF53377A), //Strong Violet
    UIntToColor(0xFFFF8E00), //Vivid Orange Yellow
    UIntToColor(0xFFB32851), //Strong Purplish Red
    UIntToColor(0xFFF4C800), //Vivid Greenish Yellow
    UIntToColor(0xFF7F180D), //Strong Reddish Brown
    UIntToColor(0xFF93AA00), //Vivid Yellowish Green
    UIntToColor(0xFF593315), //Deep Yellowish Brown
    UIntToColor(0xFFF13A13), //Vivid Reddish Orange
    UIntToColor(0xFF232C16), //Dark Olive Green
};

public static ReadOnlyCollection<Color> BoyntonOptimized
{
    get { return _boyntonOptimized.AsReadOnly(); }
}

private static readonly List<Color> _boyntonOptimized = new List<Color>
{
    Color.FromArgb(0, 0, 255),      //Blue
    Color.FromArgb(255, 0, 0),      //Red
    Color.FromArgb(0, 255, 0),      //Green
    Color.FromArgb(255, 255, 0),    //Yellow
    Color.FromArgb(255, 0, 255),    //Magenta
    Color.FromArgb(255, 128, 128),  //Pink
    Color.FromArgb(128, 128, 128),  //Gray
    Color.FromArgb(128, 0, 0),      //Brown
    Color.FromArgb(255, 128, 0),    //Orange
};

static public Color UIntToColor(uint color)
{
    var a = (byte)(color >> 24);
    var r = (byte)(color >> 16);
    var g = (byte)(color >> 8);
    var b = (byte)(color >> 0);
    return Color.FromArgb(a, r, g, b);
}

以下是十六进制和每通道 8 位表示的 RGB 值:

kelly_colors_hex = [
    0xFFB300, # Vivid Yellow
    0x803E75, # Strong Purple
    0xFF6800, # Vivid Orange
    0xA6BDD7, # Very Light Blue
    0xC10020, # Vivid Red
    0xCEA262, # Grayish Yellow
    0x817066, # Medium Gray

    # The following don't work well for people with defective color vision
    0x007D34, # Vivid Green
    0xF6768E, # Strong Purplish Pink
    0x00538A, # Strong Blue
    0xFF7A5C, # Strong Yellowish Pink
    0x53377A, # Strong Violet
    0xFF8E00, # Vivid Orange Yellow
    0xB32851, # Strong Purplish Red
    0xF4C800, # Vivid Greenish Yellow
    0x7F180D, # Strong Reddish Brown
    0x93AA00, # Vivid Yellowish Green
    0x593315, # Deep Yellowish Brown
    0xF13A13, # Vivid Reddish Orange
    0x232C16, # Dark Olive Green
    ]

kelly_colors = dict(vivid_yellow=(255, 179, 0),
                    strong_purple=(128, 62, 117),
                    vivid_orange=(255, 104, 0),
                    very_light_blue=(166, 189, 215),
                    vivid_red=(193, 0, 32),
                    grayish_yellow=(206, 162, 98),
                    medium_gray=(129, 112, 102),

                    # these aren't good for people with defective color vision:
                    vivid_green=(0, 125, 52),
                    strong_purplish_pink=(246, 118, 142),
                    strong_blue=(0, 83, 138),
                    strong_yellowish_pink=(255, 122, 92),
                    strong_violet=(83, 55, 122),
                    vivid_orange_yellow=(255, 142, 0),
                    strong_purplish_red=(179, 40, 81),
                    vivid_greenish_yellow=(244, 200, 0),
                    strong_reddish_brown=(127, 24, 13),
                    vivid_yellowish_green=(147, 170, 0),
                    deep_yellowish_brown=(89, 51, 21),
                    vivid_reddish_orange=(241, 58, 19),
                    dark_olive_green=(35, 44, 22))

对于所有 Java 开发人员,这里是 JavaFX 颜色:

// Don't forget to import javafx.scene.paint.Color;

private static final Color[] KELLY_COLORS = {
    Color.web("0xFFB300"),    // Vivid Yellow
    Color.web("0x803E75"),    // Strong Purple
    Color.web("0xFF6800"),    // Vivid Orange
    Color.web("0xA6BDD7"),    // Very Light Blue
    Color.web("0xC10020"),    // Vivid Red
    Color.web("0xCEA262"),    // Grayish Yellow
    Color.web("0x817066"),    // Medium Gray

    Color.web("0x007D34"),    // Vivid Green
    Color.web("0xF6768E"),    // Strong Purplish Pink
    Color.web("0x00538A"),    // Strong Blue
    Color.web("0xFF7A5C"),    // Strong Yellowish Pink
    Color.web("0x53377A"),    // Strong Violet
    Color.web("0xFF8E00"),    // Vivid Orange Yellow
    Color.web("0xB32851"),    // Strong Purplish Red
    Color.web("0xF4C800"),    // Vivid Greenish Yellow
    Color.web("0x7F180D"),    // Strong Reddish Brown
    Color.web("0x93AA00"),    // Vivid Yellowish Green
    Color.web("0x593315"),    // Deep Yellowish Brown
    Color.web("0xF13A13"),    // Vivid Reddish Orange
    Color.web("0x232C16"),    // Dark Olive Green
};

以下是按上述顺序未排序的凯利颜色。

以下是根据色调排序的凯利颜色(注意有些黄色不是很对比)

【讨论】:

  • 很好的答案,谢谢!我冒昧地将这两种颜色设置为convenient jsfiddle where you can see the colors in action.
  • 刚刚注意到这些列表中分别只有 20 和 9 种颜色。我猜是因为省略了白色和黑色。
  • 有趣的隐式参数,用于使用颜色对来区分图标/任何东西here(来自上面非常有用的链接)。使程序化方面变得很多更简单。
  • @OhadSchneider 非常感谢!我正在寻找凯利的 22 种颜色的 RGB 表示。我从 PDF 文档中使用了 GIMP 的颜色选择器,但这可能有点偏离……你知道将 ICSS-NGB 内容转换为 RGB 的方法吗?干杯!
【解决方案2】:

您可以使用HSL color model 来创建您的颜色。

如果您想要的只是不同的色调(可能),以及亮度或饱和度的细微变化,您可以像这样分配色调:

// assumes hue [0, 360), saturation [0, 100), lightness [0, 100)

for(i = 0; i < 360; i += 360 / num_colors) {
    HSLColor c;
    c.hue = i;
    c.saturation = 90 + randf() * 10;
    c.lightness = 50 + randf() * 10;

    addColor(c);
}

【讨论】:

  • 这假设等间距的色调值在感知上同样不同。即使不考虑各种形式的色盲,对大多数人来说也是如此:120°(绿色)和 135°(非常轻微的薄荷绿)之间的差异是难以察觉的,而 30°(橙色)和 45°(桃色)之间的差异很明显。您需要沿色调设置非线性间距以获得最佳效果。
  • 这是错误的,原因已经提到,也因为你不是picking uniformly
  • @Phrogz:您建议使用哪个功能?我会接受我的答案中的任何连续函数。
  • @strager randf() 的期望值是多少
  • 最好的答案是不要以编程方式执行此操作,而是使用静态列表。你得到的任何答案都会有问题,你只能得到大约 20 种颜色的列表,这对色盲的人来说简直就是地狱。您确实需要其他方法来确定一件事与另一件事,而不仅仅是颜色。但是,对于颜色,只需使用静态列表。
【解决方案3】:

类似于 Uri Cohen 的回答,但它是一个生成器。将从使用相距很远的颜色开始。确定性。

示例,先左颜色:

#!/usr/bin/env python3
from typing import Iterable, Tuple
import colorsys
import itertools
from fractions import Fraction
from pprint import pprint

def zenos_dichotomy() -> Iterable[Fraction]:
    """
    http://en.wikipedia.org/wiki/1/2_%2B_1/4_%2B_1/8_%2B_1/16_%2B_%C2%B7_%C2%B7_%C2%B7
    """
    for k in itertools.count():
        yield Fraction(1,2**k)

def fracs() -> Iterable[Fraction]:
    """
    [Fraction(0, 1), Fraction(1, 2), Fraction(1, 4), Fraction(3, 4), Fraction(1, 8), Fraction(3, 8), Fraction(5, 8), Fraction(7, 8), Fraction(1, 16), Fraction(3, 16), ...]
    [0.0, 0.5, 0.25, 0.75, 0.125, 0.375, 0.625, 0.875, 0.0625, 0.1875, ...]
    """
    yield Fraction(0)
    for k in zenos_dichotomy():
        i = k.denominator # [1,2,4,8,16,...]
        for j in range(1,i,2):
            yield Fraction(j,i)

# can be used for the v in hsv to map linear values 0..1 to something that looks equidistant
# bias = lambda x: (math.sqrt(x/3)/Fraction(2,3)+Fraction(1,3))/Fraction(6,5)

HSVTuple = Tuple[Fraction, Fraction, Fraction]
RGBTuple = Tuple[float, float, float]

def hue_to_tones(h: Fraction) -> Iterable[HSVTuple]:
    for s in [Fraction(6,10)]: # optionally use range
        for v in [Fraction(8,10),Fraction(5,10)]: # could use range too
            yield (h, s, v) # use bias for v here if you use range

def hsv_to_rgb(x: HSVTuple) -> RGBTuple:
    return colorsys.hsv_to_rgb(*map(float, x))

flatten = itertools.chain.from_iterable

def hsvs() -> Iterable[HSVTuple]:
    return flatten(map(hue_to_tones, fracs()))

def rgbs() -> Iterable[RGBTuple]:
    return map(hsv_to_rgb, hsvs())

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

def css_colors() -> Iterable[str]:
    return map(rgb_to_css, rgbs())

if __name__ == "__main__":
    # sample 100 colors in css format
    sample_colors = list(itertools.islice(css_colors(), 100))
    pprint(sample_colors)

【讨论】:

  • lambda 的数量太高了。这个 lambda 很强大。
  • 看起来不错,但是当我尝试在 2.7 上运行时卡住了
  • 运行此脚本并捕获输出,然后创建一个快速小提琴来可视化并使颜色数组易于以各种语言复制/粘贴:jsfiddle.net/dw6h0v4p
【解决方案4】:

为了子孙后代,我在此处添加 Python 中公认的答案。

import numpy as np
import colorsys

def _get_colors(num_colors):
    colors=[]
    for i in np.arange(0., 360., 360. / num_colors):
        hue = i/360.
        lightness = (50 + np.random.rand() * 10)/100.
        saturation = (90 + np.random.rand() * 10)/100.
        colors.append(colorsys.hls_to_rgb(hue, lightness, saturation))
    return colors

【讨论】:

    【解决方案5】:

    这是一个想法。想象一个 HSV 气缸

    定义亮度和饱和度的上限和下限。这在空间内定义了一个方形横截面环。

    现在,在这个空间内随机散布 N 个点。

    然后对它们应用迭代排斥算法,或者迭代固定次数,或者直到点稳定。

    现在你应该有 N 个点代表 N 种颜色,它们在你感兴趣的颜色空间内尽可能不同。

    雨果

    【讨论】:

      【解决方案6】:

      每个人似乎都错过了非常有用的 YUV 颜色空间的存在,该颜色空间旨在表示人类视觉系统中感知到的颜色差异。 YUV 中的距离代表人类感知的差异。我需要 MagicCube4D 的这个功能,它实现了 4 维魔方和无限数量的其他具有任意数量面孔的 4D 曲折谜题。

      我的解决方案首先在 YUV 中选择随机点,然后迭代分解最近的两个点,并在返回结果时仅转换为 RGB。该方法是 O(n^3) 但这对于小数字或可以缓存的数字并不重要。它当然可以提高效率,但结果似乎非常好。

      该函数允许对亮度阈值进行可选规范,以免产生没有分量比给定值更亮或更暗的颜色。 IE 你可能不想要接近黑色或白色的值。当生成的颜色将用作稍后通过照明、分层、透明度等进行着色并且必须仍然与其基色不同时,这很有用。

      import java.awt.Color;
      import java.util.Random;
      
      /**
       * Contains a method to generate N visually distinct colors and helper methods.
       * 
       * @author Melinda Green
       */
      public class ColorUtils {
          private ColorUtils() {} // To disallow instantiation.
          private final static float
              U_OFF = .436f,
              V_OFF = .615f;
          private static final long RAND_SEED = 0;
          private static Random rand = new Random(RAND_SEED);    
      
          /*
           * Returns an array of ncolors RGB triplets such that each is as unique from the rest as possible
           * and each color has at least one component greater than minComponent and one less than maxComponent.
           * Use min == 1 and max == 0 to include the full RGB color range.
           * 
           * Warning: O N^2 algorithm blows up fast for more than 100 colors.
           */
          public static Color[] generateVisuallyDistinctColors(int ncolors, float minComponent, float maxComponent) {
              rand.setSeed(RAND_SEED); // So that we get consistent results for each combination of inputs
      
              float[][] yuv = new float[ncolors][3];
      
              // initialize array with random colors
              for(int got = 0; got < ncolors;) {
                  System.arraycopy(randYUVinRGBRange(minComponent, maxComponent), 0, yuv[got++], 0, 3);
              }
              // continually break up the worst-fit color pair until we get tired of searching
              for(int c = 0; c < ncolors * 1000; c++) {
                  float worst = 8888;
                  int worstID = 0;
                  for(int i = 1; i < yuv.length; i++) {
                      for(int j = 0; j < i; j++) {
                          float dist = sqrdist(yuv[i], yuv[j]);
                          if(dist < worst) {
                              worst = dist;
                              worstID = i;
                          }
                      }
                  }
                  float[] best = randYUVBetterThan(worst, minComponent, maxComponent, yuv);
                  if(best == null)
                      break;
                  else
                      yuv[worstID] = best;
              }
      
              Color[] rgbs = new Color[yuv.length];
              for(int i = 0; i < yuv.length; i++) {
                  float[] rgb = new float[3];
                  yuv2rgb(yuv[i][0], yuv[i][1], yuv[i][2], rgb);
                  rgbs[i] = new Color(rgb[0], rgb[1], rgb[2]);
                  //System.out.println(rgb[i][0] + "\t" + rgb[i][1] + "\t" + rgb[i][2]);
              }
      
              return rgbs;
          }
      
          public static void hsv2rgb(float h, float s, float v, float[] rgb) {
              // H is given on [0->6] or -1. S and V are given on [0->1]. 
              // RGB are each returned on [0->1]. 
              float m, n, f;
              int i;
      
              float[] hsv = new float[3];
      
              hsv[0] = h;
              hsv[1] = s;
              hsv[2] = v;
              System.out.println("H: " + h + " S: " + s + " V:" + v);
              if(hsv[0] == -1) {
                  rgb[0] = rgb[1] = rgb[2] = hsv[2];
                  return;
              }
              i = (int) (Math.floor(hsv[0]));
              f = hsv[0] - i;
              if(i % 2 == 0)
                  f = 1 - f; // if i is even 
              m = hsv[2] * (1 - hsv[1]);
              n = hsv[2] * (1 - hsv[1] * f);
              switch(i) {
                  case 6:
                  case 0:
                      rgb[0] = hsv[2];
                      rgb[1] = n;
                      rgb[2] = m;
                      break;
                  case 1:
                      rgb[0] = n;
                      rgb[1] = hsv[2];
                      rgb[2] = m;
                      break;
                  case 2:
                      rgb[0] = m;
                      rgb[1] = hsv[2];
                      rgb[2] = n;
                      break;
                  case 3:
                      rgb[0] = m;
                      rgb[1] = n;
                      rgb[2] = hsv[2];
                      break;
                  case 4:
                      rgb[0] = n;
                      rgb[1] = m;
                      rgb[2] = hsv[2];
                      break;
                  case 5:
                      rgb[0] = hsv[2];
                      rgb[1] = m;
                      rgb[2] = n;
                      break;
              }
          }
      
      
          // From http://en.wikipedia.org/wiki/YUV#Mathematical_derivations_and_formulas
          public static void yuv2rgb(float y, float u, float v, float[] rgb) {
              rgb[0] = 1 * y + 0 * u + 1.13983f * v;
              rgb[1] = 1 * y + -.39465f * u + -.58060f * v;
              rgb[2] = 1 * y + 2.03211f * u + 0 * v;
          }
      
          public static void rgb2yuv(float r, float g, float b, float[] yuv) {
              yuv[0] = .299f * r + .587f * g + .114f * b;
              yuv[1] = -.14713f * r + -.28886f * g + .436f * b;
              yuv[2] = .615f * r + -.51499f * g + -.10001f * b;
          }
      
          private static float[] randYUVinRGBRange(float minComponent, float maxComponent) {
              while(true) {
                  float y = rand.nextFloat(); // * YFRAC + 1-YFRAC);
                  float u = rand.nextFloat() * 2 * U_OFF - U_OFF;
                  float v = rand.nextFloat() * 2 * V_OFF - V_OFF;
                  float[] rgb = new float[3];
                  yuv2rgb(y, u, v, rgb);
                  float r = rgb[0], g = rgb[1], b = rgb[2];
                  if(0 <= r && r <= 1 &&
                      0 <= g && g <= 1 &&
                      0 <= b && b <= 1 &&
                      (r > minComponent || g > minComponent || b > minComponent) && // don't want all dark components
                      (r < maxComponent || g < maxComponent || b < maxComponent)) // don't want all light components
      
                      return new float[]{y, u, v};
              }
          }
      
          private static float sqrdist(float[] a, float[] b) {
              float sum = 0;
              for(int i = 0; i < a.length; i++) {
                  float diff = a[i] - b[i];
                  sum += diff * diff;
              }
              return sum;
          }
      
          private static double worstFit(Color[] colors) {
              float worst = 8888;
              float[] a = new float[3], b = new float[3];
              for(int i = 1; i < colors.length; i++) {
                  colors[i].getColorComponents(a);
                  for(int j = 0; j < i; j++) {
                      colors[j].getColorComponents(b);
                      float dist = sqrdist(a, b);
                      if(dist < worst) {
                          worst = dist;
                      }
                  }
              }
              return Math.sqrt(worst);
          }
      
          private static float[] randYUVBetterThan(float bestDistSqrd, float minComponent, float maxComponent, float[][] in) {
              for(int attempt = 1; attempt < 100 * in.length; attempt++) {
                  float[] candidate = randYUVinRGBRange(minComponent, maxComponent);
                  boolean good = true;
                  for(int i = 0; i < in.length; i++)
                      if(sqrdist(candidate, in[i]) < bestDistSqrd)
                          good = false;
                  if(good)
                      return candidate;
              }
              return null; // after a bunch of passes, couldn't find a candidate that beat the best.
          }
      
      
          /**
           * Simple example program.
           */
          public static void main(String[] args) {
              final int ncolors = 10;
              Color[] colors = generateVisuallyDistinctColors(ncolors, .8f, .3f);
              for(int i = 0; i < colors.length; i++) {
                  System.out.println(colors[i].toString());
              }
              System.out.println("Worst fit color = " + worstFit(colors));
          }
      
      }
      

      【讨论】:

      • 此代码是否有 C# 版本?我尝试转换它并使用传递给 generateVisuallyDistinctColors() 的相同参数运行,它似乎运行得很慢。这是预期的吗?
      • 你得到相同的结果吗?它对我的需求来说非常快,但就像我说的,我没有尝试优化它,所以如果这是你唯一的问题,你可能应该注意内存分配/释放。我对 C# 内存管理一无所知。在最坏的情况下,您可以将 1,000 外循环常数减小到更小,质量差异甚至可能不明显。
      • 我的调色板必须包含某些颜色,但我想填充额外的颜色。我喜欢你的方法 b/c 我可以先把我需要的颜色放在你的 yuv 数组中,然后修改“j=0”以在我需要的颜色之后开始优化。我希望分解最差的对更聪明一点,但我能理解为什么这很难。
      • 我认为您的 yuv2rgb 方法缺少钳位(0,255)。
      • yuv2rgb 都是浮点数,而不是字节 Ryan。请写信至 melinda@superliminal.com 讨论。
      【解决方案7】:

      HSL 颜色模型可能非常适合“分类”颜色,但如果您正在寻找视觉上不同的颜色,则绝对需要 Lab 颜色模型。

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

      一旦你知道,从各种颜色中找到 N 个颜色的最优子集仍然是一个 (NP) 难题,有点类似于 Travelling salesman problem 和所有使用 k-mean 算法的解决方案或其他方法没什么用。

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

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

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

      【讨论】:

      • 关于 »相同数量的数字变化 [...] 相同数量的视觉感知变化«。我玩了一个 CIE Lab 颜色选择器,根本无法确认这一点。我将使用从 0 到 128 的范围 L 和从 -128 到 128 的 ab 来表示实验室颜色。¶ 我从 L=0、a=-128、b= 开始-128 是亮蓝色。然后我将a 增加了三倍。 ❶ 较大的变化 (+128) a=50 只导致稍微深一点的蓝色。 ❷ (+85) a=85 结果仍为蓝色。 ❸ 不过比较小的变化(+43)a=128 完全把颜色变成了紫红色。
      【解决方案8】:

      这是管理您的“独特”问题的解决方案,这完全被夸大了:

      创建一个单位球体并在其上放置带有排斥电荷的点。运行粒子系统,直到它们不再移动(或增量“足够小”)。此时,每个点都尽可能远离彼此。将 (x, y, z) 转换为 rgb。

      我提到它是因为对于某些类别的问题,这种类型的解决方案比蛮力更有效。

      我最初看到 this approach here 用于镶嵌球体。

      同样,最明显的遍历 HSL 空间或 RGB 空间的解决方案可能也可以正常工作。

      【讨论】:

      • 这是个好主意,但使用立方体而不是球体可能更有意义。
      • 这就是我基于 YUV 的解决方案所做的,但用于 3D 盒子(不是立方体)。
      【解决方案9】:

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

      它还具有一些额外的不错的功能,例如生成与现有颜色列表不同的颜色。

      【讨论】:

      【解决方案10】:

      我会尝试将饱和度和亮度固定到最大,并只关注色调。如我所见,H 可以从 0 变为 255,然后回绕。现在,如果您想要两种对比色,您将采用该环的相对两侧,即 0 和 128。如果您想要 4 种颜色,您将采用相隔 1/4 的圆的 256 长度的一些颜色,即 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 的幂),同时保持序列的每个前缀不同很多。

      这是我用例中的一个重要目标,因为我有一个图表,其中颜色按该颜色覆盖的区域排序。我希望图表的最大区域具有较大的对比度,并且我可以让一些小区域的颜色与前 10 名的颜色相似,因为读者只需观察该区域就可以清楚地知道哪个是哪个。

      【讨论】:

      【解决方案11】:

      我们只需要一系列 RGB 三元组对,这些三元组之间的距离最大。

      我们可以定义一个简单的线性渐变,然后调整渐变的大小以获得所需的颜色数量。

      在python中:

      def distinguishable_colors(n, shuffle = True, 
                                 sinusoidal = False,
                                 oscillate_tone = False): 
          ramp = ([1, 0, 0],[1,1,0],[0,1,0],[0,0,1], [1,0,1]) if n>3 else ([1,0,0], [0,1,0],[0,0,1])
          
          coltrio = np.vstack(ramp)
          
          colmap = np.round(resize(coltrio, [n,3], preserve_range=True, 
                                   order = 1 if n>3 else 3
                                   , mode = 'wrap'),3)
          
          if sinusoidal: colmap = np.sin(colmap*np.pi/2)
          
          colmap = [colmap[x,] for x  in range(colmap.shape[0])]
          
          if oscillate_tone:
              oscillate = [0,1]*round(len(colmap)/2+.5)
              oscillate = [np.array([osc,osc,osc]) for osc in oscillate]
              colmap = [.8*colmap[x] + .2*oscillate[x] for x in range(len(colmap))]
          
          #Whether to shuffle the output colors
          if shuffle:
              random.seed(1)
              random.shuffle(colmap)
              
          return colmap
      

      【讨论】:

        【解决方案12】:

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

        如果您想混合序列以使相似的颜色不会彼此相邻,您可以打乱结果列表。

        我是不是在想这个?

        【讨论】:

        • 是的,你想太多了。不幸的是,人类的颜色感知不是线性的。如果您使用不同的强度,您可能还需要考虑 Bezold–Brücke 位移。这里也有很好的资料:vis4.net/blog/posts/avoid-equidistant-hsv-colors
        【解决方案13】:

        这在 MATLAB 中很简单(有一个 hsv 命令):

        cmap = hsv(number_of_colors)
        

        【讨论】:

          【解决方案14】:

          我为 R 编写了一个名为 qualpalr 的包,专门为此目的而设计。我建议您查看vignette 以了解其工作原理,但我会尝试总结要点。

          qualpalr 采用HSL color space 中的颜色规范(之前在此线程中进行了描述),将其投影到 DIN99d 颜色空间(在感知上是均匀的)并找到最大化任意之间最小距离的 n oif 他们。

          # 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)
          

          【讨论】:

            【解决方案15】:

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

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

            【讨论】:

              【解决方案16】:

              seaborn 对于 Python 用户来说非常简洁:

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

              【讨论】:

              • 只有10种颜色。
              【解决方案17】:

              此 OpenCV 函数使用 HSV 颜色模型在 0n 均匀分布的颜色,最大 S=1.0 和 V=1.0。函数输出bgr_mat中的BGR颜色:

              void distributed_colors (int n, cv::Mat_<cv::Vec3f> & bgr_mat) {
                cv::Mat_<cv::Vec3f> hsv_mat(n,CV_32F,cv::Vec3f(0.0,1.0,1.0));
                double step = 360.0/n;
                double h= 0.0;
                cv::Vec3f value;
                for (int i=0;i<n;i++,h+=step) {
                  value = hsv_mat.at<cv::Vec3f>(i);
                  hsv_mat.at<cv::Vec3f>(i)[0] = h;
                }
                cv::cvtColor(hsv_mat, bgr_mat, CV_HSV2BGR);
                bgr_mat *= 255;
              }
              

              【讨论】:

                【解决方案18】:

                Janus 的答案,但更容易阅读。我还稍微调整了配色方案并标记了您可以自己修改的位置

                我已经把它做成了一个 sn-p,可以直接粘贴到 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()
                

                【讨论】:

                • 我希望这不是外星语言。 Python 没有意义
                猜你喜欢
                • 2011-05-05
                • 1970-01-01
                • 1970-01-01
                • 2013-07-06
                • 2012-03-08
                • 2021-03-08
                • 2021-06-02
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多