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ファイルを用いて、四隅にコピーするという方法があって、そっちの方が簡単かな? とは思うのですが・・・

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

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

画像のプリロード

一昔前、Webページ上での画像のロールオーバー(画像の上にマウスポインタを重ねると画像が変わるアレです)にJavascriptが使われていましたね。でも、今は、Javascriptではなく、CSSで実現する方法が標準となりつつあります。
たとえば・・・

a      {background: url(xxx.jpg);}
a:hover{background: url(yyy.jpg);}

こんな感じですかね。IE6では、アンカータグ(<a>)しか対応してませんが、他のブラウザなら他のタグでも可能です。

で、ここで僕がはまったのが、ページに初めてアクセスしたとき、ロールオーバーさせる要素にマウスを持っていくと一瞬空白になってしまうんですよねぇ・・・。初めてページを表示させたときまだロールオーバーさせる画像が読み込まれていないのが原因で、解決方法は簡単、画像を先読みさせてブラウザのキャッシュに入れてしまえばいい。要するにプリロードさせればいい。んな、こたぁ、言われなくても分かるんです。

その方法は・・・頭の悪い僕では2つぐらいしか思いつきません・・・

  1. javascriptを使って (new Image()).src = “yyy.jpg”; とかしておく。
  2. HTML内に幅/高さを1にした<img>タグを書いておく。
  3. 画像を一枚用意して、width,height,background-positionを駆使して切り分ける

