Функция создания цветовых кругов

Это то, что я псевдо-решал много раз, но так и не нашел решения.

Проблема состоит в том, чтобы придумать способ генерировать N цвета, которые были бы как можно более различимыми в зависимости N от параметра.

Ответов (8)

Решение

Моя первая мысль об этом - «как сгенерировать N векторов в пространстве, максимально удаленных друг от друга».

Вы можете видеть, что RGB (или любой другой масштаб, который вы используете, который формирует основу в цветовом пространстве) - это просто векторы. Взгляните на случайный выбор точек . Как только у вас есть набор векторов, которые максимально увеличены друг от друга, вы можете сохранить их в хеш-таблице или что-то в этом роде для дальнейшего использования и просто выполнять произвольное вращение над ними, чтобы получить все желаемые цвета, которые максимально разнесены друг от друга!

Если задуматься над этой проблемой, было бы лучше сопоставить цвета линейным образом, возможно (0,0,0) → (255,255,255) лексикографически, а затем распределить их равномерно.

Я действительно не знаю, насколько хорошо это будет работать, но так должно быть, скажем так:

n = 10

мы знаем, что у нас есть 16777216 цветов (256 ^ 3).

Мы можем использовать алгоритм 515 пряжки, чтобы найти лексикографически индексированный цвет. \\\\ frac {\\\\ binom {256 ^ 3} {3}} {n} * я. Вероятно, вам придется отредактировать алгоритм, чтобы избежать переполнения, и, возможно, добавить некоторые незначительные улучшения скорости.

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 яркость.

Для достижения «наиболее различимого» нам необходимо использовать перцептивное цветовое пространство, такое как Lab (или любое другое перцептивно линейное цветовое пространство), отличное от RGB. Кроме того, мы можем квантовать это пространство, чтобы уменьшить его размер.

Сгенерируйте полное трехмерное пространство со всеми возможными квантованными записями и запустите алгоритм K-средних с помощью K=N . Полученные центры / «средства» должны быть примерно максимально различимы друг от друга.

Разве это не фактор, в котором вы устанавливаете цвета?

Например, если вы используете идею Dillie-Os, вам нужно как можно больше смешивать цвета. 0 64 128 256 - от одного к другому. но 0 256 64 128 в колесе было бы больше "отдельно"

Имеет ли это смысл?

Я где-то читал, что человеческий глаз не может различить менее 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 значения и добавлять цвет в массив в приведенном выше примере.

Лучше всего найти цвета, максимально удаленные друг от друга в «перцептивно однородном» цветовом пространстве, например CIELAB (используя евклидово расстояние между координатами L *, a *, b * в качестве метрики расстояния), а затем преобразовать в цветовое пространство по вашему выбору. Единообразие восприятия достигается за счет настройки цветового пространства для аппроксимации нелинейностей в зрительной системе человека.

Некоторые связанные ресурсы:

ColorBrewer - Наборы цветов, предназначенные для максимального различения при использовании на картах.

Escaping RGBland: Выбор цветов для статистической графики - технический отчет, описывающий набор алгоритмов для создания хороших (т. Е. Максимально различимых) наборов цветов в цветовом пространстве hcl.

Вот код для равномерного распределения цветов RGB по цветовому кругу HSL указанной яркости.

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