Image::Magick の使い方


ブラウザからアップロードされた画像を、Perl モジュール Image::Magick を使って CGI プログラム内で加工する方法の紹介です。

画像がリサイズできる CGI を探しているという方はこちらをどうぞ
「すぐ使えるCGI 添付ファイル付 ウェブページ更新ツール 大容量版」

Image::Magic とは

Image::Magick は画像操作ライブラリ ImageMagick を Perl から使うためのモジュールです。

なお、ImageMagick のコマンドラインツールの方が安定していて機能も豊富で、Webで見つかるリソースも多い事から、Perl プログラムからモジュール Image::Magic 経由で操作するより、一旦ファイルを保存した後 system コマンドでコマンドラインツールを使って変換したな方が簡単で安定しており高速です。

system(“convert (オプション) source.jpg final.png”);

しかしながら、私は Perler でありますので、多少ムキになって Image::Magick での加工をしてみました。

このページで紹介する Image::Magic の使い方

このページでは、CGI で受け取ったアップロードされたファイルを、以下のように変換して保存する方法を紹介します。

  1. リサイズ(縮小と拡大)
  2. グレースケールに変換
  3. セピア調に変換
  4. ミュート画像に変換(白っぽくする)
  5. サムネールを正方形に切り抜き
  6. 画像に文字を書く(著作権表示など)
  7. 透かしを付ける(コピーガード等の目的)

変換サンプル画像

それぞれ、以下のような画像を作成します。

オリジナル

Image::Magickの使い方サンプルコード元画像

元画像 200x150

縮小

Image::Magickで縮小した画像

縮小した画像 160x120

拡大

Image::Magickで拡大した画像

拡大した画像 300x255

グレースケール

Image::Magickで変換したグレースケール画像

グレースケール画像

セピア調

Image::Magickでセピア調に変換した画像

セピア調に変換した画像

ミュート画像

Image::Magickで変換したミュート画像

ミュート画像

正方形のサムネール

Image::Magickで正方形に切り取ったサムネール

サムネール 50x50

画像に文字を書く

Image::Magickでコピーライト表示を書き込んだ画像

コピーライト表示を書き込んだ画像

透かしを付ける

Image::Magickで透かしを追加した画像

透かしを追加した画像

画像アップロードフォーム

画像をアップロードする側のフォームは、ENCTYPE を「multipart/form-data」にするのがポイントです。
文字コードは CGI プログラムを書く文字コードと揃えます。

<!DOCTYPE HTML PUBLIC “-//W3C//DTD HTML 4.01 Transitional//EN”>
<html>
<head>
<meta http-equiv=”Content-Type” content=”text/html; charset=utf-8″>
<title>ファイルアップロード</title>
</head>
<body>
<form method=”post” action=”image_resize.cgi” ENCTYPE=”multipart/form-data“>
画像ファイル選択: <input type=”file” name=”upload_file”>
<input type=”submit” value=”アップロード”>
</form>
</body>
</html>

画像変換 CGI

画像変換プログラムの方では、CGI モジュール と Image::Magick モジュールを使い加工を行います。

Image::Magic の使い方のポイント

  • 設定パラメータは仕上がりを見ながら試行錯誤して設定する必要があります。
  • Image::Magick のエラーは、CGI 内で起こると Internal Server Error を引き起こしてしまいますので、eval でトラップしながら処理を進めます。

その他、CGI でファイルを受け取るときの一般的な留意点は以下の通りです。

  • ファイルサイズをチェックする(任意)
  • ファイル拡張子で画像かどうかを事前チェック
  • 保存ディレクトリが重複しないように留意

順番に解説します。

基本の CGI プログラム

基本となる CGI プログラムは以下の通りです。
このプログラムは画像を受け取って保存しているだけですが、コード中の「ここで画像を加工」の部分で、後で説明する各種加工を行います。

#!/usr/bin/perl —

use strict;
use warnings;
use utf8;

#使用するモジュールをロード
use CGI;
use Digest::MD5 qw(md5_hex);
use Image::Magick;

この辺りは「おまじない」です。
Digest::MD5 は重複しないディレクトリ名を作成するために使います。

#####各種設定#####

#ファイルを保存するディレクトリを設定
#(CGIの実行ユーザで書き込み権限が必要)
my $dir = ‘./files’;

#受付可能な拡張子(正規表現)
my @ext_ok = qw (
gif
jpe?g
png
);

保存のための親ディレクトリを指定します。このディレクトリには書き込みパーミッションを指定しておきます。

また、画像だけにアップロードできるファイルを制限しますので、許可する拡張子を登録しておきます。

厳密に言えば拡張子だけのチェックでは画像ファイルの形式をチェックした事にはなりませんが、ひとまず人間にエラーメッセージを返すためであれば十分です。形式の方に拡張子とは異なるエラーなどがある場合は、Image::Magick の方がエラーを吐いてくれるはずなので、そちらをトラップします。

#####処理開始#####

#転送できるファイルの最大サイズを設定(任意)
#(実際は、post送信されるコンテンツ合計の最大サイズ)
#この値は、CGIオブジェクトを作成する時には既に
#設定されていなければならない。
#$CGI::POST_MAX = 1024 * 1000; #max = 1MB
#▲必要なら値を設定してコメントアウト

画像のサイズを制限する時は、送信できる容量の最大値を設定します。

上ではコメントアウトしてありますのでこの設定は無効ですが、あまり大きな画像をアップロードされてもサーバで処理できなくなりますので、CGIを一般公開するような場合はある程度の制限を設ける方が安全です。

