PHPで補色関係を色相環に描画

(1/1)
補色の組み合わせは互いの色を引き立て合う相乗効果がある。
今回はPHPを使い、あるカラーコードを指定すると、その補色を計算し、色相環 (しきそうかん) の上に位置をプロットするプログラムを作ってみることにする。
(2024年12月21日)主な変換式をクラス pahooColor に分離

目次

サンプル・プログラムの実行例

色相環と補色

サンプル・プログラム

圧縮ファイルの内容
complementaryColor.phpサンプル・プログラム本体
pahooInputData.phpデータ入力に関わる関数群。
使い方は「数値入力とバリデーション」「文字入力とバリデーション」などを参照。include_path が通ったディレクトリに配置すること。
pahooColor.php色に関わるクラス pahooColor。
色に関わるクラスの使い方は「PHPでCSSカラー一覧を表示する」「PHPで補色関係を色相環に描画」を参照。include_path が通ったディレクトリに配置すること。
complementaryColor.php 更新履歴
バージョン 更新日 内容
1.2.0 2024/12/21 主な変換式をクラス pahooColor に分離
1.1 2022/04/09 PHP8対応,リファラ・チェック改良
1.0 2017/02/24 初版
pahooInputData.php 更新履歴
バージョン 更新日 内容
1.8.0 2024/11/12 validRegexPattern() 追加
1.7.0 2024/10/09 validURL() validEmail() 追加
1.6.0 2024/10/07 isButton() -- buttonタグに対応
1.5.0 2024/01/28 exitIfExceedVersion() 追加
1.4.2 2024/01/28 exitIfLessVersion() メッセージ修正
pahooColor.php 更新履歴
バージョン 更新日 内容
1.0.0 2024/12/21 初版

色相環と補色

色相環
虹の七色(赤・橙・黄・緑・青・藍・紫)をリング状に並べたものが色相環である。

色相環の180度反対側に位置する2色を補色と呼ぶ。

補色の組み合わせは互いの色を引き立て合う相乗効果がありることから、補色調和と呼ぶ。

準備

complementaryColor.php

  57: // 初期値(START) =============================================================
  58: // 表示幅(ピクセル)
  59: define('WIDTH', 600);
  60: 
  61: // 色相環の半径
  62: define('RADIUS', 200);
  63: 
  64: // カラーコードの初期値
  65: define('DEF_COLOR', '#FFCC00');
  66: 
  67: // TrueTypeフォント;各自の環境に合わせて設定すること
  68: define('FONTFILE', '../../../../common/font/VL-PGothic-Regular.ttf');
  69: 
  70: // 色に関わるクラス:include_pathに配置すること
  71: require_once('pahooColor.php');
  72: 
  73: // 初期値(END) ===============================================================

初期値については、特段の制約はない。適宜変更してみてほしい。

今回、カラーコードをグラフィック上に文字として描画する関係で、TrueTypeフォントを用意しておく。詳しくは、「PHPでTrueTypeフォントを利用する」で紹介したとおりである。

テキスト入力やバージョンチェックは外部ファイル "pahooInputData.php" に分離しており、include_path が通ったディレクトリに配置すること。
色に関する処理はユーザー定義メソッド "pahooColor.php" に分離しており、こちらも include_path が通ったディレクトリに配置すること。

解説:補色を計算する

