PHPで黄金螺旋を描く

(1/1)
パルテノン神殿と黄金比
パルテノン神殿と黄金比
黄金比とは、 mimetex  であり、ヨーロッパでは古くから最も美しい長方形として親しまれてきた。古代ギリシアのパルテノン神殿やミロのビーナス、パリの凱旋門に黄金比が用いられている。
凱旋門と黄金比
凱旋門と黄金比
黄金比は、安定した美しさをもつ比率とされ、美術品や、さまざまな工業デザインに取り入れられている。
オウムガイと黄金比
オウムガイと黄金比
黄金比を使って螺旋を描いたものを黄金螺旋と呼ぶ。
オウムガイの螺旋模様は黄金螺旋といわれている。このほか、自然界には黄金螺旋が見られるが、なぜその比率になっているかは分かっていない。

今回は、PHPを使って黄金螺旋を描くプログラムを作ってみる。
(2022年4月24日)PHP8対応,リファラ・チェック改良

目次

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

PHPで黄金螺旋を描く
PNG画像としてブラウザに表示する。

実行時に、いくつかのURLパラメータを渡すことができる。
【例】GoldenSpiral.php?trans=1&n=12
    背景を透明にして、12段階の螺旋を描く。
パラメータ 内容 範 囲 デフォルト値
type 描画モード 1:黄金長方形のみ描く,2:黄金螺旋のみ描く,3:両方を描く 3
inspect 検証モード 0:通常モード,1:検証モード 0
margin 左右マージン(ピクセル) 0~80 1
trans 背景を透過するかどうか 0:背景色で塗りつぶす,1:透過する 0
width 描画領域の横幅(ピクセル) 100~1000 600
n 頂点の数 0~15 10
thick1 黄金長方形の線の太さ 1~10 1
thick2 黄金螺旋の線の太さ 1~10 3
bgcolor 背景色 000000~FFFFFF FFFFFF
color1 黄金長方形の描画色 000000~FFFFFF 000000
color2 黄金螺旋の描画色 000000~FFFFFF FF0000

サンプル・プログラム

圧縮ファイルの内容
GoldenSpiral.phpサンプル・プログラム本体

準備:定数

  33: //デフォルト表示幅(ピクセル)
  34: define('WIDTH', 600);
  35: 
  36: //黄金比
  37: define('RATIO', ((1 + sqrt(5)) / 2));
  38: 
  39: //検証モードで使うカラー
  40: define('INSPECT_COLOR1', '000000');     //描画済み線分
  41: define('INSPECT_COLOR2', '0000FF');     //矩形
  42: define('INSPECT_COLOR3', 'FF0000');     //円弧
  43: define('INSPECT_COLOR4', 'AAFFFF');     //塗りつぶし
  44: //検証モードの線分の太さ
  45: define('INSPECT_THICK1', 1);            //描画済み
  46: define('INSPECT_THICK2', 3);            //矩形・円弧

定数 RATIO黄金比を定義する。
検証モードで使うカラーおよび線分の太さは、INSPECT_ ではじまる定数に記述する。
これらの値は自由に変更できる。

