Thursday, October 17, 2019 / JavaScript, Jimp, image-manipulation, Node.js

Node.js と Jimp で画像をクロップする

ちょっとした画像操作に Groovy を使ってきたが、最近 Node.js を使う機会が増えてきたので、 Jimp を使って画像を操作する方法をメモしておきます.

Chromebook のイラストの右側だけクロップする、に挑戦

このChromebook のイラスト画像を対象にして実験します。

chromebook Flip C434TA

まずは単純に読み込んで 幅 256px の JPG画像 として保存

Node.js の環境がインストールされていることは前提として以下のようにプロジェクトを作成.

mkdir image-crop
cd image-crop
npm init -y
npm install --save jimp

Jimp の使い方は こちらのオフィシャルドキュメント を見ればだいたいわかります.

プロジェクトのルートフォルダに index.js を作成し、以下のコードを記述:

const Jimp = require('jimp');

const imagePath = 'https://osima.jp/imgs/chromebook-flip-c434ta/chromebook-c434ta-w640.png';
Jimp.read(imagePath, (err, image) => {
    if (err){ throw err; }
    image.resize(256, Jimp.AUTO).write('chromebook.jpg');
});

ここでは画像をリサイズした上で保存していますが、 横または縦のサイズ指定に Jimp.AUTO を使うことで、片方だけ指定でもう片方はJimpにお任せできます.

node index.js

これで chromebook.jpg が生成されます. とても簡単です、すばらしい.

chromebook Flip C434TA resized

横幅は 256px で指定した通りです.
ただ、背景が透明のPNG画像を JPG にしたので、こんな風に背景が黒になってしまいました. この問題はあとで対処しましょう.

次にクロップ

画像の右側の Chromebook を開いたイラストだけをクロップします. ドキュメントを見ると...

image.crop( x, y, w, h );  

すれば良いようです.

// image width
const width = 256;
const height = 111;

// crop 位置
const left = width/2;
const top  = 0;
const right = width;
const bottom = height;

// crop パラメータは x,y,w,h で指定するので:
const x = left;
const y = top;
const w = right - left;
const h = bottom - top;

// Let's crop.
const Jimp = require('jimp');

const imagePath = 'https://osima.jp/imgs/chromebook-flip-c434ta/chromebook-c434ta-w640.png';
Jimp.read(imagePath, (err, image) => {
    if (err){ throw err; }
    image
        .resize(width, Jimp.AUTO)
        .crop( x, y, w, h )
        .write('chromebook-cropped.jpg');
});

node index.js した結果:

chromebook Flip C434TA resized and cropped

うまくいきました。

最後に、背景ピクセルを修正

元は透明ピクセルだった背景がJPGにしたことで黒になっているのを修正します.
Jimpは image.scan(x, y, w, h, f);を使うことで pixel 単位での処理が可能です. 最後のピクセルが処理された段階で、 new Jimp() して新しいイメージを生成して背景を白にした data を与えます.

つまり:

// image width
const width = 256;
const height = 111;

// crop params
const left = width/2;
const top  = 0;
const right = width;
const bottom = height;

// Let's crop.
const Jimp = require('jimp');

const imagePath = 'https://osima.jp/imgs/chromebook-flip-c434ta/chromebook-c434ta-w640.png';
Jimp.read(imagePath, (err, image) => {
    if (err){ throw err; }

    image.scan(0, 0, image.bitmap.width, image.bitmap.height, (x, y, idx)=> {
        //const red   = image.bitmap.data[idx + 0];
        //const green = image.bitmap.data[idx + 1];
        //const blue  = image.bitmap.data[idx + 2];
        const alpha = image.bitmap.data[idx + 3];

        if( alpha==0 ){
            // 透明ピクセルの場合→ RGB and Alpha に 255 をリセットすることで白色にチェンジする.
            image.bitmap.data[idx + 0] = 255;
            image.bitmap.data[idx + 1] = 255;
            image.bitmap.data[idx + 2] = 255;
            image.bitmap.data[idx + 3] = 255;
        }

        if (x == image.bitmap.width - 1 && y == image.bitmap.height - 1) {
            // 全部のピクセルの処理が終わったので書き出しする.
            new Jimp({ data: image.bitmap.data, width: image.bitmap.width, height: image.bitmap.height }, (err, image2) => {
                if (err){ throw err; }
                
                image2
                    .resize(width, Jimp.AUTO)
                    .crop( left, top, (right-left), (bottom-top) )
                    .write('chromebook-cropped-with-background-white.jpg');
            });
        }
    });
});

うまくいきました:

chromebook Flip C434TA resized, cropped and background as white

まとめ

Jimp を使えば、とても簡単に画像操作ができることがわかりました. Groovy はこの用途ではいらないかな、もはや...