📷

画像処理100本ノックに挑戦|最近傍補間(025/100)

2025/01/27に公開

これはなに?

画像処理100本ノックを、TypeScriptとlibvipsで挑戦してみる記事の25本目です。

前回

https://zenn.dev/nyagato_00/articles/957f04f14fab61

実装

お題

最近傍補間により画像を1.5倍に拡大せよ。

最近傍補間(Nearest Neighbor)は画像の拡大時に最近傍にある画素をそのまま使う手法である。 シンプルで処理速度が速いが、画質の劣化は著しい。

次式で補間される。 I' ... 拡大後の画像、 I ... 拡大前の画像、a ... 拡大率、[ ] ... 四捨五入
https://github.com/minido/Gasyori100knock-1/tree/master/Question_21_30#q24-ガンマ補正

Coding

import sharp from 'sharp';

export async function nearestNeighbor(
  inputPath: string,
  outputPath: string,
  scaleX: number = 1.5,
  scaleY: number = 1.5
): 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 * scaleX);
    const newHeight = Math.round(height * scaleY);
    const newData = Buffer.alloc(newWidth * newHeight * channels);

    // 各ピクセルについて最近傍を計算
    for (let y = 0; y < newHeight; y++) {
      for (let x = 0; x < newWidth; x++) {
        // 元の座標を計算
        const srcX = Math.round(x / scaleX);
        const srcY = Math.round(y / scaleY);

        // 範囲内に収める
        const origX = Math.min(width - 1, Math.max(0, srcX));
        const origY = Math.min(height - 1, Math.max(0, srcY));

        // 各チャネルをコピー
        for (let c = 0; c < channels; c++) {
          const origPos = (origY * width + origX) * channels + c;
          const newPos = (y * newWidth + x) * channels + c;
          newData[newPos] = data[origPos];
        }
      }
    }

    await sharp(newData, {
      raw: {
        width: newWidth,
        height: newHeight,
        channels
      }
    })
    .toFile(outputPath);

    console.log('最近傍補間による拡大が完了しました');
  } catch (error) {
    console.error('画像処理中にエラーが発生しました:', error);
    throw error;
  }
}

結果

入力 出力

Discussion