创建颜色轮的函数

71

这是我几乎多次尝试解决,但从未找到完美解决方案的问题。

问题在于想要生成尽可能区分的 N 种颜色,其中 N 是一个参数。


据我最近查看,JFreeChart拥有这个精确算法,并且由于它是开源的,您可以查看它的操作。我知道我得到的颜色似乎不是沿着某个圆或球随机分布的,而是更具体地选择的。 - Kathy Van Stone
8个回答

28

我的第一反应是“如何在一个空间中生成N个向量,使它们彼此之间的距离最大化。”

你可以看到RGB(或任何其他色彩空间中形成基础的比例尺)只是向量。看一下随机点选择。一旦你有了一组彼此最大化分开的向量,你就可以将它们保存在哈希表或其他地方以供以后使用,并对它们进行随机旋转,以获取所有你需要的彼此最大化分开的颜色!

更深入考虑这个问题,最好的方法是将颜色以线性方式映射,可能是(0,0,0)→(255,255,255)辞典序,然后均匀分布它们。

我真的不知道这样做的效果如何,但是它应该有效,因为假设:

n = 10

我们知道有16777216种颜色(256^3)。

我们可以使用Buckles算法515来查找字典顺序索引的颜色。\frac {\binom {256^3} {3}} {n} * i。您可能需要编辑该算法以避免溢出,并可能添加一些小的速度改进。


1
这是不正确的,因为RGB颜色空间不是感知均匀的。 - adrienlucca.net
我同意那听起来很合理。RGB主要产生紫橙杂交色,相对较少产生蓝绿杂交色...颜色范围从红外到深蓝是均匀的,所以必须选择沿着它等距离分布的点。需要一个基于彩虹的算法。 - bandybabboon
请考虑给StackExchange Color Theory网站点赞/关注:https://area51.stackexchange.com/proposals/110687/color-theory - Adi Shavit

20

最好在"感知均匀"的颜色空间中找到距离最远的颜色,例如CIELAB(使用L*、a*、b*坐标之间的欧氏距离作为距离度量),然后转换为所选的颜色空间。通过调整颜色空间以近似人类视觉系统中的非线性来实现感知均匀性。


这可能是最好的解决方案,因为它非常直接。然而还有其他颜色差异公式需要考虑,比如CIE2000甚至CIECAM。 - adrienlucca.net

11

1
逃离RGB世界是一个必读的参考,用于选择在感知上可区分的颜色调色板。 - Drake Guan

10

这里是一些代码,用于在指定亮度的HSL色轮周围均匀分配RGB颜色。

class cColorPicker
{
public:
    void Pick( vector<DWORD>&v_picked_cols, int count, int bright = 50 );
private:
    DWORD HSL2RGB( int h, int s, int v );
    unsigned char ToRGB1(float rm1, float rm2, float rh);
};
/**

  Evenly allocate RGB colors around HSL color wheel

  @param[out] v_picked_cols  a vector of colors in RGB format
  @param[in]  count   number of colors required
  @param[in]  bright  0 is all black, 100 is all white, defaults to 50

  based on Fig 3 of http://epub.wu-wien.ac.at/dyn/virlib/wp/eng/mediate/epub-wu-01_c87.pdf?ID=epub-wu-01_c87

*/

void cColorPicker::Pick( vector<DWORD>&v_picked_cols, int count, int bright )
{
    v_picked_cols.clear();
    for( int k_hue = 0; k_hue < 360; k_hue += 360/count )
        v_picked_cols.push_back( HSL2RGB( k_hue, 100, bright ) );
}
/**

  Convert HSL to RGB

  based on http://www.codeguru.com/code/legacy/gdi/colorapp_src.zip

*/

DWORD cColorPicker::HSL2RGB( int h, int s, int l )
{
    DWORD ret = 0;
    unsigned char r,g,b;

    float saturation = s / 100.0f;
    float luminance = l / 100.f;
    float hue = (float)h;

    if (saturation == 0.0) 
    {
      r = g = b = unsigned char(luminance * 255.0);
    }
    else
    {
      float rm1, rm2;

      if (luminance <= 0.5f) rm2 = luminance + luminance * saturation;  
      else                     rm2 = luminance + saturation - luminance * saturation;
      rm1 = 2.0f * luminance - rm2;   
      r   = ToRGB1(rm1, rm2, hue + 120.0f);   
      g = ToRGB1(rm1, rm2, hue);
      b  = ToRGB1(rm1, rm2, hue - 120.0f);
    }

    ret = ((DWORD)(((BYTE)(r)|((WORD)((BYTE)(g))<<8))|(((DWORD)(BYTE)(b))<<16)));

    return ret;
}


unsigned char cColorPicker::ToRGB1(float rm1, float rm2, float rh)
{
  if      (rh > 360.0f) rh -= 360.0f;
  else if (rh <   0.0f) rh += 360.0f;

  if      (rh <  60.0f) rm1 = rm1 + (rm2 - rm1) * rh / 60.0f;   
  else if (rh < 180.0f) rm1 = rm2;
  else if (rh < 240.0f) rm1 = rm1 + (rm2 - rm1) * (240.0f - rh) / 60.0f;      

  return static_cast<unsigned char>(rm1 * 255);
}

