Native File System APIでローカルの画像をリサイズする

さてさて、このブログを運営している影響で、常に記事にする「ネタ」を探すようになっているのですが、そのおかげで最新情報も知ることが多くなってきました。

そして、この間そんな情報の中に「ある機能」がGoogle Chromeで本格的に使えるようになるということを知りました。

それは何かと言うと、

Native File System API

です。

つまり、PC上にあるローカル・ファイルを直接ブラウザで操作するという機能ですね😲✨

通常ブラウザでローカルのファイルを操作するには一度サーバーにアップロードしてもらうという手順が必要でしたが、この機能を使えば直接ローカルのファイルを操作できるようになります。

つまり、テキストファイルや画像の編集などもブラウザさえあれば実行できるようになるんです。これってすごいですよね。

そこで!

今回はこのNative File System APIを使ってローカルにある画像のサイズを変更して保存できるようにしてみたいと思います。

ぜひ、皆さんのお役に立てると嬉しいです😊✨
(最後に実際のソースコードをダウンロードできますよ♪)

開発環境: Vue 2.6、Bootstrap 4.3(CSSのみ)

やりたいこと

今回開発する機能の手順は次のとおりです。

  1. ローカルの画像を選ぶ
  2. 変更後のサイズ(縦・横)を指定
  3. サイズの変更によってリアルタイムでプレビュー画像を表示
  4. 指定したサイズで画像をローカルに保存

準備する

2019.10.27日現在、Native File System APIはまだ全ての環境で有効にはなっていません。そのため、この機能を有効にするためには以下の2つの方法が用意されています。

  1. 開発者用のトークンを作成してページ(もしくはHTTPヘッダー)に埋め込む
  2. 自分でChromeの設定を変える

1番目で実行すると、ユーザーは特別何もしなくてもNative File System APIが使えるようになるのですが、残念なことにトークンには有効期限があるので今回は2番目の設定を変える方法で実行します。

では、chrome://flags/にアクセスしてください。

するとページ上部に検索ボックスが表示されるので、ここに「native file system api」と入力します。

すると以下のように選別してくれますので、Native File System APIを有効(Enable)にします。

すると、ページ右下に「Relaunch」ボタンが表示されるので、クリックしてブラウザを再起動しましょう。

なお、セキュリティを考慮してかNative File System APIHTTPSでしか動きません。もしローカルにHTTPS環境をつくる場合はコピペでOK!ローカル環境にHTTPSを導入する(nginx編)を参考にしてください。

では、次の項目から実際にプログラムを書いていきます!

基本形をつくる

まずは基本になる部分のHTMLJavaScriptです。

<html>
<head>
    <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div id="app" class="p-3">

    <!-- ここに画像リサイズ・コンテンツ -->

</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.0"></script>
<script>

    new Vue({
        el: '#app',
        data: {
            imageData: null,
            resizingWidth: 0,
            resizingHeight: 0
        }
    });

</script>
</body>
</html>

ここでは、VueBootstrap(CSSのみ)が使えるようにCDNで読み込み、さらにVueのインスタンスには、以下の変数を用意しておきます。

  • imageData ・・・ 選択された画像のデータが入る
  • resizingWidth, resizingHeight ・・・ リサイズする縦横サイズ

画像ファイルを選択する部分をつくる

では、ローカル環境にある画像を選択する部分を作っていきましょう。

<div id="app" class="p-3">
    <button class="btn btn-primary" @click="chooseFile">リサイズする画像を選択</button>
</div>

HTML側にはボタンを作り、クリックした時に実行されるメソッドをVue側に追加します。

new Vue({

    // 省略

    computed: {
        imageContents() {

            if(this.imageData !== null) {

                // img タグが読み込める形式に変換
                return URL.createObjectURL(this.imageData);

            }

        }
    },
    methods: {
        async chooseFile() {

            const options = {
                accepts: [{
                    mimeTypes: ['image/png', 'image/jpeg', 'image/gif']   // 画像だけ許可
                }],
            };
            const handle = await window.chooseFileSystemEntries(options);
            this.imageData = await handle.getFile();

            let img = new Image();
            img.onload = () => {

                // 画像のサイズを取得
                this.resizingWidth = img.width;
                this.resizingHeight = img.height;

            };
            img.src = this.imageContents;

        }
    }
});

まず、この中で重要なのがcomputedにあるimageContents()です。

ここでは、選択された画像データはそのままでは<img>タグでは表示できないので、URL.createObjectURL()を使って変換をしています。

そして、chooseFileSystemEntries()が画像を選択する部分で、もし選択されたらgetFile()を実行し、中身を変数imageDataに格納しています。

