読者です 読者をやめる 読者になる 読者になる

Processingでそれっぽい色の組み合わせを自動生成する

Processing Advent Calendar 2016、13日目の記事です。

去年に続き2回目の参戦になります。kitaoです。よろしくお願いします。

私はどうにも配色というのが苦手で、Processingで何かを作るときは、つい#00FF00とか#FF8080みたいな十六進数的にはキリはいいけどセンスのない色を適当にチョイスしてしまいます。

そこで今回は必要な色数を指定するだけで、勝手にそれっぽい配色を作成してくれる関数を作ってみたいと思います。

Processingによる配色の自動生成

そもそも、配色をどう考えるべきかよくわかっていないので、まずは役立ちそうなアルゴリズムを紹介してくれているサイトを探してみました。

このあたりのサイトから楽にパクれそうな部分考え方のエッセンスを抜き出してみます。

配色の考え方

配色の自動生成には、様々な考え方や研究があるようですが、今回はざっくり以下の方針で考えてみます。

  • RGBではなくHSB色空間(色相・彩度・明度)で色を扱うと配色を作りやすい
  • 色相が近い色や、色相環を均等に割った色は相性がいいらしい

…かなり適当ですね。本職の方は見逃してください。

コード

実際に作成したコードがこちらです。

final int BOX_NUM = 200;
final int COLOR_NUM = 5;

float angle;
color[] colors;
ArrayList<SwingBox> boxList = new ArrayList<SwingBox>();

void setup() {
  size(800, 600, P3D);

  for (int i = 0; i < BOX_NUM; i++) {
    boxList.add(new SwingBox(
      random(-280, 280), random(-280, 280), random(-280, 280),
      random(10, 30), random(10, 30), random(10, 30))
    );
  }  
}

void draw() {
  if (frameCount % 100 == 1) {
    colors = makeColorScheme(COLOR_NUM);
  }
  
  background(colors[0]);
  stroke(colors[1]);
    
  translate(width / 2, height / 2, 100);
  rotateY(radians(angle));

  for (int i = 0; i < boxList.size(); i++) {
    fill(colors[i % (COLOR_NUM - 2) + 2]);
    SwingBox box = boxList.get(i);
    box.draw();
  }
  
  angle += 0.1f;
}

class SwingBox {
  float x, y, z;
  float w, h, d;
  
  SwingBox(float x, float y, float z, float w, float h, float d) {
    this.x = x; this.y = y; this.z = z;
    this.w = w; this.h = h; this.d = d;
  }
  
  void draw() {
    pushMatrix();
    
    float offset = sin(frameCount / 10.0f + y) * 10.0f;
    
    translate(x + offset, y, z);
    box(w, h, d);
    
    popMatrix();
  }
}

color hsbToRgb(int h, int s, int v){
  float f;
  int i, p, q, t;
  
  i = (int)Math.floor(h / 60.0f) % 6;
  f = (h / 60.0f) - (float)Math.floor(h / 60.0f);
  p = Math.round(v * (1.0f - (s / 255.0f)));
  q = Math.round(v * (1.0f - (s / 255.0f) * f));
  t = Math.round(v * (1.0f - (s / 255.0f) * (1.0f - f)));
      
  int r = 0, g = 0, b = 0;
    
  switch (i) {
    case 0: r = v; g = t; b = p; break;
    case 1: r = q; g = v; b = p; break;
    case 2: r = p; g = v; b = t; break;
    case 3: r = p; g = q; b = v; break;
    case 4: r = t; g = p; b = v; break;
    case 5: r = v; g = p; b = q; break;
  }
  
  return color(r, g, b);
}

color[] makeColorScheme(int n) {
  int[][] hsb = new int[n][3];
  int margin = (int)random(4);
  
  hsb[0][0] = (int)random(360);
  hsb[0][1] = (int)random(100, 240);
  hsb[0][2] = (int)random(200, 240);
  
  for (int i = 1; i < n; i++) {
    hsb[i][0] = hsb[0][0] + (360 / (n+margin)) * i;
    hsb[i][1] = hsb[0][1];
    hsb[i][2] = hsb[0][2];
  }
  
  color[] colors = new color[n];
  
  for (int i = 0; i < n; i++) {
    colors[i] = hsbToRgb(hsb[i][0], hsb[i][1], hsb[i][2]);
  }
  
  return colors;
}

hsbToRgbがHSB色空間(最大値は359, 255, 255)をRGBに変換する関数。makeColorSchemeが色の数を指定すると適当に配色を作成する関数になります。

makeColorScheme関数では、ランダムで基準となる色を選択した後に、色相環を使用する色数で均等に割るか、多めの色数で割ったうちの隣接する色を選択することで、複数の色を決定しています。

動作画面

実際の動作画面がこちらです。100フレーム毎に配色を切り替えています。

センスのある配色になったかは多少微妙な気もしますが、少なくとも色を何にするかで悩まず済むようになりました!