便利ページ：Javascriptでアイコンファイルを生成する

「[便利ページ：Javascriptでちょっとした便利な機能を作ってみた](https://qiita.com/poruruba/items/6c67c7eef34ed302feb3 )」のシリーズものです。

今回は、1つの画像ファイルから、AndroidやiPhoneアプリで使う様々なサイズのアイコンを生成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/a5a6e3c3-497a-abf8-e7f2-190f093a66b5.png)

いつもの通りGitHubにも上げてあります。
　https://github.com/poruruba/utilities

参考までに、以下にデモとしてアクセスできるようにしてあります。「画像ファイル」のタブを選択してみてください。
　https://poruruba.github.io/utilities/

(2020/2/16 修正)
・回転も加えました。

# 操作方法

まずは、ドラッグアンドドロップで、アイコン化したい画像ファイルをドロップします。

アプリで使われるアイコンは、正方形ですが、ドロップするは画像は必ずしも正方形でなくてもよいです。
次に、これから作成したいアプリのOSを選択します。各OSによって、必要とされるアイコンのサイズが異なるためです。

そして、scaleのところで、画像の拡大・縮小方法を選択します。

* cover：縦横比を維持せずに、画像を正方形に拡大あるいは縮小します。
* contain：縦横比を維持しつつ、画像全体が正方形に収まるようにします。余った領域は、透過色とします。
* crop：縦横比は維持しますが、画像の真ん中を中心に画像を切り出します。縦横のうち長い方は両端が削られます。

最後に、「ファイルに保存」ボタンを押下することで、複数のサイズのアイコンファイルをまとめたZIPファイルの保存ダイアログが表示されます。
例えば、Androidの場合には、以下のアイコンファイルがZIPファイルになっています。
　36x36.png、48x48.png、72x72.png、96x96.png、144x144.png、192x192.png

＜出力されるアイコンサイズ＞

| OS | アイコンサイズ |
|:-:|:--|
| Android | 192x192, 144x144, 96x96, 72x72, 48x48, 36x36 |
| iPhone | 180x180, 167x167, 152x152, 120x120, 87x87, 76x76, 60x60, 58x58, 40x40, 29x29, 20x20 |
| Windows | 48x48, 32x32, 16x16 |


# ソースコード

まずは該当部分のソースコードを示します。

