2.3 色彩

2.3.1 RGBとCMYK

プログラミングの世界では、色を表示する場合には赤・緑・青のRGB(加法混色)というシステムを使います。パソコンのモニタや液晶テレビも同じ方式です。また、印刷物の場合にはシアン・マゼンタ・黄・黒のCMYK(減法混色)になります。プログラミングの場合には、CMYKについては考える必要はありませんが、プログラムで書いたものを印刷するということもあるでしょう。その場合にはRGBで作成したグラフィックをCMYKに変換する必要があります。

●RGB 加法混色

加法混色は、色を混ぜれば混ぜるほど白に近づきます。

図2.3-a

●CMYK 減法混色

減法混色は、色を混ぜれば混ぜるほど黒に近づきます。

図2.3-b

●RGBの混色

概念的な話だけではなかなか分かりにくいと思うので、実際にRGBの色彩を混色できるプログラムを動かしてみましょう。

2016_09_03_12_48
図2.3-c

【リスト2.3-a】のコードをProcessingに貼り付けて動かしてみてください。
上部のR、G、Bのバーをドラッグすることによって、中央の色が混色されます。コード自体は少し複雑なので、現時点で理解する必要はありません。
注: 一部の機種ではblendModeが正常に機能しない場合があります。

リスト2.3-a
int r, g, b;  //赤、緑、青用の変数
int rWidth, gWidth, bWidth;  //バーの幅

//バーの領域にマウスが入っているかを判定
boolean rFlag, gFlag, bFlag;
int barH = 15;  //バーの高さ
int margin = 50;  //文字表示用のスペース
PFont font;  //文字用変数

void setup() {
  blendMode(LIGHTEST);  //加法混色にするため、ブレンドモードを変更
  size(400, 400);
  noStroke();

  rWidth = gWidth = bWidth = width - margin;  //初期化
  rFlag = gFlag = bFlag = false;

  font = createFont("Arial", 11);
  textFont(font, 11);
}

void draw() {
  background(0);

  //map関数を使ってバーの幅を色(0~255)に変換
  r = int(map(rWidth, 0, width - margin, 0, 255));
  g = int(map(gWidth, 0, width - margin, 0, 255));
  b = int(map(bWidth, 0, width - margin, 0, 255));

  //赤のバーを描画
  fill(255, 0, 0);
  rect(0, 0, rWidth, barH);

  //緑のバーを描画
  fill(0, 255, 0);
  rect(0, barH, gWidth, barH);

  //青のバーを描画
  fill(0, 0, 255);
  rect(0, barH*2, bWidth, barH);

  //赤の円を描画
  fill(r, 0, 0);
  ellipse(150, 180, 200, 200);

  //緑の円を描画
  fill(0, g, 0);
  ellipse(250, 180, 200, 200);

  //青の円を描画
  fill(0, 0, b);
  ellipse(200, 267, 200, 200);

  //右下の四角を描画
  fill(r, g, b);
  rect(370, 370, 30, 30);

  //テキスト描画
  fill(255);
  text("R = " + r, width - margin + 3, barH - 3);
  text("G = " + g, width - margin + 3, barH*2 - 3);
  text("B = " + b, width - margin + 3, barH*3 - 3);
}

void mousePressed() {

  //選択範囲。もしマウスが赤のバーの領域に入ったら、
  if (mouseX > 0 && mouseX < width - margin
    && mouseY > 0 && mouseY < barH) {

    rWidth = mouseX; //バーの幅をmouseXにする
    rFlag = true;  //赤用フラッグをtrue 

    //緑のバーの領域
  } else if (mouseX > barH && mouseX < width - margin 
    && mouseY > 0 && mouseY < barH*2) {

    gWidth = mouseX; 
    gFlag = true; 

    //青のバーの領域
  } else if (mouseX > barH*2 && mouseX < width - margin 
    && mouseY > 0 && mouseY < barH*3) {

    bWidth = mouseX; 
    bFlag = true;
  }
}

void mouseDragged() {
  if ( mouseX <= width - margin) {

    //ドラッグしている間はバーの幅を変更する
    if (rFlag) rWidth = mouseX;
    else if (gFlag) gWidth = mouseX;
    else if (bFlag) bWidth = mouseX;
  }
}

void mouseReleased() {
  //マウスを話したらフラッグをfalseにする
  rFlag = gFlag = bFlag = false;
}

2.3.2 色指定の基本

もちろんRGBの指定もできますが、まずは簡単な方法から始めてみましょう。fill()を例にしてみます。黒から白までのグレーを描画するときには、0(黒)〜255(白)の値を取リます。この場合、値は0~255の間の値を1つだけ指定します。

