📷
画像処理100本ノックに挑戦|アフィン変換(平行移動)(028/100)
これはなに?
画像処理100本ノックを、TypeScriptとlibvipsで挑戦してみる記事の28本目です。
前回
実装
お題
アフィン変換を利用して画像をx方向に+30、y方向に-30だけ平行移動させよ。
アフィン変換とは3x3の行列を用いて画像の変換を行う操作である。
Coding
import sharp from 'sharp';
interface AffineParams {
a: number; // scale x
b: number; // shear x
c: number; // shear y
d: number; // scale y
tx: number; // translate x
ty: number; // translate y
}
export async function affineTransform(
inputPath: string,
outputPath: string,
params: AffineParams
): Promise<void> {
try {
// 画像を読み込む
const image = await sharp(inputPath)
.raw()
.toBuffer({ resolveWithObject: true });
const { data, info } = image;
const { width, height, channels } = info;
// アフィン変換後の新しいサイズを計算
const newWidth = Math.round(width * params.a);
const newHeight = Math.round(height * params.d);
// 新しいバッファーを作成(黒で初期化)
const newBuf = Buffer.alloc(newWidth * newHeight * channels);
// アフィン変換の逆行列の係数を計算
const adbc = params.a * params.d - params.b * params.c;
if (Math.abs(adbc) < 1e-10) {
throw new Error('アフィン変換行列が特異です');
}
// 各ピクセルに対して変換を適用
for (let y = 0; y < newHeight; y++) {
for (let x = 0; x < newWidth; x++) {
// 逆変換で元の座標を求める
const srcX = Math.round(
(params.d * x - params.b * y) / adbc - params.tx
);
const srcY = Math.round(
(-params.c * x + params.a * y) / adbc - params.ty
);
// 元画像の範囲内かチェック
if (
srcX >= 0 && srcX < width &&
srcY >= 0 && srcY < height
) {
// 元のピクセル位置と新しいピクセル位置を計算
const srcPos = (srcY * width + srcX) * channels;
const destPos = (y * newWidth + x) * channels;
// ピクセルデータをコピー
for (let c = 0; c < channels; c++) {
newBuf[destPos + c] = data[srcPos + c];
}
}
}
}
// 新しい画像を作成して保存
await sharp(newBuf, {
raw: {
width: newWidth,
height: newHeight,
channels
}
})
.toFile(outputPath);
console.log('アフィン変換が完了しました');
} catch (error) {
console.error('アフィン変換中にエラーが発生しました:', error);
throw error;
}
}
結果
入力 | 出力 |
---|---|
![]() |
![]() |
Discussion