Cloud FunctionsでImageMagick(aheckmann/gm)を使う

```json:package.json
  "dependencies": {
    ...
    "axios": "^0.19.0",
    "gm": "^1.23.1"
  },    
  "devDependencies": {
    ...
    "@types/gm": "^1.18.3",
  }
```

```typescript

import axios from 'axios';
import * as gm from 'gm';
const im = gm.subClass({ imageMagick: true });

export class ImageToSmallPng {

    convert = async (imageUrl: string): Promise<Buffer> => {
        const res = await axios.get(imageUrl, { responseType: 'arraybuffer' });
        const data: ArrayBuffer = res.data;
        const image = im(Buffer.from(data));
        const png = await this.toPngBuffer(image);
        return png;
    }

    toPngBuffer = async (image: gm.State): Promise<Buffer> => {
        return new Promise((resolve, reject) => {
            image.resize(16, 16).toBuffer('PNG', (err, buffer) => {
                if (err) {
                    reject(err);
                } else {
                    resolve(buffer);
                }
            })
        });
    }
}
```

みたいな。諸事情で結局使わなかったコードなので供養。もしもgmを使わず直にやるなら

```typescript

import axios from 'axios';
import { exec } from 'child_process';
import * as fs from 'fs';
import * as utils from '../utils';

export class ImageToSmallPng {

    convert = async (imageUrl: string): Promise<Buffer> => {
        const res = await axios.get(imageUrl, { responseType: 'arraybuffer' });
        const data: ArrayBuffer = res.data;

        const tmpSource = '/tmp/' + utils.sha1(iconUrl);
        const tmpDist = `${tmpSource}.png`;
        await this.writeFile(tmpSource, Buffer.from(data));
        await this.toSmallPng(tmpSource, tmpDist);
        return this.readFile(tmpDist);
    }

    toSmallPng = async (tmpSource: string, tmpDist: string): Promise<string> => {
        return new Promise((resolve, reject) => {
            const template = `convert "${tmpSource}" -thumbnail 16x16 -alpha on -background none -flatten "${tmpDist}"`;
            exec(template, {}, (err, result) => {
                if (err) {
                    reject(err);
                } else {
                    resolve(result);
                }
            })
        });
    }

    writeFile = (path: string, data: Buffer): Promise<string> => {
        return new Promise((resolve, reject) => {
            fs.writeFile(path, data, {}, (err) => {
                if (err) {
                    reject(err);
                } else {
                    resolve();
                }
            })
        });
    }

    readFile = (path: string): Promise<Buffer> => {
        return new Promise((resolve, reject) => {
            fs.readFile(path, (err, result) => {
                if (err) {
                    reject(err);
                } else {
                    resolve(result);
                }
            })
        });
    }
}
```

とかなんとか。なおtmpSource/tmpDistに任意のユーザー入力が混ざったらもちろんやばいし、アプリケーション側でもurl先の画像形式を見てホワイトリストした方が良いのではないかと思う。

ちなみに、ちゃんとは確認がとれてないのだけれどcloud functionsに入っているImageMagickは対応する画像形式が結構絞られていて、少なくともfaviconなどで使われるico形式(Microsoft icon)は2019/6/26現在サポートされていない。たぶん。

###See Also
+ https://cloud.google.com/functions/docs/tutorials/imagemagick?hl=ja
+ https://github.com/GoogleCloudPlatform/nodejs-docs-samples/tree/master/functions/imagemagick
+ https://aheckmann.github.io/gm/docs.html
+ https://qiita.com/yoya/items/2076c1f5137d4041e3aa