送受信_環境変数 ENV - 演習編

Problem3: 画像の画質を URL からコントロールしてみよう

ImageMagick を使うと、コマンドラインから画像を処理できます。これを、CGI でできるようにしてみましょう。

例えば、-wave (振幅)x(波長) オプションで画像を波打たせることができます。

ImageMagick を使った画像処理の例
convert -wave 10x20 before.jpg after.jpg
before.jpg
処理前
(手前が筆者)
after.jpg
処理後
  1. 仮に、この CGI のファイル名を、img.cgi とすると、 波の振幅と波長をクエリーとして、 すなわち、img.cgi?10x20 の様にして入力できるようにしてみてください
  2. 明るさ(100で変化なし)、彩度(100で変化なし)、シャープネス(半径xシグマ値)を、img.cgi?b=140;c=115;s=1x1 の様にして入力できるようにしてみてください convert -gamma 1.4/1.4/1.4 -modulate 100/115/100 -sharpen 1x1 before.jpg after.jpg

⇒ ヒント

Hint

  1. クエリーなどの扱いは、前問「Problem2: カウンタ画像の桁数を URL からコントロールしてみよう」と同じです
  2. convert コマンドの詳細 ⇒ ImageMagick: Command-line Tools: Convert (Option Summary の option 名をクリック)
  3. convert コマンドの(一部)日本語解説 ⇒ ImageMagick © Takashi CHIBA

⇒ 答え

Solution

下記のCGIと同じディレクトリ内に jpeg 画像ファイル before.jpg を置きます

#!/usr/local/bin/ruby
infile  = "before.jpg"                         # 加工前画像ファイル
outfile = "after.jpg"                          # 加工後画像ファイル
q       = ENV['QUERY_STRING']                  # アドレスの?以降を取得

system("convert", "-wave", q, infile, outfile) # 画像を加工(波打ち)
Process.waitall                                # 子プロセス「system()」の処理が終わるのを待つ(ruby1.7以降)
                                               # ↓画像を出力
print <<KEYWORD
Content-Disposition: filename=#{ outfile }
Content-type:   image/jpeg
Content-Length: #{ File.size( outfile ) }

KEYWORD
f = File.open( outfile, "rb")
STDOUT.binmode
while line = f.gets do
  print line
end
f.close

しかし、この方法には問題点があります。

  1. クエリー(=アドレスの「?」以降の文字列)の入力を間違えると、加工後の画像が存在しないので、エラーが発生する
  2. クエリーの入れ方によっては、画像処理以外にも、いろいろできてしまって、閲覧者がサーバーにイタズラし放題

そこで、クエリーをチェックする仕組みを入れると、下記のようになります。画像処理が失敗した場合にも備えてみました。

#!/usr/local/bin/ruby
infile  = "before.jpg"
outfile = "after.jpg"
q       = ENV['QUERY_STRING']

if /\A\d+x\d+\Z/ =~ q.to_s then                # (数字)x(数字) の形になっているかどうかを確認
  r = system("convert", "-wave", q ,infile, outfile) rescue false
  outfile = infile unless r                    # 画像処理に失敗した場合、入力画像を表示
  Process.waitall
else
  outfile = infile                             # クエリーのパターンが違っていたら、入力画像を表示
end

print <<KEYWORD
Content-Disposition: filename="#{ outfile }"
Content-type:   image/jpeg
Content-Length: #{ File.size( outfile ) }

KEYWORD
f = File.open( outfile ,"rb")
STDOUT.binmode
while line = f.gets do
  print line
end
f.close

ひょっとして、同時に複数のアクセスがあると、処理後のファイルがぶつかってしまうかもしれません。処理毎に出力するファイル名を別々にすると回避できるでしょう。

#!/usr/local/bin/ruby
infile  = "before.jpg"
outfile = "/tmp/%08d.jpg" % rand( 10**8 ) # 共用一時保存ディレクトリに乱数ファイル名
q       = ENV['QUERY_STRING']

if /\A\d+x\d+\Z/ =~ q.to_s then
  r = system("convert", "-wave", q ,infile, outfile) rescue false
  outfile = infile unless r
  Process.waitall
else
  outfile = infile
end

print <<KEYWORD
Content-Disposition: filename="wave.jpg"
Content-type:   image/jpeg
Content-Length: #{ File.size( outfile ) }

KEYWORD
f = File.open( outfile ,"rb")
STDOUT.binmode
while line = f.gets do
  print line
end
f.close                                    # 直後にファイルを削除するので、必ず閉じる
File.delete( outfile ) if outfile != infile && FileTest.file?(outfile)

もっと画像の加工

明るさ、彩度、シャープネスの調整ができるようにしてみましょう

#!/usr/local/bin/ruby
infile  = "before.jpg"
outfile = "/tmp/%08d.jpg" % rand( 10**8 )
qs      = ENV['QUERY_STRING'].to_s

a  = Array.new
a += ["-gamma", ([$1.to_f/100]*3).join("/")] if /\bb=(\d+)\b/ =~ qs # b=140; ⇒ `-gamma 1.4/1.4/1.4`
a += ["-modulate",        "100/#{ $1 }/100"] if /\bc=(\d+)\b/ =~ qs # c=115; ⇒ `-modulate 100/115/100`
a += ["-sharpen", $1] if /\bs=(\d+(\.\d+)?(x\d+(\.\d+)?)?)\b/ =~ qs # s=2x1; ⇒ `-sharpen 2x1`

if a == [] then
  outfile = infile
else
  a  = ["convert"] + a
  a += [infile, outfile]
  r = system(*a) rescue false
  outfile = infile unless r
  Process.waitall
end