※ このimageDataが元になってimageContents()でデータ変換することになります。

さらに、このままでは選択された画像の縦横サイズがわかりませんので、new Image()を使ってそれらを取得しています。

選択画像のプレビュー部分をつくる

続いては、選択された画像をプレビュー表示する部分です。

<div id="app" class="p-3">

    <!-- 省略 -->

    <div v-if="imageContents">
        <hr>
        <div class="my-3">
            変更後のサイズは??<br>
            <input type="number" min="1" v-model="resizingWidth"> × <input type="number" min="1" v-model="resizingHeight">
            &nbsp;
            <button class="btn btn-success" @click="save">保存する</button>
        </div>
        <strong>画像プレビュー</strong>
        <br>
        <img :style="previewStyles" :src="imageContents">
    </div>
</div>

ここでやっているのは、まず先ほどつくったimageContents()

の中身が存在しているかどうかをv-ifでチェックし、もし存在していれば(つまり、画像が選択されていれば)プレビュー画像を表示するようにしています。

また、プレビュー画像の上には縦横のサイズを指定できるよう入力ボックスを2つ用意し、v-modelを使ってVueの変数と連動するようにしています。

そして、この縦横サイズは指定された数値によってリアルタイムにプレビュー画像に反映されるようにpreviewStylesというメソッドを指定するようにし、さらに、画像を保存するためのボタンにはsaveというメソッドも指定しています。

これら2つのメソッドはこうなります。

new Vue({

    // 省略

    computed: {

        // 省略

        previewStyles() {

            let styles = {};

            if(this.resizingWidth > 0) {

                // 横幅を 300px にしたときの高さを計算
                const width = 300;
                const height = width / this.resizingWidth * this.resizingHeight;
                styles = {
                    width: width +'px',
                    height: height +'px',
                    border: '3px solid #000'
                };

            }

            return styles;

        }
    },
    methods: {

        // 省略

        async save() {

            let canvas = document.createElement('canvas');
            let ctx = canvas.getContext('2d');
            canvas.width = this.resizingWidth;
            canvas.height = this.resizingHeight;

            let img = new Image();
            img.onload = () => {

                // canvasにリサイズした画像を描画
                ctx.drawImage(img, 0, 0, this.resizingWidth, this.resizingHeight);
                canvas.toBlob(this.saveImage);

            };
            img.src = this.imageContents;

        },
        async saveImage(resizedImageData) {

            const options = {
                type: 'saveFile',   // セーブモード
                accepts: [{
                    mimeTypes: ['image/png']
                }],
            };
            const handle = await window.chooseFileSystemEntries(options);
            const writer = await handle.createWriter();
            await writer.truncate(0);
            await writer.write(0, resizedImageData);
            await writer.close();

        }
    }
});

まずpreviewStyles()では、横幅を300pxに固定した場合高さがいくらになるかを計算し、それをスタイルシートに反映するようになっています。

また、save()では、JavaScript内で<canvas></canvas>を作成し、そこに選択された画像を描画することでリサイズを実行しています。

さらに、実際に画像を保存する部分はsaveImage()メソッドで、ここではセーブモードを指定したchooseFileSystemEntries()を使ってファイルを書き込むことになります。

※ 今回は説明が複雑になるためimage/pngのみを有効にしましたが、toBlob()を以下のようにするとjpegも対応できます。その場合、ラジオボタンなどでpngjpegなど画像形式が選択できるようにするといいかもしれません。

canvas.toBlob(this.saveImage, 'image/jpeg');

お疲れ様でした!

テストしてみる

では、今回開発した内容を実際にテストしてみましょう。

はい!うまくいきました😊✨

実際にリサイズした画像はこちらです。

ダウンロードする

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

※ ただし、準備するで説明した内容を全て実行しておく必要があります。

Native File System APIで画像をリサイズ

おわりに

ということで今回は比較的新しい機能のNative File System APIを使って画像をリサイズする機能を作ってみました。

こういう機能がドンドン有効になってくるとウェブとローカルの垣根ももっと低くなっていきそうです。

ただ、その反面いかに高いセキュリティを実現するかというのも重要になってくるわけで、便利さと危険は切り離せないものなんだなとも感じました。

また、今後Native File System APIを使うとどんなことが実現できるだろうかという部分ですが、web.devの説明書きにはIDE(統合開発環境)や画像エディターなど、と言及してあるので、今後paizacloud 9なんかもこの機能を取り入れていくのかな、なんて思いました。(もしかしてもうやってる???)

とにかく、今回は久しぶりに新しい機能に挑戦したので楽しかったです。

みなさんもぜひやってみてくださいね。

ではでは〜!

この記事が役立ちましたらシェアお願いします😊✨