ローカルHTML + JavaScriptで、自分用の画像ビューアのようなものを作っています(探してみても目的に合ったフリーソフトがないため)。
表示したいフォルダーは決まっており、フォルダー内の画像はすべて
- ファイル名 … 「0000.jpg」~「9999.jpg」(実際は3000枚程度)
- 大きさ … 1280 * 720px
- 容量 … 200KB前後
となっています。フォルダーの総容量はおよそ1GB台、使用PCのRAMは4GBです。
これらを一度に表示しようとすると、メモリを使いすぎるせいかページ自体が表示されなかったり、ブラウザ(Chrome)がクラッシュしたりしてしまいます。
やってみたこと
- 画像を一旦canvasに小さく描く(1/2サイズ)
- その小さいcanvasをtoBlob()もしくはtoDataURL()で<img>タグに反映
としてみることで、若干ながら軽量化を図ってみました。
それでも、表示枚数を何千枚という値にしてみるとやはりクラッシュしてしまいます…
JavaScript
1 2const canvas = document.getElementById('canvas'); 3const ctx = canvas.getContext('2d'); 4 5const WIDTH = 1280; 6const HEIGHT = 720; 7 8const divide = 2; 9 10const w = WIDTH / divide; //1280 ÷ 2 = 640 11const h = HEIGHT / divide; //720 ÷ 2 = 360 12 13canvas.width = w; 14canvas.height = h; 15 16const max = 3000; //ひとまず3000枚を表示してみる 17 18for( let i=0; i<max; i++ ){ 19 20 const serial = `000${i}`.slice(-4); 21 const img = new Image(); 22 23 //ローカル画像を読み込むため、Chromeの起動オプションにあらかじめ--allow-file-access-from-filesをつけてあります 24 img.crossOrigin = "Anonymous"; 25 img.src = `file:///C:/新しいフォルダー/${serial}.jpg`; //元のサイズは1920 * 1080px 26 27 img.onload = () => { 28 29 ctx.drawImage( img, 0, 0, this.width, this.height, 0, 0, w, h ); 30 31 canvas.toBlob( (blob) => { 32 33 const newImg = document.createElement('img'); 34 newImg.crossOrigin = "Anonymous"; 35 const url = URL.createObjectURL( blob ); 36 37 //あるいは、toBlobを使わずに 38 //const url = canvas.toDataURL('image/jpeg', 1); 39 //画質を落とせば軽量化はされるものの、できれば綺麗に表示したい 40 41 newImg.src = url; 42 newImg.title = serial; 43 document.body.appendChild( newImg ); 44 45 }); 46 47 } 48 49} 50
他に考えついた方法
画面内に表示されていない(スクロール外の)画像はメモリから解放し(ただのプレースホルダーとしての空白にし)、スクロールされた時点で画像を表示する… という手法も考えられそうですが
スクロールされてから画像を読み込んでいては遅いですし、かといって適切なプリロードのタイミングというのもいまいちイメージできず…
何かよい方法はないでしょうか?
(もしかしたらそもそもHTML + JavaScriptで作ることを諦めたほうがよかったりするのかもしれませんが、知識不足でして…)
追記 再帰的?にしてみたもの
for文をやめて以下に書き換えてみると、toBlob()する前に生成していた元画像ぶんの余計なメモリ使用が抑えられたようです
(それでも3000枚は表示できませんが)
都度delete演算子で画像オブジェクトのプロパティを削除することで、ガベージコレクションがメモリを解放してくれるのを期待してこのような形ですが(そういうことを意識するのは初めてなので合っているかわかりません)
そのため、すぐにメモリが解放されるわけではなく時間差があるようで、Chromeに内属されたタスクマネージャで見ていると一旦メモリがある程度増加してから下がる… という挙動を見せています
なのでだんだんblobによるメモリ使用量が増えてくる後半、ヒヤヒヤしながら見守るのは変わらず…(うちのPCでは600MBほど使うとChromeが落ちる?ようです)
JavaScript
1let tags = []; 2 3const blobbing = (index) => { 4 5 const serial = `000${index}`.slice(-4); 6 let img = new Image(); //あとでdeleteしてメモリから解放するので、constではなくlet 7 8 //ローカル画像を読み込むため、Chromeの起動オプションにあらかじめ--allow-file-access-from-filesをつけてあります 9 img.crossOrigin = "Anonymous"; 10 img.src = `file:///C:/新しいフォルダー/${serial}.jpg`; //元のサイズは1920 * 1080px 11 12 img.onload = () => { 13 14 ctx.drawImage( img, 0, 0, this.width, this.height, 0, 0, w, h ); 15 delete img; //ここで元画像をメモリから解放(すぐに解放されるわけではなく時間差がある?) 16 17 canvas.toBlob( (blob) => { 18 19 const url = URL.createObjectURL( blob ); 20 tags[index] = `<img src="${url}" title="${serial}" style="width: ${w}px; height: ${h}px;">`; 21 index++; 22 23 if( index === max ) { 24 25 $('.images').append( tags.join() ); 26 } 27 else { 28 29 blobbing( index ); //自分自身を呼び出してループ 30 } 31 32 }); 33 34 } 35 36 37} 38 39blobbing( 0 ); //ループ開始
あるいはdeleteを使わなくても、単にlet img = new Image(); で最初に作ったオブジェクトをそのままずっと使いまわしてもいいのかもしれません(そのほうがよかったりするのかはわかりませんが)
JavaScript
1let tags = []; 2let img = new Image(); 3 4const blobbing = (index) => { 5 6 const serial = `000${index}`.slice(-4); 7 8 //ローカル画像を読み込むため、Chromeの起動オプションにあらかじめ--allow-file-access-from-filesをつけてあります 9 img.crossOrigin = "Anonymous"; 10 img.src = `file:///C:/新しいフォルダー/${serial}.jpg`; //元のサイズは1920 * 1080px 11 12 img.onload = () => { 13 14 ctx.drawImage( img, 0, 0, this.width, this.height, 0, 0, w, h ); 15 16 canvas.toBlob( (blob) => { 17 18 const url = URL.createObjectURL( blob ); 19 tags[index] = `<img src="${url}" title="${serial}" style="width: ${w}px; height: ${h}px;">`; 20 index++; 21 22 if( index === max ) { 23 24 $('.images').append( tags.join() ); 25 } 26 else { 27 28 blobbing( index ); //自分自身を呼び出してループ 29 } 30 31} 32 33blobbing( 0 ); //ループ開始
2000枚近く読み込めるようにはなったものの、
net::ERR_INSUFFICIENT_RESOURCES 200 (OK)
というのが出ますね… やはりリソースがあと少し足りないようで
(ここまでしてBlobを使って画像を縮小する必要があるのか、そもそも複数の方からのご意見があったようにJavaScript以外の手段で縮小画像を作っておくべきなのかという話にもなってきますが)
引き続きご意見をお待ちしております。