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. 初始化界面

 

2.选择图片

 

3.显示选择的图片

 

4.旋转图片

 

5.拖动鼠标选择裁剪框

 

6.保存头像,并且显示

三、代码实现

1.图片的选择以及上传

我们肯定都见过自带的上传按钮,这个按钮比较难看。,这个是不是比上一张强多了!其实就是 一个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中设置-->

 

1 $(".bt").click(function(){
2     $("#file").click();
3 });

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

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

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

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

  

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

  

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

 1 @Controller
 2 @RequestMapping(value="upload")
 3 public class FileUploadController {
 4     
 5     @RequestMapping(value="image")
 6     public void fileUpload(MultipartHttpServletRequest request, HttpServletResponse response) {
 7         Map<String, Object> resultMap = new HashMap<String, Object>();
 8         String newRealFileName = null;
 9         try {
10         MultipartHttpServletRequest multipartRequest = request;
11         CommonsMultipartFile file = (CommonsMultipartFile) multipartRequest.getFile("file");
12         // 获得文件名:
13         String realFileName = file.getOriginalFilename();
14         if(file.getSize()/1024>=5*1024){
15         resultMap.put("status", 1);
16         resultMap.put("message", "图片不能大于5M");
17         }else{
18         System.out.println("获得文件名:" + realFileName);
19         newRealFileName = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + realFileName.substring(realFileName.indexOf("."));
20         // 获取路径
21         String ctxPath = request.getSession().getServletContext().getRealPath("//") + "//temp//";
22         System.out.println(ctxPath);
23         // 创建文件
24         File dirPath = new File(ctxPath);
25         if (!dirPath.exists()) {
26         dirPath.mkdir();
27         }
28         File uploadFile = new File(ctxPath +"now"+ newRealFileName);
29         FileCopyUtils.copy(file.getBytes(), uploadFile);
30 //        request.setAttribute("files", loadFiles(request));
31         
32         //获取图片的宽度和高度
33         InputStream is = new FileInputStream(ctxPath +"now"+ newRealFileName);
34         BufferedImage buff = ImageIO.read(is);
35         int realWidth=buff.getWidth();
36         int realHeight = buff.getHeight();
37         is.close();
38         //接下来将图片的四个旋转进行保存
39         //1.向左旋转90度
40         String onenewRealFileName = "one"+newRealFileName;
41         Thumbnails.of(uploadFile).size(realWidth, realHeight).rotate(90).toFile(new File(ctxPath+onenewRealFileName)
42                 );
43         //2.向左旋转180度
44         String twonewRealFileName="two"+newRealFileName;
45         Thumbnails.of(uploadFile).size(realWidth, realHeight).rotate(180).toFile(new File(ctxPath+twonewRealFileName)
46                 );
47         //3.向左旋转270度
48         String threenewRealFileName="thr"+newRealFileName;
49         Thumbnails.of(uploadFile).size(realWidth, realHeight).rotate(270).toFile(new File(ctxPath+threenewRealFileName)
50                 );
51         resultMap.put("status", 0);
52         resultMap.put("fileName", "now"+newRealFileName);
53         }
54         } catch (Exception e) {
55         resultMap.put("status", 1);
56         resultMap.put("message", "图片上传出错");
57         System.out.println(e);
58         } finally {
59         PrintWriter out = null;
60         try {
61         out = response.getWriter();
62         } catch (IOException e) {
63         e.printStackTrace();
64         }
65         //必须设置字符编码,否则返回json会乱码
66         response.setContentType("text/html;charset=UTF-8");
67         out.write(JSONSerializer.toJSON(resultMap).toString());
68         out.flush();
69         out.close();
70         }
71     }
80}

 

2.图片的展示

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

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

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

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

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

