SpringMVC+JQuery实现头像编辑器

时间:2023-03-09 20:54:18
SpringMVC+JQuery实现头像编辑器

一、简单说明

  本头像编辑器主要实现了图片的上传、显示(不溢出父窗口)、旋转、裁剪功能!

  1. 图片的上传用到的是异步上传,页面不进行刷新,原理是通过JQuery的异步提交+SpringMVC的上传
  2. 上传完毕后,在显示的时候,如果图片的长宽都比父窗口小,则垂直 水平 居中显示在父窗口中,否则,对图片按照宽高比进行缩放,直到长宽都不溢出!
  3. 旋转功能,这块有待改进!我在上传一张图片的时候,直接在后台将90度,180度,270度的也上传了,然后,裁剪完成后,保存头像之后,将不用的都删除!这儿确实不太现实!这里推荐用HTML5的canvas实现!
  4. 裁剪功能。用到了JQuery的一个插件。参见区域选择完成后,会自动返回 x,y,w,h。这里的x,y,w,h还需要经过处理,因为你前面可能进行了缩小显示,这里需要让x,y,w,h还原到未缩放前的坐标。然后,在后台中,通过这些坐标,截取图片中 (x,y) 宽:w 高:h的图片!

二、效果图:

  1. 初始化界面

SpringMVC+JQuery实现头像编辑器

2.选择图片

SpringMVC+JQuery实现头像编辑器

3.显示选择的图片

SpringMVC+JQuery实现头像编辑器

4.旋转图片

SpringMVC+JQuery实现头像编辑器

5.拖动鼠标选择裁剪框

SpringMVC+JQuery实现头像编辑器

6.保存头像,并且显示

SpringMVC+JQuery实现头像编辑器

三、代码实现

1.图片的选择以及上传

我们肯定都见过自带的上传按钮SpringMVC+JQuery实现头像编辑器,这个按钮比较难看。SpringMVC+JQuery实现头像编辑器,这个是不是比上一张强多了!其实就是 一个buton按钮,然后将上面的<input type="file">隐藏了,然后当点击button按钮的时候,调用file的事件。这样就实现了和点击file一样的效果。

<input type="button" value="选择头像" class="bt"><font color="#999999"><span>&nbsp;&nbsp;支持jpg、jpeg、gif、png、bmp格式的图片</span>
<input type="file" id="file" name="file"> <!--可以在css中设置隐藏属性或者在js中设置-->
 $(".bt").click(function(){
$("#file").click();
});

这样,就实现了弹出选择框的功能。接下来需要实现的就是选择图片。这里需要注意的是,现在由于浏览器的安全机制,默认情况下,你是不能直接访问本地图片地址,例如<img src="d:1.png">这种情况下在jsp页面中完全不能用,除非你对浏览器进行设置。但是这么是不合理的。所以,最好能够上传图片到服务器。

SpringMVC中,上传图片用到了"commons-fileupload-1.2.2.jar"这个插件,如果要使用图片上传,需要注意一下几点:

  • 在SpringMVC的配置文件中,添加一下内容
 <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="utf-8" />
<property name="maxUploadSize" value="10485760000" />
<property name="maxInMemorySize" value="40960" />
</bean>
  • 在HTML中,表单的提交中,需要设置'enctype="multipart/form-data" '

