plupload:Springmvc使用plupload上传头像,并自动剪切圆形


  最近在做的这个项目非常重视页面的用户效果体验,要求用户注册时上传的头像自动生成圆形,这样做一来是为了好看,二来是让公司安卓、iOS那边可以直接使用用户头像,不用剪切。该功能我使用了plupload上传插件,然后通过查阅资料写了一个圆形切割的Java工具类,下面记录一下完成的过程:

一、plupload的特性

  Plupload这个JavaScript控件可以让你选择Adobe Flash、Google Gears、HTML5、Microsoft Silverlight、Yahoo BrowserPlus或正常表单Form等多种方法进行文件上传。Plupload还提供其它功能包括:上传进度提醒、图片缩小、多文件上传,拖拽文件 到上传控件,文件类型过滤和Chunked上传等。这些功能在不同的上传方式中支持情况会受到限制。

二、plupload的使用

  1. 下载plupload:http://www.plupload.com/download;
  2. 统一写一个plupload的操作类PluploadUtil.java,注释写的很详细,代码如下:
      1 import java.io.BufferedOutputStream;
      2 import java.io.File;
      3 import java.io.FileOutputStream;
      4 import java.io.IOException;
      5 import java.io.InputStream;
      6 import java.io.OutputStream;
      7 import java.util.Iterator;
      8 import java.util.List;
      9 
     10 import org.springframework.util.MultiValueMap;
     11 import org.springframework.web.multipart.MultipartFile;
     12 import org.springframework.web.multipart.MultipartHttpServletRequest;
     13 
     14 import com.law.bean.Plupload;
     15 
     16 /**
     17  * Plupload是一个上传插件。
     18  * 上传原理为单个文件依次发送至服务器.
     19  * 上传打文件时可以将其碎片化上传。但是一般情况下,不会这样做,
     20  * 所以这里更多的是处理普通文件的批量上传。
     21  * 这里主要处理文件上传
     22  */
     23 public class PluploadUtil {
     24     private static final int BUF_SIZE = 2 * 1024;
     25     /**上传失败响应的成功状态码*/
     26     public static final String RESP_SUCCESS = "{\"jsonrpc\" : \"2.0\", \"result\" : \"success\", \"id\" : \"id\"}";
     27     /**上传失败响应的失败状态码*/
     28     public static final String RESP_ERROR = "{\"jsonrpc\" : \"2.0\", \"error\" : {\"code\": 101, \"message\": \"Failed to open input stream.\"}, \"id\" : \"id\"}";
     29     
     30     /**
     31      * 用于Plupload插件的文件上传,自动生成唯一的文件保存名
     32      * @param plupload - 存放上传所需参数的bean
     33      * @param dir - 保存目标文件目录
     34      * @throws IllegalStateException
     35      * @throws IOException
     36      */
     37     public static String upload(Plupload plupload, File dir) throws IllegalStateException, IOException {
     38         //生成唯一的文件名
     39         String filename = "" + System.currentTimeMillis() + plupload.getName();
     40         upload(plupload, dir, filename);
     41         return filename;
     42     }
     43     
     44     /**
     45      * 用于Plupload插件的文件上传
     46      * @param plupload - 存放上传所需参数的bean
     47      * @param dir - 保存目标文件目录
     48      * @param filename - 保存的文件名
     49      * @throws IllegalStateException
     50      * @throws IOException
     51      */
     52     public static void upload(Plupload plupload, File dir, String filename) throws IllegalStateException, IOException {
     53         int chunks = plupload.getChunks();    //获取总的碎片数
     54         int chunk = plupload.getChunk();    //获取当前碎片(从0开始计数)
     55         
     56         System.out.println(plupload.getMultipartFile() + "----------");
     57         
     58         MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) plupload.getRequest();    
     59         MultiValueMap<String, MultipartFile> map = multipartRequest.getMultiFileMap();
     60         
     61         if(map != null) {
     62             if (!dir.exists()) dir.mkdirs();    //如果目标文件夹不存在则创建新的文件夹
     63             
     64             //事实上迭代器中只存在一个值,所以只需要返回一个值即可
     65             Iterator<String> iter = map.keySet().iterator();
     66             while(iter.hasNext()) {
     67                 String str = (String) iter.next();
     68                 List<MultipartFile> fileList =  map.get(str);
     69                 for(MultipartFile multipartFile : fileList) {
     70                     //因为只存在一个值,所以最后返回的既是第一个也是最后一个值
     71                     plupload.setMultipartFile(multipartFile);
     72                     
     73                     //创建新目标文件
     74                     File targetFile = new File(dir.getPath()+ "/" + filename);
     75                     
     76                     //当chunks>1则说明当前传的文件为一块碎片,需要合并
     77                     if (chunks > 1) {
     78                         //需要创建临时文件名,最后再更改名称
     79                         File tempFile = new File(dir.getPath()+ "/" + multipartFile.getName());
     80                         //如果chunk==0,则代表第一块碎片,不需要合并
     81                         saveUploadFile(multipartFile.getInputStream(), tempFile, chunk == 0 ? false : true);
     82                         
     83                         //上传并合并完成,则将临时名称更改为指定名称
     84                         if (chunks - chunk == 1) {
     85                             tempFile.renameTo(targetFile);
     86                         }
     87                         
     88                     } else {
     89                         //否则直接将文件内容拷贝至新文件
     90                         multipartFile.transferTo(targetFile);
     91                     }
     92                 }
     93             }
     94         }
     95         
     96     }
     97     
     98     /**
     99      * 保存上传文件,兼合并功能
    100      */
    101     private static void saveUploadFile(InputStream input, File targetFile, boolean append) throws IOException {
    102         OutputStream out = null;
    103         try {
    104             if (targetFile.exists() && append) {
    105                 out = new BufferedOutputStream(new FileOutputStream(targetFile, true), BUF_SIZE);
    106             } else {
    107                 out = new BufferedOutputStream(new FileOutputStream(targetFile), BUF_SIZE);
    108             }
    109             
    110             byte[] buffer = new byte[BUF_SIZE];
    111             int len = 0;
    112             //写入文件
    113             while ((len = input.read(buffer)) > 0) {
    114                 out.write(buffer, 0, len);
    115             }
    116         } catch (IOException e) {
    117             throw e;
    118         } finally {
    119             //关闭输入输出流
    120             if (null != input) {
    121                 try {
    122                     input.close();
    123                 } catch (IOException e) {
    124                     e.printStackTrace();
    125                 }
    126             }
    127             if (null != out) {
    128                 try {
    129                     out.close();
    130                 } catch (IOException e) {
    131                     e.printStackTrace();
    132                 }
    133             }
    134         }
    135     }
    136     
    137     /**
    138      * 判断是否全部上传完成
    139      * 碎片需合并后才返回真
    140      */
    141     public static boolean isUploadFinish(Plupload plupload) {
    142         return (plupload.getChunks() - plupload.getChunk() == 1);
    143     }
    144 
    145 }
    View Code
  3. controller中的后台处理:
    /**上传处理方法*/
        @RequestMapping(value="/upload", method = RequestMethod.POST)
        @ResponseBody
        public Map<String,String> upload(Plupload plupload,HttpServletRequest request, HttpServletResponse response) {
            //输出信息
            System.out.println(plupload.getChunk() + "===" + plupload.getName() + "---" + plupload.getChunks());
            System.out.println("operate="+operate);
            String filename =null;
            plupload.setRequest(request);
            SimpleDateFormat sdf = new SimpleDateFormat("yyyyMM");
            String ymd = sdf.format(new Date());
            
            // 文件保存目录路径
            String savePath = plupload.getRequest().getSession().getServletContext().getRealPath("/") + FileDir;
            // 临时文件目录
            String tempPath = plupload.getRequest().getSession().getServletContext().getRealPath("/") + TempFileDir;
            Map<String,String> msg = new HashMap<String,String>();
                savePath += "photo/";
                tempPath += "photo/";
                 // 创建文件夹
                File dirFile = new File(savePath);
                if (!dirFile.exists())
                {
                    dirFile.mkdirs();
                }
                
                // 创建临时文件夹
                File dirTempFile = new File(tempPath);
                if (!dirTempFile.exists()){
                    dirTempFile.mkdirs();
                }
    
                System.out.println(dirFile.getPath());
                
                try {
                    //上传文件
                    filename= PluploadUtil.upload(plupload, dirFile);
                    //判断文件是否上传成功(被分成块的文件是否全部上传完成)
                    if (PluploadUtil.isUploadFinish(plupload)) {
                        System.out.println(plupload.getName() + "----");
                        System.out.println(savePath+"&&"+filename + "=====");
                        //限制图片大小不能小于300*300
                        BufferedImage bi = ImageIO.read(new FileInputStream(new File(savePath+filename)));
                        System.out.println("width="+bi.getWidth());
                        System.out.println("height="+bi.getHeight());
                        if(bi.getWidth()<200||bi.getHeight()<200){
                            msg.put("fail", "True");
                            msg.put("error","图片尺寸不符合要求,大于300*300");
                        }else{
                            //将图片等比压缩为300*300大小的图片
                            IconCompressUtil.compressImg(new File(savePath+filename), 300, new File(savePath+filename));    
                            msg.put("success", "True");
                            msg.put("filename", filename);
                            msg.put("filepath", savePath+"temp"+filename);
                            System.out.println("头像保存位置="+savePath+"temp"+filename);
                            msg.put("width", bi.getWidth()+"");
                            msg.put("height", bi.getHeight()+"");
                        }
                    }else{
                        msg.put("fail", "True");
                    }
                    
                } catch (IllegalStateException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            return msg;
        }
  4. jsp页面
    var uploader = new plupload.Uploader({
            // General settings
            runtimes : 'html5,flash,silverlight,html4',
            browse_button : 'pickfiles', // you can pass in id...
            multi_selection: false,
            container: document.getElementById('container'),
            url : '<%=path%>/upload.do?operate=1',
            chunk_size : '1mb',
            unique_names : true,
            // Resize images on client-side if we can
            //resize : { width : 320, height : 240, quality : 90 },
            filters : {
                max_file_size : '4mb',
    
                // Specify what files to browse for
                mime_types: [
                    {title : "图片文件", extensions : "jpg,jpeg,gif,png,bmp"}
                ]
            },
     
            flash_swf_url : '<%=path%>/plupload/js/Moxie.swf',
            silverlight_xap_url : '<%=path%>/plupload/js/Moxie.xap',
                init: {
                    FilesAdded: function (up, files) {
                        var count = uploader.files.length;
                        if (count > 1) {
                            art.dialog({ title: '操作提示', width: '24em', height: '10em', content: "一次最多只能上传一张图片", icon: 'error', yesFn: function () { return true; } });
                        } else if (count == 0) {
                            art.dialog({ title: '操作提示', width: '24em', height: '10em', content: "没有要上传的图片", icon: 'error', yesFn: function () { return true; } });
                        } else {
                            uploader.start();
                        }
                        return false;
    
                    },
                    UploadProgress: function (up, file) {
                        $("#uploadpercent").show().addClass("cutbox").addClass("cutpic").find("img").eq(0).show().attr("src", "<%=path%>/images/loading_upload.gif");
                        $("#fileName").val(file.name);
                        imgcontainer.hide();
                        },
                   
                    UploadComplete: function (up, file) {
                        if (uploader.total.failed > 0) {
                            //if (confirm("对不起," + uploader.total.failed + "个文件上传失败,是否重新上传?")) {
                                $("#uploadpercent").hide().removeClass("cutbox").removeClass("cutpic").find("img").eq(0).hide().attr("src", "");
                                alert("UploadComplete");
                                imgcontainer.show();
                                //uploader.start();
                            //} else {
                                uploader.splice();
                                uploader.refresh();
                            //}
                        }
                        else if (uploader.files.length == (uploader.total.uploaded + uploader.total.failed)) {
                            $("#uploadpercent").hide().removeClass("cutbox").removeClass("cutpic").find("img").eq(0).hide().attr("src", "");
                            imgcontainer.show();
                        }
                    },
                    FileUploaded: function (up, file, obj) {
                        var result = eval('(' + obj.response + ')');
                        if (result.success == "True") {
                            uploader.splice();
                            uploader.refresh();
                            $("#headVal").val(result.filename);
                            $("#filepath").val(result.filepath);
                            var imgurl = "<%=path%>/uploadfile/photo/" + result.filename;
                            var maxVal = 0;
                            if (result.width > result.height) {
                                maxVal = result.height;
                            }
                            else {
                               maxVal = result.width;
                            }
                            alert("maxVal="+maxVal);
                            alert("result.width="+result.width);
                            alert("result.height="+result.height);
                              
                            $("#maxVal").val(maxVal);
                            $("#owidth").val(result.width);//result.width
                            $("#oheight").val(result.height);//result.height
                            $("#oper").show();
                            imgcontainer.addClass("cutpic");
                            cutter.reload(imgurl,result.width,result.height);
                            alert("FileUploaded"); 
                        } else {
                        alert(result.error);
                            $("#uploadpercent").hide().removeClass("cutbox").removeClass("cutpic").find("img").eq(0).hide().attr("src", "");
                            imgcontainer.show();
                            uploader.total.failed += 1;
                            uploader.splice();
                            uploader.refresh();
                            art.dialog({ title: '操作提示', width: '24em', height: '10em', content: result.filename, icon: 'error', yesFn: function () { return true; } });
                        }
                    }
                }
            });
            uploader.init();

三、圆形切割类CircleCut.java:

public class CircleCut {
    String srcFile ;
    String newFile;
    
    public String getSrcFile() {
        return srcFile;
    }
    public void setSrcFile(String srcFile) {
        this.srcFile = srcFile;
    }
    public String getNewFile() {
        return newFile;
    }
    public void setNewFile(String newFile) {
        this.newFile = newFile;
    }

    public static boolean ImgCircleCut(String srcFile,String newFile){

        try {
            BufferedImage bi1 = ImageIO.read(new File(srcFile));

            // 根据需要是否使用 BufferedImage.TYPE_INT_ARGB
            BufferedImage bi2 = new BufferedImage(bi1.getWidth(), bi1.getHeight(),
                    BufferedImage.TYPE_INT_ARGB);

            Ellipse2D.Double shape = new Ellipse2D.Double(0, 0, bi1.getWidth(), bi1
                    .getHeight());

            Graphics2D g2 = bi2.createGraphics();
            AlphaComposite ac = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.9f);
            g2.setComposite(ac);
            g2.setBackground(new Color(22,2,2,0));
            //-1000000:在编写这个类时测试发现将此值设为极小值则可切出圆形
            g2.fill3DRect(-1000000, 200, 180, 80,false);
            //g2.fill(new Rectangle(bi2.getWidth(), bi2.getHeight()));
            g2.setClip(shape);
            // 使用 setRenderingHint 设置抗锯齿
            g2.drawImage(bi1, 0, 0, null);
            g2.dispose();
            ImageIO.write(bi2, "png", new File(newFile));
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            return false;
        }
        return true;        
    }    
}

