Help us understand the problem. What is going on with this article?

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

便利ページ:Javascriptでちょっとした便利な機能を作ってみた」のシリーズものです。

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

image.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

ソースコード

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

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でファイルを選択した後、再度同じファイル名を指定したときに、ファイル変更していても再読み込みしてくれないので、いったん選択ファイルを解除しています。

以上

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした