tengine+lua实现时时在线图片缩放,剪切。
Posted on 18 , 九月 2012 in 未分类 author: Syang
tenginx+lua+shell(conver)+其实也是nginx+lua,因为项目的需求变化,包括界面改版的变化,以至于每一版本的图片尺寸不定,所以不可能保存不同尺寸的版本。所以只能在线根据需求,由服务器来自动处理。
第一版我是使用了nginx+phpfpm,实现起来非常easy,但是稳定性方面不佳,
第二版换成了lua,效果还不错,由于lua本身没有对图片处理的模块(可能有第三方的,不过我还没有深入研究),所以lua是调用shell脚本实现的(当然在调用shell脚本时,程序会blocking,性能有些影响),稳定性增加。>
首先,我们定了一些生成的规则, 生成参数,是放在主文件名的后面
例如,http://pic.yiibook.com/example.jpg这个是源图片
那么在调用缩略图时,使用http://pic.yiibook.com/example!200×100.jpg 就会生成一张以源图为基础的200宽100高的图片 !200×100就是具体的参数
ok,下面说一下我定义的参数,有几种情况,具体是根据业务上面的需求,而自行调用的
1.固定尺寸缩放
!200×100 将源图缩放为宽200x高100
!200 将源图缩放为宽200x高200
!200×100-50 将源图缩放为宽200x高100 并且图片质量为50 (这个是为了给手机端使用的,因为手机端可能需要图片的size更小一些
!200-50 将源图缩放为宽200x高200 并且图片质量为50
这个参数会将源图强制缩放到这个尺寸,所以可能会有所变形
2.等比缩放
:w200 将源图以宽为准200,开始缩放,(意思是,强制将源图的宽缩到200,高不管,那么这个图是一个与源图比例相同的,不会变型
:h200 将源图以高为准200,开始缩放, 意思与上面类似
:m200 将源图以(宽,高那个值大,以哪个为准,进行缩放,比如源图是300×400,那就会以高为准,先将高缩到200),但是如果宽高都没有达到,而不处理
同时也支持 :w200-50 :h200-50 :m200-50 的图片质量
3.中心剪辑
@200×300 将源图以(宽,高那个值小,以哪个为准,进行缩放,并在缩放后的图片,以另一边中间点(就是正中间,进行剪辑)
@200×300-50 同时支持图片质量
暂时我的程序就支持这三种参数格式,
OK,下面看一下nginx配置
server { listen 80; server_name pic.yiibook.com; #access_log logs/pic.access.log main; root /var/www/pic; location / { index index.html; } #宽,高 像素 location ~ (.*)!(\d+)x(\d+).(gif|jpg|jpeg|png)$ { root /var/file/thumb/picture; #bucketname = picture set $bucketname picture; #原图片路径 set $srcPath /var/file/$bucketname; #目录图片路径 set $destPath /var/file/thumb/$bucketname; #处理类型 set $type 1; if (!-f $request_filename) { rewrite_by_lua_file conf/lua/picture_image_thumb.lua; } expires 30d; } #宽,高 像素 质量 location ~ (.*)!(\d+)x(\d+)-(\d+).(gif|jpg|jpeg|png)$ { root /var/file/thumb/picture; #bucketname = picture set $bucketname picture; #原图片路径 set $srcPath /var/file/$bucketname; #目录图片路径 set $destPath /var/file/thumb/$bucketname; #处理类型 set $type 2; if (!-f $request_filename) { rewrite_by_lua_file conf/lua/picture_image_thumb.lua; } expires 30d; } #宽高相等 location ~ (.*)!(\d+).(gif|jpg|jpeg|png)$ { root /var/file/thumb/picture; #bucketname = picture set $bucketname picture; #原图片路径 set $srcPath /var/file/$bucketname; #目录图片路径 set $destPath /var/file/thumb/$bucketname; #处理类型 set $type 3; if (!-f $request_filename) { rewrite_by_lua_file conf/lua/picture_image_thumb.lua; } expires 30d; } #宽高相等质量 location ~ (.*)!(\d+)-(\d+).(gif|jpg|jpeg|png)$ { root /var/file/thumb/picture; #bucketname = picture set $bucketname picture; #原图片路径 set $srcPath /var/file/$bucketname; #目录图片路径 set $destPath /var/file/thumb/$bucketname; #处理类型 set $type 4; if (!-f $request_filename) { rewrite_by_lua_file conf/lua/picture_image_thumb.lua; } expires 30d; } #宽,高 像素等比 location ~ (.*):(w|h|m)(\d+).(gif|jpg|jpeg|png)$ { root /var/file/thumb/picture; #bucketname = picture set $bucketname picture; #原图片路径 set $srcPath /var/file/$bucketname; #目录图片路径 set $destPath /var/file/thumb/$bucketname; #处理类型 set $type 5; if (!-f $request_filename) { rewrite_by_lua_file conf/lua/picture_image_thumb.lua; } expires 30d; } #宽,高 像素等比 质量 location ~ (.*):(w|h|m)(\d+)-(\d+).(gif|jpg|jpeg|png)$ { root /var/file/thumb/picture; #bucketname = picture set $bucketname picture; #原图片路径 set $srcPath /var/file/$bucketname; #目录图片路径 set $destPath /var/file/thumb/$bucketname; #处理类型 set $type 6; if (!-f $request_filename) { rewrite_by_lua_file conf/lua/picture_image_thumb.lua; } expires 30d; } #宽,高 CUT location ~ (.*)\@(\d+)x(\d+).(gif|jpg|jpeg|png)$ { root /var/file/thumb/picture; #bucketname = picture set $bucketname picture; #原图片路径 set $srcPath /var/file/$bucketname; #目录图片路径 set $destPath /var/file/thumb/$bucketname; #处理类型 set $type 7; if (!-f $request_filename) { rewrite_by_lua_file conf/lua/picture_image_thumb.lua; } expires 30d; } #宽,高 CUT 质量 location ~ (.*)\@(\d+)x(\d+)-(\d+).(gif|jpg|jpeg|png)$ { root /var/file/thumb/picture; #bucketname = picture set $bucketname picture; #原图片路径 set $srcPath /var/file/$bucketname; #目录图片路径 set $destPath /var/file/thumb/$bucketname; #处理类型 set $type 8; if (!-f $request_filename) { rewrite_by_lua_file conf/lua/picture_image_thumb.lua; } expires 30d; } location ~ .*.(gif|jpg|jpeg|png|bmp|swf|css|js|html)$ { expires 30d; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } }
配置文件有些长,主要是匹配参数的规制,如果缩略图存在,就不生成了,大约有8个规则匹配,上面的set $type 8会传给lua进行处理
如果你是初次进行nginx+lua编程,建议使用file,如rewrite_by_lua_file,不要直接在nginx的配置文件中写lua代码,因为单引,双引的问题,会出现很诡异的问题
下面我们再看一下picture_image_thumb.lua的代码
local wh = { {'w',170}, {'w',200}, {'w',224}, {'w',365}, {'w',150}, {'w',237}, {'w',420}, {'w',450}, {'h',32}, {'m',600}, } -- 定义可缩放尺寸 local xy = { {132,123}, {404,250}, {239,192}, {415,353}, {157,124}, {210,131}, {150,100}, {110,80}, {200,150}, {110,110}, {345,230}, {164,105}, {231,73}, } local qualitys = {10,20,30,40,50,60,70,80,90,100} -- 匹配模式 -- 宽, 高, if ngx.var.type == string.format('%d', 1) and string.find(ngx.var.uri, '(.*)!(%d+)x(%d+)%.(%w+)') then _, _, uriName, width, height, extName = string.find(ngx.var.uri, '(.*)!(%d+)x(%d+)%.(%w+)') size = "!" .. width .. "x" .. height srcFile = ngx.var.srcPath .. uriName .. "." .. extName -- 宽, 高, 质量 elseif ngx.var.type == string.format('%d', 2) and string.find(ngx.var.uri, '(.*)!(%d+)x(%d+)-(%d+)%.(%w+)') then _, _, uriName, width, height, quality, extName = string.find(ngx.var.uri, '(.*)!(%d+)x(%d+)-(%d+)%.(%w+)') size = "!" .. width .. "x" .. height .. '-' .. quality srcFile = ngx.var.srcPath .. uriName .. "." .. extName -- 宽高相等 elseif ngx.var.type == string.format('%d', 3) and string.find(ngx.var.uri, '(.*)!(%d+)%.(%w+)') then _, _, uriName, width, extName = string.find(ngx.var.uri, '(.*)!(%d+)%.(%w+)') size = "!" .. width height = width srcFile = ngx.var.srcPath .. uriName .. "." .. extName -- 宽高相等, 质量 elseif ngx.var.type == string.format('%d', 4) and string.find(ngx.var.uri, '(.*)!(%d+)-(%d+)%.(%w+)') then _, _, uriName, width, quality, extName = string.find(ngx.var.uri, '(.*)!(%d+)-(%d+)%.(%w+)') size = "!" .. width .. '-' .. quality height = width srcFile = ngx.var.srcPath .. uriName .. "." .. extName -- 宽,高 等比 elseif ngx.var.type == string.format('%d', 5) and string.find(ngx.var.uri, '(.*):([whm])(%d+)%.(%w+)') then _, _, uriName, sidetype, num, extName = string.find(ngx.var.uri, '(.*):([whm])(%d+)%.(%w+)') size = ":" .. sidetype .. num srcFile = ngx.var.srcPath .. uriName .. "." .. extName -- 宽,高 等比, 质量 elseif ngx.var.type == string.format('%d', 6) and string.find(ngx.var.uri, '(.*):([whm])(%d+)-(%d+).(%w+)') then _, _, uriName, sidetype, num, quality, extName = string.find(ngx.var.uri, '(.*):([whm])(%d+)-(%d+)%.(%w+)') size = ":" .. sidetype .. num .. '-' .. quality srcFile = ngx.var.srcPath .. uriName .. "." .. extName -- 宽,高 CUT elseif ngx.var.type == string.format('%d', 7) and string.find(ngx.var.uri, '(.*)@(%d+)x(%d+)%.(%w+)') then _, _, uriName, width, height, extName = string.find(ngx.var.uri, '(.*)@(%d+)x(%d+)%.(%w+)') size = "@" .. width .. "x" .. height srcFile = ngx.var.srcPath .. uriName .. "." .. extName elseif ngx.var.type == string.format('%d', 8) and string.find(ngx.var.uri, '(.*)@(%d+)x(%d+)-(%d+)%.(%w+)') then _, _, uriName, width, height, quality, extName = string.find(ngx.var.uri, '(.*)@(%d+)x(%d+)-(%d+)%.(%w+)') size = "@" .. width .. "x" .. height .. '-' .. quality srcFile = ngx.var.srcPath .. uriName .. "." .. extName end if quality ~= nil then -- 检测图片质量是否在范围内 local qualityfound = 0 for i=1, #qualitys do local q = qualitys[i] if string.format('%d', q) == string.format('%d', quality) then qualityfound = 1 break; end end if qualityfound == 0 then ngx.req.set_uri(ngx.var.uri, true) end end -- 等比 if sidetype ~= nil then local sidetypefound=0 local i = 1 for i=1, #wh do local t = wh[i][1] local n = wh[i][2] if string.format('%s', t) == sidetype and string.format('%d', n) == num then sidetypefound = 1 break; end end if sidetypefound == 0 then ngx.req.set_uri(ngx.var.uri, true) end else local found=0 local i = 1 for i=1, #xy do local x = xy[i][1] local y = xy[i][2] if string.format('%d', x) == width and string.format('%d', y) == height then found = 1 break; end end if found == 0 then -- 显示文件 ngx.req.set_uri(ngx.var.uri, true) end end local i = 0 local last = 0 while true do i = string.find(uriName,'/',i+1) if i == nil then break end last = i end -- 文件路径 local uriPath = string.sub(uriName, 1, last) -- 主文件名 local mainFilename = string.sub(uriName, last+1) -- 创建目录 local dircmd = '/bin/mkdir -p ' .. ngx.var.destPath .. uriPath os.execute(dircmd) -- 创建文件 local command = '/opt/scripts/makeImage.sh \\' .. size .. ' ' .. srcFile .. ' ' .. ngx.var.destPath .. uriName .. '\\' .. size .. '.' .. extName os.execute(command) -- 显示文件 ngx.req.set_uri(ngx.var.uri, true)
lua文件主要是进行参数的验证,具体处理图片,都交给了shell脚本,因为如果不限制宽度,那么会被攻击,产生没有必要的图片,浪费服务器资源。
下面我们看一下makeImage.sh脚本,脚本调用了conver和identify命令,请自行安装imagemagick
#/bin/sh CONVERT=/usr/bin/convert IDENTIFY=/usr/bin/identify DEFAULTQUALITY=70 make1 () { T=`echo "$1" | cut -b2- | cut -d- -f1` Q=`echo "$1" | cut -b2- | cut -d- -f2` if [ "$Q" == "$T" ]; then Q=$DEFAULTQUALITY fi W=`echo "$T" | cut -dx -f1` H=`echo "$T" | cut -dx -f2` $CONVERT $2 -resize $W\!x$H! -quality $Q $3 } make2 () { #是宽还是高或才是m C=`echo "$1" | cut -b2` #像素 T=`echo "$1" | cut -b3- | cut -d- -f1` #质量 Q=`echo "$1" | cut -b3- | cut -d- -f2` if [ "$Q" == "$T" ]; then Q=$DEFAULTQUALITY; fi case "$C" in w) RESIZE=$T ;; h) RESIZE=x$T ;; m) WH=`$IDENTIFY -format "%wx%h" $2` W=`echo "$WH" | cut -dx -f1` H=`echo "$WH" | cut -dx -f2` if test `/usr/bin/expr $T - $W` -gt 0 && test `/usr/bin/expr $T - $H` ; then RESIZE=$Wx$H elif test `/usr/bin/expr $W - $H` -gt 0 ; then RESIZE=$T else RESIZE=x$T fi ;; *) exit 1; esac $CONVERT $2 -resize $RESIZE -quality $Q $3 } make3 () { T=`echo "$1" | cut -b2- | cut -d- -f1` Q=`echo "$1" | cut -b2- | cut -d- -f2` if [ "$Q" == "$T" ]; then Q=$DEFAULTQUALITY fi W=`echo "$T" | cut -dx -f1` H=`echo "$T" | cut -dx -f2` SRCWH=`$IDENTIFY -format "%wx%h" $2` SRCW=`echo "$SRCWH" | cut -dx -f1` SRCH=`echo "$SRCWH" | cut -dx -f2` if test `/usr/bin/expr $SRCW - $SRCH` -gt 0 ; then FLAG='W' RESIZE=x$H else FLAG='H' RESIZE=$W fi TMPFILE=`mktemp` /bin/rm -rf $TMPFILE $CONVERT $2 -resize $RESIZE $TMPFILE REWH=`$IDENTIFY -format "%wx%h" $TMPFILE` REW=`echo "$REWH" | cut -dx -f1` REH=`echo "$REWH" | cut -dx -f2` if test $FLAG == 'W'; then DESTW=`/usr/bin/expr $W / 2` RESIZEW=`/usr/bin/expr $REW / 2` OFFSETW=`/usr/bin/expr $RESIZEW - $DESTW` $CONVERT -crop $T+$OFFSETW+0 -quality $Q $TMPFILE $3 else DESTH=`/usr/bin/expr $H / 2` RESIZEH=`/usr/bin/expr $REH / 2` OFFSETH=`/usr/bin/expr $RESIZEH - $DESTH` $CONVERT -crop $T+0+$OFFSETH -quality $Q $TMPFILE $3 fi /bin/rm -rf $TMPFILE } if test ! -f $2; then echo "无法访问$2: 没有那个文件" exit 1; fi $IDENTIFY -format "%wx%h" $2 > /dev/null 2>&1 REVAL=`echo $?` if [ "x$REVAL" != "x0" ]; then echo "$2不是图片类型文件" exit 1; fi TYPE=`echo "$1" | cut -b1`; case "$TYPE" in !) make1 $1 $2 $3 ;; :) make2 $1 $2 $3 ;; @) make3 $1 $2 $3 ;; *) echo $"Usage: $0 {:w100|!200|@300x200}" exit 1 esac
现在这个程序就编写完了,如果你有任何问题,欢迎与我探讨,QQ:8025628