E2Eテスト 番外編 -シャープで画像連結-

前回の記事では、reg-cliを使った画像差分比較について紹介しました。
[E2Eテストの始め方 番外編 - reg-cliで差分比較 -](https://qiita.com/natsu_mikan/items/692018262b617bb87859)
そして今回は、TestCafeで撮影した複数枚のスクリーンショット画像を連結するための**sharp**について書いていきたいと思います。

#sharp とは
某電機メーカーが強すぎてググラビリティが残念なライブラリ。。。:weary:
同じようなライブラリとしてImageMagickやGraphicsMagickも有名ですが、ドキュメントによるとそれらより**4倍~5倍速い**そうです。
読み込みはJPEG,PNG,WebP,TIFF,GIF,SVGをサポート。
様々なサイズの**JPEG,PNG,WebPに変換可能**で、リサイズ以外に**回転、抽出、合成、ガンマ補正、圧縮などの操作も可能**となっています。
今回sharpを選んだ理由としてImageMagickらはしばらくメンテナンスされていない(sharpは執筆時点で10h前と活発に動いてる)ことと、処理の速さでsharpを採用しました。

**シャープならサイズも画質も自由に選べる！**
![sharp.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/174180/3890d4ba-8a60-f5c2-a8d0-769a73c93c49.png)



##install

```
$ npm install sharp
```

##usage
```convert.js
const sharp = require('sharp');
```

####【ソース全文】

```convert.js

const fs = require('fs')
const config = require('../config')
const { imgPath, imgPc, imgSp  } = config;

//出力先フォルダの作成
const makeDir = () => {
  fs.mkdir('screenshots/convert', { recursive: true }, (err) => {
    if (err) throw err;
  });
}

//sharp
const convert = async (imagePaths, imageName) => {
  const imageAttrs = [];

  // 連結する画像の情報取得
  const promises = [];
  const imagePromise = path =>
    new Promise(async resolve => {
      const image = await sharp(path);
      let width = 0,
          height = 0;
      await image
        .metadata()
        .then(meta => ([width, height] = [meta.width, meta.height]));
      const buf = await image.toBuffer();
      resolve({ width, height, buf });
    });
  imagePaths.forEach(path => promises.push(imagePromise(path)));
  await Promise.all(promises).then(values => {
    values.forEach(value => imageAttrs.push(value));
  });

  // outputする画像の設定
  const outputImgWidth = imageAttrs.reduce((acc, cur) => acc + cur.width, 0);
  const outputImgHeight = Math.max(...imageAttrs.map(v => v.height));
  let totalLeft = 0;
  const compositeParams = imageAttrs.map(image => {
    const left = totalLeft;
    totalLeft += image.width;
    return {
      input: image.buf,
      //合成場所
      gravity: "northwest",
      left: left,
      top: 0
    };
  });

  // 連結処理
  sharp({
    create: {
      width: outputImgWidth,
      height: outputImgHeight,
      channels: 4,
      background: { r: 255, g: 255, b: 255, alpha: 0 }
    }
  })
    //合成
    .composite(compositeParams)
    //圧縮
    .png({
      quality: 80,
      compressionLevel: 9
    })
    .toFile(`screenshots/convert/${imageName}.png`,(err, info)=>{
      if(err){ throw err }
      console.log(info)
    });
}

(async () => {
  await
    makeDir()
    convert(imgPc, 'pc')
    convert(imgSp, 'sp')
})()
```

###解説

まず、出力先のフォルダを作成します。
フォルダがないと「どこに保存するのか分からないよ！」と怒られてしまうので、sharpを実行する前にフォルダを用意してあげます。

```convert.js
//出力先フォルダの作成
const makeDir = () => {
  fs.mkdir('screenshots/convert/corporate', { recursive: true }, (err) => {
    if (err) throw err;
  });
}
```

画像の情報取得や連結処理などのソースはこちらを参考にさせていただきました。
[NodeJSの画像処理ライブラリ「sharp」を使って画像を連結する](https://qiita.com/engabesi/items/0ec72ecc2b1a6d65cf75)

私は、連結したい画像がたくさんあるため`config.js`に画像パスの配列を記述し呼び出して使っています。
第一引数`imagePaths`に呼び出した画像パスの配列`imgPc`と`imgSp`を指定し、
第二引数のimageNameには出力される画像のファイル名を指定します。
(デフォルトのピクセル制限(268402689)を超えてしまうので複数枚に分けて出力しています)

```convert.js
const config = require('../config')
const { imgPath, imgPc, imgSp  } = config;

const convert = async (imagePaths, imageName) => {
 
 .toFile(`screenshots/convert/corporate/${imageName}.png`,(err, info)=>{
  });
}

(async () => {
  await
    convert(imgPc, 'pc')
    convert(imgSp, 'sp')
})()
```

```config.js
const imgDir = `screenshots/${year}${month}${date}/`

exports.imgPc = [
  `${imgDir}/top_pc.png`,
  `${imgDir}/company_pc.png`,
  `${imgDir}/company_access_pc.png`,
];
exports.imgSp = [
  `${imgDir}/top.png`,
  `${imgDir}/company.png`,
  `${imgDir}/company_access.png`,
];
```

####圧縮
生成された画像の圧縮も設定可能です。

#####jpeg

```convert.js
.jpeg({
  quality: 80
})
```
`options.quality`：整数1〜100,デフォルト80
`options.compressionLevel`：zlib圧縮レベル、0〜9（デフォルト9）


#####png

```convert.js
.png({
  quality: 100,
  compressionLevel: 9
})
```
`options.quality`：デフォルト`100`
`options.compressionLevel`：zlib圧縮レベル、0〜9（デフォルト`9`）

#####webp

```convert.js
.webp({
    quality: 80,
    lossless: true
})
```
`options.quality`：整数1〜100,デフォルト`80`
`options.lossless`：可逆圧縮モード,デフォルト`false`

このほか、gifやtiff,heifも対応可能でオプションで細かい設定もできるので詳細は公式をご覧ください。
https://sharp.pixelplumbing.com/api-output


###実行
```
$ node e2e/convert.js
```
###完成！
横並びにがっちゃんこして出力してくれました！
各ページフルサイズのスクリーンショットを連結させると出力サイズがかなり大きくなるので圧縮率を大きくしたり、連結枚数を少なくして何枚かに分けるなど工夫が必要ですが良い感じにできたのではないかと思います:ok_hand:
![sample.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/174180/65acd5cf-0000-6e47-8c4a-86b8f91abacc.png)


###参考
[NodeJSの画像処理ライブラリ「sharp」を使って画像を連結する](https://qiita.com/engabesi/items/0ec72ecc2b1a6d65cf75)

###公式

[sharp](https://sharp.pixelplumbing.com/)
[GitHub](https://github.com/lovell/sharp)

####SHARP Be Original
[SHARP](https://jp.sharp/)