锋利的Node.js图片处理库sharp

由于项目中需要用户上传的头像的功能,所以就参照一些网站实现在线图片裁切的功能。
经过朋友推荐和多方比较,前端采用 Jcrop 和 jQuery Form 实现用户界面,把用户要裁切的图片区域和图片异步的方式提交后端。
而后端则负责对图片的裁切,输出并保存为用户头像。

这篇主要以Express框架为例介绍如何在Node.js开发环境中使用sharp库处理图片。

sharp是由伦敦的一位开发者lovell开发的基于libvips的一款轻快的图片处理库。
以下是原文介绍,详情可以戳上面的Github链接了解更多。

The fastest Node.js module for resizing JPEG, PNG, WebP and TIFF images. Uses the libvips library.

安装

关于安装需要注意除了基本的Node.js开发环境之外,还需要预装libvips库。关于如何在各个平台上安装vips,其GitHub页面已经给出详细的参考,我在CentOS和Mac OS上都已成功预装。不过在Mac上安装时遇到一些问题,已经在之前的文章中给出相应的解决过程。

按照官方提示安装好sharp之后就可以开始对图片进行裁切、缩放、旋转,并且可以更改图片的输出格式,质量等。这些已经完全能满足裁切头像的需求了!

使用

首先自然是需要引入sharp库

1
var sharp = require('sharp');

风格

sharp使用的是跟jQuery类似的链式操作风格:sharp('input.jpg').resize(300, 300).max() ...

示例

下面代码(有省略)就是根据前台传送过来的裁切数据,进行裁切的片段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 对数据格式进行转换
var left = data.x;
var top = data.y;
var width = data.w;
var height = data.h;
// 获取上传的图片,进行裁切
fs.exists(imagePath, function(exists){
if (exists) {
sharp(imagePath)
.extract(top, left, width, height)
.resize(150, 150)
.sharpen()
.quality(100)
.toFile(avatarPath, function (err) {
if (err) throw err;
console.log('Completed!');
// 更新数据库
...
// 头像保存成功,返回成功消息给前端
res.json({success: true, data: {message: '头像已保存'}});
});
} else {
console.log('原图已过期,请重新上传');
res.json({success: false, data: {message: '操作超时,请重新上传'}});
}
});

通过上面的一段代码,就可以实现对用户前一步上传的图片进行制定范围的裁切。
开始裁切图片之前需要检查图片是否存在,如果已经丢失或未保存则返回错误信息告知前端用户。
接下来就是使用sharp对图片进行实际的操作,sharp API的链式调用方式使得处理图片如同工厂里的流水线拼接。

首先,把图片路径imagePath传递给sharp把这张图片送入车间;

然后是调用extract(top, left, width, height)按照裁切图纸,也就是裁切坐标对图片进行粗加工。这里需要注意的是需要对坐标进行简单的置换——第一个参数对应图片的起始纵坐标data.y,第二个参数则对应裁切区域的起始横坐标data.x

接下来是通过resize(width, height)将图片按照制定规格进行缩放;

当然加工的图片还需要对其质量进行控制,这里通过sharpen()quality()分别对图片进行锐化和设定输出质量。quality()针对JPEG, WebP 和 TIFF三种可以控制输出质量的格式进行控制,范围为1~100,默认为80,似乎和Photoshop默认的JPEG输出质量一致;

最后一步就是对加工完成的图片进行输出,sharp提供了pipe和文件两种方式,这里用到的是直接另存为图片的方式,使用toFile()完成最后的出厂工作。指定的输出图片路径可以是已经存在的图片,只要有文件写入权限sharp会覆盖旧的文件。但是目标文件不能是输入源,也就是不能把加工出来的成品和加工原材料放在同一个位置;
toFile()还接受另一个参数即回调函数,这个回调函数只接收一个错误信息作为参数,从而可以判断图片处理是否成功。这里选择在成功保存头像后将裁切数据保存在数据库中,这样就可以实现帮助用户重新载入裁切区域的功能。

常见错误

Bad extract area
在使用extract()对图片进行裁切时,主要注意裁切坐标只能在图片自身的范围内,比如起点横坐标left加上裁切横长width超过了图片的右边界,sharp会报出Bad extract area非法裁切区域的错误,特别注意前面提到的左上横高的参数顺序。当然我们可以在裁切前,通过sharp提供的另外一个接口先获取图片的尺寸。

获取图片信息

在对图片进行处理之前,往往需要先获取图片的尺寸格式等基本进行。sharp中可以通过metadata()获取图片的一些重要的元数据,比如尺寸、格式和颜色空间等。用法可以参考官方的示例:

1
2
3
4
5
6
var image = sharp(inputJpg);
image.metadata(function(err, metadata) {
image.resize(metadata.width / 2).webp().toBuffer(function(err, outputBuffer, info) {
// outputBuffer contains a WebP image half the width and height of the original JPEG
});
});

上面的例子,输出的Buffer信息是一个源图片宽和高各缩小一半的WebP格式图片。

关于sharp的性能,可以参考官方给出的同imagemagick-native,imagemagick,gm进行对比测试样本:Performance

gm是另一个Node.js开发环境下的图形处理库(包),不过gm所使用的底层库是更加常见 GraphicsMagick,相信对于开发过图片处理模块的PHPer来说很熟悉了。我在选择包的时候比较看重其效率,在看到sharp简述中的Fast后就决定试用一下。如果你也正在寻找一个在Node.js中高性能的图片处理方式,不妨试试sharp!