JavaScriptで画像を格子状に並べて結合する

#前置き
某ソシャゲのキャラ立ち絵を集めてたら、一覧画像を作りたくなった。
手慣れたPythonでアプリ作ってたけどおいおいGUI上での任意の並び替え機能も盛り込むために、HTMLとJavaScriptに移行してみたのが始まり。
#やりたかったこと
任意の枚数の画像を選択し、全体が大体正方形になるように自動で並べる。
画像の解像度が違う場合は最も大きいものに合わせる、必要に応じて縦横比も変える。

#開発環境
Mery(x86) 2.5.6
Firefox 78.0.2(64ビット)

#結果
##コード
```html:index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>JavaScriptで画像を格子状に結合</title>
</head>
<body>
<form>
<input type="file" accept='image/*' onchange="AlignImage(this);" multiple="multiple">
</form>
<p>
Preview:<br>
<canvas id="preview" width="600" height="400" style="background-color:black;"></canvas>
<script>
function AlignImage(obj)
{
	var images = [];
	var cnt_loaded = 1;
	var cnt_added = 0;
	for (const file of obj.files) {
		const no = cnt_added;
		const reader = new FileReader();
		reader.onload = (function() {
			images[no] = new Image();
			images[no].src = reader.result;
			console.log('no: ' + no);
			console.log('src: ' + reader.result);
			console.log('cnt_loaded: ' + cnt_loaded);
			console.log('images.length: ' + images.length);
			images[no].onload = (function () {
				console.log( no + ' is loaded.');
				console.log('cnt_loaded: ' + cnt_loaded);
				console.log('obj.files.length: ' + obj.files.length);
				if (cnt_loaded == obj.files.length) {
					var canvas = document.getElementById('preview');
					var ctx = canvas.getContext('2d');
					var w_max = 0;
					var h_max = 0;
					for (const image of images) {
						if (image.width * image.height > w_max * h_max) {
							w_max = image.width;
							h_max = image.height;
						}
					}
					var row = images.length;
					var col = 1;
					while (row != col && row * h_max > col * h_max) {
						col++;
						row = Math.ceil(images.length / col)
					}
					console.log('w x h: ' + w_max * col + ' x ' + h_max * row);
					console.log('col x row: ' + col + ' x ' + row);
					canvas.width = w_max * col;
					canvas.height = h_max * row;
					images.forEach(function(image, i){
						ctx.drawImage(image, 0, 0, image.width, image.height, w_max * (i % col), h_max * Math.floor(i / col), w_max, h_max);
					})
				}
				cnt_loaded++;
			});
		});
		reader.readAsDataURL(file);
		cnt_added++;
	}
}
</script>
</body>
</html>
```
##画面
![Screenshot_2020-07-24.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679781/98277d9e-d696-ab43-19af-53ef07a4746f.jpeg)
写真は四国旅行のもの。

#苦労したところ
##それぞれのimageのonloadが発火するタイミングが分からん
最も解像度が高い画像を特定するために、[こちらの記事](https://qiita.com/akase244/items/9f3c60219f903254a6e2)を参考に全ての画像を読み込めた時に発火するよう下記のonloadイベントを登録していた。

```javascript:images[no].onload
images[no].onload = (function () {
	if (cnt_loaded == images.length) {
		#canvasへの書き出しロジック
	}
	cnt_loaded++;
});
```
しかし2枚目以降のimageオブジェクトがimagesへ追加される前に1枚目のonloadが発火してしまうせいで、1枚だけ出力されてしまっていた。
よって最初から枚数が格納されている`obj.files.length`と`cnt_loaded`を比較するよう修正した。

##for内のvarで宣言した変数が次の繰り返し時に書き換わってしまう
onloadが発火するタイミングが分かってなかったのとvarのスコープを分かってなかった。
各画像のインデックスと、base64形式へ変換するFileReaderオブジェクトをリセットするために、下記のようにfor内で`no`と`reader`をvarで定義していた。

```javascript:for
for (const file of obj.files) {
	var no = cnt_added;
	var reader = new FileReader();
	reader.onload = (function() {
		#base64への変換
		images[no].onload = (function () {
			#canvasへの出力
			}
			cnt_loaded++;
		});
	});
	reader.readAsDataURL(file);
	cnt_added++;
}
```
しかし実際に`no`と`reader`を使用する`reader.onload`、`images[no].onload`イベントが発火する頃にはforは次の周回に入っており、`no`と`reader`は置き換わってしまう。
結果エラーだったり出力がめちゃくちゃになっていた。
これは`var`から`const`に変えることで対応した。正直上手く行ってる理由が分かってないが、おそらくスコープが限定されたことによるものだと理解している。

#今後実装したい機能
~~出力解像度の指定~~[ファイルサイズを指定できるようにしてみた](https://qiita.com/lt900ed/items/d230b89be5106d949cea)
行・列数の操作
画像のドラッグ&ドロップによる任意の並び替え

#参考
https://blog.ver001.com/javascript_preview_canvas/
https://qiita.com/akase244/items/9f3c60219f903254a6e2
https://qiita.com/mochizukikotaro/items/ff6cd42e902e79b02649
https://maku77.github.io/js/array/loop.html
https://techacademy.jp/magazine/14872
http://www.htmq.com/canvas/drawImage_s.shtml
http://www.htmq.com/canvas/fillRect.shtml
https://eng-entrance.com/javascript-array-length
https://kazumich.com/html5multiple.html