当前位置: 首页 > 后端技术 > Node.js

Node.js服务端图片处理工具——sharp进阶操作指南

时间:2023-04-03 11:50:52 Node.js

sharp是Node.js平台上非常流行的图片处理库。性能也成为夏普的一大卖点。sharp可以轻松实现常用的图片编辑操作,比如裁剪、格式转换、旋转变换、添加滤镜等。当然网上相关的文章很多,sharp的官方文档也比较详细,所以这个是不是本文的重点。这里主要是想记录下自己在使用sharp过程中遇到的一些稍微复杂的图片处理需求的解决方案。希望分享出来能对大家有所帮助。SharpBasicsSharp整体上采用流处理模式,读入图像数据后,经过一系列的处理,然后输出结果。我们可以通过看一个简单的例子来理解:constsharp=require('sharp');sharp('input.jpg').rotate().resize(200).toBuffer().then(data=>...).catch(错误=>...);sharp几乎所有的函数接口都挂载在Sharp实例上,所以图像处理的第一步肯定是读入图像数据(sharp函数接受图像本地路径或图像Buffer数据作为参数)并转化为Sharp实例,以及然后它像管道一样被处理。所以这里应该提供一个预处理函数,将服务器接收到的图片转换成Sharp实例:/****@param{String|Buffer}inputImgimagelocalpathorimageBufferdata*@return{Sharp}*/asyncconvert2Sharp(inputImg){returnsharp(inputImg)}然后就可以进行具体的图像处理了。在添加水印的后端实现水印功能应该算是一个常见的图像处理需求。Sharp在图像合成中只提供了一个函数:overlayWith,它接受一个图像参数(也就是图像本地路径字符串或图像Buffer数据)和一个可选的options配置对象(水印图像位置等可配置信息),然后Overlay图像在原始图像之上。逻辑也比较简单,我们的代码如下:/***addwatermark*@param{sharp}imgoriginalimage*@param{String}watermarkRawwatermarkimage*@param{top}水印距离上边缘的距离图片*@param{left}水印与图片左边缘的距离*/asyncwatermark(img,{watermarkRaw,top,left}){constwatermarkImg=awaitwatermarkRaw.toBuffer()returnimg.overlayWith(watermarkImg,{top,left})}为了简单起见,这里只支持水印图片的位置。Sharp还支持更复杂的配置参数,比如是否重复粘贴多张水印图,是否只在α通道粘贴水印图等,具体可以参考overlayWith的文档。前端实现这里,还要顺便提一下前端的实现。当然,如果服务端按照固定的规则给图片加水印(比如新浪微博图片水印放在固定的位置),前端什么都不用做。但是在某些场景下(比如在线图片编辑工具),用户在添加水印的时候,希望在前端得到所见即所得的体验。此时,如果用户添加了水印并选择了位置,则必须将数据发送到服务器进行处理,然后才能得到处理结果,这势必会影响整个服务的流畅性。幸运的是,强大的HTML5使得前端功能越来越丰富。借助canvas,我们可以实现前端添加水印的功能。具体实现细节并不难。主要是使用canvas提供的drawImage方法。看例子:varcanvas=document.getElementById("canvas");varctx=canvas.getContext('2d');//img:Basemap//watermarkImg:watermarkimage//x,y是坐标在画布上放置imgctx.drawImage(img,x,y);ctx.drawImage(watermarkImg,x,y);其实整个添加水印的功能(选择原图,选择水印图,设置水印图位置,获取添加水印后的图片)完全可以由前端完成。当然,为了追求服务端功能的完整性,还是推荐使用前端展示+后端处理的模式。粘贴文字粘贴文字的需要其实类似于加水印。唯一不同的是,添加的水印图片被文字代替了,我们可能需要对文字的大小和字体做一些调整。思路也比较容易想到,把文字转成图片就行了。这里我们使用text-to-svg库将文本转换为svg。利用svg的特性,我们可以方便的设置文字的字体大小和颜色。然后调用Buffer.from将svg转换为sharp可以使用的缓冲区数据。最后就是和上面加水印一样的步骤。constText2SVG=require('text-to-svg')/***要粘贴的文本*@param{Sharp}img*@param{String}text要粘贴的文本*@param{Number}fontSize文本大小*@param{String}colortextcolor*@param{Number}lefttext距图像左边缘的距离*@param{Number}toptexttext距图像上边缘的距离*/asyncpasteText(img,{text,fontSize,color,left,top,}){consttext2SVG=Text2SVG.loadSync()const属性={fill:color}constoptions={fontSize,anchor:'top',attributes,}constsvg=Buffer.from(text2SVG.getSVG(text,options))returnimg.overlayWith(svg,{left,top})}拼接图片拼接图片的操作相对来说是最复杂的。这里我们提供了两个配置项:拼接方式(水平/垂直)和背景颜色。拼接方式比较好理解,无非是将图片横向或纵向排列。背景颜色用于填充空白区域。拼接图片时,图片按轴居中排列。以图片横向排列为例,原理图如下:夏普没有提供现成的功能,一切还是靠唯一的overlayWith解决。overlayWith的用法是将一张图片粘贴到另一张图片上,与我们拼接图片的需求略有不同。我们需要换个思路:可以预先创建一张底图,根据配置值确定背景颜色,然后把所有需要拼接的图片贴在上面,就可以满足要求了。首先,我们需要读取所有需要拼接的图片的长宽。假设拼接方式为水平拼接,最终生成的图像的宽度为所有图像的宽度之和,高度为所有图像的最大高度(垂直拼接则反之):令totalWidth=0lettotalHeight=0letmaxWidth=0letmaxHeight=0constimgMetadataList=[]//获取所有图片的宽高,计算总和和最大值为(leti=0,j=imgList.length;i{constoffsetOpt={}if(mode==='horizo??ntal'){offsetOpt.left=imgMetadataList[imgIndex++].widthoffsetOpt.top=(maxHeight-imgMetadataList[imgIndex].height)/2}else{offsetOpt.top=imgMetadataList[imgIndex++].heightoffsetOpt.left=(maxWidth-imgMetadataList[imgIndex]}.width)/2}overlay=awaitoverlay.toBuffer()returninput.then(data=>sharp(data).overlayWith(overlay,offsetOpt).jpeg().toBuffer())},base)returnresult如下拼接图片功能的完整实现:??/***stitchingimage*@param{Array}imgList*@param{String}mode拼接方式:horizo??ntal(水平)/vertical(垂直)*@param{Object}backgroundbackground颜色格式为{r:0-255,g:0-255,b:0-255,alpha:0-1}默认{r:255,g:255,b:255,alpha:1}*/asyncjoinImage(imgList,{mode,background}){让totalWidth=0让totalHeight=0letmaxWidth=0letmaxHeight=0constimgMetadataList=[]//获取所有图片的宽高,求和和最大值为(leti=0,j=imgList.length;i{constoffsetOpt={}if(mode==='horizontal'){offsetOpt.left=imgMetadataList[imgIndex++].widthoffsetOpt.top=(maxHeight-imgMetadataList[imgIndex].height)/2}else{offsetOpt.top=imgMetadataList[imgIndex++].heightoffsetOpt.left=(maxWidth-imgMetadataList[imgIndex].width)/2}overlay=awaitoverlay.toBuffer()返回input.then(data=>sharp(data).overlayWith(overlay,offsetOpt).jpeg().toBuffer())},base)returnresult},以上是我在使用sharp过程中总结的一些实际操作。其实夏普还有很多高级功能我没有用过,这很符合“第二十八定律”:80%的需求往往是通过20%的功能完成的。如果以后有更多sharp的用法,有机会折腾,我会继续分享给大家~