画像生成

前へ << cookie GD::Graph によるグラフ生成 >> 次へ

静的な画像表示

まず、CGI で静的な画像ファイルを出力する方法を説明しましょう (画像は 東京発フリー写真素材集 から拝借しました)。

image-1.cgi (実行結果)

    1: #!/usr/local/bin/perl
    2: 
    3: # $Id: image-1.cgi,v 1.1 2004/06/25 17:30:47 zxr400 Exp $
    4: 
    5: my $imgfile = '../../img/tokyo-tower.jpg';
    6: my $imgsize = (stat($imgfile))[7];
    7: 
    8: print "Content-type: image/jpeg\n";
    9: print "Content-Length: $imgsize\n";
   10: print "\n";
   11: 
   12: binmode(STDOUT);
   13: 
   14: open(IN, $imgfile);
   15: binmode(IN);
   16: while (<IN>){
   17:     print $_;
   18: }
   19: close(IN);
   20: 
   21: exit 0;
やっていることは簡単で、
Content-type: image/jpeg
Content-Length: 画像ファイルサイズ
というヘッダを出力し、改行コードをはさんで、 ボディに画像ファイルの内容を出力しているだけです。

Content-Length ヘッダは必須ではありません。 あらかじめ画像ファイルのサイズがわかっているんだから、 せっかくなのでクライアント側にも伝えてあげよう、という意図があります。 これにより、ブラウザは「34% 読み込み中」などとダウンロード状況を 利用者に伝えることができます。その結果、閲覧者のストレスを軽減できるかもしれません。

   12: binmode(STDOUT);
   15: binmode(IN);
は、サーバが Windows などの環境の場合に改行コードの変換を抑止するためのものです。 Windows は、入力時にファイル中の改行コード \r\n (0x0D 0x0A) を \n (0x0A) に変換します。同様に、出力時に改行コード \n (0x0A) を \r\n (0x0D 0x0A) に変換します。

テキストファイルを扱うのであればこれでもよいのですが、 画像ファイル中にたまたま 0x0A というコードがあると、 $_ に格納する際 0x0D 0x0A に変換されてしまいます。 これを抑止するための命令が binmode です。

なお、UNIX 環境では改行コードの変換は行われませんので、 binmode は不要です。ただし binmode を指定しても害はありません。

実はこの例では、Windows 環境であっても binmode は不要です。なぜなら、 <IN> からの入力時に 0x0A が 0x0D 0x0A に変換されてしまいますが、 標準出力への出力時に 0x0D 0x0A は 0x0A に戻ってしまうからです。 ただし、扱うファイルがバイナリファイルである場合は、明示的に binmode を指定することで、 「このファイルハンドルはバイナリファイルを扱うんだよ」 とソースを読む人に意思表示をする効果があると考えます。

なお、改行コードを変換するのは Windows の標準入出力ライブラリであることに注意してください。 つまり C 言語の fopen・fread・fgets・fprintf・printf・fputc・putc などのライブラリ関数を使う場合に、 改行コードが変換されます。一方、Win32API である CreateFile・ReadFile・WriteFile を使うと、 改行コードの変換は行われません。Perl で言うと、sysopen・sysread・syswrite を使うならば 改行コードの変換は行われない、ということです。

今回の例では、対象画像が JPEG ファイルだったので Content-Type ヘッダは image/jpeg としました。その他の画像フォーマットであれば などと適切な MIME タイプを指定しましょう。

動的な画像表示 - gd とは -

いつも同じ画像ではつまらないので、CGI プログラムで動的な画像生成にチャレンジしてみましょう。 極論を言えば、PNG でも GIF でも JPEG でも画像フォーマットは公開されているため、 適切なバイナリデータを生成して、それを標準出力に出力してやればいいだけです。

しかし PNG 画像を生成するために 311KB もの 規格 を読む気にはなれないでしょうし、 JPEG 画像を生成するために高速フーリエ変換の勉強をするのは嫌でしょう。 当ページ管理人も嫌です。 そんな怠惰な人のために、gd という画像ライブラリがフリーで配布されています。今回はこのライブラリを利用して画像を生成してみましょう。

