【PHP実践】GDライブラリ深掘り:画像アフィン変換の基盤を操るimageaffinematrixget徹底解説

概要

PHPのGDライブラリは、Webアプリケーションにおける画像操作の強力なツールとして広く利用されています。その中でも、画像の幾何学的変換、特にアフィン変換を扱う際に不可欠な関数の一つが`imageaffinematrixget`です。この関数は、回転、拡大縮小、平行移動、傾斜といった基本的な変換要素を指定することで、それらを組み合わせた単一のアフィン変換行列を生成します。生成された行列は`imageaffine`関数に渡され、実際の画像変換処理が実行されます。

本記事では、`imageaffinematrixget`の機能、その背後にあるアフィン変換の数学的原理、そしてPHPのGDライブラリでの具体的な実装方法について深く掘り下げて解説します。単に関数の使い方を覚えるだけでなく、アフィン変換行列がどのように構成され、どのような意味を持つのかを理解することで、より柔軟で高度な画像処理をPHPで実現するための基盤を築くことを目指します。Webサイトでのサムネイル生成、ユーザーアップロード画像の自動修正、動的な画像エフェクトなど、多岐にわたるシナリオで`imageaffinematrixget`の真価を発揮させるための知識と実践的なアドバイスを提供します。

詳細解説

アフィン変換の基礎と行列表現

アフィン変換とは、画像や図形に対して平行移動、回転、拡大縮小、傾斜(せん断)といった操作を組み合わせた幾何学的変換の総称です。この変換の大きな特徴は、直線が直線として、平行な線が平行な線として保持される点にあります。また、変換前後で点の相対的な位置関係が維持されるため、画像の歪みを最小限に抑えつつ柔軟な変形が可能です。

アフィン変換は、数学的には3×3の行列を用いて表現されます。通常、2次元の点 (x, y) を変換する場合、同次座標系を用いることで、平行移動を含む全てのアフィン変換を単一の行列乗算で表現できるようになります。同次座標では、点 (x, y) は (x, y, 1) と表され、変換行列 M は以下の形式を取ります。

$$
M = \begin{pmatrix}
a & b & t_x \\
c & d & t_y \\
0 & 0 & 1
\end{pmatrix}
$$

この行列Mと点P(x, y, 1)を乗算することで、新しい点P'(x’, y’, 1)が得られます。
$$
P’ = P \cdot M \Rightarrow \begin{pmatrix} x’ \\ y’ \\ 1 \end{pmatrix} = \begin{pmatrix} x & y & 1 \end{pmatrix} \cdot \begin{pmatrix} a & b & t_x \\ c & d & t_y \\ 0 & 0 & 1 \end{pmatrix}
$$
(※GDライブラリの内部実装は列ベクトル・行ベクトルどちらで乗算するかによって行列の定義が逆になることがありますが、概念としては同じです。PHPの`imageaffine`関数が期待する形式は、この行列をフラットにした配列です。)

各要素の意味は以下の通りです。

  • `a, b, c, d`: 線形変換(回転、拡大縮小、傾斜)を担当する部分。
  • `t_x, t_y`: 平行移動(translate)を担当する部分。
  • `0, 0, 1`: 同次座標の性質を維持するための定数。

`imageaffinematrixget`関数の役割と引数

`imageaffinematrixget`関数は、指定されたアフィン変換のオプションに基づいて、前述の3×3アフィン変換行列を計算し、PHPの配列として返します。この関数自体は画像に変換を適用するわけではなく、あくまで変換のための行列を生成する役割を担います。

array imageaffinematrixget(array $options)

引数 `$options` は連想配列であり、以下のキーと値を指定できます。これらのオプションは、内部で適切な順序(通常は拡大縮小 -> 回転 -> 傾斜 -> 平行移動)で行列乗算され、最終的な複合変換行列が生成されます。

  • `’scale’` (float または array): 拡大縮小率を指定します。
    • `float`値の場合: X軸とY軸の両方に同じ拡大縮小率が適用されます。例: `1.5`で1.5倍に拡大。
    • `array`の場合: `[float $sx, float $sy]` の形式で、X軸とY軸に異なる拡大縮小率を指定できます。例: `[1.0, 0.5]`でX軸はそのまま、Y軸は0.5倍に縮小。
  • `’translate’` (array): 平行移動量を指定します。
    • `[float $tx, float $ty]` の形式で、X軸とY軸の移動量をピクセル単位で指定します。正の値は右/下への移動、負の値は左/上への移動を意味します。
  • `’rotate’` (float): 回転角度をラジアンで指定します。
    • 正の値は時計回り、負の値は反時計回りの回転です。例えば、90度回転させるには `M_PI / 2` を指定します。
  • `’shearx’` (float): X軸方向の傾斜(せん断)係数を指定します。
    • 傾斜は、X軸方向の各点におけるY座標に比例したX方向のずれを生じさせます。
  • `’sheary’` (float): Y軸方向の傾斜(せん断)係数を指定します。
    • 傾斜は、Y軸方向の各点におけるX座標に比例したY方向のずれを生じさせます。

戻り値は、以下の形式を持つ9要素の配列です。これは、前述の3×3行列をフラットにしたものです。
`[a, b, c, d, tx, ty, 0, 0, 1]`


array(9) {
[0]=> float(a)
[1]=> float(b)
[2]=> float(c)
[3]=> float(d)
[4]=> float(tx)
[5]=> float(ty)
[6]=> float(0)
[7]=> float(0)
[8]=> float(1)
}

この配列が、`imageaffine`関数に渡すことで画像に変換を適用するための「設計図」となります。

各変換要素と行列の対応

それぞれの基本変換がどのように行列に影響するかを具体的に見ていきましょう。

1. **拡大縮小 (Scale)**
X軸方向に $s_x$ 倍、Y軸方向に $s_y$ 倍に拡大縮小する場合の行列は以下のようになります。
$$
S = \begin{pmatrix}
s_x & 0 & 0 \\
0 & s_y & 0 \\
0 & 0 & 1
\end{pmatrix}
$$
`imageaffinematrixget`では `’scale’ => [$sx, $sy]` で指定します。

2. **回転 (Rotate)**
原点を中心に $\theta$ ラジアン回転させる場合の行列は以下のようになります。
$$
R = \begin{pmatrix}
\cos\theta & -\sin\theta & 0 \\
\sin\theta & \cos\theta & 0 \\
0 & 0 & 1
\end{pmatrix}
$$
`imageaffinematrixget`では `’rotate’ => $theta` で指定します。

3. **平行移動 (Translate)**
X軸方向に $t_x$、Y軸方向に $t_y$ 平行移動させる場合の行列は以下のようになります。
$$
T = \begin{pmatrix}
1 & 0 & t_x \\
0 & 1 & t_y \\
0 & 0 & 1
\end{pmatrix}
$$
`imageaffinematrixget`では `’translate’ => [$tx, $ty]` で指定します。

4. **傾斜 (Shear)**
X軸方向の傾斜係数 $sh_x$、Y軸方向の傾斜係数 $sh_y$ で傾斜させる場合の行列はそれぞれ以下のようになります。
$$
Sh_x = \begin{pmatrix}
1 & sh_x & 0 \\
0 & 1 & 0 \\
0 & 0 & 1
\end{pmatrix}
\quad
Sh_y = \begin{pmatrix}
1 & 0 & 0 \\
sh_y & 1 & 0 \\
0 & 0 & 1
\end{pmatrix}
$$
`imageaffinematrixget`では `’shearx’ => $sh_x` または `’sheary’ => $sh_y` で指定します。

`imageaffinematrixget`はこれらの基本行列を、指定されたオプションに基づいて適切な順序で乗算し、最終的な複合変換行列を生成します。この内部的な乗算順序は非常に重要であり、開発者が手動で行列を組み合わせる手間とバグのリスクを軽減してくれます。

サンプルコード

1. 基本的なアフィン変換(回転、拡大縮小、平行移動)

この例では、画像を読み込み、回転、拡大縮小、平行移動を組み合わせて変換し、新しい画像として保存します。


<?php

// 元画像のパス
$sourceImagePath = 'original.jpg';
// 出力画像のパス
$outputImagePath = 'transformed_image.jpg';

// 元の画像が存在するか確認
if (!file_exists($sourceImagePath)) {
    die("Error: Source image '{$sourceImagePath}' not found.");
}

// 画像の読み込み
$image = imagecreatefromjpeg($sourceImagePath);
if (!$image) {
    die("Error: Could not load image '{$sourceImagePath}'.");
}

$imageWidth = imagesx($image);
$imageHeight = imagesy($image);

// 変換オプションを定義
// 90度(時計回り)回転、X軸1.2倍、Y軸0.8倍に拡大縮小、右に50px、下に30px平行移動
$options = [
    'rotate'    => M_PI / 2,     // 90度回転 (ラジアン)
    'scale'     => [1.2, 0.8],   // X軸1.2倍, Y軸0.8倍に拡大縮小
    'translate' => [50, 30]      // X軸+50px, Y軸+30px 平行移動
];

// アフィン変換行列を取得
$matrix = imageaffinematrixget($options);

// 変換後の画像のサイズを計算 (オプション)
// imageaffineは自動的にキャンバスサイズを調整しますが、
// 明示的に制御したい場合は事前に計算が必要です。
// ここではimageaffineに任せるため、新しいイメージは作成しません。

// アフィン変換を適用
// imageaffineの第2引数は変換行列、第3引数は背景色(透明にする場合は -1)
$transformedImage = imageaffine($image, $matrix, imagecolorallocatealpha($image, 0, 0, 0, 127)); // 半透明の黒背景

if (!$transformedImage) {
    die("Error: imageaffine failed.");
}

// 変換後の画像をJPEGとして保存
imagejpeg($transformedImage, $outputImagePath, 90); // 品質90

// メモリを解放
imagedestroy($image);
imagedestroy($transformedImage);

echo "Image successfully transformed and saved as '{$outputImagePath}'.\n";

?>

このコードを実行する前に、スクリプトと同じディレクトリに `original.jpg` という画像ファイルを配置してください。

2. 画像の中心を基準に回転させる

デフォルトでは、アフィン変換は画像の左上(原点)を基準に行われます。画像を中央を基準に回転させたい場合、以下のステップを踏む必要があります。

タイトルとURLをコピーしました