最近看了豆瓣的头像剪切应用,也想自己做一个,但在Django上做这样的功能,怎么入手呢?google了一下,很少相关的材料,还得自己构想一下。理清了思路,大概是这样的:前端使用JQuery,用户选择剪切区域,之后取到图片的宽度和高度,起始点的xy坐标(左上角的xy轴位置)和结束点的xy坐标(右下角的xy轴位置),然后把这六个参数呈现到表单上,提交给django处理;后端用Python的PIL来处理,Python的PIL图形处理类库功能很全,可以在里面找到相关的函数,根据前端提供的六个参数,对原图片进行剪切。
废话少说,开始动手了,搜了相关的JQuery插件,发现顶顶有名的插件:
jquery imgareaselect ,恰恰是我需要的东西,通过它能得到我上面提到的六个必须参数。倒是Python的PIL库很陌生,找了很多的函数都不满意,所以暂时使用transform函数,它是我目前所需最接近的函数。
前端页面有两个图片容器,一个是放置原图的,另一个是放置缩略图,然后是六个输入框,分别呈现剪切图片的宽度 高度 起始点的x坐标值,y坐标值和结束点的x坐标值,y坐标值。只要脚本选中区域,这六个输入框就有数据,然后提交给django来处理,这样的话显然用到django的form了。准备工作算是完成了,开始编代码了:
前端代码hathead_cut.html:
<
link
rel
="stylesheet"
type
="text/css"
href
="/media/js/jquery_imgareaselect_0_9_1/css/imgareaselect-default.css"
/>
< style rel ="stylesheet" type ="text/css" >
.demo {
background : none repeat scroll 0 0 #EEEEFF ;
border : 2px solid #DDDDEE ;
padding : 0.6em ;
width : 85% ;
}
div.frame {
background : none repeat scroll 0 0 #FFFFFF ;
border : 2px solid #DDDDDD ;
padding : 0.8em ;
}
</ style >
< script type ="text/javascript" src ="/media/js/jquery.js" ></ script >
< script type ="text/javascript" src ="/media/js/jquery_imgareaselect_0_9_1/scripts/jquery.imgareaselect.min.js" ></ script >
< script type ="text/javascript" >
function preview(img, selection) {
if ( ! selection.width || ! selection.height)
return ;
var scaleX = 100 / selection.width;
var scaleY = 100 / selection.height;
$( ' #preview img ' ).css({
width: Math.round(scaleX * 300 ),
height: Math.round(scaleY * 300 ),
marginLeft: - Math.round(scaleX * selection.x1),
marginTop: - Math.round(scaleY * selection.y1)
});
$( ' #id_x1 ' ).val(selection.x1);
$( ' #id_y1 ' ).val(selection.y1);
$( ' #id_x2 ' ).val(selection.x2);
$( ' #id_y2 ' ).val(selection.y2);
$( ' #id_w ' ).val(selection.width);
$( ' #id_h ' ).val(selection.height);
}
$( function () {$( ' #photo ' ).imgAreaSelect({ aspectRatio: ' 1:1 ' , handles: true ,
fadeSpeed: 200 , minHeight: 100 ,minWidth: 100 ,onSelectChange: preview });
});
</ script >
< div >
< h3 > 头像剪切 </ h3 >
< div class ="demo" >
< div style ="float: left; width: 45%;" >
< p class ="instructions" >
点击原图 选择剪切区域
</ p >
< div style ="margin: 0pt 0.3em; width: 300px; height: 300px;" class ="frame" >
< img src ="{{baseinfo.hathead}}_300_300.jpg" id ="photo" alt ="30" />
</ div >
</ div >
< div style ="float: left; width: 40%; padding-top:50px;" >
< p style ="font-size: 110%; font-weight: bold; padding-left: 0.1em;" >
预览选择区域
</ p >
< div style ="margin: 0pt 1em; width: 100px; height: 100px;" class ="frame" >
< div style ="width: 100px; height: 100px; overflow: hidden;" id ="preview" >
< img style ="width: 244px; height: 244px; margin-left: -71px; margin-top: -54px;" src ="{{baseinfo.hathead}}_300_300.jpg" alt ="300" />
</ div >
</ div >
< form action ="" method ="POST" >
< table style ="margin-top: 1em;" >
< thead >
< tr >
< th style ="font-size: 110%; font-weight: bold; text-align: left; padding-left: 0.1em;" colspan ="2" >
剪切坐标
</ th >
< th style ="font-size: 110%; font-weight: bold; text-align: left; padding-left: 0.1em;" colspan ="2" >
剪切尺寸
</ th >
</ tr >
</ thead >
< tbody >
< tr >
< td style ="width: 10%;" >< b > X < sub > 1 </ sub > : </ b ></ td >
< td style ="width: 30%;" > {{form.x1}} </ td >
< td style ="width: 20%;" >< b > 宽度: </ b ></ td >
< td > {{form.w}} </ td >
</ tr >
< tr >
< td >< b > Y < sub > 1 </ sub > : </ b ></ td >
< td > {{form.y1}} </ td >
< td >< b > 高度: </ b ></ td >
< td > {{form.h}} </ td >
</ tr >
< tr >
< td >< b > X < sub > 2 </ sub > : </ b ></ td >
< td > {{form.x2}} </ td >
< td ></ td >
< td ></ td >
</ tr >
< tr >
< td >< b > Y < sub > 2 </ sub > : </ b ></ td >
< td > {{form.y2}} </ td >
< td ></ td >
< td >< input type ="submit" value ="保存" /></ td >
</ tr >
</ tbody >
</ table >
</ form >
</ div >
< style rel ="stylesheet" type ="text/css" >
.demo {
background : none repeat scroll 0 0 #EEEEFF ;
border : 2px solid #DDDDEE ;
padding : 0.6em ;
width : 85% ;
}
div.frame {
background : none repeat scroll 0 0 #FFFFFF ;
border : 2px solid #DDDDDD ;
padding : 0.8em ;
}
</ style >
< script type ="text/javascript" src ="/media/js/jquery.js" ></ script >
< script type ="text/javascript" src ="/media/js/jquery_imgareaselect_0_9_1/scripts/jquery.imgareaselect.min.js" ></ script >
< script type ="text/javascript" >
function preview(img, selection) {
if ( ! selection.width || ! selection.height)
return ;
var scaleX = 100 / selection.width;
var scaleY = 100 / selection.height;
$( ' #preview img ' ).css({
width: Math.round(scaleX * 300 ),
height: Math.round(scaleY * 300 ),
marginLeft: - Math.round(scaleX * selection.x1),
marginTop: - Math.round(scaleY * selection.y1)
});
$( ' #id_x1 ' ).val(selection.x1);
$( ' #id_y1 ' ).val(selection.y1);
$( ' #id_x2 ' ).val(selection.x2);
$( ' #id_y2 ' ).val(selection.y2);
$( ' #id_w ' ).val(selection.width);
$( ' #id_h ' ).val(selection.height);
}
$( function () {$( ' #photo ' ).imgAreaSelect({ aspectRatio: ' 1:1 ' , handles: true ,
fadeSpeed: 200 , minHeight: 100 ,minWidth: 100 ,onSelectChange: preview });
});
</ script >
< div >
< h3 > 头像剪切 </ h3 >
< div class ="demo" >
< div style ="float: left; width: 45%;" >
< p class ="instructions" >
点击原图 选择剪切区域
</ p >
< div style ="margin: 0pt 0.3em; width: 300px; height: 300px;" class ="frame" >
< img src ="{{baseinfo.hathead}}_300_300.jpg" id ="photo" alt ="30" />
</ div >
</ div >
< div style ="float: left; width: 40%; padding-top:50px;" >
< p style ="font-size: 110%; font-weight: bold; padding-left: 0.1em;" >
预览选择区域
</ p >
< div style ="margin: 0pt 1em; width: 100px; height: 100px;" class ="frame" >
< div style ="width: 100px; height: 100px; overflow: hidden;" id ="preview" >
< img style ="width: 244px; height: 244px; margin-left: -71px; margin-top: -54px;" src ="{{baseinfo.hathead}}_300_300.jpg" alt ="300" />
</ div >
</ div >
< form action ="" method ="POST" >
< table style ="margin-top: 1em;" >
< thead >
< tr >
< th style ="font-size: 110%; font-weight: bold; text-align: left; padding-left: 0.1em;" colspan ="2" >
剪切坐标
</ th >
< th style ="font-size: 110%; font-weight: bold; text-align: left; padding-left: 0.1em;" colspan ="2" >
剪切尺寸
</ th >
</ tr >
</ thead >
< tbody >
< tr >
< td style ="width: 10%;" >< b > X < sub > 1 </ sub > : </ b ></ td >
< td style ="width: 30%;" > {{form.x1}} </ td >
< td style ="width: 20%;" >< b > 宽度: </ b ></ td >
< td > {{form.w}} </ td >
</ tr >
< tr >
< td >< b > Y < sub > 1 </ sub > : </ b ></ td >
< td > {{form.y1}} </ td >
< td >< b > 高度: </ b ></ td >
< td > {{form.h}} </ td >
</ tr >
< tr >
< td >< b > X < sub > 2 </ sub > : </ b ></ td >
< td > {{form.x2}} </ td >
< td ></ td >
< td ></ td >
</ tr >
< tr >
< td >< b > Y < sub > 2 </ sub > : </ b ></ td >
< td > {{form.y2}} </ td >
< td ></ td >
< td >< input type ="submit" value ="保存" /></ td >
</ tr >
</ tbody >
</ table >
</ form >
</ div >
django的form代码:
class
HatHeadCutForm(forms.Form):
x1 = forms.IntegerField(widget = forms.TextInput(attrs = { ' size ' : 4 ,}))
y1 = forms.IntegerField(widget = forms.TextInput(attrs = { ' size ' : 4 ,}))
x2 = forms.IntegerField(widget = forms.TextInput(attrs = { ' size ' : 4 ,}))
y2 = forms.IntegerField(widget = forms.TextInput(attrs = { ' size ' : 4 ,}))
w = forms.IntegerField(widget = forms.TextInput(attrs = { ' size ' : 4 ,}))
h = forms.IntegerField(widget = forms.TextInput(attrs = { ' size ' : 4 ,}))
x1 = forms.IntegerField(widget = forms.TextInput(attrs = { ' size ' : 4 ,}))
y1 = forms.IntegerField(widget = forms.TextInput(attrs = { ' size ' : 4 ,}))
x2 = forms.IntegerField(widget = forms.TextInput(attrs = { ' size ' : 4 ,}))
y2 = forms.IntegerField(widget = forms.TextInput(attrs = { ' size ' : 4 ,}))
w = forms.IntegerField(widget = forms.TextInput(attrs = { ' size ' : 4 ,}))
h = forms.IntegerField(widget = forms.TextInput(attrs = { ' size ' : 4 ,}))
django的views代码:
def
hathead_cut(request,id):
template_var = {}
try :
baseinfo = BaseInfo.objects.get(id = int(id))
except BaseInfo.DoesNotExist:
return Http404()
if not baseinfo.hathead:
request.user.message_set.create(message = u " 请先上传图片! " )
return HttpResponseRedirect(reverse( " upload_hathead " ))
template_var[ " baseinfo " ] = baseinfo
abs_path = " %s/%s/ " % (MEDIA_ROOT, " hathead " )
file_name = " %s_%s " % (request.user.username, " hathead_300_300.jpg " )
form = HatHeadCutForm()
if request.method == ' POST ' :
form = HatHeadCutForm(request.POST)
if form.is_valid():
try :
img = Image.open(abs_path + file_name)
except IOError:
request.user.message_set.create(message = u " 系统错误! " )
data = form.cleaned_data
img = img.transform((data[ " w " ],data[ " h " ]),EXTENT,(data[ " x1 " ],data[ " y1 " ],data[ " x2 " ],data[ " y2 " ]))
img.thumbnail(( 100 , 100 ))
file_name = " %s_%s " % (request.user.username, " hathead_100_100.jpg " )
img.save( " %s%s " % (abs_path,file_name), " JPEG " )
img.thumbnail(( 50 , 50 ))
file_name = " %s_%s " % (request.user.username, " hathead_50_50.jpg " )
img.save( " %s%s " % (abs_path,file_name), " JPEG " )
request.user.message_set.create(message = u " 保存成功! " )
return HttpResponseRedirect(reverse( " upload_hathead " ))
else :
request.user.message_set.create(message = u " 请剪切后 再保存! " )
template_var[ " form " ] = form
return render_to_response( " hathead_cut.html " ,template_var,context_instance = RequestContext(request))
template_var = {}
try :
baseinfo = BaseInfo.objects.get(id = int(id))
except BaseInfo.DoesNotExist:
return Http404()
if not baseinfo.hathead:
request.user.message_set.create(message = u " 请先上传图片! " )
return HttpResponseRedirect(reverse( " upload_hathead " ))
template_var[ " baseinfo " ] = baseinfo
abs_path = " %s/%s/ " % (MEDIA_ROOT, " hathead " )
file_name = " %s_%s " % (request.user.username, " hathead_300_300.jpg " )
form = HatHeadCutForm()
if request.method == ' POST ' :
form = HatHeadCutForm(request.POST)
if form.is_valid():
try :
img = Image.open(abs_path + file_name)
except IOError:
request.user.message_set.create(message = u " 系统错误! " )
data = form.cleaned_data
img = img.transform((data[ " w " ],data[ " h " ]),EXTENT,(data[ " x1 " ],data[ " y1 " ],data[ " x2 " ],data[ " y2 " ]))
img.thumbnail(( 100 , 100 ))
file_name = " %s_%s " % (request.user.username, " hathead_100_100.jpg " )
img.save( " %s%s " % (abs_path,file_name), " JPEG " )
img.thumbnail(( 50 , 50 ))
file_name = " %s_%s " % (request.user.username, " hathead_50_50.jpg " )
img.save( " %s%s " % (abs_path,file_name), " JPEG " )
request.user.message_set.create(message = u " 保存成功! " )
return HttpResponseRedirect(reverse( " upload_hathead " ))
else :
request.user.message_set.create(message = u " 请剪切后 再保存! " )
template_var[ " form " ] = form
return render_to_response( " hathead_cut.html " ,template_var,context_instance = RequestContext(request))
以上的代码,先读取一张300*300的图片,脚本选中100*100的区域,提交给Django剪切,最后把100*100剪切好的图再剪切一张50*50的图。脚本剪切的比列是1:1。脚本jquery imgareaselect的相关文档你可以从
官方网找到,可以任意做出自己需要的效果。django的form的作用是渲染出表单(六个输入框。注:图片是直接读取磁盘文件,缩略图效果全是是jquery imgareaselect生成的);django的views的作用是根据表单提交的六个参数,剪切图片,主要代码是:img=img.transform((data["w"],data["h"]),EXTENT,(data["x1"],data["y1"],data["x2"],data["y2"]))。
之前有一个操作就不在这里介绍了,它的工作主要是上传一张宽度高度不规则的图片,然后只经过PIL剪切成正方形的图片,保存到磁盘里,PIL剪切图片很简单,如果想把一张不规则的图片剪切成正方形就需要用到一点技巧了,如果兴趣的话,接下来想单独介绍一下原理。