PHPで黄金螺旋を描く

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

今回は、PHP を使って黄金螺旋を描くプログラムを作ってみる。

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

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

サンプル・プログラム

準備:定数

0026: //黄金比
0027: define('RATIO', ((1 + sqrt(5)) / 2));
0028: 
0029: //検証モードで使うカラー
0030: define('INSPECT_COLOR1', '000000');        //描画済み線分
0031: define('INSPECT_COLOR2', '0000FF');        //矩形
0032: define('INSPECT_COLOR3', 'FF0000');        //円弧
0033: define('INSPECT_COLOR4', 'AAFFFF');        //塗りつぶし
0034: //検証モードの線分の太さ
0035: define('INSPECT_THICK1', 1);          //描画済み
0036: define('INSPECT_THICK2', 3);          //矩形・円弧

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

解説:各種パラメータ

0137: /**
0138:  * $_GET または $_POST から正規化した数値を取り出す
0139:  * @param string $type int:整数 float:浮動小数
0140:  * @param string $key キー
0141:  * @param mixed  $min, $max 最小値、最大値
0142:  * @param string $errmsg エラーメッセージを格納する
0143:  * @return mixed 取得値/NULL:エラー
0144: */
0145: function getParameters(&$items) {
0146:     static $tbl_val = array(
0147:         //描画モード
0148:         'mode'=>array('type'=>'int', 'min'=>1, 'max'=>3, 'def'=>3),
0149:         //検証モード
0150:         'inspect'=>array('type'=>'int', 'min'=>0, 'max'=>1, 'def'=>0),
0151:         //左右マージン(ピクセル)
0152:         'margin'=>array('type'=>'int', 'min'=>0, 'max'=>80, 'def'=>1),
0153:         //背景を透過するかどうか
0154:         'trans'=>array('type'=>'int', 'min'=>0, 'max'=>1, 'def'=>0),
0155:         //描画領域の横幅(ピクセル)
0156:         'width'=>array('type'=>'float', 'min'=>100, 'max'=>1000, 'def'=>600),
0157:         //頂点の数
0158:         'n'=>array('type'=>'int', 'min'=>0, 'max'=>15, 'def'=>10),
0159:         //黄金長方形の線の太さ
0160:         'thick1'=>array('type'=>'int', 'min'=>1, 'max'=>10, 'def'=>1),
0161:         //黄金螺旋の線の太さ
0162:         'thick2'=>array('type'=>'int', 'min'=>1, 'max'=>10, 'def'=>3)
0163:     );
0164:     static $tbl_str = array(
0165:         //背景色
0166:         'bgcolor'=>array('pat'=>'/^([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})/i', 'def'=>'FFFFFF'),
0167:         //黄金長方形の描画色
0168:         'color1'=>array('pat'=>'/^([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})/i', 'def'=>'000000'),
0169:         //黄金螺旋の描画色
0170:         'color2'=>array('pat'=>'/^([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})/i', 'def'=>'FF0000')
0171:     );
0172:     foreach ($tbl_val as $key=>$arr) {
0173:         $items[$key] = getValidNumber($key$arr['type'], $arr['min'], $arr['max'], $arr['def']);
0174:     }
0175:     foreach ($tbl_str as $key=>$arr) {
0176:         $items[$key] = getValidString($key$arr['pat'], $arr['def']);
0177:     }
0178: }

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

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

0192: /**
0193:  * 頂点パラメータを計算
0194:  * @param int $n 頂点の数
0195:  * @return array  頂点パラメータ
0196:  *                  [$i][0] X座標(0.0以上1.0以下)
0197:  *                  [$i][1] Y座標(0.0以上1.0以下)
0198:  *                  [$i][2] 辺の長さ(0.0以上1.0以下)
0199:  *                  [$i][3] 方向(0度以上360度以下)
0200: */
0201: function calcApexes($n) {
0202:     $k = RATIO;
0203:     $p = array();
0204:     if ($n == 0)    return $p;
0205: 
0206:     //始点
0207:     $p[0][0] = 1.0;
0208:     $p[0][1] = 0.0;
0209:     $p[0][2] = 1.0;
0210:     $p[0][3] = 90;
0211: 
0212:     //頂点座標の計算
0213:     for ($i = 1; $i < $n$i++) {
0214:         $p[$i][2] = $p[$i - 1][2] * ($k - 1);
0215:         switch ($p[$i - 1][3]) {
0216:             case 90:
0217:                 $p[$i][0] = $p[$i - 1][0];
0218:                 $p[$i][1] = $p[$i - 1][1] + $p[$i - 1][2] - $p[$i][2];
0219:                 $p[$i][3] = 0;
0220:                 break;
0221:             case 0;
0222:                 $p[$i][0] = $p[$i - 1][0] + $p[$i - 1][2] - $p[$i][2];
0223:                 $p[$i][1] = $p[$i - 1][1];
0224:                 $p[$i][3] = 270;
0225:                 break;
0226:             case 270;
0227:                 $p[$i][0] = $p[$i - 1][0];
0228:                 $p[$i][1] = $p[$i - 1][1] - $p[$i - 1][2] + $p[$i][2];
0229:                 $p[$i][3] = 180;
0230:                 break;
0231:             case 180;
0232:                 $p[$i][0] = $p[$i - 1][0] - $p[$i - 1][2] + $p[$i][2];
0233:                 $p[$i][1] = $p[$i - 1][1];
0234:                 $p[$i][3] = 90;
0235:                 break;
0236:         }
0237:     }
0238:     return $p;
0239: }

黄金長方形の描画開始点、および黄金螺旋の中心点を頂点と呼ぶことにする。
ユーザー関数 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 つめの黄金長方形を配置してゆく。
黄金長方形

