Electron 画像をドラッグ&ドロップするだけでリサイズするアプリをつくる

さてさて、少し前に公開した初心者向き!electron で簡単なメモ帳をつくってみようという記事が思いのほか好評のようでした。

正直な所Electronの開発は個人的にも好きなので、せっかくなら何かブログ執筆に役立つアプリをつくってみようと考えるようになりました。

そこで、今回作るアプリは 画像をドラッグ&ドロップするだけでリサイズする アプリです。つまり、ファイルを選んで(複数可)Electronのウィンドウに持っていけば、自動でリサイズが実行できるという便利アプリですね。

というのも、このブログの画像は最大横幅を640pxと決めていているのですが、たまに大きな画像がたくさんあるときはいちいちconvertコマンドを打って変換していて、なかなかめんどうなのです。(もっというとプラグインも入れてますが、このプラグインは一旦別サーバーへ送信してから変換し、その直後にダウンロードするタイプなので、アップロードに時間がかかってしまうのです・・・)

では、さっそく開発を始めましょう!

※ 開発環境: Electron 2.0.5

Electron開発ができるようにする

インストールは、初心者向き!electron で簡単なメモ帳をつくってみよう“electronのインストール” に詳しく書いてあるので、そちらを見てください。

フォルダ名はauto-resizeです。

アプリの設定をする

アプリの設定はmain.jsで行います。

ではやってみましょう!

const electron = require('electron')
const app = electron.app
const BrowserWindow = electron.BrowserWindow

let mainWindow

