📷

画像処理100本ノックに挑戦|Bi-linear補間(026/100)

2025/01/28に公開

これはなに?

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

前回

https://zenn.dev/nyagato_00/articles/8bb967fe63fd6e

実装

お題

Bi-linear補間により画像を1.5倍に拡大せよ。

Bi-linear補間とは周辺の4画素に距離に応じた重みをつけることで補完する手法である。 計算量が多いだけ処理時間がかかるが、画質の劣化を抑えることができる。

https://github.com/minido/Gasyori100knock-1/tree/master/Question_21_30#q26-bi-linear補間

Coding

import sharp from 'sharp';

export async function bilinearInterpolation(
  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 = x / scaleX;
        const srcY = y / scaleY;

        // 四隅の座標を計算
        const x1 = Math.floor(srcX);
        const y1 = Math.floor(srcY);
        const x2 = Math.min(x1 + 1, width - 1);
        const y2 = Math.min(y1 + 1, height - 1);

        // 距離による重みを計算
        const dx = srcX - x1;
        const dy = srcY - y1;

        // 各チャネルについて補間を実行
        for (let c = 0; c < channels; c++) {
          // 四隅のピクセル値を取得
          const pos11 = (y1 * width + x1) * channels + c;
          const pos12 = (y1 * width + x2) * channels + c;
          const pos21 = (y2 * width + x1) * channels + c;
          const pos22 = (y2 * width + x2) * channels + c;

          const f11 = data[pos11];
          const f12 = data[pos12];
          const f21 = data[pos21];
          const f22 = data[pos22];

          // バイリニア補間の計算
          const value = (1 - dx) * (1 - dy) * f11 +
                       dx * (1 - dy) * f12 +
                       (1 - dx) * dy * f21 +
                       dx * dy * f22;

          // 結果を保存
          const newPos = (y * newWidth + x) * channels + c;
          newData[newPos] = Math.min(255, Math.max(0, Math.round(value)));
        }
      }
    }

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

    console.log('バイリニア補間による拡大が完了しました');
    console.log(`元のサイズ: ${width}x${height}`);
    console.log(`拡大後のサイズ: ${newWidth}x${newHeight}`);

  } catch (error) {
    console.error('画像処理中にエラーが発生しました:', error);
    throw error;
  }
}

結果

入力 出力

Discussion