print <<KEYWORD
Content-Disposition: filename="photo.jpg"
Content-type:   image/jpeg
Content-Length: #{ File.size( outfile ) }

KEYWORD
f = File.open( outfile ,"rb")
STDOUT.binmode
while line = f.gets do
  print line
end
f.close
File.delete( outfile ) if outfile != infile && FileTest.file?(outfile)

さらにいろんな加工ができるようにすると、ImageMagick でのコマンドの書き方に準拠して、

#!/usr/local/bin/ruby
infile  = "before.jpg"
outfile = "/tmp/%08d.jpg" % rand( 10**8 )
qs      = ENV['QUERY_STRING'].to_s

a  = Array.new
a += ["-crop",        $1] if /\bcrop=(\d+%|\d+x\d+\+\d+\+\d+)\b/ =~ qs
a += ["-roll",        $1] if /\broll=((\+|\-)\d+(\+|\-)\d+)\b/   =~ qs
a += ["-rotate",      $1] if /\brotate=((\+|\-)\d+)\b/           =~ qs
a += ["-flip"           ] if /\bflip\b/                          =~ qs
a += ["-flop"           ] if /\bflop\b/                          =~ qs
a += ["-antialias"      ] if /\bantialias\b/                     =~ qs
a += ["-charcoal",   "1"] if /\bcharcoal\b/                      =~ qs
a += ["-colorize",    $1] if /\bcolorize=(\d+\/\d+\/\d+)\b/      =~ qs
a += ["-contrast"       ] if /\bcontrast\b/                      =~ qs
a += ["-edge",        $1] if /\bedge=(\d+)\b/                    =~ qs
a += ["-emboss",      $1] if /\bemboss=(\d+)\b/                  =~ qs
a += ["-equalize"       ] if /\bequalize\b/                      =~ qs
a += ["-implode",     $1] if /\bimplode=(\d+)\b/                 =~ qs
a += ["-monochrome"     ] if /\bmonochrome\b/                    =~ qs
a += ["-negate"         ] if /\bnegate\b/                        =~ qs
a += ["-median",      $1] if /\bmedian=(\d+)\b/                  =~ qs
a += ["-raise",       $1] if /\braise=(\d+x\d+)\b/               =~ qs
a += ["-segment",     $1] if /\bsegment=(\d+x\d+)\b/             =~ qs
a += ["-shade",       $1] if /\bshade=(\d+x\d+)\b/               =~ qs
a += ["-spread",      $1] if /\bspread=(\d+(\.\d+)?)\b/          =~ qs
a += ["-threshold",   $1] if /\bthreshold=(\d+)\b/               =~ qs
a += ["-wave",        $1] if /\bwave=(\d+x\d+)\b/                =~ qs
a += ["-swirl",       $1] if /\bswirl=(\-?\d+)\b/                =~ qs
a += ["-interlace",   $1] if /\binterlace=(none|line|plane|partition)\b/ =~ qs
a += ["-gamma",       $1] if /\bgamma=(\d+(\.\d+)?\/\d+(\.\d+)?\/\d+(\.\d+)?)\b/ =~ qs
a += ["-gamma", ([$1.to_f/100]*3).join("/")] if /\bb=(\d+)\b/                    =~ qs
a += ["-modulate",    $1] if /\bmodulate=(\d+\/\d+\/\d+)\b/                      =~ qs
a += ["-modulate", "100/#{ $1 }/100"]      if /\bc=(\d+)\b/                      =~ qs
a += ["-geometry",    $1] if /\bgeometry=(\d+%|\d+x\d+((\+|\-)\d+x\d+)?)\b/      =~ qs
a += ["-sharpen",     $1] if /\bsharpen=(\d+(\.\d+)?(x\d+(\.\d+)?)?)\b/          =~ qs
a += ["-sharpen",     $1] if       /\bs=(\d+(\.\d+)?(x\d+(\.\d+)?)?)\b/          =~ qs
a += ["-blur",        $1] if    /\bblur=(\d+(\.\d+)?(x\d+(\.\d+)?)?)\b/          =~ qs
if /\btransparent=([a-f0-9]{3,9})\b/ =~ qs then a += ["-transparent", "#" + $1] elsif
   /\btransparent=([a-z]+)\b/        =~ qs then a += ["-transparent",       $1] end
if /\bborder=(\d+x\d+)\b/            =~ qs then a += ["-border",            $1] end
if /\bbordercolor=([a-f0-9]{3,9})\b/ =~ qs then a += ["-bordercolor", "#" + $1] elsif
   /\bbordercolor=([a-z]+)\b/        =~ qs then a += ["-bordercolor",       $1] end
if /\bframe=(\d+x\d+\+\d+\+\d+)\b/   =~ qs then a += ["-frame",             $1] end
if /\bmattecolor=([a-f0-9]{3,9})\b/  =~ qs then a += ["-mattecolor",  "#" + $1] elsif
   /\bmattecolor=([a-z]+)\b/         =~ qs then a += ["-mattecolor",        $1] end

if a == [] then
  outfile = infile
else
  a  = ["convert"] + a
  a += [infile, outfile]
  r = system(*a) rescue false
  outfile = infile unless r
  Process.waitall
end

print <<KEYWORD
Content-Disposition: filename="after.jpg"
Content-type:   image/jpeg
Content-Length: #{ File.size( outfile ) }

KEYWORD
f = File.open( outfile ,"rb")
STDOUT.binmode
while line = f.gets do
  print line
end
f.close
File.delete( outfile ) if outfile != infile && FileTest.file?(outfile)

raise

⇒ 次の問題に進む

以上です。

Last updated 15.Sep.2006 [ Home ] [ Up ] [ 質問メール ]
Copyright © 2005-2006 Shigeru Konno All Rights Reserved..