fill(グレーの階調)
fill(0〜255)

0が黒で255が白なので、グレーの段階は256段階あります。厳密には、この範囲は自由に変更することができので、まずは0〜255で試してみましょう。この256段階を変更したい場合はcolorMode()を参照してください。

fill(グレーの階調, 不透明度)
fill(0〜255, 0〜255)

fillの後に値が2つ続いていたら、その値は自動的にグレーの階調不透明度だと解釈されます。
実際にプログラムを実行してみます。

color_gray
図2.3-d
//色は必ず0~255の値を取る
size(400, 400);  //ウィンドウのサイズ(幅, 高さ)
background(255);  //背景を白に設定
stroke(0);     //線は黒

//fill(グレー)
fill(127);
ellipse(150, 150, 200, 200);     //後ろの円の描画

//fill(グレー, 不透明度)
fill(180, 200);
ellipse(250, 250, 200, 200);     //手前の円の描画

しかし、一番よく使われるのは次の2種類のパターンでしょう。

fill(赤, 緑, 青)
fill(0〜255, 0〜255, 0〜255);

fill(赤, 緑, 青, 不透明度)
fill(0〜255, 0〜255, 0〜255, 0〜255);

同じように、プログラムを実行してみます。

color_rgb
図2.3-e
size(400, 400);  //ウィンドウのサイズ(幅, 高さ)
background(255);  //背景を白に設定
stroke(0);     //線は黒

//fill(赤, 緑, 青);
fill(255, 100, 20);
ellipse(150, 150, 200, 200);     //後ろの円の描画

//fill(赤, 緑, 青, 不透明度);
fill(0, 100, 150, 200);
ellipse(250, 250, 200, 200);     //手前の円の描画

また、color変数というものもあります。これは、コード上の違う箇所で同じ色を設定したい時に便利です。

color c1 = color(102, 102, 0);
fill(c1);
noStroke();
rect(30, 20, 55, 55);

また、Processingには、PhotoshopやIllustratorなどのグラフィックソフトと同じようにカラーピッカーの機能があります。インタフェース自体も、それらのソフトの仕様と似ています。
Processingでは「Color Selector」という名称になっています。ツールメニュー > 色選択…で表示してください。

2016_05_02_20_27
図2.3-f

2.3.3 カラーモード

すでにプログラミングの場合の色の表示方法(カラーモード)はRGBだと述べましたが、ProcessingにはRGBとHSBという2つのカラーモードがあります。

●RGB

コンピュータで使われる色彩の表記法として代表的なのはRGB(Red、 Green、Blue)ですが、Processingでは、透明度を表すA(alpha)が加わってRGBAもよく使われます。
RGBの色の関係は3次元的な立体で表すことができます。RGBを立体で表してみましょう。

rgbCube
図2.3-g

Processing上でドラッグしながら動かせるサンプルを作りました。Processingにコードを貼りつけて実行してみてください【リスト2.3-b】。コード自体は結構難しいので現時点で理解する必要はありません。ちなみに、このコードは、本書を全て学び終えると理解できます。

リスト2.3-b
float boxSize = 15;    //立方体のサイズ
float distance = 20;    //立方体同士の距離
float halfDis;    //立方体同士の一辺の全体の距離の半分
int boxNum = 10;    //立方体の数
float step = 255/boxNum;  //色を変化させる単位
int angleX = 30;  //x軸を中心とした回転角度の初期値
int angleY = 60;  //y軸を中心とした回転角度

void setup() {
  size(400, 400, P3D);

  //立方体が並んだ際の距離の半分
  halfDis = distance*(boxNum- 1)/2;

  noStroke();
}

void draw() {
  background(0);

  translate(width/2, height/2);    //基準点を画面中央に移動   
  rotateX(radians(-angleX));  //x軸を中心に回転
  rotateY(radians(angleY));  //y軸を中心に回転

  for (int z = 0; z < boxNum; z ++) {    //立方体を、z軸方向に並べる  
    for (int y = 0; y < boxNum; y ++) {    //立方体を、y軸方向に並べる
      for (int x = 0; x < boxNum; x ++) {    //立方体を、x軸方向に並べる

        pushMatrix();

        //一つずつ移動させる
        translate(x*distance - halfDis, y*distance - halfDis, 
          z*distance - halfDis);
        fill(255 - step*x, 255 - step*y, 255 - step*z);  //色を指定する
        box(boxSize, boxSize, boxSize);    //立方体を描く

        popMatrix();
      }
    }
  }
}

void mouseDragged() {

  //マウスをドラッグすることによって立体と回転
  angleX += (mouseY - pmouseY);  //x軸を中心とした回転
  angleY += (mouseX - pmouseX);  //y軸を中心とした回転
  if (angleX < -120) angleX = -120;  //上下は-60度から60度の間しか回転しない
  if (angleX > 0) angleX = 0;
}

