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

(1/1)
補色の組み合わせは互いの色を引き立て合う相乗効果がある。
今回はPHPを使い、あるカラーコードを指定すると、その補色を計算し、色相環 (しきそうかん) の上に位置をプロットするプログラムを作ってみることにする。
(2026年2月21日)PHP8.5対応:imagedestroyを使わない

目次

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

色相環と補色

サンプル・プログラム

圧縮ファイルの内容
complementaryColor.phpサンプル・プログラム本体
pahooInputData.phpデータ入力に関わる関数群。
使い方は「数値入力とバリデーション」「文字入力とバリデーション」などを参照。include_path が通ったディレクトリに配置すること。
pahooColor.php色に関わるクラス pahooColor。
色に関わるクラスの使い方は「PHPでCSSカラー一覧を表示する」「PHPで補色関係を色相環に描画」を参照。include_path が通ったディレクトリに配置すること。
complementaryColor.php 更新履歴
バージョン 更新日 内容
1.3.0 2026/02/21 PHP8.5対応:imagedestroyを使わない
1.2.0 2024/12/21 主な変換式をクラス pahooColor に分離
1.1 2022/04/09 PHP8対応,リファラ・チェック改良
1.0 2017/02/24 初版
pahooInputData.php 更新履歴
バージョン 更新日 内容
2.0.1 2025/08/11 getParam() bug-fix
2.0.0 2025/08/11 pahooLoadEnv() 追加
1.9.0 2025/07/26 getParam() 引数に$trim追加
1.8.1 2025/03/15 validRegexPattern() debug
1.8.0 2024/11/12 validRegexPattern() 追加
pahooColor.php 更新履歴
バージョン 更新日 内容
1.0.0 2024/12/21 初版

準備:pahooInputData 関数群

PHPのバージョンや入力データのバリデーションなど、汎用的に使う関数群を収めたファイル "pahooInputData.php" が同梱されているが、include_path が通ったディレクトリに配置してほしい。他のプログラムでも "pahooInputData.php" を利用するが、常に最新のファイルを1つ配置すればよい。

また、各種クラウドサービスに登録したときに取得するアカウント情報、アプリケーションパスワードなどを登録した .pahooEnv ファイルから読み込む関数 pahooLoadEnv を備えている。こちらについては、「各種クラウド連携サービス(WebAPI)の登録方法」をご覧いただきたい。

色相環と補色

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

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

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

準備

complementaryColor.php

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

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

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

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

解説:補色を計算する

pahooColor.php

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

RGBで与えられたカラーコードから補色R'G'B'を求める計算式は次の通り――。 $$ \displaystyle \begin{align*} 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{align*} $$

解説:RGB→HSL変換

pahooColor.php

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

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

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

解説:HSL→RGB変換

pahooColor.php

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

逆に、HSLからRGBへの変換式は次の通りー。 $$ \begin{aligned} \text{MAX} = \left\{ \begin{array}{ll} \displaystyle 2.55 \times \left(L + L \times \frac{S}{100} \right) & \text{if} \quad L \leq 49 \\ \displaystyle 2.55 \times \left(L + (100 - L) \times \frac{S}{100} \right) & \text{if} \quad L > 49 \end{array} \right. \\ \\ \text{MIN} = \left\{ \begin{array}{ll} \displaystyle 2.55 \times \left(L - L \times \frac{S}{100} \right) & \text{if} \quad L \leq 49 \\ \displaystyle 2.55 \times \left(L - (100 - L) \times \frac{S}{100} \right) & \text{if} \quad L > 49 \end{array} \right. \\ \\ R = \left\{ \begin{array}{ll} \displaystyle \text{MAX} & \text{if} \quad H < 60 \ \text{or} \ H \geq 300 \\ \displaystyle \text{MIN} + (\text{MAX} - \text{MIN}) \times \frac{120 - H}{60} & \text{if} \quad 60 \leq H < 120 \\ \displaystyle \text{MIN} & \text{if} \quad 120 \leq H < 240 \\ \displaystyle \text{MIN} + (\text{MAX} - \text{MIN}) \times \frac{H - 240}{60} & \text{if} \quad 240 \leq H < 300 \end{array} \right. \\ \\ G = \left\{ \begin{array}{ll} \displaystyle \text{MIN} + (\text{MAX} - \text{MIN}) \times \frac{H}{60} & \text{if} \quad H < 60 \\ \displaystyle \text{MAX} & \text{if} \quad 60 \leq H < 180 \\ \displaystyle \text{MIN} + (\text{MAX} - \text{MIN}) \times \frac{240 - H}{60} & \text{if} \quad 180 \leq H < 240 \\ \displaystyle \text{MIN} & \text{if} \quad 240 \leq H < 360 \end{array} \right. \\ \\ B = \left\{ \begin{array}{ll} \displaystyle \text{MIN} & \text{if} \quad H < 120 \\ \displaystyle \text{MIN} + (\text{MAX} - \text{MIN}) \times \frac{H - 120}{60} & \text{if} \quad 120 \leq H < 180 \\ \displaystyle \text{MAX} & \text{if} \quad 180 \leq H < 300 \\ \displaystyle \text{MIN} + (\text{MAX} - \text{MIN}) \times \frac{360 - H}{60} & \text{if} \quad 300 \leq H < 360 \end{array} \right. \end{aligned} $$

解説:色相環を描く

complementaryColor.php

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

色相環を描くのには 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