この記事はJavaScript Advent Calendar 2014の12/19(金)の記事になります。
glitchとは
このような感じのデジタルノイズエフェクトをglitch(グリッチ)といいます。
電子信号の異常によって引き起こされる映像の乱れの再現処理〜
〜とかなんやかんや。
実際にはこんなglitch起きないよ!というような__正しい__glitch論もありますが
ここでは触れません。こわい。
今回はこのglitchをjavascriptで実装しようじゃないかと。
うすうす感付かれてるかもしれませんが
そう、あたりです。
見栄えヨロシクでチャラい方のjs案内になります。
どうぞお手柔らかにお願いします。
解説
1. はじめてのglitch
簡単なglitch処理は、
canvas内をgetImageData & putImageDataすることによって可能です。
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できなくなってしまうのです。
実際どうしたらいいのかは、
// 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の編集をしているので見て行きましょう。
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したものです。
/*
: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.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を試してみました。
imageData.dataよりももっと複雑な処理ができて動作も軽そうですよね。
そして蛇足ですが、ようやくiOS8からwebGLに対応したようなのです。
ということはthree.jsが難なく表示できます。
three.jsと同時にGLSL処理をした上記htmlもiPhoneで問題なく動作しました。
ぱない。
また、ちょうどタイミングよくkayacさんから
というGLSLいじってカメラフィルタ作れるiPhoneアプリが出ました。
ですのでみんなGLSLで遊べばいいと思います。
そしていいかんじのGLSL作って教えてください。
以上!