解説:黄金長方形を描く

0241: /**
0242:  * 黄金長方形を描く
0243:  * @param array $p 頂点パラメータ
0244:  * @param obj   $image イメージストリーム
0245:  * @param int   $width 描画領域の横幅(ピクセル)
0246:  * @param int   $margin  左右マージン(ピクセル)
0247:  * @param int   $color 色ID
0248:  * @param int   $thick 太さ(省略時=1)
0249:  * @param bool  $inspect FALSE:通常モード(デフォルト)/TRUE:検証モード
0250:  * @return bool TRUE:成功/FALSE:失敗
0251: */
0252: function drawRect($p$image$width$margin$color$thick=1, $inspect=FALSE) {
0253:     $res = TRUE;
0254:     $w = (double)($width - $margin * 2) / RATIO;
0255: 
0256:     //一番外側の長方形
0257:     imagesetthickness($imageINSPECT_THICK2);
0258:     imagerectangle($image$margin$margin / RATIO$width - $margin$margin / RATIO + floor($w), INSPECT_COLOR1);
0259: 
0260:     //黄金長方形
0261:     $n = count($p) - 1;
0262:     foreach ($p as $i=>$val) {
0263:         //始点
0264:         $x1 = $x0 = $val[0];
0265:         $y1 = $y0 = $val[1];
0266:         $len = $val[2];
0267:         $dir = $val[3];
0268:         //正方形を描く
0269:         for ($j = 0; $j < 4; $j++) {
0270:             switch ($dir) {
0271:                 case 90:
0272:                     $x2  = $x1;
0273:                     $y2  = $y1 + $len;
0274:                     $dir = 180;
0275:                     break;
0276:                 case 180;
0277:                     $x2  = $x1 - $len;
0278:                     $y2  = $y1;
0279:                     $dir = 270;
0280:                     break;
0281:                 case 270;
0282:                     $x2  = $x1;
0283:                     $y2  = $y1 - $len;
0284:                     $dir = 0;
0285:                     break;
0286:                 case 0;
0287:                     $x2  = $x1 + $len;
0288:                     $y2  = $y1;
0289:                     $dir = 90;
0290:                     break;
0291:             }
0292:             if ($x2 < $x0)  $x0 = $x2;
0293:             if ($y2 < $y0)  $y0 = $y2;
0294:             if (! $inspect) {
0295:                 $tck = $thick;
0296:                 $col = $color;
0297:             } else {
0298:                 if ($i != $n) {
0299:                     $tck = INSPECT_THICK1;
0300:                     $arr = color2rgb(INSPECT_COLOR1);
0301:                     $col = imagecolorallocate($image$arr[0]$arr[1]$arr[2]);
0302:                 } else {
0303:                     $tck = INSPECT_THICK2;
0304:                     $arr = color2rgb(INSPECT_COLOR2);
0305:                     $col = imagecolorallocate($image$arr[0]$arr[1]$arr[2]);
0306:                 }
0307:             }
0308:             imagesetthickness($image$tck);
0309:             $res = imageline($image$margin + $x1 * $w$margin / RATIO + $y1 * $w$margin + $x2 * $w$margin / RATIO + $y2 * $w$col);
0310:             if ($res == FALSE)  break;
0311:             $x1  = $x2;
0312:             $y1  = $y2;
0313:         }
0314:         //塗りつぶし(検証モード)
0315:         if ($inspect && ($n == $i)) {
0316:             $arr = color2rgb(INSPECT_COLOR4);
0317:             $col2 = imagecolorallocate($image$arr[0]$arr[1]$arr[2]);
0318:             $res = imagefill($image$margin + ($x0 + $len / 2) * $w$margin + ($y0 + $len / 2) * $w$col2);
0319:         }
0320:     }
0321:     return $res;
0322: }

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

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

解説:黄金螺旋を描く

0324: /**
0325:  * 黄金螺旋を描く
0326:  * @param array $p 頂点パラメータ
0327:  * @param obj   $image イメージストリーム
0328:  * @param int   $width 描画領域の横幅(ピクセル)
0329:  * @param int   $margin  左右マージン(ピクセル)
0330:  * @param int   $color 色ID
0331:  * @param int   $thick 太さ(省略時=1)
0332:  * @param bool  $inspect FALSE:通常モード(デフォルト)/TRUE:検証モード
0333:  * @return bool TRUE:成功/FALSE:失敗
0334: */
0335: function drawSpiral($p$image$width$margin$color$thick=1, $inspect=FALSE) {
0336:     $res = TRUE;
0337:     $w = (double)($width - $margin * 2) / RATIO;
0338: 
0339:     $n = count($p) - 1;
0340:     foreach ($p as $i=>$val) {
0341:         if (! $inspect) {
0342:             $tck = $thick;
0343:             $col = $color;
0344:         } else {
0345:             if ($i != $n) {
0346:                 $tck = INSPECT_THICK1;
0347:                 $arr = color2rgb(INSPECT_COLOR1);
0348:                 $col = imagecolorallocate($image$arr[0]$arr[1]$arr[2]);
0349:             } else {
0350:                 $tck = INSPECT_THICK2;
0351:                 $arr = color2rgb(INSPECT_COLOR3);
0352:                 $col = imagecolorallocate($image$arr[0]$arr[1]$arr[2]);
0353:             }
0354:         }
0355:         imagesetthickness($image$tck);
0356:         $res = imagearc($image$margin + $val[0] * $w$margin / RATIO + $val[1] * $w$val[2] * $w * 2, $val[2] * $w * 2, $val[3]$val[3] + 90, $col);
0357:         if ($res == FALSE)  break;
0358:     }
0359:     return $res;
0360: }

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

参考サイト

(この項おわり)
header