pahooColor.php

 399: /**
 400:  * 補色を計算する
 401:  * @param   string $hexcode = RGBコード(#ではじまる16進数6桁)
 402:  * @return  string 補色のカラーコード/FALSE:入力値異常
 403:  * @参考URL     https://www.pahoo.org/e-soul/webtech/phpgd/php06-gd-20.shtm
 404: */
 405: function complementaryColor($hex) {
 406:     $rgb = array();
 407: 
 408:     if (($rgb = $this->hex2rgb($hex)) == FALSEreturn FALSE;
 409: 
 410:     $rgbMax = max($rgb['r'], $rgb['g'], $rgb['b']);
 411:     $rgbMin = min($rgb['r'], $rgb['g'], $rgb['b']);
 412:     $cc = $rgbMin + $rgbMax;
 413:     $rgb['r'] = $cc - $rgb['r'];
 414:     $rgb['g'] = $cc - $rgb['g'];
 415:     $rgb['b'] = $cc - $rgb['b'];
 416: 
 417:     return $this->rgb2hex($rgb);
 418: }

RGBで与えられたからコードから補色R'G'B'を求める計算式は次の通り――。
\[
\displaystyle
\begin{eqnarray}
MAX &=& max(R_0, G_0, B_0)
\\\\
MIN &=& min(R_0, G_0, B_0)
\\\\
C &=& MAX + MIN
\\\\
R_1 &=& C - R_0
\\\\
G_1 &=& C - G_0
\\\\
B_1 &=& C - B_0
\end{eqnarray}
\]

解説:RGB→HSL変換

pahooColor.php

 280: /**
 281:  * RGB成分を HSL成分に変換する
 282:  * @param   array $rgb RGB成分
 283:  *                  'r' => Red成分(0~255)
 284:  *                  'g' => Green成分(0~255)
 285:  *                  'b' => Blue成分(0~255)
 286:  * @return  array HSL成分/FALSE:引数の異常
 287:  *                  'h' => 色相(0~360度
 288:  *                  's' => 彩度(0~100%)
 289:  *                  'l' => 明度(0~100%)
 290:  * @参考URL     https://www.pahoo.org/e-soul/webtech/phpgd/php06-gd-20.shtm
 291: */
 292: function rgb2hsl($rgb) {
 293:     // 引数のバリデーション
 294:     if (! $this->validateRGB($rgb)) {
 295:         $this->seterror(__FUNCTION__ . ' に渡したRGBのいずれかの値が異常です');
 296:         return FALSE;
 297:     }
 298: 
 299:     $rgbMax = max($rgb['r'], $rgb['g'], $rgb['b']);
 300:     $rgbMin = min($rgb['r'], $rgb['g'], $rgb['b']);
 301: 
 302:     $hsl['h'] = 0;
 303:     $hsl['s'] = 0;
 304:     $hsl['l'] = ($rgbMax + $rgbMin) / 2;
 305: 
 306:     if ($rgbMax !$rgbMin) {
 307:         // H(色相)
 308:         if ($rgbMax == $rgb['r']) {
 309:             $hsl['h'] = 60 * ($rgb['g'- $rgb['b']) / ($rgbMax - $rgbMin);
 310:         }
 311:         if ($rgbMax == $rgb['g']) {
 312:             $hsl['h'] = 60 * ($rgb['b'- $rgb['r']) / ($rgbMax - $rgbMin+ 120;
 313:         }
 314:         if ($rgbMax == $rgb['b']) {
 315:             $hsl['h'] = 60 * ($rgb['r'- $rgb['g']) / ($rgbMax - $rgbMin+ 240;
 316:         }
 317:         // S(彩度)
 318:         if ($hsl['l'<127) {
 319:             $hsl['s'] = ($rgbMax - $rgbMin) / ($rgbMax + $rgbMin);
 320:         } else {
 321:             $hsl['s'] = ($rgbMax - $rgbMin) / (510 - $rgbMax - $rgbMin);
 322:         }
 323:     }
 324:     if ($hsl['h'< 0) {
 325:         $hsl['h'+360;
 326:     }
 327: 
 328:     $hsl['h'] = round($hsl['h']);
 329:     $hsl['s'] = round($hsl['s'* 100);
 330:     $hsl['l'] = round(($hsl['l'] / 255* 100);
 331: 
 332:     return $hsl;
 333: }

色相環は、文字通り、色相をリング状に並べたカラーチャートである。RGB色空間とは異なる、HLS色空間を使って描くことができる。

HSL色空間は、 H(色相;0~360度)、S(彩度;0~100%)、L(明度;0~100%)の3値でカラーを表す。RGBからHSLへの変換式は次の通りー。
\[
\displaystyle
\begin{eqnarray}
MAX &=& max(r,g,b)
\\
\\
MIN &=& min(r,g,b)
\\
\\
H &=& \left\{
\begin{array}{ll}
undefined&:&if \quad MIN = MAX \\
\displaystyle
60\times\frac{G - R}{MAX - MIN}+ 60&:&if \quad MIN=B \\
\displaystyle
60\times\frac{B - G}{MAX - MIN}+ 60&:&if \quad MIN=R \\
\displaystyle
60\times\frac{R - B}{MAX - MIN}+ 60&:&if \quad MIN=G
\end{array}
\right.
\\
\\
l &=& \frac{MAX+MIN}{2}
\\
\\
S &=& \left\{
\begin{array}{ll}
\displaystyle
\frac{MAX-MIN}{MAX+MIN}&:&if \quad l\leq127 \\
\displaystyle
\frac{MAX-MIN}{510-MAX-MIN}&:&if \quad l\>127
\end{array}
\right.
\\
\\
\displaystyle
L &=& \frac{l}{255}\times100
\end{eqnarray}
\]

解説:HSL→RGB変換

pahooColor.php

 335: /**
 336:  * HSL成分を RGB成分に変換する
 337:  * @param   array $hsl HSL成分
 338:  *                  'h' => 色相(0~360度
 339:  *                  's' => 彩度(0~100%)
 340:  *                  'l' => 明度(0~100%)
 341:  * @param   array $rgb RGB成分
 342:  *                  'r' => Red成分(0~255)
 343:  *                  'g' => Green成分(0~255)
 344:  *                  'b' => Blue成分(0~255)
 345:  * @参考URL     https://www.pahoo.org/e-soul/webtech/phpgd/php06-gd-20.shtm
 346: */
 347: function hsl2rgb($hsl) {
 348:     // 引数のバリデーション
 349:     if (! $this->validateHSL($hsl)) {
 350:         $this->seterror(__FUNCTION__ . ' の引数が異常です');
 351:         return FALSE;
 352:     }
 353: 
 354:     if ($hsl['h'] == 360) {
 355:         $h = 0;
 356:     }
 357:  
 358:     if ($hsl['l'<49) {
 359:         $hslMax = 2.55 * ($hsl['l'+ $hsl['l'* ($hsl['s'] / 100));
 360:         $hslMin = 2.55 * ($hsl['l'- $hsl['l'* ($hsl['s'] / 100));
 361:     } else {
 362:         $hslMax = 2.55 * ($hsl['l'+ (100 - $hsl['l']) * ($hsl['s'] / 100));
 363:         $hslMin = 2.55 * ($hsl['l'- (100 - $hsl['l']) * ($hsl['s'] / 100)); 
 364:     }
 365: 
 366:     if ($hsl['h'< 60) {
 367:         $rgb['r'] = $hslMax;
 368:         $rgb['g'] = $hslMin + ($hslMax - $hslMin* ($hsl['h'] / 60) ;
 369:         $rgb['b'] = $hslMin;
 370:     } else if ($hsl['h'>60 && $hsl['h'< 120) {
 371:         $rgb['r'] = $hslMin + ($hslMax - $hslMin* ((120 - $hsl['h']) / 60);
 372:         $rgb['g'] = $hslMax;
 373:         $rgb['b'] = $hslMin;
 374:     } else if ($hsl['h'>120 && $hsl['h'< 180) {
 375:         $rgb['r'] = $hslMin;
 376:         $rgb['g'] = $hslMax ;
 377:         $rgb['b'] = $hslMin + ($hslMax - $hslMin* (($hsl['h'- 120) / 60);
 378:     } else if ($hsl['h'>180 && $hsl['h'< 240) {
 379:         $rgb['r'] = $hslMin;
 380:         $rgb['g'] = $hslMin + ($hslMax - $hslMin* ((240 - $hsl['h']) / 60);
 381:         $rgb['b'] = $hslMax;
 382:     } else if ($hsl['h'>240 && $hsl['h'< 300) {
 383:         $rgb['r'] = $hslMin + ($hslMax - $hslMin* (($hsl['h'- 240) / 60);
 384:         $rgb['g'] = $hslMin;
 385:         $rgb['b'] = $hslMax;
 386:     } else if ($hsl['h'>300 && $hsl['h'< 360) {
 387:         $rgb['r'] = $hslMax;
 388:         $rgb['g'] = $hslMin;
 389:         $rgb['b'] = $hslMin + ($hslMax - $hslMin* ((360 - $hsl['h']) / 60);
 390:     }
 391: 
 392:     $rgb['r'] = round($rgb['r']);
 393:     $rgb['g'] = round($rgb['g']);
 394:     $rgb['b'] = round($rgb['b']);
 395: 
 396:     return $rgb;
 397: }

逆に、HSLからRGBへの変換式は次の通りー。

\[
\displaystyle
\begin{eqnarray}
MAX &=& \left\{
\begin{array}{ll}
\displaystyle
2.55 \times (L + L \times \frac{S}{100})&:&if \quad L\leq49 \\
\displaystyle
2.55 \times (L + (100 - L) \times \frac{S}{100})&:&if \quad L>49 \\
\end{array}
\right.
\\
\\
MIN &=& \left\{
\begin{array}{ll}
\displaystyle
2.55 \times (L - L \times \frac{S}{100})&:&if \quad L\leq49 \\
\displaystyle
2.55 \times (L - (100 - L) \times \frac{S}{100})&:&if \quad L>49 \\
\end{array}
\right.
\\
\\
\displaystyle
R &=& \left\{
\begin{array}{ll}
MAX&:&if \quad H<60\ or\ H\geq300 \\
\displaystyle
MIN + (MAX - MIN) \times \frac{120-H}{60}&:&if \quad H\geq60\ and\ H<120 \\
MIN&:&if \quad H\geq120\ and\ H<240 \\
\displaystyle
MIN + (MAX - MIN) \times \frac{H-240}{60}&:&if \quad H\geq240\ and\ H<300
\end{array}
\right.
\\
\\
\displaystyle
G &=& \left\{
\begin{array}{ll}
\displaystyle
MIN + (MAX - MIN) \times \frac{H}{60}&:&i \quad H<60 \\
MAX&:&if \quad H\geq60\ and\ H<180 \\
\displaystyle
MIN + (MAX - MIN) \times (\frac{240-H}{60})&:&if \quad H\geq180\ and\ H<240 \\
MIN&:&if\ H\geq240\ and\ H<360
\end{array}
\right.
\\
\\
\displaystyle
B &=& \left\{
\begin{array}{ll}
MIN&:&if \quad H<120 \\
\displaystyle
MIN + (MAX - MIN) \times \frac{H-120}{60}&:&if \quad H\geq120\ and\ H<180 \\
MAX&:&if \quad H\geq180\ and\ H<300 \\
\displaystyle
MIN + (MAX - MIN) \times \frac{360-H}{60}&:&if \quad H\geq300\ and\ H<360
\end{array}
\right.
\end{eqnarray}
\]

解説:色相環を描く

complementaryColor.php

 136: /**
 137:  * 色相環を描画
 138:  * @param   object $pcc pahooColorオブジェクト
 139:  * @param   float  $r 半径
 140:  * @param   array  $spots スポットする複数のカラーコード[省略可]
 141:  *                  #xxxxxx形式表記
 142:  * @return  array(最小年,最大年)
 143: */
 144: function drawColorCircle($pcc, $r, $spots=NULL) {
 145:     $rgb = array();
 146:     $hsl = array();
 147: 
 148:     $canvas  = imagecreatetruecolor($r * 2, $r * 2);
 149:     $white = imagecolorallocate($canvas, 255, 255, 255);
 150:     $black = imagecolorallocate($canvas, 0, 0, 0);
 151:     imagecolortransparent($canvas, $black); //背景を透明に
 152: 
 153:     for ($h = 0$h < 360$h++) {
 154:         $hsl['h'] = $h;
 155:         $hsl['s'] = 100;
 156:         $hsl['l'] = 50;
 157:         $rgb = $pcc->hsl2rgb($hsl);
 158:         $color = imagecolorallocate($canvas, $rgb['r'], $rgb['g'], $rgb['b']);
 159:         $start = $h - 90;
 160:         imagefilledarc($canvas, $r, $r, $r * 2, $r * 2, $start, $start + 2, $color, 0);
 161:     }
 162: 
 163:     //カラーコード(配列)をスポットする
 164:     if ($spots !NULL) {
 165:         foreach ($spots as $hex) {
 166:             if (($rgb = $pcc->hex2rgb($hex)) == FALSE)  continue;
 167:             $hsl = $pcc->rgb2hsl($rgb);
 168:             $th = deg2rad($hsl['h'- 90);
 169:             $x = ($r / 1.5* cos($th);
 170:             $y = ($r / 1.5* sin($th);
 171:             $c1 = imagecolorallocate($canvas, $rgb['r'], $rgb['g'], $rgb['b']);
 172:             imagefilledellipse($canvas, (int)($r + $x), (int)($r + $y), (int)($r / 2), (int)($r / 2), (int)$c1);
 173:             imageellipse($canvas, (int)($r + $x), (int)($r + $y), (int)($r / 2), (int)($r / 2), (int)$white);
 174:             $hex = $pcc->rgb2hex($rgb);
 175:             $cc = $pcc->complementaryColor($hex);       //補色
 176:             $rgb = $pcc->hex2rgb($cc);
 177:             $c2 = imagecolorallocate($canvas, $rgb['r'], $rgb['g'], $rgb['b']);
 178:             imagettftext($canvas, (int)($r / 16), 0, (int)($r + $x - $r / 4 + $r / 16), (int)($r + $y + $r / 32), (int)$c2, FONTFILE, $hex);
 179:         }
 180:     }
 181: 
 182:     header('Content-type: image/png');      //MIMEはPNGで
 183:     imagepng($canvas);                      //ブラウザ表示
 184:     imagedestroy($canvas);                  //後処理
 185: }

色相環を描くのには GD関数を用いる。

まず、GD関数  imagecreatetruecolor  を使ってキャンパスを用意。続いて、GD関数  imagecolortransparent  を使って背景を透明にしておく。

色相環は、色相 $h を0度から359度まで回しながら、先ほど作ったユーザー関数 hsl2rgb によってRGBカラーを計算し、GD関数  imagefilledarc  を使って円弧を塗りつぶしていく。
なお、色相環は通常、90度の位置を Red としていることから、色相 $h - 90 を角度として描画する。

次に、配列 $supots で受け取ったカラーコードにスポットを描画する。
先ほど作ったユーザー関数 rgb2hsl によって色相を算出し、これを角度として、先ほどの色相環の中のスポットする位置を三角関数によって求める。
そして、GD関数  imagefilledellipse  を使って、塗りつぶした円を描いてスポットにする。また、GD関数  imagettftext  を使ってカラーコードを文字として描画する。このとき、背景となるカラーコードから目立つように、文字カラーに補色を使った。

最期に、GD関数  imagepng  を使って画像を出力する。

活用例

みんなの知識 ちょっと便利帳」では、このサンプル・プログラムを活用し、「補色を取得し色相環に描画」というサービスを提供している。

参考サイト

(この項おわり)
header