四、由于以上圆形切割类CircleCut.java最好是剪切尺寸比例为1:1的图片,偏离这个比例则会成为椭圆。那么为解决这一问题,我们在上传头像后在后台使用图片处理工具类IconCompressUtil.java切割成固定1:1尺寸的图片,再调用CircleCut.java。以下为IconCompressUtil.java:

  1 public class IconCompressUtil {
  2     public String path = "";
  3 
  4     public IconCompressUtil(String path) {
  5         this.path = path;
  6     }
  7 
  8     public void change(int size) {
  9         compressImg(new File(path), size, null);
 10     }
 11 
 12     /**
 13      * @param oldfile
 14      * @param size
 15      * @param newfile
 16      * @return
 17      * @描述 —— 将oldfile的图片文件等比例压缩为size的newfile文件
 18      */
 19 
 20     public static File compressImg(File oldfile, int size, File newfile) {
 21         if(!newfile.exists())
 22             try {
 23                 newfile.createNewFile();
 24             } catch (IOException e1) {
 25                 // TODO Auto-generated catch block
 26                 //e1.printStackTrace();
 27                 System.out.println("无法创建文件!!!");
 28                 return null;
 29             }
 30         BufferedImage bi;
 31         try {
 32             System.out.println("正在压缩:" + oldfile.getName());
 33             bi = ImageIO.read(new FileInputStream(oldfile));
 34             int width = bi.getWidth();
 35             int height = bi.getHeight();
 36             if (width > size || height > size) {
 37                 Image image;
 38                 if (width > height) {
 39                     height = (int) (bi.getHeight() / (bi.getWidth() * 1d) * size);
 40                     image = bi.getScaledInstance(size, height,
 41                             Image.SCALE_DEFAULT);
 42                 } else {
 43                     width = (int) (bi.getWidth() / (bi.getHeight() * 1d) * size);
 44                     image = bi.getScaledInstance(width, size,
 45                             Image.SCALE_DEFAULT);
 46                 }
 47                 ImageIO.write(toBufferedImage(image), "png",
 48                         new FileOutputStream(newfile));
 49                 System.out.println("压缩完成:" + newfile.getName());
 50                 return newfile;
 51             } else {
 52                 System.out.println("无须压缩:" + oldfile.getName());
 53                 return oldfile;
 54             }
 55         } catch (Exception e) {
 56             e.printStackTrace();
 57         }
 58         return null;
 59     }
 60 
 61     public static BufferedImage toBufferedImage(Image image) {
 62         if (image instanceof BufferedImage) {
 63             return (BufferedImage) image;
 64         }
 65         image = new ImageIcon(image).getImage();
 66         BufferedImage bimage = null;
 67         GraphicsEnvironment ge = GraphicsEnvironment
 68                 .getLocalGraphicsEnvironment();
 69         try {
 70             int transparency = Transparency.TRANSLUCENT;
 71             GraphicsDevice gs = ge.getDefaultScreenDevice();
 72             GraphicsConfiguration gc = gs.getDefaultConfiguration();
 73             bimage = gc.createCompatibleImage(image.getWidth(null), image
 74                     .getHeight(null), transparency);
 75         } catch (HeadlessException e) {
 76         }
 77         if (bimage == null) {
 78             int type = BufferedImage.TYPE_INT_RGB;
 79             bimage = new BufferedImage(image.getWidth(null), image
 80                     .getHeight(null), type);
 81         }
 82         Graphics g = bimage.createGraphics();
 83         g.drawImage(image, 0, 0, null);
 84         g.dispose();
 85         return bimage;
 86     }
 87 
 88     /**
 89      * @return
 90      * @描述 —— 生成随机名字,不可能重复(用于文件的命名)
 91      */
 92     public static String getRandomName() {
 93         Random r = new Random();
 94         SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmssSSS");
 95         StringBuffer sb = new StringBuffer();
 96         sb.append(r.nextInt(100));
 97         sb.append(r.nextInt(100));
 98         sb.append("_");
 99         sb.append(sdf.format(new Date()));
100         sb.append("_");
101         sb.append(r.nextInt(100));
102         sb.append(r.nextInt(100));
103         return sb.toString();
104     }
105 
106     /**
107      * @param inputFile源文件
108      * @param outFile生成文件
109      * @param width指定宽度
110      * @param height指定高度
111      * @param proportion是否等比例操作
112      * @return
113      * @描述 —— 是否等比例缩放图片
114      */
115     public static boolean compressPic(String inputFile, String outFile,
116             int width, int height, boolean proportion) {
117         try {
118             // 获得源文件
119             File file = new File(inputFile);
120             if (!file.exists()) {
121                 return false;
122             }
123             Image img = ImageIO.read(file);
124             // 判断图片格式是否正确
125             if (img.getWidth(null) == -1) {
126                 return false;
127             } else {
128                 int newWidth;
129                 int newHeight;
130                 // 判断是否是等比缩放
131                 if (proportion == true) {
132                     // 为等比缩放计算输出的图片宽度及高度
133                     double rate1 = ((double) img.getWidth(null))
134                             / (double) width + 0.1;
135                     double rate2 = ((double) img.getHeight(null))
136                             / (double) height + 0.1;
137                     // 根据缩放比率大的进行缩放控制
138                     double rate = rate1 > rate2 ? rate1 : rate2;
139                     newWidth = (int) (((double) img.getWidth(null)) / rate);
140                     newHeight = (int) (((double) img.getHeight(null)) / rate);
141                 } else {
142                     newWidth = width; // 输出的图片宽度
143                     newHeight = height; // 输出的图片高度
144                 }
145 
146                 // 如果图片小于目标图片的宽和高则不进行转换
147                 /*
148                  * if (img.getWidth(null) < width && img.getHeight(null) <
149                  * height) { newWidth = img.getWidth(null); newHeight =
150                  * img.getHeight(null); }
151                  */
152                 BufferedImage tag = new BufferedImage((int) newWidth,
153                         (int) newHeight, BufferedImage.TYPE_INT_RGB);
154 
155                 // Image.SCALE_SMOOTH 的缩略算法 生成缩略图片的平滑度的,优先级比速度高 生成的图片质量比较好 但速度慢
156                 tag.getGraphics().drawImage(
157                         img.getScaledInstance(newWidth, newHeight,
158                                 Image.SCALE_SMOOTH), 0, 0, null);
159                 FileOutputStream out = new FileOutputStream(outFile);
160                 // JPEGImageEncoder可适用于其他图片类型的转换
161                 JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out);
162                 encoder.encode(tag);
163                 out.close();
164             }
165         } catch (IOException ex) {
166             ex.printStackTrace();
167         }
168         return true;
169     }
170 
171     /**
172      * @param srcFile源文件
173      * @param outFile输出文件
174      * @param x坐标
175      * @param y坐标
176      * @param width宽度
177      * @param height高度
178      * @return
179      * @描述 —— 裁剪图片
180      */
181     public static boolean cutPic(String srcFile, String outFile, int x, int y,
182             int width, int height) {
183         FileInputStream is = null;
184         ImageInputStream iis = null;
185         try {
186             // 如果源图片不存在
187             if (!new File(srcFile).exists()) {
188                 return false;
189             }
190 
191             // 读取图片文件
192             is = new FileInputStream(srcFile);
193 
194             // 获取文件格式
195             String ext = srcFile.substring(srcFile.lastIndexOf(".") + 1);
196 
197             // ImageReader声称能够解码指定格式
198             Iterator<ImageReader> it = ImageIO.getImageReadersByFormatName(ext);
199             ImageReader reader = it.next();
200 
201             // 获取图片流
202             iis = ImageIO.createImageInputStream(is);
203 
204             // 输入源中的图像将只按顺序读取
205             reader.setInput(iis, true);
206 
207             // 描述如何对流进行解码
208             ImageReadParam param = reader.getDefaultReadParam();
209 
210             // 图片裁剪区域
211             Rectangle rect = new Rectangle(x, y, width, height);
212 
213             // 提供一个 BufferedImage,将其用作解码像素数据的目标
214             param.setSourceRegion(rect);
215 
216             // 使用所提供的 ImageReadParam 读取通过索引 imageIndex 指定的对象
217             BufferedImage bi = reader.read(0, param);
218 
219             // 保存新图片
220             File tempOutFile = new File(outFile);
221             if (!tempOutFile.exists()) {
222                 tempOutFile.mkdirs();
223             }
224             ImageIO.write(bi, ext, new File(outFile));
225             return true;
226         } catch (Exception e) {
227             e.printStackTrace();
228             return false;
229         } finally {
230             try {
231                 if (is != null) {
232                     is.close();
233                 }
234                 if (iis != null) {
235                     iis.close();
236                 }
237             } catch (IOException e) {
238                 e.printStackTrace();
239                 return false;
240             }
241         }
242     }
243 
244     /**
245      * @param doubleValue
246      * @return
247      * @描述 —— 将浮点型数据保留整数位转换成int型
248      */
249     public static Integer getRoundIntFromDouble(Double doubleValue) {
250         return Integer.parseInt(String.valueOf(Math.round(doubleValue)));
251     }
252 }
View Code

注:1、jsp页面处理的代码中包含少量无关代码,我将继续修改成通用版。2、此功能第一步是使用Jcrop组件实现图片预览,具体如何使用的Jcrop我将写在下一篇文章里。


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM