Bannerify
記事一覧

PHP で API を使って動的な画像を生成する

テンプレートを 1 つデザインすれば、バナー、商品画像、証明書を PHP から生成できます。cURL でも bannerify/bannerify SDK でも使え、GD や Imagick は不要です。

· Duc An

PHP の画像処理コードは、ほとんどがレイアウトのコードです。GD や Imagick では、デザイン上の判断がすべて関数呼び出しになります。

$image = imagecreatetruecolor(1200, 630);
$blue = imagecolorallocate($image, 37, 99, 235);
imagefilledrectangle($image, 0, 0, 1200, 630, $blue);
imagettftext($image, 48, 0, 80, 300, $white, 'Inter-Bold.ttf', 'Summer sale: 30% off');

最初のうちは問題なく動きます。ところが、見出しを 2 行にしたい、価格に影をつけたい、隅に QR コードを置きたいと言われた時点で、デザインが PHP の中に入り込んでしまいます。細かな調整のたびにコードを変更してデプロイすることになります。

「自分で描く」と「ヘッドレスブラウザを大量に動かす」の間には、もうひとつ選択肢があります。レイアウトはテンプレートに置いておき、PHP からは画像生成 APIにデータだけを送る方法です。

API を使った場合の流れ

  1. エディタでバナーを一度デザインします。ギャラリーのテンプレートを複製してもかまいません。
  2. あとで差し替えたいレイヤーに、headline、price、photo のような名前をつけます。
  3. テンプレート ID と値を API に送ります。レスポンスが完成した画像です。

フォント、余白、グラデーション、影、テーブル、QR コードはテンプレート側に残ります。デザイナーは PHP に触れずにこれらを変更でき、PHP はデータの受け渡しという得意な仕事に専念できます。

必要なもの

  • API キー。 ダッシュボード → APIキー で作成し、サーバー上で管理します。データベースのパスワードと同じように扱ってください。
  • テンプレート ID。 デザインしたテンプレートを開いて、その ID をコピーします。tpl_kd93mz のような形です。
  • レイヤー名。 エディタでレイヤーにつけた名前が、そのままリクエストで使う名前になります。大文字小文字も含めて一致させてください。

方法 1:素の cURL

依存パッケージは不要で、どの PHP ホスティングでも動きます。拡張機能のインストールに苦労する環境でも使えます。

<?php

$payload = [
    'apiKey' => getenv('BANNERIFY_API_KEY'),
    'templateId' => 'tpl_kd93mz',
    'format' => 'png',
    'modifications' => [
        ['name' => 'headline', 'text' => 'Summer sale: 30% off'],
        ['name' => 'price', 'text' => '$149.00'],
        ['name' => 'photo', 'src' => 'https://example.com/sneaker.jpg'],
    ],
];

$ch = curl_init('https://api.bannerify.co/v1/templates/createImage');

curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
    CURLOPT_POSTFIELDS => json_encode($payload),
    CURLOPT_TIMEOUT => 30,
]);

$image = curl_exec($ch);

if ($image === false) {
    throw new RuntimeException('Transport error: ' . curl_error($ch));
}

$status = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($status !== 200) {
    throw new RuntimeException("Bannerify returned {$status}: " . substr($image, 0, 200));
}

file_put_contents(__DIR__ . '/banner.png', $image);

つまずきやすい点が 2 つあります。

  • レスポンスの本文は画像そのものです。 base64 が入った JSON ではありません。そのままファイルに書き込むか、ブラウザにストリーミングしてください。
  • エラーは 4xx ステータスの JSON で返ります。 上のコードが保存の前にステータスコードを確認しているのは、そのためです。

方法 2:公式の PHP SDK

リクエストを自分で組み立てたくない場合は、SDK をインストールします。

composer require bannerify/bannerify

SDK は result か error のどちらか一方だけを返します。失敗時の処理を書き忘れる心配がありません。

<?php

use Bannerify\Bannerify\BannerifyClient;

$client = new BannerifyClient(getenv('BANNERIFY_API_KEY'));

$result = $client->createImage('tpl_kd93mz', [
    'modifications' => [
        ['name' => 'headline', 'text' => $product['name']],
        ['name' => 'price', 'text' => '$' . number_format($product['price'], 2)],
        ['name' => 'photo', 'src' => $product['image_url']],
    ],
]);

if (isset($result['error'])) {
    error_log('Render failed: ' . $result['error']['message']);

    return null;
}