gd は、画像生成ライブラリです。つまり make && make install すると /usr/local/lib/libgd.so.4 などのライブラリがインストールされます。 C 言語のプログラムを書いて gd が提供するライブラリ関数を使えば、 簡単に画像が生成できる、ということです。 Perl から gd ライブラリを利用するにはどうすればよいかというと、 CPAN で GD モジュールが配布されていますので、 これを使いましょう。

当ページ管理者は FreeBSD を使っているので、port/package から ja-gd-2.0.15_1,1 と p5-GD-2.07 をインストールしました。 つまり

# pkg_add ja-gd-2.0.15_1,1.tgz
# pkg_add p5-GD-2.07.tgz
これだけです。

ソースからコンパイルせざるをえない人のために、基礎知識を書いておきます。

もし管理者権限がなく、ホームディレクトリの下にインストールしたい場合は 当ページ管理者のメモ を参考にしてください。 これは Solaris 2.6 でホームディレクトリの下に をインストールしたときのものです。zlib は既にインストール済だったので特に触れていません。 JPEG 画像を扱う必要がなかったので、jpeg6b はインストールしていません。

動的な画像表示 - サンプルプログラム -

以下の画像は、CGI プログラムによって動的に生成しています。 動的に生成している証拠に、リロードするたびに異なる文字列が表示されると思います。

ソースは以下の通り。

image-2.cgi (実行結果)

    1: #!/usr/local/bin/perl
    2: 
    3: # $Id: image-2.cgi,v 1.4 2004/07/21 17:42:09 68user Exp $
    4: 
    5: use strict;
    6: use GD;
    7: 
    8: my $PI = 3.141592;
    9: 
   10: my $width  = 500;            # 画像の幅
   11: my $height = 200;            # 画像の高さ
   12: 
   13: my $im = new GD::Image($width, $height);
   14: 
   15: my $black             = $im->colorAllocate(  0,   0,   0);
   16: my $str_color         = $im->colorAllocate(  0,   0,   0);
   17: my $str_shadow_color  = $im->colorAllocate(190, 190, 190);
   18: my $bgcolor           = $im->colorAllocate(255, 255, 255);
   19: my $circle_color      = $im->colorAllocate(230, 110, 110);
   20: 
   21: # インタレースを ON
   22: $im->interlaced('true');
   23: 
   24: # 背景色として塗りつぶし四角を描く
   25: $im->filledRectangle(0, 0, $width-1, $height-1, $bgcolor);
   26: 
   27: # 外枠を描画
   28: $im->rectangle(0, 0, $width-1, $height-1, $black);
   29: 
   30: # 内側の丸を書いて、外側の丸を書いて、内側と外側の丸の間を塗りつぶす
   31: $im->arc($width/2, $height/2, 160, 160, 0, 360, $circle_color);     
   32: $im->arc($width/2, $height/2, 220, 220, 0, 360, $circle_color);
   33: $im->fill($width/2+81, $height/2, $circle_color);
   34: 
   35: my $fontsize_min = 10;   # 初期フォントサイズ
   36: my $fontsize_max = 50;   # 最大フォントサイズ
   37: my $fontsize_delta = 7;  # フォントサイズ増分
   38: 
   39: my $str_x = 0;             # X 座標
   40: my $str_y = $fontsize_min; # Y 座標
   41: my $angle = 0;             # 回転角度 (ラジアン)
   42: 
   43: my $font_file ='/usr/X11R6/lib/X11/fonts/TrueType/sazanami-gothic.ttf';
   44: my @strs =
   45:     ('寿限無寿限無五劫のすりきれ海砂利水魚の水行末雲来末風来末食う寝るところに住むところやぶら小路のぶら小路',
   46:      '祇園精舎の鐘の声 諸行無常の響きあり 沙羅双樹の花の色 盛者必衰の理をあらわす おごれる人も久しからず',
   47:      '春はあけぼの。やうやう白くなりゆく山ぎは、少し明りて、紫だちたる雲の、細くたなびきたる。夏は、夜。',
   48:      '色は匂へど 散りぬるを 我がよ誰そ 常ならむ 有為の奥山 今日越えて あさき夢見し ゑひもせず',
   49:      );
   50: my $str = $strs[int(rand(scalar(@strs)))];
   51: 
   52: for ( my $size=$fontsize_min ; $size<=$fontsize_max ; $size += $fontsize_delta ){
   53: 
   54:     # 文字の影を描画
   55:     $im->stringFT($str_shadow_color,  # 色
   56:                   $font_file, $size,  # フォント・フォントサイズ
   57:                   $angle*($PI/180),   # 回転角度
   58:                   $str_x+2, $str_y+2, # X・Y 座標
   59:                   $str);              # 表示文字列
   60: 
   61:     # 文字を描画
   62:     $im->stringFT($str_color,         # 色
   63:                   $font_file, $size,  # フォント・フォントサイズ
   64:                   $angle*($PI/180),   # 回転角度
   65:                   $str_x, $str_y,     # X・Y 座標
   66:                   $str);              # 表示文字列
   67: 
   68:     $str_y += $size+14;
   69:     $angle -= 2;
   70: }
   71: 
   72: binmode STDOUT;
   73: print "Content-type: image/png\n\n";
   74: print $im->png;
   75: 
   76: exit 0;