上传完图片后,这里不像再去跳转,所以,这里我用到了异步上传,所谓的异步上传,其实就是异步提交表单。异步提交用得到了JQuery的异步提交插件:"jquery-form.js";

  

 $("#file").change(function(){
var options = {
url : "upload/image", //这个url是异步提交的路径,这里对应的是上传图片的路径
dataType : 'json',
contentType : "application/json; charset=utf-8",
success : function(data) {
//上传成功后,需要进行哪些处理,比方说这里实现的是显示图片
};
$("#uploadImgForm").ajaxSubmit(options);
});

  

如上所示,就实现了图片的异步上传,那么后台是如何处理?这里 是先上传原图,然后再用一个旋转类,将该图片90度,180度,270度的图片保存成功。以供旋转使用,这里是一个大问题,其实我们可以使用html5的canvas实现,这样只需要上传一张图片呢。

 @Controller
@RequestMapping(value="upload")
public class FileUploadController { @RequestMapping(value="image")
public void fileUpload(MultipartHttpServletRequest request, HttpServletResponse response) {
Map<String, Object> resultMap = new HashMap<String, Object>();
String newRealFileName = null;
try {
MultipartHttpServletRequest multipartRequest = request;
CommonsMultipartFile file = (CommonsMultipartFile) multipartRequest.getFile("file");
// 获得文件名:
String realFileName = file.getOriginalFilename();
if(file.getSize()/1024>=5*1024){
resultMap.put("status", 1);
resultMap.put("message", "图片不能大于5M");
}else{
System.out.println("获得文件名:" + realFileName);
newRealFileName = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + realFileName.substring(realFileName.indexOf("."));
// 获取路径
String ctxPath = request.getSession().getServletContext().getRealPath("//") + "//temp//";
System.out.println(ctxPath);
// 创建文件
File dirPath = new File(ctxPath);
if (!dirPath.exists()) {
dirPath.mkdir();
}
File uploadFile = new File(ctxPath +"now"+ newRealFileName);
FileCopyUtils.copy(file.getBytes(), uploadFile);
// request.setAttribute("files", loadFiles(request)); //获取图片的宽度和高度
InputStream is = new FileInputStream(ctxPath +"now"+ newRealFileName);
BufferedImage buff = ImageIO.read(is);
int realWidth=buff.getWidth();
int realHeight = buff.getHeight();
is.close();
//接下来将图片的四个旋转进行保存
//1.向左旋转90度
String onenewRealFileName = "one"+newRealFileName;
Thumbnails.of(uploadFile).size(realWidth, realHeight).rotate(90).toFile(new File(ctxPath+onenewRealFileName)
);
//2.向左旋转180度
String twonewRealFileName="two"+newRealFileName;
Thumbnails.of(uploadFile).size(realWidth, realHeight).rotate(180).toFile(new File(ctxPath+twonewRealFileName)
);
//3.向左旋转270度
String threenewRealFileName="thr"+newRealFileName;
Thumbnails.of(uploadFile).size(realWidth, realHeight).rotate(270).toFile(new File(ctxPath+threenewRealFileName)
);
resultMap.put("status", 0);
resultMap.put("fileName", "now"+newRealFileName);
}
} catch (Exception e) {
resultMap.put("status", 1);
resultMap.put("message", "图片上传出错");
System.out.println(e);
} finally {
PrintWriter out = null;
try {
out = response.getWriter();
} catch (IOException e) {
e.printStackTrace();
}
//必须设置字符编码,否则返回json会乱码
response.setContentType("text/html;charset=UTF-8");
out.write(JSONSerializer.toJSON(resultMap).toString());
out.flush();
out.close();
}
}
80}

2.图片的展示

图片上传成功后,然后讲一个json字符串返回到前端,其中包括了上传完后图片的路径。下面就需要展示图片。想一下,上传的图片大小不一,DIV如何才能包住图片呢?如果在<img>设置图片的宽度=div宽度,图片的高度=div高度,这样的话,对图片会进行不等比例拉伸或者缩小,这样图片就完全变形,看上去肯定很不美观。我们需要做的就是,让图片按照原来的比例进行缩放。这样,图片看上去没有拉伸的那种效果。

这种请款下,就得分四种情况讨论:

  • 图片的原始宽和高都比DIV的宽和高小,那么这种情况下,图片需要在水平和垂直上居中显示

SpringMVC+JQuery实现头像编辑器

我是通过margin-top属性和margin-left属性来对图片进行设置,以使其居中。margin-top:(DIV高-图片高)/2    margin-left:(DIV宽-图片宽)/2

  • 图片的原始宽<DIV的宽,图片的原始高>DIV的高,这种情况下,计算图片的原始宽高比compareImage,然后,让图片的高=DIV高度,再根据图片的原始宽高比,算出现在图片的宽,也就是图片缩放的效果;

SpringMVC+JQuery实现头像编辑器

这种情况下margin-top:0 margin-left:(DIV宽度-图片现宽)/2让图片居中;

  • 图片的原始宽>DIV的宽,图片的原始高<DIV的高,这种情况下,计算图片的原始宽高比compareImage,然后,让图片的宽=DIV宽,再根据图片的原始宽高比,算出现在图片的高,也就是图片缩放的效果;后面两种效果图就不展示了
  • 图片的原始宽>DIV的宽,图片的原始高>DIV的高,这种情况比较复杂,因为你要比较谁最后溢出,例如,因为图片的宽和高都溢出,所以,如果图片你的宽==DIV的宽度,那么按照比例缩放后,你必须保证图片现高<=DIV的高度。也就是必须保证图片的宽和高都不溢出。

好,既然长宽都固定好了,肯定图片不会溢出DIV,那么现在就需要在DIV中展示效果,这里用到了"jquery.Jcrop.min.js"这个插件来实现,图片加载成功的时候,裁剪框也跟着显示出来。

也许你不愿意去看懂"jquery.Jcrop.min.js"的代码,说实话,我也不愿意去看,用法:

  • 初始化一个对象,这个对象在我的"jQuery.UtrialAvatarCutter.js"中有说明,下面有提供下载地址,以及源码

这里的picture_original指的是图片外面DIV的id ,这里的resourceImage是图片的id

这里的bigImage 和 smallImage指的是头像预览两个DIV框

var cutter = new jQuery.UtrialAvatarCutter(
{
//主图片所在容器ID
content : "picture_original,resourceImage", //缩略图配置,ID:所在容器ID;width,height:缩略图大小
purviews : [{id:"bigImage",width:100,height:100},{id:"smallImage",width:50,height:50}],
}
);

    

  • 在图片加载完成的时候,这里,也就是异步提交完成的时候,进行的处理,在上面异步提交图片的时候有说明,success()里面的逻辑代码没有写,下面就是要处理的业务逻辑,也就是设置图片的宽和高,显示裁剪框
 success : function(data) {

     $(".page-left-center").hide();
$(".page-left-center1").show();
var imagepath="temp/"+data.fileName;
$("#resourceImage").attr("src", imagepath).load(function(){
//获取图片的真实大小:
var realWidth;
var realHeight;
realWidth = parseInt(this.width);
realHeight =parseInt(this.height);
rWidth=realWidth;
rHeight=realHeight;
realCompare=parseFloat(realWidth)/parseFloat(realHeight);
//让图片适应div大小
console.info("图片的真实宽度:"+realWidth);
console.info("图片的真实高度:"+realHeight);
if(realWidth<divWidth){
if(realHeight<divHeight){
console.info("进入了宽小,高小");
imageWidthAfter=realWidth;
imageHeightAfter=realHeight;
shengHeight=parseInt((divHeight-imageHeightAfter)/2);
$("#resourceImage").css("margin-top",shengHeight);
shengWidth=parseInt((divWidth-imageWidthAfter)/2);
$("#resourceImage").css("margin-left",shengWidth);
suoCompare=1;
}else{
console.info("进入了宽小,高大");
imageHeightAfter=divHeight;
imageWidthAfter=imageHeightAfter*realCompare;
shengWidth=parseInt((divWidth-imageWidthAfter)/2);
$("#resourceImage").css("margin-left",shengWidth);
shengHeight=0;
suoCompare=parseFloat(realHeight)/parseFloat(divHeight);
}
}else{
if(realHeight<divHeight){
console.info("进入了宽大,高小");
imageWidthAfter=divWidth;
imageHeightAfter=parseFloat(divWidth/parseFloat(realCompare));
shengHeight=parseInt((divHeight-imageHeightAfter)/2);
$("#resourceImage").css("margin-top",shengHeight);
shengWidth=0;
suoCompare=parseFloat(realWidth)/parseFloat(divWidth);
}else{
console.info("进入了高大,宽大但处理后不满");
//刚开始假如高满宽不满,那么,根据高得出宽
imageHeightAfter=divHeight;
imageWidthAfter=imageHeightAfter*realCompare;
//处理完后,宽还是溢出的话,说明以宽为基准
console.info("处理后的宽度:"+imageWidthAfter);
if(imageWidthAfter>divWidth){
console.info("进入到了宽大,高大但高不满");
imageWidthAfter=divWidth;
imageHeightAfter=parseFloat(divWidth/parseFloat(realCompare));
shengHeight=parseInt((divHeight-imageHeightAfter)/2);
$("#resourceImage").css("margin-top",shengHeight);
shengWidth=0;
suoCompare=parseFloat(realWidth)/parseFloat(divWidth);
}else{
shengWidth=parseInt((divWidth-imageWidthAfter)/2);
$("#resourceImage").css("margin-left",shengWidth);
shengHeight=0;
suoCompare=parseFloat(realHeight)/parseFloat(divHeight);
}
}
} $("#resourceImage").show();
$(".jcrop-holder").remove();
console.info("shengHeight:"+shengHeight);
$("#resourceImage").width(imageWidthAfter);
$("#resourceImage").height(imageHeightAfter);
cutter.init();
$(".jcrop-holder").css("margin-top",shengHeight);
$(".jcrop-holder").css("margin-left",shengWidth);
$(".jcrop-holder img").css("margin-top","0px");
$(".jcrop-holder img").css("margin-left","0px");
});
$("#rechose").show();
$(".btsave").attr("disabled",false);
$(".btsave").addClass("btsaveclick");
}
};

    