$path = __DIR__ . "/banners/{$product['sku']}.png";
file_put_contents($path, $result['result']);

メソッドの一覧とオプションは、PHP SDK のドキュメントにあります。

実際のデータを送る

modifications の配列を手で組み立てる必要はありません。API は、キーをレイヤー名にしたオブジェクト形式の省略記法も受け付けます。

'modifications' => [
    'headline' => $product['name'],
    'price' => '$' . number_format($product['price'], 2),
    'photo' => ['src' => $product['image_url']],
    'qr' => ['qrcode' => $product['checkout_url']],
],

単純な値はテキストとして扱われます。配列をネストすると、それぞれのフィールドがそのまま渡されます。画像、QR コード、バーコード、グラフ、テーブルにはこの形を使います。

レイヤーごとに送れるフィールドは次のとおりです。

  • text はテキストレイヤーの内容を置き換えます
  • color はレイヤーの色を上書きします(例:#FF0000)
  • src は画像レイヤーを差し替えます。外部から取得できる HTTPS の URL を指定します
  • qrcode と barcode は、リンク、SKU、追跡番号をエンコードします
  • rows と columns はグラフとテーブルにデータを入れます
  • visible は、その 1 回の生成に限ってレイヤーを非表示にします
  • star は評価の値を設定します

1 つのレイヤーには 1 つのフィールドを、名前を指定して送ります。同じデータを 2 つのレイヤーに入れたい場合は、2 回送ってください。

バッチ処理とキャッシュ

1,000 枚の画像を生成するのも、特別なことではなく、ただのループです。

foreach ($products as $product) {
    $payload = buildPayload($product);
    $cacheKey = hash('xxh128', json_encode($payload));
    $path = __DIR__ . "/cache/{$cacheKey}.png";

    if (file_exists($path)) {
        continue;
    }

    file_put_contents($path, renderBanner($payload));
}

ループは上限を決め(同時リクエストは数件まで)、キャッシュのキーにはペイロードを使います。そうすれば、同じリクエストに二度支払うことはありません。生成は同期的に行われるので、ジョブキューをポーリングせず、同じリクエストの中でファイルを受け取れます。

ファイルをまったく保存したくない場合は、代わりに createStoredImage を使います。生成した画像をアップロードして、ホスティングされた URL を返します。

{ "url": "https://cdn.bannerify.co/..." }

OG画像、メールのヘッダー、そのほかファイルより URL のほうが扱いやすい場面では、この方法が便利です。

フォーマットとサイズ

  • format には png(デフォルト)、jpeg、webp を指定できます。
  • PDF が必要な場合は、同じテンプレートを createPdf で PDF にできます。詳しくは PDF 生成 API のページを、PHP のコードは請求書 PDF のガイドをご覧ください。
  • キャンバスのサイズはリクエストではなくテンプレートで決まります。必要なサイズごとにテンプレートを 1 つ用意してください。リンクプレビューなら 1200x630、フィードなら 1080x1080、ストーリーズなら 1080x1920 です。
  • 写真を src で送る場合、レンダラーがその URL を取得しに行くため、公開されている必要があります。ローカルのファイルやプライベートなバケットでは失敗します。

よくある間違い

  • API キーをブラウザの JavaScript やモバイルアプリに入れてしまう。API キーはサーバーに置いてください。
  • 何も変わっていないのに画像を作り直す。ペイロードのハッシュでキャッシュしてください。
  • 1 回の Web リクエストの中で数千枚の画像を生成する。数十枚なら動きますが、数千枚ならキューを使ってください。
  • レスポンスが JSON だと思い込む。stored エンドポイントを使う場合を除き、レスポンスはファイルそのものです。

料金

無料プランには、月 100 回(1 日 10 回まで)の生成、テンプレート 1 つ、1GB の画像保存容量が含まれています。実際のアプリに組み込んで試すには十分です。Automate は月 $29 から始まり、3,000 回の生成とテンプレート無制限が含まれます。詳しくは料金をご覧ください。

いま PHP で画像を生成しているなら、無料プランで始めて、まず 1 つのテンプレートを GD から移してみてください。レイアウトが崩れずに最初の画像が返ってきたとき、多くの人は位置調整のコードを書くのをやめます。

最初の 100 枚は無料です。

テンプレートを作り、API キーを取得すれば、数分で最初の画像を生成できます。