Perlでジュリア集合を描く(前編)
マンデルブロ集合の前にジュリア集合。
これは結構面倒で、
結果を見るためにいちいち計算していると時間が掛かってしまう。
そこで、色を付ける直前のデータをファイルに書き出しておいて、
色を付けるのは、そのファイルから読み込んで行えば、
少しは試行錯誤に掛かる負担が軽減される。
まずは、色を付ける直前のデータのファイル出力から。
use v5.14; use strict; use warnings; use Imager; use Time::HiRes qw/time/; use constant KL => 5000; use constant KS => 2000; use constant RS => -0.3; use constant RE => 0.3; use constant IS => -0.3; use constant IE => 0.3; my $width = KS; my $height = KS; my $dst_file = ($0 =~ s/\.pl//r) . sprintf("_%d_%d.dat", $width, $height); my ( $a_r, $a_i ) = ( -0.64, -0.405 ); my $dr = ( RE - RS ) / $width; my $di = ( IE - IS ) / $height; local $| = 1; my $start = time(); printf( "%4d/%4d", 1, $height ); my @pixels = (); for (my $iy=0; $iy<$height; $iy++) { my @buf = (); for (my $ix=0; $ix<$width; $ix++) { my $z_r = ($ix * $dr) + RS; my $z_i = ($iy * $di) + IS; my $i = -1; foreach ( 0..KL ) { my $z2_r = ($z_r * $z_r) - ($z_i * $z_i) + $a_r; my $z2_i = (2.0 * $z_r * $z_i) + $a_i; if ( 4 < (($z2_r * $z2_r) + ($z2_i * $z2_i)) ) { $i = $_; last; } ( $z_r, $z_i ) = ( $z2_r, $z2_i ); } push @buf, $i; } push @pixels, \@buf; printf( "\r%4d/%4d", $iy + 1, $height ); } printf( "\rcomplete! %.2fsec\n", (time() - $start) ); open( my $fh, '>', $dst_file ) or die "cannot open $dst_file : $!"; binmode( $fh ); foreach ( @pixels ) { print $fh pack('s*', @{$_}); } close( $fh );
これを読み込んで色を付ける。
use v5.14; use strict; use warnings; use Imager; use Time::HiRes qw/time/; if ( not @ARGV ) { say "Usage: perl $0 [dat file]"; exit 0; } my $src_file = $ARGV[0]; my ( $width, $height ) = $src_file =~ /_(\d+)_(\d+)\.dat/; say "w: " . $width; say "h: " . $height; my @pixels = (); open( my $fh, '<', $src_file ) or die "cannot open $src_file : $!"; binmode( $fh ); for ( 1..$height ) { my $buf; my $result = read( $fh, $buf, ($width * 2) ); if ( $result != ($width * 2) ) { close( $fh ); die "read faild!"; } my @tmp = unpack( 's*', $buf ); push @pixels, \@tmp; } close( $fh ); my $img = Imager->new( xsize => $width, ysize => $height ); $img->box( filled => 1, color => 'black' ); my $center = 60; local $| = 1; my $start = time(); printf( "%4d/%4d", 1, $height ); for (my $iy=0; $iy<$height; $iy++) { my @rgba = map { my $diff = abs( $_ - $center ); my $tmp = $diff / 32; my $v = int( 255 * (1.0 - ((1.0 < $tmp) ? 1.0 : ($tmp ** 2.0))) ); ( $v, $v, $v, 255 ); } @{$pixels[$iy]}; $img->setscanline( y => $iy, pixels => pack('C*', @rgba) ); printf( "\r%4d/%4d", $iy + 1, $height ); } printf( "\rcomplete! %.2fsec\n", (time() - $start) ); $img->write( file => 'render.png' ) or die $img->errstr;
ほんとは、これを実行する前に、
色を付ける直前のデータのヒストグラムを見ている。
# ファイル読み込みを終えたところまで一緒 my %hist = (); foreach my $buf ( @pixels ) { foreach ( @{$buf} ) { $hist{$_}++; } } printf("%4d: %5d\n", $_, $hist{$_}) for sort { $a <=> $b } keys %hist;
ヒストグラムがあると目星は付けられるけど、
その程度にしか役に立たない。
あと、Imager::Color
を使うよりは、
R, G, B, Aの順でバイト列を作って、setscanline
を呼んだ方が断然早いので、
今回はそのように実装した。
でもって、結果はこんな感じ。
おしまい。
Leave a Comment