画像を読み込んでみようと思います

 今回は画像を読み込んでみます。読み込むのはローカルフォルダに置かれた画像です。 インターネットに画像をアップロードするわけではなく、特に準備も必要ありません。ブラウザとテキストエディタがあればOKです。
 ローカルに保存してある画像をcanvasで表示するのに何の意味があるのかと思われるかもしれませんが、アップロードしたくない画像でもブラウザで表示・加工ができるようになります。 拡散・流出を気にせず安心して使えるわけです。

Canvas要素を載せたHTML5の書き方

htmlファイルを用意します。 文字エンコードは「UTF-8(BOM無し)」で保存してください。(サンプルページが下にあります。)

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <title>canvas tutorial</title>
    <style>
      #canvas { background: #666; }
    </style>
  </head>
  <body>
    <h1>画像を選択してください。</h1>
    <form id="my_form">
        <input id="ufile" name="ufile" type="file" accept="image/jpeg,image/png"><br>
    </form>
    <canvas id="canvas" width="640" height="480"></canvas>
    <script src="sample02-1.js"></script>
  </body>
</html>

 ファイルを読み込むのに文字列を扱うのでmetaタグで文字コードを明示してあります。

<meta charset="UTF-8">

 前世紀は文字化けに悩まされていましたが、今は明示できるおかげかほとんど遭遇することがありません。良い世の中になりました。

そんなことより動くサンプル

 次のスクリプトをファイル名「sample02-1.js」で保存してください。ファイルはさっきのhtmlファイルと同じフォルダに置きます。 もちろん文字エンコードは「UTF-8(BOM無し)」で。

// イベント発生時に実行する関数
function setListeners(event){
	// --------------------
	//	引数チェック
	// --------------------
	var tg = event.target;	// イベントが発生した要素

	if (!tg.files.length) {
		console.log('ファイルが選択されていません');
		return;
	}
	// Formからファイルを取得
	var file = tg.files[0];

	// --------------------
	//	ファイル読み込み
	// --------------------
	var cve = document.getElementById("canvas");
	if (cve.getContext) {
		var ctx = cve.getContext('2d');

		var img = new Image();
		var fr  = new FileReader();
		
		// 画像ファイル読み込み完了後に実行する処理
		fr.onload = function(evt) {
			// 画像読み込み完了後に実行する処理
			img.onload = function () {
				// canvasサイズを画像サイズに合わせて描画
				cve.setAttribute('width',  img.naturalWidth);
				cve.setAttribute('height', img.naturalHeight);
				// 描画
				ctx.drawImage(img, 0, 0, img.naturalWidth, img.naturalHeight);
			}
			// Base64エンコードされた文字を画像のurlとしてsrcプロパティに渡す
			// すると、画像として表示される。
			img.src = evt.target.result;
		}
		
		// fileを読み込む データはBase64エンコードされる
		fr.readAsDataURL(file);
	}

};

// --------------------
//	イベントのリスナーを登録
// --------------------
document.getElementById("ufile").addEventListener("change", setListeners, false);

 htmlファイルをブラウザで開いて動作確認してみてください。 ボタンをクリックするとファイル選択ダイアログが開くので、ローカルに保存してあるJpegかPNGの画像ファイルを指定してください。 ボタンの下に画像が表示されるはずですが…どうでしょうか?
サンプルページ

 今回も参考サイトがあります。もっと細かな解説も下記サイトに書かれていますのでこちらも御覧ください。
