GDで角丸サムネール画像の生成~その2~

GDで角丸サムネール画像の生成の続きです。
 
 

前回のコードだと、円の方程式から得られた判定に対して単に背景色の点を打つ、打たないだけだったので、角丸部分がガタガタになってしまい、非常に見苦しいものでした。それを隠すために(笑)元画像にガタガタの角丸を適用して元画像をリサンプルして誤魔化していました。
 

それでは、あまりにも情けない・・・ということで、アンチエイリアス処理を施そう・・・ということで、アンチエイリアスのアルゴリズムをネットで検索して調べましたが・・・アルゴリズム自体の解説が少なく、ドキュメントを見つけても、高校・大学時代にやったような、複雑な数式のオンパレードで・・・頭がクラクラ(ーー;;;
 

目眩を起こしつつザラッと流し読みしたけど・・・ドキュメントが英語なので・・・ほとんど理解できん・・・ああ、柔らか頭に戻りたい。。。
 

前置きが長くなりましたが・・・これは、もう、思いつきで行きあたりバッタリで実装するしかない!というわけで・・・
 

「なんちゃってアンチエイリアス処理」を書いてみました。
あくまで、なんちゃってなので、ガタガタより、まぁマシかな・・・というレベルです。
 

角丸

角丸部分にする上下左右の半径Rの1/4円弧を囲んだ正方形部分のピクセルをおのおのスキャンして円の方程式によりピクセル色を決定するところまでは前回と同じです。
変えたところは、判定部分で、ピクセル座標(x,y)の点において、xのときの円周上の点を求め、それとの差(delta)が、0 ≦ delta ≦ 1のとき、
若干薄く描画。その差が1以上のとき、0以下のときで、点を打つ・何もしないを決定します。実際には、↓の setPixelサブルーチンのようにしています。
忠実にアンチエイリアス処理をしている訳ではなく、0 ≦ delta ≦ 1 部分の描画色(描画色というか、背景色の透明度(α)を単に変化させています)の決定は、かなり適当です。「なんちゃってアンチエイリアス」たる所以です(^^;;;

#
# perl script
#

#
#簡易アンチエイリアス処理をする点(ピクセル)の描画サブルーチン
#
#$thisはGD::Imageを継承したオブジェクト,$rは角丸の半径ピクセル、$x,$yはピクセル位置、
#$yfは$xのときの円周上の実際のy座標(実数でピクセル位置ではない),
#$colorは背景色,$bは角丸が画像の上部分(1)か、下部分(0)か。

our $PARAM = 0.75;
our $RANGE = 127;
our $NEAREST_INNER = 120;
our $NEAREST_OUTER = 15;

sub setPixel
{
  my ($this,$r,$x,$y,$yf,$color,$b) = @_;
  my $retVal = 1;
  my $delta = ($b ? $yf - $y : $y - $yf) * ($b ? $r - $y : $r - $this->height + $y) / ($r * $PARAM);
  
  if($delta >= 0 && $delta <= 1)
    {
      $delta = 0.1 if($delta == 0);
      #ほぼ円周上
      my $range = $RANGE - round($delta*$RANGE);
      my $alphacolor = $this->colorAllocateAlpha($this->rgb($color),$range);

      $this->GD::Image::setPixel($x,$y,$alphacolor);
    }
  elsif($delta > 1)
    {
      #円外
      $delta < 2 ? $this->GD::Image::setPixel($x,$y,$this->colorAllocateAlpha($this->rgb($color),$NEAREST_OUTER)) : $this->GD::Image::setPixel($x,$y,$color);
    }
  elsif($delta < 0)
    {
      #円内
      $this->GD::Image::setPixel($x,$y,$this->colorAllocateAlpha($this->rgb($color),$NEAREST_INNER)) if($delta > -1);
      $retVal = 0;
    }

  $retVal;
}

#
#角丸部分のピクセル値をスキャンする。実際の描画は、上記のsetPixelを使う。
#
# $this は、GD::Imageを継承したオブジェクト 、$radiusは円弧の半径ピクセル
sub applyRoundCorner
{
  my ($this,$radius,$color) = @_;
  my ($x,$y) = (0,0);
  my ($width,$height) = ($this->width-1,$this->height-1);
  my $diameter = 2 * $radius;
  my $limit = $radius;
  $this->alphaBlending(1);
  
  if($diameter > $width || $diameter > $height)
    {
      print "Radius of round corner must be less than width or height...\n";
      print "Process can not apply round corner...\n";
      return;
    }

  #画像の上部分の角丸
  for($y = 0;$y < $limit;$y++)
    {
      #左
      for($x = 0;$x <= $radius;$x++)
        {
          my $yf = abs(sqrt(abs($radius**2 - ($x - $radius)**2)) - $radius);
          last unless($this->setPixel($radius,$x,$y,$yf,$color,1));
        }
      #右
      for($x = $width;$x >= $width - $radius;$x--)
        {
          my $yf = abs(sqrt(abs($radius**2 - ($x - ($width - $radius))**2)) - $radius);
          last unless($this->setPixel($radius,$x,$y,$yf,$color,1));
        }
    }

  #画像の下部分の角丸
  $limit = $height + 1 - $radius;
  for($y = $height;$y > $limit;$y--)
    {
      #左
      for($x = 0;$x <= $radius;$x++)
        {
          my $yf = abs(sqrt($radius**2 - ($x - $radius)**2) + ($height - $radius));
          last unless($this->setPixel($radius,$x,$y,$yf,$color));
        }
      #右
      for($x = $width;$x >= $width - $radius;$x--)
        {
          my $yf = abs(sqrt($radius**2 - ($x - ($width - $radius))**2) + ($height - $radius));
          last unless($this->setPixel($radius,$x,$y,$yf,$color));
        }
    }
}

というわけで、自作のサムネール作成 perlモジュールに組み込んで、実際に角丸サムネールを作成してみました。
  

拡大すると・・・

こんな感じ。
アンチエイリアスしている風に見えるでしょう。。。あああ。

アンチエイリアスがかかった角丸が簡単にできるライブラリ・・・ないもんでしょうかねぇ・・・。
 

忘れてたけどImagickってのもありましたねぇ~、多機能すぎて、一度挫折した覚えが・・・。

GDで角丸サムネール画像の生成

公開・表示したい画像がいっぱいあるとき、サムネール画像を作るのは非常に手間です。
いちいち一つ一つ画像編集する人は・・・まぁ、いないでしょうね・・・(^^;

以前に、CGIでも使えるようにGDを使用してサムネール画像生成のPerlモジュールを作成していました。

で、今回、そのサムネール画像に角丸を適用したものを生成できるように、機能を付け足すことにしたんですが・・・、
画像に角丸を適用するスマートな方法がわからず、考えたあげく、少々チカラワザ的にインプリメントしてしまいました。

四隅のピクセル値をスキャンして、円弧外のピクセル値は背景色に、円弧内のピクセル値はそのままに・・・という感じです。

ピクセル値の判定には、円の方程式、X2 + Y2 = Radius2 を使用しています。

#
# perl script
#
# $this は、GD::Image 、$radiusは円弧の半径ピクセル
sub applyRoundCorner
{
  my ($this,$radius,$color) = @_;
  my ($x,$y) = (0,0);
  my ($width,$height) = ($this->width-1,$this->height-1);
  $this->alphaBlending(1);
  my ($lhs,$rhs) = (0,$radius**2);

  #上
  for($y = 0;$y <= $radius;$y++)
    {
      #左
      for($x = 0;$x <= $radius;$x++)
        {
          $lhs = ($x - $radius)**2 + ($y - $radius)**2;
          last unless(setPixel($this,$radius,$x,$y,$color,$lhs,$rhs));
        }
      #右
      for($x = $width;$x >= $width - $radius;$x--)
        {
          $lhs = ($x - ($width - $radius))**2 + ($y - $radius)**2;
          last unless(setPixel($this,$radius,$x,$y,$color,$lhs,$rhs));
        }
    }

  #下
  for($y = $height;$y >= $height-$radius;$y--)
    {
      #左
      for($x = 0;$x <= $radius;$x++)
        {
          $lhs = ($x - $radius)**2 + ($y - ($height - $radius))**2;
          last unless(setPixel($this,$radius,$x,$y,$color,$lhs,$rhs));
        }
      #右
      for($x = $width;$x >= $width - $radius;$x--)
        {
          $lhs = ($x - ($width - $radius))**2 + ($y - ($height - $radius))**2;
          last unless(setPixel($this,$radius,$x,$y,$color,$lhs,$rhs));
        }
    }
}

#
# ピクセル(x,y)にカラー値をセットする。
#
sub setPixel
{
  my ($this,$radius,$x,$y,$color,$lhs,$rhs) = @_;
  my $ret = 1;
 
  if($lhs > $rhs)
    {
      $this->setPixel($x,$y,$color);
    }
  else
    {
      $ret = 0;
    }

  return $ret;
}

ただ、サムネール画像にこれをそのまま適用すると、当然ですがアンチエリアス処理がされないので角丸部分がガタガタになってしまいます。
そこで、サムネール画像に角丸を適用するのではなく、メモリ上の元画像に角丸を適用し、GD::ImageのcopyResampledでサムネール画像を生成(縮小)することでアンチエリアス処理の替わりとすることにしました。

他の方法としては、角丸部分をアルファ付きのPNGファイルを用いて、四隅にコピーするという方法があって、そっちの方が簡単かな? とは思うのですが・・・

根本的に改善するには、やはり画像処理のアルゴリズム(主にアンチエリアス処理なんですが・・・)について勉強しなければいけませんねぇ・・・。

他の方が作ったライブラリを探した方がいいかも・・・(^^;;;

ベストなSSHクライアントは?

会社のWebサーバーが取引先のVPSサーバーに移行した関係で、コンテンツだけでなくWebサーバー自体の管理も私がするようになりました。

個人的にも使用し始めようとしているDTIのVPSサーバーと違って会社が契約しているVPSサーバーは、やっぱりレスポンスが速いです~。
これで僕が受け持っている仮想サーバーは、テスト目的で入れているものも含めて4つ。このところ僕の中では仮想サーバーが大流行りです(^^;

実機を持つとサーバー実機自体の管理、電気代、廃熱、緊急時の対応、バックアップなどの諸々の煩わしさから解放されます。仮想化というと、パフォーマンス(性能)がなぁ~・・・と思う方がまだまだいるみたいですが、デスクトップ機ならまだしも、サーバー用途では必要十分な性能です。もちろん過負荷時のパフォーマンスは実機よりも落ちますが、負荷が大きくなりだした時点で実機へ移行すればいいだけ。デメリットよりメリットの方が大きいので流行ってんでしょうねぇ。

で、管理するのにSSHアクセスすることが多いので、やっぱりSSHクライアントは使い勝手を求めてしまいます。

現状、Windowsで動くSSHクライアントは

ぐらいでしょうか。
最近までPoderosaの複数のセッションをタブで切り替える方式が好きで使っていたのですが・・・ちょっと前からTeraTermを使うようししました。
これといって理由はないのですが、まぁ、使うケースによってPoderosaを使ったり、TeraTermをつかったり・・します。タブでセッションを切り替えられるのは便利だし~。

TeraTermはファイル転送もTeraTermからできるのでWinSCPといった別プログラムを立ち上げないで済むのがいい感じです。

で、付属のTera Term Menuを使ってサーバー毎に接続設定を保存して、タスクバーの通知領域からTeraTermを立ち上げるようにしているのですが、このTera Term Menuがちょっと問題あり。

一時的に使用しているテンポラリファイル(下記参照)にパスワードが平文で保存されてしまっているんですよねぇ・・・このテンポラリファイルはTera Term Menuを終了しても残ってしまっているのでなんとも変な仕様です。

【Tera Term Menu が作っていると思われるテンポラリファイル】
filedelete 'T:TEMPttmA.tmp'
connect '172.16.***.***:23'
UsernamePrompt = 'login:'
Username = 'admin'
PasswordPrompt = 'Password:'
Password = '1234567'
wait   UsernamePrompt
sendln Username
wait   PasswordPrompt
sendln Password

一応管理しているすべてのサーバーはパスワードログイン不可設定なので、秘密鍵ファイルを漏らさないようにすればいい話ですが・・・う~ん・・・共用PCで、これはちょっと問題かなと。

幸い僕が使っているPCはテンポラリディレクトリをRAMディスク上に作るようにしているのでPCをシャットダウンすると自動的に削除されるので実質問題はないとは思うのですが・・・。

探せばどなたかがパッチを作っていそうな気もしますが・・・。