このところ、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-Typeimage/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とかが要るって言われたら、今の俺にはお手上げかな。

話が逸れてきたから今回はこの辺で。