• 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",100,height:100},{id:"smallImage",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     }

     

     

      

  • 相关阅读:
    ADO.NET_01_概述
    我对持久层的一点看法
    浅谈极限编程(XP)和代码重构(Refectoring)
    设计模式_第一篇_开场
    .NET OracleLob 读写操作
    ASP.NET_ASP.NET Cookies
    设计模式_第二篇_策略模式
    Oracle_C# 实现 Oracle Text(全文检索)的一个简单例子
    IKVM.NET_06_用户指南_教程
    ADO.NET_02_DataTable
  • 原文地址:https://www.cnblogs.com/robert-blue/p/4433935.html
Copyright © 2020-2023  润新知