📷

「ImageMagick × PHP」で実装!背景画像の中央にテキストを描画したカバー画像を自動生成するコードサンプル

2022/09/10に公開

概要

ImageMagickとPHPを使用して、背景画像の中央にテキストを描画したカバー画像を自動生成するコードを作成しました。画像やフォントやフォントサイズなどによって多少動きが変わってくる可能性があるので都度調整してください。

実装内容

以下が実装の全体像です。

/**
 * カバー画像のパスを取得する
 *
 * @access private
 * @param string $text text
 * @return string
 */
private function getCoverPath(string $text)
{
    $filename = 'filename.png';
    $backgroundImage = 'background.png';

    try {
        $coverPath = $this->generateCoverPath($filename, $text, $backgroundImage);
    } catch (\Exception $e) {
        return;
    } finally {
        if (file_exists($filename)) {
            unlink($filename);
        }
    }

    return $coverPath;
}

/**
 * カバー画像を自動生成しパスを返す
 *
 * @access private
 * @param string $filename image file name
 * @param string $text text
 * @param string $backgroundImage background image path
 * @return string
 */
private function generateCoverPath(string $filename, string $text, string $backgroundImage)
{
    // テキストを描画する画像
    $image = new \Imagick($backgroundImage);

    // テキストの情報
    $font = 'yugothib.ttf';
    $fontSize = 28;
    $lineHeight = 46;

    // テキスト情報を設定する
    $draw = new \ImagickDraw();
    $draw->setFont($font);
    $draw->setFontSize($fontSize);
    $draw->setFillColor("#FFFFFF");

    // 指定のテキスト幅を超える場合は文字を改行描画する
    $textWidth = $image->getImageWidth() - 90 * 2;
    $lines = $this->getTextRows($draw, $image, $text, $textWidth);
    $coordinate = $this->getCoordinateInfo($lines, $draw, $image, $textWidth);

    foreach ($lines as $index => $line) {
        $y = $coordinate['y'] + $lineHeight * $index;
        $draw->annotation($coordinate['x'], $y, $line);
    }

    $image->drawImage($draw);
    $image->writeImage(TMP . $filename);

    $image->clear();
    $image->destroy();

    return $result['path'];
}

/**
 * カバー画像のテキスト描画幅に合うようにテキストを分割して返却する
 *
 * @access private
 * @param \ImagickDraw $draw draw  object
 * @param \Imagick $image image object
 * @param string $text text
 * @param int $maxWidth line's width
 * @return array
 */
private function getTextRows(\ImagickDraw $draw, \Imagick $image, string $text, int $maxWidth)
{
    $lines = [];

    // whileの処理で$lineに1行分の文字列が格納される
    $line = '';
    while ($text) {
        // $rowに先頭1文字加えた描画幅を取得
        $letter = mb_substr($text, 0, 1);
        // 行の幅を取得
        $lineWidth = $image->queryFontMetrics($draw, $line . $letter)['textWidth'];

        // 1行の幅がテキストの幅を超えたら次の行に折り返す
        if ($lineWidth > $maxWidth) {
            $lines[] = $line;
            // 行リセット
            $line = '';
        } else {
            // $lineに1文字ずつ追加していく
            $line = $line . $letter;
            // $textの先頭一文字を削除する
            $text = mb_substr($text, 1);
        }

        // $textが最後まで到達したら終了
        if (strlen($text) == 0) {
            $lines[] = $line;
            break;
        }
    }

    return $lines;
}

/**
 * テキストを描画する座標情報を返却する
 *
 * @access private
 * @param array $lines Text lines
 * @param \ImagickDraw $draw draw  object
 * @param \Imagick $image image object
 * @param int $maxWidth line's width
 * @return array
 */
private function getCoordinateInfo(array $lines, \ImagickDraw $draw, \Imagick $image, int $maxWidth)
{
    // テキストが1行だった場合はx軸の座標を調整する
    if (count($lines) === 1) {
        $lineWidth = $image->queryFontMetrics($draw, $lines[0])['textWidth'];
        $x = 100 + ($maxWidth - $lineWidth) / 2;
    } else {
        $x = 100;
    }

    switch (count($lines)) {
        case 1:
            $y = 140;
            break;
        case 2:
            $y = 110;
            break;
        case 3:
            $y = 80;
            break;
        default:
            $y = 60;
    }

    return [
        'x' => $x,
        'y' => $y,
    ];
}

実装詳細

カバー画像を取得する

getCoverPathは引数に描画したいテキストを渡すとカバー画像のパスが返ってくるメソッドです。

private function getCoverPath(string $text)
{
    $filename = 'filename.png';
    $backgroundImage = 'background.png';

    try {
        $coverPath = $this->generateCoverPath($filename, $text, $backgroundImage);
    } catch (\Exception $e) {
        return;
    }

    return $coverPath;
}

