canvas、gm、jimp、sharp は、Node.js 環境で画像の生成、変換、編集を行うための代表的なライブラリです。それぞれアプローチが異なり、sharp は高性能なネイティブバインディングを提供し、canvas はブラウザの Canvas API をサーバーで再現します。jimp は純粋な JavaScript で動作し依存関係が少なく、gm は GraphicsMagick または ImageMagick のシステムコマンドをラップします。プロジェクトの要件(速度、機能、デプロイ環境)に応じて適切な選択が必要です。
Node.js で画像処理を行う場合、canvas、gm、jimp、sharp の 4 つが主要な選択肢となります。これらはすべて「画像を扱う」という点では共通していますが、内部アーキテクチャ、パフォーマンス、そして得意とするユースケースは大きく異なります。アーキテクトとして、単に「画像をリサイズしたい」という要件だけでなく、デプロイ環境の制約や処理規模を考慮して選定する必要があります。
ライブラリの内部構造を理解することは、トラブルシューティングやデプロイ設計において重要です。
sharp は、C++ で書かれた画像処理ライブラリ「libvips」のラッパーです。
# sharp のインストール
npm install sharp
# 環境によっては node-gyp によるビルドが走ります
canvas は、Cairo グラフィックスライブラリを使用しています。
# canvas のインストール
npm install canvas
# Ubuntu などの場合、libcairo2-dev などのシステムパッケージが必要
jimp は、純粋な JavaScript(TypeScript)で書かれています。
# jimp のインストール
npm install jimp
# ネイティブモジュールのビルドは発生しません
gm は、GraphicsMagick または ImageMagick のコマンドラインツールをラップします。
# gm のインストール
npm install gm
# 別途、OS に graphicsmagick または imagemagick のインストールが必須
画像処理において、速度とメモリ使用量は直接的なコストに影響します。
sharp は圧倒的に高速です。
// sharp: 高速なリサイズと変換
import sharp from 'sharp';
await sharp('input.jpg')
.resize(800, 600)
.webp({ quality: 80 })
.toFile('output.webp');
jimp は小規模な処理には十分ですが、速度は遅いです。
// jimp: 純粋な JS による処理
import Jimp from 'jimp';
const image = await Jimp.read('input.jpg');
await image.resize(800, 600);
await image.writeAsync('output.jpg');
canvas は描画に特化しており、写真処理には向きません。
// canvas: 描画コンテキストの使用
import { createCanvas } from 'canvas';
const canvas = createCanvas(800, 600);
const ctx = canvas.getContext('2d');
// 画像を描画して加工
const img = new Image();
img.src = 'input.jpg';
ctx.drawImage(img, 0, 0, 800, 600);
gm は安定していますが、sharp より遅いことが多いです。
sharp の方がコードが簡潔になります。// gm: コマンドのラップ
import gm from 'gm';
gm('input.jpg')
.resize(800, 600)
.write('output.jpg', (err) => {
if (err) console.error(err);
});
「画像をどうしたいか」によって選ぶべきツールが変わります。
この用途では sharp が最適です。WebP、AVIF などのモダンなフォーマットへの対応も早く、画質とファイルサイズのバランスを制御しやすいです。
// sharp: フォーマット変換と最適化
await sharp('input.png').avif({ quality: 50 }).toFile('output.avif');
jimp も可能ですが、AVIF などのサポートは限定的か、プラグイン依存になります。
// jimp: 基本的なフォーマット変換
const image = await Jimp.read('input.png');
await image.writeAsync('output.jpg'); // AVIF は標準では非対応の場合あり
canvas が唯一の選択肢です。他のライブラリはテキストレンダリング機能が弱いか、ありません。
// canvas: テキスト描画
ctx.font = '30px Arial';
ctx.fillText('Hello World', 10, 50);
// これを画像として保存
const buffer = canvas.toBuffer('image/png');
sharp では、sharp 単体ではテキスト描画ができず、svg を経由するなどの工夫が必要です。
// sharp: SVG を経由したテキスト描画(工夫が必要)
const svgText = `<svg><text x="10" y="50" font-size="30">Hello</text></svg>`;
await sharp({ text: svgText }).png().toFile('text.png');
gm は ImageMagick の膨大な機能セットにアクセスできるため、特殊なエフェクトが必要な場合に有利です。ただし、その機能の多くは sharp や canvas で代替可能になっています。
// gm: ImageMagick の機能を使用
gm('input.jpg').blur(5).rotate(90).write('output.jpg');
プロダクション環境では、ストリーミング処理とエラーハンドリングが重要です。
sharp は Node.js の Stream インターフェースを完全にサポートしています。
// sharp: ストリーミング処理
import { pipeline } from 'stream';
import { createReadStream, createWriteStream } from 'fs';
pipeline(
createReadStream('input.jpg'),
sharp().resize(800, 600),
createWriteStream('output.jpg'),
(err) => { if (err) console.error(err); }
);
jimp は基本的にバッファベースです。
// jimp: バッファ読み込み
const image = await Jimp.read('input.jpg'); // 全量メモリへ
canvas も基本的にメモリ上で操作します。
// canvas: バッファからの読み込み
const img = new Image();
img.src = fs.readFileSync('input.jpg');
| 特徴 | sharp | canvas | jimp | gm |
|---|---|---|---|---|
| 速度 | ⚡ 非常に高速 | 🐢 普通 | 🐌 遅い | 🐢 普通 |
| 依存関係 | 🔧 ネイティブ (libvips) | 🔧 ネイティブ (Cairo) | ✅ なし (Pure JS) | 🔧 システム (ImageMagick) |
| 主な用途 | 写真加工、変換 | 描画、テキスト、SVG | 簡易編集、手軽さ | 特殊なフィルタ |
| モダンフォーマット | ✅ WebP, AVIF, HEIC | ❌ 基本は PNG, JPEG | ⚠️ 限定的 | ✅ 対応 |
| テキスト描画 | ⚠️ SVG 経由が必要 | ✅ 標準機能 | ❌ 非対応 | ⚠️ 複雑 |
新規プロジェクトで画像処理ライブラリを選定する際、以下の基準で決定することをお勧めします。
写真の最適化や変換が主目的なら sharp
パフォーマンス、メモリ効率、サポートフォーマットのすべてにおいて現代的な標準です。AWS Lambda やコンテナ環境でも、レイヤーを適切に管理すれば問題なく動作します。
動的な画像生成(テキスト合成など)なら canvas
ブラウザで Canvas を使った開発経験があれば、サーバー側でも同じ感覚でコードを書けます。OGP 画像の生成や、チャートの画像化に適しています。
環境制約でネイティブモジュールが使えないなら jimp
コンパイル環境が用意できない場合や、処理する画像が小さく、速度を求めない場合に限り有効です。
gm は既存資産の維持以外は避ける
新規採用するメリットは薄く、sharp で大半の機能がカバーできます。
技術選定では「できること」だけでなく「維持できること」が重要です。パフォーマンスとメンテナンス性のバランスが取れている sharp を第一候補とし、描画機能が必要なら canvas を併用するのが、現代の Node.js アプリケーションにおける堅実なアーキテクチャと言えます。
処理速度とメモリ効率が最優先の場合、sharp を選択してください。libvips ベースのアーキテクチャにより、他のライブラリより大幅に高速で、WebP や AVIF などの現代なフォーマットもサポートしています。大規模な画像処理パイプラインや、レスポンス時間が重要な API 構築に適しています。
画像の「描画」や「生成」が主な目的の場合、canvas が最適です。ブラウザの Canvas API と互換性があり、テキストの書き込み、図形の描画、SVG のラスタライズなどに強みがあります。チャート生成や動的な画像合成が必要な場合に選定します。
ネイティブモジュールのコンパイル問題を避けたい場合や、シンプルな編集機能だけで十分な場合は jimp を検討します。純粋な JavaScript で書かれており、システム依存がありません。ただし、処理速度は遅く、大容量の画像処理には向きません。
既存のシステムで GraphicsMagick または ImageMagick が既に導入されており、その特定の機能が必要な場合に限り gm を使用します。新規プロジェクトでは、メンテナンス性やパフォーマンスの観点から sharp への移行を推奨します。
The typical use case for this high speed Node-API module is to convert large images in common formats to smaller, web-friendly JPEG, PNG, WebP, GIF and AVIF images of varying dimensions.
It can be used with all JavaScript runtimes that provide support for Node-API v9, including Node.js (^18.17.0 or >= 20.3.0), Deno and Bun.
Resizing an image is typically 4x-5x faster than using the quickest ImageMagick and GraphicsMagick settings due to its use of libvips.
Colour spaces, embedded ICC profiles and alpha transparency channels are all handled correctly. Lanczos resampling ensures quality is not sacrificed for speed.
As well as image resizing, operations such as rotation, extraction, compositing and gamma correction are available.
Most modern macOS, Windows and Linux systems do not require any additional install or runtime dependencies.
Visit sharp.pixelplumbing.com for complete installation instructions, API documentation, benchmark tests and changelog.
npm install sharp
const sharp = require('sharp');
sharp(inputBuffer)
.resize(320, 240)
.toFile('output.webp', (err, info) => { ... });
sharp('input.jpg')
.rotate()
.resize(200)
.jpeg({ mozjpeg: true })
.toBuffer()
.then( data => { ... })
.catch( err => { ... });
const semiTransparentRedPng = await sharp({
create: {
width: 48,
height: 48,
channels: 4,
background: { r: 255, g: 0, b: 0, alpha: 0.5 }
}
})
.png()
.toBuffer();
const roundedCorners = Buffer.from(
'<svg><rect x="0" y="0" width="200" height="200" rx="50" ry="50"/></svg>'
);
const roundedCornerResizer =
sharp()
.resize(200, 200)
.composite([{
input: roundedCorners,
blend: 'dest-in'
}])
.png();
readableStream
.pipe(roundedCornerResizer)
.pipe(writableStream);
A guide for contributors covers reporting bugs, requesting features and submitting code changes.
Copyright 2013 Lovell Fuller and others.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.