EagleLand

2015.01.06

画像をdataURIに変換するライブラリをES6で書きなおす

だいぶ前に書いたコードを掘り当ててそれをES6で書き直したのと、エディタのバグらしき現象にぶつかった話。ライブラリはファイルパスを渡して、Canvasに突っ込んだ後、データをdataURIにして出力する。インターフェースがPromise。特に変わったことしてないです(。-人-。)ゴメンナサイ

ちなみに、リポジトリは1000ch/image-encoderです。

ES5で書かれた実装

ES5と言っておきながらPromiseを使っている点には突っ込まないこと。

(function (window) {

  function ImageEncoder(path, width, height) {
    this.path = path || '';
    this.width = width || 1;
    this.height = height || 1;
  }

  // ...

  ImageEncoder.prototype.getDataURI = function () {

    var that = this;

    return new Promise(function (resolve, reject) {

      var image = new Image();
      image.setAttribute('crossOrigin','anonymous');

      var onLoad = function (e) {

        var canvas = document.createElement('canvas');
        canvas.width = that.width ? that.width : image.width;
        canvas.height = that.height ? that.height : image.height;

        var context = canvas.getContext('2d');
        context.drawImage(image, 0, 0, canvas.width, canvas.height);

        image.removeEventListener('load', onLoad);
        image.removeEventListener('error', onError);

        resolve(canvas.toDataURL('image/png', 1));
      };

      var onError = function (e) {

        image.removeEventListener('load', onLoad);
        image.removeEventListener('error', onError);

        reject(e);
      };

      image.addEventListener('load', onLoad);
      image.addEventListener('error', onError);
      image.src = that.path;
    });
  };

  window.ImageEncoder = ImageEncoder;

})(window);

使うには、のような感じ。

new ImageEncoder('image.png').getDataURI().then(function onFulfilled(dataURI) {
  console.log(dataURI);
});

ES6で書きなおした実装

コンストラクタで引数をチェックしていたり、var that = this;はアロー記法のthisキャプチャで無くせそうだなとか。class構文を使って書きなおしたクラスをexportしたり、varletにしたり。色々妄想をしながら書き直す。

やや古いポストだけど、ES6の各種機能等は以下の記事が参考になるかも。

以下がES6で書き直したコード。

export class ImageEncoder {

  constructor(path = '', width = 1, height = 1) {
    this.path = path;
    this.width = width;
    this.height = height;
  }

  // ...

  getDataURI() {

    return new Promise((resolve, reject) => {

      let image = new Image();
      image.setAttribute('crossOrigin','anonymous');

      let onLoad = (e) => {

        let canvas = document.createElement('canvas');
        canvas.width = this.width ? this.width : image.width;
        canvas.height = this.height ? this.height : image.height;
        canvas.getContext('2d').drawImage(image, 0, 0, canvas.width, canvas.height);

        image.removeEventListener('load', onLoad);
        image.removeEventListener('error', onError);

        resolve(canvas.toDataURL('image/png', 1));
      };

      let onError = (e) => {

        image.removeEventListener('load', onLoad);
        image.removeEventListener('error', onError);

        reject(e);
      };

      image.addEventListener('load', onLoad);
      image.addEventListener('error', onError);
      image.src = this.path;
    });
  }
}

かなりJavaっぽく見える…(´・∀・`)

ImageオブジェクトのloadイベントにバインドしているonLoad等はアロー関数で書いている。その手前のPromiseのコンストラクタでもアロー関数を使うことでgetDataURIthisをここでは2段構えで引き継いでいる。

Imageオブジェクトのloadにバインドしていると、イベントハンドラの呼び出し元オブジェクトはImageになるはずなのに、こうやってアロー記法でキャプチャしてしまうのも見通しが良くないような気もする(ネストしていると尚更)。

トランスパイル

ECMAScript 6から5へのトランスパイルだとgoogle/traceur-compilerか、6to5/6to5がメジャーっぽい。

Google Traceur Compiler

traceurはプリビルド以外にも、ランタイムエンジンを入れればES6のコードをそのまま実行できたりするので、ES6のブラウザサポートが進んだ時にランタイムエンジンをロードするコードを外せばそのまま動く!みたいなことが可能(というか、それを想定してのランタイムエンジンかとは思う)。カスタムビルドしないと重いけど。

<script src="https://google.github.io/traceur-compiler/bin/traceur.js"></script>
<script src="https://google.github.io/traceur-compiler/src/bootstrap.js"></script>
<script>
  // ES6のコード
</script>

6to5

こちらも有名所なES6→ES5のトランスパイラ。ES6で書きなおしたImageEncoderを6to5で変換してみる。

# 6to5をグローバルにインストールする
$ npm install -g 6to5

# es6のコードをes5に変換する
$ 6to5 src/image-encoder-es6.js > transpiled.js

変換結果は以下の様になる。

"use strict";

var ImageEncoder = function ImageEncoder() {
  var path = arguments[0] === undefined ? "" : arguments[0];
  var width = arguments[1] === undefined ? 1 : arguments[1];
  var height = arguments[2] === undefined ? 1 : arguments[2];
  this.path = path;
  this.width = width;
  this.height = height;
};

// ...

ImageEncoder.prototype.getDataURI = function () {
  var _this = this;

  return new Promise(function (resolve, reject) {
    var image = new Image();
    image.setAttribute("crossOrigin", "anonymous");

    var onLoad = function (e) {
      var canvas = document.createElement("canvas");
      canvas.width = _this.width ? _this.width : image.width;
      canvas.height = _this.height ? _this.height : image.height;
      canvas.getContext("2d").drawImage(image, 0, 0, canvas.width, canvas.height);

      image.removeEventListener("load", onLoad);
      image.removeEventListener("error", onError);

      resolve(canvas.toDataURL("image/png", 1));
    };

    var onError = function (e) {
      image.removeEventListener("load", onLoad);
      image.removeEventListener("error", onError);

      reject(e);
    };

    image.addEventListener("load", onLoad);
    image.addEventListener("error", onError);
    image.src = _this.path;
  });
};

exports.ImageEncoder = ImageEncoder;

とても綺麗に見える。綺麗に見えるどころか、元のコードに酷似している。この辺はclassがシンタックスシュガーだなと改めて思わされるところ。

変な改行コードが挿入される?

MarkdownはiA Writerを使って書いているけど、このエディタ内でコードを編集して再びWebStormに貼り付けると文法ミス等が無いのに注意される。

所々改行位置に謎のエラーが。これ、実は違う原稿を書いている時にも時折発生していた現象だったけどスルーしていた。

nkfで正体を探る。

$ nkf -Lu src/image-encoder-es6.js > test.js

すると、注意されている箇所で 窶ィ という謎の呪文が現れた。

ググっても有益そうな情報が中々出てこない。cat -eでも正体不明な記号が表示されてよくわからない…。