情報学部 菅沼ホーム JavaScript 目次 基礎技術目次 索引

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

  1. ビットマップ

      図を描く方法として,「 2d コンテキストのプロパティとメソッド」のうち「画像の描画とピクセル操作」を利用し,ピクセル単位に色を指定することによって描く方法があります.この例において,左側の矩形は 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		<META NAME=viewport CONTENT="width=device-width, initial-scale=1">
    07		<LINK REL="stylesheet" TYPE="text/css" HREF="../../master.css">
    08		<SCRIPT TYPE="text/javascript">
    09			function draw() {
    10				let canvas    = document.getElementById('canvas_e');
    11				canvas.width  = 270;   // キャンバス要素の幅
    12				canvas.height = 130;   // キャンバス要素の高さ
    13				let ctx       = canvas.getContext('2d');
    14						// 塗りつぶした矩形
    15				ctx.beginPath();
    16				ctx.fillStyle = "rgb(0, 255, 0)";
    17				ctx.fillRect(20, 15, 100, 100);
    18				ctx.fill();
    19						// ピクセル操作による矩形
    20				let width = 100;
    21				let height = 100;
    22				let img = ctx.createImageData(width, height);
    23				for (let i1 = 0; i1 < height; i1++) {
    24					for (let i2 = 0; i2 < width; i2++) {
    25						img.data[(i1 * width + i2) * 4] = 0;   // 赤
    26						img.data[(i1 * width + i2) * 4 + 1] = 255;   // 緑
    27						img.data[(i1 * width + i2) * 4 + 2] = 0;   // 青
    28						img.data[(i1 * width + i2) * 4 + 3] = 255;   // 透明度(不透明)
    29					}
    30				}
    31				ctx.putImageData(img, 150, 15);
    32			}
    33		</SCRIPT>
    34	</HEAD>
    35	<BODY CLASS="white" STYLE="text-align: center" onLoad="draw()">
    36		<H1>ビットマップ</H1>
    37		<CANVAS ID="canvas_e" STYLE="background-color: #eeffee;" WIDTH="270" HEIGHT="130"></CANVAS>
    38	</BODY>
    39	</HTML>
    			
    20 行目~ 22 行目

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

    23,24 行目

      23 行目の for 文によって,24 行目~ 29 行目が height 回繰り返され,また,24 行目の for 文によって,25 行目~ 28 行目が width 回繰り返されます.従って,25 行目~ 28 行目は,height × width 回繰り返されることになります.変数 i1,i2 は,let を付加して for ブロックの中で定義されていますので,各変数の有効範囲は,23 行目~30 行目,24 行目~29 行目となります.

    25 行目~ 28 行目

      22 行目で生成したイメージは,( 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 から始まる).なお,1 次元配列の場合も,多次元配列の場合も,要素の数は必要に応じて増加していきます.

      幅が 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 の値をとります.従って,( height × width ) 個のピクセルから構成されたイメージの場合は,4 × ( height × width ) 個のデータが必要になります.これらの値を適当に設定すれば,任意のイメージを描くことが出来ます.

      先ほど述べたように,イメージデータは 2 次元配列として見た方が取り扱いやすいのですが,実際は,1 次元配列に記憶されています.従って,イメージにおける 2 次元配列上の位置を,要素のサイズが 1 バイトである 1 次元配列上の位置に変換する必要があります.記憶されている順番は,x[0][0] を先頭に,左から右,上から下の順(列番号が最初に変化する順)で記憶されていますので,例えば,イメージ上の i 行 j 列にあるピクセルデータ( 4 バイトとする)は,1 次元配列上の以下に示すような位置に対応します.なお,一般に,連続した領域に記憶されている 2 次元配列 x を 1 次元配列 y とみなしたとき,2 次元配列 x の要素 x[i][j] は,1 次元配列 y の要素 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 : 透明度

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

    31 行目

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

  2. 外部画像

      外部にある画像を取り込むことも可能です.この例における右側の矩形は,外部画像 rect.gif を取り込み( 15 行目~ 16 行目),drawImage メソッドを利用して表示しています( 25 行目).
    01	<!DOCTYPE HTML>
    02	<HTML>
    03	<HEAD>
    04		<TITLE>外部画像</TITLE>
    05		<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=utf-8">
    06		<META NAME=viewport CONTENT="width=device-width, initial-scale=1">
    07		<LINK REL="stylesheet" TYPE="text/css" HREF="../../master.css">
    08		<SCRIPT TYPE="text/javascript">
    09			function draw() {
    10				let canvas    = document.getElementById('canvas_e');
    11				canvas.width  = 270;   // キャンバス要素の幅
    12				canvas.height = 130;   // キャンバス要素の高さ
    13				let ctx       = canvas.getContext('2d');
    14						// 画像の読み込み
    15				let img = new Image();
    16				img.src = "rect.gif";
    17						// 塗りつぶした矩形
    18				ctx.beginPath();
    19				ctx.fillStyle = "rgb(0, 255, 0)";
    20				ctx.fillRect(20, 15, 100, 100);
    21				ctx.fill();
    22						// 外部画像
    23				let width = 100;
    24				let height = 100;
    25				ctx.drawImage(img, 150, 15, width, height);
    26			}
    27		</SCRIPT>
    28	</HEAD>
    29	<BODY CLASS="white" STYLE="text-align: center" onLoad="draw()">
    30		<H1>外部画像</H1>
    31		<CANVAS ID="canvas_e" STYLE="background-color: #eeffee;" WIDTH="270" HEIGHT="130"></CANVAS>
    32	</BODY>
    33	</HTML>
    			

  3. フィルタ

      「 2d コンテキストのプロパティとメソッド」には,Java の場合のように,画像にフィルタを適用するようなメソッドが存在しません(ただし,を付けることは可能:「影の付加」参照).この例では,フィルタを適用する関数 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		<META NAME=viewport CONTENT="width=device-width, initial-scale=1">
    07		<LINK REL="stylesheet" TYPE="text/css" HREF="../../master.css">
    08		<SCRIPT TYPE="text/javascript">
    09			function draw() {
    10				let canvas    = document.getElementById('canvas_e');
    11				canvas.width  = 270;   // キャンバス要素の幅
    12				canvas.height = 130;   // キャンバス要素の高さ
    13				let ctx       = canvas.getContext('2d');
    14						// 元の画像
    15				ctx.beginPath();
    16				ctx.fillStyle = "rgb(0, 0, 255)";
    17				ctx.fillRect(20, 15, 100, 100);
    18				ctx.fill();
    19						// ぼかし
    20				let img1 = ctx.getImageData(17, 12, 106, 106);
    21				let mx = new Array();   // フィルタ
    22				for (let i1 = 0; i1 < 7; i1++) {   // フィルタの高さが 7
    23					mx[i1] = new Array();
    24					for (let i2 = 0; i2 < 7; i2++)   // フィルタの幅が 7
    25						mx[i1][i2] = 1.0 / 49.0;
    26				}
    27				let img2 = filter(ctx, img1, 106, 106, mx, 7, 7);
    28				ctx.putImageData(img2, 147, 12);
    29			}
    30			/****************************************/
    31			/* フィルタ処理                         */
    32			/*      ctx : コンテキスト              */
    33			/*      im1 : 元の画像                  */
    34			/*      width : 画像の幅                */
    35			/*      height : 画像の高さ             */
    36			/*      mx : フィルタ                   */
    37			/*      w : フィルタの幅(奇数)        */
    38			/*      h : フィルタの高さ(奇数)      */
    39			/*      return : フィルタを適用した画像 */
    40			/****************************************/
    41			function filter(ctx, im1, width, height, mx, w, h)
    42			{
    43				let im2 = ctx.createImageData(width, height);
    44				let whf = Math.floor(w / 2);
    45				let hhf = Math.floor(h / 2);
    46				for (let i1 = 0; i1 < height; i1++) {
    47					for (let i2 = 0; i2 < width; i2++) {
    48						let v0 = 0.0, v1 = 0.0, v2 = 0.0, v3 = 0.0;
    49						let k = (i1 * width + i2) * 4;
    50						for (let i3 = 0; i3 < h; i3++) {
    51							let k1 = i1 - hhf + i3;
    52							if (k1 < 0)
    53								k1 = 0;
    54							for (let i4 = 0; i4 < w; i4++) {
    55								let k2 = i2 - whf + i4;
    56								if (k2 < 0)
    57									k2 = 0;
    58								let kk = (k1 * width + k2) * 4;
    59								v0 += im1.data[kk] * mx[i3][i4];
    60								v1 += im1.data[kk+1] * mx[i3][i4];
    61								v2 += im1.data[kk+2] * mx[i3][i4];
    62								v3 += im1.data[kk+3] * mx[i3][i4];
    63							}
    64						}
    65						if (v0 < 0)
    66							v0 = 0;
    67						else if (v0 > 255)
    68							v0 = 255;
    69						if (v1 < 0)
    70							v1 = 0;
    71						else if (v1 > 255)
    72							v1 = 255;
    73						if (v2 < 0)
    74							v2 = 0;
    75						else if (v2 > 255)
    76							v2 = 255;
    77						if (v3 < 0)
    78							v3 = 0;
    79						else if (v3 > 255)
    80							v3 = 255;
    81						im2.data[k]     = Math.floor(v0);
    82						im2.data[k + 1] = Math.floor(v1);
    83						im2.data[k + 2] = Math.floor(v2);
    84						im2.data[k + 3] = Math.floor(v3);
    85					}
    86				}
    87				return im2;
    88			}
    89		</SCRIPT>
    90	</HEAD>
    91	<BODY CLASS="white" STYLE="text-align: center" onLoad="draw()">
    92		<H1>ぼかし</H1>
    93		<CANVAS ID="canvas_e" STYLE="background-color: #eeffee;" WIDTH="270" HEIGHT="130"></CANVAS>
    94	</BODY>
    95	</HTML>
    			
    15 行目~ 18 行目

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

    21 行目~ 26 行目

      7 行 7 列のフィルタを定義しています.この例に示すように,配列変数を定義し( 21 行目),その各要素を再び配列として定義する( 23 行目)ことによって,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 次元配列の例です.
    	let a = new Array(2);   // let a = new Array(); でも可
    	for (let i1 = 0; i1 < 2; i1++)
    		a[i1] = new Array(3);				
    初期設定も同時に行いたい場合は,例えば,以下のようにして行います.
    	let a = new Array(2);   // let a = new Array(); でも可
    	a[0] = new Array(1, 2, 3);
    	a[1] = new Array(4, 5, 6);				
    27 行目~ 28 行目

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

    41 行目~ 88 行目

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

  4. もう一つの例

      上の例においては,描画した画像にフィルタを適用しましたが,この例では,外部から読み込んだ画像にぼかしを加えています.
    <!DOCTYPE HTML>
    <HTML>
    <HEAD>
    	<TITLE>ぼかし</TITLE>
    	<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=utf-8">
    	<META NAME=viewport CONTENT="width=device-width, initial-scale=1">
    	<LINK REL="stylesheet" TYPE="text/css" HREF="../../master.css">
    	<SCRIPT TYPE="text/javascript">
    		function draw() {
    			let canvas    = document.getElementById('canvas_e');
    			canvas.width  = 460;   // キャンバス要素の幅
    			canvas.height = 240;   // キャンバス要素の高さ
    			let ctx       = canvas.getContext('2d');
    					// 画像の読み込み
    			let img = new Image();
    			img.src = "hana.gif";
    					// 元の画像
    			let width = 194;
    			let height = 203;
    			ctx.drawImage(img, 20, 20, width, height);
    					// ぼかし
    			let img1 = ctx.getImageData(20, 20, width, height);
    			let mx = new Array();   // フィルタ
    			for (let i1 = 0; i1 < 7; i1++) {   // フィルタの高さが 7
    				mx[i1] = new Array();
    				for (let i2 = 0; i2 < 7; i2++)   // フィルタの幅が 7
    					mx[i1][i2] = 1.0 / 49.0;
    			}
    			let 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)
    		{
    			let im2 = ctx.createImageData(width, height);
    			let whf = Math.floor(w / 2);
    			let hhf = Math.floor(h / 2);
    			for (let i1 = 0; i1 < height; i1++) {
    				for (let i2 = 0; i2 < width; i2++) {
    					let v0 = 0.0, v1 = 0.0, v2 = 0.0, v3 = 0.0;
    					let k = (i1 * width + i2) * 4;
    					for (let i3 = 0; i3 < h; i3++) {
    						k1 = i1 - hhf + i3;
    						if (k1 < 0)
    							k1 = 0;
    						for (let i4 = 0; i4 < w; i4++) {
    							k2 = i2 - whf + i4;
    							if (k2 < 0)
    								k2 = 0;
    							let 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 目次 基礎技術目次 索引