解説するまでもないんですが、いちおう軽く触れておきましょう。
    6: use GD;
まずは GD モジュールをインポートします。
   10: my $width  = 500;            # 画像の幅
   11: my $height = 200;            # 画像の高さ
   13: my $im = new GD::Image($width, $height);
画像の幅と高さを決めて、new GD::Image で GD のオブジェクトを生成します。 このオブジェクトは仮想的なキャンパスのようなもので、 このオブジェクトの上に線や文字を書いていくわけです。
   15: my $black             = $im->colorAllocate(  0,   0,   0);
   16: my $str_color         = $im->colorAllocate(  0,   0,   0);
   17: my $str_shadow_color  = $im->colorAllocate(190, 190, 190);
   18: my $bgcolor           = $im->colorAllocate(255, 255, 255);
   19: my $circle_color      = $im->colorAllocate(230, 110, 110);
色を確保します。色は RGB で 0〜255 の範囲で指定します。
   22: $im->interlaced('true');
interlaced('true') でインタレースモードにできます。 このモードにすることで「最初は解像度が低く、時間が経つごとに解像度があがっていく」 というおなじみの機能を使うことができます。
PNG・GIF の場合は「インタレース PNG・インタレース GIF」と呼び、 JPEG の場合は「プログレッシブ JPEG」と呼ぶようです。

   25: $im->filledRectangle(0, 0, $width-1, $height-1, $bgcolor);
filledRectangle は塗りつぶした四角を描きます。つまり、 画像の全領域を塗りつぶすことで、背景色を設定する効果があるわけです。

GD の座標系は左上が (0,0)、右下が (x-1, y-1) となります。


   28: $im->rectangle(0, 0, $width-1, $height-1, $black);
rectangle は四角を描きます。filledRectangle と違い、 四角の内側は塗りつぶしません。
   31: $im->arc($width/2, $height/2, 160, 160, 0, 360, $circle_color);     
   32: $im->arc($width/2, $height/2, 220, 220, 0, 360, $circle_color);
   33: $im->fill($width/2+81, $height/2, $circle_color);
arc は円弧を描くメソッド、 fill は指定した点を塗りつぶすメソッドです。 小さな赤い丸と大きな赤い丸を書いて、その中間を塗りつぶすことで、 太い丸を書いているわけです。
ここから文字を描画するわけですが、まず使用するフォントと表示する文字を決めます。 まずフォントですが、
   43: my $font_file ='/usr/X11R6/lib/X11/fonts/TrueType/sazanami-gothic.ttf';
と指定しています。これは「さざなみフォント」と呼ばれるもので、 2004年6月にリリースされたものですから、 みなさんのお手元のマシンにはインストールされていないかもしれません。 その場合は、さざなみフォント配布サイトから を取得して適当なディレクトリに置けばそれで OK です。

