前書き
javascriptで画像処理をしたいと思って検索していたら見つけたライブラリ。
よくみる犬のアイコンを想像したが、名前の由来が似ているだけで(おそらく)関係ない
今回はこのライブラリを使って画像処理をしてみた、という話より、
このライブラリをどうやってTypescript+Browser側で使用したかというお話。
動作環境
- node v18.12.1
- typescript v5.2.2
- react v18.2.0
- vite v4.4.9
- jimp v0.22.10
- use ESModules
Jimpとは
全てjavascriptのコードで書かれており、ネイティブ依存なしのライブラリ。
jpeg
、png
、bmp
、tiff
、gif
の形式に対応していて、リサイズや画像加工などができる。
使ってみる
import Jimp from 'jimp'
const main = async () => {
const image = await Jimp.read('image.png')
image.resize(256, 256)
image.write('output.png')
}
image.png
画像を読み込んで、256x256にリサイズしてoutput.png
として出力するコード。
サーバーサイドで動かす場合は、これだけで動く。とてもシンプル
browserで使う
ではこれをブラウザで動かす。
ファイルを読み込み、またはファイルの書き出しはブラウザらしい挙動にするため、
<input type="file">
と、canvasを用意する。
<input type="file" id="file" />
<canvas id="canvas"></canvas>
import Jimp from 'jimp'
const main = async () => {
const file = document.getElementById('file') as HTMLInputElement
const canvas = document.getElementById('canvas') as HTMLCanvasElement
const ctx = canvas.getContext('2d')
file.addEventListener('change', async () => {
// inputからファイルを読み込む
const arrayBuffer = await file.files[0].arrayBuffer()
// Jimpで読み込み、リサイズ
const image = await Jimp.read(Buffer.from(arrayBuffer))
image.resize(256, 256)
// canvasに描画
const imageData = new ImageData(
Uint8ClampedArray.from(image.bitmap.data),
image.bitmap.width,
image.bitmap.height
)
ctx.putImageData(imageData, 0, 0)
})
}
これで、inputから画像を読み込んで、リサイズしてcanvasに描画することができる。
・・・ように見えて、実際には動かなかった。
Module "fs" has been externalized for browser compatibility. Cannot access "fs.existsSync" in client code.
Jimp
の何処かにfs
が使われているらしく、このままでは動かない。
ブラウザのjimpを読み込む
jimp/packages/jimp/browser/README.md at main · jimp-dev/jimp
調べてみると、ブラウザ用も用意されていた。なのでこれをimportする。
import 'jimp/browser/lib/jimp.js'
const main = async () => {
// ...
}
ファイルから何か変数を読み込むのではなく、単純にファイルを読み込むだけ。
そうすると、Jimpがグローバルに定義されるようになる。
javascriptであればおそらくこれで終わり。
しかし、typescriptでは、Jimpが定義されていないというエラーが出る。
なので、型だけを定義する必要がある。
型定義を追加する
これが一番良くわからなかった。
シンプルに表現するのであれば、
import 'jimp/browser/lib/jimp.js'
declare const Jimp: any
const main = async () => {
// ...
}
と、anyで定義してしまえば良いのだが、もちろん補完は効かなくなる。
Issuesで検索をかけてみて、それっぽいissueを探すも、うまくいかず。
色々試行錯誤した結果、以下の型定義ファイルを作成することで、それっぽい動きになった。
import Jimp from 'jimp'
type JimpObject = typeof Jimp
type JimpType = Jimp
import 'jimp/browser/lib/jimp.js'
import { JimpObject } from '../types/jimp'
declare const Jimp: JimpObject
const main = async () => {
// ...
}
Jimpファイルの型定義ファイルをみにいくと、Jimp
は、型(?)定義とオブジェクト(クラス?)定義の両方が1つになっている。
そのため、typeof Jimp
と定義すると、Jimp.read()
などのメソッドの戻り値の型がうまくいかず、
Jimp
と定義すると、Jimp.read()
などのメソッドが存在しないというエラーが出る。
これをうまく繋げて、1つの定義に収める方法がわからなかったため、2つに分けて定義した。
import { JimpObject, JimpType } from '../types/jimp'
declare const Jimp: JimpObject
const read = (file: string): Promise<JimpType> => {
const image = await Jimp.read(Buffer.from(arrayBuffer))
return image
}
このように、グローバル定義されているJimpにはJimpObject
を、
Jimp.read()
などのメソッドの戻り値の型にはJimpType
を使うことで、
補完が効くようになった。
終わりに
最初の方に型補完が効かないと気づけばおそらく別のライブラリを使っていたのだが、
なぜか一通りコードが書くまで気が付かなかったので、試行錯誤してしまった。
ブラウザでの画像処理むずかしい。。
参考サイト
How to draw jimp image onto canvas · Issue #215 · jimp-dev/jimp