#CGIオブジェクトを作成
my $form = new CGI;

#クライアントにヘッダを送信
#これは、結果メッセージ表示のため

binmode STDOUT, ‘:encoding(utf8)’;
print “Content-Type: text/html;charset=utf-8\r\n”;
print “\r\n”;

#ファイルを読み取り
my $filename = $form->param(‘upload_file’);

#ファイルの転送のチェック
my $error;
if (!defined($filename) and $error = $form->cgi_error){
print “ファイルが転送できませんでした:$error\n”;
exit;
}

unless ($filename) { #ファイルが転送されていれば、値は真
print “ファイルはアップロードされていません。\n”;
exit;
}

この辺りは、モジュール CGI.pm でフォームから送信された値を扱う際の基本的操作です。

#ファイルが転送されていた時は処理を続ける
my ($parsename, $ext);

#$filename から ファイル名(パス)を文字列としてコピー。
$parsename = ”.$filename;

#末尾のドット+英文字を拡張子として保存
$ext = ($parsename =~ /(\.\w+)$/)[0];

引き続き、アップロードされたファイルの処理を行います。
CGI モジュールが 「$form->param(‘upload_file’)」 で返してくる変数(ここでは $filename )は不思議な値で、空文字を結合すればファイル名文字列になりますし(上の $parsename )、後で見るようにファイルハンドルにもなります。

#拡張子のチェック
unless (grep {$ext =~ /^\.$_$/i} @ext_ok){ #どれかに合致
printf(‘許可されていない拡張子(%s)です。’,
$ext,
);
exit;
}

先ほど用意した拡張子のチェックリスト(配列)を利用してアプロードされたファイルの拡張子がこれに合致するかをチェックします。

「grep {$ext =~ /^\.$_$/i} @ext_ok」 で全てのチェックを行い、結果として合致したものがあったかを「unless (…)」で判別していますので若干効率が悪いですが(=合致したらそこでチェック終了、としていない)、チェックする拡張子のリストはは3つ4つでので問題ないでしょう。

#ディレクトリ名の決定
while (-d $dir or -f _) {
$dir = $dir.’/upload_’.md5_hex(time ^ $$ ^ unpack “%32L*”, `ps wwaxl | gzip`);
#md5_hex の引数はWindows の場合は time ^ $$ などに変更
}

#ファイルを格納するディレクトリを作成
unless (mkdir($dir, oct(777))){
print “保存ファイル用ディレクトリの作成に失敗しました。: $!\n”;
exit;
}

ディレクトリを準備します。ディレクトリ名は、繰り返し処理した時に同じファイルで上書きしてよければ固定でもOKですが、ここでは毎回結果を保存する為にランダムな名前でディレクトリを作成しています。
「日付+連番」等でも分かりやすくていいかもしれません。

#####画像加工開始#####
# Image::Magic での処理はエラーが起きると die する。
# そのままだと Internal Server Error を引き起こしてしまうので、
# eval でトラップ。

#ファイルの読み込み
my ($img, $ret);

#各種処理後のファイル名を保存しておく変数
my %saved;

eval{$img = Image::Magick->new};
unless ($img){
print “Image::Magick の初期化でエラーが起きました。: $ret $@\n”;
exit;
}

ご本尊の Image::Magick での処理に取りかかります。
まずは、CGI の値とは無関係で Image::Magick オブジェクトを作成します。無事オブジェクトが作成されると $img には作成されたオブジェクトが代入されますので、これが定義されているかをチェックするのがエラーチェックとなります。

エラーメッセージの中で書き出している詳細情報「$ret $@」は、$ret の方はここではまだ使っていません(後述)。$@ は「最後の eval の中で発生したエラーに関するエラーメッセージ」です。両方を出力すると、どちらかから何かの情報が得られるでしょう。

eval{$img->Read(file=>\$filename)};
if ($@){
print “ファイルの読込みに失敗しました。: $ret $@\n”;
exit;
}

#ここで画像を加工

上記のように、アップロードされたファイルをファイルシステムに保存せず直接 Image::Magick で加工する時は、CGI モジュールが渡してくれたファイルハンドル($filename)から直接画像を読み込むことができます。

「ここで画像を加工」の部分では、必要な加工を行います(詳細後述)。

#オリジナルを保存
$saved{org} = “$dir/orgiginal$ext”;
eval{$ret = $img->Write($saved{org})};
if ($ret||$@){
print “オリジナルファイルの保存に失敗しました。: $ret $@\n”;
exit;
}

とりあえず、ここでは何もせず Write メソッドで保存しておきます。

一旦画像を読み込んだ後は、Image::Magick のメッソッドはエラーが起こるとエラーの詳細情報を返します。このため、Image::Magick の処理を行った際は、メソッドが返す値を $ret に代入し、エラーの判別を「if ($ret||$@)」で行います。

#結果確認
print ‘<p>ファイルを保存しました。</p><ul>’
for (keys %saved){
printf (‘<li><a href=”%s” target=”_blank”>%s</a></li>’,
$saved{$_},
$_,
);
}
print ‘</ul>’;

保存したファイルへのリンクを、結果としてブラウザ画面に表示します。

厳密に言えばこれでは <html><body> 等のタグが抜けていて全くダメなHTML出力ですが、ここではログとして出力しますので、大抵のブラウザは期待通りタグを解釈してくれる事をいい事に、これでよしとしています。

シリーズ目次