```js:start.js
        file_drag: function(e){
            e.stopPropagation();
            e.preventDefault();
        },

        /* 画像ファイル */
        image_open: function(e){
            this.image_open_file(e.target.files[0]);
        },
        image_drop: function(e){
            e.stopPropagation();
            e.preventDefault();

            $('#image_file')[0].files = e.dataTransfer.files;
            this.image_open_file(e.dataTransfer.files[0]);
        },
        image_open_file: function(file){
            if( !file.type.startsWith('image/') ){
                alert('画像ファイルではありません。');
                return;
            }

            var reader = new FileReader();
            reader.onload = (theFile) =>{
                this.image_type = file.type;
                this.image_src = reader.result;
                this.image_image = new Image();
                this.image_image.onload = () =>{
                    this.image_size = { width: this.image_image.width, height: this.image_image.height };
                    this.image_scale_change();
                };
                this.image_image.src = this.image_src;
            };
            reader.readAsDataURL(file);
        },
        image_scale_change: function(){
            if(!this.image_src)
                return;

            var image = this.image_image;
            var size, sx, sy, sw, sh, dx, dy, dw, dh;

            if( this.image_scale == 'cover'){
                size = (image.width > image.height) ? image.width : image.height;
                sx = sy = 0;
                sw = image.width;
                sh = image.height;
                dx = dy = 0;
                dw = dh = size;
            }else
            if( this.image_scale == 'contain'){
                size = (image.width > image.height) ? image.width : image.height;
                var x = Math.floor((size - image.width) / 2);
                var y = Math.floor((size - image.height) / 2);
                sx = sy = 0;
                sw = image.width;
                sh = image.height;
                dx = x;
                dy = y;
                dw = image.width;
                dh = image.height;                
            }else
            if( this.image_scale == 'crop'){
                size = (image.width < image.height) ? image.width : image.height;
                var x = Math.floor((image.width - size) / 2);
                var y = Math.floor((image.height - size) / 2);
                sx = x;
                sy = y;
                sw = sh = size;
                dx = dy = 0;
                dw = dh = size;         
            }

            var canvas = document.createElement('canvas');
            canvas.width = size;
            canvas.height = size;
            var context = canvas.getContext('2d');

            var angle = this.image_rotate;
            var trans = Math.floor(size / 2);
            context.translate(trans, trans);
            context.rotate(angle * Math.PI / 180);
            context.translate(-trans, -trans);

            context.drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh);

            this.image_image_scaled = canvas;

            var canvas2 = $('#image_icon')[0];
            canvas2.width = canvas.width;
            canvas2.height = canvas.height;
            var context2 = canvas2.getContext('2d');
            context2.drawImage(canvas, 0, 0);
        },
        image_save: async function(){
            if(!this.image_src)
                return;

            var canvas = document.createElement('canvas');
            var context = canvas.getContext('2d');
            var zip = new JSZip();
            var list = this.image_icon_list[this.image_icon];
            for( var i = 0 ; i < list.length ; i++ ){
                canvas.width = list[i];
                canvas.height = list[i];
                context.drawImage(this.image_image_scaled, 0, 0, this.image_image_scaled.width, this.image_image_scaled.height, 0, 0, canvas.width, canvas.height);

                var data_url = canvas.toDataURL('image/png');
                var byteStr = atob( data_url.split( "," )[1] ) ;
                var content = new Uint8Array(byteStr.length);
                for( var j = 0; j < byteStr.length; j++ )
                    content[j] = byteStr.charCodeAt( j ) ;
                var blob = new Blob( [ content ], {
                    type: this.image_type,
                });

                var fname = list[i] + "x" + list[i] + '.png';
                zip.file(fname, blob);
            }

            var zip_blob = await zip.generateAsync({type: "blob"})
            var url = window.URL.createObjectURL(zip_blob);

            var a = document.createElement("a");
            a.href = url;
            a.target = '_blank';
            a.download = "icon_list.zip";
            a.click();
            window.URL.revokeObjectURL(url);
        },
        image_click: function(e){
            this.image_type = '';
            this.image_src = null;

            e.target.value = '';
        },
```

その他のソースはGitHubを参照してください。

#解説

各関数について、少し解説しておきます。

* image_open
HTMLのinput type=”file” でファイルが選択されたときに呼び出されます。

* file_drag
マウスでファイルをドラッグされたときに呼び出されます。まだドロップされていないので何もしません。

* image_drop
マウスでファイルをドロップされたときに呼び出されます。
以下は、ブラウザがドラッグ中のファイルを横取りされるのを防ぐためのものです。
            e.stopPropagation();
            e.preventDefault();

* image_open_file
実際の画像ファイルの読み込み処理です。image_openによるファイルの選択時と、image_dropによるファイルのドロップ時に、共通で呼び出されます。
HTML5のFileAPIを使ってファイルを読み込み、いったんデータURL形式にして、後の画像処理用に保持しておくとともに、HTMLのimgに画像を当てています。

* image_scale_change
読み込んだ画像を、指定されたscaleに合わせて、整形します。Canvasを使います。
また、HTMLのcanvasに整形後の画像を当てています。

* image_save
アイコンサイズに合わせて画像をリサイズしファイルに出力します。ここでも、Canvasを活用します。
ファイルに出力する際に、複数のアイコンファイルを1つのファイルにするために、ZIP化しています。以下のライブラリを利用させていただきました。
　JSZip
　　https://stuk.github.io/jszip/ 

* image_click
image_openでファイルを選択した後、再度同じファイル名を指定したときに、ファイル変更していても再読み込みしてくれないので、いったん選択ファイルを解除しています。

以上