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で角丸サムネール画像の生成~その2~」への2件のフィードバック

  1. 通りすがり のコメント:

    PHPerです。
    私もGDで同じようなことをしようと思っていろいろあさってました。
    そこで、見たのは画像を4倍で作って1ピクセルあたりを4つのピクセルとして、その4つのピクセルの中の平均の色で1ピクセルを塗る、といったアルゴリズムが説明されてました。

    ので、「その1」でやっている、大きい画像で作ってcopyResampledで縮小する、ことはアルゴリズム的にはそう外れてなさそうに思いました。

  2. なかがわ のコメント:

    コメントどうもです。

    4倍で作って・・・という方法は、大きい画像を処理すると急に効率が悪く(重く)なるんですよね~。
    アンチエイリアス処理自体をcopyResampledに任せてしまうのは、
    本当にやりたいメインの処理を考えることに集中できるので、便利かもしれませんねぇ。

コメントは停止中です。