3.图片的旋转

这块实现的不是特别理想,前面也说过了,就是刚开始上传的时候,上传四张,0度,90度,180度,270度。最近查看了HTML5的canvas,觉得这个挺好的。以后会试着通过HTML5来实现。旋转的实现很简单,每点击的时候,设置一个表示step 然后,通过switch进行判断,如果switch=0,则设置image的src=0度的图片,一次往后推。设置完成后,然后通过 cutter.init();重新显示裁剪框以及图片!

4.图片的裁剪

图片的裁剪原理,其实就是在原有图片的基础上,从(x,y)点开始,截取长度为宽度为w,高度为h的区域。

SpringMVC+JQuery实现头像编辑器

注意:由于显示的时候,不让图片超出DIV,所以,对图片进行了缩放,所以一这里你的x,y,w,h是还原后的。

 $(".btsave").click(function(){
var data = cutter.submit();
var trueX=parseInt(parseFloat(data.x)*suoCompare);
var trueY=parseInt(parseFloat(data.y)*suoCompare);
var trueW=parseInt(parseFloat(data.w)*suoCompare);
var trueH=parseInt(parseFloat(data.h)*suoCompare);
var message=trueX+","+trueY+","+trueW+","+trueH+","+data.s;
console.info(message);
$("#cutHeader").val(message);
$("#formSubmit").submit();
});

  如上所示,提交到后台,在后台进行处理,后台逻辑如下:

 @RequestMapping(value="cutImage")