変数

  • $filename:ファイル名を代入する
  • $backgroundImage:背景画像として使いたい画像のパスを代入する
  • $coverPath:自動生成されたカバー画像のパスが代入される

カバー画像を自動生成する

generateCoverPathは引数にファイル名、描画したいテキスト、背景画像を渡すとカバー画像を自動生成するメソッドです。

private function generateCoverPath(string $filename, string $text, string $backgroundImage)
{
    // テキストを描画する画像
    $image = new \Imagick($backgroundImage);

    // テキストの情報
    $font = 'yugothib.ttf';
    $fontSize = 28;
    $lineHeight = 46;

    // テキスト情報を設定する
    $draw = new \ImagickDraw();
    $draw->setFont($font);
    $draw->setFontSize($fontSize);
    $draw->setFillColor("#FFFFFF");

    // 指定のテキスト幅を超える場合は文字を改行描画する
    $textWidth = $image->getImageWidth() - 90 * 2;
    $lines = $this->getTextRows($draw, $image, $text, $textWidth);
    $coordinate = $this->getCoordinateInfo($lines, $draw, $image, $textWidth);

    foreach ($lines as $index => $line) {
        $y = $coordinate['y'] + $lineHeight * $index;
        $draw->annotation($coordinate['x'], $y, $line);
    }

    $image->drawImage($draw);
    $image->writeImage(TMP . $filename);

    $image->clear();
    $image->destroy();

    return TMP . $filename;
}

変数

  • $font:フォントを代入する
  • $fontSize:フォントサイズを代入する。今回は28pxを指定
  • $lineHeight:行ボックスの高さを代入する。今回は46pxを設定
  • $draw:ImagickDrawオブジェクトを代入する
  • $textWidth:描画したいテキストの幅を代入する。今回は背景画像から左右90pxのpaddingを取りたかったため90*2を差し引いた値を設定
  • $lines:テキストを描画幅に収まるように分割した結果の配列が代入される
  • $coordinate:座標のx軸とy軸が代入される

Imagickのクラスメソッド

テキストを描画幅に収まるように分割する

getTextRowsは引数にImagickDrawオブジェクト、Imagickオブジェクト、描画したいテキスト、テキストの幅を渡すと、テキストを描画幅に収まるように分割してくれるメソッドです。

private function getTextRows(\ImagickDraw $draw, \Imagick $image, string $text, int $maxWidth)
{
    $lines = [];

    // whileの処理で$lineに1行分の文字列が格納される
    $line = '';
    while ($text) {
        // $rowに先頭1文字加えた描画幅を取得
        $letter = mb_substr($text, 0, 1);
        // 行の幅を取得
        $lineWidth = $image->queryFontMetrics($draw, $line . $letter)['textWidth'];

        // 1行の幅がテキストの幅を超えたら次の行に折り返す
        if ($lineWidth > $maxWidth) {
            $lines[] = $line;
            // 行リセット
            $line = '';
        } else {
            // $lineに1文字ずつ追加していく
            $line = $line . $letter;
            // $textの先頭一文字を削除する
            $text = mb_substr($text, 1);
        }

        // $textが最後まで到達したら終了
        if (strlen($text) == 0) {
            $lines[] = $line;
            break;
        }
    }

    return $lines;
}

変数

  • $lines:描画幅に合わせて分割したテキストが配列で代入される
  • $line:描画幅に合わせて分割したテキストが代入される
  • $letter:while文でテキストの文字を1文字ずつ代入する
  • $lineWidth:現在のテキストの長さが代入される

Imagickのクラスメソッド

テキストを描画する座標情報を返却する

getCoordinateInfoは引数にImagickDrawオブジェクト、Imagickオブジェクト、描画したいテキスト、テキストの幅を渡すと、テキストを描画する座標を返却してくれるメソッドです。

private function getCoordinateInfo(array $lines, \ImagickDraw $draw, \Imagick $image, int $maxWidth)
{
    // テキストが1行だった場合はx軸の座標を調整する
    if (count($lines) === 1) {
        $lineWidth = $image->queryFontMetrics($draw, $lines[0])['textWidth'];
        $x = 100 + ($maxWidth - $lineWidth) / 2;
    } else {
        $x = 100;
    }

    switch (count($lines)) {
        case 1:
            $y = 140;
            break;
        case 2:
            $y = 110;
            break;
        case 3:
            $y = 80;
            break;
        default:
            $y = 60;
    }

    return [
        'x' => $x,
        'y' => $y,
    ];
}

変数

  • $x:テキストを描画するx座標を代入する。今回は画像の左上を起点として100pxの位置をベースとしている。1行の場合はテキストを中央よせするために位置調整を行なっている
  • $y:テキストを描画するy座標を代入する。画像の左上を起点としているところはx軸と同じだが、テキストの行数によって座標が変わってくる。今回は1行、2行、3行のパターンで分けている

Imagickのクラスメソッド

参考

Discussion