静岡理工科大学 菅沼ホーム JavaScript 目次 基礎技術目次 索引

ビットマップ,フィルタ,外部画像

  1. ビットマップ

      図を描く方法として,「 DOM 及び CANVAS 関係の JavaScript のプロパティとメソッド」における「画像の描画とピクセル操作」を利用し,ピクセル単位に色を指定することによって描く方法があります.この例において,左側の矩形は fillRect メソッドを,また,右側の矩形は createImageData メソッドによってイメージを作成し,putImageData メソッドによってその結果を描いています.

    01	<!DOCTYPE HTML>
    02	<HTML>
    03	<HEAD>
    04		<TITLE>ビットマップ</TITLE>
    05		<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=utf-8">
    06		<LINK REL="stylesheet" TYPE="text/css" HREF="../../master.css">
    07		<SCRIPT TYPE="text/javascript">
    08			function draw() {
    09				var canvas    = document.getElementById('canvas_e');
    10				canvas.width  = 270;   // キャンバス要素の幅
    11				canvas.height = 130;   // キャンバス要素の高さ
    12				var ctx       = canvas.getContext('2d');
    13						// 塗りつぶした矩形
    14				ctx.beginPath();
    15				ctx.fillStyle = "rgb(0, 255, 0)";
    16				ctx.fillRect(20, 15, 100, 100);
    17				ctx.fill();
    18						// ピクセル操作による矩形
    19				var width = 100;
    20				var height = 100;
    21				var img = ctx.createImageData(width, height);
    22				for (var i1 = 0; i1 < height; i1++) {
    23					for (var i2 = 0; i2 < width; i2++) {
    24						img.data[(i1 * width + i2) * 4] = 0;   // 赤
    25						img.data[(i1 * width + i2) * 4 + 1] = 255;   // 緑
    26						img.data[(i1 * width + i2) * 4 + 2] = 0;   // 青
    27						img.data[(i1 * width + i2) * 4 + 3] = 255;   // 透明度(不透明)
    28					}
    29				}
    30				ctx.putImageData(img, 150, 15);
    31			}
    32		</SCRIPT>
    33	</HEAD>
    34	<BODY CLASS="white" STYLE="text-align: center" onLoad="draw()">
    35		<H1>ビットマップ</H1>
    36		<CANVAS ID="canvas_e" STYLE="background-color: #eeffee;" WIDTH="270" HEIGHT="130"></CANVAS>
    37	</BODY>
    38	</HTML>
    			

    19 行目~ 21 行目

      幅 100 ピクセル,高さ 100 ピクセルのイメージを保存する ImageData オブジェクト img を生成しています.

    22,23 行目

      変数 i1 と i2 は,ここで( for 文の中で),var を付加して宣言されています.そのため,22 行目で宣言された変数 i1 の有効範囲は対応する for ブロック( 22 行目~ 29 行目)の中だけ,さらに,23 行目で宣言された変数 i2 の有効範囲は対応する for ブロック( 23 行目~ 28 行目)の中だけになります.同様に,関数 draw 内で var を付加して宣言されている変数は,定義された以降で,かつ,この関数内だけで有効です.このような変数をロ-カル変数と呼びます.複数の関数から参照可能な変数は,グローバル変数と呼ばれ,var を付加しないで定義する必要があります.なお,混乱を招かないため,グローバル変数は関数の外で定義した方が良いと思います.

    24 行目~ 27 行目

      21 行目で生成したイメージは,(height×width) 個のピクセルから構成されています.各ピクセルの値を適当に設定すれば任意のイメージを作成できるわけですが,そのためには,各ピクセルの値を記憶するための変数が必要です.今まで説明した単純変数を使用すれば (height×width) 個の変数が必要になります.これは,現実的に不可能ですし,ここで使用されているような for 文も使用できなくなります.

      そこで,配列を使用することになります.配列変数では,例えば,
      a = new Array();
      a = new Array(100);
      a = new Array("abc", "efg", "hij");
      				
    のように,Array オブジェクトを使用します.配列変数の各要素は,各々,x[0],x[1],x[2] のように括弧と添え字(必ず 0 から始まる)によって参照することができます.また,各要素として,別の配列を指定することによって,多次元配列を作成することができます.例えば,表に対応する 2 次元配列は,i 行 j 列要素を x[i][j] のように参照することになります( i も j も,必ず 0 から始まる).なお,要素の数は必要に応じて自動的に増加していきます.

      幅が w 個のピクセル,高さが h 個のピクセルで構成されているイメージの場合,

        1行1列 1行2列 ・・・ 1行w列
        2行1列 2行2列 ・・・ 2行w列
            ・・・・・
        h行1列 h行2列 ・・・ h行w列

    のように並べて表示されます.従って,2 次元配列 x を使用し,各ピクセルの値を,

        x[0][0] x[0][1] ・・・ x[0][w-1]
        x[1][0] x[1][1] ・・・ x[1][w-1]
            ・・・・・
        x[h-1][0] x[h-1][1] ・・・ x[h-1][w-1]

    のように,2 次元配列の各要素に記憶すれば良いことになります.

      しかしながら,JavaScript においては,イメージに対する各ピクセルのデータは,ImageData オブジェクト のプロパティである data の中に入っています.data は,各要素のサイズが 1 バイトである 1 次元配列です.各ピクセルデータは,赤緑青( RGB ),及び,透明度という 4 つのデータから構成され,各データは 0 ~ 255 の値をとります.これらの値を適当に設定すれば,任意のイメージを描くことが出来ます.

      先ほど述べたように,イメージデータは 2 次元配列として見た方が取り扱いやすいのですが,実際は,1 次元配列に記憶されています.従って,イメージにおける 2 次元配列上の位置を,要素のサイズが 1 バイトである 1 次元配列上の位置に変換する必要があります.記憶されている順番は,x[0][0] を先頭に,左から右,上から下の順(列番号が最初に変化する順)で記憶されていますので,例えば,イメージ上の i 行 j 列にあるピクセルデータ( 4 バイトとする)は,1 次元配列上の以下に示すような位置に対応します.なお,各配列における要素のサイズが同じ場合は,x[i][j] が y[i * col + j] に対応します( col は,2 次元配列 x の列の数).

       4 * (i * width + j) : 赤
       4 * (i * width + j) + 1 : 緑
       4 * (i * width + j) + 2 : 青
       4 * (i * width + j) + 3 : 透明度

      22 行目~ 29 行目においては,for 文を使用して,i1 行 i2 列のピクセルに値を設定しています(ただし,すべて同じ値).

    30 行目

      上で作成したイメージを描画しています.

  2. 外部画像

      外部にある画像を取り込むことも可能です.この例における右側の矩形は,外部画像 rect.gif を取り込み( 14 行目~ 15 行目),drawImage メソッドを利用して表示しています( 24 行目).

    01	<!DOCTYPE HTML>
    02	<HTML>
    03	<HEAD>
    04		<TITLE>外部画像</TITLE>
    05		<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=utf-8">
    06		<LINK REL="stylesheet" TYPE="text/css" HREF="../../master.css">
    07		<SCRIPT TYPE="text/javascript">
    08			function draw() {
    09				var canvas    = document.getElementById('canvas_e');
    10				canvas.width  = 270;   // キャンバス要素の幅
    11				canvas.height = 130;   // キャンバス要素の高さ
    12				var ctx       = canvas.getContext('2d');
    13						// 画像の読み込み
    14				var img = new Image();
    15				img.src = "rect.gif";
    16						// 塗りつぶした矩形
    17				ctx.beginPath();
    18				ctx.fillStyle = "rgb(0, 255, 0)";
    19				ctx.fillRect(20, 15, 100, 100);
    20				ctx.fill();
    21						// 外部画像
    22				var width = 100;
    23				var height = 100;
    24				ctx.drawImage(img, 150, 15, width, height);
    25			}
    26		</SCRIPT>
    27	</HEAD>
    28	<BODY CLASS="white" STYLE="text-align: center" onLoad="draw()">
    29		<H1>外部画像</H1>
    30		<CANVAS ID="canvas_e" STYLE="background-color: #eeffee;" WIDTH="270" HEIGHT="130"></CANVAS>
    31	</BODY>
    32	</HTML>
    			

  3. フィルタ

      「 DOM 及び CANVAS 関係の JavaScript のプロパティとメソッド」には,ActionScriptJava の場合のように,画像にフィルタを適用するようなメソッドが存在しません(ただし,を付けることは可能:「影の付加」参照).この例では,フィルタを適用する関数 filter を作成し,左側に示した画像にぼかしを加え,その右側に表示しています.なお,関数 filter は,ぼかしだけではなく,他のフィルタとしても利用できます.

    01	<!DOCTYPE HTML>
    02	<HTML>
    03	<HEAD>
    04		<TITLE>ぼかし</TITLE>
    05		<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=utf-8">
    06		<LINK REL="stylesheet" TYPE="text/css" HREF="../../master.css">
    07		<SCRIPT TYPE="text/javascript">
    08			function draw() {
    09				var canvas    = document.getElementById('canvas_e');
    10				canvas.width  = 270;   // キャンバス要素の幅
    11				canvas.height = 130;   // キャンバス要素の高さ
    12				var ctx       = canvas.getContext('2d');
    13						// 元の画像
    14				ctx.beginPath();
    15				ctx.fillStyle = "rgb(0, 0, 255)";
    16				ctx.fillRect(20, 15, 100, 100);
    17				ctx.fill();
    18						// ぼかし
    19				var img1 = ctx.getImageData(17, 12, 106, 106);
    20				var mx = new Array();   // フィルタ
    21				for (var i1 = 0; i1 < 7; i1++) {   // フィルタの高さが 7
    22					mx[i1] = new Array();
    23					for (var i2 = 0; i2 < 7; i2++)   // フィルタの幅が 7
    24						mx[i1][i2] = 1.0 / 49.0;
    25				}
    26				var img2 = filter(ctx, img1, 106, 106, mx, 7, 7);
    27				ctx.putImageData(img2, 147, 12);
    28			}
    29			/****************************************/
    30			/* フィルタ処理                         */
    31			/*      ctx : コンテキスト              */
    32			/*      im1 : 元の画像                  */
    33			/*      width : 画像の幅                */
    34			/*      height : 画像の高さ             */
    35			/*      mx : フィルタ                   */
    36			/*      w : フィルタの幅(奇数)        */
    37			/*      h : フィルタの高さ(奇数)      */
    38			/*      return : フィルタを適用した画像 */
    39			/****************************************/
    40			function filter(ctx, im1, width, height, mx, w, h)
    41			{
    42				var im2 = ctx.createImageData(width, height);
    43				var whf = Math.floor(w / 2);
    44				var hhf = Math.floor(h / 2);
    45				for (var i1 = 0; i1 < height; i1++) {
    46					for (var i2 = 0; i2 < width; i2++) {
    47						var v0 = 0.0, v1 = 0.0, v2 = 0.0, v3 = 0.0;
    48						var k = (i1 * width + i2) * 4;
    49						for (var i3 = 0; i3 < h; i3++) {
    50							k1 = i1 - hhf + i3;
    51							if (k1 < 0)
    52								k1 = 0;
    53							for (var i4 = 0; i4 < w; i4++) {
    54								k2 = i2 - whf + i4;
    55								if (k2 < 0)
    56									k2 = 0;
    57								var kk = (k1 * width + k2) * 4;
    58								v0 += im1.data[kk] * mx[i3][i4];
    59								v1 += im1.data[kk+1] * mx[i3][i4];
    60								v2 += im1.data[kk+2] * mx[i3][i4];
    61								v3 += im1.data[kk+3] * mx[i3][i4];
    62							}
    63						}
    64						if (v0 < 0)
    65							v0 = 0;
    66						else if (v0 > 255)
    67							v0 = 255;
    68						if (v1 < 0)
    69							v1 = 0;
    70						else if (v1 > 255)
    71							v1 = 255;
    72						if (v2 < 0)
    73							v2 = 0;
    74						else if (v2 > 255)
    75							v2 = 255;
    76						if (v3 < 0)
    77							v3 = 0;
    78						else if (v3 > 255)
    79							v3 = 255;
    80						im2.data[k]     = Math.floor(v0);
    81						im2.data[k + 1] = Math.floor(v1);
    82						im2.data[k + 2] = Math.floor(v2);
    83						im2.data[k + 3] = Math.floor(v3);
    84					}
    85				}
    86				return im2;
    87			}
    88		</SCRIPT>
    89	</HEAD>
    90	<BODY CLASS="white" STYLE="text-align: center" onLoad="draw()">
    91		<H1>ぼかし</H1>
    92		<CANVAS ID="canvas_e" STYLE="background-color: #eeffee;" WIDTH="270" HEIGHT="130"></CANVAS>
    93	</BODY>
    94	</HTML>
    			

    14 行目~ 17 行目

      位置 (20, 15) に,幅 100 ピクセル,高さ 100 ピクセルの青色の矩形を描画しています.

    19 行目

      上で描いた矩形の周辺をぼかすため,位置 (17, 12) を始点とし,幅 106 ピクセル,高さ 106 ピクセルの領域を ImageData として img1 に取り込んでいます(右図参照).

    20 行目~ 25 行目

      7 行 7 列のフィルタを定義しています.この例に示すように,配列変数を定義し( 20 行目),その各要素を再び配列として定義する( 22 行目)ことによって,2 次元配列を定義することができます.また,この例では,’ぼかし’を目的としたフィルタですが,様々なフィルタの設定も可能です.例えば,3 行 3 列のフィルタの例として以下のようなものが考えられます.
    	/* ぼかし */
    		1/9, 1/9, 1/9,
    		1/9, 1/9, 1/9,
    		1/9, 1/9, 1/9,
    
    	/* シャープ */
    		-1, -1, -1,
    		-1,  9, -1,
    		-1, -1, -1,
    
    	/* エンボス(立体視) */
    		 7,  0,  0,
    		 0, -3,  0,
    		 0,  0, -3,
    
    	/* エッジ */
    		-1,  0,  1,
    		-2,  0,  2,
    		-1,  0,  1,
    				
      一般に,配列の各要素を配列として定義することによって,多次元配列を定義することが可能です.以下に示すのは 2 行 3 列の 2 次元配列の例です.
      var a = new Array(2);   // var a = new Array(); でも可
      for (var i1 = 0; i1 < 2; i1++)
      	a[i1] = new Array(3);
      				
    初期設定も同時に行いたい場合は,例えば,以下のようにして行います.
      var a = new Array(2);   // var a = new Array(); でも可
      a[0] = new Array(1, 2, 3);
      a[1] = new Array(4, 5, 6);
      				
    26 行目~ 27 行目

      上で作成したフィルタを img1 に適用した後,その結果を描画しています.

    40 行目~ 87 行目

      フィルタを適用するための関数です.各ピクセルの値( ImageData の各要素の値)をフィルタを使用して再計算しています(たたみ込み演算).例えば,イメージデータ A の i 行 j 列の要素に,3 行 3 列のフィルタ mx を適用する場合,以下に示すような計算を実行します.ただし,計算した結果が 0 未満になったり,255 より大きくなった場合は修正します(プログラムの 64 行目~ 79 行目に対応).なお,以下の図においては,理解しやすさのため,イメージデータを 2 次元配列で表現しています.

  4. もう一つの例

      上の例においては,描画した画像にフィルタを適用しましたが,この例では,外部から読み込んだ画像にぼかしを加えています.

    <!DOCTYPE HTML>
    <HTML>
    <HEAD>
    	<TITLE>ぼかし</TITLE>
    	<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=utf-8">
    	<LINK REL="stylesheet" TYPE="text/css" HREF="../../master.css">
    	<SCRIPT TYPE="text/javascript">
    		function draw() {
    			var canvas    = document.getElementById('canvas_e');
    			canvas.width  = 460;   // キャンバス要素の幅
    			canvas.height = 240;   // キャンバス要素の高さ
    			var ctx       = canvas.getContext('2d');
    					// 画像の読み込み
    			var img = new Image();
    			img.src = "hana.gif";
    					// 元の画像
    			var width = 194;
    			var height = 203;
    			ctx.drawImage(img, 20, 20, width, height);
    					// ぼかし
    			var img1 = ctx.getImageData(20, 20, width, height);
    			var mx = new Array();   // フィルタ
    			for (var i1 = 0; i1 < 7; i1++) {   // フィルタの高さが 7
    				mx[i1] = new Array();
    				for (var i2 = 0; i2 < 7; i2++)   // フィルタの幅が 7
    					mx[i1][i2] = 1.0 / 49.0;
    			}
    			var img2 = filter(ctx, img1, width, height, mx, 7, 7);
    			ctx.putImageData(img2, 240, 20);
    		}
    		/****************************************/
    		/* フィルタ処理                         */
    		/*      ctx : コンテキスト              */
    		/*      im1 : 元の画像                  */
    		/*      width : 画像の幅                */
    		/*      height : 画像の高さ             */
    		/*      mx : フィルタ                   */
    		/*      w : フィルタの幅(奇数)        */
    		/*      h : フィルタの高さ(奇数)      */
    		/*      return : フィルタを適用した画像 */
    		/****************************************/
    		function filter(ctx, im1, width, height, mx, w, h)
    		{
    			var im2 = ctx.createImageData(width, height);
    			var whf = Math.floor(w / 2);
    			var hhf = Math.floor(h / 2);
    			for (var i1 = 0; i1 < height; i1++) {
    				for (var i2 = 0; i2 < width; i2++) {
    					var v0 = 0.0, v1 = 0.0, v2 = 0.0, v3 = 0.0;
    					var k = (i1 * width + i2) * 4;
    					for (var i3 = 0; i3 < h; i3++) {
    						k1 = i1 - hhf + i3;
    						if (k1 < 0)
    							k1 = 0;
    						for (var i4 = 0; i4 < w; i4++) {
    							k2 = i2 - whf + i4;
    							if (k2 < 0)
    								k2 = 0;
    							var kk = (k1 * width + k2) * 4;
    							v0 += im1.data[kk] * mx[i3][i4];
    							v1 += im1.data[kk+1] * mx[i3][i4];
    							v2 += im1.data[kk+2] * mx[i3][i4];
    							v3 += im1.data[kk+3] * mx[i3][i4];
    						}
    					}
    					if (v0 < 0)
    						v0 = 0;
    					else if (v0 > 255)
    						v0 = 255;
    					if (v1 < 0)
    						v1 = 0;
    					else if (v1 > 255)
    						v1 = 255;
    					if (v2 < 0)
    						v2 = 0;
    					else if (v2 > 255)
    						v2 = 255;
    					if (v3 < 0)
    						v3 = 0;
    					else if (v3 > 255)
    						v3 = 255;
    					im2.data[k]     = Math.floor(v0);
    					im2.data[k + 1] = Math.floor(v1);
    					im2.data[k + 2] = Math.floor(v2);
    					im2.data[k + 3] = Math.floor(v3);
    				}
    			}
    			return im2;
    		}
    	</SCRIPT>
    </HEAD>
    <BODY CLASS="white" STYLE="text-align: center" onLoad="draw()">
    	<H1>ぼかし</H1>
    	<CANVAS ID="canvas_e" STYLE="background-color: #eeffee;" WIDTH="460" HEIGHT="240"></CANVAS>
    </BODY>
    </HTML>
    			

静岡理工科大学 菅沼ホーム JavaScript 目次 基礎技術目次 索引