このように、RGBは立方体で表すことができます。サンプルでは一辺10 x 10 x 10の1000個のキューブを表示していますが、実際には、一辺256 x 256 x 256色の16,777,216色(約1677万色)の色彩を表示することができます。

●HSB(HSV)

もう一つ、Processingで使うことができるもうひとつのカラーモードはHSB(Hue-色相、Saturation-彩度、Brightness-明度)です。
HSV (Hue-色相、Saturation-彩度、Value-明度)とも呼ばれます。
一般的にこのモードのように、色彩を「色相・彩度・明度」で捉える方法は、より人間の知覚に近いといわれます。

図2.3-h

同じように、Processing用のサンプルを作成してみました。Processingに貼りつけて回転してみてください【リスト2.3-c】。

hsbCube
図2.3-i
リスト2.3-c
float boxSize = 2;    //立方体のサイズの初期値
float distance = 28;    //縦方向の立方体同士の距離
float halfDis;    //立方体同士の一辺の全体の距離の半分
float radius = 4;  //x, y軸方向の立方体の距離の初期値
int boxNum = 8;    //立方体の数
int angleX = -50;  //x軸を中心とした回転角度の初期値
int angleZ = 0;  //z軸を中心とした回転角度の初期値

void setup() {
  size(400, 400, P3D);
  colorMode(HSB, 360, 100, 100);

  //立方体を縦方向に並べた際の距離の半分
  halfDis = distance*(boxNum - 1)/2;

  noStroke();
}

void draw() {
  background(0);

  translate(width/2, height/2);    //基準点を画面中央に移動   
  rotateX(radians(-angleX));  //x軸を中心に回転
  rotateZ(radians(-angleZ));  //z軸を中心に回転

  for (int z = 0; z < boxNum; z ++) {  //縦にも増やす
    for (int angle = 0; angle < 360; angle += 15) {  //15度ずつ移動

      //boxNumの数だけx軸方向に立方体を増やす
      for (int i = 0; i < boxNum; i ++) {

        pushMatrix();

        //円状に配置するためのxy座標の計算
        float x = i*(radius + i*1.5)*cos(radians(angle));
        float y = -i*(radius + i*1.5)*sin(radians(angle));  

        float saturation = float(i)/float(boxNum - 1)*100.0;  //彩度を計算
        float brigtness = float(z)/float(boxNum - 1)*100.0;  //明度を計算

        translate(x, y, z*distance - halfDis);  //一つずつ移動させる
        rotateZ(radians(-angle));  //円状に配置
        fill(angle, saturation, brigtness);  //色を指定する

        //立方体を描く
        box(boxSize + boxSize*i, boxSize + boxSize*i, boxSize + boxSize*i);

        popMatrix();
      }
    }
  }
}

void mouseDragged() {

  //マウスをドラッグすることによって立体と回転
  angleX += (mouseY - pmouseY);  //x軸を中心とした回転
  angleZ += (mouseX - pmouseX);  //z軸を中心とした回転
  if (angleX < -60) angleX = -60;
  if (angleX > 60) angleX = 60;
}

●HSL(HLS)

さらにもうひとつ、Processing上では利用できませんが、HSL(Hue-色相、Saturation-彩度、Luminance-輝度)というカラーモードがあります。HSLはWindowsで使われる色空間で、HLSとも呼ばれます。
HSLとHSBは概念的にはかなり近いモデルですが、実際には違うシステムです。より深く知りたい方は次のページを参考にしてください。
HSLとHSBの違い

2.3.4 RGB vs. HSB

Processingでは、RGBとHSBのカラーモードが使えます。初期値がRGBなので、プログラム上で何も宣言しなければ自動的にRGBのカラーモードだと判断されます。もし、特にこだわりがなければRGBのままで問題ありません。
しかし、一般的に色相、彩度、明度で表記するHSBの方が直感的に色を想像しやすいのは確かです。例えば、徐々に彩度を落としていくグラデーションを作りたい場合にはHSBの方が簡単に再現できますし、コードも簡単になります。数値計算上指定しやすい色はそれぞれのモードで異なるので、各プログラムに合わせてカラーモードを選んでみてください。

カラーモードをHSBにするには次のコードを実行します。

カラーモードをHSBに変換
本来、値は0~255の数値を取るが、カラーモード設定時に範囲の値は自由に設定できる。
ここでは、色相は360°、彩度100%、明度100%として、範囲の設定を次のように変更する。

//colorMode(HSB, 色相, 彩度, 明度);
colorMode(HSB, 360, 100, 100);