function createWindow () {

  const screen = electron.screen.getPrimaryDisplay()
  const width = 350
  const height = 350
  const x = parseInt(screen.workAreaSize.width*0.5 - width*0.5)

  // Create the browser window.
  mainWindow = new BrowserWindow({
    width: width,
    height: height,
    x: x,
    y: 0,
    autoHideMenuBar: true
  })

// 省略

まず、モジュール読み込みはモニター(デスクトップ)のサイズを取得するためにelectronが必要なので、3行に分けています。

そして、実際にウィンドウを作るcreateWindow()の中では、ウィンドウのサイズ、メニューバー非表示、そして位置がトップの中央にくるよう計算して設定しています。

これで、このアプリを起動すると画像の位置にウィンドウが現れるようになりました。

レイアウトをつくる

では、アプリのレイアウトをつくりましょう。
やはりここはCSSフレームワークを使ってサクッとつくりたいので次のコマンドでbootstrapをインストールします。

npm install bootstrap --save

また、今回もJSフレームワークにはVueを使うのでこちらもインストールします。

npm install vue --save

では、index.htmlにこの2つをセットしてレイアウトを作ります。(bootstrapはCSSだけ使います)

<!DOCTYPE html>
<html>
<head>
    <title>画像リサイズ</title>
    <meta charset="UTF-8">
    <link rel="stylesheet" href="./node_modules/bootstrap/dist/css/bootstrap.min.css">
    <style>

        .receiving-box-outer {
            display: table;
            width: 100%;
            padding: 15px 0;
        }
        .receiving-box-inner {
            height: 230px;
            color: #777;
            border: 10px dashed #aaa;
            border-radius: 10px;
            display: table-cell;
            vertical-align: middle;
            text-align: center;
        }

    </style>
</head>
<body>
<div id="app" class="container">
    <div class="receiving-box-outer">
        <div class="receiving-box-inner">画像をドラッグ&ドロップしてください。</div>
    </div>
    <div class="row">
        <div class="col-6">
            <label class="text-muted">横幅:</label>
        </div>
        <div class="col-6">
            <label class="text-muted">高さ:</label>
        </div>
    </div>
    <div class="row">
        <div class="col-6">
            <input type="number" min="0" class="form-control" placeholder="例: 640">
        </div>
        <div class="col-6">
            <input type="number" min="0" class="form-control" placeholder="例: 0(自動)">
        </div>
    </div>
</div>
<script>
    require('./renderer.js')
</script>
</body>
</html>

このコードを実行するとこうなります。

JavaScript部分をつくる

では本題の画像をリサイズする部分を作っていきましょう。

Vueの基本形をつくる

まずVue.jsを読み込み基本形を作ります。

<script src="./node_modules/vue/dist/vue.min.js"></script>
<script>

    new Vue({
        el: '#app'
    })

</script>

もちろんHTMLタグへid登録するのも忘れないでください。

<div id="app" class="container">

横幅・高さの入力部分をつくる

サイズ指定の入力ボックスに変数widthheightをそれぞれv-modelでバインディングしてリアルタイムに中身が変更されるようにします。また、初期値も代入します。

<input type="number" min="0" class="form-control" placeholder="例: 640" v-model="width">

<!-- 省略 -->

<input type="number" min="0" class="form-control" placeholder="例: 0(自動)" v-model="height">
new Vue({
    el: '#app',
    data: {
        width: 640,
        height: 0
    }
})

ドラッグ&ドロップのイベントをつくる

では続いて画像がドラッグ&ドロップされたときのイベントをつくります。
ページが表示された時点で必ず実行されるmounted内にコードを書きましょう。

mounted() {

    document.ondragover =
    document.ondragenter =
    document.ondrop =
    document.ondragleave = (e) => {

        if(e.type == 'drop') {

            const files = e.dataTransfer.files
            this.resize(files) // ここで画像をリサイズ

        }

        e.preventDefault();

    }

}

※ ちなみにondropだけでいいような気がしますが、実際にはその他のイベントでe.preventDefault()をしておく必要があります。

画像編集パッケージをインストールする

では、実際に画像をリサイズするコードをつくる前にJavaScriptで画像編集ができるlovell/sharpをインストールしておきましょう。

npm install sharp --save

そして、sharpを読み込みます。

<script>
    require('./renderer.js')
    const sharp = require('sharp');
</script>

(注意)ただし、これだけでは実際にはアプリは起動できなくなります。なぜなら、sharpnodeのネイティブモジュールなので、electron用に再構築する必要があるからです。

そのため、続いて次の作業も実行しておきましょう。
まず再構築パッケージをインストールします。

npm install --save-dev electron-rebuild

そして、次のコマンドで再構築を実行します。

./node_modules/.bin/electron-rebuild

画像リサイズ部分をつくる

まずは、ファイル操作に必要な2つのモジュールfspathを呼び出します。

<script>
    require('./renderer.js')
    const sharp = require('sharp');
    const fs = require('fs');
    const path = require('path');
</script>

そして、methodsresize()を作ってこの中で画像をリサイズしていきます。

methods: {
    resize(files) {

        const width = parseInt(this.width)
        const height = parseInt(this.height)

        if(width > 0 || height > 0) {

            for(file of files) {

                // ファイルタイプ・チェック
                if(!file.type.startsWith('image/')) {

                    continue

                }

                // リサイズ情報
                let dir = path.dirname(file.path)
                let extension = path.extname(file.path)
                let filename = path.basename(file.path, extension)
                let savePath = dir +'/resize-'+ filename + extension
                let size = {
                    width: (width > 0) ? width : null,
                    height: (height > 0) ? height : null,
                    fit: (width > 0 && height > 0) ? sharp.fit.fill : null
                }

                // リサイズ
                sharp(file.path)
                    .resize(size)
                    .toBuffer()
                    .then((data) => {

                        // 保存
                        fs.writeFileSync(savePath, data, 'binary')

                    })

            }

        } else {

            alert('最低でも横幅か高さは指定してください。')

        }

    }
},

やっていることは、filesをループでひとつずつ処理をしながら、

  1. ファイルが画像かどうかをチェック
  2. もし画像ならリサイズ情報をつくる
  3. そして “sharp” を使って画像をリサイズして保存(*1)

※1 ・・・ 画像のファイル名はresize-******になります。

なお、リサイズ情報にあるsharp.fit.fillはアスペクト比(縦横比)を無視する設定です。

テストしてみる

では次の画像を使って実際にテストしてみましょう。

(元画像のサイズ: 300 x 320 ピクセル)

横幅を決めてリサイズする

まずは横幅を150px、高さを0(自動)にしてやってみましょう。
ファイルをアプリの中へドラッグ&ドロップします。

すると新しい画像が作成されました。

実際に作成された画像です。

横幅150px、高さが160pxの画像ができました。
成功です!

高さを決めてリサイズする

では、次は逆に横幅を0(自動)にして、高さ200pxでやってみましょう。

結果はこうなりました。

横幅188px、高さが200pxの画像です。

横幅と高さを決めてリサイズする

では、最後に横幅を300px、高さ150pxでやってみましょう。

結果です。

画像としては歪んでしまいましたが、思い通りにいきました。

おまけ

フォーカスしたら数字を自動選択するようにする

いちいち横幅や高さの数字を消してから入力するのはめんどうなので、フォーカスがあたったら選択状態にするようにします。

<input type="number" min="0" class="form-control" placeholder="例: 1280" v-model="width" @focus="($event.target.select())">
<input type="number" min="0" class="form-control" placeholder="例: 0(自動)" v-model="height" @focus="($event.target.select())">

最後の横幅と高さを自動保存する

毎回サイズを入力するのはめんどうなので、横幅と高さのデータをjsonファイルに保存し、アプリ起動時に前のデータを代入するようにしましょう。

サイズを保存するコード

まず、変数が変化したときに呼ばれるwatchwidthheightを監視するようにし、変化があったらsaveSize()を呼ぶようにします。

watch: {
    width() {
        this.saveSize()
    },
    height() {
        this.saveSize()
    }
},

そして、saveSize()ではこれら横幅と高さ2つのデータをJSONにして保存します。

saveSize() {
    const json = JSON.stringify({
        width: this.width,
        height: this.height
    });
    fs.writeFileSync('size.json', json)
}

サイズを呼び出すコード

まず、保存したJSONファイルからサイズを取得するloadSize()を作ります。

loadSize() {
    if(fs.existsSync('size.json')) {
        const json = fs.readFileSync('size.json')
        const size = JSON.parse(json)
        this.width = size.width
        this.height = size.height
    }
}

そして、mounted()の中でこのloadSize()を呼ぶようにすればOKです。

mounted() {

    this.loadSize()

    // 省略

}

※ なお、説明のために直書きしましたが、size.jsonファイル名は一元管理すべきなのでdataに入れておくべきです。

data: {
    width: 640,
    height: 0,
    sizeJsonFile: 'size.json'
},

教材ソースコードをダウンロードする

今回実際に開発したソースコード一式を以下からダウンロードすることができます。

※ ただし、npmのパッケージなどはご自身でインストールしてください。(npm install でOKなはずです)

Electron 画像をドラッグ&ドロップするだけでリサイズするアプリ