治安の悪い Slack Emoji を作るツールを作った

(治安の悪くない Emoji も作れます)

# 作ったもの

![emoji1.gif](https://qiita-image-store.s3.amazonaws.com/0/54240/8104cbfe-6bcf-b196-fb08-c8ada9283939.gif) ![emoji3.gif](https://qiita-image-store.s3.amazonaws.com/0/54240/96c20515-63b9-d49c-f4d2-469aab8da26b.gif) ![emoji4.gif](https://qiita-image-store.s3.amazonaws.com/0/54240/159205ba-0d10-2dc6-25bc-3d1befcdcfdb.gif) ![emoji5.gif](https://qiita-image-store.s3.amazonaws.com/0/54240/cdbb8bed-ea39-f214-b9b2-32ce4c83ee0f.gif)
 ![emoji2.gif](https://qiita-image-store.s3.amazonaws.com/0/54240/820f77cc-9090-aebb-7f8f-1b8521890ac5.gif)![emoji2.gif](https://qiita-image-store.s3.amazonaws.com/0/54240/820f77cc-9090-aebb-7f8f-1b8521890ac5.gif)![emoji2.gif](https://qiita-image-store.s3.amazonaws.com/0/54240/820f77cc-9090-aebb-7f8f-1b8521890ac5.gif)![emoji2.gif](https://qiita-image-store.s3.amazonaws.com/0/54240/820f77cc-9090-aebb-7f8f-1b8521890ac5.gif)

### ここで遊べます

https://zk-phi.github.io/MEGAMOJI/

## おもしろいところ

GIF アニメのエンコードまですべて js で完結しているので、ありがちな「謎のサーバーに画像アップロードするといい感じに変換してくれる」的なサービスと違って、素性の知れたコードがクライアント側でサクサク動きます。

# なにができるの？
## 画像を 128px x 128px に変形

画像を、 Slack にアップロードできる（現状）最大サイズの 128px x 128px に変形します。

ローカルのファイルから選ぶか、画像の URL を入力できます。アップロードするわけではないので、デカい画像でもサクサクなのがお気に入りです。

<img width="952" alt="スクリーンショット 2018-06-18 1.00.26.png" src="https://qiita-image-store.s3.amazonaws.com/0/54240/7808e95d-95f2-21b4-eb65-7ea57011d0cc.png">

変形は

+ 正方形に引き伸ばし（アス比無視）
+ 正方形いっぱいに拡大して、余ったところはトリミング（アス比維持）
+ 正方形に収まるように縮める（アス比維持）

から選べます。

<img width="951" alt="スクリーンショット 2018-06-18 1.05.12.png" src="https://qiita-image-store.s3.amazonaws.com/0/54240/f69e8df6-5317-a08a-22b5-51d0eb9a41e4.png">

## テキストから画像生成

既存の画像を使う代わりに、テキストから画像を生成することもできます。

生成したテキスト画像も、普通の画像と同じく、引き伸ばしたり、縮小したりして正方形に収められます。

フォントは CSS っぽい感じで指定します。

<img width="951" alt="スクリーンショット 2018-06-18 1.23.03.png" src="https://qiita-image-store.s3.amazonaws.com/0/54240/c2594516-dccc-68d8-ce66-12d85a5d0eb6.png">

## アニメ

GIF アニメを作ります。節度を持ってどうぞ 😇

Slack にアップロードできるファイルサイズ制限の都合で、解像度が 1/4 (64px x 64px) になります。

### キラ

![emoji1.gif](https://qiita-image-store.s3.amazonaws.com/0/54240/8104cbfe-6bcf-b196-fb08-c8ada9283939.gif)

絵文字がスーパーレアになります。パーティ感が出ます。

白、黒など無彩色のところはキラキラしないので、うまく使ってください。

### スクロール

![emoji2.gif](https://qiita-image-store.s3.amazonaws.com/0/54240/c90d9681-c422-7cb9-71e9-188df9dbbecd.gif)

絵文字がスクロールします。複数並べてループできます。

![emoji2.gif](https://qiita-image-store.s3.amazonaws.com/0/54240/654df786-8561-556d-25c1-122018ee952f.gif)![emoji2.gif](https://qiita-image-store.s3.amazonaws.com/0/54240/654df786-8561-556d-25c1-122018ee952f.gif)![emoji2.gif](https://qiita-image-store.s3.amazonaws.com/0/54240/654df786-8561-556d-25c1-122018ee952f.gif)![emoji2.gif](https://qiita-image-store.s3.amazonaws.com/0/54240/654df786-8561-556d-25c1-122018ee952f.gif)

### ぐるぐる

![emoji3.gif](https://qiita-image-store.s3.amazonaws.com/0/54240/91366015-bf57-6aaa-d166-67394e2b7577.gif)

絵文字が回転します。

### ガタガタ

![emoji5.gif](https://qiita-image-store.s3.amazonaws.com/0/54240/899c0d7a-4f1e-2045-ace8-1477d67bf8fe.gif)![emojix.gif](https://qiita-image-store.s3.amazonaws.com/0/54240/e1ce0cb4-aae6-b773-dda1-3ee97458e086.gif)

絵文字がガタガタします。飲みたくて飲みたくて震える

### and more ...

プルリク待ってます✨

## 巨大 Emoji

画像を複数の正方形に分割して、連結すると一枚の絵になるような Emoji が作れます


![emoji6.png](https://qiita-image-store.s3.amazonaws.com/0/54240/228d0392-3706-285a-5915-c5dcdfc71ac0.png)
![emoji7.png](https://qiita-image-store.s3.amazonaws.com/0/54240/8f2ab54c-824c-43bd-b6fc-f5301968d39e.png)

巨大 Emoji にもアニメーションをつけられます (ロードのタイミングの都合で微妙に連動しないことがあります)。

![emoji8.gif](https://qiita-image-store.s3.amazonaws.com/0/54240/29c78766-8ccc-3beb-3fd8-18641d5ced09.gif)
![emoji9.gif](https://qiita-image-store.s3.amazonaws.com/0/54240/a459999e-318a-cb4c-eae0-96a940d73d32.gif)

# 技術的な話

申し訳程度に技術的にハマったところとか学んだところとか。

## `<img>` の `crossorigin` プロパティ

`img` タグは `src` プロパティにヨソの URL を設定してもちゃんと読んでくれるのですが、読んだ画像を `canvas` に転写してゴリゴリ変形しようとかやると、同一生成元ポリシが発動して無が転写されます。

適切なヘッダが設定されていれば、 `img` に `crossorigin` プロパティを設定するだけで CORS できるようなので、それを使いましたが、気付くまではハマりました。

## `canvas` で文字列画像を作る

`canvas` で文字列画像を作るのが地味に大変というのがわかりました。

+ `canvas.ctx.fillText`　が複数行テキストに対応してない

文字通り、対応していないので、一行づつレンダリングする必要があります。しかもテキストの正確な高さが取れないので、二行目をどこからレンダリングしたらいいかもよくわかりません。 phina.js が `canvas.ctx.measureText('あ')` (`あ` の横幅) を高さとして使っていたので、今はそれを ~~パクっ~~ 参考にしています。

+ `canvas` 自体のサイズがちょっとでも足りてないと謎の物体がレンダリングされる

当初は、 `canvas.ctx.measureText` でテキストの幅を、上の方法でテキストの高さを、それぞれ事前に計算して、ぴったりサイズの `canvas` にレンダリングしていたのですが、謎の物体がレンダリングされて困っていました。 

どうにもうまくいかなかったので、諦めてデカめの `canvas` に一度レンダリングしてからトリミングしています。

## JS で GIF アニメをエンコードする

技術的には一番難しいところなんですが、ここはさすがにライブラリ頼みです。

jsgif: https://github.com/antimatter15/jsgif

これが使いやすそうだったので、これを submodule にして使っています。「`gh-pages` でも submodule は使えるけど、 `git@...` ではなく `https://...` でないとダメ」という学びがありました。

```javascript
var encoder = new GIFEncoder();

encoder.setRepeat(0);     // リピート回数 (0 は無限)
encoder.setFrameRate(20); // フレームレート決める

encoder.start();

for (...) {
    // ... canvas で何かレンダリングする ...
    encoder.addFrame(ctx);
}

encoder.finish();
```

こんな感じで一つ一つのフレームをレンダリングしては `encoder.addFrame` していけば、謎の技術によって GIF アニメにエンコードされます。

```javascript
img.src = "data:image/gif;base64," + encode64(encoder.stream().getData());
```

僕の作ったツールでは、できた GIF アニメをさらに data URL にエンコードして、 `img` タグの `src` にすることでユーザーに返しています。

# まとめ

`canvas` は ~~つらい~~ 楽しい!!

というわけで楽しい Emoji ライフを！病的なエフェクトを思いついたらぜひ[プルリク](https://github.com/zk-phi/MEGAMOJI/pulls)ください！