PerlでフラクタルをSVGで出力する

これを作る時に、コッホ曲線を描くスクリプトをいじってSVGで出力したので、
その手順を紹介します。

use v5.14;
use strict;
use warnings;

#use Imager;

use constant N => 5;
use constant WIDTH => 1200;
use constant HEIGHT => 1200;
use constant TRI_W => 800;

# レイアウトはSVGを読み込んでから行えば良い
my $margin = (WIDTH - TRI_W) / 2;
my ( $x0, $y0 ) = ( $margin, HEIGHT - $margin - $margin );

# コッホ曲線
my @gen = (
    # [ 0.0, 0.0 ] は不要
    [ 1/3, 0 ],
    [ 0.5, -sqrt(3.0)/6 ],
    [ 2/3, 0 ],
    [ 1.0, 0 ]
);
 
my $points = [
    [ 0, 0 ],
    [ TRI_W, 0 ],
    [ (TRI_W / 2), ((TRI_W / 2) * sqrt(3.0))],
    [ 0, 0 ]
];

#my $img = Imager->new(
#    xsize => WIDTH, ysize => HEIGHT );
#$img->box( filled => 1, color => 'black' );

foreach my $n ( 0..N ) {
    #$img->box( filled => 1, color => 'black' );

    if ( 0 < $n ) {
        $points = generate( $points );
    }

    my @tmp = map {
        [ ($x0 + $_->[0]), ($y0 - $_->[1]) ];
    } @{$points};
    #$img->polyline( points => \@tmp, color => 'white' );

    my $dst_file = ($0 =~ s/\.pl//r) . "_${n}.png";
    #$img->write( file => $dst_file ) or die $img->errstr;

    my $points_data = join(' ', map { sprintf('%f,%f', $_->[0], $_->[1]); } @tmp);
    my ( $width, $height ) = ( WIDTH, HEIGHT );
    my $text = <<EOF;
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.5, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1 Tiny//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-tiny.dtd">
<svg version="1.1" baseProfile="tiny" id="レイヤー_1"
	 xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="${width}px" height="${height}px"
	 viewBox="0 0 ${width} ${height}" xml:space="preserve">
<polygon fill="#FFFFFF" stroke="#000000" stroke-miterlimit="10" points="$points_data "/>
</svg>
EOF

    $dst_file = ($0 =~ s/\.pl//r) . "_${n}.svg";
    open( my $fh, '>', $dst_file ) or die "cannot opne \"$dst_file\"";
    print $fh $text;
    close( $fh );
}

sub generate {
    my $points = shift;
    my $cnt = scalar( @{$points} );

    my @result = ( $points->[0] );
    for (my $i=1; $i<$cnt; $i++) {
        my ( $st, $en ) = ( $points->[$i-1], $points->[$i] );

        my $dx = $en->[0] - $st->[0];
        my $dy = $en->[1] - $st->[1];

        push @result, map {
            my ( $x, $y ) = ( $_->[0], $_->[1] );
            # x2 = $x * cos(a) - $y * sin(a) + st.x;
            # y2 = $x * sin(a) - $y * cos(a) + st.y;
            # sin(a) = dy / 1.0, cos(a) = dx / 1.0
            [
                ($x * $dx) - ($y * $dy) + $st->[0],
                ($x * $dy) + ($y * $dx) + $st->[1]
            ];
        } @gen;
    }

    return \@result;
}

Imagerを使って画像を出力するコードは残しておきました。
画像も出力したい方は、コメントアウトしてるところを解除して頂ければと思います。

作業手順は、以下の通りです。

1. テンプレートの用意

SVGの扱えるドローソフト(イラストレーターとか)で、
適当なポリゴン、またはポリラインを追加してSVGで保存する。
この作業の注意点としては、うっかりベジェ曲線を描かないこと。
あと、最低でも3点以上追加するのと、長方形にならないように気をつけること。(*1)

2. SVGをヒアドキュメントとしてPerlスクリプトに追加

ヒアドキュメントとして追加した文字列のうち、
幅と高さに関する箇所と、頂点データを差し替える。
頂点データは、xとyをカンマでつないで、スペース区切りの文字列を生成するだけ。

 
こうして完成したのが、上記のスクリプトです。
必要に応じて、polygonpolylineを切り替える必要があって、
塗りつぶす必要があるならpolygon、そうでないならpolylineを使います。
使うというのは、ヒアドキュメントの一部分を書き換えるという意味です。

あとは適当なパラメータで、SVGを出力するだけですね!

おしまい。

(*1) Rect(長方形), Line(線分)として扱われる

Leave a Comment