int _tmain(int argc, _TCHAR* argv[])
{
    vector<DWORD> myCols;
    cColorPicker colpick;
    colpick.Pick( myCols, 20 );
    for( int k = 0; k < (int)myCols.size(); k++ )
        printf("%d: %d %d %d\n", k+1,
        ( myCols[k] & 0xFF0000 ) >>16,
        ( myCols[k] & 0xFF00 ) >>8,
        ( myCols[k] & 0xFF ) );

    return 0;
}

2
据我所知,将代码从C++移植到Java很简单。 - ravenspoint
不懂位移操作等其他内容时,我就无法理解这些代码了 :/ - CodeGuy
我已经提供了链接,链接到代码的解释。 - ravenspoint
如果我想让颜色与我提供的背景颜色有所区别,该怎么办? - CodeGuy
计算生成的颜色与您的背景颜色之间的“距离”。不要使用最接近您背景的颜色。 - ravenspoint
这可能有点可行,但效果不佳,CIELAB更好。 - adrienlucca.net

5

不同的颜色排列顺序也是一个影响因素,对吧?

比如说,如果你采用Dillie-O的想法,需要尽可能混合颜色。0 64 128 256 是从一个到另一个的过渡。但是在一个圆盘中,0 256 64 128 的排列会更加“分散”。

这样讲清楚了吗?


3
function random_color($i = null, $n = 10, $sat = .5, $br = .7) {
    $i = is_null($i) ? mt_rand(0,$n) : $i;
    $rgb = hsv2rgb(array($i*(360/$n), $sat, $br));
    for ($i=0 ; $i<=2 ; $i++) 
        $rgb[$i] = dechex(ceil($rgb[$i]));
    return implode('', $rgb);
}

function hsv2rgb($c) { 
    list($h,$s,$v)=$c; 
    if ($s==0) 
        return array($v,$v,$v); 
    else { 
        $h=($h%=360)/60; 
        $i=floor($h); 
        $f=$h-$i; 
        $q[0]=$q[1]=$v*(1-$s); 
        $q[2]=$v*(1-$s*(1-$f)); 
        $q[3]=$q[4]=$v; 
        $q[5]=$v*(1-$s*$f); 
        return(array($q[($i+4)%6]*255,$q[($i+2)%6]*255,$q[$i%6]*255)); //[1] 
    } 
}

只需调用random_color()函数,其中$i表示颜色编号,$n表示可能的颜色数量,$sat表示饱和度,$br表示亮度。


你能解释一下这种情况下的“i”是什么吗?问题要求N个数字。那么“i”参数是什么? - CodeGuy
random_color()函数中,$i是生成色调的“种子”,应该是从0到$n的数字,如果您没有输入种子(NULL),则函数会选择一个随机种子。$n是给定饱和度和亮度的可能颜色数量,即调色板中的颜色数量。我们基本上将360个色调度分为$n份,并使用$i作为乘数。换句话说,较高的$n将给您更多的颜色,较低的$n将给您较少的颜色,但它们彼此之间更不同。$i将标识颜色,并且如果您继续使用此函数,则始终将保持相同。希望这有所帮助。 - Mauro
我明白了!感谢您的解释。还有一件事...如果我有一个背景颜色,并且我希望所有颜色都尽可能远离它,您有什么建议吗? - CodeGuy
你需要将颜色的色相增加180度,同时保持饱和度和亮度。为此,请发布一个新问题,将链接粘贴在这里,我会进一步解释! - Mauro

3

我曾经看过一篇文章,说人眼无法区分相差不到4的颜色值。所以这是需要注意的。下面的算法没有考虑到这一点。

我不确定这是否完全符合您的要求,但这是一种随机生成不重复颜色值的方法:

(注意,下面的伪代码可能不够严谨)

//colors entered as 0-255 [R, G, B]
colors = []; //holds final colors to be used
rand = new Random();

//assumes n is less than 16,777,216
randomGen(int n){
   while (len(colors) < n){
      //generate a random number between 0,255 for each color
      newRed = rand.next(256);
      newGreen = rand.next(256);
      newBlue = rand.next(256);
      temp = [newRed, newGreen, newBlue];
      //only adds new colors to the array
      if temp not in colors {
         colors.append(temp);
      }
   }
}

你可以通过比较每个新颜色和数组中所有颜色之间的距离来优化此操作,以获得更好的可见性:

for item in color{
   itemSq = (item[0]^2 + item[1]^2 + item[2]^2])^(.5);
   tempSq = (temp[0]^2 + temp[1]^2 + temp[2]^2])^(.5);
   dist = itemSq - tempSq;
   dist = abs(dist);
}
//NUMBER can be your chosen distance apart.
if dist < NUMBER and temp not in colors {
   colors.append(temp);
}

但这种方法会显著减慢您的算法。

另一种方法是放弃随机性,系统地浏览每4个值,并在上面的示例中将颜色添加到数组中。


3
为了实现“最具区分性”,我们需要使用感知色彩空间,如Lab(或任何其他感知线性色彩空间),而不是RGB。此外,我们可以量化此空间以减小空间的大小。
生成所有可能的量化条目的完整3D空间,并使用K = N运行K-means算法。结果中心/“均值”应该大致上相互区分。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接