解説:各種パラメータ

 164: /**
 165:  * $_GET または $_POST から正規化した数値を取り出す
 166:  * @param   string $type int:整数 float:浮動小数
 167:  * @param   string $key キー
 168:  * @param   mixed  $min, $max 最小値、最大値
 169:  * @param   string $errmsg エラーメッセージを格納する
 170:  * @return  mixed 取得値/NULL:エラー
 171: */
 172: function getParameters(&$items) {
 173:     static $tbl_val = array(
 174:         //描画モード
 175:         'mode'=>array('type'=>'int', 'min'=>1, 'max'=>3, 'def'=>3),
 176:         //検証モード
 177:         'inspect'=>array('type'=>'int', 'min'=>0, 'max'=>1, 'def'=>0),
 178:         //左右マージン(ピクセル)
 179:         'margin'=>array('type'=>'int', 'min'=>0, 'max'=>80, 'def'=>1),
 180:         //背景を透過するかどうか
 181:         'trans'=>array('type'=>'int', 'min'=>0, 'max'=>1, 'def'=>0),
 182:         //描画領域の横幅(ピクセル)
 183:         'width'=>array('type'=>'float', 'min'=>100, 'max'=>1000, 'def'=>WIDTH),
 184:         //頂点の数
 185:         'n'=>array('type'=>'int', 'min'=>0, 'max'=>15, 'def'=>10),
 186:         //黄金長方形の線の太さ
 187:         'thick1'=>array('type'=>'int', 'min'=>1, 'max'=>10, 'def'=>1),
 188:         //黄金螺旋の線の太さ
 189:         'thick2'=>array('type'=>'int', 'min'=>1, 'max'=>10, 'def'=>3)
 190:     );
 191:     static $tbl_str = array(
 192:         //背景色
 193:         'bgcolor'=>array('pat'=>'/^([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})/i', 'def'=>'FFFFFF'),
 194:         //黄金長方形の描画色
 195:         'color1'=>array('pat'=>'/^([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})/i', 'def'=>'000000'),
 196:         //黄金螺旋の描画色
 197:         'color2'=>array('pat'=>'/^([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})/i', 'def'=>'FF0000')
 198:     );
 199:     foreach ($tbl_val as $key=>$arr) {
 200:         $items[$key] = getValidNumber($key, $arr['type'], $arr['min'], $arr['max'], $arr['def']);
 201:     }
 202:     foreach ($tbl_str as $key=>$arr) {
 203:         $items[$key] = getValidString($key, $arr['pat'], $arr['def']);
 204:     }
 205: }

上述のURLパラメータを取り出すのがユーザー関数 getParameters である。「PHPで雪の結晶を描く」で紹介したものと同じである。
配列 $tbl_val および $tbl_str を変更することで、パラメータの範囲やデフォルト値を変更できる。

