WordPressでタイトルからアイキャッチ画像を合成するプラグインを導入(とその改造をする)

WordPressを利用していて、アイキャッチ画像の準備というのが結構面倒だったりします。
Photoshopなどを利用して毎回作るのも面倒、nodejsでアプリケーションを動かすのも面倒。
WordPressを動かしているのだから勝手に作ってほしいという願いをかなえてくれるプラグインを見つけました。

アイキャッチ画像を作るプラグインを探す

求める機能

私がアイキャッチ画像の生成に関して求めることは以下の点でした。

  • 外部アプリではなく、WordPressだけで動作する
  • 背景画像+タイトル文字の画像を生成する

プラグインの候補

調べてみると、次の2つが候補として見つかりました。

このうちOGPanicは有償のサービスで、プラグインからAPIを叩いて画像を生成するものだったので、検討から外しました。

Create Eye Catch For Classic

もう一方のCreate Eye Catch For Classicは、WordPressの管理画面上で画像を動的に生成するものです。
htmlのcanvasを利用しています。
このプラグイン新しめなのもあって、情報が皆無、現時点では利用者もほぼ居ない状況です。


(利用者が10人未満)

さてこのプラグイン、実際に導入してみたところ求める機能を実現するのに、最適なプラグインであることが分かりました。
開発者の方は日本人のようなのでそれもグッドポイントですね。
このプラグインを作成、公開してくださっているnove-bさんには非常に感謝です!

Create Eye Catch For Classicは具体的に何ができるのかというと、記事投稿画面でタイトルに応じてアイキャッチ画像を自動生成してくれます。

実際に導入してタイトル画像が生成されている状況が分かるスクリーンショットがこちらです。
サイドバーの右下にアイキャッチ画像が生成されています。

アイキャッチ画像は自動で設定されないので、画像をダウンロードして、自分の手でアイキャッチ画像に設定してあげる必要がありますが、画像が自動生成されるだけでも手間の99%がなくなります!

気に入らないところを改造する

気に入らないところ

さて、プラグイン自体は非常に素晴らしいのですが、気に入らない点がいくつかありました。

  • タイトルが1行でも複数行にまたがる場合でも、縦位置が上寄せになる
  • 文字間隔が半角も全角も等間隔
  • フォントが嫌い

これらの不満がない他のプラグインを探したりもしたのですが、そんなものは存在せず。
仕方ないので自分で改造して利用することにします。

なおプラグイン自体はGPLで公開されていて、改変自由、再頒布自由となっています。

気に入らない点がどうしてそう実装されているか

改造するに当たって、どうしてそういう実装になっているのか、読んでいきます。
プラグインのソースコードを見てみると、かなりシンプルな構成で分かりやすかったので、簡単に仕組みが把握できました。

確認するのは以下のファイルです。
/wp-content/plugins/create-eye-catch-for-classic/create-eye-catch.php

タイトルが1行でも複数行にまたがる場合でも、縦位置が上寄せになる

縦位置の決定が、上の空き幅(cecfc_topMargin)+行数*行間だけで決定されているためでした。
該当のコードは以下の箇所になります。
なお、同様のコードが計2箇所(設定画面のプレビュー用と、投稿画面の画像生成用)あります。以下同じです。

      //文字の表示 y軸とx軸をループする
      for (var i = 0; i < aryRow.length; i++) {
        aryStr = aryRow[i].split('');
        for (var j = 0; j < aryStr.length; j++) {
          cecfc_ctxGround.fillText(aryStr[j], (j * cecfc_fontSize) + cecfc_leftMargin, (i * cecfc_fontSize * cecfc_lineHeight) + cecfc_topMargin);
        }
      }
    }

文字間隔が半角も全角も等間隔