次のサンプルでは、左から右にかけて徐々に彩度を段階的に落としています。

図2.3-j
リスト2.3-d
size(400, 400);

//カラーモードをHSBに変換
colorMode(HSB, 360, 100, 100);

//このカラーモードの設定の場合、彩度を0、明度を100にすれば、
//色相はどんな値でも白になる。
background(0, 0, 100);
noStroke();

//左から1つ目の長方形
fill(0, 100, 50);
rect(0, 0, 80, height);

//2つ目の長方形
fill(0, 80, 50);
rect(80, 0, 80, height);

//3つ目の長方形
fill(0, 60, 50);
rect(160, 0, 80, height);

//4つ目の長方形
fill(0, 40, 50);
rect(240, 0, 80, height);

//5つ目の長方形
fill(0, 20, 50);
rect(320, 0, 80, height);

//for文でまとめると以下になる
//for (int x = 0; x < width; x+= 80) {
    
//  fill(0, 100 - x / 4, 50);
//  rect(x, 0, 80, height);
//}

これを、RGBで表すと次のコードになります。この場合はカラーモードを宣言していないので、各色0~255になります。

リスト2.3-e
size(400, 400);

//カラーモードをHSBに変換
//colorMode(HSB, 360, 100, 100);

//このカラーモードの設定の場合、彩度を0、明度を100にすれば、
//色相はどんな値でも白になる。
background(0, 0, 100);
noStroke();

//左から1つ目の長方形
fill(127, 0, 0);
rect(0, 0, 80, height);

//2つ目の長方形
fill(127, 26, 26);
rect(80, 0, 80, height);

//3つ目の長方形
fill(127, 51, 51);
rect(160, 0, 80, height);

//4つ目の長方形
fill(127, 77, 77);
rect(240, 0, 80, height);

//5つ目の長方形
fill(127, 102, 102);
rect(320, 0, 80, height);

出力した画面の色はほぼ同じですが、1つ目の四角から5つ目の四角に至るまでの数値の変化は、HSBの方が単純です。
このように、それぞれのカラーモードにおいて表示しやすい階調があります。

2.3.5 色の空間性

また、色には「膨張・進出」して見える性質のものと、「収縮・後退」して見える性質のものがあります。
一般的に、次の傾向があります。

暖色系、明るい色は、膨張・進出する
寒色系、暗い色は、収縮・後退する

暖色、寒色は【図2.3-k】のとおりです。赤から黄に至るまでが暖色系、青から紫に至るまでが寒色系、それ以外は中性色と呼ばれます。

2016_09_03_18_45
図2.3-k

参考:サンプルのコード(少し難しいので、現時点で理解する必要はありません)

リスト2.3-f
float angle = 10;
float R = 150;
PFont font;
 
size(400, 400);
background(255);
smooth();
noStroke();
colorMode(HSB, 360, 100, 100);
 
float cX = width/2;
float cY = height/2;
 
for(int i = 0; i < 360; i += angle) {
 
  fill(i, 100, 100);
  float rad = radians(i);
  ellipse(cX + R*cos(rad), cY + R*sin(rad), 20, 20);
}
 
//フォントリストの中から選ぶ。
//MacとWindowsの両方に入っているフォントが無難
font = createFont("Arial", 24);
textFont(font);
 
//テキスト表示
textSize(25);
fill(0, 100, 100);
text("暖色系", 230, 250);
 
fill(240, 150, 150);
text("寒色系", 100, 150);

この特性をうまく使うと、色彩だけで構成した画面でも手前・奥の関係を作ることができます。例えば、次のサンプルは、左から右の正方形にいくに従って彩度と明度を上げています。結果、右の正方形の方が手前にあるように見えます。

2016_09_03_18_56
図2.3-l

しかし、上段と下段を比べると、上段の黄色の正方形の方が全体的に若干手前にあるように見えます。これは、黄色が膨張色・進出色であるため、収縮色・後退色である青よりも手前に見えるためです【リスト2.3-g】。

リスト2.3-g
size(300, 300);
background(0);
colorMode(HSB, 360, 100, 100);  //カラーモードをHSBに変更
noStroke();

//黄色の四角
for (int x = 0; x < 5; x ++) {

  //左から順番に彩度と明度を20ずつ上げる
  fill(60, x*20 + 10, x*20 + 10);
  rect(x*50 + 35, height/3 - 15, 30, 30);
}

//青の四角
for (int x = 0; x < 5; x ++) {

  //左から順番に彩度と明度を20ずつ上げる
  fill(240, x*20 + 10, x*20 + 10); 
  rect(x*50 + 35, height/3*2 - 15, 30, 30);
}