📷
画像処理100本ノックに挑戦|アフィン変換(拡大縮小)(029/100)
これはなに?
画像処理100本ノックを、TypeScriptとlibvipsで挑戦してみる記事の29本目です。
前回
実装
お題
アフィン変換を用いて、(1)x方向に1.3倍、y方向に0.8倍にリサイズせよ。
また、(2) (1)の条件に加えて、x方向に+30、y方向に-30だけ平行移動を同時に実現せよ。
Coding
imageProcessor.ts
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;
}
}
index.ts
import * as path from 'path';
import { affineTransform } from './imageProcessor';
async function main() {
// __dirnameを使用して相対パスを解決
const inputPath = path.join(__dirname, '../imori.jpg');
const outputPath1 = path.join(__dirname, '../output_scale.jpg');
const outputPath2 = path.join(__dirname, '../output_scale_translate.jpg');
try {
console.log('画像処理を開始します...');
// Case 1: 拡大縮小のみ
// x方向に1.3倍、y方向に0.8倍
await affineTransform(inputPath, outputPath1, {
a: 1.3, // x方向の拡大率
b: 0, // xのシアー
c: 0, // yのシアー
d: 0.8, // y方向の縮小率
tx: 0, // x方向の移動なし
ty: 0 // y方向の移動なし
});
console.log('拡大縮小の処理が完了しました');
// Case 2: 拡大縮小 + 平行移動
// x方向に1.3倍かつ+30移動、y方向に0.8倍かつ-30移動
await affineTransform(inputPath, outputPath2, {
a: 1.3, // x方向の拡大率
b: 0, // xのシアー
c: 0, // yのシアー
d: 0.8, // y方向の縮小率
tx: 30, // x方向の移動量
ty: -30 // y方向の移動量
});
console.log('拡大縮小と平行移動の処理が完了しました');
} catch (error) {
console.error('プログラムの実行に失敗しました:', error);
}
}
main();
結果
入力 | 出力(リサイズ) | 出力(平行移動) |
---|---|---|
![]() |
![]() |
![]() |
Discussion