HTML5で画像加工処理

HTML5では画面に表示しているピクセルの色を取ってきたり、
セットしたりの操作が簡単にできるようになりました。
ためしに簡単な画像フィルタを二つ作ってみます。


こちらが元画像
http://www.zd.ztv.ne.jp/h64qh2pq/html5/filterset00/PH_1.jpg


ガウスフィルタ

グレースケール


これらは上に貼っている元画像をリアルタイムで処理して表示しています。
ガウスフィルタなどは結構重い処理なので、このままじゃ実用的ではないですが、画像をサーバではなくクライアント側で加工させることができるってのは大いに利点がありそうです。



せっかくなので、フィルタのポイントなど

ガウスフィルタは平滑化と呼ばれる処理を行います。
簡単に言うと、対象のピクセルの色を周囲何ピクセルかの色と合成するって処理なのですが、
ただ単にそのままの色を合成するのではなく、対象ピクセルからの距離に応じて重みをつけて合成していきます。

http://www.zd.ztv.ne.jp/h64qh2pq/html5/filterset00/kaisetsu.png

この距離に応じての重み(近いほど重みを大きく、遠いほど小さくなるような値)をガウス関数を使って計算しています。


グレースケールについて

グレースケールはこちらのサイト(http://www.geocities.co.jp/Milkyway/4171/graphics/002-6.html)を参考にさせていただきました。
ただ単に平均を取るのではなく、各色に重み付けて計算しています。
なんでも「人間の視覚の色に対する感度の違いを考慮する」んだそうです。


出力画素 = (0.299 * R成分 + 0.587 * G成分 + 0.114 * B成分)


なるほど、たったこれだけで、ただ平均するより綺麗に見えますね。すばらしい。



もっと色々なフィルタを作ってみたら面白そうだなーと思いつつも、
こういうのをやってくれるライブラリってもうあるんだろうなと考えると、あんまり気乗りしなくなる……。

どうせライブラリ使ったほうが高速だし綺麗だしで、
自分で書くメリットなんてほとんどありませんもんね……(と元も子もないことを言ってみる(笑))


ソースコードは以下においておきます。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
	"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja">
	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
		<meta http-equiv="Content-Language" content="ja" />
		<meta http-equiv="Content-Style-Type" content="text/css" />
		<meta http-equiv="Content-Script-Type" content="text/javascript" />
		<title>HTML5 Filter</title>
		<!--[if IE]><script type="text/javascript" src="excanvas.compiled.js"></script><![endif]-->
		<script type="text/javascript">
			
			/**
			 * 乱数を取得します。
			 */
			var getRand = function(r) { return parseInt(Math.random()*r); }
			
			/**
			 * inputDataの画像をグレースケール加工します。
			 * @param inputData 入力画像
			 */
			var grayscale = function(inputData, canvasContext)
			{
				// データを入れる空のImageDataを作成
				var outputData = canvasContext.createImageData(inputData.width, inputData.height);
				
				// フィルタ処理
				for ( var y = 0; y < inputData.height; ++y )
				{
					for ( var x = 0; x < inputData.width; ++x )
					{
						var rgb = getPixel( inputData, x, y );
						// グレースケールの計算(重み付き平均をとる方法)
						// 出力画素 = (0.299 * R成分 + 0.587 * G成分 + 0.114 * B成分)
						// 参考:http://www.geocities.co.jp/Milkyway/4171/graphics/002-6.html
						var newcolor = (0.299 * rgb.R) + (0.587 * rgb.G) + (0.114 * rgb.B);
						setPixel(outputData, x, y, newcolor, newcolor, newcolor, 255);
					}
				}
				return outputData;
			}
			
			/**
			 * inputDataの画像をガウスフィルタ加工します。
			 * @param inputData 入力画像
			 */
			var gaussian = function(inputData, canvasContext)
			{
				// データを入れる空のImageDataを作成
				var outputData = canvasContext.createImageData(inputData.width, inputData.height);
				
				var sigma = 2.0;
				var dot = 4;
				var div = 2 * Math.PI * ((sigma)*(sigma));

				var weight = new Array();
				for ( var i = -dot; i <= dot; i++ )
				{
					weight[i + dot] = new Array();
					for ( var j = -dot; j <= dot; j++ )
					{
						weight[i + dot][j + dot] = Math.exp(- ((j*j) + (i*i)) / (2 * ((sigma)*(sigma))) );
					}
				}
				
				// フィルタ処理
				// 参考:http://lovezawa.it-frogs.jp/programming/c/sift/sift↓
				//       %E3%81%AE%E5%AE%9F%E8%A3%851-%E3%82%AC%E3%82%A6%E3%82%B9%E↓
				//       3%83%95%E3%82%A3%E3%83%AB%E3%82%BF/
				var tmpR, tmpG, tmpB;
				for ( var y = 0; y < inputData.height; ++y )
				{
					for ( var x = 0; x < inputData.width; ++x )
					{
						tmpR = tmpG = tmpB = 0;
						for( var yy = -dot; yy <= dot; yy++ )
						{
							for( var xx = -dot; xx <= dot; xx++ )
							{
								var rgb = getPixel( inputData, x + xx, y + yy );
								var w = weight[yy + dot][xx + dot];
								if(typeof(rgb) == "undefined") continue;
								tmpR += rgb.R * w;
								tmpG += rgb.G * w;
								tmpB += rgb.B * w;
							}
						}
						setPixel(outputData, x, y, 
							Math.round(tmpR / div), Math.round(tmpG / div), Math.round(tmpB / div), 
							255);
					}
				}
				return outputData;
			}
			
			function getPixel(imageData, x, y)
			{
				var pixels = imageData.data;
				var index = (imageData.width * y * 4) + (x * 4);
				if(index < 0 || index + 3 > pixels.length) return undefined;
				return { R:pixels[index + 0], G:pixels[index + 1], B:pixels[index + 2], A:pixels[index + 3] };
			}
			
			function setPixel(imageData, x, y, r, g, b, a)
			{
				var pixels = imageData.data;
				var index = (imageData.width * y * 4) + (x * 4);
				if(index < 0 || index + 3 > pixels.length) return false;
				pixels[index + 0] = r;
				pixels[index + 1] = g;
				pixels[index + 2] = b;
				pixels[index + 3] = a;
				return true;
			}
				
			onload = function()
			{
				// canvas要素のノードオブジェクト
				var canvas = document.getElementById('CanvasTest');
				// canvas要素の存在チェックとCanvas未対応ブラウザの対処
				if ( !canvas || !canvas.getContext )
				{
					alert("お使いのブラウザはCanvasに対応していません。");
					return false;
				}
				// 2Dコンテキストの取得
				var canvasContext = canvas.getContext('2d');
				
				// Imageオブジェクトを生成
				var img = new Image();
				img.src = "PH_1.jpg";
				// いったん描画
				canvasContext.drawImage(img, 0, 0);
					
				// キャンバスからImageDataを取得
				var input = canvasContext.getImageData(0, 0, canvas.width, canvas.height);
				
				// パラメータ取得
				var param = window.location.search.substring(1,window.location.search.length);
				
				if( typeof(eval(param)) == "undefined" )
					alert("そんなフィルタないよ!");
				else
				{
					// 変換!
					var output = eval(param)(input, canvasContext);
					// 変換した画像を出力
					canvasContext.putImageData(output, 0, 0);
				}
			};
		</script>
	</head>
	<body>
		<canvas id="CanvasTest" width="240" height="180"></canvas>
	</body>
</html>