一つ上と同じ箇所なのですが、文字毎に横位置を設定していて、横位置が左の空き幅(cecfc_leftMargin)+文字サイズ*文字数で決定されているためでした。
半角文字でも文字サイズ分の幅が空くため、間延びしたような見た目になります。

      //文字の表示 y軸とx軸をループする
      for (var i = 0; i < aryRow.length; i++) {
        aryStr = aryRow[i].split('');
        for (var j = 0; j < aryStr.length; j++) {
          cecfc_ctxGround.fillText(aryStr[j], (j * cecfc_fontSize) + cecfc_leftMargin, (i * cecfc_fontSize * cecfc_lineHeight) + cecfc_topMargin);
        }
      }
    }

フォントが嫌い

ソースコード上で以下のようにRobotoとNoto Sans JPを利用するように設定されていました。

      cecfc_ctxGround.font = `bold ${cecfc_fontSize}px 'Roboto', 'Noto Sans JP'`;

コードを修正する

それぞれの修正を個別に説明するのが面倒なので、diffを貼っておきます。
一応修正した内容をまとめると以下のような感じです。

  • タイトルが1行でも複数行にまたがる場合でも、縦位置が上寄せになる
    • 最大行数に満たない場合はオフセット(top_offset)を計算し、topMarginに加えてtop_offsetを追加するようにして上下中央に揃うようにした
  • 文字間隔が半角も全角も等間隔
    • 1文字毎に横位置を指定して文字を配置するのではなく、1行毎にまとめて文字を配置するようにした
    • プロポーショナルフォントや半角文字で1行に入る文字数が変動するので、「あ」を1行の最大文字数並べた際の横幅を最大横幅として、その横幅を超えたら改行するようにした
  • フォントが嫌い
    • BIZ UDPゴシックへ変更した
    • ブラウザ上で画像を生成するので、WordPressを利用するPCに含まれているフォントであれば何でもよい

気に入らない点の修正だけではなく以下の点も修正しました。

  • 変数定義がvarになっている箇所を、letconstへ置き換えた
  • (偶然見つけた)非推奨の関数を、現行の関数へ置き換えた
  • アイキャッチ画像のダウンロードをpng形式へ変更した
