#前置き
某ソシャゲのキャラ立ち絵を集めてたら、一覧画像を作りたくなった。
手慣れたPythonでアプリ作ってたけどおいおいGUI上での任意の並び替え機能も盛り込むために、HTMLとJavaScriptに移行してみたのが始まり。
#やりたかったこと
任意の枚数の画像を選択し、全体が大体正方形になるように自動で並べる。
画像の解像度が違う場合は最も大きいものに合わせる、必要に応じて縦横比も変える。
#開発環境
Mery(x86) 2.5.6
Firefox 78.0.2(64ビット)
#結果
##コード
<!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>
#苦労したところ
##それぞれのimageのonloadが発火するタイミングが分からん
最も解像度が高い画像を特定するために、こちらの記事を参考に全ての画像を読み込めた時に発火するよう下記の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で定義していた。
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://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