みさご解体新書

モーションブラー

実行例

ソースコード

TypeScript

0.jpg / app.ts

解説/アルゴリズム

あるピクセルに注目して、そのピクセルと斜めにあるピクセルの平均値を RGB ごとに算出する。

算出した RGB ごとの平均値を、別に用意した新しい画像の同じ位置にピクセルとして代入する。

同じ画像のピクセルに計算結果を上書きすると、別のピクセルで平均値を算出する際に、もとのピクセルの値ではなく計算済みの平均値を取得してしまう場合がでてくるため、新しいピクセルに計算結果を入れる必要がある。

斜めの範囲を広げると、その分大きくぶれたブラーができる。

範囲計算の際にインデックスがはみ出る場合はその部分の計算は行わない。

上記だと、 (171 + 180) / 2 ≒ 175 を新しい画像のピクセルに代入する。

コード例

import * as p5 from "p5";

new p5((p: p5) => {
  let image: p5.Image;

  // ブラーの範囲
  const range = 5;

  p.preload = () => {
    image = p.loadImage("./0.jpg");
  };

  p.setup = () => {
    p.createCanvas(p.windowWidth, p.windowHeight);

    // モーションブラー出力用の新しい画像を用意する
    const dest = p.createImage(image.width, image.height);
    dest.copy(
      image,
      0,
      0,
      image.width,
      image.height,
      0,
      0,
      image.width,
      image.height
    );
    dest.loadPixels();

    for (let y = 0; y < image.height; y++) {
      for (let x = 0; x < image.width; x++) {
        // RGBそれぞれの合計
        let tr = 0;
        let tg = 0;
        let tb = 0;

        // 対象となるピクセル数
        let count = 0;

        // 左上から右下、range分だけ斜めに走査
        for (let i = -range; i <= range; i++) {
          const ty = y + i;
          const tx = x + i;
          if (0 < ty && ty < image.height && 0 < tx && tx < image.width) {
            const color = getPixel(image, tx, ty);
            const r = p.red(color);
            const g = p.green(color);
            const b = p.blue(color);
            tr += r;
            tg += g;
            tb += b;
            count++;
          }
        }

        // RGBごとに平均を取る
        tr /= count;
        tg /= count;
        tb /= count;

        // 平均の色で斜めに走査したピクセルを塗りつぶす
        for (let yy = 0; yy < range; yy++) {
          for (let xx = 0; xx < range; xx++) {
            const ty = y + yy;
            const tx = x + xx;
            if (ty < image.height && tx < image.width) {
              setPixel(dest, tx, ty, [tr, tg, tb]);
            }
          }
        }
      }
    }

    dest.updatePixels();
    p.image(dest, 0, 0);
  };

  function getPixel(image: p5.Image, x: number, y: number) {
    const i = (y * image.width + x) * 4;
    return [
      image.pixels[i],
      image.pixels[i + 1],
      image.pixels[i + 2],
      image.pixels[i + 3],
    ];
  }

  function setPixel(image: p5.Image, x: number, y: number, color: number[]) {
    const i = (y * image.width + x) * 4;
    image.pixels[i + 0] = color[0];
    image.pixels[i + 1] = color[1];
    image.pixels[i + 2] = color[2];
  }
});