このところ、WordPressのカスタムフィールドを活用した、プラグインも外部サービスも使わない「アクセスカウンター」を開発していた。
このブログでは現在のところ、外部サービスのアクセスカウンターを利用しているが、それを自作してみようというわけだ。
文字(数字)での表示でもいいけど、やっぱりアクセスカウンターといえば画像だよね。
ということで、1桁ずつの画像を並べて何桁かのアクセスカウンターを作る、というようなことをしていた。
しかし、これでは桁数の分だけ画像を読み込むことになる。
よくある外部サービスみたいに、素材となる画像から、何桁かの数字を含む1枚の画像を生成することができれば……
PHPでできるらしいので、やってみた。
※この記事ではアクセスカウンターのカウント機能の作り方は扱わない。その話はまたいつか……
画像のための「ページ」を作成
その都度ページ内にカウンターの画像を生成するのもいいけど、俺はカウンター画像にURLを与え、そのURLにアクセスすることで、カウンター画像をどこからでも呼び出せるようにした。
URLを与えるには、固定ページを作り、スラッグを設定しなければならない。
そして、固定ページのテンプレートに画像を生成するPHPを書けばよい。
「page-○○.php」という名前のファイルをテーマディレクトリに置けば、○○というスラッグをもつ固定ページにのみ、そのテンプレートを適用させることができる。
俺の場合は、複数の固定ページに適用させられるように、テンプレートファイルを作成した。
固定ページのテンプレートは名称が混同されている感も否めないが、要するに page.php とか single.php といった自動的に適用されるファイルではなく、固定ページの編集画面で選択して適用させるファイルのことである。
固定ページのテンプレートの作り方は、テーマディレクトリの直下に page.php と同様のPHPファイルを新規作成し、その先頭に以下のような記述をしておく。
※ファイル名はWordPress標準のファイル名とかぶらなければ何でもいい。
<?php
/*
Template Name: <任意のテンプレート名>
*/
?>
これで、固定ページの編集画面でそのテンプレートを選択できるようになる。
このファイルに、これから説明するコードを書いていく。
「GD」で画像を生成
PHPにはGDという画像処理のためのモジュールがあり、最近のバージョンのPHPには標準で組み込まれているようだ。
これを使って画像を生成・出力することができる。
GDによる画像処理の手順は、まずキャンバスのような画像の土台を定義し、そこに素材となる画像を読み込んで貼り付けていき、最後に完成した画像を出力する、という流れになる。
以下、俺がアクセスカウンターの画像を生成した手順を追って説明する。
素材画像の情報を取得する
素材となる画像は、ここではPNG形式のものをテーマディレクトリにアップロードして使用する。
まず、素材のファイル名(フルパスを含む)を $file_name
に代入する。
URLでもいいんだけど、その場合は画像をHTTPアクセスで取得することになるから、処理は遅くなる。
get_stylesheet_directory()
でテーマディレクトリ直下のフルパス(末尾のスラッシュは含まない)を取得できるので、ファイル名は以下のようになる。赤字部分は各自変更すること。
$file_name = get_stylesheet_directory() . '/img/counter.png';
次に、素材画像のサイズなどの情報を取得しておく。
以下のコードで $width
(画像の幅)を÷10しているのは、素材が0から9までの画像を左から順に結合した画像であり、そのひとつひとつのサイズを取得するためだ。
$image_size = getimagesize($file_name);
$width = $image_size[0] / 10;
$height = $image_size[1];
キャンバスを作る
次に、画像処理の土台となるキャンバスを設定する。
$cols
にはアクセスカウンターに表示したい桁数を代入しておく。
$canvas = imagecreatetruecolor($width * $cols, $height);
imagecolortransparent($canvas, imagecolorallocate($canvas, 0, 0, 0));
1行目で imagecreatetruecolor()
によりキャンバスを設定している。
第1引数はキャンバスの幅、第2引数はキャンバスの高さとなる。
ただし、このままではキャンバスの背景色が黒のままになる。
2行目では imagecolorallocate()
により黒を指定し、 imagecolortransparent()
によりその色を透過色に設定している。
これにより、キャンバスの背景色が透明になる。
素材画像を読み込む
先ほどは素材画像の情報を取得したが、今度は画像データを読み込む。
たった1行。
$the_image = imagecreatefrompng($file_name);
素材画像を貼り付ける
いよいよ生成する画像を形作っていく工程だ。
アクセス数はあらかじめ $view_count
に代入しておくが、形式は数字添字の配列とし、左の桁から順に配列の各要素に1桁ずつ格納されているものとする。
つまり、たとえば「003176」なら以下のようになる。
$view_count = array(0, 0, 3, 1, 7, 6);
// つまり以下と同じ
$view_count[0] = 0;
$view_count[1] = 0;
$view_count[2] = 3;
$view_count[3] = 1;
$view_count[4] = 7;
$view_count[5] = 6;
この配列の作り方は、以下の通り。
$view_count = 3176;
$view_count = sprintf( '%0' . $cols . 's', $view_count ); // 先頭をゼロで埋める
$view_count = str_split($view_count);
この配列を使い、以下のように foreach
ループを回す。
foreach ( $view_count as $key => $value ) {
$process = imagecopy($canvas, $the_image, $width * $key, 0, $width * $value, 0, $width, $height);
}
キャンバスに画像を貼り付ける関数が imagecopy()
であるが、引数が8つもあってややこしい。
第1引数がキャンバス、第2引数が貼り付ける画像。
第3引数は貼り付ける位置のx座標(左端が基準)、第4引数はy座標(上端が基準)となる。
foreach
ループ内で $key
は現在の桁番号(ゼロが基準)を表すので、 $width * $key
が現在の貼り付け位置となる。
素材はこの位置を左上として貼り付けられる。
素材画像は任意の大きさにトリミングされて貼り付けられる。
素材画像のトリミング範囲の左上のx座標が第5引数、y座標が第6引数となる。
$value
は“現在の桁の数字”を表し、素材画像は左から順に「0、1、2、3、……」となっているから、 $width * $value
が目的の数字の位置となるのだ。
そして、トリミング範囲の幅が第7引数、高さが第8引数だ。
つまり、まとめると以下のような感じ。
imagecopy(キャンバス, 素材画像, 貼付位置x, 貼付位置y, トリミング位置x, トリミング位置y, トリミング幅, トリミング高);
foreach
ループにより1桁ずつ位置をずらしながら貼り付けることにより、何桁のアクセスカウンターでも作ることができる。
画像を出力する
以上で画像は完成したので、あとはデータをリクエストに対して出力するだけだ。
ファイルとして出力することも可能だが、今回は画像を動的に生成するのが目的のため、標準出力(テキストであれば echo
)に出力する。
GDにおいては専用の関数 imagepng()
が用意されているのでこれを使用する。
$print = imagepng($canvas, null, 9);
第1引数はキャンバス、第2引数は保存するファイル名(今回は保存しないので null
)、第3引数はPNGの圧縮率(たぶん9でいい)。
そして最後に後片付けだ。
imagedestroy($canvas);
imagedestroy($the_image);
exit;
imagedestroy()
という物騒な名前の関数で、キャンバスと素材画像のキャッシュを消去。
画像を出力した以上、後の処理は必要ないので(というか下手に処理をしないように) exit
で一切を終了させる。
その他
wp_header()
や wp_footer()
は?
そんなもの、いりません!
……それらはWebページを表示させるのに使うもので、今回は画像だから、むしろそれらを使ってはならない。
その他、何かを出力しかねないような余計なコードは一切不要。
アクセスカウンターの数値を取得するなどの処理は別途、必要に応じて、ただし出力は一切しないように。
HTTPヘッダーを変更
さて、これで生成した画像を出力できるようになったが、このままでは作成した固定ページのURLにアクセスしても、画像を表示させることはできない。
なぜなら、その固定ページはあくまで「ページ」、すなわちHTMLなWebページでしかないからだ。
いや、データとしては画像だが、ユーザーエージェント(ブラウザ)がそれを画像として扱わないという意味だ。
つまり表示されるのは文字化けした謎のページ……
この辺りはHTTPの仕組みの話になるが、ユーザーエージェントとWebサーバーがやり取りをするとき、ページ(または画像など)本体のデータを転送する前に、HTTPヘッダーという一連のデータを送信する。
HTTPヘッダーの中に、そのURLがどんなデータを表すかを示す Content-Type
というものが含まれる。
これはサーバーが勝手に送信するものだが、WordPressの固定ページでは、もちろん Content-Type
はHTMLのWebページを指す text/html
となっている。
PNG画像ならば image/png
としなければならないのだ。
これを変更する機能がWordPressにも用意されている。
PHPの標準関数に、HTTPヘッダーを追加・上書きする header()
というものがあるが、これは実際にHTTPヘッダーを送信する前にこの関数を実行しなければならない。
固定ページのテンプレートにこれを書いても、実行タイミングとしては遅すぎて無意味となる。
そこで、WordPressではアクションフック send_headers
が用意されており、テーマ関数 functions.php に以下のように記述することで、特定のページの Content-Type
を image/png
に変更することができる。
function http_header_image_png() {
if ( strpos($_SERVER['REQUEST_URI'], '/counter-img') === 0 ) {
header('Content-Type: image/png');
header('Cache-Control: private');
}
}
add_action( 'send_headers', 'http_header_image_png' );
赤字部分はカウンター画像(固定ページ)のURL(ドメイン名以降)に各自変えること。
3行目が Content-Type
の指定、4行目はユーザーエージェント以外にキャッシュさせないための設定である。
※4行目のコードはこのブログのサーバーの仕様に従って追加した。
2行目はカウンター画像のURLを判別する部分だが、注意しなければならないのは、固定ページの判別に使う関数 is_page()
は使えないという点だ。
なぜなら、アクションフック send_headers
のタイミングはメインクエリより前であり、どのページを表示するのかがまだ決まっていないからだ。
よって、スーパーグローバル変数である $_SERVER
に含まれる、リクエストされたURLを参照して判別している。
なお上記の例では、リクエストされたURL(ドメイン名以降)が /counter-img から始まる、という先頭一致での判別を行なっている。
完全一致にて判別しない理由は、 $_SERVER['REQUEST_URI']
にはクエリ文字列も含まれるため、URLにクエリ文字列を付けてカウンター画像を制御する、という場合にも正しく判別できるようにするためだ。
まとめ
今回はWordPressで画像を生成する方法と、固定ページを「画像」としてアクセスできるようにする方法を紹介した。
WordPressで画像を生成できるということは、画像を生成するようなWebサービスもWordPressで作れるということだ。
「GD」というやつはかなり興味深い。
俺も試しにいろんな画像を生成してみたい。
QRコードとか作ってみたいね。
Captchaの読みにくい文字画像とか……
何のために? っていう。
Captcha自体も頑張ったら作れそうだけど、nonceとかが要るって言われたら、今の俺にはお手上げかな。
話が逸れてきたから今回はこの辺で。
こめんと