• 117
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

この記事はJavaScript Advent Calendar 2014の12/19(金)の記事になります。

glitchとは

tumblr_mtjr86ECVR1qav3uso9_r1_500.gif
STF_Header.gif
tumblr_m8475z6yfi1qzqnxxo1_500.gif
tumblr_mhlc75gVRs1r94e9jo1_500.gif
このような感じのデジタルノイズエフェクトをglitch(グリッチ)といいます。
電子信号の異常によって引き起こされる映像の乱れの再現処理〜
〜とかなんやかんや。

実際にはこんなglitch起きないよ!というような正しいglitch論もありますが
ここでは触れません。こわい。

今回はこのglitchをjavascriptで実装しようじゃないかと。
うすうす感付かれてるかもしれませんが
そう、あたりです。
見栄えヨロシクでチャラい方のjs案内になります。
どうぞお手柔らかにお願いします。

解説

1. はじめてのglitch

簡単なglitch処理は、
canvas内をgetImageData & putImageDataすることによって可能です。

sample.js
  var ctx = canvas.getContext('2d');
  // 範囲コピー
  var imagedata = ctx.getImageData(sx, sy, sw, sh);
  // 指定位置にペースト
  ctx.putImageData(imagedata, dx, dy [, dirtyX, dirtyY, dirtyWidth, dirtyHeight ]);

ということは、取得先と配置先をずらせばglitchになりますね。
イエーイ完成。

2. image画像やvideo映像もglitchしたい

しかしこのままではimage画像やvideo映像はglitchできません。
可能にするにはCross-Origin Resource Sharing(CORS)をキーワードに調べておく必要があります。

CORS を用いずとも Canvas で画像は扱えますが、この場合 Canvas は汚染 (taint) されてしまいます。Canvas が一度汚染されてしまうと、その Canvas からデータを取り出すことはできなくなります。つまり、toBlob(), toDataURL(), getImageData() などのメソッドが利用できません。利用するとセキュリティエラーが投げられます。

これはこうした制限をかけることによって、外部 Web サイトがユーザのプライベートな情報を画像を利用して許可なしに引き出すことを防いでいるのです。
CORS Enabled Image - HTML | MDN

とあるように、
image画像やvideo映像のload時に下記設定をしておかないとcanvasが汚染されてしまい、glitchできなくなってしまうのです。
実際どうしたらいいのかは、

tips
// Glitch image
var img = document.createElement('img');
img.crossOrigin = 'anonymous';

// Glitch Video
var video = document.createElement('video');
video.crossOrigin = 'anonymous';

といったcrossOriginを設定することで対策できます。
実際に動かしてみたコードは下記リンク先にて。

glitchiken / uriuriuriu on CodePen
Glitch Video / uriuriuriu on CodePen

3. imageData.data編集による複雑なglitch

1.で解説したgetImageData & putImageDataだけでは所詮、範囲コピペなので柔軟な画像処理はできません。
だがしかーし!です。
imageData.dataを編集することにより1px内のRGBA毎に処理が可能となるのです。

2.で紹介したサンプルもimageData.dataの編集をしているので見て行きましょう。

glitcheken.gif
glitchiken / uriuriuriu on CodePen

glitchiken.js
var imageData = ctxOrigine.getImageData(0,0,width,height);
// imageData.dataにはcanvasのwidth * height * 4(RGBA)の配列が入っている。
// [R, G, B, A, 
//  R, G, B, A,
//  ...,
//  ...,
//  R, G, B, A]
var data = imageData.data;
// ★option.factorで設定した数値をmaxとしたrandom値を
var randR = Math.floor(Math.random()*options.factor);
var randG = Math.floor(Math.random()*options.factor)*3;
var randB = Math.floor(Math.random()*options.factor);
for(var i = 0, len = imageData.width * imageData.height; i<len; i++){
  // ★ここで使う。少し先にあるRGB値を取得し、設定。
  data[i*4 + 0] = data[(i + randR)*4 + 0];
  data[i*4 + 1] = data[(i + randG)*4 + 1];
  data[i*4 + 2] = data[(i + randB)*4 + 2];
  data[i*4 + 3] = 255;
}
ctx.putImageData(imageData,0,0);