canvasにローカル画像を表示(https://qiita.com/michimaru/items/595cbf090569bfab2d20

解説:file

 inputタグで取得したファイル情報は、FileListオブジェクトに格納されています。
 まずはいくつ選択されているかを確認します。1つも選択されていない場合は処理を終了させます。

if (!tg.files.length) { (選択がゼロ個の場合の処理) }

選択されていた場合は、1個目のFileオブジェクトを取り出します。

var file = tg.files[0];

FileListオブジェクトについてはこちらのサイトが詳しいようです。
Web アプリケーションからファイルを扱う ファイルの情報を得る - MDN web docs
https://developer.mozilla.org/ja/docs/Web/API/File/Using_files_from_web_applications#Getting_information_about_selected_files

解説:ImageとFileReader

今回はImageとFileReaderというオブジェクトを使用しています。

var img = new Image();
var fr  = new FileReader();

.onloadで、それぞれ読み込み完了後に実行する内容を作ってから、データを読み込んでいますね。
処理される順番に見ていきましょう。

fr.readAsDataURL(file);

 まずは FileReader オブジェクト。
 指定したファイル(file)を読み込んで、画像データを文字列化します。文字列はresult プロパティから取り出せます。
画像データが文字列にと言われても意味不明かもしれませんが、バイナリデータを文字列を使った表現に書き換えたというか… ともかくconsole.log(img.src); などとして内容を表示してみてください。「data:image/…」という文字列が沢山表示されましたね? この文字列が画像データです。
FileReader.readAsDataURL() - MDN web docs(https://developer.mozilla.org/ja/docs/Web/API/FileReader/readAsDataURL

img.src = evt.target.result;

 この文字列化した画像データをurlとして Image オブジェクトの src プロパティに渡します。 これでこの Image オブジェクトは画像が入った状態になります。
 通常なら、準備した画像ファイルのURLを src プロパティに指定すると Image オブジェクトは画像データが入った状態になります。 ですが、今回のように画像をBase64エンコードで文字列化したものを src プロパティに渡しても同じように Image オブジェクトに画像データが入った状態になります。 少し不思議な感じがしますが、ファイルを準備しなくていいので今回のように都合がいい場合があります。

img.onload = function () { ~ }

 あとはキャンバスの大きさを調整して Image オブジェクトの中身を描画します。

ドラッグ&ドロップを使う場合 HTML5の書き方

ついでなのでファイル選択にドラッグ&ドロップを使う場合も書いてみます。 htmlファイルを用意します。 文字エンコードは「UTF-8(BOM無し)」で保存してください。(サンプルページが下にあります。)

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <title>canvas tutorial</title>
    <style>
      #canvas {
          background: #666;
          border: 1px solid black;
      }
    </style>
  </head>
  <body>
    <h1>画像をドラッグ&ドロップしてください</h1>
    <canvas id="canvas" width="640" height="480"></canvas>
    <script src="sample02-2.js"></script>
  </body>
</html>

 基本は変わりません。キャンバス要素の位置を見つけやすくするため、スタイルシートで縁取りをしました。 border: 1px solid black;という感じ。あとは今回使用しない要素を削除。

ドラッグ&ドロップを使う場合 JavaScript

次のスクリプトをファイル名「sample02-2.js」で保存してください。ファイルはさっきのhtmlファイルと同じフォルダに置きます。 もちろん文字エンコードは「UTF-8(BOM無し)」で。

// イベント発生時に実行する関数
function setListeners(event){
	// ブラウザ標準の動作をキャンセル
	event.preventDefault();
	
	// --------------------
	//	引数チェック
	// --------------------
	var file = event.dataTransfer.files[0];

	// ファイルタイプ(MIME)で対応しているファイルか判定
	if (!file.type.match(/image\/\w+/)){
		alert('画像ファイル以外は利用できません');
		return;
	}

	// --------------------
	//	ファイル読み込み
	// --------------------
	var cve = document.getElementById("canvas");
	if (cve.getContext) {
		var ctx = cve.getContext('2d');

		var img = new Image();
		var fr  = new FileReader();
		
		// 画像ファイル読み込み完了後に実行する処理
		fr.onload = function(evt) {
			// 画像読み込み完了後に実行する処理
			img.onload = function () {
				// canvasサイズを画像サイズに合わせて描画
				cve.setAttribute('width',  img.naturalWidth);
				cve.setAttribute('height', img.naturalHeight);
				// 描画
				ctx.drawImage(img, 0, 0, img.naturalWidth, img.naturalHeight);
			}
			// Base64エンコードされた文字を画像のurlとしてsrcプロパティに渡す
			// すると、画像として表示される。
			img.src = evt.target.result;
		}
		
		// fileを読み込む データはBase64エンコードされる
		fr.readAsDataURL(file);
	}
};

// --------------------
//	イベントのリスナーを登録
// --------------------
document.getElementById("canvas").addEventListener('dragover', function(event) { event.preventDefault();}, false)
document.getElementById("canvas").addEventListener('drop',setListeners, false);

 htmlファイルをブラウザで開いて動作確認してみてください。 canvas要素(灰色の四角)にブラウザの外からファイルをドラッグ&ドロップしてみてください。 上手く動作すれば画像が読み込まれてcanvas要素に表示されるはずです。
サンプルページ

解説:ドラッグ&ドロップ

 ドラッグ&ドロップ処理は、ポイントを押さえればあまり難しくはありません。今回は特にファイルの読み込み限定ですし。

 まず.addEventListener()で、dragoverイベントの標準動作を無効にします。そのままだとブラウザ標準機能が働いて画像読み込んじゃうので。これは.preventDefault()で無効にできます。
次にdropイベントでの動作を指定します。 この中でもまず.preventDefault()でブラウザの標準動作を無効にします。

dropイベント発生後は、dataTransfer オブジェクトの中にドラッグ中のファイル情報が入っているので取り出します。

var file = event.dataTransfer.files[0];

 後は…そうそう。ドラッグ&ドロップだとファイルを開くダイアログと違って、ファイルタイプが制限できないのでファイルタイプのチェックを行っています。 ここまで来るとcanvasを使った残りの処理は全く同じものになります。 あまり難しくないので両方のやり方に対応させたほうが便利でしょうね。

次回予告

 昔同じものを作ってるんですが、あまり覚えてませんでした。当時はあまり理解してなかったんでしょうね。旬が命のネタでしたし。

 さて、画像が読み込めたら…やっぱり加工とかしたいですよね。 では次回は画像を加工してみましょう。