LoginSignup
12
9

More than 3 years have passed since last update.

NodeJSの画像処理ライブラリ「sharp」を使って画像を連結する

Last updated at Posted at 2020-01-13

はじめに

NodeJSでImageMagickのappendのようにサイズがバラバラの画像を単純に横並びで連結させたい機会があったのですが、単純すぎて中々記事が見つからなかったので備忘録的な意味合いも込めて書き残します。

やること

  • sharpで画像情報取得、連結

バージョン

NodeJS: v12.14.1
sharp: v0.23.4

成果物

出来上がる画像

output.png

ソースコード

terminal
yarn init -y
yarn add sharp

ソースはこちら
https://github.com/engabesi/appendImages

まず全文を貼ります。

index.js
const sharp = require("sharp");

(async () => {
  const imagePaths = ["images/1.jpg", "images/2.jpg", "images/3.jpg"];
  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)
    .toFile("output.png");
})();

これでnode index.jsで実行すると上記のように左上詰めで画像が連結されます。

解説

まずsharpをimportします。
sharpについて詳しくは公式Documentを見てください。
https://github.com/lovell/sharp
https://sharp.pixelplumbing.com/en/stable/

const sharp = require("sharp");

画像情報取得

連結する画像パスの配列と画像情報を格納する配列を宣言します。

const imagePaths = ["images/1.jpg", "images/2.jpg", "images/3.jpg"];
const imageAttrs = [];

画像のwidth, height, bufferを取得するPromiseを作成し、Promise.allで全画像分並列実行させます。
Promise.allthenで先程宣言した画像情報を格納する配列に取得した情報をpushします

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));
});

なぜPromise実行時(imagePromise(path))のthenで処理しないのかですが、こちらのthenは実行が完了した順に走ることになります。
今回は連結される並びを固定にしたいのでPromise.allthenで処理を行っています。

出力画像設定

widthには全画像のwidth合計値、heightには全画像のheightの最大値を取得します

const outputImgWidth = imageAttrs.reduce((acc, cur) => acc + cur.width, 0);
const outputImgHeight = Math.max(...imageAttrs.map(v => v.height));

sharpcompositeというメソッドで画像の結合処理等を行います。
そのcompositeメソッドのparam設定を行います

let totalLeft = 0;
const compositeParams = imageAttrs.map(image => {
  const left = totalLeft;
  totalLeft += image.width;
  return {
    input: image.buf,
    gravity: "northwest",
    left: left,
    top: 0
  };
});

inputに画像のbufferを入れます。
gravityは方角で指定します。今回左上詰めで連結させるのでnorthwestを入れます。
lefttopはoffsetをpx単位で設定します。
詳しくはsharpの公式Docを読んでください。
https://sharp.pixelplumbing.com/en/stable/api-composite/

画像出力

最後にsharpで画像を出力します。

sharp({
  create: {
    width: outputImgWidth,
    height: outputImgHeight,
    channels: 4,
    background: { r: 255, g: 255, b: 255, alpha: 0 }
  }
})
  .composite(compositeParams)
  .toFile("output.png");

これでoutput.pngがrootに作成されます。
連結の余白部分が透明でなくてもよかったり、サイズを削減したい場合は、
channelsを4から3に変更してbackgroundのalphaを消したり、
出力画像拡張子をjpgに変更等好きなように変更してください。

まとめ

これでrejectを使ってエラーハンドリングをしていない等、色々ゴリ押し気味ですが画像連結処理を実装できました。
一応車輪の再発明をしていないかとドキュメントを眺めましたがそれらしい処理が見当たりませんでした。
もしあれば教えていただけると助かります。

12
9
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
9