RGB値を毎フレームランダム値分ずらしてglitch処理を行っています。
これでもう本格的なglitch処理を作成出来ます。

ついでにもう一つサンプル、
こちらはcanvasに書きだした現在時刻に4種類glitchしたものです。

now20.gif
now / uriuriuriu on CodePen

glitch.js
/* 
  :glitchBox
*/
function glitchBox(canvas){
  this.canvas = canvas;
  this.ctx = canvas.getContext('2d');
  this.width = canvas.width;
  this.height = canvas.height;
}
// ずらしてコピペ処理
glitchBox.prototype.glitchWave = function(renderLineHeight, cuttingHeight){
  var image = this.ctx.getImageData(0, renderLineHeight, this.width, cuttingHeight);
  this.ctx.putImageData(image, 0, renderLineHeight - 10);
};
glitchBox.prototype.glitchSlip = function(waveDistance){
  // ...
};
// 色のスリップ処理
glitchBox.prototype.glitchSlipColor = function(waveDistance){
  var startHeight = this.height * Math.random();
  var endHeight = startHeight + 30 + (Math.random() * 40);
  var imageData = this.ctx.getImageData(0, startHeight,
                                        this.width, endHeight);
  var data = imageData.data;
  var r = g = b = 0;
  for(var i = 0, len = imageData.width * imageData.height; i<len; i++){
    if(i % imageData.width ==0){
      r = i + Math.floor((Math.random() -0.5) * waveDistance);
      g = i + Math.floor((Math.random() -0.5) * waveDistance);
      b = i + Math.floor((Math.random() -0.5) * waveDistance);
    }
    data[i*4] = data[r*4];         //r
    data[i*4 + 1] = data[g*4 + 1]; //g
    data[i*4 + 2] = data[b*4 + 2]; //b
//    data[i*4 + 3] = data[a*4 + 3];  //a
  }
  this.ctx.putImageData(imageData, 0, startHeight);
};
glitchBox.prototype.glitchFillRandom = function(fillCnt, cuttingMaxHeight){
  // ...
}

数種類のglitchを順番に実行させると見栄えがいい感じになりますね。
しかし、複数同時に実行させてるタイミングではどうしても重く感じられます。。

4. もっと処理を早くしたい

どうしたらいいのか。
とりあえず毎フレーム走らせる処理を減らしましょう。
canvas描写で変更の無い部分(固定背景など)はgetImageDataで変数保持するなど。
2フレーム以降ペーストのみで済みます。

canvasView.js
canvasView.prototype.drawBG = function(){
  if(this.bgCanvasImage == null){
    // draw Image
    ...

    // save Image
    this.bgCanvasImage = ctx.getImageData(0, 0,cw,ch);
  }
  // put saved image
  this.ctx.putImageData(this.bgCanvasImage, 0,0);
}

5. つっても重くない?

ですよね!
しょうがないです。今までの内容全部忘れてGLSL勉強しましょう!

GLSLはC言語をベースとした高レベルシェーディング言語です。
が、じつはhtmlでも動かせるのです。
ぱない。

それではと思い、three.jsでGLSLを試してみました。

glsl4.gif
three.js's shading selecter / uriuriuriu on CodePen

imageData.dataよりももっと複雑な処理ができて動作も軽そうですよね。

そして蛇足ですが、ようやくiOS8からwebGLに対応したようなのです。
ということはthree.jsが難なく表示できます。
three.jsと同時にGLSL処理をした上記htmlもiPhoneで問題なく動作しました。
ぱない。

また、ちょうどタイミングよくkayacさんから

スクリーンショット 2014-12-19 3.46.36.png
Filters〜世界一面白いカメラフィルターが、ここから生まれる。〜

というGLSLいじってカメラフィルタ作れるiPhoneアプリが出ました。
ですのでみんなGLSLで遊べばいいと思います。
そしていいかんじのGLSL作って教えてください。

tumblr_ktfp8cVy351qznhsso1_250.gif

以上!

この投稿は JavaScript Advent Calendar 201419日目の記事です。