110a111
>
189a191
> let title = el.substring(0, cecfc_oneLineTextLength * cecfc_totalLine)
191c193,196
< const title = el;
---
> // ↑で生成されたタイトルの文字量が記入されたタイトルより少ない時
> if (title.length < el.length) {
> title = title.substr(0, title.length - 1) + '…';
> }
193c198
< cecfc_ctxPreview.font = `bold ${cecfc_fontSize}px 'BIZ UDPゴシック'`;
---
> cecfc_ctxPreview.font = `bold ${cecfc_fontSize}px 'Roboto', 'Noto Sans JP'`;
199c204
< const aryRow = [];
---
> var aryRow = [];
201,204c206
< let row_cnt = 0;
<
< //全角時の全幅を確認
< const max_width = cecfc_ctxPreview.measureText('あ'.repeat(cecfc_oneLineTextLength)).width;
---
> var row_cnt = 0;
207,209c209,211
< for (let i = 0; i < inputTextArray.length; i++) {
< let text = inputTextArray[i];
< if (cecfc_ctxPreview.measureText(aryRow[row_cnt] + text).width >= max_width) {
---
> for (var i = 0; i < inputTextArray.length; i++) {
> var text = inputTextArray[i];
> if (aryRow[row_cnt].length >= cecfc_oneLineTextLength) {
211,216d212
< // 最大行数を超えた場合に、末尾を削って…に
< if (row_cnt >= cecfc_totalLine) {
< row_cnt--;
< aryRow[row_cnt] = aryRow[row_cnt].substring(0, aryRow[row_cnt].length-1) + '…';
< break;
< }
221,226d216
< // 最大行数を超えた場合に、末尾を削って…に
< if (row_cnt >= cecfc_totalLine) {
< row_cnt--;
< aryRow[row_cnt] = aryRow[row_cnt].substring(0, aryRow[row_cnt].length-1) + '…';
< break;
< }
233,235d222
< //行数に応じたオフセット
< const top_offset = (cecfc_totalLine - row_cnt - 1) * cecfc_fontSize * cecfc_lineHeight / 2;
<
237,238c224,228
< for (let i = 0; i < aryRow.length; i++) {
< cecfc_ctxPreview.fillText(aryRow[i], cecfc_leftMargin, (i * cecfc_fontSize * cecfc_lineHeight) + cecfc_topMargin + top_offset);
---
> for (var i = 0; i < aryRow.length; i++) {
> aryStr = aryRow[i].split('');
> for (var j = 0; j < aryStr.length; j++) {
> cecfc_ctxPreview.fillText(aryStr[j], (j * cecfc_fontSize) + cecfc_leftMargin, (i * cecfc_fontSize * cecfc_lineHeight) + cecfc_topMargin);
> }
259a250
>
306c297,301
< const title = el;
---
> let title = el.substring(0, cecfc_oneLineTextLength * cecfc_totalLine)
> // ↑で生成されたタイトルの文字量が記入されたタイトルより少ない時
> if (title.length < el.length) {
> title = title.substr(0, title.length - 1) + '…';
> }
308c303
< cecfc_ctxGround.font = `bold ${cecfc_fontSize}px 'BIZ UDPゴシック'`;
---
> cecfc_ctxGround.font = `bold ${cecfc_fontSize}px 'Roboto', 'Noto Sans JP'`;
314c309
< const aryRow = [];
---
> var aryRow = [];
316,319c311
< let row_cnt = 0;
<
< //全角時の全幅を確認
< const max_width = cecfc_ctxGround.measureText('あ'.repeat(cecfc_oneLineTextLength)).width;
---
> var row_cnt = 0;
322,324c314,316
< for (let i = 0; i < inputTextArray.length; i++) {
< let text = inputTextArray[i];
< if (cecfc_ctxGround.measureText(aryRow[row_cnt] + text).width >= max_width) {
---
> for (var i = 0; i < inputTextArray.length; i++) {
> var text = inputTextArray[i];
> if (aryRow[row_cnt].length >= cecfc_oneLineTextLength) {
326,331d317
< // 最大行数を超えた場合に、末尾を削って…に
< if (row_cnt >= cecfc_totalLine) {
< row_cnt--;
< aryRow[row_cnt] = aryRow[row_cnt].substring(0, aryRow[row_cnt].length-1) + '…';
< break;
< }
336,341d321
< // 最大行数を超えた場合に、末尾を削って…に
< if (row_cnt >= cecfc_totalLine) {
< row_cnt--;
< aryRow[row_cnt] = aryRow[row_cnt].substring(0, aryRow[row_cnt].length-1) + '…';
< break;
< }
348,350d327
< //行数に応じたオフセット
< const top_offset = (cecfc_totalLine - row_cnt - 1) * cecfc_fontSize * cecfc_lineHeight / 2;
<
352,353c329,333
< for (let i = 0; i < aryRow.length; i++) {
< cecfc_ctxGround.fillText(aryRow[i], cecfc_leftMargin, (i * cecfc_fontSize * cecfc_lineHeight) + cecfc_topMargin + top_offset);
---
> for (var i = 0; i < aryRow.length; i++) {
> aryStr = aryRow[i].split('');
> for (var j = 0; j < aryStr.length; j++) {
> cecfc_ctxGround.fillText(aryStr[j], (j * cecfc_fontSize) + cecfc_leftMargin, (i * cecfc_fontSize * cecfc_lineHeight) + cecfc_topMargin);
> }
358c338
< const base64 = canvasBackground.toDataURL("image/png");
---
> var base64 = canvasBackground.toDataURL("image/jpeg");
360c340
< cecfc_saveEyeCatch.download = `${new Date().getTime()}.png`;
---
> cecfc_saveEyeCatch.download = `${new Date().getTime()}.jpg`;

まとめ

非常に有用なプラグインを開発頂いたおかげで、きれいなアイキャッチ画像を生成できるようになりました。
WordPressプラグインの開発はしたことがなく、一から開発するのはコストが大きかったのですが、既存のプラグインを修正する形であればスムーズに開発ができ、低コストで自分の求めるものが用意できたので良かったです。

おすすめ

コメントを残す

Amazon プライム対象