無駄なタグを含める後者は論外。画像を一枚に・・・というのは新規につくるページだと最適ですが、既存の組みあがっているページとなると無駄に工数がかかってしまう。一番簡単にできそうなのは・・・やはりスクリプトで。・・・せっかくロールオーバー処理からJavascriptを排除したのに・・・(--;;;
ブラウザの最適化処理で自動的に読み込んでくれれば問題ないんですけどね・・・。そもそも、css内にある画像は先読みしてほしい気もします。まぁ、無駄にメモリー消費が増えてしまう、パフォーマンスの問題も絡むので無理か・・・。

結局、

/**********************************************
  画像を読み込んでおく関数
  preload(画像1,画像2,....);
***********************************************/
function preload()
{
  var len = 0;

  if((len = arguments.length) &lt;= 0)
    return;

   for(var i=0;i&lt;len;i++)
    (new Image()).src = arguments[i];
}

preload('yyy.jpg','zzz.png');

とかやってしまうんだけど・・・。

う~ん・・・スクリプトを使わない上手いやり方はないものか・・・。

着いた! 早!

朝会社に行くと、机に箱が置いてあって、何かな? と開けてみると・・・先日注文したIllustorator CS5でした。。。Illustrator CS5、28日発売だったはずなのだが・・・?う~ん・・・。お金下ろしてこないと・・・。

いつも思うんだけど、パッケージソフトウェアの外箱って必要ないんじゃないかなぁ~・・・。薄型のCD・DVDケースとかでも十分だと思うんだけど・・・。

Illustrator CS5のパッケージ

アップグレード版のインストーラーって、なんで前のバージョンを削除してくんないでしょうねぇ・・・?CS5をインストールした後、CS2をアンインストールしたらCS5の関連付けが解除されて・・・またCS5をインストールし直し。なんとかならんの・・・?

というわけで、

Illustrator CS5


Win7+CS2より、かなり使いやすくなりました(^^;
 

Adobeといえば・・・

Appleと仲が良い会社というイメージが未だにあるんだけど、ちょっと前のアドビ vs アップル のFlashに関する応酬はで、実はそうじゃないんだな、と思いました。コカコーラvsペプシみたいな日本では到底起こりえない、アメリカの典型的な企業間闘争でしょうか(笑) これに、Googleが絡んできているので、もうメチャクチャ。な感じです。

最近、やたらとHTML5というキーワードが情報系のニュース記事で頻繁に見かけるようになってきました。HTMLは、HTML4以降主に二つの系列に分かれてきています。
一つは、XHTML。XHTMLは、HTMLをXMLのサブセットとするような感じです。もう一つは、このHTML5です。HTML5はよりWebアプリのため、と言ってもいいようなぐらいですが、まだ標準化作業のまっただ中です。

なぜこれほどまでに、HTML5が重要なんだろうか? と考えたとき、行き着く先は「金になる」からなんだと思います。

iPhoneアプリにしろ、アンドロイドアプリにしろ、ほとんどのアプリは、ネット環境前提のWebアプリで、その実態はHTML文とJavascriptのようなスクリプトで駆動するタイプかなと思います。

パソコンにインストールしてあるブラウザでウェブサイトを見る、というスタイルは、この先主流ではなくなる、これは間違いないでしょう。ウェブサイトを見るためだけにパソコンを立ち上げるなんて、ナンセンスですよ、今の時代。僕自身、家の無線LAN経由でiPod Touchを使って見ることの方が圧倒的に多いです。iPadを代表とする、5~10インチ端末の出現で、この流れは加速するはずです。

携帯端末に自由にHTMLとJavascriptを転送できて、それらをその携帯端末で見ることが出来れば便利なのにな・・・と常々思ってました。そこんところを制限して金儲けにしているのが、現在のアプリストア。そのアプリは、端末に搭載されているブラウザで駆動するHTML5とスクリプト。

つまり、極端なこというと、HTML5とJavascriptで組んだ、単なる写真のスライドショーであっても、もし売れればお金になる、ということ。売れるか売れないかは別にして、もっと極端なことを言うと「文字を表示するだけ」のHTMLファイルでもWebアプリだ!と言い張っても通るわけ。

以前ならHTMLとスクリプトでは1円にもならなかったものが、携帯端末から自由を奪う(制限をかける)ことで、コンテンツの保護と金儲けを実現させた、このモデルを考え出して実現した人たちって、頭がいいんでしょうねぇ。
(学歴とか偏差値とかいう意味じゃないですよ、もちろんビジネス的な頭の良さ。)

まぁ、商売人にとってHTML5は「金儲け」のツールでしかないんでしょうねぇ。

ImageFlow and HighSlideJS

今日、仕事で画像中心のギャラリーページのためのギャラリー・スクリプトをネットで探してましたら・・・もう完璧じゃね?的なクールなJavascriptライブラリを発見。結構有名なものみたいです。

その名は ImageFlow

iPhoneなどでおなじみの CoverFlow を Javascript で再現したライブラリです。ただ残念なことに、営利サイト(会社のサイトとか)で使用するにはライセンスが必要・・・とのことで採用するのは却下。

でも、サイトのドキュメントを読むと、HighSlideJSと組み合わせて利用する方法が書いてあって、これがなかなかいいので個人で使おうと・・・思ってたんだけど・・・PHP環境が必要なんで無理っぽそうです。今度DTIの格安のVPSサービスを申し込もうと思っているので、もうちょっとしたら使えそうです。

で、ローカル環境のサーバーにインストールしてみました。
 

マウスホイールも効きます

 
クリックすると・・・

HighSlideJSと併用

使うときも簡単で、簡単なサンプルのHTMLファイルがあるのですぐ分かります。
が、いちいち <img>タグを列挙するのも面倒くさいというわけで、特定のディレクトリをスキャンしてjpegファイルを列挙させるphpファイルを書けばラクになります。

<?php
function DirectoryScanAndWrite($img_dir)
{
  if ($handle = opendir($img_dir))
    {
      while (false !== ($file = readdir($handle)))
        {
          if(preg_match("/^\.{1,2}$/",$file) || preg_match("/^refl_(.+)$/i",$file))
            continue;
          
          $path = "$img_dir/$file";
          print "<img src=\"$path\" longdesc=\"$path\" alt=\"$file\">\n";
        }
    }
}
?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html lang="ja">
	<head>
		<meta http-equiv="content-type" content="text/html; charset=shift_jis">
		<title>ImageFlow</title>
		<meta name="robots" content="index, follow, noarchive" />
		<link rel="stylesheet" href="style.css" type="text/css" />

		<!-- This includes the ImageFlow CSS and JavaScript -->
		<link rel="stylesheet" href="imageflow.packed.css" type="text/css">
		<link rel="stylesheet" href="highslide.css" type="text/css">
		<script type="text/javascript" src="imageflow.packed.js"></script>
		<script type="text/javascript" src="highslide.packed.js"></script>
		<script type="text/javascript">
hs.graphicsDir = './graphics/';

domReady(function()
         {
           var Highslide = new ImageFlow();
           Highslide.init({ ImageFlowID: 'Highslide',
           opacity: true,
           onClick: function() 
             { 
               return hs.expand(this, { src: this.getAttribute('longdesc'),
               outlineType: 'rounded-white',
               fadeInOut: true } ); 
             } 
           });
         });
		</script>
		
		<style type="text/css">
			.highslide-container {z-index:10003 !important;}
			.highslide-credits {display:none !important; }
		</style>
	</head>
	<body>
		<h1>ImageFlow with HighSlide JS</h1>

		<!-- This is all the XHTML ImageFlow needs -->
		<div id="Highslide" class="imageflow">
			<?php DirectoryScanAndWrite('./img2/'); ?>
		</div>

	</body>
</html>

で、PHPは、画像の鏡像反射効果を作成するために必要みたいで同じディレクトリに反射効果が適用されたものが画像ファイルと同じ数だけ生成されます。

ImageFlowのインスタンスを作成するときに、いろいろなオプションが指定できたり、CSSファイルを編集することでかなり自由にデザインできそうです。PHP環境が使えるサイトでは、写真のデキがそこそこでも、かなりクール?な見せ方ができるので、僕にピッタリかもしれません(^^;;;