🖼

ブラウザで画像圧縮が出来るbrowser-image-compressionとその内部実装についてと他ライブラリとの比較

2024/09/11に公開

会社名を出して記事を発信するのは初めてとなります、RUN.EDGE株式会社のフロントエンドエンジニアの猪野浩子と申します。
今回RUN.EDGEのテックブログをZennのPublicationで始めまして、一発目の記事になります。
これから各メンバーが記事を書いていきますので、よろしくお願いします!

今回は普段の実装で使ったライブラリについての記事になります。

本題

今回要件として、ユーザーがブラウザ上で動作するアプリからアップロードする画像ファイル(jpg, png)を、アップロード前に1MBを上限に圧縮したいというのがありました。
1MB以下のものは圧縮しない、拡張子はそのままという要件もあり、ライブラリで手軽に済ませることになりました。

🤔画像圧縮ライブラリの比較

画像圧縮ライブラリに関しては、自分の調べた限りではbrowser-image-compression、compressorjs、image-blob-reduceあたりがヒットしました。

https://npmtrends.com/browser-image-compression-vs-compressorjs-vs-image-blob-reduce

各ライブラリを簡単に比較すると、このような感じになります。

browser-image-compression

https://www.npmjs.com/package/browser-image-compression

  • 2024/08/05時点でWeekly Downloadsが170,417
  • 最終更新: 一年前
  • 組み込みのTypeScript型定義が含まれている(@typeパッケージ不要)
  • png画像などのjpg以外の拡張子の圧縮も可能
  • 圧縮後の拡張子をoptions.fileTypeで指定可能

また、内部実装を確認した限りでは、以下になります。

  • imageCompressionという単一の関数を提供している
  • imageCompressionはFileとオプションを引数に受け取り、Promise<File>を返す
  • options.maxSizeMB以下にファイルサイズがなるまでクオリティを落としながら圧縮処理をループする
公式のサンプルコード
async function handleImageUpload(event) {

  const imageFile = event.target.files[0];
  console.log('originalFile instanceof Blob', imageFile instanceof Blob); // true
  console.log(`originalFile size ${imageFile.size / 1024 / 1024} MB`);

  const options = {
    maxSizeMB: 1,
    maxWidthOrHeight: 1920,
    useWebWorker: true,
  }
  try {
    const compressedFile = await imageCompression(imageFile, options);
    console.log('compressedFile instanceof Blob', compressedFile instanceof Blob); // true
    console.log(`compressedFile size ${compressedFile.size / 1024 / 1024} MB`); // smaller than maxSizeMB

    await uploadToServer(compressedFile); // write your own logic
  } catch (error) {
    console.log(error);
  }

}
圧縮処理の主要箇所

compressorjs

https://www.npmjs.com/package/compressorjs

  • 2024/08/06時点でWeekly Downloadsが137,705
  • 最終更新: 一年前
  • 組み込みのTypeScript型定義が含まれている(@typeパッケージ不要)

また、内部実装を確認した限りでは、以下になります。

  • Compressorという単一のクラスを提供している
  • CompressorはFile | Blobとオプションを受け取る
  • options.convertSizeというオプションはあるが、そのバイト以上だった場合にjpgに変換してoption.qualityで圧縮する処理なのでoptions.convertSize以上になることもある
  • Compressorのインスタンス生成のオプションに渡したoptions.successコールバック関数の中で圧縮後のファイルを処理する。options.successコールバックはFile | Blobを受け取る
  • 失敗などの場合、オプションで渡したoptions.errorコールバックが呼ばれる。options.errorコールバックはErrorを受け取る。options.errorコールバックがなければスローする
公式のサンプルコード
import axios from 'axios';
import Compressor from 'compressorjs';

document.getElementById('file').addEventListener('change', (e) => {
  const file = e.target.files[0];

  if (!file) {
    return;
  }

  new Compressor(file, {
    quality: 0.6,

    // The compression process is asynchronous,
    // which means you have to access the `result` in the `success` hook function.
    success(result) {
      const formData = new FormData();

      // The third parameter is required for server
      formData.append('file', result, result.name);

      // Send the compressed image file to server with XMLHttpRequest.
      axios.post('/path/to/upload', formData).then(() => {
        console.log('Upload success');
      });
    },
    error(err) {
      console.log(err.message);
    },
  });

});
圧縮処理の主要箇所

image-blob-reduce

https://www.npmjs.com/package/image-blob-reduce

  • 2024/08/06時点でWeekly Downloadsが29,746
  • 最終更新: 3年前
  • 要@typeパッケージ
  • picaというライブラリのラッパーで、fileのinputからのデータを扱うならこちら(image-blob-reduce)を使う
  • picaの説明文:ブラウザで画像をピクセル化せずに、しかも高速にリサイズします。 webworkers、webassembly、createImageBitmap、ピュアJSなど、利用可能な技術の中から最適なものを自動的に選択します。(訳)
  • フロントでもNode.jsでも使用可能
  • pica.toBlobを使えばquality、mimeTypeの指定は可能
  • maxは画像サイズを指定
公式のサンプルコード
const reduce = require('image-blob-reduce')();

//...

reduce
  .toBlob(image_blob, { max: 1000 })
  .then(blob => { ... });
picaのtoBlobの箇所

上記を踏まえて、比較したところ、下記の点においてbrowser-image-compressionを選択しました。

  • png画像などのjpg以外の拡張子の圧縮も可能 : 透過画像も扱うので必須
  • 圧縮後の拡張子をoptions.fileTypeで指定可能: 今回は圧縮後の拡張子を指定しませんでしたが、後ほど必要になる可能性があります
  • options.maxSizeMB以下にファイルサイズがなるまでクオリティを落としながら圧縮処理をループする : 確実にmaxSizeMB以下に圧縮できる

圧縮処理がループするので、場合によっては圧縮に時間がかかることもあると思いますが、そちらは許容範囲でした。
今回、ファイルインプットからのライブラリの使用になるので、直前の状態はFileだったので困りませんでしたが、Blobの場合はnew File(...)処理が必要になります。

アップデート頻度がそこまでどのライブラリも高くないのは気になりますが、もし最近のライブラリをご存じでしたら知りたいのでコメントいただけると幸いです。

実際にどう使ったのか

改めて要件を説明すると、1MBを最大サイズにしたいので、下記のようになります。

import imageCompression from 'browser-image-compression'

...

const options = {
  maxSizeMB: 1,
  useWebWorker: true,
}
try {
  const compressedFile = await imageCompression(file, options) // 戻り値の中身はFile
  // ...compressedFileを使ったその後の処理
} catch (error) {
  // ...例外処理
}

これだけで処理が完了します。
今回の要件としては、最適なライブラリだったかと思います。

おわりに

各ライブラリにはそれぞれ利点がありますので、各々機能や処理で好ましいと思ったものを使っていただけるとよいのかなと思います。
テックブログ一発目の記事でしたが、いかがでしたでしょうか。感想・指摘・いいねいただけると嬉しいです!

RUN.EDGE株式会社

Discussion