こうこく
作 ▸

CanvasでRGBチャンネルごとに色の位置をずらす加工

タイトルわかりにく!! グリッチっぽいあれです。

元の画像
サンプル1: 赤を→2
サンプル2: 緑を←2, ↑2
サンプル3: 青を←4, ↓3

加工の流れは次の通りです。

  1. 全ピクセル (ImageData.data) をスキャンして、各座標の指定チャンネルの値を取得したあと、現在の値をゼロにします。
  2. ふたたび全ピクセルをスキャンして、移動先座標の指定チャンネルに、取得しておいた値を加算します。

移動先座標を算出するので、2回目のスキャンはxyでやってます。xyの座標は、(y \* 幅 + x) * 4ImageData.data のインデックスに変換できます。

/**
 * 指定チャンネルの描画位置をずらす
 * @param {HTMLCanvasElement} canvas canvas要素
 * @param {number} channel 0: 赤, 1: 緑, 2: 青
 * @param {number} shiftX 右方向移動量 (px)
 * @param {number} shiftY 下方向移動量 (px)
 */
function shiftRGB(canvas, channel, shiftX, shiftY) {
  const ctx = canvas.getContext('2d');
  const w = canvas.width;
  const h = canvas.height;
  const tmp = [];
  const imageData = ctx.getImageData(0, 0, w, h);
  for (let i = 0, len = imageData.data.length; i < len; i += 4) {
    tmp[i] = imageData.data[i + channel];
    imageData.data[i + channel] = 0;
  }
  for (let y = 0; y < h; ++y) {
    for (let x = 0; x < w; ++x) {
      const dstX = x + shiftX;
      const dstY = y + shiftY;
      if (0 <= dstX && dstX < w && 0 <= dstY && dstY < h) {
        const i = (y * w + x) * 4;
        const dstI = (dstY * w + dstX) * 4;
        imageData.data[dstI + channel] = imageData.data[dstI + channel] + tmp[i];
      }
    }
  }
  ctx.putImageData(imageData, 0, 0);
}

以下、ページ上部でやってるサンプルの例です。

HTML
<figure>
	<img id="exsample-org" src="/assets/entry/20171118-rgb-channel-shifting/org.gif" />
	<figcaption>元の画像</figcaption>
</figure>

<div style="display:flex; flex-wrap:wrap;">
	<figure style="flex-grow:1;">
		<canvas id="exsample-1"></canvas>
		<figcaption>サンプル1: 赤を→2</figcaption>
	</figure>
	<figure style="flex-grow:1;">
		<canvas id="exsample-2"></canvas>
		<figcaption>サンプル2: 緑を←2, ↑2</figcaption>
	</figure>
	<figure style="flex-grow:1;">
		<canvas id="exsample-3"></canvas>
		<figcaption>サンプル3: 青を←4, ↓3</figcaption>
	</figure>
</div>
JavaScript
document.getElementById('example-org').addEventListener("load", (event) => {
  /**
   * Canvasをコピー
   * @param {HTMLCanvasElement} src コピー元
   * @param {HTMLCanvasElement} dst コピー先
   */
  function copyCanvas(src, dst) {
    dst.width = src.width;
    dst.height = src.height;
    const ctx = dst.getContext('2d');
    ctx.drawImage(src, 0, 0, dst.width, dst.height);
    return dst;
  }

  const orgImage = event.currentTarget;
  const canvas1 = copyCanvas(orgImage, document.getElementById('example-1'));
  const canvas2 = copyCanvas(orgImage, document.getElementById('example-2'));
  const canvas3 = copyCanvas(orgImage, document.getElementById('example-3'));

  // サンプル1: 赤を→2
  shiftRGB(canvas1, 0, 2, 0);

  // サンプル2: 緑を←2, ↑2
  shiftRGB(canvas2, 1, -2, -2);

  // サンプル3: 青を←4, ↓3
  shiftRGB(canvas3, 2, -4, 3);
});
この記事に何かあればこちらまで (非公開)