public ModelAndView cutImage(HttpServletRequest request, HttpServletResponse response) throws IOException {
String message=request.getParameter("header");
int x=Integer.parseInt(message.split(",")[0]);
int y=Integer.parseInt(message.split(",")[1]);
int w=Integer.parseInt(message.split(",")[2]);
int h=Integer.parseInt(message.split(",")[3]);
String src=message.split(",")[4].split("\\?")[0];
//
// 获取路径
String ctxPath = request.getSession().getServletContext().getRealPath("//") ;
System.out.println(ctxPath);
// 创建文件
src =ctxPath+"\\"+src;
String last=src.substring(src.lastIndexOf ("."));
File dirPath = new File(ctxPath+"\\header");
if (!dirPath.exists()) {
dirPath.mkdir();
}
String dest = ctxPath+"\\header\\"+new SimpleDateFormat("yyyyMMddHHmmss").format(new Date())+last;
String headerurl="header/"+new SimpleDateFormat("yyyyMMddHHmmss").format(new Date())+last;
boolean isSuccess=cutImageHeader(src,dest,x,y,w,h);
if(isSuccess){
return new ModelAndView("showHeader","image",headerurl);
}else{
return new ModelAndView("changeHeader");
} } public static boolean cutImageHeader(String src,String dest,int x,int y,int w,int h) throws IOException{
String last=src.substring(src.lastIndexOf (".")+1);
System.out.println("*****"+last);
Iterator iterator = ImageIO.getImageReadersByFormatName(last);
ImageReader reader = (ImageReader)iterator.next();
InputStream in=new FileInputStream(src);
ImageInputStream iis = ImageIO.createImageInputStream(in);
reader.setInput(iis, true);
ImageReadParam param = reader.getDefaultReadParam();
Rectangle rect = new Rectangle(x, y, w,h);
System.out.println("x:"+x+";y:"+y+";w:"+w+";h:"+h);
param.setSourceRegion(rect);
BufferedImage bi = reader.read(0,param);
boolean isSuccess=ImageIO.write(bi, last, new File(dest));
return isSuccess;
}