Windows や Solaris であればそれなりのフォントが標準で用意されています。 フリーの UNIX 系 OS でもいくつか TrueType フォントがあります。 拡張子が ttf で、ファイルサイズが数 MB あるファイルがあれば、 おそらくそれは日本語の TrueType フォントでしょう。


   44: my @strs =
   45:     ('寿限無寿限無五劫のすりきれ海砂利水魚の水行末雲来末風来末食う寝るところに住むところやぶら小路のぶら小路',
   46:      '祇園精舎の鐘の声 諸行無常の響きあり 沙羅双樹の花の色 盛者必衰の理をあらわす おごれる人も久しからず',
   47:      '春はあけぼの。やうやう白くなりゆく山ぎは、少し明りて、紫だちたる雲の、細くたなびきたる。夏は、夜。',
   48:      '色は匂へど 散りぬるを 我がよ誰そ 常ならむ 有為の奥山 今日越えて あさき夢見し ゑひもせず',
   49:      );
   50: my $str = $strs[int(rand(scalar(@strs)))];
表示する文字列はいくつかのパターンを用意しておき、ランダムに決定しています。
   62:     $im->stringFT($str_color,         # 色
   63:                   $font_file, $size,  # フォント・フォントサイズ
   64:                   $angle*($PI/180),   # 回転角度
   65:                   $str_x, $str_y,     # X・Y 座標
   66:                   $str);              # 表示文字列
stringFT メソッドを使って文字列を表示します。
以前は stringTTF メソッドでしたが、今は名前が変わって stringFT になりました。 ただし stringTTF も従来どおり使えます。

   72: binmode STDOUT;
   73: print "Content-type: image/png\n\n";
   74: print $im->png;
最後に png メソッドを使って画像を表示しています。

最初のサンプルプログラムでは「この例では Windows であっても実は binmode は不要」と書きましたが、 今回は Windows ならば必ず binmode が必要です。binmode を使用しなかった場合、 GD が生成する画像データ内にたまたま 0x0A があったとして、 それを標準出力 (STDOUT) に出力する際に 0x0A が 0x0D 0x0A に変換されてしまい、 本来の画像データが化けてしまうことになるからです。

サンプルプログラムでは文字を傾けたり、フォントサイズを少しずつ大きくしたりしていますが、 そんな細かいところはどうでもいいので、GD モジュールを使うにはどうしたらよいか、 という雰囲気をつかんでください。

画像生成ツールいろいろ(1) - fly -

fly (日本語解説ページ) というプログラムは画像ファイルを生成することができます。 コンパイルするには gd ライブラリが必要です。 つまりコマンドラインから使用できる gd ライブラリの wrapper のような位置づけです。 awk や sh などの貧弱な言語から画像を生成するのに向いていると思います。 GD モジュールが使える環境であれば fly を使う必要はないでしょう。

画像生成ツールいろいろ(2) - GD::Graph -

グラフ描画が目的であれば、Perl で利用できる GD::Graph モジュールという選択肢もあります。 次節 GD::Graph によるグラフ生成 にて解説しています。

画像生成ツールいろいろ(3) - GIMP -

GIMP というイメージエディタがあります。Adobe Photoshop に匹敵するような機能を持つとか持たないとか (よく知りません)。その GIMP のウリとしてスクリプト機能が充実している、というものがあります。 これはあらゆる操作を定義化しておき、マウスをいちいち操作せずに自動化してしまおうという思想です。

GIMP に標準添付されている「Script-Fu」というスクリプト言語を使えば、 コマンドラインから美しいロゴを自動的に作るような CGI プログラムが比較的簡単に作成できます。 ただし Script-Fu は Scheme で記述する必要がありますので、括弧嫌いの人にはつらいかもしれません。 Perl であれば Gimp::Fu モジュールをインストールすることで括弧とたわむれることなく GIMP の機能を活用できます。

Perl から Gimp::Fu モジュールを利用することを「Perl-Fu」とか「Gimp-Fu」とか呼ぶようです。 どっちが正しいんですかね。

前へ << cookie GD::Graph によるグラフ生成 >> 次へ

$Id: image-1.html,v 1.5 2011/08/05 05:17:38 68user Exp $