📷
「ImageMagick × PHP」で実装!背景画像の中央にテキストを描画したカバー画像を自動生成するコードサンプル
概要
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のクラスメソッド
- ImagickDraw::setFont:フォントを設定(https://www.php.net/manual/ja/imagickdraw.setfont.php)
- ImagickDraw::setFontSize:描画するテキストのフォントのポイント数を設定(https://www.php.net/manual/ja/imagickdraw.setfontsize.php)
- ImagickDraw::setFillColor:描画するテキストのカラーを設定(https://www.php.net/manual/ja/imagickdraw.setfillcolor.php)
- Imagick::getImageWidth:画像の幅を返す(https://www.php.net/manual/ja/imagick.getimagewidth.php)
- ImagickDraw::annotation:画像にテキストを描画するメソッド。第一引数にテキストを描画するx座標、第二引数にテキストを描画するy座標、第三引数にテキストを渡す(https://www.php.net/manual/ja/imagickdraw.annotation.php)
- Imagick::drawImage:現在の画像上のImagickDrawオブジェクトをレンダリングする(https://www.php.net/manual/ja/imagick.drawimage.php)
- Imagick::writeImage:指定した名前で画像を書き込む(https://www.php.net/manual/ja/imagick.writeimage.php)
- Imagick::clear:Imagickオブジェクトに関連付けられたすべてのリソースをクリアする(https://www.php.net/manual/ja/imagick.clear.php)
- Imagick::destroy:Imagickオブジェクトを破棄する(https://www.php.net/manual/ja/imagick.destroy.php)
テキストを描画幅に収まるように分割する
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のクラスメソッド
- Imagick::queryFontMetrics:フォントメトリクスを表す配列を返す。今回はその中で
textWidth
のみを使用する(https://www.php.net/manual/ja/imagick.queryfontmetrics.php)
テキストを描画する座標情報を返却する
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のクラスメソッド
- Imagick::queryFontMetrics:フォントメトリクスを表す配列を返す。今回はその中で
textWidth
のみを使用する(https://www.php.net/manual/ja/imagick.queryfontmetrics.php)
参考
- https://www.php.net/manual/ja/language.exceptions.php
- https://www.php.net/manual/ja/imagickdraw.setfont.php
- https://www.php.net/manual/ja/imagickdraw.setfontsize.php
- https://www.php.net/manual/ja/imagickdraw.setfillcolor.php
- https://www.php.net/manual/ja/imagick.getimagewidth.php
- https://www.php.net/manual/ja/imagickdraw.annotation.php
- https://www.php.net/manual/ja/imagick.drawimage.php
- https://www.php.net/manual/ja/imagick.writeimage.php
- https://www.php.net/manual/ja/imagick.clear.php
- https://www.php.net/manual/ja/imagick.destroy.php
- https://www.php.net/manual/ja/imagick.queryfontmetrics.php
Discussion