
ブログをNext.js14(App Router)でリファクタリングしました
JavaScriptの画像処理ライブラリ「Jimp」をTypeScriptで使い、ブラウザ上で画像処理を行う方法を説明します。Jimpは画像処理のアルゴリズムを知らない人も簡単に画像処理ができますし、独自でカーネルを定義できたりピクセル単位の操作が簡単に行えるので、ある程度のカスタマイズができて拡張性があります。
目次
Jimpの使い方について、TypeScriptのコードと実際の画像処理結果を共に記載します。
前置きなので使用方法だけが知りたい方は次の章へ飛ばして下さい。
以前「TypeScriptを用いたブラウザ上の画像処理【ライブラリ不使用】」ということで、ライブラリを使わないで画像処理を行う記事を作成しました。興味のある方はご参照下さい。
画像を1次元配列として操作する必要があったり、入力出力周りが面倒だったり、正直かなり画像処理が行いにくい言語です。
また、画像処理は大学数学レベルの内容になってくるので、初心者がとっつきにくい分野でもあると思います。
そこでJimpです。行いたい画像処理を関数名で呼び出してパラメータを調整する、という直感的な操作ができます。
用意されているフィルターより複雑な処理をしたくなったら、カーネルを定義したりピクセル単位で操作を行えるので、ある程度カスタマイズも出来ます。また、画像の類似度を計算する機能等もあります。
有名なライブラリであるOpenCVほど複雑な処理はできませんが、画像の簡単な加工や変換だけの用途ならJimpのほうが楽です。
なにはともあれ、まずはnpmでJimpをインストールします。
npm install --save jimp
TSファイルでimportしますが、ブラウザ上で画像処理が行いたい場合は次のimport先にします。
import Jimp from "jimp/browser/lib/jimp"
今回は、inputフォームで選択したファイルに対してブラウザ上で画像処理を行うことにします。
入力も出力もimgタグとします。
このあたりの決まりはそれぞれでカスタマイズして下さい。
<input type="file" id="file">
<img src="" alt="入力画像" class="inputImage">
<img src="" alt="出力" class="outputImage">
import Jimp from "jimp/browser/lib/jimp"
document.querySelector<HTMLInputElement>('#file')?.addEventListener('change', e => {
const target = e.target as HTMLInputElement
if (target.files !== null) {
const file = target.files[0]
const url = URL.createObjectURL(file)
document.querySelector<HTMLImageElement>('.inputImage')!.src = url
Jimp.read(url).then(image => {
// ここから画像処理を書く
///// 画像処理 /////
//ここまで
image.getBase64(Jimp.MIME_JPEG, (error, img) => {
document.querySelector<HTMLImageElement>('.outputImage')!.src = img
})
})
}
}, false)
たったのこれだけです。フォーム周りの処理の説明は割愛します。
Jimpで行っている処理は2つです
Jimp.read(url).then(image => {/**画像処理 */})
Jimp.readに画像のURLを指定して読み込みを行っています。Promiseを返すのでthenでつなげており、コールバック関数内で画像処理を行います。コールバックの引数(上のコードだとimage)が処理対象の画像になります。画像はライブラリ固有のJimp型です。
image.getBase64(Jimp.MIME_JPEG, (error, img) => {
document.querySelector<HTMLImageElement>('.outputImage')!.src = img
})
このコードは画像をどの形式で出力するかによって変わってくるので、あくまで参考程度にとらえてください。
getBase64で画像のData URIが取得でき、コールバック内でアウトプット用のimgタグのsrc属性にセットしています。これによって、処理結果の画像がimgタグとしてブラウザ上で出力されます。
それでは、実際に画像処理を行います。
Jimpのコードは基本的に、
image.フィルタ(パラメータ)
の書式です。フィルタを何個も適用したい場合はチェーンして書きます。
Jimp.read(url).then(image => {
image.フィルター1()
.フィルター2()
.フィルター3()
image.getBase64(Jimp.MIME_JPEG, (error, img) => {
document.querySelector<HTMLImageElement>('.outputImage')!.src = img
})
})
それではJimpで予め用意されているフィルターを紹介します。
入力画像は次のお寿司の画像にします。
画像の大きさを変更します。
入力画像は、指定した幅と高さに収まるようにリサイズされます。足りない部分は黒で埋め尽くされます。
image.contain(幅 , 高さ [, Alignモード, Resizeモード])
結果: image.contain(500,300)
元画像より小さいサイズを指定して、画像を縮小しました。
足りない部分は黒で塗りつぶされています。
省略可能なパラメータです。
Alignモードは画像を揃える位置を指定します。例えば Jimp.HORIZONTAL_ALIGN_LEFT;
とすると左添えになります。
結果:image.contain(500, 300, Jimp.HORIZONTAL_ALIGN_LEFT)
この他にも揃える位置について次のAlignモードがあります。それぞれの意味は読んで字の如くです。
Jimp.HORIZONTAL_ALIGN_LEFT;
Jimp.HORIZONTAL_ALIGN_CENTER;
Jimp.HORIZONTAL_ALIGN_RIGHT;
Jimp.VERTICAL_ALIGN_TOP;
Jimp.VERTICAL_ALIGN_MIDDLE;
Jimp.VERTICAL_ALIGN_BOTTOM;
これも省略可能なパラメータです。リサイズの際のアルゴリズムを指定します。
画像処理に詳しくない方は無視しても構いません。それぞれのアルゴリズムは読んで字の如く。
Jimp.RESIZE_NEAREST_NEIGHBOR;
Jimp.RESIZE_BILINEAR;
Jimp.RESIZE_BICUBIC;
Jimp.RESIZE_HERMITE;
Jimp.RESIZE_BEZIER;
デフォルトはバイリニアです。
画像の大きさを変更します。
入力画像は、指定した幅と高さいっぱいに埋め尽くされます。比率は一定のままです。
image.cover(幅 , 高さ [, Alignモード, Resizeモード])
結果: image.cover(500, 300)
画像の大きさを変更します。
入力画像は指定した高さと幅になります。比率も変わります。
image.resize(幅 , 高さ [, Resizeモード])
結果: image.resize(500, 300)
幅、高さの一方のみを指定して、一定の比率でのリサイズもできます。
その際、指定しない方のパラメータはJimp.AUTO
と指定します。
画像の大きさを変更します。
比率は一定で[パラメータ]倍します。
image.scale(倍率 [, Resizeモード])
画像の一部分を切り取ります。
image.crop(x座標, y座標, 幅, 高さ)
結果:image.crop(100, 100, 200, 200)
同じ色の線を自動的にクロップしますが、使い勝手と結果があまり良くないので割愛します。
画像の上に画像を重ねます。
image1.blit(image2, x座標, y座標[, image2のx座標, 2のy座標, 2の幅, 2の高さ])
image2もJimp形式の画像である必要があります。
結果: image.blit(image2, 100, 100)
2つの画像を合成(ブレンド)します。PhosoShopの描画モードのようなものです。
image.composite(image2, 0, 0, { // image2のx座標とy座標
mode: Jimp.BLEND_MULTIPLY, //モード
opacitySource: 1, //image2の透明度
opacityDest: 1 //image1の透明度
});
お寿司の画像に次のグラデーション画像を合成してみます。
結果
描画モードは他にも次のようなものがあります。
Jimp.BLEND_SOURCE_OVER;
Jimp.BLEND_DESTINATION_OVER;
Jimp.BLEND_MULTIPLY;
Jimp.BLEND_ADD;
Jimp.BLEND_SCREEN;
Jimp.BLEND_OVERLAY;
Jimp.BLEND_DARKEN;
Jimp.BLEND_LIGHTEN;
Jimp.BLEND_HARDLIGHT;
Jimp.BLEND_DIFFERENCE;
Jimp.BLEND_EXCLUSION;
2枚の画像の平均画素値を求めます。2値画像を使えばマスクできます。
image.mask(image2, x座標, y座標)
例として次の画像でマスクしてみます。
結果: image.mask(image2, 0, 0)
左右、上下に反転します。mirrorはflipのエイリアスです。
image.flip(左右に反転するか: boolean, 上下に反転するか: boolean)
結果:image.flip(false, true)
画像を時計回りに回転します。
image.rotate(角度, リサイズするか: boolean)
第2パラメータをtrueにすると回転した画像がリサイズされます。
falseにするとリサイズされず、回転によりはみ出た部分は切り取られます。
結果:image.rotate(30, true)
明度を変更します。パラメータは-1 ~ 1で指定し、0より大きい場合は明るく、小さい場合は暗くなります。
image.brightness(パラメータ)
結果:image.brightness(0.6)
コントラストを変更します。パラメータは-1 ~ 1で指定し、0より大きい場合はコントラストが強く、0より小さい場合は弱くなります。
image.contrast(パラメータ)
結果:image.contrast(0.6)
ディザリングをして、色空間を16bitに縮小します。
image.dither565()
グレースケール画像に変換します。
image.greyscale()
結果:
ネガポジ反転します。
image.invert()
画像のチャネルを正規化します。
image.normalize()
これだけ?と思われるかもしれませんが、他の色変換処理は勝手が違うので、次章で説明します。
画像の透過度を設定します。パラメータは0 〜1の範囲を取ります。
image.fade(パラメータ)
結果:image.fade(0.5)
画像を不透明にします。
image.opaque()
ガウシアンフィルタをかけます。ただし処理はとても遅いです。
image.gaussian(ピクセル値)
結果:image.gaussian(6)
ガウシアンフィルタより高速にぼかします。
image.blur(ピクセル値)
ポスタリゼーションします。パラメータでレベルを指定します。
image.posterize(レベル: number)
結果: image.posterize(4)
セピアにします。
image.sepia()
結果:
ピクセレーションします。いわゆるモザイク処理です。部分的にモザイクをかけることも可能です。
image.pixelate(サイズ [,x座標, y座標, 幅, 高さ])
結果: image.pixelate(20, 100, 100, 300, 300)
JPEGの画質を変更できます。パラメータは0 ~ 100を取ります。
image.quality(パラメータ)
結果:image.quality(1)
colorメソッドを用いると、色の変換を細かく調整できます。
image.color([
{ apply: 'hue', params: [-90] },
{ apply: 'lighten', params: [50] },
{ apply: 'xor', params: ['#06D'] }
])
colorにはapplyとparamsをキーに持つオブジェクトの配列を渡します。
applyには行いたい処理をstring型で、paramsにはパラメータを配列で渡します。
applyで指定できる処理は以下のとおりです。
apply | params | 説明 |
---|---|---|
lighten | 0 ~ 100 | 明るくする |
brighten | 0 ~ 100 | 明るくする |
darken | 0 ~ 100 | 暗くする |
desaturate | 0 ~ 100 | 彩度を下げる |
saturate | 0 ~ 100 | 彩度を上げる |
greyscale | – | グレースケールにする |
spin | -360 ~ 360 | 色相を回転させる |
hue | -360 ~ 360 | spinのエイリアス |
mix | 色(hex), 0 ~ 100 | 指定した色でオーバーレイ。数値は透明度。 |
tint | 0 ~ 100 | 白とmixしたのと同じ |
shade | 0 ~ 100 | 黒とmixしたのと同じ |
xor | 色(hex) | 指定した色とxor演算 |
red | 0 ~ | 赤を強める |
green | 0 ~ | 緑を強める |
blue | 0 ~ | 青を強める |
前章の説明にない画像処理を行った結果をいくつか載せます。
結果 { apply: ‘hue’ , params:[60] }
結果: { apply: ‘saturate’, params: [50] }
結果: { apply: ‘blue’, params: [80] }
カーネルを指定して空間フィルタリング(畳み込み演算)ができます。
カーネルは3 x 3の2次元配列で指定します。
image.convolute(3 x 3の2次元配列)
具体的な使用例として、上で行っていない処理を2つほど紹介します。
結果: image.convolute([[-1, -1, -1], [-1, 9, -1], [-1, -1, -1]])
結果: image.convolute([[-2, -1, 0], [-1, 1, 1], [0, 1, 2]])
ピクセルレベルの操作もできます。
scanメソッドを使い画像をラスタスキャンすることで、ピクセルレベルでRGBα値を操作できます。
image.scan(開始位置x, 開始位置y, 走査幅, 走査高, コールバック)
scanする範囲を引数で指定できます。また、コールバックでスキャン中の画素に対してアクセスします。
image.scan(0, 0, image.bitmap.width, image.bitmap.height, function(x, y, idx) {
// 画像をピクセルレベルで左上から右下まで走査(ラスタスキャン)
// x, y は画像上のピクセルの2次元的な位置
// idx は、画素を1列に並べたときの、1次元的な画素位置
// idx番目のとき、R、G、B、αへは以下でアクセスできる
let R = this.bitmap.data[idx + 0];
let G = this.bitmap.data[idx + 1];
let B = this.bitmap.data[idx + 2];
let alpha = this.bitmap.data[idx + 3];
});
ここで、上のコードのように、image.bitmap.widthで画像幅、image.bitmap.heightで画像の高さ、 image.bitmap.dataで画素にアクセスできます。
コールバック内での画素へのアクセス方法はImageData型と同じです。
(参考)ImageData型
以上で一通りの画像処理を紹介しましたが、Jimpでは次のようなこともできます。
興味のある方は、公式のドキュメントをお読みください
JavaScriptの画像処理ライブラリ「Jimp」をTypeScriptで使い、ブラウザ上で画像処理を行う方法を説明し、実際の処理結果とともに記載しました。紹介したとおり簡単に画像処理が行なえますし、空間フィルタリングやピクセル走査も簡単に行えます。基本的な画像処理のみ行いたいなら十分な機能を備えているでしょう。
最新の記事
カテゴリー一覧
アーカイブ
目次