解説:頂点パラメータを計算

 219: /**
 220:  * 頂点パラメータを計算
 221:  * @param   int $n 頂点の数
 222:  * @return  array  頂点パラメータ
 223:  *                  [$i][0] X座標(0.0以上1.0以下)
 224:  *                  [$i][1] Y座標(0.0以上1.0以下)
 225:  *                  [$i][2] 辺の長さ(0.0以上1.0以下)
 226:  *                  [$i][3] 方向(0度以上360度以下)
 227: */
 228: function calcApexes($n) {
 229:     $k = RATIO;
 230:     $p = array();
 231:     if ($n == 0)    return $p;
 232: 
 233:     //始点
 234:     $p[0][0] = 1.0;
 235:     $p[0][1] = 0.0;
 236:     $p[0][2] = 1.0;
 237:     $p[0][3] = 90;
 238: 
 239:     //頂点座標の計算
 240:     for ($i = 1$i < $n$i++) {
 241:         $p[$i][2] = $p[$i - 1][2* ($k - 1);
 242:         switch ($p[$i - 1][3]) {
 243:             case 90:
 244:                 $p[$i][0] = $p[$i - 1][0];
 245:                 $p[$i][1] = $p[$i - 1][1+ $p[$i - 1][2- $p[$i][2];
 246:                 $p[$i][3] = 0;
 247:                 break;
 248:             case 0;
 249:                 $p[$i][0] = $p[$i - 1][0+ $p[$i - 1][2- $p[$i][2];
 250:                 $p[$i][1] = $p[$i - 1][1];
 251:                 $p[$i][3] = 270;
 252:                 break;
 253:             case 270;
 254:                 $p[$i][0] = $p[$i - 1][0];
 255:                 $p[$i][1] = $p[$i - 1][1- $p[$i - 1][2+ $p[$i][2];
 256:                 $p[$i][3] = 180;
 257:                 break;
 258:             case 180;
 259:                 $p[$i][0] = $p[$i - 1][0- $p[$i - 1][2+ $p[$i][2];
 260:                 $p[$i][1] = $p[$i - 1][1];
 261:                 $p[$i][3] = 90;
 262:                 break;
 263:         }
 264:     }
 265:     return $p;
 266: }

黄金長方形の描画開始点、および黄金螺旋の中心点を頂点と呼ぶことにする。
ユーザー関数 calcApexes は、指定した数だけ頂点の座標を計算して配列に格納する。あわせて、黄金長方形の1辺の長さ(=黄金螺旋の半径)、次の正方形を配置する方向(=螺旋の描画開始角度)を計算し、同じ配列に格納する。

座標計算は、方向に応じて場合分けすることで計算する。1辺の長さは、黄金比に応じて小さくなってゆく。
黄金長方形
描画領域の横幅を w、黄金比をRとすると、1つめの頂点の座標は  mimetex  となる。方向は90度(PHPのGDでは、3時方向を0度、時計回りに度数をカウント)、1辺の長さは  mimetex  である。
黄金長方形
2つめの頂点の座標は、1つめの方向(90度)へ向かって、X座標は同じで、Y座標を  mimetex  だけ増やした座標である。方向は180度、1辺の長さは  mimetex  である。
黄金長方形
同様に、3つめ、4つめの黄金長方形を配置してゆく。
黄金長方形

解説:黄金長方形を描く

 268: /**
 269:  * 黄金長方形を描く
 270:  * @param   array $p 頂点パラメータ
 271:  * @param   obj   $image イメージストリーム
 272:  * @param   int   $width 描画領域の横幅(ピクセル)
 273:  * @param   int   $margin  左右マージン(ピクセル)
 274:  * @param   int   $color 色ID
 275:  * @param   int   $thick 太さ(省略時=1)
 276:  * @param   bool  $inspect FALSE:通常モード(デフォルト)/TRUE:検証モード
 277:  * @return  bool TRUE:成功/FALSE:失敗
 278: */
 279: function drawRect($p, $image, $width, $margin, $color, $thick=1, $inspect=FALSE) {
 280:     $res = TRUE;
 281:     $w = (double)($width - $margin * 2) / RATIO;
 282: 
 283:     //一番外側の長方形
 284:     imagesetthickness($image, INSPECT_THICK2);
 285:     imagerectangle($image, $margin, (int)($margin / RATIO), $width - $margin, (int)($margin / RATIO + floor($w)), INSPECT_COLOR1);
 286: 
 287:     //黄金長方形
 288:     $n = count($p- 1;
 289:     foreach ($p as $i=>$val) {
 290:         //始点
 291:         $x1 = $x0 = $val[0];
 292:         $y1 = $y0 = $val[1];
 293:         $len = $val[2];
 294:         $dir = $val[3];
 295:         //正方形を描く
 296:         for ($j = 0$j < 4$j++) {
 297:             switch ($dir) {
 298:                 case 90:
 299:                     $x2  = $x1;
 300:                     $y2  = $y1 + $len;
 301:                     $dir = 180;
 302:                     break;
 303:                 case 180;
 304:                     $x2  = $x1 - $len;
 305:                     $y2  = $y1;
 306:                     $dir = 270;
 307:                     break;
 308:                 case 270;
 309:                     $x2  = $x1;
 310:                     $y2  = $y1 - $len;
 311:                     $dir = 0;
 312:                     break;
 313:                 case 0;
 314:                     $x2  = $x1 + $len;
 315:                     $y2  = $y1;
 316:                     $dir = 90;
 317:                     break;
 318:             }
 319:             if ($x2 < $x0)   $x0 = $x2;
 320:             if ($y2 < $y0)   $y0 = $y2;
 321:             if (! $inspect) {
 322:                 $tck = $thick;
 323:                 $col = $color;
 324:             } else {
 325:                 if ($i !$n) {
 326:                     $tck = INSPECT_THICK1;
 327:                     $arr = color2rgb(INSPECT_COLOR1);
 328:                     $col = imagecolorallocate($image, $arr[0], $arr[1], $arr[2]);
 329:                 } else {
 330:                     $tck = INSPECT_THICK2;
 331:                     $arr = color2rgb(INSPECT_COLOR2);
 332:                     $col = imagecolorallocate($image, $arr[0], $arr[1], $arr[2]);
 333:                 }
 334:             }
 335:             imagesetthickness($image, $tck);
 336:             $res = imageline($image, (int)($margin + $x1 * $w), (int)($margin / RATIO + $y1 * $w), (int)($margin + $x2 * $w), (int)($margin / RATIO + $y2 * $w), $col);
 337:             if ($res == FALSE)  break;
 338:             $x1  = $x2;
 339:             $y1  = $y2;
 340:         }
 341:         //塗りつぶし(検証モード)
 342:         if ($inspect && ($n == $i)) {
 343:             $arr = color2rgb(INSPECT_COLOR4);
 344:             $col2 = imagecolorallocate($image, $arr[0], $arr[1], $arr[2]);
 345:             $res = imagefill($image, $margin + ($x0 + $len / 2* $w, $margin + ($y0 + $len / 2* $w, $col2);
 346:         }
 347:     }
 348:     return $res;
 349: }

calcApexes に代入された座標に従い、黄金長方形を描くのがユーザー関数 drawRect である。

まず、一番外側の長方形を組み込み関数 imagerectangle を使って描く。
黄金長方形は、実際には正方形を描いてゆくのだが、頂点が必ずしも正方形の左上端とはかぎらないので、方向の値を見ながら正方形の頂点を計算し、組み込み関数 imageline を使って4辺を描画する。

解説:黄金螺旋を描く

 351: /**
 352:  * 黄金螺旋を描く
 353:  * @param   array $p 頂点パラメータ
 354:  * @param   obj   $image イメージストリーム
 355:  * @param   int   $width 描画領域の横幅(ピクセル)
 356:  * @param   int   $margin  左右マージン(ピクセル)
 357:  * @param   int   $color 色ID
 358:  * @param   int   $thick 太さ(省略時=1)
 359:  * @param   bool  $inspect FALSE:通常モード(デフォルト)/TRUE:検証モード
 360:  * @return  bool TRUE:成功/FALSE:失敗
 361: */
 362: function drawSpiral($p, $image, $width, $margin, $color, $thick=1, $inspect=FALSE) {
 363:     $res = TRUE;
 364:     $w = (double)($width - $margin * 2) / RATIO;
 365: 
 366:     $n = count($p- 1;
 367:     foreach ($p as $i=>$val) {
 368:         if (! $inspect) {
 369:             $tck = $thick;
 370:             $col = $color;
 371:         } else {
 372:             if ($i !$n) {
 373:                 $tck = INSPECT_THICK1;
 374:                 $arr = color2rgb(INSPECT_COLOR1);
 375:                 $col = imagecolorallocate($image, $arr[0], $arr[1], $arr[2]);
 376:             } else {
 377:                 $tck = INSPECT_THICK2;
 378:                 $arr = color2rgb(INSPECT_COLOR3);
 379:                 $col = imagecolorallocate($image, $arr[0], $arr[1], $arr[2]);
 380:             }
 381:         }
 382:         imagesetthickness($image, $tck);
 383:         $res = imagearc($image, (int)($margin + $val[0* $w), (int)($margin / RATIO + $val[1* $w), (int)($val[2* $w * 2), (int)($val[2* $w * 2), $val[3], $val[3+ 90, $col);
 384:         if ($res == FALSE)  break;
 385:     }
 386:     return $res;
 387: }

calcApexes に代入された座標に従い、黄金螺旋を描くのがユーザー関数 drawSpiral である。
黄金螺旋
黄金螺旋は、頂点の座標を中心とし、半径は辺の長さ、方向から90度だけ円弧を描く。
黄金螺旋
この円弧を繋いでゆくと‥‥
黄金螺旋
黄金螺旋となる。

参考サイト

(この項おわり)
header