这种情况下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()里面的逻辑代码没有写,下面就是要处理的业务逻辑,也就是设置图片的宽和高,显示裁剪框
 1 success : function(data) {
 2                         
 3     $(".page-left-center").hide();
 4     $(".page-left-center1").show();
 5     var imagepath="temp/"+data.fileName;
 6     $("#resourceImage").attr("src", imagepath).load(function(){
 7          //获取图片的真实大小:
 8          var realWidth;
 9          var realHeight;
10          realWidth = parseInt(this.width);
11          realHeight =parseInt(this.height);
12          rWidth=realWidth;
13          rHeight=realHeight;
14          realCompare=parseFloat(realWidth)/parseFloat(realHeight);
15          //让图片适应div大小
16          console.info("图片的真实宽度:"+realWidth);
17          console.info("图片的真实高度:"+realHeight);
18          if(realWidth<divWidth){
19              if(realHeight<divHeight){
20                 console.info("进入了宽小,高小");
21                 imageWidthAfter=realWidth;
22                 imageHeightAfter=realHeight;
23                 shengHeight=parseInt((divHeight-imageHeightAfter)/2);
24                 $("#resourceImage").css("margin-top",shengHeight);
25                 shengWidth=parseInt((divWidth-imageWidthAfter)/2);
26                 $("#resourceImage").css("margin-left",shengWidth);
27                 suoCompare=1;
28              }else{
29                console.info("进入了宽小,高大");
30                imageHeightAfter=divHeight;
31                imageWidthAfter=imageHeightAfter*realCompare;
32                shengWidth=parseInt((divWidth-imageWidthAfter)/2);
33                $("#resourceImage").css("margin-left",shengWidth);
34                shengHeight=0;
35                suoCompare=parseFloat(realHeight)/parseFloat(divHeight);
36              }
37          }else{
38              if(realHeight<divHeight){
39                 console.info("进入了宽大,高小");
40                 imageWidthAfter=divWidth;
41                 imageHeightAfter=parseFloat(divWidth/parseFloat(realCompare));
42                 shengHeight=parseInt((divHeight-imageHeightAfter)/2);
43                 $("#resourceImage").css("margin-top",shengHeight);
44                 shengWidth=0;
45                 suoCompare=parseFloat(realWidth)/parseFloat(divWidth);
46              }else{
47                 console.info("进入了高大,宽大但处理后不满");
48                 //刚开始假如高满宽不满,那么,根据高得出宽
49                 imageHeightAfter=divHeight;
50                 imageWidthAfter=imageHeightAfter*realCompare;
51                 //处理完后,宽还是溢出的话,说明以宽为基准
52                 console.info("处理后的宽度:"+imageWidthAfter);
53                 if(imageWidthAfter>divWidth){
54                    console.info("进入到了宽大,高大但高不满");
55                    imageWidthAfter=divWidth;
56                    imageHeightAfter=parseFloat(divWidth/parseFloat(realCompare));
57                    shengHeight=parseInt((divHeight-imageHeightAfter)/2);
58                    $("#resourceImage").css("margin-top",shengHeight);
59                    shengWidth=0;
60                    suoCompare=parseFloat(realWidth)/parseFloat(divWidth);
61                 }else{
62                    shengWidth=parseInt((divWidth-imageWidthAfter)/2);
63                    $("#resourceImage").css("margin-left",shengWidth);
64                    shengHeight=0;
65                    suoCompare=parseFloat(realHeight)/parseFloat(divHeight);
66                 }
67               }                            
68             }
69                             
70             $("#resourceImage").show();
71             $(".jcrop-holder").remove();
72             console.info("shengHeight:"+shengHeight);
73             $("#resourceImage").width(imageWidthAfter);
74             $("#resourceImage").height(imageHeightAfter);
75             cutter.init(); 
76             $(".jcrop-holder").css("margin-top",shengHeight);
77             $(".jcrop-holder").css("margin-left",shengWidth);
78             $(".jcrop-holder img").css("margin-top","0px");
79             $(".jcrop-holder img").css("margin-left","0px");
80           });
81           $("#rechose").show();
82           $(".btsave").attr("disabled",false);
83           $(".btsave").addClass("btsaveclick");
84          }
85 };

 

    

3.图片的旋转

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

4.图片的裁剪

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

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

 1 $(".btsave").click(function(){
 2      var data = cutter.submit();
 3      var trueX=parseInt(parseFloat(data.x)*suoCompare);
 4      var trueY=parseInt(parseFloat(data.y)*suoCompare);
 5      var trueW=parseInt(parseFloat(data.w)*suoCompare);
 6      var trueH=parseInt(parseFloat(data.h)*suoCompare);
 7      var message=trueX+","+trueY+","+trueW+","+trueH+","+data.s;
 8      console.info(message);
 9      $("#cutHeader").val(message);
10      $("#formSubmit").submit();
11});

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

 1 @RequestMapping(value="cutImage")
 2     public ModelAndView cutImage(HttpServletRequest request, HttpServletResponse response) throws IOException {
 3         String message=request.getParameter("header");
 4         int x=Integer.parseInt(message.split(",")[0]);
 5         int y=Integer.parseInt(message.split(",")[1]);
 6         int w=Integer.parseInt(message.split(",")[2]);
 7         int h=Integer.parseInt(message.split(",")[3]);
 8         String src=message.split(",")[4].split("\\?")[0];
 9 //        
10         // 获取路径
11         String ctxPath = request.getSession().getServletContext().getRealPath("//") ;
12         System.out.println(ctxPath);
13         // 创建文件
14         src =ctxPath+"\\"+src;
15          String last=src.substring(src.lastIndexOf ("."));
16          File dirPath = new File(ctxPath+"\\header");
17             if (!dirPath.exists()) {
18                     dirPath.mkdir();
19             }
20         String dest = ctxPath+"\\header\\"+new SimpleDateFormat("yyyyMMddHHmmss").format(new Date())+last;
21         String headerurl="header/"+new SimpleDateFormat("yyyyMMddHHmmss").format(new Date())+last;
22         boolean isSuccess=cutImageHeader(src,dest,x,y,w,h);
23         if(isSuccess){
24             return new ModelAndView("showHeader","image",headerurl);
25         }else{
26             return new ModelAndView("changeHeader");
27         }
28         
29     }
30     
31     public static boolean cutImageHeader(String src,String dest,int x,int y,int w,int h) throws IOException{
32            String last=src.substring(src.lastIndexOf (".")+1);
33            System.out.println("*****"+last);
34            Iterator iterator = ImageIO.getImageReadersByFormatName(last);   
35            ImageReader reader = (ImageReader)iterator.next();   
36            InputStream in=new FileInputStream(src);  
37            ImageInputStream iis = ImageIO.createImageInputStream(in);   
38            reader.setInput(iis, true);   
39            ImageReadParam param = reader.getDefaultReadParam();   
40            Rectangle rect = new Rectangle(x, y, w,h);
41            System.out.println("x:"+x+";y:"+y+";w:"+w+";h:"+h);
42            param.setSourceRegion(rect);   
43            BufferedImage bi = reader.read(0,param);  
44            boolean isSuccess=ImageIO.write(bi, last, new File(dest));
45            return isSuccess;
46     }

 

 

 

  

posted @ 2015-04-20 08:49  就像你一样回不来  阅读(3343)  评论(13编辑  收藏  举报